@podosoft/podokit 0.3.0 → 0.4.0
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.
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +20 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +3 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +43 -4
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
- package/dist/templates/fullstack-nest-svelte/package.json +11 -3
- package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
- package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/package.json +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +23 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +59 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +40 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +359 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +25 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +374 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +78 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +122 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +406 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +54 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +59 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +60 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +48 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +43 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +29 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +55 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +21 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +15 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +129 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +19 -0
- package/dist/templates/modules/admin-dashboard/module.manifest.json +41 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +70 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +29 -8
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +7 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +36 -21
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +2 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +10 -4
- package/dist/templates/modules/audit-log/module.manifest.json +30 -7
- package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +17 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +26 -3
- package/dist/templates/todo/apps/api/package.json +20 -2
- package/dist/templates/todo/apps/api/tsconfig.json +14 -5
- package/dist/templates/todo/apps/web/package.json +3 -2
- package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +43 -4
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
- package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/todo/dot-gitignore +5 -0
- package/dist/templates/todo/package.json +11 -3
- package/dist/templates/todo/tests/README.md +15 -0
- package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/todo/tests/package.json +14 -0
- package/dist/templates/todo/tests/playwright.config.ts +25 -0
- package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
- package/package.json +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Tooltip from "$lib/components/ui/tooltip/index.js";
|
|
3
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
4
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
5
|
+
import {
|
|
6
|
+
SIDEBAR_COOKIE_MAX_AGE,
|
|
7
|
+
SIDEBAR_COOKIE_NAME,
|
|
8
|
+
SIDEBAR_WIDTH,
|
|
9
|
+
SIDEBAR_WIDTH_ICON,
|
|
10
|
+
} from "./constants.js";
|
|
11
|
+
import { setSidebar } from "./context.svelte.js";
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
ref = $bindable(null),
|
|
15
|
+
open = $bindable(true),
|
|
16
|
+
onOpenChange = () => {},
|
|
17
|
+
class: className,
|
|
18
|
+
style,
|
|
19
|
+
children,
|
|
20
|
+
...restProps
|
|
21
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
|
22
|
+
open?: boolean;
|
|
23
|
+
onOpenChange?: (open: boolean) => void;
|
|
24
|
+
} = $props();
|
|
25
|
+
|
|
26
|
+
const sidebar = setSidebar({
|
|
27
|
+
open: () => open,
|
|
28
|
+
setOpen: (value: boolean) => {
|
|
29
|
+
open = value;
|
|
30
|
+
onOpenChange(value);
|
|
31
|
+
|
|
32
|
+
// This sets the cookie to keep the sidebar state.
|
|
33
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<svelte:window onkeydown={sidebar.handleShortcutKeydown} />
|
|
39
|
+
|
|
40
|
+
<Tooltip.Provider delayDuration={0}>
|
|
41
|
+
<div
|
|
42
|
+
data-slot="sidebar-wrapper"
|
|
43
|
+
style="--sidebar-width: {SIDEBAR_WIDTH}; --sidebar-width-icon: {SIDEBAR_WIDTH_ICON}; {style}"
|
|
44
|
+
class={cn(
|
|
45
|
+
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
bind:this={ref}
|
|
49
|
+
{...restProps}
|
|
50
|
+
>
|
|
51
|
+
{@render children?.()}
|
|
52
|
+
</div>
|
|
53
|
+
</Tooltip.Provider>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import { useSidebar } from "./context.svelte.js";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(null),
|
|
8
|
+
class: className,
|
|
9
|
+
children,
|
|
10
|
+
...restProps
|
|
11
|
+
}: WithElementRef<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> = $props();
|
|
12
|
+
|
|
13
|
+
const sidebar = useSidebar();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<button
|
|
17
|
+
bind:this={ref}
|
|
18
|
+
data-sidebar="rail"
|
|
19
|
+
data-slot="sidebar-rail"
|
|
20
|
+
aria-label="Toggle Sidebar"
|
|
21
|
+
tabindex={-1}
|
|
22
|
+
onclick={sidebar.toggle}
|
|
23
|
+
title="Toggle Sidebar"
|
|
24
|
+
class={cn(
|
|
25
|
+
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
|
|
26
|
+
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
|
27
|
+
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
|
28
|
+
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
|
|
29
|
+
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
|
30
|
+
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
{...restProps}
|
|
34
|
+
>
|
|
35
|
+
{@render children?.()}
|
|
36
|
+
</button>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Separator } from "$lib/components/ui/separator/index.js";
|
|
3
|
+
import { cn } from "$lib/utils.js";
|
|
4
|
+
import type { ComponentProps } from "svelte";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
ref = $bindable(null),
|
|
8
|
+
class: className,
|
|
9
|
+
...restProps
|
|
10
|
+
}: ComponentProps<typeof Separator> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<Separator
|
|
14
|
+
bind:ref
|
|
15
|
+
data-slot="sidebar-separator"
|
|
16
|
+
data-sidebar="separator"
|
|
17
|
+
class={cn("bg-sidebar-border mx-2 w-auto", className)}
|
|
18
|
+
{...restProps}
|
|
19
|
+
/>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from "$lib/components/ui/button/index.js";
|
|
3
|
+
import PanelLeftIcon from '@lucide/svelte/icons/panel-left';
|
|
4
|
+
import { cn } from "$lib/utils.js";
|
|
5
|
+
import type { ComponentProps } from "svelte";
|
|
6
|
+
import { useSidebar } from "./context.svelte.js";
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
ref = $bindable(null),
|
|
10
|
+
class: className,
|
|
11
|
+
onclick,
|
|
12
|
+
...restProps
|
|
13
|
+
}: ComponentProps<typeof Button> & {
|
|
14
|
+
onclick?: (e: MouseEvent) => void;
|
|
15
|
+
} = $props();
|
|
16
|
+
|
|
17
|
+
const sidebar = useSidebar();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Button
|
|
21
|
+
bind:ref
|
|
22
|
+
data-sidebar="trigger"
|
|
23
|
+
data-slot="sidebar-trigger"
|
|
24
|
+
variant="ghost"
|
|
25
|
+
size="icon-sm"
|
|
26
|
+
class={cn("cn-sidebar-trigger", className)}
|
|
27
|
+
type="button"
|
|
28
|
+
onclick={(e) => {
|
|
29
|
+
onclick?.(e);
|
|
30
|
+
sidebar.toggle();
|
|
31
|
+
}}
|
|
32
|
+
{...restProps}
|
|
33
|
+
>
|
|
34
|
+
<PanelLeftIcon />
|
|
35
|
+
<span class="sr-only">Toggle Sidebar</span>
|
|
36
|
+
</Button>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Sheet from "$lib/components/ui/sheet/index.js";
|
|
3
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
4
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
5
|
+
import { SIDEBAR_WIDTH_MOBILE } from "./constants.js";
|
|
6
|
+
import { useSidebar } from "./context.svelte.js";
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
ref = $bindable(null),
|
|
10
|
+
side = "left",
|
|
11
|
+
variant = "sidebar",
|
|
12
|
+
collapsible = "offcanvas",
|
|
13
|
+
class: className,
|
|
14
|
+
children,
|
|
15
|
+
...restProps
|
|
16
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
|
17
|
+
side?: "left" | "right";
|
|
18
|
+
variant?: "sidebar" | "floating" | "inset";
|
|
19
|
+
collapsible?: "offcanvas" | "icon" | "none";
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
const sidebar = useSidebar();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#if collapsible === "none"}
|
|
26
|
+
<div
|
|
27
|
+
class={cn(
|
|
28
|
+
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
bind:this={ref}
|
|
32
|
+
{...restProps}
|
|
33
|
+
>
|
|
34
|
+
{@render children?.()}
|
|
35
|
+
</div>
|
|
36
|
+
{:else if sidebar.isMobile}
|
|
37
|
+
<Sheet.Root
|
|
38
|
+
bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)}
|
|
39
|
+
{...restProps}
|
|
40
|
+
>
|
|
41
|
+
<Sheet.Content
|
|
42
|
+
bind:ref
|
|
43
|
+
data-sidebar="sidebar"
|
|
44
|
+
data-slot="sidebar"
|
|
45
|
+
data-mobile="true"
|
|
46
|
+
class={cn(
|
|
47
|
+
"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE};"
|
|
51
|
+
{side}
|
|
52
|
+
>
|
|
53
|
+
<Sheet.Header class="sr-only">
|
|
54
|
+
<Sheet.Title>Sidebar</Sheet.Title>
|
|
55
|
+
<Sheet.Description>Displays the mobile sidebar.</Sheet.Description>
|
|
56
|
+
</Sheet.Header>
|
|
57
|
+
<div class="flex h-full w-full flex-col">
|
|
58
|
+
{@render children?.()}
|
|
59
|
+
</div>
|
|
60
|
+
</Sheet.Content>
|
|
61
|
+
</Sheet.Root>
|
|
62
|
+
{:else}
|
|
63
|
+
<div
|
|
64
|
+
bind:this={ref}
|
|
65
|
+
class="text-sidebar-foreground group peer hidden md:block"
|
|
66
|
+
data-state={sidebar.state}
|
|
67
|
+
data-collapsible={sidebar.state === "collapsed" ? collapsible : ""}
|
|
68
|
+
data-variant={variant}
|
|
69
|
+
data-side={side}
|
|
70
|
+
data-slot="sidebar"
|
|
71
|
+
>
|
|
72
|
+
<!-- This is what handles the sidebar gap on desktop -->
|
|
73
|
+
<div
|
|
74
|
+
data-slot="sidebar-gap"
|
|
75
|
+
class={cn(
|
|
76
|
+
"transition-[width] duration-200 ease-linear relative w-(--sidebar-width) bg-transparent",
|
|
77
|
+
"group-data-[collapsible=offcanvas]:w-0",
|
|
78
|
+
"group-data-[side=right]:rotate-180",
|
|
79
|
+
variant === "floating" || variant === "inset"
|
|
80
|
+
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
|
81
|
+
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
|
|
82
|
+
)}
|
|
83
|
+
></div>
|
|
84
|
+
<div
|
|
85
|
+
data-slot="sidebar-container"
|
|
86
|
+
class={cn(
|
|
87
|
+
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
|
|
88
|
+
side === "left"
|
|
89
|
+
? "start-0 group-data-[collapsible=offcanvas]:start-[calc(var(--sidebar-width)*-1)]"
|
|
90
|
+
: "end-0 group-data-[collapsible=offcanvas]:end-[calc(var(--sidebar-width)*-1)]",
|
|
91
|
+
// Adjust the padding for floating and inset variants.
|
|
92
|
+
variant === "floating" || variant === "inset"
|
|
93
|
+
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
|
94
|
+
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-e group-data-[side=right]:border-s",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...restProps}
|
|
98
|
+
>
|
|
99
|
+
<div
|
|
100
|
+
data-sidebar="sidebar"
|
|
101
|
+
data-slot="sidebar-inner"
|
|
102
|
+
class="bg-sidebar group-data-[variant=floating]:ring-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 flex size-full flex-col"
|
|
103
|
+
>
|
|
104
|
+
{@render children?.()}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
{/if}
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Avatar from "$lib/components/ui/avatar";
|
|
3
|
+
import type { SessionUser } from "../../app.d.ts";
|
|
4
|
+
|
|
5
|
+
let { user, class: className }: { user: Pick<SessionUser, "name" | "email" | "image">; class?: string } = $props();
|
|
6
|
+
const initials = $derived((user.name || user.email || "?").slice(0, 2).toUpperCase());
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<Avatar.Root class={className}>
|
|
10
|
+
{#if user.image}<Avatar.Image src={user.image} alt={user.name} />{/if}
|
|
11
|
+
<Avatar.Fallback>{initials}</Avatar.Fallback>
|
|
12
|
+
</Avatar.Root>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getContext, setContext } from "svelte";
|
|
2
|
+
import type { Locale, Messages } from "./messages";
|
|
3
|
+
|
|
4
|
+
const KEY = Symbol("i18n");
|
|
5
|
+
|
|
6
|
+
export type I18nContext = { readonly t: Messages; readonly locale: Locale };
|
|
7
|
+
|
|
8
|
+
export function setI18nContext(ctx: I18nContext): void {
|
|
9
|
+
setContext(KEY, ctx);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getI18n(): I18nContext {
|
|
13
|
+
return getContext(KEY) as I18nContext;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Fill {name}-style placeholders in a message.
|
|
17
|
+
export function fmt(template: string, values: Record<string, string | number>): string {
|
|
18
|
+
return template.replace(/\{(\w+)\}/g, (match, key: string) =>
|
|
19
|
+
key in values ? String(values[key]) : match,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Render a timestamp in the viewer's own locale and timezone (browser defaults),
|
|
24
|
+
// so dates always match the user's regional settings. Used across all pages.
|
|
25
|
+
export function formatDateTime(value: string | Date): string {
|
|
26
|
+
return new Date(value).toLocaleString();
|
|
27
|
+
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// Static message catalogs for the admin dashboard. Default locale is English.
|
|
2
|
+
export const LOCALES = ["en", "ko"] as const;
|
|
3
|
+
export type Locale = (typeof LOCALES)[number];
|
|
4
|
+
export const defaultLocale: Locale = "en";
|
|
5
|
+
|
|
6
|
+
export const localeNames: Record<Locale, string> = {
|
|
7
|
+
en: "English",
|
|
8
|
+
ko: "한국어",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const en = {
|
|
12
|
+
common: { appName: "Admin" },
|
|
13
|
+
landing: { title: "Welcome", subtitle: "This is your app's home page. Replace it with your own content — the admin area lives under /admin.", openAdmin: "Open admin" },
|
|
14
|
+
language: { label: "Language" },
|
|
15
|
+
theme: { toggle: "Toggle theme", light: "Light", dark: "Dark", system: "System" },
|
|
16
|
+
auth: {
|
|
17
|
+
signInTitle: "Sign in",
|
|
18
|
+
signInDesc: "Enter your credentials to continue.",
|
|
19
|
+
email: "Email",
|
|
20
|
+
password: "Password",
|
|
21
|
+
forgot: "Forgot?",
|
|
22
|
+
signIn: "Sign in",
|
|
23
|
+
signingIn: "Signing in…",
|
|
24
|
+
noAccount: "No account?",
|
|
25
|
+
signUp: "Sign up",
|
|
26
|
+
signInFailed: "Sign in failed",
|
|
27
|
+
createTitle: "Create account",
|
|
28
|
+
createDesc: "Sign up to get started.",
|
|
29
|
+
name: "Name",
|
|
30
|
+
create: "Create account",
|
|
31
|
+
creating: "Creating…",
|
|
32
|
+
haveAccount: "Have an account?",
|
|
33
|
+
signUpFailed: "Sign up failed",
|
|
34
|
+
resetTitle: "Reset password",
|
|
35
|
+
resetDesc: "We'll send a reset link to your email.",
|
|
36
|
+
sendReset: "Send reset link",
|
|
37
|
+
sending: "Sending…",
|
|
38
|
+
resetSent: "If that email exists, a reset link is on its way.",
|
|
39
|
+
backToSignIn: "Back to sign in",
|
|
40
|
+
requestFailed: "Request failed",
|
|
41
|
+
newTitle: "Set a new password",
|
|
42
|
+
newPassword: "New password",
|
|
43
|
+
updatePassword: "Update password",
|
|
44
|
+
updating: "Updating…",
|
|
45
|
+
missingToken: "Missing or invalid reset token.",
|
|
46
|
+
resetFailed: "Reset failed",
|
|
47
|
+
},
|
|
48
|
+
nav: { overview: "Overview", users: "Users", sessions: "Sessions", audit: "Audit log", account: "Account", signOut: "Sign out" },
|
|
49
|
+
dashboard: { title: "Dashboard", welcome: "Welcome back, {name}.", yourRole: "Your role", signedInAs: "Signed in as" },
|
|
50
|
+
audit: { title: "Audit log", when: "When", actor: "Actor", action: "Action", target: "Target", ip: "IP", empty: "No audit entries.", loadFailed: "Failed to load audit log", total: "{count} entries" },
|
|
51
|
+
users: {
|
|
52
|
+
title: "Users",
|
|
53
|
+
search: "Search by email…",
|
|
54
|
+
name: "Name",
|
|
55
|
+
email: "Email",
|
|
56
|
+
role: "Role",
|
|
57
|
+
status: "Status",
|
|
58
|
+
active: "Active",
|
|
59
|
+
banned: "Banned",
|
|
60
|
+
makeUser: "Make user",
|
|
61
|
+
makeAdmin: "Make admin",
|
|
62
|
+
ban: "Ban",
|
|
63
|
+
unban: "Unban",
|
|
64
|
+
empty: "No users.",
|
|
65
|
+
total: "{count} users",
|
|
66
|
+
previous: "Previous",
|
|
67
|
+
next: "Next",
|
|
68
|
+
roleSet: "Role set to {role}",
|
|
69
|
+
userBanned: "User banned",
|
|
70
|
+
userUnbanned: "User unbanned",
|
|
71
|
+
loadFailed: "Failed to load users",
|
|
72
|
+
actionFailed: "Action failed",
|
|
73
|
+
addUser: "Add user",
|
|
74
|
+
createTitle: "Create user",
|
|
75
|
+
password: "Password",
|
|
76
|
+
create: "Create",
|
|
77
|
+
cancel: "Cancel",
|
|
78
|
+
userCreated: "User created",
|
|
79
|
+
setPassword: "Set password",
|
|
80
|
+
newPassword: "New password",
|
|
81
|
+
save: "Save",
|
|
82
|
+
passwordSet: "Password updated",
|
|
83
|
+
delete: "Delete",
|
|
84
|
+
deleteTitle: "Delete user",
|
|
85
|
+
deleteConfirm: "Delete {email}? This cannot be undone.",
|
|
86
|
+
userDeleted: "User deleted",
|
|
87
|
+
confirmPassword: "Confirm password",
|
|
88
|
+
confirmNewPassword: "Confirm new password",
|
|
89
|
+
passwordMismatch: "Passwords do not match",
|
|
90
|
+
edit: "Edit",
|
|
91
|
+
editTitle: "Edit user",
|
|
92
|
+
userUpdated: "User updated",
|
|
93
|
+
impersonate: "Impersonate",
|
|
94
|
+
banTitle: "Ban user",
|
|
95
|
+
banReason: "Reason (optional)",
|
|
96
|
+
banExpiryDays: "Expires in (days, optional)",
|
|
97
|
+
stopImpersonating: "Stop impersonating",
|
|
98
|
+
impersonatingAs: "Impersonating {email}",
|
|
99
|
+
manage: "Manage",
|
|
100
|
+
revokeSession: "Revoke",
|
|
101
|
+
},
|
|
102
|
+
sessions: {
|
|
103
|
+
title: "Active sessions",
|
|
104
|
+
device: "Device",
|
|
105
|
+
ip: "IP",
|
|
106
|
+
since: "Since",
|
|
107
|
+
revoke: "Revoke",
|
|
108
|
+
unknown: "Unknown",
|
|
109
|
+
empty: "No active sessions.",
|
|
110
|
+
revoked: "Session revoked",
|
|
111
|
+
loadFailed: "Failed to load sessions",
|
|
112
|
+
revokeFailed: "Failed to revoke",
|
|
113
|
+
status: "Status",
|
|
114
|
+
current: "Current",
|
|
115
|
+
signOutOthers: "Sign out other sessions",
|
|
116
|
+
othersRevoked: "Signed out other sessions",
|
|
117
|
+
total: "{count} sessions",
|
|
118
|
+
},
|
|
119
|
+
adminSessions: {
|
|
120
|
+
title: "User sessions",
|
|
121
|
+
user: "User",
|
|
122
|
+
device: "Device",
|
|
123
|
+
ip: "IP",
|
|
124
|
+
since: "Since",
|
|
125
|
+
revoke: "Revoke session",
|
|
126
|
+
revokeAll: "Revoke all for user",
|
|
127
|
+
unknown: "Unknown",
|
|
128
|
+
empty: "No active sessions.",
|
|
129
|
+
revoked: "Session revoked",
|
|
130
|
+
allRevoked: "All sessions revoked for user",
|
|
131
|
+
loadFailed: "Failed to load sessions",
|
|
132
|
+
total: "{count} sessions",
|
|
133
|
+
},
|
|
134
|
+
account: {
|
|
135
|
+
title: "Account",
|
|
136
|
+
profile: "Profile",
|
|
137
|
+
name: "Name",
|
|
138
|
+
email: "Email",
|
|
139
|
+
role: "Role",
|
|
140
|
+
changePassword: "Change password",
|
|
141
|
+
currentPassword: "Current password",
|
|
142
|
+
newPassword: "New password",
|
|
143
|
+
update: "Update password",
|
|
144
|
+
updating: "Updating…",
|
|
145
|
+
changed: "Password changed",
|
|
146
|
+
changeFailed: "Failed to change password",
|
|
147
|
+
nav: { profile: "Profile", security: "Security", connected: "Connected", sessions: "Sessions", danger: "Danger zone" },
|
|
148
|
+
twoFactorTitle: "Two-factor authentication",
|
|
149
|
+
twoFactorDesc: "Add a one-time code from your authenticator app at sign-in.",
|
|
150
|
+
twoFactorOn: "Enabled",
|
|
151
|
+
twoFactorOff: "Disabled",
|
|
152
|
+
enable: "Enable",
|
|
153
|
+
disable: "Disable",
|
|
154
|
+
password: "Password",
|
|
155
|
+
scanHint: "Add this to your authenticator app, then enter the 6-digit code.",
|
|
156
|
+
setupKey: "Setup key (otpauth URI)",
|
|
157
|
+
backupCodes: "Backup codes — store them somewhere safe",
|
|
158
|
+
code: "Verification code",
|
|
159
|
+
verify: "Verify",
|
|
160
|
+
twoFactorEnabled: "Two-factor enabled",
|
|
161
|
+
twoFactorDisabled: "Two-factor disabled",
|
|
162
|
+
connectedTitle: "Connected accounts",
|
|
163
|
+
connectedDesc: "Link social accounts to sign in with them.",
|
|
164
|
+
connect: "Connect",
|
|
165
|
+
disconnect: "Disconnect",
|
|
166
|
+
connected: "Connected",
|
|
167
|
+
notConnected: "Not connected",
|
|
168
|
+
disconnected: "Disconnected",
|
|
169
|
+
dangerTitle: "Delete account",
|
|
170
|
+
dangerDesc: "Permanently delete your account and all associated data.",
|
|
171
|
+
deleteAccount: "Delete account",
|
|
172
|
+
deleteAccountConfirm: "This permanently deletes your account and cannot be undone. Enter your password to confirm.",
|
|
173
|
+
accountDeleted: "Account deleted",
|
|
174
|
+
save: "Save changes",
|
|
175
|
+
saved: "Profile updated",
|
|
176
|
+
saveFailed: "Failed to save profile",
|
|
177
|
+
verified: "Verified",
|
|
178
|
+
unverified: "Unverified",
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export type Messages = typeof en;
|
|
183
|
+
|
|
184
|
+
const ko: Messages = {
|
|
185
|
+
common: { appName: "관리자" },
|
|
186
|
+
landing: { title: "환영합니다", subtitle: "앱의 홈 페이지입니다. 원하는 내용으로 교체하세요 — 관리자 기능은 /admin 에 있습니다.", openAdmin: "관리자 열기" },
|
|
187
|
+
language: { label: "언어" },
|
|
188
|
+
theme: { toggle: "테마 전환", light: "라이트", dark: "다크", system: "시스템" },
|
|
189
|
+
auth: {
|
|
190
|
+
signInTitle: "로그인",
|
|
191
|
+
signInDesc: "계속하려면 로그인하세요.",
|
|
192
|
+
email: "이메일",
|
|
193
|
+
password: "비밀번호",
|
|
194
|
+
forgot: "비밀번호 찾기",
|
|
195
|
+
signIn: "로그인",
|
|
196
|
+
signingIn: "로그인 중…",
|
|
197
|
+
noAccount: "계정이 없으신가요?",
|
|
198
|
+
signUp: "회원가입",
|
|
199
|
+
signInFailed: "로그인에 실패했습니다",
|
|
200
|
+
createTitle: "회원가입",
|
|
201
|
+
createDesc: "가입하고 시작하세요.",
|
|
202
|
+
name: "이름",
|
|
203
|
+
create: "계정 만들기",
|
|
204
|
+
creating: "생성 중…",
|
|
205
|
+
haveAccount: "이미 계정이 있으신가요?",
|
|
206
|
+
signUpFailed: "회원가입에 실패했습니다",
|
|
207
|
+
resetTitle: "비밀번호 재설정",
|
|
208
|
+
resetDesc: "이메일로 재설정 링크를 보내드립니다.",
|
|
209
|
+
sendReset: "재설정 링크 보내기",
|
|
210
|
+
sending: "전송 중…",
|
|
211
|
+
resetSent: "해당 이메일이 존재하면 재설정 링크가 전송됩니다.",
|
|
212
|
+
backToSignIn: "로그인으로 돌아가기",
|
|
213
|
+
requestFailed: "요청에 실패했습니다",
|
|
214
|
+
newTitle: "새 비밀번호 설정",
|
|
215
|
+
newPassword: "새 비밀번호",
|
|
216
|
+
updatePassword: "비밀번호 변경",
|
|
217
|
+
updating: "변경 중…",
|
|
218
|
+
missingToken: "재설정 토큰이 없거나 유효하지 않습니다.",
|
|
219
|
+
resetFailed: "재설정에 실패했습니다",
|
|
220
|
+
},
|
|
221
|
+
nav: { overview: "개요", users: "회원", sessions: "세션", audit: "감사 로그", account: "계정", signOut: "로그아웃" },
|
|
222
|
+
dashboard: { title: "대시보드", welcome: "{name}님, 환영합니다.", yourRole: "내 권한", signedInAs: "로그인 계정" },
|
|
223
|
+
audit: { title: "감사 로그", when: "시각", actor: "수행자", action: "동작", target: "대상", ip: "IP", empty: "감사 기록이 없습니다.", loadFailed: "감사 로그를 불러오지 못했습니다", total: "항목 {count}개" },
|
|
224
|
+
users: {
|
|
225
|
+
title: "회원",
|
|
226
|
+
search: "이메일로 검색…",
|
|
227
|
+
name: "이름",
|
|
228
|
+
email: "이메일",
|
|
229
|
+
role: "권한",
|
|
230
|
+
status: "상태",
|
|
231
|
+
active: "활성",
|
|
232
|
+
banned: "차단됨",
|
|
233
|
+
makeUser: "일반 사용자로",
|
|
234
|
+
makeAdmin: "관리자로",
|
|
235
|
+
ban: "차단",
|
|
236
|
+
unban: "차단 해제",
|
|
237
|
+
empty: "회원이 없습니다.",
|
|
238
|
+
total: "회원 {count}명",
|
|
239
|
+
previous: "이전",
|
|
240
|
+
next: "다음",
|
|
241
|
+
roleSet: "권한을 {role}(으)로 변경했습니다",
|
|
242
|
+
userBanned: "회원을 차단했습니다",
|
|
243
|
+
userUnbanned: "차단을 해제했습니다",
|
|
244
|
+
loadFailed: "회원 목록을 불러오지 못했습니다",
|
|
245
|
+
actionFailed: "작업에 실패했습니다",
|
|
246
|
+
addUser: "사용자 추가",
|
|
247
|
+
createTitle: "사용자 생성",
|
|
248
|
+
password: "비밀번호",
|
|
249
|
+
create: "생성",
|
|
250
|
+
cancel: "취소",
|
|
251
|
+
userCreated: "사용자를 생성했습니다",
|
|
252
|
+
setPassword: "비밀번호 설정",
|
|
253
|
+
newPassword: "새 비밀번호",
|
|
254
|
+
save: "저장",
|
|
255
|
+
passwordSet: "비밀번호를 변경했습니다",
|
|
256
|
+
delete: "삭제",
|
|
257
|
+
deleteTitle: "사용자 삭제",
|
|
258
|
+
deleteConfirm: "{email} 사용자를 삭제할까요? 되돌릴 수 없습니다.",
|
|
259
|
+
userDeleted: "사용자를 삭제했습니다",
|
|
260
|
+
confirmPassword: "비밀번호 확인",
|
|
261
|
+
confirmNewPassword: "새 비밀번호 확인",
|
|
262
|
+
passwordMismatch: "비밀번호가 일치하지 않습니다",
|
|
263
|
+
edit: "편집",
|
|
264
|
+
editTitle: "사용자 편집",
|
|
265
|
+
userUpdated: "사용자를 수정했습니다",
|
|
266
|
+
impersonate: "이 사용자로 로그인",
|
|
267
|
+
banTitle: "사용자 차단",
|
|
268
|
+
banReason: "사유 (선택)",
|
|
269
|
+
banExpiryDays: "만료 (일, 선택)",
|
|
270
|
+
stopImpersonating: "대리 로그인 종료",
|
|
271
|
+
impersonatingAs: "{email} 사용자로 대리 로그인 중",
|
|
272
|
+
manage: "관리",
|
|
273
|
+
revokeSession: "종료",
|
|
274
|
+
},
|
|
275
|
+
sessions: {
|
|
276
|
+
title: "활성 세션",
|
|
277
|
+
device: "기기",
|
|
278
|
+
ip: "IP",
|
|
279
|
+
since: "시작",
|
|
280
|
+
revoke: "종료",
|
|
281
|
+
unknown: "알 수 없음",
|
|
282
|
+
empty: "활성 세션이 없습니다.",
|
|
283
|
+
revoked: "세션을 종료했습니다",
|
|
284
|
+
loadFailed: "세션을 불러오지 못했습니다",
|
|
285
|
+
revokeFailed: "세션 종료에 실패했습니다",
|
|
286
|
+
status: "상태",
|
|
287
|
+
current: "현재",
|
|
288
|
+
signOutOthers: "다른 세션 모두 종료",
|
|
289
|
+
othersRevoked: "다른 세션을 모두 종료했습니다",
|
|
290
|
+
total: "세션 {count}개",
|
|
291
|
+
},
|
|
292
|
+
adminSessions: {
|
|
293
|
+
title: "사용자 세션",
|
|
294
|
+
user: "사용자",
|
|
295
|
+
device: "기기",
|
|
296
|
+
ip: "IP",
|
|
297
|
+
since: "시작",
|
|
298
|
+
revoke: "세션 종료",
|
|
299
|
+
revokeAll: "사용자 세션 모두 종료",
|
|
300
|
+
unknown: "알 수 없음",
|
|
301
|
+
empty: "활성 세션이 없습니다.",
|
|
302
|
+
revoked: "세션을 종료했습니다",
|
|
303
|
+
allRevoked: "사용자의 세션을 모두 종료했습니다",
|
|
304
|
+
loadFailed: "세션을 불러오지 못했습니다",
|
|
305
|
+
total: "세션 {count}개",
|
|
306
|
+
},
|
|
307
|
+
account: {
|
|
308
|
+
title: "계정",
|
|
309
|
+
profile: "프로필",
|
|
310
|
+
name: "이름",
|
|
311
|
+
email: "이메일",
|
|
312
|
+
role: "권한",
|
|
313
|
+
changePassword: "비밀번호 변경",
|
|
314
|
+
currentPassword: "현재 비밀번호",
|
|
315
|
+
newPassword: "새 비밀번호",
|
|
316
|
+
update: "비밀번호 변경",
|
|
317
|
+
updating: "변경 중…",
|
|
318
|
+
changed: "비밀번호를 변경했습니다",
|
|
319
|
+
changeFailed: "비밀번호 변경에 실패했습니다",
|
|
320
|
+
nav: { profile: "프로필", security: "보안", connected: "연결", sessions: "세션", danger: "위험 영역" },
|
|
321
|
+
twoFactorTitle: "2단계 인증",
|
|
322
|
+
twoFactorDesc: "로그인 시 인증 앱의 일회용 코드를 추가로 요구합니다.",
|
|
323
|
+
twoFactorOn: "사용 중",
|
|
324
|
+
twoFactorOff: "사용 안 함",
|
|
325
|
+
enable: "사용",
|
|
326
|
+
disable: "해제",
|
|
327
|
+
password: "비밀번호",
|
|
328
|
+
scanHint: "인증 앱에 등록한 뒤 6자리 코드를 입력하세요.",
|
|
329
|
+
setupKey: "설정 키 (otpauth URI)",
|
|
330
|
+
backupCodes: "백업 코드 — 안전한 곳에 보관하세요",
|
|
331
|
+
code: "인증 코드",
|
|
332
|
+
verify: "확인",
|
|
333
|
+
twoFactorEnabled: "2단계 인증을 사용합니다",
|
|
334
|
+
twoFactorDisabled: "2단계 인증을 해제했습니다",
|
|
335
|
+
connectedTitle: "연결된 계정",
|
|
336
|
+
connectedDesc: "소셜 계정을 연결하면 해당 계정으로 로그인할 수 있습니다.",
|
|
337
|
+
connect: "연결",
|
|
338
|
+
disconnect: "연결 해제",
|
|
339
|
+
connected: "연결됨",
|
|
340
|
+
notConnected: "연결 안 됨",
|
|
341
|
+
disconnected: "연결을 해제했습니다",
|
|
342
|
+
dangerTitle: "계정 삭제",
|
|
343
|
+
dangerDesc: "계정과 모든 관련 데이터를 영구히 삭제합니다.",
|
|
344
|
+
deleteAccount: "계정 삭제",
|
|
345
|
+
deleteAccountConfirm: "계정이 영구히 삭제되며 되돌릴 수 없습니다. 확인하려면 비밀번호를 입력하세요.",
|
|
346
|
+
accountDeleted: "계정을 삭제했습니다",
|
|
347
|
+
save: "변경 저장",
|
|
348
|
+
saved: "프로필을 저장했습니다",
|
|
349
|
+
saveFailed: "프로필 저장에 실패했습니다",
|
|
350
|
+
verified: "인증됨",
|
|
351
|
+
unverified: "미인증",
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
export const messages: Record<Locale, Messages> = { en, ko };
|
|
356
|
+
|
|
357
|
+
export function resolveLocale(value: string | undefined | null): Locale {
|
|
358
|
+
return LOCALES.includes(value as Locale) ? (value as Locale) : defaultLocale;
|
|
359
|
+
}
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LayoutServerLoad } from "./$types";
|
|
2
|
+
|
|
3
|
+
type Capabilities = { twoFactor: boolean; providers: string[]; deleteAccount: boolean; auditLog: boolean };
|
|
4
|
+
|
|
5
|
+
export const load: LayoutServerLoad = async ({ locals, fetch }) => {
|
|
6
|
+
let capabilities: Capabilities = { twoFactor: false, providers: [], deleteAccount: false, auditLog: false };
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch("/api/account/capabilities");
|
|
9
|
+
if (res.ok) capabilities = (await res.json()) as Capabilities;
|
|
10
|
+
} catch {
|
|
11
|
+
/* keep defaults */
|
|
12
|
+
}
|
|
13
|
+
return { user: locals.user, impersonating: !!locals.session?.impersonatedBy, capabilities };
|
|
14
|
+
};
|