release nouri 0.6.0 polish backup and pwa

This commit is contained in:
2026-04-12 17:46:18 +02:00
parent 9ff7a6d57c
commit 555fddab80
31 changed files with 1257 additions and 164 deletions
+6 -1
View File
@@ -44,7 +44,12 @@
<article class="item-card">
<div class="item-media">
{% if item.photo_filename %}
<img src="{{ url_for('uploaded_file', filename=item.photo_filename) }}" alt="{{ item.name }}">
<img
src="{{ image_url(item.photo_filename, 'md') }}"
srcset="{{ image_srcset(item.photo_filename) }}"
sizes="{{ image_sizes('grid') }}"
alt="{{ item.name }}"
loading="lazy">
{% else %}
<div class="placeholder-tile">{{ item.name[:1] }}</div>
{% endif %}
+16 -1
View File
@@ -5,7 +5,22 @@
<div class="auth-card">
<p class="eyebrow">Erster Start</p>
<h1>Den ersten Haushalt-Zugang anlegen</h1>
<p class="lead">Danach könnt ihr Nouri gemeinsam nutzen. Persönliche und gemeinsame Einträge lassen sich später ruhig auseinanderhalten.</p>
<p class="lead">Danach könnt ihr Nouri gemeinsam nutzen. Der erste Einstieg bleibt bewusst klein: Zugang anlegen, Einkaufstag festlegen, erste Lebensmittel sammeln und bei Bedarf später eine Tagesvorlage merken.</p>
<div class="setup-intro-grid">
<div class="setup-tip">
<strong>1. Ruhig starten</strong>
<p class="muted">Ein erster Haushalt und ein Admin-Zugang reichen für den Anfang völlig.</p>
</div>
<div class="setup-tip">
<strong>2. Alltag festhalten</strong>
<p class="muted">Später könnt ihr Lebensmittel, Mahlzeitenideen und Planungen gemeinsam nutzen.</p>
</div>
<div class="setup-tip">
<strong>3. Als App nutzen</strong>
<p class="muted">Auf dem iPhone lässt sich Nouri später über Safari zum Home-Bildschirm hinzufügen.</p>
</div>
</div>
<form method="post" class="stack-form">
{{ csrf_input() }}
+11 -9
View File
@@ -4,27 +4,29 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Nouri{% endblock %}</title>
<meta name="theme-color" content="#efab72">
<meta name="theme-color" content="#de9862">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Nouri">
<meta name="application-name" content="Nouri">
<meta name="csrf-token" content="{{ csrf_token_value }}">
<meta name="nouri-push-public-key" content="{{ push_public_key }}">
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='brand/favicon.svg') }}">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='brand/pwa-192.png') }}">
<link rel="icon" type="image/svg+xml" href="{{ asset_url('brand/favicon.svg') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset_url('brand/pwa-180.png') }}">
<link rel="manifest" href="{{ url_for('webmanifest') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<script defer src="{{ url_for('static', filename='js/theme.js') }}"></script>
<script defer src="{{ url_for('static', filename='js/planner.js') }}"></script>
<script defer src="{{ url_for('static', filename='js/ui.js') }}"></script>
<script defer src="{{ url_for('static', filename='js/pwa.js') }}"></script>
<link rel="stylesheet" href="{{ asset_url('css/styles.css') }}">
<script defer src="{{ asset_url('js/theme.js') }}"></script>
<script defer src="{{ asset_url('js/planner.js') }}"></script>
<script defer src="{{ asset_url('js/ui.js') }}"></script>
<script defer src="{{ asset_url('js/pwa.js') }}"></script>
</head>
<body class="{% if g.user %}has-mobile-nav{% endif %}">
<div class="page-shell">
<header class="site-header">
<a class="brand" href="{{ url_for('main.dashboard') }}">
<span class="brand-mark">
<img src="{{ url_for('static', filename='brand/nouri-icon.svg') }}" alt="">
<img src="{{ asset_url('brand/nouri-icon.svg') }}" alt="">
</span>
<span class="brand-copy">
<strong>Nouri</strong>
+17
View File
@@ -13,6 +13,23 @@
</div>
</section>
{% if setup_checklist %}
<section class="panel">
<div class="panel-head">
<h2>Gut anfangen</h2>
</div>
<div class="more-link-grid">
{% for step in setup_checklist %}
<a class="more-link-card" href="{{ step.url }}">
<strong>{{ step.title }}</strong>
<small>{{ step.text }}</small>
<span class="chip">{{ step.label }}</span>
</a>
{% endfor %}
</div>
</section>
{% endif %}
<section class="stats-grid">
<article class="stat-card">
<span>Zuhause</span>
+6 -1
View File
@@ -72,7 +72,12 @@
<article class="item-card compact">
<div class="item-media">
{% if item.photo_filename %}
<img src="{{ url_for('uploaded_file', filename=item.photo_filename) }}" alt="{{ item.name }}">
<img
src="{{ image_url(item.photo_filename, 'md') }}"
srcset="{{ image_srcset(item.photo_filename) }}"
sizes="{{ image_sizes('grid') }}"
alt="{{ item.name }}"
loading="lazy">
{% else %}
<div class="placeholder-tile">{{ item.name[:1] }}</div>
{% endif %}
+8 -1
View File
@@ -67,7 +67,12 @@
{% if item and item.photo_filename %}
<div class="inline-photo">
<img src="{{ url_for('uploaded_file', filename=item.photo_filename) }}" alt="{{ item.name }}">
<img
src="{{ image_url(item.photo_filename, 'lg') }}"
srcset="{{ image_srcset(item.photo_filename) }}"
sizes="{{ image_sizes('detail') }}"
alt="{{ item.name }}"
loading="lazy">
</div>
{% endif %}
@@ -97,10 +102,12 @@
placeholder="z. B. Reis, Banane, Joghurt"
data-filter-input
data-filter-target="#meal-components-list"
data-filter-limit="3"
>
</label>
<button class="secondary" type="submit" name="form_action" value="filter_foods">Suchen</button>
</div>
<p class="helper-text">Während der Suche zeigt Nouri nur die drei passendsten Lebensmittel, damit die Auswahl ruhig bleibt.</p>
{% if food_groups %}
<div class="stack-sections" id="meal-components-list">
{% for group in food_groups %}
+6 -1
View File
@@ -54,7 +54,12 @@
<article class="item-card">
<div class="item-media">
{% if item.photo_filename %}
<img src="{{ url_for('uploaded_file', filename=item.photo_filename) }}" alt="{{ item.name }}">
<img
src="{{ image_url(item.photo_filename, 'md') }}"
srcset="{{ image_srcset(item.photo_filename) }}"
sizes="{{ image_sizes('grid') }}"
alt="{{ item.name }}"
loading="lazy">
{% else %}
<div class="placeholder-tile">{{ item.name[:1] }}</div>
{% endif %}
+58 -8
View File
@@ -4,8 +4,8 @@
<section class="page-intro">
<div>
<p class="eyebrow">Optionen</p>
<h1>Ruhige Einstellungen für Alltag, Einkauf und Erinnerungen</h1>
<p class="lead">Hier lässt sich festlegen, wann Einkäufe vorbereitet werden, welche Hinweise hilfreich sind und ob Nouri sich wie eine App auf dem Home-Bildschirm verhalten soll.</p>
<h1>Ruhige Einstellungen für Alltag, Sicherung und iPhone-Nutzung</h1>
<p class="lead">Hier lässt sich festlegen, wann Einkäufe vorbereitet werden, welche Hinweise hilfreich sind und wie Nouri sich auf dem Home-Bildschirm oder beim Backup verhalten soll.</p>
</div>
</section>
@@ -33,23 +33,29 @@
Erinnerung ungefähr um
<input type="time" name="shopping_reminder_time" value="{{ household_settings.shopping_reminder_time }}">
</label>
<button type="submit">Speichern</button>
<div class="form-actions">
<button type="submit">Speichern</button>
</div>
</form>
</article>
<article class="panel">
<div class="panel-head">
<h2>Home-Bildschirm & Push</h2>
<h2>Für den Homescreen</h2>
</div>
<div class="stack-sections">
<div class="pwa-card">
<strong>Als Web-App nutzen</strong>
<p class="muted">Auf dem iPhone kannst du Nouri über Teilen → Zum Home-Bildschirm hinzufügen. Danach wirkt die App deutlich app-näher.</p>
<strong>Auf dem iPhone installieren</strong>
<p class="muted">Öffne Nouri in Safari, tippe auf Teilen und dann auf <em>Zum Home-Bildschirm</em>. Danach startet Nouri deutlich app-näher und ruhiger.</p>
</div>
<div class="pwa-card">
<strong>Offline etwas stabiler</strong>
<p class="muted">Die wichtigsten Oberflächen und Brand-Dateien bleiben lokal greifbar. Wenn das Netz kurz weg ist, wirkt die App dadurch stabiler und klarer.</p>
</div>
<div class="pwa-card">
<strong>Push-Mitteilungen</strong>
{% if push_ready %}
<p class="muted">Push ist vorbereitet. Du kannst es auf diesem Gerät freigeben und später testweise prüfen.</p>
<p class="muted">Push ist vorbereitet. Wenn du möchtest, kannst du es auf diesem Gerät freigeben und mit einer Test-Mitteilung prüfen.</p>
<div class="row-actions">
<button class="secondary" type="button" data-push-enable>Push erlauben</button>
<button class="ghost-button" type="button" data-push-disable>Push beenden</button>
@@ -61,7 +67,10 @@
</form>
<small class="helper-text">{{ push_subscription_count }} aktives Gerät{% if push_subscription_count != 1 %}e{% endif %}</small>
{% else %}
<p class="muted">Push wird sichtbar, sobald VAPID-Schlüssel für die App gesetzt sind.</p>
<p class="muted">Push wird sichtbar, sobald VAPID-Schlüssel für diese App gesetzt sind.</p>
{% if push_public_key_value %}
<small class="helper-text">Öffentlicher Schlüssel erkannt, privater Schlüssel fehlt noch.</small>
{% endif %}
{% endif %}
</div>
</div>
@@ -122,4 +131,45 @@
</div>
</form>
</section>
{% if is_admin() %}
<section class="two-column">
<article class="panel">
<div class="panel-head">
<h2>Backup exportieren</h2>
</div>
<div class="stack-sections">
<div class="pwa-card">
<strong>Komplettes App-Backup</strong>
<p class="muted">Das ZIP enthält Nutzer, Einstellungen, Lebensmittel, Mahlzeiten, Vorlagen, Planungen, Einkaufsdaten und hochgeladene Bilder.</p>
<a class="button" href="{{ url_for('main.backup_export') }}">Backup herunterladen</a>
</div>
</div>
</article>
<article class="panel">
<div class="panel-head">
<h2>Backup wiederherstellen</h2>
</div>
<form method="post" action="{{ url_for('main.backup_restore') }}" class="stack-form" enctype="multipart/form-data">
{{ csrf_input() }}
<div class="restore-warning">
<strong>Nur bewusst verwenden</strong>
<p class="muted">Die Wiederherstellung ersetzt den aktuellen Datenstand dieses Haushalts. Vorher am besten selbst noch ein frisches Backup herunterladen.</p>
</div>
<label>
Backup-Datei
<input type="file" name="backup_file" accept=".zip" required>
</label>
<label>
Zur Bestätigung bitte {{ restore_confirmation_text }} eintragen
<input type="text" name="restore_confirmation" placeholder="{{ restore_confirmation_text }}" required>
</label>
<div class="form-actions">
<button type="submit">Backup wiederherstellen</button>
</div>
</form>
</article>
</section>
{% endif %}
{% endblock %}