diff --git a/apps/calendar/apps/backend/src/app.module.ts b/apps/calendar/apps/backend/src/app.module.ts index 4d159c594..6668f5985 100644 --- a/apps/calendar/apps/backend/src/app.module.ts +++ b/apps/calendar/apps/backend/src/app.module.ts @@ -65,7 +65,10 @@ import { HttpExceptionFilter } from './common/http-exception.filter'; provide: APP_FILTER, useClass: HttpExceptionFilter, }, - // ThrottlerGuard registered via ThrottlerModule — use @UseGuards(ThrottlerGuard) on controllers + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, ], }) export class AppModule {} diff --git a/apps/calendar/apps/web/scripts/generate-icons.mjs b/apps/calendar/apps/web/scripts/generate-icons.mjs new file mode 100644 index 000000000..1af7bd63f --- /dev/null +++ b/apps/calendar/apps/web/scripts/generate-icons.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * Generate PWA icons from SVG favicon + * Run: node scripts/generate-icons.mjs + * Requires: sharp (available in workspace) + */ + +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const staticDir = join(__dirname, '..', 'static'); + +const sizes = [ + { name: 'favicon.png', size: 32 }, + { name: 'pwa-192x192.png', size: 192 }, + { name: 'pwa-512x512.png', size: 512 }, + { name: 'apple-touch-icon.png', size: 180 }, +]; + +async function generateIcons() { + try { + const sharp = (await import('sharp')).default; + const svgPath = join(staticDir, 'favicon.svg'); + const svgBuffer = readFileSync(svgPath); + + for (const { name, size } of sizes) { + const outputPath = join(staticDir, name); + await sharp(svgBuffer).resize(size, size).png().toFile(outputPath); + console.log(`Generated: ${name} (${size}x${size})`); + } + + console.log('\nAll icons generated successfully!'); + } catch (error) { + if (error.code === 'ERR_MODULE_NOT_FOUND') { + console.error('Sharp is not installed. Run: pnpm add -D sharp'); + } else { + console.error('Error generating icons:', error); + } + process.exit(1); + } +} + +generateIcons(); diff --git a/apps/calendar/apps/web/src/app.html b/apps/calendar/apps/web/src/app.html index cff15d4b6..6cf6c7464 100644 --- a/apps/calendar/apps/web/src/app.html +++ b/apps/calendar/apps/web/src/app.html @@ -1,9 +1,25 @@ - + - - + + + + + + + + + + + + + + + + + + Calendar %sveltekit.head% diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/de.json b/apps/calendar/apps/web/src/lib/i18n/locales/de.json index d195ddbac..41c73fd83 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/de.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/de.json @@ -129,5 +129,9 @@ "a11y": { "createEventOn": "Termin erstellen am {date}", "slotTime": "{day} {time}" + }, + "error": { + "notFound": "Seite nicht gefunden", + "backToHome": "Zurück zur Startseite" } } diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/en.json b/apps/calendar/apps/web/src/lib/i18n/locales/en.json index 18a452fa9..ecf11e8ce 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/en.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/en.json @@ -129,5 +129,9 @@ "a11y": { "createEventOn": "Create event on {date}", "slotTime": "{day} {time}" + }, + "error": { + "notFound": "Page not found", + "backToHome": "Back to home" } } diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/es.json b/apps/calendar/apps/web/src/lib/i18n/locales/es.json index c8f3cd1be..803c66120 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/es.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/es.json @@ -97,5 +97,9 @@ "search": "Buscar", "error": "Error", "success": "Éxito" + }, + "error": { + "notFound": "Página no encontrada", + "backToHome": "Volver al inicio" } } diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/fr.json b/apps/calendar/apps/web/src/lib/i18n/locales/fr.json index 7a069d492..dea43a0bc 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/fr.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/fr.json @@ -97,5 +97,9 @@ "search": "Rechercher", "error": "Erreur", "success": "Succès" + }, + "error": { + "notFound": "Page non trouvée", + "backToHome": "Retour à l'accueil" } } diff --git a/apps/calendar/apps/web/src/lib/i18n/locales/it.json b/apps/calendar/apps/web/src/lib/i18n/locales/it.json index 68df0d73b..5fae7ff68 100644 --- a/apps/calendar/apps/web/src/lib/i18n/locales/it.json +++ b/apps/calendar/apps/web/src/lib/i18n/locales/it.json @@ -97,5 +97,9 @@ "search": "Cerca", "error": "Errore", "success": "Successo" + }, + "error": { + "notFound": "Pagina non trovata", + "backToHome": "Torna alla home" } } diff --git a/apps/calendar/apps/web/src/routes/+error.svelte b/apps/calendar/apps/web/src/routes/+error.svelte index d5a57d247..5b9e9cd2a 100644 --- a/apps/calendar/apps/web/src/routes/+error.svelte +++ b/apps/calendar/apps/web/src/routes/+error.svelte @@ -1,9 +1,10 @@

{$page.status}

-

{$page.error?.message || 'Seite nicht gefunden'}

- Zurück zur Startseite +

{$page.error?.message || $_('error.notFound')}

+ {$_('error.backToHome')}
diff --git a/apps/calendar/apps/web/src/routes/+layout.svelte b/apps/calendar/apps/web/src/routes/+layout.svelte index 72bb7bc61..f8d969b87 100644 --- a/apps/calendar/apps/web/src/routes/+layout.svelte +++ b/apps/calendar/apps/web/src/routes/+layout.svelte @@ -26,6 +26,19 @@ }); + + + + + + + {#if !appReady} {:else} diff --git a/apps/calendar/apps/web/static/apple-touch-icon.png b/apps/calendar/apps/web/static/apple-touch-icon.png new file mode 100644 index 000000000..4e86574d5 Binary files /dev/null and b/apps/calendar/apps/web/static/apple-touch-icon.png differ diff --git a/apps/calendar/apps/web/static/favicon.png b/apps/calendar/apps/web/static/favicon.png new file mode 100644 index 000000000..bd550906b Binary files /dev/null and b/apps/calendar/apps/web/static/favicon.png differ diff --git a/apps/calendar/apps/web/static/favicon.svg b/apps/calendar/apps/web/static/favicon.svg new file mode 100644 index 000000000..bb9d53400 --- /dev/null +++ b/apps/calendar/apps/web/static/favicon.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 24 + diff --git a/apps/calendar/apps/web/static/pwa-192x192.png b/apps/calendar/apps/web/static/pwa-192x192.png new file mode 100644 index 000000000..a0db9cb61 Binary files /dev/null and b/apps/calendar/apps/web/static/pwa-192x192.png differ diff --git a/apps/calendar/apps/web/static/pwa-512x512.png b/apps/calendar/apps/web/static/pwa-512x512.png new file mode 100644 index 000000000..d7e2f04b1 Binary files /dev/null and b/apps/calendar/apps/web/static/pwa-512x512.png differ diff --git a/apps/todo/apps/web/src/lib/i18n/locales/de.json b/apps/todo/apps/web/src/lib/i18n/locales/de.json index 44f98227e..233f24403 100644 --- a/apps/todo/apps/web/src/lib/i18n/locales/de.json +++ b/apps/todo/apps/web/src/lib/i18n/locales/de.json @@ -93,6 +93,10 @@ "loadProjects": "Projekte konnten nicht geladen werden", "loadLabels": "Labels konnten nicht geladen werden" }, + "error": { + "notFound": "Seite nicht gefunden", + "backToHome": "Zurück zur Startseite" + }, "success": { "taskCreated": "Aufgabe erstellt", "taskUpdated": "Aufgabe aktualisiert", diff --git a/apps/todo/apps/web/src/lib/i18n/locales/en.json b/apps/todo/apps/web/src/lib/i18n/locales/en.json index 3038a61f0..323f6792c 100644 --- a/apps/todo/apps/web/src/lib/i18n/locales/en.json +++ b/apps/todo/apps/web/src/lib/i18n/locales/en.json @@ -93,6 +93,10 @@ "loadProjects": "Failed to load projects", "loadLabels": "Failed to load labels" }, + "error": { + "notFound": "Page not found", + "backToHome": "Back to home" + }, "success": { "taskCreated": "Task created", "taskUpdated": "Task updated", diff --git a/apps/todo/apps/web/src/lib/i18n/locales/es.json b/apps/todo/apps/web/src/lib/i18n/locales/es.json index a65dc8cff..0206d5709 100644 --- a/apps/todo/apps/web/src/lib/i18n/locales/es.json +++ b/apps/todo/apps/web/src/lib/i18n/locales/es.json @@ -93,6 +93,10 @@ "loadProjects": "No se pudieron cargar los proyectos", "loadLabels": "No se pudieron cargar las etiquetas" }, + "error": { + "notFound": "Página no encontrada", + "backToHome": "Volver al inicio" + }, "success": { "taskCreated": "Tarea creada", "taskUpdated": "Tarea actualizada", diff --git a/apps/todo/apps/web/src/lib/i18n/locales/fr.json b/apps/todo/apps/web/src/lib/i18n/locales/fr.json index 41fabbe9e..f2ab22cdf 100644 --- a/apps/todo/apps/web/src/lib/i18n/locales/fr.json +++ b/apps/todo/apps/web/src/lib/i18n/locales/fr.json @@ -93,6 +93,10 @@ "loadProjects": "Impossible de charger les projets", "loadLabels": "Impossible de charger les labels" }, + "error": { + "notFound": "Page non trouvée", + "backToHome": "Retour à l'accueil" + }, "success": { "taskCreated": "Tâche créée", "taskUpdated": "Tâche mise à jour", diff --git a/apps/todo/apps/web/src/lib/i18n/locales/it.json b/apps/todo/apps/web/src/lib/i18n/locales/it.json index 5114f9f95..8101be387 100644 --- a/apps/todo/apps/web/src/lib/i18n/locales/it.json +++ b/apps/todo/apps/web/src/lib/i18n/locales/it.json @@ -93,6 +93,10 @@ "loadProjects": "Impossibile caricare i progetti", "loadLabels": "Impossibile caricare le etichette" }, + "error": { + "notFound": "Pagina non trovata", + "backToHome": "Torna alla home" + }, "success": { "taskCreated": "Attività creata", "taskUpdated": "Attività aggiornata", diff --git a/apps/todo/apps/web/src/routes/+error.svelte b/apps/todo/apps/web/src/routes/+error.svelte index b060de2f4..1b638d893 100644 --- a/apps/todo/apps/web/src/routes/+error.svelte +++ b/apps/todo/apps/web/src/routes/+error.svelte @@ -1,9 +1,10 @@

{$page.status}

-

{$page.error?.message || 'Seite nicht gefunden'}

- Zurück zur Startseite +

{$page.error?.message || $_('error.notFound')}

+ {$_('error.backToHome')}
diff --git a/apps/todo/apps/web/src/routes/+layout.svelte b/apps/todo/apps/web/src/routes/+layout.svelte index 2af09115b..8d50185a1 100644 --- a/apps/todo/apps/web/src/routes/+layout.svelte +++ b/apps/todo/apps/web/src/routes/+layout.svelte @@ -10,6 +10,7 @@ let { children } = $props(); let loading = $state(true); + let appReady = $derived(!loading && !$i18nLoading); onMount(async () => { @@ -23,6 +24,19 @@ }); + + + + + + + {#if !appReady} {:else}