fix(clock-web): add missing source files for build

Add essential files that were never committed to git:
- app.css with Tailwind imports
- theme.svelte.ts store
- toast.ts store
- ToastContainer.svelte component
- AppLoadingSkeleton.svelte component
- i18n setup with de/en locales

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-01-23 00:30:10 +01:00
parent 9936a12fdd
commit 42c75bdc74
9 changed files with 326 additions and 0 deletions

View file

@ -0,0 +1,10 @@
@import "tailwindcss";
@import "@manacore/shared-tailwind/themes.css";
/* Scan shared packages for Tailwind classes */
@source "../../../../packages/shared-ui/src";
@source "../../../../packages/shared-auth-ui/src";
@source "../../../../packages/shared-branding/src";
@source "../../../../packages/shared-theme-ui/src";
@source "../../../../packages/shared-theme-ui/src/components";
@source "../../../../packages/shared-theme-ui/src/pages";

View file

@ -0,0 +1,72 @@
<script lang="ts">
import { toasts } from '$lib/stores/toast';
import type { Toast } from '$lib/stores/toast';
function getIcon(type: Toast['type']) {
switch (type) {
case 'success':
return '✓';
case 'error':
return '✕';
case 'warning':
return '⚠';
case 'info':
default:
return '';
}
}
function getColorClass(type: Toast['type']) {
switch (type) {
case 'success':
return 'bg-green-500';
case 'error':
return 'bg-red-500';
case 'warning':
return 'bg-yellow-500';
case 'info':
default:
return 'bg-blue-500';
}
}
</script>
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
{#each $toasts as toast (toast.id)}
<div
class="flex items-center gap-3 rounded-lg bg-card px-4 py-3 shadow-lg border border-border animate-in slide-in-from-right duration-200"
>
<span
class="{getColorClass(
toast.type
)} flex h-6 w-6 items-center justify-center rounded-full text-white text-sm"
>
{getIcon(toast.type)}
</span>
<span class="text-foreground">{toast.message}</span>
<button
onclick={() => toasts.remove(toast.id)}
class="ml-2 text-muted-foreground hover:text-foreground"
>
</button>
</div>
{/each}
</div>
<style>
@keyframes slide-in-from-right {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.animate-in {
animation: slide-in-from-right 0.2s ease-out;
}
</style>

View file

@ -0,0 +1,90 @@
<script lang="ts">
/**
* AppLoadingSkeleton - Full page loading skeleton for initial app load
* Shows a minimal skeleton layout while auth is being checked
*/
import { SkeletonBox } from '@manacore/shared-ui';
</script>
<div class="app-loading-skeleton" role="status" aria-label="App wird geladen...">
<!-- Header placeholder -->
<div class="header-skeleton">
<SkeletonBox width="120px" height="32px" borderRadius="8px" />
<div class="header-nav">
<SkeletonBox width="80px" height="32px" borderRadius="16px" />
<SkeletonBox width="80px" height="32px" borderRadius="16px" />
</div>
<SkeletonBox width="36px" height="36px" borderRadius="50%" />
</div>
<!-- Content placeholder - Clock specific -->
<div class="content-skeleton">
<!-- Clock display placeholder -->
<div class="clock-placeholder">
<SkeletonBox width="300px" height="300px" borderRadius="50%" />
</div>
<!-- Controls placeholder -->
<div class="controls-placeholder">
<SkeletonBox width="200px" height="48px" borderRadius="12px" />
</div>
</div>
</div>
<style>
.app-loading-skeleton {
min-height: 100vh;
background: hsl(var(--background));
}
.header-skeleton {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 2rem;
border-bottom: 1px solid hsl(var(--border));
}
.header-nav {
display: flex;
gap: 0.5rem;
}
.content-skeleton {
max-width: 80rem;
margin: 0 auto;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: calc(100vh - 80px);
gap: 2rem;
}
.clock-placeholder {
display: flex;
align-items: center;
justify-content: center;
}
.controls-placeholder {
display: flex;
gap: 1rem;
}
@media (max-width: 768px) {
.header-nav {
display: none;
}
.header-skeleton {
padding: 1rem;
}
.content-skeleton {
padding: 1rem;
}
}
</style>

View file

@ -0,0 +1,8 @@
/**
* Clock App Skeleton Components
*
* App-specific skeleton loaders for loading states.
*/
// App Loading Skeleton
export { default as AppLoadingSkeleton } from './AppLoadingSkeleton.svelte';

View file

@ -0,0 +1,49 @@
import { browser } from '$app/environment';
import { init, register, locale, waitLocale } from 'svelte-i18n';
// List of supported locales
export const supportedLocales = ['de', 'en'] as const;
export type SupportedLocale = (typeof supportedLocales)[number];
// Default locale
const defaultLocale = 'de';
// Register all available locales
register('de', () => import('./locales/de.json'));
register('en', () => import('./locales/en.json'));
// Get initial locale from browser or localStorage
function getInitialLocale(): SupportedLocale {
if (browser) {
// Check localStorage first
const stored = localStorage.getItem('clock_locale');
if (stored && supportedLocales.includes(stored as SupportedLocale)) {
return stored as SupportedLocale;
}
// Fall back to browser language
const browserLang = navigator.language.split('-')[0];
if (supportedLocales.includes(browserLang as SupportedLocale)) {
return browserLang as SupportedLocale;
}
}
return defaultLocale;
}
// Initialize i18n at module scope (required for SSR)
init({
fallbackLocale: defaultLocale,
initialLocale: getInitialLocale(),
});
// Set locale and persist to localStorage
export function setLocale(newLocale: SupportedLocale) {
locale.set(newLocale);
if (browser) {
localStorage.setItem('clock_locale', newLocale);
}
}
// Wait for locale to be loaded (useful for SSR)
export { waitLocale };

View file

@ -0,0 +1,23 @@
{
"app": {
"name": "Clock"
},
"common": {
"back": "Zurück",
"cancel": "Abbrechen",
"loading": "Lade..."
},
"nav": {
"home": "Startseite",
"settings": "Einstellungen"
},
"clock": {
"title": "Life Clock",
"remaining": "Verbleibende Zeit",
"elapsed": "Vergangene Zeit"
},
"messages": {
"saved": "Gespeichert",
"error": "Ein Fehler ist aufgetreten"
}
}

View file

@ -0,0 +1,23 @@
{
"app": {
"name": "Clock"
},
"common": {
"back": "Back",
"cancel": "Cancel",
"loading": "Loading..."
},
"nav": {
"home": "Home",
"settings": "Settings"
},
"clock": {
"title": "Life Clock",
"remaining": "Time remaining",
"elapsed": "Time elapsed"
},
"messages": {
"saved": "Saved",
"error": "An error occurred"
}
}

View file

@ -0,0 +1,7 @@
import { createThemeStore } from '@manacore/shared-theme';
// Create theme store with Clock's styling
export const theme = createThemeStore({
appId: 'clock',
defaultVariant: 'lume',
});

View file

@ -0,0 +1,44 @@
import { writable } from 'svelte/store';
export interface Toast {
id: string;
type: 'success' | 'error' | 'info' | 'warning';
message: string;
duration?: number;
}
function createToastStore() {
const { subscribe, update } = writable<Toast[]>([]);
function addToast(toast: Omit<Toast, 'id'>) {
const id = crypto.randomUUID();
const newToast = { ...toast, id };
update((toasts) => [...toasts, newToast]);
// Auto-remove after duration
const duration = toast.duration || 5000;
setTimeout(() => {
removeToast(id);
}, duration);
return id;
}
function removeToast(id: string) {
update((toasts) => toasts.filter((t) => t.id !== id));
}
return {
subscribe,
success: (message: string, duration?: number) =>
addToast({ type: 'success', message, duration }),
error: (message: string, duration?: number) => addToast({ type: 'error', message, duration }),
info: (message: string, duration?: number) => addToast({ type: 'info', message, duration }),
warning: (message: string, duration?: number) =>
addToast({ type: 'warning', message, duration }),
remove: removeToast,
};
}
export const toasts = createToastStore();