@djangocfg/layouts 2.1.109 → 2.1.110

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.
Files changed (38) hide show
  1. package/package.json +14 -14
  2. package/src/layouts/AuthLayout/AuthLayout.tsx +92 -20
  3. package/src/layouts/AuthLayout/components/index.ts +11 -7
  4. package/src/layouts/AuthLayout/components/oauth/index.ts +0 -1
  5. package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +35 -0
  6. package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +56 -0
  7. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +22 -0
  8. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +26 -0
  9. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +47 -0
  10. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +53 -0
  11. package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +41 -0
  12. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +42 -0
  13. package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +48 -0
  14. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +57 -0
  15. package/src/layouts/AuthLayout/components/shared/index.ts +21 -0
  16. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +171 -0
  17. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +114 -0
  18. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +70 -0
  19. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +24 -0
  20. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +125 -0
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +91 -0
  22. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +92 -0
  23. package/src/layouts/AuthLayout/components/steps/index.ts +6 -0
  24. package/src/layouts/AuthLayout/constants.ts +24 -0
  25. package/src/layouts/AuthLayout/content.ts +78 -0
  26. package/src/layouts/AuthLayout/hooks/index.ts +1 -0
  27. package/src/layouts/AuthLayout/hooks/useCopyToClipboard.ts +37 -0
  28. package/src/layouts/AuthLayout/index.ts +9 -5
  29. package/src/layouts/AuthLayout/styles/auth.css +578 -0
  30. package/src/layouts/ProfileLayout/ProfileLayout.tsx +2 -2
  31. package/src/layouts/ProfileLayout/components/TwoFactorSection.tsx +2 -2
  32. package/src/layouts/AuthLayout/components/AuthHelp.tsx +0 -114
  33. package/src/layouts/AuthLayout/components/AuthSuccess.tsx +0 -101
  34. package/src/layouts/AuthLayout/components/IdentifierForm.tsx +0 -322
  35. package/src/layouts/AuthLayout/components/OTPForm.tsx +0 -174
  36. package/src/layouts/AuthLayout/components/TwoFactorForm.tsx +0 -140
  37. package/src/layouts/AuthLayout/components/TwoFactorSetup.tsx +0 -286
  38. package/src/layouts/AuthLayout/components/oauth/OAuthProviders.tsx +0 -56
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.109",
3
+ "version": "2.1.110",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -74,12 +74,12 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/api": "^2.1.109",
78
- "@djangocfg/centrifugo": "^2.1.109",
79
- "@djangocfg/ui-core": "^2.1.109",
80
- "@djangocfg/ui-nextjs": "^2.1.109",
81
- "@djangocfg/ui-tools": "^2.1.109",
82
- "@hookform/resolvers": "^5.2.0",
77
+ "@djangocfg/api": "^2.1.110",
78
+ "@djangocfg/centrifugo": "^2.1.110",
79
+ "@djangocfg/ui-core": "^2.1.110",
80
+ "@djangocfg/ui-nextjs": "^2.1.110",
81
+ "@djangocfg/ui-tools": "^2.1.110",
82
+ "@hookform/resolvers": "^5.2.2",
83
83
  "consola": "^3.4.2",
84
84
  "lucide-react": "^0.545.0",
85
85
  "moment": "^2.30.1",
@@ -92,7 +92,7 @@
92
92
  "swr": "^2.3.7",
93
93
  "tailwindcss": "^4.1.14",
94
94
  "tailwindcss-animate": "^1.0.7",
95
- "zod": "^4.1.13"
95
+ "zod": "^4.3.4"
96
96
  },
97
97
  "dependencies": {
98
98
  "nextjs-toploader": "^3.9.17",
@@ -101,12 +101,12 @@
101
101
  "uuid": "^11.1.0"
102
102
  },
103
103
  "devDependencies": {
104
- "@djangocfg/api": "^2.1.109",
105
- "@djangocfg/centrifugo": "^2.1.109",
106
- "@djangocfg/typescript-config": "^2.1.109",
107
- "@djangocfg/ui-core": "^2.1.109",
108
- "@djangocfg/ui-nextjs": "^2.1.109",
109
- "@djangocfg/ui-tools": "^2.1.109",
104
+ "@djangocfg/api": "^2.1.110",
105
+ "@djangocfg/centrifugo": "^2.1.110",
106
+ "@djangocfg/typescript-config": "^2.1.110",
107
+ "@djangocfg/ui-core": "^2.1.110",
108
+ "@djangocfg/ui-nextjs": "^2.1.110",
109
+ "@djangocfg/ui-tools": "^2.1.110",
110
110
  "@types/node": "^24.7.2",
111
111
  "@types/react": "^19.1.0",
112
112
  "@types/react-dom": "^19.1.0",
@@ -1,24 +1,31 @@
1
1
  /**
2
- * Auth Layout
2
+ * Auth Layout - Apple Style
3
3
  *
4
- * Layout for authentication pages with OTP (email/phone), OAuth (GitHub), and 2FA support.
5
- * Supports multi-step authentication flow: identifier OTP 2FA (if enabled)
4
+ * Minimal, clean authentication layout with smooth animations.
5
+ * Supports: email/phone OTP, OAuth (GitHub), 2FA (TOTP + backup codes)
6
6
  */
7
7
 
8
8
  'use client';
9
9
 
10
- import React from 'react';
10
+ import React, { useEffect, useState } from 'react';
11
+
12
+ import { useCfgRouter } from '@djangocfg/api/auth';
11
13
 
12
14
  import { Suspense } from '../../components';
13
- import { AuthSuccess, IdentifierForm, OAuthCallback, OTPForm, TwoFactorForm, TwoFactorSetup } from './components';
15
+ import { OAuthCallback } from './components/oauth';
16
+ import { IdentifierStep, OTPStep, SetupStep, TwoFactorStep } from './components/steps';
17
+ import { AUTH } from './constants';
18
+ import { AUTH_CONTENT } from './content';
14
19
  import { AuthFormProvider, useAuthFormContext } from './context';
15
20
 
21
+ import './styles/auth.css';
22
+
16
23
  import type { AuthLayoutProps } from './types';
17
24
 
18
25
  export type { AuthLayoutProps };
19
26
 
20
27
  export const AuthLayout: React.FC<AuthLayoutProps> = (props) => {
21
- const { enableGithubAuth, redirectUrl = '/dashboard', onOAuthSuccess, onError, className } = props;
28
+ const { enableGithubAuth, redirectUrl = AUTH.DEFAULT_REDIRECT, onOAuthSuccess, onError, className } = props;
22
29
 
23
30
  return (
24
31
  <Suspense>
@@ -26,9 +33,7 @@ export const AuthLayout: React.FC<AuthLayoutProps> = (props) => {
26
33
  {/* Full-screen success overlay */}
27
34
  <AuthSuccessOverlay />
28
35
 
29
- <div
30
- className={`min-h-screen flex flex-col items-center justify-center bg-background py-6 px-4 sm:py-12 sm:px-6 lg:px-8 ${className || ''}`}
31
- >
36
+ <div className={`auth-layout ${className || ''}`}>
32
37
  {/* Handle OAuth callback when GitHub auth is enabled */}
33
38
  {enableGithubAuth && (
34
39
  <Suspense fallback={null}>
@@ -40,10 +45,8 @@ export const AuthLayout: React.FC<AuthLayoutProps> = (props) => {
40
45
  </Suspense>
41
46
  )}
42
47
 
43
- <div className="w-full sm:max-w-md space-y-8">
44
- {props.children}
45
- <AuthContent />
46
- </div>
48
+ {props.children}
49
+ <AuthContent />
47
50
  </div>
48
51
  </AuthFormProvider>
49
52
  </Suspense>
@@ -55,14 +58,14 @@ const AuthContent: React.FC = () => {
55
58
 
56
59
  switch (step) {
57
60
  case 'identifier':
58
- return <IdentifierForm />;
61
+ return <IdentifierStep />;
59
62
  case 'otp':
60
- return <OTPForm />;
63
+ return <OTPStep />;
61
64
  case '2fa':
62
- return <TwoFactorForm />;
65
+ return <TwoFactorStep />;
63
66
  case '2fa-setup':
64
67
  return (
65
- <TwoFactorSetup
68
+ <SetupStep
66
69
  onComplete={() => setStep('success')}
67
70
  onSkip={() => setStep('success')}
68
71
  />
@@ -71,16 +74,85 @@ const AuthContent: React.FC = () => {
71
74
  // Success is rendered as full-screen overlay, return null here
72
75
  return null;
73
76
  default:
74
- return <IdentifierForm />;
77
+ return <IdentifierStep />;
75
78
  }
76
79
  };
77
80
 
78
81
  const AuthSuccessOverlay: React.FC = () => {
79
- const { step } = useAuthFormContext();
82
+ const { step, logoUrl, redirectUrl } = useAuthFormContext();
80
83
 
81
84
  if (step !== 'success') {
82
85
  return null;
83
86
  }
84
87
 
85
- return <AuthSuccess />;
88
+ return <AuthSuccess logoUrl={logoUrl} redirectUrl={redirectUrl} />;
89
+ };
90
+
91
+ // AuthSuccess component - Apple-style success screen
92
+ interface AuthSuccessInlineProps {
93
+ logoUrl?: string;
94
+ redirectUrl?: string;
95
+ redirectDelay?: number;
96
+ }
97
+
98
+ const AuthSuccess: React.FC<AuthSuccessInlineProps> = ({
99
+ logoUrl,
100
+ redirectUrl,
101
+ redirectDelay = AUTH.REDIRECT_DELAY,
102
+ }) => {
103
+ const router = useCfgRouter();
104
+ const [isVisible, setIsVisible] = useState(false);
105
+
106
+ useEffect(() => {
107
+ const animTimer = setTimeout(() => setIsVisible(true), AUTH.ANIMATION_START_DELAY);
108
+ const redirectTimer = setTimeout(() => {
109
+ const finalUrl = redirectUrl || AUTH.DEFAULT_REDIRECT;
110
+ router.hardPush(finalUrl);
111
+ }, redirectDelay);
112
+
113
+ return () => {
114
+ clearTimeout(animTimer);
115
+ clearTimeout(redirectTimer);
116
+ };
117
+ }, [redirectUrl, redirectDelay, router]);
118
+
119
+ return (
120
+ <div className="auth-success-overlay">
121
+ <div className="auth-success-content">
122
+ {logoUrl ? (
123
+ <img
124
+ src={logoUrl}
125
+ alt=""
126
+ className="auth-success-logo"
127
+ style={{ opacity: isVisible ? 1 : 0 }}
128
+ />
129
+ ) : (
130
+ <div
131
+ className="auth-success-check"
132
+ style={{ opacity: isVisible ? 1 : 0 }}
133
+ >
134
+ <svg
135
+ fill="none"
136
+ stroke="currentColor"
137
+ viewBox="0 0 24 24"
138
+ aria-hidden="true"
139
+ >
140
+ <path
141
+ strokeLinecap="round"
142
+ strokeLinejoin="round"
143
+ strokeWidth={2.5}
144
+ d="M5 13l4 4L19 7"
145
+ />
146
+ </svg>
147
+ </div>
148
+ )}
149
+ <p
150
+ className="auth-success-text"
151
+ style={{ opacity: isVisible ? 1 : 0 }}
152
+ >
153
+ {AUTH_CONTENT.success.message}
154
+ </p>
155
+ </div>
156
+ </div>
157
+ );
86
158
  };
@@ -1,7 +1,11 @@
1
- export { IdentifierForm } from './IdentifierForm';
2
- export { OTPForm } from './OTPForm';
3
- export { AuthHelp } from './AuthHelp';
4
- export { OAuthProviders, OAuthCallback, type OAuthCallbackProps } from './oauth';
5
- export { TwoFactorForm } from './TwoFactorForm';
6
- export { TwoFactorSetup, type TwoFactorSetupProps } from './TwoFactorSetup';
7
- export { AuthSuccess, type AuthSuccessProps } from './AuthSuccess';
1
+ // Apple-style step components
2
+ export { IdentifierStep } from './steps/IdentifierStep';
3
+ export { OTPStep } from './steps/OTPStep';
4
+ export { TwoFactorStep } from './steps/TwoFactorStep';
5
+ export { SetupStep, type SetupStepProps } from './steps/SetupStep';
6
+
7
+ // Shared UI components
8
+ export * from './shared';
9
+
10
+ // OAuth
11
+ export { OAuthCallback, type OAuthCallbackProps } from './oauth';
@@ -1,2 +1 @@
1
- export { OAuthProviders } from './OAuthProviders';
2
1
  export { OAuthCallback, type OAuthCallbackProps } from './OAuthCallback';
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export interface AuthButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
6
+ variant?: 'primary' | 'secondary';
7
+ loading?: boolean;
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ /**
12
+ * AuthButton - Apple-style button with loading state
13
+ */
14
+ export const AuthButton: React.FC<AuthButtonProps> = ({
15
+ variant = 'primary',
16
+ loading = false,
17
+ disabled,
18
+ children,
19
+ className = '',
20
+ ...props
21
+ }) => {
22
+ const variantClass = variant === 'primary' ? 'auth-button-primary' : 'auth-button-secondary';
23
+ const loadingClass = loading ? 'auth-button-loading' : '';
24
+
25
+ return (
26
+ <button
27
+ type="submit"
28
+ className={`auth-button ${variantClass} ${loadingClass} ${className}`}
29
+ disabled={disabled || loading}
30
+ {...props}
31
+ >
32
+ {children}
33
+ </button>
34
+ );
35
+ };
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useState } from 'react';
4
+
5
+ import { AUTH } from '../../constants';
6
+ import type { AuthStep } from '../../types';
7
+
8
+ export interface AuthContainerProps {
9
+ children: React.ReactNode;
10
+ step: AuthStep;
11
+ className?: string;
12
+ }
13
+
14
+ /**
15
+ * AuthContainer - Apple-style minimal container with step animations
16
+ *
17
+ * Features:
18
+ * - Full viewport centering
19
+ * - Max-width constraint (400px)
20
+ * - Animate-in on step change
21
+ * - No visible card/border
22
+ */
23
+ export const AuthContainer: React.FC<AuthContainerProps> = ({
24
+ children,
25
+ step,
26
+ className = '',
27
+ }) => {
28
+ const [isEntering, setIsEntering] = useState(true);
29
+ const [currentStep, setCurrentStep] = useState(step);
30
+
31
+ // Trigger animation on step change
32
+ useEffect(() => {
33
+ if (step !== currentStep) {
34
+ setIsEntering(true);
35
+ setCurrentStep(step);
36
+ }
37
+ }, [step, currentStep]);
38
+
39
+ // Reset entering state after animation
40
+ useEffect(() => {
41
+ if (isEntering) {
42
+ const timer = setTimeout(() => setIsEntering(false), AUTH.ANIMATION_DURATION);
43
+ return () => clearTimeout(timer);
44
+ }
45
+ }, [isEntering]);
46
+
47
+ return (
48
+ <div
49
+ className={`auth-container ${className}`}
50
+ data-entering={isEntering}
51
+ data-step={step}
52
+ >
53
+ {children}
54
+ </div>
55
+ );
56
+ };
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export interface AuthDividerProps {
6
+ text?: string;
7
+ className?: string;
8
+ }
9
+
10
+ /**
11
+ * AuthDivider - Minimal "or" divider
12
+ */
13
+ export const AuthDivider: React.FC<AuthDividerProps> = ({
14
+ text = 'or',
15
+ className = '',
16
+ }) => {
17
+ return (
18
+ <div className={`auth-divider ${className}`}>
19
+ {text}
20
+ </div>
21
+ );
22
+ };
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export interface AuthErrorProps {
6
+ message?: string | null;
7
+ className?: string;
8
+ }
9
+
10
+ /**
11
+ * AuthError - Subtle error display with shake animation
12
+ */
13
+ export const AuthError: React.FC<AuthErrorProps> = ({
14
+ message,
15
+ className = '',
16
+ }) => {
17
+ if (!message) {
18
+ return null;
19
+ }
20
+
21
+ return (
22
+ <div className={`auth-error ${className}`} role="alert">
23
+ {message}
24
+ </div>
25
+ );
26
+ };
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export interface AuthFooterProps {
6
+ termsUrl?: string;
7
+ privacyUrl?: string;
8
+ supportUrl?: string;
9
+ className?: string;
10
+ }
11
+
12
+ /**
13
+ * AuthFooter - Minimal link row for terms, privacy, and support
14
+ */
15
+ export const AuthFooter: React.FC<AuthFooterProps> = ({
16
+ termsUrl,
17
+ privacyUrl,
18
+ supportUrl,
19
+ className = '',
20
+ }) => {
21
+ const links = [
22
+ termsUrl && { href: termsUrl, label: 'Terms' },
23
+ privacyUrl && { href: privacyUrl, label: 'Privacy' },
24
+ supportUrl && { href: supportUrl, label: 'Help' },
25
+ ].filter(Boolean) as { href: string; label: string }[];
26
+
27
+ if (links.length === 0) {
28
+ return null;
29
+ }
30
+
31
+ return (
32
+ <div className={`auth-footer ${className}`}>
33
+ {links.map((link, index) => (
34
+ <React.Fragment key={link.href}>
35
+ {index > 0 && <span className="auth-footer-dot">&middot;</span>}
36
+ <a
37
+ href={link.href}
38
+ target="_blank"
39
+ rel="noopener noreferrer"
40
+ >
41
+ {link.label}
42
+ </a>
43
+ </React.Fragment>
44
+ ))}
45
+ </div>
46
+ );
47
+ };
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export interface AuthHeaderProps {
6
+ logo?: string;
7
+ title: string;
8
+ subtitle?: string;
9
+ identifier?: string;
10
+ className?: string;
11
+ }
12
+
13
+ /**
14
+ * AuthHeader - Apple-style header with logo, title, and subtitle
15
+ *
16
+ * Features:
17
+ * - Optional logo with scale animation
18
+ * - Large, bold title
19
+ * - Muted subtitle
20
+ * - Optional highlighted identifier
21
+ */
22
+ export const AuthHeader: React.FC<AuthHeaderProps> = ({
23
+ logo,
24
+ title,
25
+ subtitle,
26
+ identifier,
27
+ className = '',
28
+ }) => {
29
+ return (
30
+ <div className={`auth-header ${className}`}>
31
+ {logo && (
32
+ <img
33
+ src={logo}
34
+ alt=""
35
+ className="auth-logo"
36
+ aria-hidden="true"
37
+ />
38
+ )}
39
+ <h1 className="auth-title">{title}</h1>
40
+ {subtitle && (
41
+ <p className="auth-subtitle">
42
+ {subtitle}
43
+ {identifier && (
44
+ <>
45
+ <br />
46
+ <span className="auth-identifier">{identifier}</span>
47
+ </>
48
+ )}
49
+ </p>
50
+ )}
51
+ </div>
52
+ );
53
+ };
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export interface AuthLinkProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
6
+ children: React.ReactNode;
7
+ href?: string;
8
+ }
9
+
10
+ /**
11
+ * AuthLink - Text link styled button/anchor
12
+ */
13
+ export const AuthLink: React.FC<AuthLinkProps> = ({
14
+ children,
15
+ href,
16
+ className = '',
17
+ ...props
18
+ }) => {
19
+ if (href) {
20
+ return (
21
+ <a
22
+ href={href}
23
+ className={`auth-link ${className}`}
24
+ target="_blank"
25
+ rel="noopener noreferrer"
26
+ >
27
+ {children}
28
+ </a>
29
+ );
30
+ }
31
+
32
+ return (
33
+ <button
34
+ type="button"
35
+ className={`auth-link ${className}`}
36
+ {...props}
37
+ >
38
+ {children}
39
+ </button>
40
+ );
41
+ };
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { OTPInput } from '@djangocfg/ui-core/components';
6
+
7
+ import { AUTH } from '../../constants';
8
+
9
+ export interface AuthOTPInputProps {
10
+ value: string;
11
+ onChange: (value: string) => void;
12
+ onComplete?: (value: string) => void;
13
+ disabled?: boolean;
14
+ autoFocus?: boolean;
15
+ }
16
+
17
+ /**
18
+ * AuthOTPInput - Reusable OTP input with consistent styling
19
+ *
20
+ * Used in: OTPStep, TwoFactorStep, SetupStep
21
+ */
22
+ export const AuthOTPInput: React.FC<AuthOTPInputProps> = ({
23
+ value,
24
+ onChange,
25
+ onComplete,
26
+ disabled = false,
27
+ autoFocus = true,
28
+ }) => (
29
+ <div className="auth-otp-container auth-otp-wrapper">
30
+ <OTPInput
31
+ length={AUTH.OTP_LENGTH}
32
+ validationMode="numeric"
33
+ pasteBehavior="clean"
34
+ value={value}
35
+ onChange={onChange}
36
+ onComplete={onComplete}
37
+ disabled={disabled}
38
+ autoFocus={autoFocus}
39
+ size="lg"
40
+ />
41
+ </div>
42
+ );
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { Mail, Phone } from 'lucide-react';
4
+ import React from 'react';
5
+
6
+ import type { AuthChannel } from '../../types';
7
+
8
+ export interface ChannelToggleProps {
9
+ channel: AuthChannel;
10
+ onChange: (channel: AuthChannel) => void;
11
+ disabled?: boolean;
12
+ className?: string;
13
+ }
14
+
15
+ /**
16
+ * ChannelToggle - Apple-style segmented control for email/phone
17
+ */
18
+ export const ChannelToggle: React.FC<ChannelToggleProps> = ({
19
+ channel,
20
+ onChange,
21
+ disabled = false,
22
+ className = '',
23
+ }) => {
24
+ return (
25
+ <div className={`auth-channel-toggle ${className}`}>
26
+ <button
27
+ type="button"
28
+ className="auth-channel-option"
29
+ data-active={channel === 'email'}
30
+ onClick={() => onChange('email')}
31
+ disabled={disabled}
32
+ >
33
+ <Mail />
34
+ Email
35
+ </button>
36
+ <button
37
+ type="button"
38
+ className="auth-channel-option"
39
+ data-active={channel === 'phone'}
40
+ onClick={() => onChange('phone')}
41
+ disabled={disabled}
42
+ >
43
+ <Phone />
44
+ Phone
45
+ </button>
46
+ </div>
47
+ );
48
+ };
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { Checkbox } from '@djangocfg/ui-core/components';
6
+
7
+ export interface TermsCheckboxProps {
8
+ checked: boolean;
9
+ onChange: (checked: boolean) => void;
10
+ termsUrl?: string;
11
+ privacyUrl?: string;
12
+ disabled?: boolean;
13
+ className?: string;
14
+ }
15
+
16
+ /**
17
+ * TermsCheckbox - Compact terms acceptance checkbox
18
+ */
19
+ export const TermsCheckbox: React.FC<TermsCheckboxProps> = ({
20
+ checked,
21
+ onChange,
22
+ termsUrl,
23
+ privacyUrl,
24
+ disabled = false,
25
+ className = '',
26
+ }) => {
27
+ // Don't render if no links provided
28
+ if (!termsUrl && !privacyUrl) {
29
+ return null;
30
+ }
31
+
32
+ return (
33
+ <div className={`auth-terms ${className}`}>
34
+ <Checkbox
35
+ id="auth-terms"
36
+ checked={checked}
37
+ onCheckedChange={onChange}
38
+ disabled={disabled}
39
+ className="auth-terms-checkbox"
40
+ />
41
+ <label htmlFor="auth-terms">
42
+ I agree to the{' '}
43
+ {termsUrl && (
44
+ <a href={termsUrl} target="_blank" rel="noopener noreferrer">
45
+ Terms
46
+ </a>
47
+ )}
48
+ {termsUrl && privacyUrl && ' and '}
49
+ {privacyUrl && (
50
+ <a href={privacyUrl} target="_blank" rel="noopener noreferrer">
51
+ Privacy Policy
52
+ </a>
53
+ )}
54
+ </label>
55
+ </div>
56
+ );
57
+ };