@djangocfg/layouts 2.1.334 → 2.1.335
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/README.md +21 -1
- package/package.json +17 -17
- package/src/configurator/auth/defaults.ts +101 -0
- package/src/configurator/auth/index.ts +36 -0
- package/src/configurator/auth/schema.ts +205 -0
- package/src/configurator/auth/types.ts +48 -0
- package/src/configurator/index.ts +2 -0
- package/src/layouts/AppLayout/BaseApp.tsx +2 -1
- package/src/layouts/AuthLayout/AuthLayout.tsx +39 -30
- package/src/layouts/AuthLayout/README.md +77 -3
- package/src/layouts/AuthLayout/index.ts +12 -0
- package/src/layouts/AuthLayout/shells/AuthShell.tsx +80 -0
- package/src/layouts/AuthLayout/shells/CenteredShell.tsx +46 -0
- package/src/layouts/AuthLayout/shells/SplitShell.tsx +63 -0
- package/src/layouts/AuthLayout/shells/context.tsx +25 -0
- package/src/layouts/AuthLayout/shells/index.ts +9 -0
- package/src/layouts/AuthLayout/shells/types.ts +67 -0
- package/src/layouts/AuthLayout/styles/auth.css +4 -42
- package/src/layouts/AuthLayout/styles/centered-shell.css +57 -0
- package/src/layouts/AuthLayout/styles/split-shell.css +101 -0
- package/src/layouts/AuthLayout/types.ts +11 -0
- package/src/layouts/types/layout.types.ts +4 -2
package/README.md
CHANGED
|
@@ -54,10 +54,30 @@ Use `BaseApp` directly when you don't need route-based layout switching — see
|
|
|
54
54
|
|---|---|---|
|
|
55
55
|
| **`PublicLayout`** | Marketing / docs. Slots for navbar (`Floating`/`Flush`/`Minimal`) + footer + locale + auth controls. | [README](./src/layouts/PublicLayout/README.md) |
|
|
56
56
|
| **`PrivateLayout`** | Authenticated app shell — sidebar (collapsible icon rail, accordion groups, rail/featured/CTA slots) + popover account footer. | [README](./src/layouts/PrivateLayout/README.md) |
|
|
57
|
-
| **`AuthLayout`** | Sign-in / sign-up flows. | [README](./src/layouts/AuthLayout/README.md) |
|
|
57
|
+
| **`AuthLayout`** | Sign-in / sign-up flows. Shell-based: `centered` (default) or `split` (two-column desktop). | [README](./src/layouts/AuthLayout/README.md) |
|
|
58
58
|
| **`AdminLayout`** | Admin console. | — |
|
|
59
59
|
| **`ProfileLayout`** | Profile page — see below. | |
|
|
60
60
|
|
|
61
|
+
### `AuthLayout` shells
|
|
62
|
+
|
|
63
|
+
Two visual variants, switchable via the `variant` prop:
|
|
64
|
+
|
|
65
|
+
- **`centered`** — Apple HIG-style: frameless, centered form, animated glow background.
|
|
66
|
+
- **`split`** — Two-column card on desktop (form left, customizable sidebar right); single-column on mobile. Background image/gradient with overlay + blur via the `background` prop.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// Split variant with background image and testimonial sidebar
|
|
70
|
+
<AuthLayout
|
|
71
|
+
variant="split"
|
|
72
|
+
background={{ imageUrl: '/bg.jpg', overlay: 'hsl(var(--background) / 0.6)', blur: '8px' }}
|
|
73
|
+
sidebar={<Testimonial />}
|
|
74
|
+
>
|
|
75
|
+
{/* optional custom header, shown only on identifier step */}
|
|
76
|
+
</AuthLayout>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Architecture: `AuthShell` orchestrator dispatches to `CenteredShell` or `SplitShell`. Add new variants by extending `AuthShellVariant` and creating a new shell component.
|
|
80
|
+
|
|
61
81
|
### `ProfileLayout`
|
|
62
82
|
|
|
63
83
|
```tsx
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.335",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -84,13 +84,13 @@
|
|
|
84
84
|
"check": "tsc --noEmit"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
-
"@djangocfg/api": "^2.1.
|
|
88
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
89
|
-
"@djangocfg/debuger": "^2.1.
|
|
90
|
-
"@djangocfg/i18n": "^2.1.
|
|
91
|
-
"@djangocfg/monitor": "^2.1.
|
|
92
|
-
"@djangocfg/ui-core": "^2.1.
|
|
93
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
87
|
+
"@djangocfg/api": "^2.1.335",
|
|
88
|
+
"@djangocfg/centrifugo": "^2.1.335",
|
|
89
|
+
"@djangocfg/debuger": "^2.1.335",
|
|
90
|
+
"@djangocfg/i18n": "^2.1.335",
|
|
91
|
+
"@djangocfg/monitor": "^2.1.335",
|
|
92
|
+
"@djangocfg/ui-core": "^2.1.335",
|
|
93
|
+
"@djangocfg/ui-nextjs": "^2.1.335",
|
|
94
94
|
"@hookform/resolvers": "^5.2.2",
|
|
95
95
|
"consola": "^3.4.2",
|
|
96
96
|
"lucide-react": "^0.545.0",
|
|
@@ -120,15 +120,15 @@
|
|
|
120
120
|
"uuid": "^11.1.0"
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
|
-
"@djangocfg/api": "^2.1.
|
|
124
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
125
|
-
"@djangocfg/debuger": "^2.1.
|
|
126
|
-
"@djangocfg/i18n": "^2.1.
|
|
127
|
-
"@djangocfg/monitor": "^2.1.
|
|
128
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
129
|
-
"@djangocfg/ui-core": "^2.1.
|
|
130
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
131
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
123
|
+
"@djangocfg/api": "^2.1.335",
|
|
124
|
+
"@djangocfg/centrifugo": "^2.1.335",
|
|
125
|
+
"@djangocfg/debuger": "^2.1.335",
|
|
126
|
+
"@djangocfg/i18n": "^2.1.335",
|
|
127
|
+
"@djangocfg/monitor": "^2.1.335",
|
|
128
|
+
"@djangocfg/typescript-config": "^2.1.335",
|
|
129
|
+
"@djangocfg/ui-core": "^2.1.335",
|
|
130
|
+
"@djangocfg/ui-nextjs": "^2.1.335",
|
|
131
|
+
"@djangocfg/ui-tools": "^2.1.335",
|
|
132
132
|
"@types/node": "^24.7.2",
|
|
133
133
|
"@types/react": "^19.1.0",
|
|
134
134
|
"@types/react-dom": "^19.1.0",
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthLayout configurator defaults, presets, and image catalogue.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AuthPlaygroundConfig, AuthCenteredConfig, AuthSplitConfig } from './types';
|
|
6
|
+
|
|
7
|
+
export const BG_IMAGES = [
|
|
8
|
+
{
|
|
9
|
+
label: 'Mountains',
|
|
10
|
+
value: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&q=80',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'Night City',
|
|
14
|
+
value: 'https://images.unsplash.com/photo-1519501025264-65ba15a82390?w=1920&q=80',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: 'Abstract Mesh',
|
|
18
|
+
value: 'https://images.unsplash.com/photo-1550684848-fac1c5b4e853?w=1920&q=80',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'Ocean Waves',
|
|
22
|
+
value: 'https://images.unsplash.com/photo-1505118380757-91f5f5632de0?w=1920&q=80',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: 'Forest Fog',
|
|
26
|
+
value: 'https://images.unsplash.com/photo-1448375240586-882707db888b?w=1920&q=80',
|
|
27
|
+
},
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
export const defaultCenteredConfig: AuthCenteredConfig = {
|
|
31
|
+
background: {
|
|
32
|
+
enabled: false,
|
|
33
|
+
imageUrl: BG_IMAGES[0].value,
|
|
34
|
+
gradient: 'linear-gradient(135deg, hsl(var(--primary) / 0.08) 0%, hsl(var(--background)) 100%)',
|
|
35
|
+
overlay: 'hsl(var(--background) / 0.6)',
|
|
36
|
+
blur: '8px',
|
|
37
|
+
},
|
|
38
|
+
form: {
|
|
39
|
+
showLogo: true,
|
|
40
|
+
showTerms: true,
|
|
41
|
+
showGithub: true,
|
|
42
|
+
showSupport: true,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const defaultSplitConfig: AuthSplitConfig = {
|
|
47
|
+
background: {
|
|
48
|
+
enabled: true,
|
|
49
|
+
imageUrl: BG_IMAGES[0].value,
|
|
50
|
+
gradient: '',
|
|
51
|
+
overlay: 'hsl(var(--background) / 0.6)',
|
|
52
|
+
blur: '8px',
|
|
53
|
+
},
|
|
54
|
+
sidebar: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
showQuote: true,
|
|
57
|
+
showAuthor: true,
|
|
58
|
+
},
|
|
59
|
+
form: {
|
|
60
|
+
showLogo: true,
|
|
61
|
+
showTerms: true,
|
|
62
|
+
showGithub: true,
|
|
63
|
+
showSupport: true,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Full config used when the caller still needs a single object (e.g. URL encode). */
|
|
68
|
+
export const defaultConfig: AuthPlaygroundConfig = {
|
|
69
|
+
variant: 'centered',
|
|
70
|
+
...defaultCenteredConfig,
|
|
71
|
+
sidebar: { enabled: false, showQuote: true, showAuthor: true },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const presets = {
|
|
75
|
+
centered: defaultCenteredConfig,
|
|
76
|
+
split: defaultSplitConfig,
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
export function detectPreset(config: AuthPlaygroundConfig): 'centered' | 'split' | 'custom' {
|
|
80
|
+
if (deepEqual(config, { variant: 'centered', ...defaultCenteredConfig, sidebar: { enabled: false, showQuote: true, showAuthor: true } })) {
|
|
81
|
+
return 'centered';
|
|
82
|
+
}
|
|
83
|
+
if (deepEqual(config, { variant: 'split', ...defaultSplitConfig })) {
|
|
84
|
+
return 'split';
|
|
85
|
+
}
|
|
86
|
+
return 'custom';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function deepEqual(a: unknown, b: unknown): boolean {
|
|
90
|
+
if (a === b) return true;
|
|
91
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
|
92
|
+
const ak = Object.keys(a as Record<string, unknown>);
|
|
93
|
+
const bk = Object.keys(b as Record<string, unknown>);
|
|
94
|
+
if (ak.length !== bk.length) return false;
|
|
95
|
+
for (const k of ak) {
|
|
96
|
+
if (!deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthLayout configurator — schemas, defaults, and presets.
|
|
3
|
+
*
|
|
4
|
+
* Two independent schemas are provided:
|
|
5
|
+
* - `authCenteredSchema` + `authCenteredUiSchema` — Apple-style frameless
|
|
6
|
+
* - `authSplitSchema` + `authSplitUiSchema` — two-column card with sidebar
|
|
7
|
+
*
|
|
8
|
+
* Use `defaultCenteredConfig` / `defaultSplitConfig` as safe initial values.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
AuthPlaygroundConfig,
|
|
13
|
+
AuthCenteredConfig,
|
|
14
|
+
AuthSplitConfig,
|
|
15
|
+
AuthShellVariant,
|
|
16
|
+
PresetKind,
|
|
17
|
+
AuthBackgroundConfig,
|
|
18
|
+
AuthSidebarConfig,
|
|
19
|
+
AuthFormConfig,
|
|
20
|
+
} from './types';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
BG_IMAGES,
|
|
24
|
+
defaultCenteredConfig,
|
|
25
|
+
defaultSplitConfig,
|
|
26
|
+
defaultConfig,
|
|
27
|
+
presets,
|
|
28
|
+
detectPreset,
|
|
29
|
+
} from './defaults';
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
authCenteredSchema,
|
|
33
|
+
authCenteredUiSchema,
|
|
34
|
+
authSplitSchema,
|
|
35
|
+
authSplitUiSchema,
|
|
36
|
+
} from './schema';
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema + uiSchema for an AuthLayout configurator UI.
|
|
3
|
+
*
|
|
4
|
+
* Two independent schemas are exported:
|
|
5
|
+
* - `authCenteredSchema` / `authCenteredUiSchema` — Apple-style frameless shell
|
|
6
|
+
* - `authSplitSchema` / `authSplitUiSchema` — two-column card with sidebar
|
|
7
|
+
*
|
|
8
|
+
* Pair with `<JsonSchemaForm density="compact" schema={...} uiSchema={...}>`
|
|
9
|
+
* from `@djangocfg/ui-tools` to get a ready-made sidebar configurator.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { CustomJsonSchema7, CustomJsonUiSchema7 } from '@djangocfg/ui-core/lib';
|
|
13
|
+
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// Centered variant
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export const authCenteredSchema: CustomJsonSchema7 = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
background: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
title: 'Background',
|
|
24
|
+
properties: {
|
|
25
|
+
enabled: {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
title: 'Enable custom background',
|
|
28
|
+
description: 'When off, uses the default GlowBackground.',
|
|
29
|
+
},
|
|
30
|
+
imageUrl: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
title: 'Image',
|
|
33
|
+
enum: [
|
|
34
|
+
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&q=80',
|
|
35
|
+
'https://images.unsplash.com/photo-1519501025264-65ba15a82390?w=1920&q=80',
|
|
36
|
+
'https://images.unsplash.com/photo-1550684848-fac1c5b4e853?w=1920&q=80',
|
|
37
|
+
'https://images.unsplash.com/photo-1505118380757-91f5f5632de0?w=1920&q=80',
|
|
38
|
+
'https://images.unsplash.com/photo-1448375240586-882707db888b?w=1920&q=80',
|
|
39
|
+
],
|
|
40
|
+
enumNames: ['Mountains', 'Night City', 'Abstract Mesh', 'Ocean Waves', 'Forest Fog'],
|
|
41
|
+
description: 'Full-bleed background image (loaded via useImageLoader).',
|
|
42
|
+
},
|
|
43
|
+
gradient: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
title: 'CSS gradient',
|
|
46
|
+
description: 'Fallback when no image or image fails to load.',
|
|
47
|
+
},
|
|
48
|
+
overlay: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
title: 'Overlay color',
|
|
51
|
+
description: 'e.g. hsl(var(--background) / 0.6)',
|
|
52
|
+
},
|
|
53
|
+
blur: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
title: 'Backdrop blur',
|
|
56
|
+
description: 'e.g. 8px, 12px',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
form: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
title: 'Form options',
|
|
63
|
+
properties: {
|
|
64
|
+
showLogo: { type: 'boolean', title: 'Logo on success screen' },
|
|
65
|
+
showTerms: { type: 'boolean', title: 'Terms checkbox' },
|
|
66
|
+
showGithub: { type: 'boolean', title: 'GitHub OAuth button' },
|
|
67
|
+
showSupport: { type: 'boolean', title: 'Support link in footer' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const authCenteredUiSchema: CustomJsonUiSchema7 = {
|
|
74
|
+
background: {
|
|
75
|
+
'ui:collapsible': true,
|
|
76
|
+
'ui:groups': [
|
|
77
|
+
{ title: 'Toggle', fields: ['enabled'], defaultOpen: true },
|
|
78
|
+
{ title: 'Style', fields: ['imageUrl', 'gradient', 'overlay', 'blur'], defaultOpen: true },
|
|
79
|
+
],
|
|
80
|
+
enabled: { 'ui:widget': 'switch' },
|
|
81
|
+
imageUrl: {
|
|
82
|
+
'ui:widget': 'select',
|
|
83
|
+
'ui:disabledWhen': { path: 'background.enabled', eq: false },
|
|
84
|
+
},
|
|
85
|
+
gradient: { 'ui:disabledWhen': { path: 'background.enabled', eq: false } },
|
|
86
|
+
overlay: { 'ui:disabledWhen': { path: 'background.enabled', eq: false } },
|
|
87
|
+
blur: { 'ui:disabledWhen': { path: 'background.enabled', eq: false } },
|
|
88
|
+
},
|
|
89
|
+
form: {
|
|
90
|
+
'ui:collapsible': true,
|
|
91
|
+
showLogo: { 'ui:widget': 'switch' },
|
|
92
|
+
showTerms: { 'ui:widget': 'switch' },
|
|
93
|
+
showGithub: { 'ui:widget': 'switch' },
|
|
94
|
+
showSupport: { 'ui:widget': 'switch' },
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
99
|
+
// Split variant
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export const authSplitSchema: CustomJsonSchema7 = {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
background: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
title: 'Background',
|
|
108
|
+
properties: {
|
|
109
|
+
enabled: {
|
|
110
|
+
type: 'boolean',
|
|
111
|
+
title: 'Enable custom background',
|
|
112
|
+
description: 'When off, uses plain background.',
|
|
113
|
+
},
|
|
114
|
+
imageUrl: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
title: 'Image',
|
|
117
|
+
enum: [
|
|
118
|
+
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&q=80',
|
|
119
|
+
'https://images.unsplash.com/photo-1519501025264-65ba15a82390?w=1920&q=80',
|
|
120
|
+
'https://images.unsplash.com/photo-1550684848-fac1c5b4e853?w=1920&q=80',
|
|
121
|
+
'https://images.unsplash.com/photo-1505118380757-91f5f5632de0?w=1920&q=80',
|
|
122
|
+
'https://images.unsplash.com/photo-1448375240586-882707db888b?w=1920&q=80',
|
|
123
|
+
],
|
|
124
|
+
enumNames: ['Mountains', 'Night City', 'Abstract Mesh', 'Ocean Waves', 'Forest Fog'],
|
|
125
|
+
description: 'Full-bleed background image (loaded via useImageLoader).',
|
|
126
|
+
},
|
|
127
|
+
gradient: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
title: 'CSS gradient',
|
|
130
|
+
description: 'Fallback when no image or image fails to load.',
|
|
131
|
+
},
|
|
132
|
+
overlay: {
|
|
133
|
+
type: 'string',
|
|
134
|
+
title: 'Overlay color',
|
|
135
|
+
description: 'e.g. hsl(var(--background) / 0.6)',
|
|
136
|
+
},
|
|
137
|
+
blur: {
|
|
138
|
+
type: 'string',
|
|
139
|
+
title: 'Backdrop blur',
|
|
140
|
+
description: 'e.g. 8px, 12px',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
sidebar: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
title: 'Sidebar',
|
|
147
|
+
properties: {
|
|
148
|
+
enabled: {
|
|
149
|
+
type: 'boolean',
|
|
150
|
+
title: 'Show sidebar',
|
|
151
|
+
description: 'Right column content — visible on desktop only.',
|
|
152
|
+
},
|
|
153
|
+
showQuote: { type: 'boolean', title: 'Show quote' },
|
|
154
|
+
showAuthor: { type: 'boolean', title: 'Show author' },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
form: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
title: 'Form options',
|
|
160
|
+
properties: {
|
|
161
|
+
showLogo: { type: 'boolean', title: 'Logo on success screen' },
|
|
162
|
+
showTerms: { type: 'boolean', title: 'Terms checkbox' },
|
|
163
|
+
showGithub: { type: 'boolean', title: 'GitHub OAuth button' },
|
|
164
|
+
showSupport: { type: 'boolean', title: 'Support link in footer' },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const authSplitUiSchema: CustomJsonUiSchema7 = {
|
|
171
|
+
background: {
|
|
172
|
+
'ui:collapsible': true,
|
|
173
|
+
'ui:groups': [
|
|
174
|
+
{ title: 'Toggle', fields: ['enabled'], defaultOpen: true },
|
|
175
|
+
{ title: 'Style', fields: ['imageUrl', 'gradient', 'overlay', 'blur'], defaultOpen: true },
|
|
176
|
+
],
|
|
177
|
+
enabled: { 'ui:widget': 'switch' },
|
|
178
|
+
imageUrl: {
|
|
179
|
+
'ui:widget': 'select',
|
|
180
|
+
'ui:disabledWhen': { path: 'background.enabled', eq: false },
|
|
181
|
+
},
|
|
182
|
+
gradient: { 'ui:disabledWhen': { path: 'background.enabled', eq: false } },
|
|
183
|
+
overlay: { 'ui:disabledWhen': { path: 'background.enabled', eq: false } },
|
|
184
|
+
blur: { 'ui:disabledWhen': { path: 'background.enabled', eq: false } },
|
|
185
|
+
},
|
|
186
|
+
sidebar: {
|
|
187
|
+
'ui:collapsible': true,
|
|
188
|
+
enabled: { 'ui:widget': 'switch' },
|
|
189
|
+
showQuote: {
|
|
190
|
+
'ui:widget': 'switch',
|
|
191
|
+
'ui:disabledWhen': { path: 'sidebar.enabled', eq: false },
|
|
192
|
+
},
|
|
193
|
+
showAuthor: {
|
|
194
|
+
'ui:widget': 'switch',
|
|
195
|
+
'ui:disabledWhen': { path: 'sidebar.enabled', eq: false },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
form: {
|
|
199
|
+
'ui:collapsible': true,
|
|
200
|
+
showLogo: { 'ui:widget': 'switch' },
|
|
201
|
+
showTerms: { 'ui:widget': 'switch' },
|
|
202
|
+
showGithub: { 'ui:widget': 'switch' },
|
|
203
|
+
showSupport: { 'ui:widget': 'switch' },
|
|
204
|
+
},
|
|
205
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthLayout configurator types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type AuthShellVariant = 'centered' | 'split';
|
|
6
|
+
export type PresetKind = 'centered' | 'split' | 'custom';
|
|
7
|
+
|
|
8
|
+
export interface AuthBackgroundConfig {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
imageUrl: string;
|
|
11
|
+
gradient: string;
|
|
12
|
+
overlay: string;
|
|
13
|
+
blur: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AuthSidebarConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
showQuote: boolean;
|
|
19
|
+
showAuthor: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AuthFormConfig {
|
|
23
|
+
showLogo: boolean;
|
|
24
|
+
showTerms: boolean;
|
|
25
|
+
showGithub: boolean;
|
|
26
|
+
showSupport: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Full config used by the playground and embed page. */
|
|
30
|
+
export interface AuthPlaygroundConfig {
|
|
31
|
+
variant: AuthShellVariant;
|
|
32
|
+
background: AuthBackgroundConfig;
|
|
33
|
+
sidebar: AuthSidebarConfig;
|
|
34
|
+
form: AuthFormConfig;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Centered-only subset (no sidebar). */
|
|
38
|
+
export interface AuthCenteredConfig {
|
|
39
|
+
background: AuthBackgroundConfig;
|
|
40
|
+
form: AuthFormConfig;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Split-only subset (always has sidebar + background). */
|
|
44
|
+
export interface AuthSplitConfig {
|
|
45
|
+
background: AuthBackgroundConfig;
|
|
46
|
+
sidebar: AuthSidebarConfig;
|
|
47
|
+
form: AuthFormConfig;
|
|
48
|
+
}
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Public surface today:
|
|
8
8
|
* - `private` — `PrivateLayout` configurator (shell + sidebar + header)
|
|
9
|
+
* - `auth` — `AuthLayout` configurator (centered / split variants)
|
|
9
10
|
*
|
|
10
11
|
* Public + theme configurators are not exported yet — they currently live in
|
|
11
12
|
* the demo app while the schema shape is iterated on.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
export * from './private';
|
|
16
|
+
export * from './auth';
|
|
@@ -192,7 +192,8 @@ export function BaseApp({
|
|
|
192
192
|
<AuthDialog authPath={auth?.routes?.auth} />
|
|
193
193
|
|
|
194
194
|
{/* Debug Panel — auto in dev, ?debug=1 in prod, disable with debug={{ enabled: false }} */}
|
|
195
|
-
|
|
195
|
+
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
196
|
+
<DebugButton enabled={debugEnabled} {...(debugProps as any)} />
|
|
196
197
|
</ErrorTrackingProvider>
|
|
197
198
|
</PwaProvider>
|
|
198
199
|
</CentrifugoProvider>
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth Layout
|
|
2
|
+
* Auth Layout
|
|
3
|
+
*
|
|
4
|
+
* Shell-based authentication layout with two variants:
|
|
5
|
+
* - `centered` (default) — Apple-style frameless, centered, glow background
|
|
6
|
+
* - `split` — Two-column on desktop (form + sidebar), single-column on mobile
|
|
3
7
|
*
|
|
4
|
-
* Minimal, clean authentication layout with smooth animations.
|
|
5
8
|
* Supports: email/phone OTP, OAuth (GitHub), 2FA (TOTP + backup codes)
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
'use client';
|
|
9
12
|
|
|
10
|
-
import React, { createContext, useContext
|
|
13
|
+
import React, { createContext, useContext } from 'react';
|
|
11
14
|
|
|
12
15
|
import { useCfgRouter } from '@djangocfg/api/auth';
|
|
13
16
|
import { useAppT } from '@djangocfg/i18n';
|
|
14
17
|
|
|
15
|
-
import { GlowBackground } from '@djangocfg/ui-core/components';
|
|
16
|
-
|
|
17
18
|
import { Suspense } from '../../components';
|
|
18
19
|
import { OAuthCallback } from './components/oauth';
|
|
19
20
|
import { IdentifierStep, OTPStep, SetupStep, TwoFactorStep } from './components/steps';
|
|
20
21
|
import { AUTH } from './constants';
|
|
21
22
|
import { AuthFormProvider, useAuthFormContext } from './context';
|
|
23
|
+
import { AuthShell } from './shells';
|
|
22
24
|
|
|
23
25
|
import './styles/auth.css';
|
|
26
|
+
import './styles/centered-shell.css';
|
|
27
|
+
import './styles/split-shell.css';
|
|
24
28
|
|
|
25
29
|
import type { AuthLayoutProps } from './types';
|
|
26
30
|
|
|
27
|
-
|
|
28
31
|
// ─── Layout-level context (header suppression, etc.) ─────────────────────────
|
|
29
32
|
|
|
30
33
|
interface AuthLayoutContextValue {
|
|
@@ -39,8 +42,19 @@ export const useAuthLayoutContext = (): AuthLayoutContextValue => useContext(Aut
|
|
|
39
42
|
// ─── Layout ──────────────────────────────────────────────────────────────────
|
|
40
43
|
|
|
41
44
|
export const AuthLayout: React.FC<AuthLayoutProps> = (props) => {
|
|
42
|
-
const {
|
|
43
|
-
|
|
45
|
+
const {
|
|
46
|
+
variant = 'centered',
|
|
47
|
+
background,
|
|
48
|
+
sidebar,
|
|
49
|
+
enableGithubAuth,
|
|
50
|
+
redirectUrl = AUTH.DEFAULT_REDIRECT,
|
|
51
|
+
onOAuthSuccess,
|
|
52
|
+
onError,
|
|
53
|
+
className,
|
|
54
|
+
children,
|
|
55
|
+
} = props;
|
|
56
|
+
|
|
57
|
+
const hideHeader = Boolean(children);
|
|
44
58
|
|
|
45
59
|
return (
|
|
46
60
|
<Suspense>
|
|
@@ -49,26 +63,21 @@ export const AuthLayout: React.FC<AuthLayoutProps> = (props) => {
|
|
|
49
63
|
{/* Full-screen success overlay */}
|
|
50
64
|
<AuthSuccessOverlay />
|
|
51
65
|
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<AuthHeaderSlot>{props.children}</AuthHeaderSlot>
|
|
69
|
-
<AuthContent />
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
66
|
+
<AuthShell variant={variant} background={background} sidebar={sidebar} className={className}>
|
|
67
|
+
{/* Handle OAuth callback when GitHub auth is enabled */}
|
|
68
|
+
{enableGithubAuth && (
|
|
69
|
+
<Suspense fallback={null}>
|
|
70
|
+
<OAuthCallback
|
|
71
|
+
redirectUrl={redirectUrl}
|
|
72
|
+
onSuccess={onOAuthSuccess ? (user, isNewUser) => onOAuthSuccess(user, isNewUser, 'github') : undefined}
|
|
73
|
+
onError={onError}
|
|
74
|
+
/>
|
|
75
|
+
</Suspense>
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
<AuthHeaderSlot>{children}</AuthHeaderSlot>
|
|
79
|
+
<AuthContent />
|
|
80
|
+
</AuthShell>
|
|
72
81
|
</AuthLayoutContext.Provider>
|
|
73
82
|
</AuthFormProvider>
|
|
74
83
|
</Suspense>
|
|
@@ -131,11 +140,11 @@ const AuthSuccess: React.FC<AuthSuccessInlineProps> = ({
|
|
|
131
140
|
}) => {
|
|
132
141
|
const router = useCfgRouter();
|
|
133
142
|
const t = useAppT();
|
|
134
|
-
const [isVisible, setIsVisible] = useState(false);
|
|
143
|
+
const [isVisible, setIsVisible] = React.useState(false);
|
|
135
144
|
|
|
136
145
|
const successMessage = React.useMemo(() => t('layouts.auth.success.message'), [t]);
|
|
137
146
|
|
|
138
|
-
useEffect(() => {
|
|
147
|
+
React.useEffect(() => {
|
|
139
148
|
const animTimer = setTimeout(() => setIsVisible(true), AUTH.ANIMATION_START_DELAY);
|
|
140
149
|
const redirectTimer = setTimeout(() => {
|
|
141
150
|
const finalUrl = redirectUrl || AUTH.DEFAULT_REDIRECT;
|