@djangocfg/layouts 2.1.356 → 2.1.358
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/package.json +21 -19
- package/src/configurator/private/schema.ts +12 -0
- package/src/layouts/AdminLayout/AdminLayout.tsx +2 -1
- package/src/layouts/AppLayout/AppLayout.tsx +35 -15
- package/src/layouts/AppLayout/BaseApp.tsx +2 -2
- package/src/layouts/AuthLayout/AuthLayout.tsx +26 -19
- package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +10 -4
- package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +10 -10
- package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -5
- package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +10 -10
- package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +28 -20
- package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +11 -5
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +12 -4
- package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +9 -4
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +12 -5
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +9 -4
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +11 -5
- package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +15 -5
- package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +9 -4
- package/src/layouts/AuthLayout/context.tsx +35 -13
- package/src/layouts/AuthLayout/shells/AuthShell.tsx +11 -4
- package/src/layouts/AuthLayout/shells/CenteredShell.tsx +10 -4
- package/src/layouts/AuthLayout/shells/SplitShell.tsx +10 -4
- package/src/layouts/AuthLayout/shells/context.tsx +16 -5
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +45 -248
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +113 -430
- package/src/layouts/{_components → PrivateLayout/components}/PrivateSidebarAccount.tsx +82 -105
- package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +168 -0
- package/src/layouts/{_components → PrivateLayout/components}/SidebarFeatured.tsx +2 -2
- package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +189 -0
- package/src/layouts/PrivateLayout/components/SidebarNavItem.tsx +137 -0
- package/src/layouts/PrivateLayout/components/SidebarSlots.tsx +71 -0
- package/src/layouts/PrivateLayout/components/index.ts +4 -0
- package/src/layouts/PrivateLayout/context.tsx +211 -0
- package/src/layouts/PrivateLayout/density.ts +48 -0
- package/src/layouts/PrivateLayout/hooks/index.ts +14 -0
- package/src/layouts/PrivateLayout/hooks/useAuthGuard.ts +54 -0
- package/src/layouts/PrivateLayout/hooks/useHoverExpand.ts +110 -0
- package/src/layouts/PrivateLayout/hooks/useLayoutVisual.ts +113 -0
- package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +207 -0
- package/src/layouts/PrivateLayout/hooks/useSidebarDefaultOpen.ts +21 -0
- package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +115 -0
- package/src/layouts/PrivateLayout/index.ts +2 -2
- package/src/layouts/PrivateLayout/types.ts +193 -0
- package/src/layouts/ProfileLayout/ProfileDialog/ProfileDialog.tsx +32 -0
- package/src/layouts/ProfileLayout/ProfileDialog/index.ts +2 -0
- package/src/layouts/ProfileLayout/ProfileDialog/store.ts +19 -0
- package/src/layouts/ProfileLayout/{context.tsx → ProfileForm/context.tsx} +8 -8
- package/src/layouts/ProfileLayout/ProfileForm/index.tsx +148 -0
- package/src/layouts/ProfileLayout/README.md +118 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/ApiKeySection.tsx +197 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/context.tsx +159 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/index.ts +3 -0
- package/src/layouts/ProfileLayout/components/EditableField.tsx +1 -1
- package/src/layouts/ProfileLayout/components/PreferencesSection.tsx +56 -0
- package/src/layouts/ProfileLayout/components/ProfileHeader.tsx +110 -0
- package/src/layouts/ProfileLayout/components/ProfileTab.tsx +35 -0
- package/src/layouts/ProfileLayout/components/{TwoFactorSection.tsx → TwoFactorSection/TwoFactorSection.tsx} +1 -1
- package/src/layouts/ProfileLayout/components/TwoFactorSection/index.ts +1 -0
- package/src/layouts/ProfileLayout/components/index.ts +5 -2
- package/src/layouts/ProfileLayout/hooks/index.ts +2 -0
- package/src/layouts/ProfileLayout/hooks/useProfileTabs.ts +48 -0
- package/src/layouts/ProfileLayout/index.ts +7 -3
- package/src/layouts/ProfileLayout/types.ts +47 -0
- package/src/layouts/{_components → PublicLayout/components}/UserMenu.tsx +3 -3
- package/src/layouts/PublicLayout/components/index.ts +4 -0
- package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +12 -2
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +1 -1
- package/src/layouts/PublicLayout/primitives/NavActions.tsx +44 -3
- package/src/layouts/PublicLayout/primitives/NavBrand.tsx +4 -2
- package/src/layouts/PublicLayout/primitives/NavDesktopItems.tsx +42 -2
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +1 -1
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +60 -1
- package/src/layouts/_components/index.ts +2 -6
- package/src/layouts/index.ts +9 -4
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +0 -284
- package/src/layouts/ProfileLayout/__tests__/TwoFactorSection.test.tsx +0 -234
- package/src/layouts/ProfileLayout/components/ProfileForm.tsx +0 -198
- /package/src/layouts/{_components → PublicLayout/components}/UserAvatar.tsx +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { memo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { OTPInput } from '@djangocfg/ui-core/components';
|
|
6
6
|
|
|
@@ -17,11 +17,15 @@ export interface AuthOTPInputProps {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* AuthOTPInput - Reusable OTP input with consistent styling
|
|
20
|
+
* AuthOTPInput - Reusable OTP input with consistent styling.
|
|
21
21
|
*
|
|
22
|
-
* Used in: OTPStep (email, 4 digits), TwoFactorStep (TOTP, 6 digits), SetupStep (TOTP, 6 digits)
|
|
22
|
+
* Used in: OTPStep (email, 4 digits), TwoFactorStep (TOTP, 6 digits), SetupStep (TOTP, 6 digits).
|
|
23
|
+
*
|
|
24
|
+
* Memoised: re-renders only when value, disabled, autoFocus, size or length
|
|
25
|
+
* change. `onChange` and `onComplete` are compared by reference — callers
|
|
26
|
+
* should stabilise them with useCallback.
|
|
23
27
|
*/
|
|
24
|
-
|
|
28
|
+
function AuthOTPInputRaw({
|
|
25
29
|
value,
|
|
26
30
|
onChange,
|
|
27
31
|
onComplete,
|
|
@@ -29,19 +33,23 @@ export const AuthOTPInput: React.FC<AuthOTPInputProps> = ({
|
|
|
29
33
|
autoFocus = true,
|
|
30
34
|
size,
|
|
31
35
|
length = AUTH.TOTP_LENGTH,
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
}: AuthOTPInputProps) {
|
|
37
|
+
return (
|
|
38
|
+
<div className="auth-otp-container auth-otp-wrapper">
|
|
39
|
+
<OTPInput
|
|
40
|
+
length={length}
|
|
41
|
+
validationMode="numeric"
|
|
42
|
+
pasteBehavior="clean"
|
|
43
|
+
value={value}
|
|
44
|
+
onChange={onChange}
|
|
45
|
+
onComplete={onComplete}
|
|
46
|
+
disabled={disabled}
|
|
47
|
+
autoFocus={autoFocus}
|
|
48
|
+
size={size}
|
|
49
|
+
fluid
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const AuthOTPInput = memo(AuthOTPInputRaw);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useMemo } from 'react';
|
|
3
|
+
import React, { memo, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useAppT } from '@djangocfg/i18n';
|
|
6
6
|
import { Checkbox } from '@djangocfg/ui-core/components';
|
|
@@ -15,16 +15,20 @@ export interface TermsCheckboxProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* TermsCheckbox - Compact terms acceptance checkbox
|
|
18
|
+
* TermsCheckbox - Compact terms acceptance checkbox.
|
|
19
|
+
*
|
|
20
|
+
* Memoised: re-renders only when checked, termsUrl, privacyUrl, disabled
|
|
21
|
+
* or className change. `onChange` is compared by reference — callers
|
|
22
|
+
* should stabilise it with useCallback.
|
|
19
23
|
*/
|
|
20
|
-
|
|
24
|
+
function TermsCheckboxRaw({
|
|
21
25
|
checked,
|
|
22
26
|
onChange,
|
|
23
27
|
termsUrl,
|
|
24
28
|
privacyUrl,
|
|
25
29
|
disabled = false,
|
|
26
30
|
className = '',
|
|
27
|
-
})
|
|
31
|
+
}: TermsCheckboxProps) {
|
|
28
32
|
const t = useAppT();
|
|
29
33
|
const labels = useMemo(() => ({
|
|
30
34
|
agree: t('layouts.auth.terms.agree'),
|
|
@@ -63,4 +67,6 @@ export const TermsCheckbox: React.FC<TermsCheckboxProps> = ({
|
|
|
63
67
|
</label>
|
|
64
68
|
</div>
|
|
65
69
|
);
|
|
66
|
-
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const TermsCheckbox = memo(TermsCheckboxRaw);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Github } from 'lucide-react';
|
|
4
|
-
import React, { useMemo } from 'react';
|
|
4
|
+
import React, { memo, useMemo } from 'react';
|
|
5
5
|
|
|
6
6
|
import { useGithubAuth } from '@djangocfg/api/auth';
|
|
7
7
|
import { useAppT } from '@djangocfg/i18n';
|
|
@@ -20,15 +20,21 @@ import {
|
|
|
20
20
|
} from '../shared';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* IdentifierStep - Apple-style email input step
|
|
23
|
+
* IdentifierStep - Apple-style email input step.
|
|
24
24
|
*
|
|
25
25
|
* Clean, minimal design with:
|
|
26
26
|
* - Optional logo
|
|
27
27
|
* - Single email input field
|
|
28
28
|
* - Terms checkbox
|
|
29
29
|
* - OAuth options
|
|
30
|
+
*
|
|
31
|
+
* Memoised: this component has no props; it reads everything from
|
|
32
|
+
* context. The memo wrapper prevents re-renders when the parent
|
|
33
|
+
* (AuthContent) re-renders due to step changes in a sibling branch.
|
|
34
|
+
* Internal context selectors still trigger updates when relevant
|
|
35
|
+
* fields change.
|
|
30
36
|
*/
|
|
31
|
-
|
|
37
|
+
function IdentifierStepRaw() {
|
|
32
38
|
const { hideHeader } = useAuthLayoutContext();
|
|
33
39
|
|
|
34
40
|
const {
|
|
@@ -131,4 +137,6 @@ export const IdentifierStep: React.FC = () => {
|
|
|
131
137
|
/>
|
|
132
138
|
</AuthContainer>
|
|
133
139
|
);
|
|
134
|
-
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const IdentifierStep = memo(IdentifierStepRaw);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import React, { memo, useCallback, useMemo, useState } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useAppT } from '@djangocfg/i18n';
|
|
6
6
|
|
|
@@ -19,15 +19,18 @@ import {
|
|
|
19
19
|
type SubmitState = 'idle' | 'submitting';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* OTPStep - Apple-style OTP verification
|
|
22
|
+
* OTPStep - Apple-style OTP verification.
|
|
23
23
|
*
|
|
24
24
|
* Minimal design focused on the OTP input:
|
|
25
25
|
* - Clear title
|
|
26
26
|
* - Masked identifier for privacy
|
|
27
27
|
* - Large OTP input
|
|
28
28
|
* - Text links for actions
|
|
29
|
+
*
|
|
30
|
+
* Memoised: no props; reads from context. Prevents re-renders when
|
|
31
|
+
* AuthContent switches steps or when sibling branches update.
|
|
29
32
|
*/
|
|
30
|
-
|
|
33
|
+
function OTPStepRaw() {
|
|
31
34
|
const {
|
|
32
35
|
identifier,
|
|
33
36
|
otp,
|
|
@@ -121,4 +124,6 @@ export const OTPStep: React.FC = () => {
|
|
|
121
124
|
</form>
|
|
122
125
|
</AuthContainer>
|
|
123
126
|
);
|
|
124
|
-
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const OTPStep = memo(OTPStepRaw);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Check, Copy } from 'lucide-react';
|
|
4
|
-
import React, { useMemo } from 'react';
|
|
4
|
+
import React, { memo, useMemo } from 'react';
|
|
5
5
|
|
|
6
6
|
import { useAppT } from '@djangocfg/i18n';
|
|
7
7
|
|
|
@@ -15,13 +15,18 @@ interface SetupCompleteProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* SetupComplete - Backup codes display after 2FA setup
|
|
18
|
+
* SetupComplete - Backup codes display after 2FA setup.
|
|
19
|
+
*
|
|
20
|
+
* Memoised: re-renders only when `backupCodes` array reference,
|
|
21
|
+
* `backupCodesWarning`, or `onDone` change. The array content is
|
|
22
|
+
* compared by reference — the parent should not recreate the array
|
|
23
|
+
* on every render.
|
|
19
24
|
*/
|
|
20
|
-
|
|
25
|
+
function SetupCompleteRaw({
|
|
21
26
|
backupCodes,
|
|
22
27
|
backupCodesWarning,
|
|
23
28
|
onDone,
|
|
24
|
-
})
|
|
29
|
+
}: SetupCompleteProps) {
|
|
25
30
|
const { copied, copy } = useCopyToClipboard();
|
|
26
31
|
const t = useAppT();
|
|
27
32
|
const content = useMemo(() => ({
|
|
@@ -76,4 +81,6 @@ export const SetupComplete: React.FC<SetupCompleteProps> = ({
|
|
|
76
81
|
</div>
|
|
77
82
|
</AuthContainer>
|
|
78
83
|
);
|
|
79
|
-
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const SetupComplete = memo(SetupCompleteRaw);
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useMemo } from 'react';
|
|
3
|
+
import React, { memo, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useAppT } from '@djangocfg/i18n';
|
|
6
6
|
|
|
7
7
|
import { AuthButton, AuthContainer, AuthHeader } from '../../shared';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* SetupLoading - Loading state for 2FA setup
|
|
10
|
+
* SetupLoading - Loading state for 2FA setup.
|
|
11
|
+
*
|
|
12
|
+
* Memoised: no props; pure translation-driven component. Prevents
|
|
13
|
+
* re-renders when parent orchestrators re-render for unrelated reasons.
|
|
11
14
|
*/
|
|
12
|
-
|
|
15
|
+
function SetupLoadingRaw() {
|
|
13
16
|
const t = useAppT();
|
|
14
17
|
const content = useMemo(() => ({
|
|
15
18
|
title: t('layouts.auth.setup.loading.title'),
|
|
@@ -26,4 +29,6 @@ export const SetupLoading: React.FC = () => {
|
|
|
26
29
|
</div>
|
|
27
30
|
</AuthContainer>
|
|
28
31
|
);
|
|
29
|
-
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const SetupLoading = memo(SetupLoadingRaw);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Check, Copy } from 'lucide-react';
|
|
4
|
-
import React, { useMemo, useState } from 'react';
|
|
4
|
+
import React, { memo, useMemo, useState } from 'react';
|
|
5
5
|
import { QRCodeSVG } from 'qrcode.react';
|
|
6
6
|
|
|
7
7
|
import { useAppT } from '@djangocfg/i18n';
|
|
@@ -32,16 +32,20 @@ interface SetupQRCodeProps {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* SetupQRCode - QR code scanning step for 2FA setup
|
|
35
|
+
* SetupQRCode - QR code scanning step for 2FA setup.
|
|
36
|
+
*
|
|
37
|
+
* Memoised: re-renders only when `provisioningUri`, `secret`, `isLoading`,
|
|
38
|
+
* `error`, `onConfirm` or `onSkip` change. `onConfirm` and `onSkip` are
|
|
39
|
+
* compared by reference — callers should stabilise them with useCallback.
|
|
36
40
|
*/
|
|
37
|
-
|
|
41
|
+
function SetupQRCodeRaw({
|
|
38
42
|
provisioningUri,
|
|
39
43
|
secret,
|
|
40
44
|
isLoading,
|
|
41
45
|
error,
|
|
42
46
|
onConfirm,
|
|
43
47
|
onSkip,
|
|
44
|
-
})
|
|
48
|
+
}: SetupQRCodeProps) {
|
|
45
49
|
const [confirmCode, setConfirmCode] = useState('');
|
|
46
50
|
const { copied, copy } = useCopyToClipboard();
|
|
47
51
|
const t = useAppT();
|
|
@@ -132,4 +136,6 @@ export const SetupQRCode: React.FC<SetupQRCodeProps> = ({
|
|
|
132
136
|
</form>
|
|
133
137
|
</AuthContainer>
|
|
134
138
|
);
|
|
135
|
-
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const SetupQRCode = memo(SetupQRCodeRaw);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { memo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useTwoFactorSetup } from '@djangocfg/api/auth';
|
|
6
6
|
|
|
@@ -17,8 +17,11 @@ export interface SetupStepProps {
|
|
|
17
17
|
/**
|
|
18
18
|
* SetupStep - Orchestrator for 2FA setup flow (requires AuthFormContext).
|
|
19
19
|
* For use outside AuthLayout use SetupStepStandalone.
|
|
20
|
+
*
|
|
21
|
+
* Memoised: re-renders only when `onComplete` or `onSkip` references
|
|
22
|
+
* change. Callers should stabilise these callbacks with useCallback.
|
|
20
23
|
*/
|
|
21
|
-
|
|
24
|
+
function SetupStepRaw({ onComplete, onSkip }: SetupStepProps) {
|
|
22
25
|
const { setStep } = useAuthFormContext();
|
|
23
26
|
|
|
24
27
|
const handleComplete = () => {
|
|
@@ -84,13 +87,18 @@ export const SetupStep: React.FC<SetupStepProps> = ({ onComplete, onSkip }) => {
|
|
|
84
87
|
|
|
85
88
|
// Fallback to loading
|
|
86
89
|
return <SetupLoading />;
|
|
87
|
-
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const SetupStep = memo(SetupStepRaw);
|
|
88
93
|
|
|
89
94
|
/**
|
|
90
95
|
* SetupStepStandalone — same flow as SetupStep but without AuthFormContext.
|
|
91
96
|
* Use this inside ProfileLayout or any page outside AuthLayout.
|
|
97
|
+
*
|
|
98
|
+
* Memoised: re-renders only when `onComplete` or `onSkip` references
|
|
99
|
+
* change. Callers should stabilise these callbacks with useCallback.
|
|
92
100
|
*/
|
|
93
|
-
|
|
101
|
+
function SetupStepStandaloneRaw({ onComplete, onSkip }: SetupStepProps) {
|
|
94
102
|
const {
|
|
95
103
|
isLoading,
|
|
96
104
|
error,
|
|
@@ -132,4 +140,6 @@ export const SetupStepStandalone: React.FC<SetupStepProps> = ({ onComplete, onSk
|
|
|
132
140
|
}
|
|
133
141
|
|
|
134
142
|
return <SetupLoading />;
|
|
135
|
-
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const SetupStepStandalone = memo(SetupStepStandaloneRaw);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useMemo } from 'react';
|
|
3
|
+
import React, { memo, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useAppT } from '@djangocfg/i18n';
|
|
6
6
|
|
|
@@ -16,14 +16,17 @@ import {
|
|
|
16
16
|
} from '../shared';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* TwoFactorStep - Apple-style 2FA verification
|
|
19
|
+
* TwoFactorStep - Apple-style 2FA verification.
|
|
20
20
|
*
|
|
21
21
|
* Minimal design with:
|
|
22
22
|
* - TOTP code input (default)
|
|
23
23
|
* - Backup code input (alternate)
|
|
24
24
|
* - Clean toggle between modes
|
|
25
|
+
*
|
|
26
|
+
* Memoised: no props; reads from context. Prevents re-renders when
|
|
27
|
+
* AuthContent switches steps or sibling branches update.
|
|
25
28
|
*/
|
|
26
|
-
|
|
29
|
+
function TwoFactorStepRaw() {
|
|
27
30
|
const {
|
|
28
31
|
twoFactorCode,
|
|
29
32
|
useBackupCode,
|
|
@@ -125,4 +128,6 @@ export const TwoFactorStep: React.FC = () => {
|
|
|
125
128
|
</div>
|
|
126
129
|
</AuthContainer>
|
|
127
130
|
);
|
|
128
|
-
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const TwoFactorStep = memo(TwoFactorStepRaw);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, useContext } from 'react';
|
|
3
|
+
import React, { createContext, useContext, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useAuthForm } from '@djangocfg/api/auth';
|
|
6
6
|
|
|
@@ -8,6 +8,14 @@ import type { AuthFormContextType, AuthLayoutProps } from './types';
|
|
|
8
8
|
|
|
9
9
|
const AuthFormContext = createContext<AuthFormContextType | undefined>(undefined);
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* AuthFormProvider — wraps useAuthForm and merges UI config into context.
|
|
13
|
+
*
|
|
14
|
+
* The context value is memoised so that object reference churn from the
|
|
15
|
+
* hook does not re-render the entire auth subtree when only internal
|
|
16
|
+
* form state (e.g. `isLoading`) changes. Consumers that select specific
|
|
17
|
+
* fields via useAuthFormContext will still re-render as expected.
|
|
18
|
+
*/
|
|
11
19
|
export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
|
|
12
20
|
children,
|
|
13
21
|
sourceUrl: sourceUrlProp,
|
|
@@ -38,18 +46,32 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
|
|
|
38
46
|
enable2FASetup,
|
|
39
47
|
});
|
|
40
48
|
|
|
41
|
-
const value: AuthFormContextType =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const value: AuthFormContextType = useMemo(
|
|
50
|
+
() => ({
|
|
51
|
+
...authForm,
|
|
52
|
+
// UI-specific configuration
|
|
53
|
+
sourceUrl,
|
|
54
|
+
supportUrl,
|
|
55
|
+
termsUrl,
|
|
56
|
+
privacyUrl,
|
|
57
|
+
enableGithubAuth,
|
|
58
|
+
enable2FASetup,
|
|
59
|
+
logoUrl,
|
|
60
|
+
redirectUrl,
|
|
61
|
+
}),
|
|
62
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
63
|
+
[
|
|
64
|
+
authForm,
|
|
65
|
+
sourceUrl,
|
|
66
|
+
supportUrl,
|
|
67
|
+
termsUrl,
|
|
68
|
+
privacyUrl,
|
|
69
|
+
enableGithubAuth,
|
|
70
|
+
enable2FASetup,
|
|
71
|
+
logoUrl,
|
|
72
|
+
redirectUrl,
|
|
73
|
+
]
|
|
74
|
+
);
|
|
53
75
|
|
|
54
76
|
return <AuthFormContext.Provider value={value}>{children}</AuthFormContext.Provider>;
|
|
55
77
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useMemo } from 'react';
|
|
3
|
+
import React, { memo, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useImageLoader } from '@djangocfg/ui-core/hooks';
|
|
6
6
|
|
|
@@ -18,14 +18,19 @@ import type { AuthShellProps } from './types';
|
|
|
18
18
|
* - variant dispatch (centered | split)
|
|
19
19
|
*
|
|
20
20
|
* Variants own their own chrome (positioning, shape, background).
|
|
21
|
+
*
|
|
22
|
+
* Memoised: re-renders only when `variant`, `background`, `sidebar`,
|
|
23
|
+
* `className` or `children` reference change. Background styles are
|
|
24
|
+
* memoised internally so object reference churn from the parent does
|
|
25
|
+
* not cause shell re-renders.
|
|
21
26
|
*/
|
|
22
|
-
|
|
27
|
+
function AuthShellRaw({
|
|
23
28
|
variant,
|
|
24
29
|
children,
|
|
25
30
|
background,
|
|
26
31
|
sidebar,
|
|
27
32
|
className,
|
|
28
|
-
})
|
|
33
|
+
}: AuthShellProps) {
|
|
29
34
|
const { isLoaded: bgLoaded, hasError: bgError } = useImageLoader(background?.imageUrl);
|
|
30
35
|
|
|
31
36
|
const hasBgImage = Boolean(background?.imageUrl) && bgLoaded && !bgError;
|
|
@@ -77,4 +82,6 @@ export const AuthShell: React.FC<AuthShellProps> = ({
|
|
|
77
82
|
)}
|
|
78
83
|
</AuthShellProvider>
|
|
79
84
|
);
|
|
80
|
-
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const AuthShell = memo(AuthShellRaw);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { memo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { GlowBackground } from '@djangocfg/ui-core/components';
|
|
6
6
|
|
|
@@ -14,13 +14,17 @@ import type { ShellRenderProps } from './types';
|
|
|
14
14
|
* - Glow background (default)
|
|
15
15
|
* - Optional custom background image/gradient via shell props
|
|
16
16
|
* - No visible card chrome
|
|
17
|
+
*
|
|
18
|
+
* Memoised: re-renders only when bgStyle/overlayStyle references or
|
|
19
|
+
* children change. The background layer is cheap to recreate; the main
|
|
20
|
+
* win is preserving the content subtree when parent re-renders.
|
|
17
21
|
*/
|
|
18
|
-
|
|
22
|
+
function CenteredShellRaw({
|
|
19
23
|
children,
|
|
20
24
|
className,
|
|
21
25
|
bgStyle,
|
|
22
26
|
overlayStyle,
|
|
23
|
-
})
|
|
27
|
+
}: ShellRenderProps) {
|
|
24
28
|
const hasCustomBg = Boolean(bgStyle);
|
|
25
29
|
|
|
26
30
|
return (
|
|
@@ -43,4 +47,6 @@ export const CenteredShell: React.FC<ShellRenderProps> = ({
|
|
|
43
47
|
</div>
|
|
44
48
|
</div>
|
|
45
49
|
);
|
|
46
|
-
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const CenteredShell = memo(CenteredShellRaw);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { memo } from 'react';
|
|
4
4
|
|
|
5
5
|
import type { ShellRenderProps } from './types';
|
|
6
6
|
|
|
@@ -16,15 +16,19 @@ interface SplitShellProps extends ShellRenderProps {
|
|
|
16
16
|
* - Desktop (>= xl): two columns inside a card — form left, sidebar right
|
|
17
17
|
*
|
|
18
18
|
* Background: full-bleed image/gradient with optional overlay + blur.
|
|
19
|
+
*
|
|
20
|
+
* Memoised: re-renders only when bgStyle, overlayStyle, blurValue or
|
|
21
|
+
* children references change. `sidebar` is compared by reference — the
|
|
22
|
+
* consumer should stabilise it (e.g. wrap in useMemo) to avoid re-renders.
|
|
19
23
|
*/
|
|
20
|
-
|
|
24
|
+
function SplitShellRaw({
|
|
21
25
|
children,
|
|
22
26
|
className,
|
|
23
27
|
bgStyle,
|
|
24
28
|
overlayStyle,
|
|
25
29
|
blurValue,
|
|
26
30
|
sidebar,
|
|
27
|
-
})
|
|
31
|
+
}: SplitShellProps) {
|
|
28
32
|
return (
|
|
29
33
|
<div className={`auth-shell-split ${className || ''}`}>
|
|
30
34
|
{/* Background layer */}
|
|
@@ -60,4 +64,6 @@ export const SplitShell: React.FC<SplitShellProps> = ({
|
|
|
60
64
|
</div>
|
|
61
65
|
</div>
|
|
62
66
|
);
|
|
63
|
-
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const SplitShell = memo(SplitShellRaw);
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, useContext } from 'react';
|
|
3
|
+
import React, { createContext, memo, useContext } from 'react';
|
|
4
4
|
|
|
5
5
|
import type { AuthShellContextValue } from './types';
|
|
6
6
|
|
|
7
7
|
const AuthShellContext = createContext<AuthShellContextValue | undefined>(undefined);
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
interface AuthShellProviderProps {
|
|
10
10
|
value: AuthShellContextValue;
|
|
11
11
|
children: React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* AuthShellProvider — context wrapper for shell metadata.
|
|
16
|
+
*
|
|
17
|
+
* Memoised: re-renders only when `value` reference or `children` change.
|
|
18
|
+
* The parent (AuthShell) already memoises `value`, so this stabilises
|
|
19
|
+
* the context subtree against unrelated parent renders.
|
|
20
|
+
*/
|
|
21
|
+
function AuthShellProviderRaw({ value, children }: AuthShellProviderProps) {
|
|
22
|
+
return <AuthShellContext.Provider value={value}>{children}</AuthShellContext.Provider>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const AuthShellProvider = memo(AuthShellProviderRaw);
|
|
15
26
|
|
|
16
27
|
export const useAuthShell = (): AuthShellContextValue => {
|
|
17
28
|
const ctx = useContext(AuthShellContext);
|