@svadmin/lite 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +49 -6
  2. package/package.json +6 -2
  3. package/src/components/LiteArrayField.svelte +112 -0
  4. package/src/components/LiteAuditLog.svelte +104 -0
  5. package/src/components/LiteBreadcrumbs.svelte +39 -0
  6. package/src/components/LiteChatDialog.svelte +101 -0
  7. package/src/components/LiteConfirmDialog.svelte +60 -0
  8. package/src/components/LiteEmptyState.svelte +39 -0
  9. package/src/components/LiteLayout.svelte +58 -10
  10. package/src/components/LitePermissionMatrix.svelte +147 -0
  11. package/src/components/LiteShow.svelte +3 -38
  12. package/src/components/LiteShowField.svelte +51 -0
  13. package/src/components/LiteStatsCard.svelte +70 -0
  14. package/src/components/LiteTabs.svelte +57 -0
  15. package/src/components/advanced/LiteAutoSaveIndicator.svelte +26 -0
  16. package/src/components/advanced/LiteDraggableHeader.svelte +33 -0
  17. package/src/components/advanced/LiteDrawerForm.svelte +42 -0
  18. package/src/components/advanced/LiteInlineEdit.svelte +32 -0
  19. package/src/components/advanced/LiteModalForm.svelte +44 -0
  20. package/src/components/advanced/LiteToast.svelte +34 -0
  21. package/src/components/advanced/LiteUndoableNotification.svelte +25 -0
  22. package/src/components/advanced/LiteVirtualTable.svelte +44 -0
  23. package/src/components/advanced/index.ts +8 -0
  24. package/src/components/buttons/LiteCloneButton.svelte +33 -0
  25. package/src/components/buttons/LiteCreateButton.svelte +31 -0
  26. package/src/components/buttons/LiteDeleteButton.svelte +57 -0
  27. package/src/components/buttons/LiteEditButton.svelte +33 -0
  28. package/src/components/buttons/LiteExportButton.svelte +31 -0
  29. package/src/components/buttons/LiteImportButton.svelte +50 -0
  30. package/src/components/buttons/LiteListButton.svelte +31 -0
  31. package/src/components/buttons/LiteRefreshButton.svelte +27 -0
  32. package/src/components/buttons/LiteSaveButton.svelte +27 -0
  33. package/src/components/buttons/LiteShowButton.svelte +33 -0
  34. package/src/components/buttons/index.ts +10 -0
  35. package/src/components/fields/LiteBooleanField.svelte +41 -0
  36. package/src/components/fields/LiteDateField.svelte +51 -0
  37. package/src/components/fields/LiteEmailField.svelte +40 -0
  38. package/src/components/fields/LiteFileField.svelte +53 -0
  39. package/src/components/fields/LiteImageField.svelte +57 -0
  40. package/src/components/fields/LiteJsonField.svelte +43 -0
  41. package/src/components/fields/LiteMarkdownField.svelte +33 -0
  42. package/src/components/fields/LiteMultiSelectField.svelte +57 -0
  43. package/src/components/fields/LiteNumberField.svelte +34 -0
  44. package/src/components/fields/LiteRelationField.svelte +66 -0
  45. package/src/components/fields/LiteRichTextField.svelte +45 -0
  46. package/src/components/fields/LiteSelectField.svelte +47 -0
  47. package/src/components/fields/LiteTagField.svelte +44 -0
  48. package/src/components/fields/LiteTextField.svelte +34 -0
  49. package/src/components/fields/LiteUrlField.svelte +40 -0
  50. package/src/components/fields/index.ts +15 -0
  51. package/src/components/layout/LiteAuthenticated.svelte +23 -0
  52. package/src/components/layout/LiteCanAccess.svelte +27 -0
  53. package/src/components/layout/LiteCatchAllNavigate.svelte +20 -0
  54. package/src/components/layout/LiteErrorBoundary.svelte +20 -0
  55. package/src/components/layout/LiteErrorComponent.svelte +37 -0
  56. package/src/components/layout/LiteHeader.svelte +19 -0
  57. package/src/components/layout/LiteNavigateToResource.svelte +25 -0
  58. package/src/components/layout/LiteSidebar.svelte +93 -0
  59. package/src/components/layout/index.ts +8 -0
  60. package/src/components/pages/LiteCreatePage.svelte +39 -0
  61. package/src/components/pages/LiteEditPage.svelte +54 -0
  62. package/src/components/pages/LiteForgotPasswordPage.svelte +60 -0
  63. package/src/components/pages/LiteListPage.svelte +77 -0
  64. package/src/components/pages/LiteProfilePage.svelte +61 -0
  65. package/src/components/pages/LiteRegisterPage.svelte +64 -0
  66. package/src/components/pages/LiteShowPage.svelte +61 -0
  67. package/src/components/pages/LiteUpdatePasswordPage.svelte +51 -0
  68. package/src/components/pages/index.ts +8 -0
  69. package/src/components/widgets/LiteAnomalyBadge.svelte +40 -0
  70. package/src/components/widgets/LiteBarChart.svelte +45 -0
  71. package/src/components/widgets/LiteInsightCard.svelte +28 -0
  72. package/src/components/widgets/LiteLineChart.svelte +48 -0
  73. package/src/components/widgets/LitePieChart.svelte +44 -0
  74. package/src/components/widgets/index.ts +5 -0
  75. package/src/index.ts +28 -0
  76. package/src/lite.css +372 -124
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+
4
+ interface Props {
5
+ field: FieldDefinition;
6
+ value?: unknown;
7
+ error?: string[];
8
+ mode?: 'show' | 'edit' | 'create';
9
+ }
10
+
11
+ let { field, value, error = [], mode = 'show' }: Props = $props();
12
+ const hasError = error.length > 0;
13
+ </script>
14
+
15
+ {#if mode === 'show'}
16
+ <pre style="white-space: pre-wrap; padding: 12px; background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 4px;">{String(value ?? '—')}</pre>
17
+ {:else}
18
+ <div>
19
+ <textarea
20
+ name={field.key}
21
+ id={field.key}
22
+ class="lite-input {hasError ? 'lite-input-error' : ''}"
23
+ style="min-height: 200px; font-family: monospace;"
24
+ placeholder="Enter markdown..."
25
+ {...field.required ? { required: true } : {}}
26
+ >{String(value ?? '')}</textarea>
27
+ {#if hasError}
28
+ {#each error as err}
29
+ <div class="lite-error-text">{err}</div>
30
+ {/each}
31
+ {/if}
32
+ </div>
33
+ {/if}
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+
4
+ interface Props {
5
+ field: FieldDefinition;
6
+ value?: unknown;
7
+ error?: string[];
8
+ mode?: 'show' | 'edit' | 'create';
9
+ }
10
+
11
+ let { field, value, error = [], mode = 'show' }: Props = $props();
12
+ const hasError = error.length > 0;
13
+
14
+ function displayOptions(valuesRaw: unknown): string[] {
15
+ if (!valuesRaw || !Array.isArray(valuesRaw)) return [];
16
+ return valuesRaw.map(v => {
17
+ const opt = field.options?.find(o => String(o.value) === String(v));
18
+ return opt?.label ?? String(v);
19
+ });
20
+ }
21
+
22
+ const valuesArray = $derived(Array.isArray(value) ? value.map(String) : []);
23
+ </script>
24
+
25
+ {#if mode === 'show'}
26
+ <div style="display:flex; gap: 4px; flex-wrap: wrap;">
27
+ {#each displayOptions(value) as opt}
28
+ <span class="lite-badge">{opt}</span>
29
+ {:else}
30
+ <span>—</span>
31
+ {/each}
32
+ </div>
33
+ {:else}
34
+ <div>
35
+ <select
36
+ name={field.key}
37
+ id={field.key}
38
+ multiple
39
+ class="lite-select {hasError ? 'lite-input-error' : ''}"
40
+ style="height: 120px;"
41
+ {...field.required ? { required: true } : {}}
42
+ >
43
+ {#if field.options}
44
+ {#each field.options as opt}
45
+ <option value={String(opt.value)} selected={valuesArray.includes(String(opt.value))}>
46
+ {opt.label}
47
+ </option>
48
+ {/each}
49
+ {/if}
50
+ </select>
51
+ {#if hasError}
52
+ {#each error as err}
53
+ <div class="lite-error-text">{err}</div>
54
+ {/each}
55
+ {/if}
56
+ </div>
57
+ {/if}
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+
4
+ interface Props {
5
+ field: FieldDefinition;
6
+ value?: unknown;
7
+ error?: string[];
8
+ mode?: 'show' | 'edit' | 'create';
9
+ }
10
+
11
+ let { field, value, error = [], mode = 'show' }: Props = $props();
12
+ const hasError = error.length > 0;
13
+ </script>
14
+
15
+ {#if mode === 'show'}
16
+ <span>{value == null ? '—' : Number(value).toLocaleString()}</span>
17
+ {:else}
18
+ <div>
19
+ <input
20
+ type="number"
21
+ name={field.key}
22
+ id={field.key}
23
+ value={value == null ? '' : Number(value)}
24
+ class="lite-input {hasError ? 'lite-input-error' : ''}"
25
+ placeholder={field.placeholder ?? field.label}
26
+ {...field.required ? { required: true } : {}}
27
+ />
28
+ {#if hasError}
29
+ {#each error as err}
30
+ <div class="lite-error-text">{err}</div>
31
+ {/each}
32
+ {/if}
33
+ </div>
34
+ {/if}
@@ -0,0 +1,66 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+ import { t } from '@svadmin/core/i18n';
4
+
5
+ interface Props {
6
+ field: FieldDefinition;
7
+ value?: unknown;
8
+ error?: string[];
9
+ mode?: 'show' | 'edit' | 'create';
10
+ }
11
+
12
+ let { field, value, error = [], mode = 'show' }: Props = $props();
13
+ const hasError = error.length > 0;
14
+
15
+ // In Lite version, relations might need to be rendered via server-side joined data
16
+ // passed through options, or just standard inputs requesting ID.
17
+ function displayRelation(v: unknown): string {
18
+ if (v == null) return '—';
19
+ if (typeof v === 'object' && v !== null && 'id' in v) {
20
+ // It's a populated relation object
21
+ const labelField = field.relation?.labelField || 'name';
22
+ return String((v as Record<string, unknown>)[labelField] || (v as Record<string, unknown>).id);
23
+ }
24
+ // Try options
25
+ const opt = field.options?.find(o => String(o.value) === String(v));
26
+ return opt?.label ?? String(v);
27
+ }
28
+ </script>
29
+
30
+ {#if mode === 'show'}
31
+ <span class="lite-badge">{displayRelation(value)}</span>
32
+ {:else}
33
+ <div>
34
+ {#if field.options && field.options.length > 0}
35
+ <!-- Render as select if options provided by loader -->
36
+ <select
37
+ name={field.key}
38
+ id={field.key}
39
+ class="lite-select {hasError ? 'lite-input-error' : ''}"
40
+ >
41
+ <option value="">-- {t('common.select') || 'Select'} Reference --</option>
42
+ {#each field.options as opt}
43
+ <option value={String(opt.value)} selected={String(value) === String(opt.value)}>
44
+ {opt.label}
45
+ </option>
46
+ {/each}
47
+ </select>
48
+ {:else}
49
+ <!-- Fallback text input for target ID -->
50
+ <input
51
+ type="text"
52
+ name={field.key}
53
+ id={field.key}
54
+ value={typeof value === 'object' && value ? String((value as Record<string, unknown>).id || '') : String(value ?? '')}
55
+ class="lite-input {hasError ? 'lite-input-error' : ''}"
56
+ placeholder="Enter reference ID"
57
+ {...field.required ? { required: true } : {}}
58
+ />
59
+ {/if}
60
+ {#if hasError}
61
+ {#each error as err}
62
+ <div class="lite-error-text">{err}</div>
63
+ {/each}
64
+ {/if}
65
+ </div>
66
+ {/if}
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+
4
+ interface Props {
5
+ field: FieldDefinition;
6
+ value?: unknown;
7
+ error?: string[];
8
+ mode?: 'show' | 'edit' | 'create';
9
+ }
10
+
11
+ let { field, value, error = [], mode = 'show' }: Props = $props();
12
+ let hasError = $derived(error.length > 0);
13
+
14
+ </script>
15
+
16
+ {#if mode === 'show'}
17
+ <!-- SSR fallback for rich text: render raw HTML if safe, or escaped.
18
+ We use standard HTML output for SSR. If it includes scripts,
19
+ backend should have sanitized it. -->
20
+ <div style="padding: 16px; border: 1px solid #e2e8f0; border-radius: 6px; max-height: 500px; overflow-y: auto; background: #fff;" class="prose">
21
+ {@html String(value ?? '—')}
22
+ </div>
23
+ {:else}
24
+ <div class="lite-markdown-editor">
25
+ <div style="padding: 8px 12px; border: 1px solid #cbd5e1; border-bottom: none; border-radius: 6px 6px 0 0; background: #f8fafc; color: #64748b; font-size: 12px; display: flex; gap: 12px; font-weight: 500;">
26
+ <span>Markdown is supported</span>
27
+ <span style="color: #94a3b8;">**bold**</span>
28
+ <span style="color: #94a3b8; font-style: italic;">*italic*</span>
29
+ <span style="color: #94a3b8;"># heading</span>
30
+ </div>
31
+ <textarea
32
+ name={field.key}
33
+ id={field.key}
34
+ class="lite-input {hasError ? 'lite-input-error' : ''}"
35
+ style="min-height: 280px; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; line-height: 1.6; padding: 16px; border-radius: 0 0 6px 6px; resize: vertical;"
36
+ placeholder="Wait... Rich Text Editor is disabled in lite mode.&#10;&#10;You can write standard HTML or use Markdown syntax here. Content will be safely rendered on the server."
37
+ {...field.required ? { required: true } : {}}
38
+ >{String(value ?? '')}</textarea>
39
+ {#if hasError}
40
+ {#each error as err}
41
+ <div class="lite-error-text">{err}</div>
42
+ {/each}
43
+ {/if}
44
+ </div>
45
+ {/if}
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+ import { t } from '@svadmin/core/i18n';
4
+
5
+ interface Props {
6
+ field: FieldDefinition;
7
+ value?: unknown;
8
+ error?: string[];
9
+ mode?: 'show' | 'edit' | 'create';
10
+ }
11
+
12
+ let { field, value, error = [], mode = 'show' }: Props = $props();
13
+ const hasError = error.length > 0;
14
+
15
+ function displaySelect(v: unknown): string {
16
+ if (v == null) return '—';
17
+ const opt = field.options?.find(o => String(o.value) === String(v));
18
+ return opt?.label ?? String(v);
19
+ }
20
+ </script>
21
+
22
+ {#if mode === 'show'}
23
+ <span class="lite-badge">{displaySelect(value)}</span>
24
+ {:else}
25
+ <div>
26
+ <select
27
+ name={field.key}
28
+ id={field.key}
29
+ class="lite-select {hasError ? 'lite-input-error' : ''}"
30
+ {...field.required ? { required: true } : {}}
31
+ >
32
+ <option value="">-- {t('common.select') || 'Select'} --</option>
33
+ {#if field.options}
34
+ {#each field.options as opt}
35
+ <option value={String(opt.value)} selected={String(value) === String(opt.value)}>
36
+ {opt.label}
37
+ </option>
38
+ {/each}
39
+ {/if}
40
+ </select>
41
+ {#if hasError}
42
+ {#each error as err}
43
+ <div class="lite-error-text">{err}</div>
44
+ {/each}
45
+ {/if}
46
+ </div>
47
+ {/if}
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+
4
+ interface Props {
5
+ field: FieldDefinition;
6
+ value?: unknown;
7
+ error?: string[];
8
+ mode?: 'show' | 'edit' | 'create';
9
+ }
10
+
11
+ let { field, value, error = [], mode = 'show' }: Props = $props();
12
+ const hasError = error.length > 0;
13
+ const tags = $derived(Array.isArray(value) ? value.map(String) : []);
14
+ </script>
15
+
16
+ {#if mode === 'show'}
17
+ <div style="display:flex; gap: 4px; flex-wrap: wrap;">
18
+ {#each tags as tag}
19
+ <span class="lite-badge">{tag}</span>
20
+ {:else}
21
+ <span>—</span>
22
+ {/each}
23
+ </div>
24
+ {:else}
25
+ <div>
26
+ <!-- For SSR without JS, we just accept a comma-separated string -->
27
+ <input
28
+ type="text"
29
+ name={field.key}
30
+ id={field.key}
31
+ value={tags.join(', ')}
32
+ class="lite-input {hasError ? 'lite-input-error' : ''}"
33
+ placeholder="Comma separated tags (e.g. admin, user, internal)"
34
+ />
35
+ <span style="font-size: 11px; color: #6b7280; display: block; margin-top: 4px;">
36
+ Separate tags with commas.
37
+ </span>
38
+ {#if hasError}
39
+ {#each error as err}
40
+ <div class="lite-error-text">{err}</div>
41
+ {/each}
42
+ {/if}
43
+ </div>
44
+ {/if}
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+
4
+ interface Props {
5
+ field: FieldDefinition;
6
+ value?: unknown;
7
+ error?: string[];
8
+ mode?: 'show' | 'edit' | 'create';
9
+ }
10
+
11
+ let { field, value, error = [], mode = 'show' }: Props = $props();
12
+ const hasError = error.length > 0;
13
+ </script>
14
+
15
+ {#if mode === 'show'}
16
+ <span>{value == null ? '—' : String(value)}</span>
17
+ {:else}
18
+ <div>
19
+ <input
20
+ type="text"
21
+ name={field.key}
22
+ id={field.key}
23
+ value={String(value ?? '')}
24
+ class="lite-input {hasError ? 'lite-input-error' : ''}"
25
+ placeholder={field.placeholder ?? field.label}
26
+ {...field.required ? { required: true } : {}}
27
+ />
28
+ {#if hasError}
29
+ {#each error as err}
30
+ <div class="lite-error-text">{err}</div>
31
+ {/each}
32
+ {/if}
33
+ </div>
34
+ {/if}
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import type { FieldDefinition } from '@svadmin/core';
3
+
4
+ interface Props {
5
+ field: FieldDefinition;
6
+ value?: unknown;
7
+ error?: string[];
8
+ mode?: 'show' | 'edit' | 'create';
9
+ }
10
+
11
+ let { field, value, error = [], mode = 'show' }: Props = $props();
12
+ const hasError = error.length > 0;
13
+ </script>
14
+
15
+ {#if mode === 'show'}
16
+ <span>
17
+ {#if value}
18
+ <a href="{value}" target="_blank" rel="noopener noreferrer">{value}</a>
19
+ {:else}
20
+
21
+ {/if}
22
+ </span>
23
+ {:else}
24
+ <div>
25
+ <input
26
+ type="url"
27
+ name={field.key}
28
+ id={field.key}
29
+ value={String(value ?? '')}
30
+ class="lite-input {hasError ? 'lite-input-error' : ''}"
31
+ placeholder={field.placeholder ?? field.label}
32
+ {...field.required ? { required: true } : {}}
33
+ />
34
+ {#if hasError}
35
+ {#each error as err}
36
+ <div class="lite-error-text">{err}</div>
37
+ {/each}
38
+ {/if}
39
+ </div>
40
+ {/if}
@@ -0,0 +1,15 @@
1
+ export { default as LiteTextField } from './LiteTextField.svelte';
2
+ export { default as LiteEmailField } from './LiteEmailField.svelte';
3
+ export { default as LiteUrlField } from './LiteUrlField.svelte';
4
+ export { default as LiteNumberField } from './LiteNumberField.svelte';
5
+ export { default as LiteDateField } from './LiteDateField.svelte';
6
+ export { default as LiteBooleanField } from './LiteBooleanField.svelte';
7
+ export { default as LiteSelectField } from './LiteSelectField.svelte';
8
+ export { default as LiteMultiSelectField } from './LiteMultiSelectField.svelte';
9
+ export { default as LiteTagField } from './LiteTagField.svelte';
10
+ export { default as LiteRelationField } from './LiteRelationField.svelte';
11
+ export { default as LiteImageField } from './LiteImageField.svelte';
12
+ export { default as LiteFileField } from './LiteFileField.svelte';
13
+ export { default as LiteRichTextField } from './LiteRichTextField.svelte';
14
+ export { default as LiteMarkdownField } from './LiteMarkdownField.svelte';
15
+ export { default as LiteJsonField } from './LiteJsonField.svelte';
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ /** True if server layout load confirms authentication */
6
+ isAuthenticated?: boolean;
7
+ loginUrl?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { isAuthenticated = false, loginUrl = '/lite/login', children }: Props = $props();
12
+ </script>
13
+
14
+ {#if isAuthenticated}
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ {:else}
19
+ <div style="padding: 40px; text-align: center; color: #475569;">
20
+ <h2>Authentication Required</h2>
21
+ <p>Please <a href={loginUrl} style="color: #2563eb;">login here</a> to access this page.</p>
22
+ </div>
23
+ {/if}
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ /** Whether the user has access (computed from server load function) */
6
+ allowed: boolean;
7
+ children?: Snippet;
8
+ fallback?: Snippet;
9
+ }
10
+
11
+ let { allowed, children, fallback }: Props = $props();
12
+ </script>
13
+
14
+ {#if allowed}
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ {:else}
19
+ {#if fallback}
20
+ {@render fallback()}
21
+ {:else}
22
+ <div style="padding: 24px; background: #fff1f2; color: #be123c; border-radius: 6px; border: 1px solid #fecdd3;">
23
+ <h3>Access Denied</h3>
24
+ <p>You do not have permission to view this content.</p>
25
+ </div>
26
+ {/if}
27
+ {/if}
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+
4
+ interface Props {
5
+ basePath?: string;
6
+ defaultResource?: string;
7
+ }
8
+
9
+ let { basePath = '/lite', defaultResource = '' }: Props = $props();
10
+ const target = $derived(`${basePath}${defaultResource ? '/' + defaultResource : ''}`);
11
+
12
+ onMount(() => {
13
+ window.location.href = target;
14
+ });
15
+ </script>
16
+
17
+ <noscript>
18
+ <meta http-equiv="refresh" content={`0; url=${target}`} />
19
+ </noscript>
20
+ <p>Redirecting...</p>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SSR Error Boundary.
4
+ * Since native Svelte <svelte:boundary> relies on CSR to catch JS exceptions,
5
+ * this component delegates error handling upstream to the SvelteKit +error.svelte mechanism.
6
+ * In CSR disabled environments, returning just the slot is standard practice.
7
+ */
8
+ import type { Snippet } from 'svelte';
9
+
10
+ interface Props {
11
+ children?: Snippet;
12
+ }
13
+
14
+ let { children }: Props = $props();
15
+ </script>
16
+
17
+ <!-- The error interception must be handled by the backend SSR framework (e.g. SvelteKit) -->
18
+ {#if children}
19
+ {@render children()}
20
+ {/if}
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import { AlertTriangle } from 'lucide-svelte';
4
+
5
+ interface Props {
6
+ title?: string;
7
+ message?: string;
8
+ code?: string | number;
9
+ homeUrl?: string;
10
+ }
11
+
12
+ let {
13
+ title = 'An error occurred',
14
+ message = 'Something went wrong while processing your request.',
15
+ code = 500,
16
+ homeUrl = '/lite',
17
+ }: Props = $props();
18
+ </script>
19
+
20
+ <div style="min-height: 50vh; display: flex; align-items: center; justify-content: center; padding: 20px;">
21
+ <div class="lite-card" style="max-width: 480px; width: 100%; text-align: center; padding: 40px;">
22
+ <AlertTriangle style="width: 48px; height: 48px; color: #ef4444; margin: 0 auto 16px;" />
23
+
24
+ <h1 style="font-size: 24px; font-weight: 600; color: #0f172a; margin: 0 0 8px;">
25
+ {#if code}
26
+ <span style="color: #ef4444; margin-right: 8px;">{code}</span>
27
+ {/if}
28
+ {title}
29
+ </h1>
30
+
31
+ <p style="color: #64748b; font-size: 15px; margin: 0 0 24px; line-height: 1.5;">{message}</p>
32
+
33
+ <a href={homeUrl} class="lite-btn lite-btn-primary" style="display: inline-block;">
34
+ {t('common.returnHome') || 'Return to Home'}
35
+ </a>
36
+ </div>
37
+ </div>
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ title?: string;
6
+ children?: Snippet;
7
+ }
8
+
9
+ let { title = '', children }: Props = $props();
10
+ </script>
11
+
12
+ <header class="lite-header" style="padding: 16px 24px; background: white; border-bottom: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center;">
13
+ <h2 style="margin: 0; font-size: 18px; font-weight: 600; color: #0f172a;">{title}</h2>
14
+ <div style="display: flex; gap: 12px; align-items: center;">
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ </div>
19
+ </header>
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SSR redirection helper component
4
+ * Normally redirection is done via +page.server.ts load() function in SvelteKit.
5
+ * This component will do a client-side fallback redirect if rendered.
6
+ */
7
+ import { onMount } from 'svelte';
8
+
9
+ interface Props {
10
+ resource: string;
11
+ basePath?: string;
12
+ }
13
+
14
+ let { resource, basePath = '/lite' }: Props = $props();
15
+
16
+ onMount(() => {
17
+ // Only fires if JS somehow runs, otherwise server should have redirected.
18
+ window.location.href = `${basePath}/${resource}`;
19
+ });
20
+ </script>
21
+
22
+ <noscript>
23
+ <meta http-equiv="refresh" content={`0; url=${basePath}/${resource}`} />
24
+ </noscript>
25
+ <p>Redirecting to {resource}...</p>