mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 02:01:10 +02:00
fix(calendar,todo): production readiness improvements
- Calendar: apply ThrottlerGuard globally (was registered but not used) - Calendar: localize error page with i18n (5 languages) - Calendar: add meta/OG tags and PWA meta improvements - Todo: localize error page with i18n (5 languages) - Todo: add meta/OG tags to root layout Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
52e9aa5889
commit
8bc52f4264
22 changed files with 172 additions and 8 deletions
|
|
@ -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 {}
|
||||
|
|
|
|||
45
apps/calendar/apps/web/scripts/generate-icons.mjs
Normal file
45
apps/calendar/apps/web/scripts/generate-icons.mjs
Normal file
|
|
@ -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();
|
||||
|
|
@ -1,9 +1,25 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="theme-color" content="#3b82f6" />
|
||||
<meta name="application-name" content="Calendar" />
|
||||
<meta name="description" content="Kalender und Terminverwaltung" />
|
||||
|
||||
<!-- PWA/iOS Meta Tags -->
|
||||
<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="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Calendar" />
|
||||
<link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="%sveltekit.assets%/favicon.png" />
|
||||
|
||||
<title>Calendar</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -129,5 +129,9 @@
|
|||
"a11y": {
|
||||
"createEventOn": "Termin erstellen am {date}",
|
||||
"slotTime": "{day} {time}"
|
||||
},
|
||||
"error": {
|
||||
"notFound": "Seite nicht gefunden",
|
||||
"backToHome": "Zurück zur Startseite"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,5 +129,9 @@
|
|||
"a11y": {
|
||||
"createEventOn": "Create event on {date}",
|
||||
"slotTime": "{day} {time}"
|
||||
},
|
||||
"error": {
|
||||
"notFound": "Page not found",
|
||||
"backToHome": "Back to home"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,5 +97,9 @@
|
|||
"search": "Buscar",
|
||||
"error": "Error",
|
||||
"success": "Éxito"
|
||||
},
|
||||
"error": {
|
||||
"notFound": "Página no encontrada",
|
||||
"backToHome": "Volver al inicio"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,5 +97,9 @@
|
|||
"search": "Rechercher",
|
||||
"error": "Erreur",
|
||||
"success": "Succès"
|
||||
},
|
||||
"error": {
|
||||
"notFound": "Page non trouvée",
|
||||
"backToHome": "Retour à l'accueil"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,5 +97,9 @@
|
|||
"search": "Cerca",
|
||||
"error": "Errore",
|
||||
"success": "Successo"
|
||||
},
|
||||
"error": {
|
||||
"notFound": "Pagina non trovata",
|
||||
"backToHome": "Torna alla home"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { _ } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-[60vh] flex-col items-center justify-center text-center">
|
||||
<h1 class="text-6xl font-bold text-blue-600 mb-4">{$page.status}</h1>
|
||||
<p class="text-xl text-muted-foreground mb-8">{$page.error?.message || 'Seite nicht gefunden'}</p>
|
||||
<a href="/" class="btn btn-primary">Zurück zur Startseite</a>
|
||||
<p class="text-xl text-muted-foreground mb-8">{$page.error?.message || $_('error.notFound')}</p>
|
||||
<a href="/" class="btn btn-primary">{$_('error.backToHome')}</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,19 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta
|
||||
name="description"
|
||||
content="Termine verwalten, Kalender teilen und den Überblick behalten mit Calendar von ManaCore."
|
||||
/>
|
||||
<meta property="og:title" content="Calendar - Kalender" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Termine verwalten, Kalender teilen und den Überblick behalten."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
</svelte:head>
|
||||
|
||||
{#if !appReady}
|
||||
<AppLoadingSkeleton />
|
||||
{:else}
|
||||
|
|
|
|||
BIN
apps/calendar/apps/web/static/apple-touch-icon.png
Normal file
BIN
apps/calendar/apps/web/static/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
BIN
apps/calendar/apps/web/static/favicon.png
Normal file
BIN
apps/calendar/apps/web/static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
31
apps/calendar/apps/web/static/favicon.svg
Normal file
31
apps/calendar/apps/web/static/favicon.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3b82f6"/>
|
||||
<stop offset="100%" style="stop-color:#2563eb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Rounded rectangle background -->
|
||||
<rect width="512" height="512" rx="96" ry="96" fill="url(#bg)"/>
|
||||
<!-- Calendar top bar -->
|
||||
<rect x="80" y="120" width="352" height="48" rx="8" fill="rgba(255,255,255,0.3)"/>
|
||||
<!-- Calendar rings -->
|
||||
<rect x="160" y="96" width="24" height="56" rx="12" fill="white"/>
|
||||
<rect x="328" y="96" width="24" height="56" rx="12" fill="white"/>
|
||||
<!-- Calendar body -->
|
||||
<rect x="80" y="168" width="352" height="248" rx="0 0 16 16" fill="rgba(255,255,255,0.15)"/>
|
||||
<!-- Grid lines horizontal -->
|
||||
<line x1="80" y1="230" x2="432" y2="230" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<line x1="80" y1="292" x2="432" y2="292" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<line x1="80" y1="354" x2="432" y2="354" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<!-- Grid lines vertical -->
|
||||
<line x1="130" y1="168" x2="130" y2="416" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<line x1="180" y1="168" x2="180" y2="416" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<line x1="230" y1="168" x2="230" y2="416" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<line x1="280" y1="168" x2="280" y2="416" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<line x1="330" y1="168" x2="330" y2="416" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<line x1="380" y1="168" x2="380" y2="416" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<!-- Today highlight -->
|
||||
<rect x="232" y="294" width="46" height="58" rx="8" fill="white" opacity="0.9"/>
|
||||
<text x="255" y="333" font-family="system-ui, -apple-system, sans-serif" font-size="32" font-weight="700" fill="#2563eb" text-anchor="middle">24</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
BIN
apps/calendar/apps/web/static/pwa-192x192.png
Normal file
BIN
apps/calendar/apps/web/static/pwa-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
BIN
apps/calendar/apps/web/static/pwa-512x512.png
Normal file
BIN
apps/calendar/apps/web/static/pwa-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { _ } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-[60vh] flex-col items-center justify-center text-center">
|
||||
<h1 class="text-6xl font-bold text-emerald-600 mb-4">{$page.status}</h1>
|
||||
<p class="text-xl text-muted-foreground mb-8">{$page.error?.message || 'Seite nicht gefunden'}</p>
|
||||
<a href="/" class="btn btn-primary">Zurück zur Startseite</a>
|
||||
<p class="text-xl text-muted-foreground mb-8">{$page.error?.message || $_('error.notFound')}</p>
|
||||
<a href="/" class="btn btn-primary">{$_('error.backToHome')}</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
let { children } = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
|
||||
let appReady = $derived(!loading && !$i18nLoading);
|
||||
|
||||
onMount(async () => {
|
||||
|
|
@ -23,6 +24,19 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta
|
||||
name="description"
|
||||
content="Aufgaben verwalten, Projekte organisieren und Produktivität steigern mit Todo von ManaCore."
|
||||
/>
|
||||
<meta property="og:title" content="Todo - Aufgabenverwaltung" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Aufgaben verwalten, Projekte organisieren und Produktivität steigern."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
</svelte:head>
|
||||
|
||||
{#if !appReady}
|
||||
<AppLoadingSkeleton layout="tasks" listItemCount={4} />
|
||||
{:else}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue