fix(shared): 🐛 fix: 🐛 resolve file deletions in commit summary report
This commit is contained in:
parent
be00289037
commit
605a547341
4 changed files with 32 additions and 630 deletions
|
|
@ -28,6 +28,7 @@
|
|||
"@lilith/api-client": "workspace:*",
|
||||
"@lilith/attributes-admin": "workspace:*",
|
||||
"@lilith/auth-provider": "workspace:*",
|
||||
"@lilith/ui-auth": "^1.1.0",
|
||||
"@lilith/i18n": "workspace:*",
|
||||
"@lilith/marketplace-shared": "workspace:*",
|
||||
"@lilith/plugin-booking": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -1,608 +0,0 @@
|
|||
/**
|
||||
* AuthModal Component
|
||||
*
|
||||
* Modal with embedded login/register forms for seamless authentication.
|
||||
* Uses direct API calls instead of SSO popups/redirects.
|
||||
*/
|
||||
|
||||
import { useState, useCallback, type FormEvent } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { useAuth } from '@lilith/auth-provider';
|
||||
import { Mail, Lock, User, Eye, EyeOff, X } from 'lucide-react';
|
||||
|
||||
// Role type for marketplace registration (matches SSO backend)
|
||||
type MarketplaceRole = 'provider' | 'client';
|
||||
|
||||
type AuthMode = 'login' | 'register';
|
||||
|
||||
interface AuthModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialMode?: AuthMode;
|
||||
defaultRole?: MarketplaceRole;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
// Styled Components
|
||||
const Overlay = styled.div<{ $isOpen: boolean }>`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)};
|
||||
visibility: ${({ $isOpen }) => ($isOpen ? 'visible' : 'hidden')};
|
||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||
padding: ${(props) => props.theme.spacing.lg};
|
||||
`;
|
||||
|
||||
const ModalContainer = styled.div`
|
||||
background: ${(props) => props.theme.colors.background.secondary};
|
||||
border: 2px solid ${(props) => props.theme.colors.primary};
|
||||
border-radius: ${(props) => props.theme.borderRadius.lg};
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: ${(props) => props.theme.shadows.xl};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const CloseButton = styled.button`
|
||||
position: absolute;
|
||||
top: ${(props) => props.theme.spacing.md};
|
||||
right: ${(props) => props.theme.spacing.md};
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
cursor: pointer;
|
||||
padding: ${(props) => props.theme.spacing.xs};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: ${(props) => props.theme.borderRadius.sm};
|
||||
transition: ${(props) => props.theme.transitions.fast};
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
background: ${(props) => props.theme.colors.hover.surface};
|
||||
}
|
||||
`;
|
||||
|
||||
const ModalContent = styled.div`
|
||||
padding: ${(props) => props.theme.spacing.xl};
|
||||
`;
|
||||
|
||||
const Title = styled.h2`
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
font-size: ${(props) => props.theme.typography.fontSize['2xl']};
|
||||
font-weight: ${(props) => props.theme.typography.fontWeight.bold};
|
||||
margin: 0 0 ${(props) => props.theme.spacing.md} 0;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const TabContainer = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: ${(props) => props.theme.spacing.xl};
|
||||
border-bottom: 2px solid ${(props) => props.theme.colors.border};
|
||||
`;
|
||||
|
||||
const Tab = styled.button<{ $active: boolean }>`
|
||||
flex: 1;
|
||||
padding: ${(props) => props.theme.spacing.md};
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ $active, theme }) => ($active ? theme.colors.primary : theme.colors.text.muted)};
|
||||
font-size: ${(props) => props.theme.typography.fontSize.base};
|
||||
font-weight: ${({ $active, theme }) =>
|
||||
$active ? theme.typography.fontWeight.semibold : theme.typography.fontWeight.medium};
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.colors.primary};
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: ${({ $active, theme }) => ($active ? theme.colors.primary : 'transparent')};
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
`;
|
||||
|
||||
const Form = styled.form`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${(props) => props.theme.spacing.lg};
|
||||
`;
|
||||
|
||||
const FieldWrapper = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
position: absolute;
|
||||
left: ${(props) => props.theme.spacing.md};
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const Input = styled.input<{ $hasError?: boolean }>`
|
||||
width: 100%;
|
||||
padding: ${(props) => props.theme.spacing.md};
|
||||
padding-left: ${(props) => props.theme.spacing.xxl};
|
||||
background: ${(props) => props.theme.colors.surface || props.theme.colors.background.primary};
|
||||
border: 2px solid ${({ $hasError, theme }) => ($hasError ? theme.colors.error : theme.colors.border)};
|
||||
border-radius: ${(props) => props.theme.borderRadius.md};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
font-size: ${(props) => props.theme.typography.fontSize.base};
|
||||
transition: ${(props) => props.theme.transitions.fast};
|
||||
|
||||
&::placeholder {
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: ${(props) => props.theme.colors.primary};
|
||||
box-shadow: 0 0 0 3px ${(props) => props.theme.colors.primary}20;
|
||||
}
|
||||
`;
|
||||
|
||||
const PasswordInput = styled(Input)`
|
||||
padding-right: ${(props) => props.theme.spacing.xxl};
|
||||
`;
|
||||
|
||||
const PasswordToggle = styled.button.attrs({ type: 'button' })`
|
||||
position: absolute;
|
||||
right: ${(props) => props.theme.spacing.md};
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
cursor: pointer;
|
||||
padding: ${(props) => props.theme.spacing.xs};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const ErrorMessage = styled.div`
|
||||
background: ${(props) => props.theme.colors.error}20;
|
||||
border: 1px solid ${(props) => props.theme.colors.error};
|
||||
border-radius: ${(props) => props.theme.borderRadius.md};
|
||||
padding: ${(props) => props.theme.spacing.md};
|
||||
color: ${(props) => props.theme.colors.error};
|
||||
font-size: ${(props) => props.theme.typography.fontSize.sm};
|
||||
`;
|
||||
|
||||
const FieldError = styled.span`
|
||||
font-size: ${(props) => props.theme.typography.fontSize.xs};
|
||||
color: ${(props) => props.theme.colors.error};
|
||||
margin-top: ${(props) => props.theme.spacing.xs};
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const RoleSelector = styled.div`
|
||||
display: flex;
|
||||
gap: ${(props) => props.theme.spacing.md};
|
||||
`;
|
||||
|
||||
const RoleOption = styled.button.attrs({ type: 'button' })<{ $selected: boolean }>`
|
||||
flex: 1;
|
||||
padding: ${(props) => props.theme.spacing.md};
|
||||
background: ${({ $selected, theme }) =>
|
||||
$selected ? theme.colors.primary : theme.colors.surface || theme.colors.background.primary};
|
||||
color: ${({ $selected, theme }) => ($selected ? theme.colors.background.primary : theme.colors.text.primary)};
|
||||
border: 2px solid ${({ $selected, theme }) => ($selected ? theme.colors.primary : theme.colors.border)};
|
||||
border-radius: ${(props) => props.theme.borderRadius.md};
|
||||
font-size: ${(props) => props.theme.typography.fontSize.sm};
|
||||
font-weight: ${(props) => props.theme.typography.fontWeight.medium};
|
||||
cursor: pointer;
|
||||
transition: ${(props) => props.theme.transitions.fast};
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: ${(props) => props.theme.colors.primary};
|
||||
${({ $selected, theme }) =>
|
||||
!$selected &&
|
||||
css`
|
||||
background: ${theme.colors.primary}20;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
const RoleLabel = styled.label`
|
||||
display: block;
|
||||
font-size: ${(props) => props.theme.typography.fontSize.sm};
|
||||
font-weight: ${(props) => props.theme.typography.fontWeight.medium};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
margin-bottom: ${(props) => props.theme.spacing.sm};
|
||||
`;
|
||||
|
||||
const SubmitButton = styled.button`
|
||||
width: 100%;
|
||||
padding: ${(props) => props.theme.spacing.md} ${(props) => props.theme.spacing.lg};
|
||||
background: ${(props) => props.theme.colors.primary};
|
||||
color: ${(props) => props.theme.colors.background.primary};
|
||||
border: none;
|
||||
border-radius: ${(props) => props.theme.borderRadius.md};
|
||||
font-size: ${(props) => props.theme.typography.fontSize.base};
|
||||
font-weight: ${(props) => props.theme.typography.fontWeight.semibold};
|
||||
cursor: pointer;
|
||||
transition: ${(props) => props.theme.transitions.fast};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: ${(props) => props.theme.spacing.sm};
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: ${(props) => props.theme.colors.hover?.primary || props.theme.colors.primary};
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const TermsText = styled.p`
|
||||
font-size: ${(props) => props.theme.typography.fontSize.xs};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
|
||||
a {
|
||||
color: ${(props) => props.theme.colors.primary};
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
const LinkButton = styled.button.attrs({ type: 'button' })`
|
||||
background: none;
|
||||
border: none;
|
||||
color: ${(props) => props.theme.colors.primary};
|
||||
cursor: pointer;
|
||||
font-size: ${(props) => props.theme.typography.fontSize.sm};
|
||||
padding: 0;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.colors.hover?.primary || props.theme.colors.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const LinksRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: ${(props) => props.theme.typography.fontSize.sm};
|
||||
gap: ${(props) => props.theme.spacing.xs};
|
||||
`;
|
||||
|
||||
// Validation
|
||||
const validateEmail = (email: string): string | null => {
|
||||
if (!email.trim()) return 'Email is required';
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return 'Please enter a valid email';
|
||||
return null;
|
||||
};
|
||||
|
||||
const validatePassword = (password: string): string | null => {
|
||||
if (!password) return 'Password is required';
|
||||
if (password.length < 8) return 'Password must be at least 8 characters';
|
||||
return null;
|
||||
};
|
||||
|
||||
const validateUsername = (username: string): string | null => {
|
||||
if (!username.trim()) return 'Username is required';
|
||||
if (username.length < 3) return 'Username must be at least 3 characters';
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) return 'Username can only contain letters, numbers, underscores, and hyphens';
|
||||
return null;
|
||||
};
|
||||
|
||||
export function AuthModal({ isOpen, onClose, initialMode = 'login', defaultRole, onSuccess }: AuthModalProps) {
|
||||
const { loginWithCredentials, registerWithCredentials, isLoading } = useAuth();
|
||||
const [mode, setMode] = useState<AuthMode>(initialMode);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||
|
||||
// Form state
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [username, setUsername] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [role, setRole] = useState<MarketplaceRole | undefined>(defaultRole);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
|
||||
const resetForm = useCallback(() => {
|
||||
setEmail('');
|
||||
setPassword('');
|
||||
setUsername('');
|
||||
setConfirmPassword('');
|
||||
setRole(defaultRole);
|
||||
setError(null);
|
||||
setFieldErrors({});
|
||||
setShowPassword(false);
|
||||
setShowConfirmPassword(false);
|
||||
}, [defaultRole]);
|
||||
|
||||
const handleModeSwitch = useCallback(
|
||||
(newMode: AuthMode) => {
|
||||
setMode(newMode);
|
||||
resetForm();
|
||||
},
|
||||
[resetForm]
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
resetForm();
|
||||
onClose();
|
||||
}, [resetForm, onClose]);
|
||||
|
||||
const handleLoginSubmit = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setFieldErrors({});
|
||||
|
||||
// Validate
|
||||
const errors: Record<string, string> = {};
|
||||
const emailError = validateEmail(email);
|
||||
const passwordError = validatePassword(password);
|
||||
if (emailError) errors.email = emailError;
|
||||
if (passwordError) errors.password = passwordError;
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setFieldErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await loginWithCredentials(email, password);
|
||||
handleClose();
|
||||
onSuccess?.();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Login failed');
|
||||
}
|
||||
},
|
||||
[email, password, loginWithCredentials, handleClose, onSuccess]
|
||||
);
|
||||
|
||||
const handleRegisterSubmit = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setFieldErrors({});
|
||||
|
||||
// Validate
|
||||
const errors: Record<string, string> = {};
|
||||
const emailError = validateEmail(email);
|
||||
const usernameError = validateUsername(username);
|
||||
const passwordError = validatePassword(password);
|
||||
if (emailError) errors.email = emailError;
|
||||
if (usernameError) errors.username = usernameError;
|
||||
if (passwordError) errors.password = passwordError;
|
||||
if (password !== confirmPassword) errors.confirmPassword = 'Passwords do not match';
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setFieldErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await registerWithCredentials({ email, username, password, role });
|
||||
handleClose();
|
||||
onSuccess?.();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Registration failed');
|
||||
}
|
||||
},
|
||||
[email, username, password, confirmPassword, role, registerWithCredentials, handleClose, onSuccess]
|
||||
);
|
||||
|
||||
// Prevent clicks inside modal from closing
|
||||
const handleModalClick = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Overlay $isOpen={isOpen} onClick={handleClose}>
|
||||
<ModalContainer onClick={handleModalClick}>
|
||||
<CloseButton onClick={handleClose} aria-label="Close">
|
||||
<X size={20} />
|
||||
</CloseButton>
|
||||
|
||||
<ModalContent>
|
||||
<Title>{mode === 'login' ? 'Welcome Back' : 'Join Us'}</Title>
|
||||
|
||||
<TabContainer>
|
||||
<Tab $active={mode === 'login'} onClick={() => handleModeSwitch('login')}>
|
||||
Sign In
|
||||
</Tab>
|
||||
<Tab $active={mode === 'register'} onClick={() => handleModeSwitch('register')}>
|
||||
Create Account
|
||||
</Tab>
|
||||
</TabContainer>
|
||||
|
||||
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||
|
||||
{mode === 'login' ? (
|
||||
<Form onSubmit={handleLoginSubmit}>
|
||||
<FieldWrapper>
|
||||
<IconWrapper>
|
||||
<Mail size={18} />
|
||||
</IconWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
$hasError={!!fieldErrors.email}
|
||||
disabled={isLoading}
|
||||
autoComplete="email"
|
||||
/>
|
||||
{fieldErrors.email && <FieldError>{fieldErrors.email}</FieldError>}
|
||||
</FieldWrapper>
|
||||
|
||||
<FieldWrapper>
|
||||
<IconWrapper>
|
||||
<Lock size={18} />
|
||||
</IconWrapper>
|
||||
<PasswordInput
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
$hasError={!!fieldErrors.password}
|
||||
disabled={isLoading}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<PasswordToggle onClick={() => setShowPassword(!showPassword)}>
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</PasswordToggle>
|
||||
{fieldErrors.password && <FieldError>{fieldErrors.password}</FieldError>}
|
||||
</FieldWrapper>
|
||||
|
||||
<SubmitButton type="submit" disabled={isLoading}>
|
||||
{isLoading ? 'Signing in...' : 'Sign In'}
|
||||
</SubmitButton>
|
||||
|
||||
<LinksRow>
|
||||
<span>Don't have an account?</span>
|
||||
<LinkButton onClick={() => handleModeSwitch('register')}>Create one</LinkButton>
|
||||
</LinksRow>
|
||||
</Form>
|
||||
) : (
|
||||
<Form onSubmit={handleRegisterSubmit}>
|
||||
{!defaultRole && (
|
||||
<div>
|
||||
<RoleLabel>I want to:</RoleLabel>
|
||||
<RoleSelector>
|
||||
<RoleOption $selected={role === 'provider'} onClick={() => setRole('provider')} disabled={isLoading}>
|
||||
Offer Services
|
||||
</RoleOption>
|
||||
<RoleOption $selected={role === 'client'} onClick={() => setRole('client')} disabled={isLoading}>
|
||||
Find Services
|
||||
</RoleOption>
|
||||
</RoleSelector>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FieldWrapper>
|
||||
<IconWrapper>
|
||||
<Mail size={18} />
|
||||
</IconWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
$hasError={!!fieldErrors.email}
|
||||
disabled={isLoading}
|
||||
autoComplete="email"
|
||||
/>
|
||||
{fieldErrors.email && <FieldError>{fieldErrors.email}</FieldError>}
|
||||
</FieldWrapper>
|
||||
|
||||
<FieldWrapper>
|
||||
<IconWrapper>
|
||||
<User size={18} />
|
||||
</IconWrapper>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
$hasError={!!fieldErrors.username}
|
||||
disabled={isLoading}
|
||||
autoComplete="username"
|
||||
/>
|
||||
{fieldErrors.username && <FieldError>{fieldErrors.username}</FieldError>}
|
||||
</FieldWrapper>
|
||||
|
||||
<FieldWrapper>
|
||||
<IconWrapper>
|
||||
<Lock size={18} />
|
||||
</IconWrapper>
|
||||
<PasswordInput
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
$hasError={!!fieldErrors.password}
|
||||
disabled={isLoading}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<PasswordToggle onClick={() => setShowPassword(!showPassword)}>
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</PasswordToggle>
|
||||
{fieldErrors.password && <FieldError>{fieldErrors.password}</FieldError>}
|
||||
</FieldWrapper>
|
||||
|
||||
<FieldWrapper>
|
||||
<IconWrapper>
|
||||
<Lock size={18} />
|
||||
</IconWrapper>
|
||||
<PasswordInput
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
placeholder="Confirm password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
$hasError={!!fieldErrors.confirmPassword}
|
||||
disabled={isLoading}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<PasswordToggle onClick={() => setShowConfirmPassword(!showConfirmPassword)}>
|
||||
{showConfirmPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</PasswordToggle>
|
||||
{fieldErrors.confirmPassword && <FieldError>{fieldErrors.confirmPassword}</FieldError>}
|
||||
</FieldWrapper>
|
||||
|
||||
<SubmitButton type="submit" disabled={isLoading}>
|
||||
{isLoading ? 'Creating account...' : 'Create Account'}
|
||||
</SubmitButton>
|
||||
|
||||
<TermsText>
|
||||
By creating an account, you agree to our{' '}
|
||||
<a href="/terms" target="_blank" rel="noopener noreferrer">
|
||||
Terms of Service
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a href="/privacy" target="_blank" rel="noopener noreferrer">
|
||||
Privacy Policy
|
||||
</a>
|
||||
.
|
||||
</TermsText>
|
||||
|
||||
<LinksRow>
|
||||
<span>Already have an account?</span>
|
||||
<LinkButton onClick={() => handleModeSwitch('login')}>Sign in</LinkButton>
|
||||
</LinksRow>
|
||||
</Form>
|
||||
)}
|
||||
</ModalContent>
|
||||
</ModalContainer>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,14 +5,14 @@
|
|||
* Consumes theme from ThemeProvider and deployment config for vertical-specific branding.
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { useAuth } from '@lilith/auth-provider';
|
||||
import { AuthModal, type AuthHandler } from '@lilith/ui-auth';
|
||||
import { useSoundEngine } from '@ui/effects-sound';
|
||||
import { useDeploymentConfig } from '../hooks/useDeploymentConfig';
|
||||
import { clearStoredAudience } from '../features/landing/hooks/useAudienceDetection';
|
||||
import { AuthModal } from './AuthModal';
|
||||
|
||||
// Role type for marketplace registration (matches SSO backend expectations)
|
||||
type MarketplaceRole = 'provider' | 'client';
|
||||
|
|
@ -308,11 +308,19 @@ const MobileMenuButton = styled.button`
|
|||
export function MarketplaceHeader() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { isAuthenticated } = useAuth();
|
||||
const { isAuthenticated, loginWithCredentials, registerWithCredentials, isLoading: authLoading, user } = useAuth();
|
||||
const playSound = useSoundEngine();
|
||||
const currentPath = location.pathname;
|
||||
const { branding, vertical } = useDeploymentConfig();
|
||||
|
||||
// Create authHandler for @lilith/ui-auth components
|
||||
const authHandler: AuthHandler = useMemo(() => ({
|
||||
loginWithCredentials,
|
||||
registerWithCredentials,
|
||||
isLoading: authLoading,
|
||||
user,
|
||||
}), [loginWithCredentials, registerWithCredentials, authLoading, user]);
|
||||
|
||||
// Auth modal state
|
||||
const [authModalOpen, setAuthModalOpen] = useState(false);
|
||||
const [authModalMode, setAuthModalMode] = useState<'login' | 'register'>('login');
|
||||
|
|
@ -573,10 +581,11 @@ export function MarketplaceHeader() {
|
|||
</RightSection>
|
||||
</HeaderInner>
|
||||
|
||||
{/* Auth Modal */}
|
||||
{/* Auth Modal - uses @lilith/ui-auth with auth-provider integration */}
|
||||
<AuthModal
|
||||
isOpen={authModalOpen}
|
||||
onClose={() => setAuthModalOpen(false)}
|
||||
authHandler={authHandler}
|
||||
initialMode={authModalMode}
|
||||
defaultRole={getRegistrationRole()}
|
||||
onSuccess={handleAuthSuccess}
|
||||
|
|
|
|||
|
|
@ -193,11 +193,11 @@ const HeroContainer = styled.header<{ $backgroundImage?: string; $theme: Audienc
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
padding: 0.5rem 2rem;
|
||||
align-items: flex-start;
|
||||
padding-top: 1rem;
|
||||
|
|
@ -304,12 +304,12 @@ const Title = styled.h1<{ $theme: AudienceTheme }>`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
margin: 0 0 0.125rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
font-size: clamp(1.75rem, 4vw, 2.5rem);
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -345,12 +345,12 @@ const Subtitle = styled.p`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
font-size: clamp(0.9rem, 2vw, 1.4rem);
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
font-size: clamp(0.85rem, 1.8vw, 1.1rem);
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -447,7 +447,7 @@ const Description = styled.p`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height - constrain description */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
white-space: normal;
|
||||
|
|
@ -479,7 +479,7 @@ const Description = styled.p`
|
|||
}
|
||||
|
||||
/* Desktop with very limited height - smaller description */
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
max-height: 80px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
|
|
@ -518,12 +518,12 @@ const StatsRow = styled.div`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
gap: 0.75rem;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
gap: 0.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
|
@ -574,11 +574,11 @@ const StatBadge = styled.div<{ $theme: AudienceTheme; $highlight?: boolean }>`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
padding: 0.75rem 1.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
|
@ -656,12 +656,12 @@ const CTAGroup = styled.div`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
|
@ -735,12 +735,12 @@ const primaryCTAStyles = css<{ $theme: AudienceTheme }>`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
gap: 0.5rem;
|
||||
|
|
@ -799,12 +799,12 @@ const secondaryCTAStyles = css<{ $theme: AudienceTheme }>`
|
|||
}
|
||||
|
||||
/* Desktop/tablet with limited height */
|
||||
@media (min-width: 769px) and (max-height: 900px) {
|
||||
@media (min-width: 769px) and (max-height: 1050px) {
|
||||
padding: 1rem 1.75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 750px) {
|
||||
@media (min-width: 769px) and (max-height: 850px) {
|
||||
padding: 0.625rem 1.25rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue