@djangocfg/layouts 2.1.447 → 2.1.449
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.449",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
"check": "tsc --noEmit"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
|
-
"@djangocfg/api": "^2.1.
|
|
93
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
94
|
-
"@djangocfg/debuger": "^2.1.
|
|
95
|
-
"@djangocfg/i18n": "^2.1.
|
|
96
|
-
"@djangocfg/monitor": "^2.1.
|
|
97
|
-
"@djangocfg/ui-core": "^2.1.
|
|
92
|
+
"@djangocfg/api": "^2.1.449",
|
|
93
|
+
"@djangocfg/centrifugo": "^2.1.449",
|
|
94
|
+
"@djangocfg/debuger": "^2.1.449",
|
|
95
|
+
"@djangocfg/i18n": "^2.1.449",
|
|
96
|
+
"@djangocfg/monitor": "^2.1.449",
|
|
97
|
+
"@djangocfg/ui-core": "^2.1.449",
|
|
98
98
|
"@hookform/resolvers": "^5.2.2",
|
|
99
99
|
"consola": "^3.4.2",
|
|
100
100
|
"lucide-react": "^0.545.0",
|
|
@@ -125,14 +125,14 @@
|
|
|
125
125
|
"uuid": "^11.1.0"
|
|
126
126
|
},
|
|
127
127
|
"devDependencies": {
|
|
128
|
-
"@djangocfg/api": "^2.1.
|
|
129
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
130
|
-
"@djangocfg/debuger": "^2.1.
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
132
|
-
"@djangocfg/monitor": "^2.1.
|
|
133
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
134
|
-
"@djangocfg/ui-core": "^2.1.
|
|
135
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
128
|
+
"@djangocfg/api": "^2.1.449",
|
|
129
|
+
"@djangocfg/centrifugo": "^2.1.449",
|
|
130
|
+
"@djangocfg/debuger": "^2.1.449",
|
|
131
|
+
"@djangocfg/i18n": "^2.1.449",
|
|
132
|
+
"@djangocfg/monitor": "^2.1.449",
|
|
133
|
+
"@djangocfg/typescript-config": "^2.1.449",
|
|
134
|
+
"@djangocfg/ui-core": "^2.1.449",
|
|
135
|
+
"@djangocfg/ui-tools": "^2.1.449",
|
|
136
136
|
"@types/node": "^25.2.3",
|
|
137
137
|
"@types/react": "^19.2.15",
|
|
138
138
|
"@types/react-dom": "^19.2.3",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { memo } from 'react';
|
|
4
|
+
import { Mail } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WebmailIcon — brand glyph for a webmail provider.
|
|
8
|
+
*
|
|
9
|
+
* lucide-react dropped brand icons, so the recognisable providers get a small
|
|
10
|
+
* inline SVG (brand-coloured, currentColor-agnostic). Everything else falls
|
|
11
|
+
* back to a generic `Mail` glyph that inherits `currentColor`.
|
|
12
|
+
*
|
|
13
|
+
* `provider` is the slug from the backend WebmailLink (gmail, outlook, …).
|
|
14
|
+
*/
|
|
15
|
+
export interface WebmailIconProps {
|
|
16
|
+
provider: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
size?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SIZE = 20;
|
|
22
|
+
|
|
23
|
+
function GmailGlyph({ size }: { size: number }) {
|
|
24
|
+
return (
|
|
25
|
+
<svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true">
|
|
26
|
+
<path fill="#4285F4" d="M2 6.5A1.5 1.5 0 0 1 3.5 5H4l8 6 8-6h.5A1.5 1.5 0 0 1 22 6.5V18a1 1 0 0 1-1 1h-2V9.2l-7 5.2-7-5.2V19H3a1 1 0 0 1-1-1V6.5Z" />
|
|
27
|
+
<path fill="#EA4335" d="M2 6.5A1.5 1.5 0 0 1 3.5 5H4l8 6-2 1.5L2 6.5Z" />
|
|
28
|
+
<path fill="#FBBC04" d="M22 6.5A1.5 1.5 0 0 0 20.5 5H20l-8 6 2 1.5 8-6Z" />
|
|
29
|
+
<path fill="#34A853" d="M3 19h2V9.2L3 7.7V19Zm18 0h-2V9.2l2-1.5V19Z" />
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function OutlookGlyph({ size }: { size: number }) {
|
|
35
|
+
return (
|
|
36
|
+
<svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true">
|
|
37
|
+
<rect x="10" y="5" width="12" height="14" rx="1" fill="#0A2767" />
|
|
38
|
+
<rect x="10" y="5" width="12" height="7" fill="#0364B8" />
|
|
39
|
+
<path fill="#28A8EA" d="M22 8.5 13 13l9 4.5V8.5Z" />
|
|
40
|
+
<rect x="2" y="6" width="11" height="12" rx="2" fill="#0078D4" />
|
|
41
|
+
<path fill="#fff" d="M7.5 8.6c-2 0-3.2 1.5-3.2 3.5s1.2 3.4 3.1 3.4 3.2-1.4 3.2-3.5-1.2-3.4-3.1-3.4Zm0 1.5c1 0 1.5.9 1.5 1.9s-.5 2-1.5 2-1.5-1-1.5-2 .5-1.9 1.5-1.9Z" />
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function YandexGlyph({ size }: { size: number }) {
|
|
47
|
+
return (
|
|
48
|
+
<svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true">
|
|
49
|
+
<circle cx="12" cy="12" r="10" fill="#FC3F1D" />
|
|
50
|
+
<path fill="#fff" d="M13 6h-1.8c-2 0-3.3 1.3-3.3 3.2 0 1.5.7 2.4 2 3.3L7.6 18h1.9l2.2-4.9h.6V18H14V6Zm-.7 5.7c-1.1 0-1.6-.6-1.6-2 0-1.5.6-2.1 1.6-2.1h.7v4.1h-.7Z" />
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function MailRuGlyph({ size }: { size: number }) {
|
|
56
|
+
return (
|
|
57
|
+
<svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true">
|
|
58
|
+
<circle cx="12" cy="12" r="10" fill="#005FF9" />
|
|
59
|
+
<path fill="#fff" d="M12 7.2a4.8 4.8 0 1 0 3 8.6l-1-1.2a3.2 3.2 0 1 1 1.1-2.6h1.6a4.8 4.8 0 0 0-4.7-4.8Zm0 3.2a1.6 1.6 0 1 0 0 3.2 1.6 1.6 0 0 0 0-3.2Z" />
|
|
60
|
+
<path fill="#FFA930" d="M16.8 12v.9a1.4 1.4 0 0 0 2.6.7A5 5 0 1 0 12 17v-1.6a3.4 3.4 0 1 1 3.2-4.6h1.6Z" />
|
|
61
|
+
</svg>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const GLYPHS: Record<string, React.FC<{ size: number }>> = {
|
|
66
|
+
gmail: GmailGlyph,
|
|
67
|
+
outlook: OutlookGlyph,
|
|
68
|
+
yandex: YandexGlyph,
|
|
69
|
+
mail_ru: MailRuGlyph,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function WebmailIconRaw({ provider, className, size = SIZE }: WebmailIconProps) {
|
|
73
|
+
const Glyph = GLYPHS[provider];
|
|
74
|
+
if (Glyph) {
|
|
75
|
+
return (
|
|
76
|
+
<span className={className} style={{ display: 'inline-flex', lineHeight: 0 }}>
|
|
77
|
+
<Glyph size={size} />
|
|
78
|
+
</span>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return <Mail className={className} width={size} height={size} aria-hidden="true" />;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const WebmailIcon = memo(WebmailIconRaw);
|
|
@@ -6,6 +6,7 @@ export { AuthError } from './AuthError';
|
|
|
6
6
|
export { AuthButton } from './AuthButton';
|
|
7
7
|
export { AuthLink } from './AuthLink';
|
|
8
8
|
export { AuthOTPInput } from './AuthOTPInput';
|
|
9
|
+
export { WebmailIcon } from './WebmailIcon';
|
|
9
10
|
export { AuthConsent } from './AuthConsent';
|
|
10
11
|
|
|
11
12
|
export type { AuthContainerProps } from './AuthContainer';
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
AuthHeader,
|
|
15
15
|
AuthLink,
|
|
16
16
|
AuthOTPInput,
|
|
17
|
+
WebmailIcon,
|
|
17
18
|
} from '../shared';
|
|
18
19
|
|
|
19
20
|
type SubmitState = 'idle' | 'submitting';
|
|
@@ -38,6 +39,7 @@ function OTPStepRaw() {
|
|
|
38
39
|
error,
|
|
39
40
|
isRateLimited,
|
|
40
41
|
rateLimitLabel,
|
|
42
|
+
webmail,
|
|
41
43
|
setOtp,
|
|
42
44
|
handleOTPSubmit,
|
|
43
45
|
handleResendOTP,
|
|
@@ -58,6 +60,17 @@ function OTPStepRaw() {
|
|
|
58
60
|
devMode: t('layouts.auth.dev.anyCodeWorks'),
|
|
59
61
|
}), [t]);
|
|
60
62
|
|
|
63
|
+
// "Open Gmail / Outlook / …" — jumps straight to a search for our email.
|
|
64
|
+
const openMailboxLabel = useMemo(
|
|
65
|
+
() => (webmail ? t('layouts.auth.otp.openMailbox', { provider: webmail.provider_name }) : ''),
|
|
66
|
+
[t, webmail]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const handleOpenMailbox = useCallback(() => {
|
|
70
|
+
if (!webmail) return;
|
|
71
|
+
window.open(webmail.url, '_blank', 'noopener,noreferrer');
|
|
72
|
+
}, [webmail]);
|
|
73
|
+
|
|
61
74
|
// Mask identifier for privacy
|
|
62
75
|
const maskedIdentifier = useMemo(() => {
|
|
63
76
|
const [local, domain] = identifier.split('@');
|
|
@@ -107,6 +120,18 @@ function OTPStepRaw() {
|
|
|
107
120
|
<div className="auth-dev-notice">{content.devMode}</div>
|
|
108
121
|
)}
|
|
109
122
|
|
|
123
|
+
{webmail && (
|
|
124
|
+
<AuthButton
|
|
125
|
+
type="button"
|
|
126
|
+
variant="secondary"
|
|
127
|
+
onClick={handleOpenMailbox}
|
|
128
|
+
className="auth-webmail-button"
|
|
129
|
+
>
|
|
130
|
+
<WebmailIcon provider={webmail.provider} />
|
|
131
|
+
{openMailboxLabel}
|
|
132
|
+
</AuthButton>
|
|
133
|
+
)}
|
|
134
|
+
|
|
110
135
|
<AuthError message={error} />
|
|
111
136
|
|
|
112
137
|
<AuthButton loading={isLoading} disabled={otp.length < AUTH.EMAIL_OTP_LENGTH}>
|
|
@@ -5,6 +5,7 @@ import React, { useMemo, useRef, useState } from 'react';
|
|
|
5
5
|
import type {
|
|
6
6
|
AuthFormReturn,
|
|
7
7
|
AuthStep,
|
|
8
|
+
WebmailLink,
|
|
8
9
|
} from '@djangocfg/api/auth';
|
|
9
10
|
|
|
10
11
|
import { AuthFormContext } from '../layouts/AuthLayout/context';
|
|
@@ -29,6 +30,7 @@ export interface MockAuthFormProviderProps extends Partial<AuthLayoutConfig> {
|
|
|
29
30
|
twoFactorCode?: string;
|
|
30
31
|
useBackupCode?: boolean;
|
|
31
32
|
rateLimitSeconds?: number;
|
|
33
|
+
webmail?: WebmailLink | null;
|
|
32
34
|
is2FALoading?: boolean;
|
|
33
35
|
twoFactorWarning?: string | null;
|
|
34
36
|
twoFactorAttemptsRemaining?: number | null;
|
|
@@ -68,6 +70,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
|
|
|
68
70
|
twoFactorCode: initialTwoFactorCode = '',
|
|
69
71
|
useBackupCode: initialUseBackupCode = false,
|
|
70
72
|
rateLimitSeconds: initialRateLimitSeconds = 0,
|
|
73
|
+
webmail: initialWebmail = null,
|
|
71
74
|
is2FALoading = false,
|
|
72
75
|
twoFactorWarning = null,
|
|
73
76
|
twoFactorAttemptsRemaining = null,
|
|
@@ -83,6 +86,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
|
|
|
83
86
|
const [twoFactorCode, setTwoFactorCode] = useState(initialTwoFactorCode);
|
|
84
87
|
const [useBackupCode, setUseBackupCode] = useState(initialUseBackupCode);
|
|
85
88
|
const [rateLimitSeconds, setRateLimitSeconds] = useState(initialRateLimitSeconds);
|
|
89
|
+
const [webmail, setWebmail] = useState<WebmailLink | null>(initialWebmail);
|
|
86
90
|
|
|
87
91
|
const isAutoSubmittingFromUrl = useRef(false);
|
|
88
92
|
|
|
@@ -106,6 +110,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
|
|
|
106
110
|
rateLimitSeconds,
|
|
107
111
|
isRateLimited: rateLimitSeconds > 0,
|
|
108
112
|
rateLimitLabel: formatRateLimit(rateLimitSeconds),
|
|
113
|
+
webmail,
|
|
109
114
|
|
|
110
115
|
// State handlers
|
|
111
116
|
setIdentifier,
|
|
@@ -120,6 +125,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
|
|
|
120
125
|
setTwoFactorCode,
|
|
121
126
|
setUseBackupCode,
|
|
122
127
|
startRateLimitCountdown: (seconds: number) => setRateLimitSeconds(seconds),
|
|
128
|
+
setWebmail,
|
|
123
129
|
|
|
124
130
|
// Submit handlers — all no-op (preventDefault only) so the visible
|
|
125
131
|
// step never changes from under the storyteller. To preview a
|
|
@@ -166,6 +172,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
|
|
|
166
172
|
twoFactorCode,
|
|
167
173
|
useBackupCode,
|
|
168
174
|
rateLimitSeconds,
|
|
175
|
+
webmail,
|
|
169
176
|
is2FALoading,
|
|
170
177
|
twoFactorWarning,
|
|
171
178
|
twoFactorAttemptsRemaining,
|