@modern-admin/react 0.1.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/action-guard.d.ts +13 -0
- package/dist/action-guard.d.ts.map +1 -0
- package/dist/action-guard.js +15 -0
- package/dist/action-guard.js.map +1 -0
- package/dist/action-menu.d.ts +17 -0
- package/dist/action-menu.d.ts.map +1 -0
- package/dist/action-menu.jsx +80 -0
- package/dist/action-menu.jsx.map +1 -0
- package/dist/admin-app.d.ts +23 -0
- package/dist/admin-app.d.ts.map +1 -0
- package/dist/admin-app.jsx +407 -0
- package/dist/admin-app.jsx.map +1 -0
- package/dist/admin-router.d.ts +29 -0
- package/dist/admin-router.d.ts.map +1 -0
- package/dist/admin-router.jsx +215 -0
- package/dist/admin-router.jsx.map +1 -0
- package/dist/breadcrumbs.d.ts +17 -0
- package/dist/breadcrumbs.d.ts.map +1 -0
- package/dist/breadcrumbs.jsx +40 -0
- package/dist/breadcrumbs.jsx.map +1 -0
- package/dist/client.d.ts +526 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +582 -0
- package/dist/client.js.map +1 -0
- package/dist/component-loader.d.ts +10 -0
- package/dist/component-loader.d.ts.map +1 -0
- package/dist/component-loader.js +23 -0
- package/dist/component-loader.js.map +1 -0
- package/dist/components/ai-assistant-widget.d.ts +3 -0
- package/dist/components/ai-assistant-widget.d.ts.map +1 -0
- package/dist/components/ai-assistant-widget.jsx +390 -0
- package/dist/components/ai-assistant-widget.jsx.map +1 -0
- package/dist/components/ai-fill-dialog.d.ts +9 -0
- package/dist/components/ai-fill-dialog.d.ts.map +1 -0
- package/dist/components/ai-fill-dialog.jsx +105 -0
- package/dist/components/ai-fill-dialog.jsx.map +1 -0
- package/dist/components/chart-builder-dialog.d.ts +10 -0
- package/dist/components/chart-builder-dialog.d.ts.map +1 -0
- package/dist/components/chart-builder-dialog.jsx +433 -0
- package/dist/components/chart-builder-dialog.jsx.map +1 -0
- package/dist/components/chart-widget.d.ts +12 -0
- package/dist/components/chart-widget.d.ts.map +1 -0
- package/dist/components/chart-widget.jsx +365 -0
- package/dist/components/chart-widget.jsx.map +1 -0
- package/dist/components/global-search-dialog.d.ts +7 -0
- package/dist/components/global-search-dialog.d.ts.map +1 -0
- package/dist/components/global-search-dialog.jsx +187 -0
- package/dist/components/global-search-dialog.jsx.map +1 -0
- package/dist/components/group-settings-dialog.d.ts +13 -0
- package/dist/components/group-settings-dialog.d.ts.map +1 -0
- package/dist/components/group-settings-dialog.jsx +53 -0
- package/dist/components/group-settings-dialog.jsx.map +1 -0
- package/dist/components/move-chart-dialog.d.ts +18 -0
- package/dist/components/move-chart-dialog.d.ts.map +1 -0
- package/dist/components/move-chart-dialog.jsx +68 -0
- package/dist/components/move-chart-dialog.jsx.map +1 -0
- package/dist/components/reference-multi-table-dialog.d.ts +12 -0
- package/dist/components/reference-multi-table-dialog.d.ts.map +1 -0
- package/dist/components/reference-multi-table-dialog.jsx +126 -0
- package/dist/components/reference-multi-table-dialog.jsx.map +1 -0
- package/dist/components/related-records-tabs.d.ts +8 -0
- package/dist/components/related-records-tabs.d.ts.map +1 -0
- package/dist/components/related-records-tabs.jsx +75 -0
- package/dist/components/related-records-tabs.jsx.map +1 -0
- package/dist/components/revisions-button.d.ts +7 -0
- package/dist/components/revisions-button.d.ts.map +1 -0
- package/dist/components/revisions-button.jsx +152 -0
- package/dist/components/revisions-button.jsx.map +1 -0
- package/dist/components/wizard-form.d.ts +43 -0
- package/dist/components/wizard-form.d.ts.map +1 -0
- package/dist/components/wizard-form.jsx +136 -0
- package/dist/components/wizard-form.jsx.map +1 -0
- package/dist/dashboard/time-series.d.ts +20 -0
- package/dist/dashboard/time-series.d.ts.map +1 -0
- package/dist/dashboard/time-series.js +108 -0
- package/dist/dashboard/time-series.js.map +1 -0
- package/dist/dialogs.d.ts +35 -0
- package/dist/dialogs.d.ts.map +1 -0
- package/dist/dialogs.jsx +152 -0
- package/dist/dialogs.jsx.map +1 -0
- package/dist/export.d.ts +39 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +114 -0
- package/dist/export.js.map +1 -0
- package/dist/extension-registry.d.ts +122 -0
- package/dist/extension-registry.d.ts.map +1 -0
- package/dist/extension-registry.js +93 -0
- package/dist/extension-registry.js.map +1 -0
- package/dist/header-controls.d.ts +4 -0
- package/dist/header-controls.d.ts.map +1 -0
- package/dist/header-controls.jsx +70 -0
- package/dist/header-controls.jsx.map +1 -0
- package/dist/hooks.d.ts +104 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +374 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hotkey-help.d.ts +3 -0
- package/dist/hotkey-help.d.ts.map +1 -0
- package/dist/hotkey-help.jsx +32 -0
- package/dist/hotkey-help.jsx.map +1 -0
- package/dist/hotkey-registry.d.ts +18 -0
- package/dist/hotkey-registry.d.ts.map +1 -0
- package/dist/hotkey-registry.jsx +34 -0
- package/dist/hotkey-registry.jsx.map +1 -0
- package/dist/i18n.d.ts +74 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.jsx +127 -0
- package/dist/i18n.jsx.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/notify.d.ts +41 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.jsx +58 -0
- package/dist/notify.jsx.map +1 -0
- package/dist/pages/ai-assistant-settings-section.d.ts +3 -0
- package/dist/pages/ai-assistant-settings-section.d.ts.map +1 -0
- package/dist/pages/ai-assistant-settings-section.jsx +126 -0
- package/dist/pages/ai-assistant-settings-section.jsx.map +1 -0
- package/dist/pages/audit-log-page.d.ts +3 -0
- package/dist/pages/audit-log-page.d.ts.map +1 -0
- package/dist/pages/audit-log-page.jsx +354 -0
- package/dist/pages/audit-log-page.jsx.map +1 -0
- package/dist/pages/edit-page.d.ts +7 -0
- package/dist/pages/edit-page.d.ts.map +1 -0
- package/dist/pages/edit-page.jsx +614 -0
- package/dist/pages/edit-page.jsx.map +1 -0
- package/dist/pages/export-dialog.d.ts +11 -0
- package/dist/pages/export-dialog.d.ts.map +1 -0
- package/dist/pages/export-dialog.jsx +102 -0
- package/dist/pages/export-dialog.jsx.map +1 -0
- package/dist/pages/home-page.d.ts +3 -0
- package/dist/pages/home-page.d.ts.map +1 -0
- package/dist/pages/home-page.jsx +211 -0
- package/dist/pages/home-page.jsx.map +1 -0
- package/dist/pages/list-page.d.ts +42 -0
- package/dist/pages/list-page.d.ts.map +1 -0
- package/dist/pages/list-page.jsx +1596 -0
- package/dist/pages/list-page.jsx.map +1 -0
- package/dist/pages/login-page.d.ts +11 -0
- package/dist/pages/login-page.d.ts.map +1 -0
- package/dist/pages/login-page.jsx +157 -0
- package/dist/pages/login-page.jsx.map +1 -0
- package/dist/pages/settings-page.d.ts +5 -0
- package/dist/pages/settings-page.d.ts.map +1 -0
- package/dist/pages/settings-page.jsx +787 -0
- package/dist/pages/settings-page.jsx.map +1 -0
- package/dist/pages/settings-shared.d.ts +51 -0
- package/dist/pages/settings-shared.d.ts.map +1 -0
- package/dist/pages/settings-shared.jsx +66 -0
- package/dist/pages/settings-shared.jsx.map +1 -0
- package/dist/pages/show-page.d.ts +7 -0
- package/dist/pages/show-page.d.ts.map +1 -0
- package/dist/pages/show-page.jsx +147 -0
- package/dist/pages/show-page.jsx.map +1 -0
- package/dist/pages/wizard-create-page.d.ts +14 -0
- package/dist/pages/wizard-create-page.d.ts.map +1 -0
- package/dist/pages/wizard-create-page.jsx +106 -0
- package/dist/pages/wizard-create-page.jsx.map +1 -0
- package/dist/property-renderer.d.ts +8 -0
- package/dist/property-renderer.d.ts.map +1 -0
- package/dist/property-renderer.jsx +690 -0
- package/dist/property-renderer.jsx.map +1 -0
- package/dist/provider.d.ts +20 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.jsx +32 -0
- package/dist/provider.jsx.map +1 -0
- package/dist/realtime.d.ts +22 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +38 -0
- package/dist/realtime.js.map +1 -0
- package/dist/reference.d.ts +52 -0
- package/dist/reference.d.ts.map +1 -0
- package/dist/reference.jsx +224 -0
- package/dist/reference.jsx.map +1 -0
- package/dist/relations.d.ts +11 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +36 -0
- package/dist/relations.js.map +1 -0
- package/dist/router.d.ts +82 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.jsx +187 -0
- package/dist/router.jsx.map +1 -0
- package/dist/show-when.d.ts +7 -0
- package/dist/show-when.d.ts.map +1 -0
- package/dist/show-when.js +77 -0
- package/dist/show-when.js.map +1 -0
- package/dist/types.d.ts +194 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/use-dashboard-charts.d.ts +93 -0
- package/dist/use-dashboard-charts.d.ts.map +1 -0
- package/dist/use-dashboard-charts.js +263 -0
- package/dist/use-dashboard-charts.js.map +1 -0
- package/dist/use-hotkey.d.ts +17 -0
- package/dist/use-hotkey.d.ts.map +1 -0
- package/dist/use-hotkey.js +103 -0
- package/dist/use-hotkey.js.map +1 -0
- package/dist/user-directory.d.ts +18 -0
- package/dist/user-directory.d.ts.map +1 -0
- package/dist/user-directory.js +51 -0
- package/dist/user-directory.js.map +1 -0
- package/dist/validation.d.ts +22 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +338 -0
- package/dist/validation.js.map +1 -0
- package/package.json +59 -0
- package/src/action-guard.ts +20 -0
- package/src/action-menu.tsx +161 -0
- package/src/admin-app.tsx +630 -0
- package/src/admin-router.tsx +273 -0
- package/src/breadcrumbs.tsx +75 -0
- package/src/client.ts +1093 -0
- package/src/component-loader.ts +33 -0
- package/src/components/ai-assistant-widget.tsx +565 -0
- package/src/components/ai-fill-dialog.tsx +143 -0
- package/src/components/chart-builder-dialog.tsx +618 -0
- package/src/components/chart-widget.tsx +654 -0
- package/src/components/global-search-dialog.tsx +272 -0
- package/src/components/group-settings-dialog.tsx +93 -0
- package/src/components/move-chart-dialog.tsx +130 -0
- package/src/components/reference-multi-table-dialog.tsx +196 -0
- package/src/components/related-records-tabs.tsx +130 -0
- package/src/components/revisions-button.tsx +237 -0
- package/src/components/wizard-form.tsx +302 -0
- package/src/dashboard/time-series.ts +125 -0
- package/src/dialogs.tsx +265 -0
- package/src/export.ts +140 -0
- package/src/extension-registry.ts +195 -0
- package/src/header-controls.tsx +125 -0
- package/src/hooks.ts +509 -0
- package/src/hotkey-help.tsx +56 -0
- package/src/hotkey-registry.tsx +60 -0
- package/src/i18n.tsx +267 -0
- package/src/index.ts +192 -0
- package/src/notify.tsx +94 -0
- package/src/pages/ai-assistant-settings-section.tsx +167 -0
- package/src/pages/audit-log-page.tsx +580 -0
- package/src/pages/edit-page.tsx +743 -0
- package/src/pages/export-dialog.tsx +154 -0
- package/src/pages/home-page.tsx +318 -0
- package/src/pages/list-page.tsx +2645 -0
- package/src/pages/login-page.tsx +242 -0
- package/src/pages/settings-page.tsx +1143 -0
- package/src/pages/settings-shared.tsx +134 -0
- package/src/pages/show-page.tsx +223 -0
- package/src/pages/wizard-create-page.tsx +164 -0
- package/src/property-renderer.tsx +1143 -0
- package/src/provider.tsx +70 -0
- package/src/realtime.ts +55 -0
- package/src/reference.tsx +386 -0
- package/src/relations.ts +55 -0
- package/src/router.tsx +211 -0
- package/src/show-when.ts +76 -0
- package/src/types.ts +198 -0
- package/src/use-dashboard-charts.ts +362 -0
- package/src/use-hotkey.ts +128 -0
- package/src/user-directory.ts +56 -0
- package/src/validation.ts +361 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Two-pane sign-in screen: branded panel on the left, form on the right.
|
|
2
|
+
// Collapses into a single column on mobile (left pane hides — the form
|
|
3
|
+
// reuses the brand mark inline). Posts to Better Auth's email handler via
|
|
4
|
+
// AdminClient.login, then invalidates the `me` query so the gate flips
|
|
5
|
+
// to the authenticated app shell.
|
|
6
|
+
//
|
|
7
|
+
// Social providers are driven entirely by the server: BetterAuthProvider
|
|
8
|
+
// returns the list of enabled provider ids from `betterAuth({ socialProviders })`.
|
|
9
|
+
// Known providers (google, github, apple) get a dedicated lucide icon; any
|
|
10
|
+
// other id falls back to Globe with a capitalised label.
|
|
11
|
+
|
|
12
|
+
import * as React from 'react'
|
|
13
|
+
import { Button, Field, FieldDescription, FieldError, FieldLabel, Input, PasswordInput } from '@modern-admin/ui'
|
|
14
|
+
import { Database, Globe, LogIn } from 'lucide-react'
|
|
15
|
+
import { useAuthUiProps, useLogin, useSocialLogin } from '../hooks.js'
|
|
16
|
+
import { useI18n } from '../i18n.js'
|
|
17
|
+
import { AdminApiError } from '../client.js'
|
|
18
|
+
|
|
19
|
+
// ─── Social provider registry ────────────────────────────────────────────────
|
|
20
|
+
// Maps a Better Auth provider id to a display label and an icon element.
|
|
21
|
+
// lucide-react 1.x dropped all brand icons, so known social providers use
|
|
22
|
+
// inline monochrome SVGs; unknown ids fall through to Globe.
|
|
23
|
+
|
|
24
|
+
type ProviderDef = { label: string; icon: React.ReactElement }
|
|
25
|
+
|
|
26
|
+
function GoogleIcon(): React.ReactElement {
|
|
27
|
+
return (
|
|
28
|
+
<svg className="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
29
|
+
<path
|
|
30
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
|
31
|
+
<path
|
|
32
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
|
33
|
+
<path
|
|
34
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"/>
|
|
35
|
+
<path
|
|
36
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
|
37
|
+
</svg>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function GitHubIcon(): React.ReactElement {
|
|
42
|
+
return (
|
|
43
|
+
<svg className="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
44
|
+
<path
|
|
45
|
+
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/>
|
|
46
|
+
</svg>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AppleIcon(): React.ReactElement {
|
|
51
|
+
return (
|
|
52
|
+
<svg className="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
53
|
+
<path
|
|
54
|
+
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.459 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zm3.378-3.066c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701z"/>
|
|
55
|
+
</svg>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const SOCIAL_PROVIDERS: Record<string, ProviderDef> = {
|
|
60
|
+
google: { label: 'Google', icon: <GoogleIcon/> },
|
|
61
|
+
github: { label: 'GitHub', icon: <GitHubIcon/> },
|
|
62
|
+
apple: { label: 'Apple', icon: <AppleIcon/> },
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveProvider(id: string): ProviderDef {
|
|
66
|
+
return (
|
|
67
|
+
SOCIAL_PROVIDERS[id] ?? {
|
|
68
|
+
label: id.charAt(0).toUpperCase() + id.slice(1),
|
|
69
|
+
icon: <Globe className="size-4" aria-hidden="true"/>,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Components ──────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export interface LoginPageProps {
|
|
77
|
+
/** Optional title override (defaults to common:appName). */
|
|
78
|
+
title?: React.ReactNode
|
|
79
|
+
/** Optional helper line under the form — e.g. demo credentials. */
|
|
80
|
+
hint?: React.ReactNode
|
|
81
|
+
/** Optional tagline shown on the brand panel. */
|
|
82
|
+
tagline?: React.ReactNode
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function BrandMark({ size = 'md' }: { size?: 'sm' | 'md' }): React.ReactElement {
|
|
86
|
+
const tile = size === 'sm' ? 'size-8 rounded-lg' : 'size-9 rounded-xl'
|
|
87
|
+
const icon = size === 'sm' ? 'size-4' : 'size-5'
|
|
88
|
+
return (
|
|
89
|
+
<span
|
|
90
|
+
className={`flex ${tile} items-center justify-center bg-primary/10 ring-1 ring-primary/20`}
|
|
91
|
+
>
|
|
92
|
+
<Database className={`${icon} text-primary`} aria-hidden="true"/>
|
|
93
|
+
</span>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function LoginPage({ title, hint, tagline }: LoginPageProps): React.ReactElement {
|
|
98
|
+
const { t } = useI18n()
|
|
99
|
+
const login = useLogin()
|
|
100
|
+
const socialLogin = useSocialLogin()
|
|
101
|
+
const { data: authUi } = useAuthUiProps()
|
|
102
|
+
const [email, setEmail] = React.useState('')
|
|
103
|
+
const [password, setPassword] = React.useState('')
|
|
104
|
+
|
|
105
|
+
const providers = authUi?.providers ?? []
|
|
106
|
+
const showEmailForm = authUi === undefined || authUi.emailAndPassword
|
|
107
|
+
|
|
108
|
+
const submit = (event: React.FormEvent<HTMLFormElement>): void => {
|
|
109
|
+
event.preventDefault()
|
|
110
|
+
if (login.isPending) return
|
|
111
|
+
login.mutate({ email, password })
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const errMessage = (() => {
|
|
115
|
+
const err = login.error
|
|
116
|
+
if (!err) return null
|
|
117
|
+
if (err instanceof AdminApiError && err.status === 401) {
|
|
118
|
+
return t('auth:loginFailed')
|
|
119
|
+
}
|
|
120
|
+
// Better Auth surfaces structured errors as JSON; surface the message
|
|
121
|
+
// field when present, otherwise fall back to the raw payload.
|
|
122
|
+
if (err instanceof AdminApiError) {
|
|
123
|
+
try {
|
|
124
|
+
const body = JSON.parse(err.message) as { message?: string }
|
|
125
|
+
if (body.message) return body.message
|
|
126
|
+
} catch {
|
|
127
|
+
/* non-JSON body — fall through */
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return err.message || t('errors:server')
|
|
131
|
+
})()
|
|
132
|
+
|
|
133
|
+
const heading = title ?? t('common:appName')
|
|
134
|
+
const subtitle = t('auth:subtitle')
|
|
135
|
+
const taglineText = tagline ?? t('auth:tagline')
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className="grid min-h-screen grid-cols-1 bg-background lg:grid-cols-2">
|
|
139
|
+
{/* Brand panel — desktop only. Mirrors the sidebar's visual rhythm. */}
|
|
140
|
+
<aside className="relative hidden flex-col justify-between border-r border-border bg-muted/30 p-10 lg:flex">
|
|
141
|
+
<div className="flex items-center gap-3">
|
|
142
|
+
<BrandMark/>
|
|
143
|
+
<span className="text-base font-semibold tracking-tight">
|
|
144
|
+
{t('common:appName')}
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
<p className="max-w-md text-sm text-muted-foreground">{taglineText}</p>
|
|
148
|
+
</aside>
|
|
149
|
+
|
|
150
|
+
{/* Form column. Centered card with mobile-friendly padding. */}
|
|
151
|
+
<main className="flex items-center justify-center p-6 sm:p-10">
|
|
152
|
+
<div className="w-full max-w-sm">
|
|
153
|
+
<div className="mb-8 flex flex-col items-center gap-3 text-center">
|
|
154
|
+
<BrandMark/>
|
|
155
|
+
<h1 className="text-2xl font-semibold tracking-tight">{heading}</h1>
|
|
156
|
+
<p className="text-sm text-muted-foreground">{subtitle}</p>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Social login buttons */}
|
|
160
|
+
{providers.length > 0 && (
|
|
161
|
+
<div className="mb-4 flex flex-col gap-2">
|
|
162
|
+
{providers.map((id) => {
|
|
163
|
+
const { label, icon } = resolveProvider(id)
|
|
164
|
+
return (
|
|
165
|
+
<Button
|
|
166
|
+
key={id}
|
|
167
|
+
type="button"
|
|
168
|
+
variant="outline"
|
|
169
|
+
className="w-full gap-2"
|
|
170
|
+
disabled={socialLogin.isPending}
|
|
171
|
+
onClick={() => socialLogin.mutate(id)}
|
|
172
|
+
>
|
|
173
|
+
{icon}
|
|
174
|
+
{t('auth:continueWith').replace('{provider}', label)}
|
|
175
|
+
</Button>
|
|
176
|
+
)
|
|
177
|
+
})}
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{/* Divider between social and email/password */}
|
|
182
|
+
{providers.length > 0 && showEmailForm && (
|
|
183
|
+
<div className="relative mb-4 flex items-center gap-3">
|
|
184
|
+
<div className="flex-1 border-t border-border"/>
|
|
185
|
+
<span className="text-xs text-muted-foreground">
|
|
186
|
+
{t('auth:orContinueWith')}
|
|
187
|
+
</span>
|
|
188
|
+
<div className="flex-1 border-t border-border"/>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{/* Email / password form */}
|
|
193
|
+
{showEmailForm && (
|
|
194
|
+
<form onSubmit={submit} className="flex flex-col gap-4">
|
|
195
|
+
<Field>
|
|
196
|
+
<FieldLabel htmlFor="login-email">{t('auth:email')}</FieldLabel>
|
|
197
|
+
<Input
|
|
198
|
+
id="login-email"
|
|
199
|
+
type="email"
|
|
200
|
+
autoComplete="email"
|
|
201
|
+
autoFocus
|
|
202
|
+
required
|
|
203
|
+
placeholder="you@example.com"
|
|
204
|
+
value={email}
|
|
205
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
206
|
+
disabled={login.isPending}
|
|
207
|
+
/>
|
|
208
|
+
</Field>
|
|
209
|
+
<Field>
|
|
210
|
+
<FieldLabel htmlFor="login-password">{t('auth:password')}</FieldLabel>
|
|
211
|
+
<PasswordInput
|
|
212
|
+
id="login-password"
|
|
213
|
+
autoComplete="current-password"
|
|
214
|
+
required
|
|
215
|
+
value={password}
|
|
216
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
217
|
+
disabled={login.isPending}
|
|
218
|
+
toggleLabel={{
|
|
219
|
+
show: t('common:showPassword'),
|
|
220
|
+
hide: t('common:hidePassword'),
|
|
221
|
+
}}
|
|
222
|
+
/>
|
|
223
|
+
</Field>
|
|
224
|
+
{errMessage && <FieldError>{errMessage}</FieldError>}
|
|
225
|
+
<Button
|
|
226
|
+
type="submit"
|
|
227
|
+
className="mt-2 w-full gap-2"
|
|
228
|
+
disabled={login.isPending || !email || !password}
|
|
229
|
+
>
|
|
230
|
+
<LogIn className="size-4"/>
|
|
231
|
+
{t('auth:login')}
|
|
232
|
+
</Button>
|
|
233
|
+
{hint && (
|
|
234
|
+
<FieldDescription className="text-center">{hint}</FieldDescription>
|
|
235
|
+
)}
|
|
236
|
+
</form>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
</main>
|
|
240
|
+
</div>
|
|
241
|
+
)
|
|
242
|
+
}
|