mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:01:09 +02:00
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:
parent
9936a12fdd
commit
42c75bdc74
9 changed files with 326 additions and 0 deletions
10
apps/clock/apps/web/src/app.css
Normal file
10
apps/clock/apps/web/src/app.css
Normal 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";
|
||||
72
apps/clock/apps/web/src/lib/components/ToastContainer.svelte
Normal file
72
apps/clock/apps/web/src/lib/components/ToastContainer.svelte
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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';
|
||||
49
apps/clock/apps/web/src/lib/i18n/index.ts
Normal file
49
apps/clock/apps/web/src/lib/i18n/index.ts
Normal 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 };
|
||||
23
apps/clock/apps/web/src/lib/i18n/locales/de.json
Normal file
23
apps/clock/apps/web/src/lib/i18n/locales/de.json
Normal 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"
|
||||
}
|
||||
}
|
||||
23
apps/clock/apps/web/src/lib/i18n/locales/en.json
Normal file
23
apps/clock/apps/web/src/lib/i18n/locales/en.json
Normal 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"
|
||||
}
|
||||
}
|
||||
7
apps/clock/apps/web/src/lib/stores/theme.svelte.ts
Normal file
7
apps/clock/apps/web/src/lib/stores/theme.svelte.ts
Normal 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',
|
||||
});
|
||||
44
apps/clock/apps/web/src/lib/stores/toast.ts
Normal file
44
apps/clock/apps/web/src/lib/stores/toast.ts
Normal 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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue