fix(login): replicate original Leopost auth layout exactly

- Left panel: dark ink (#1A1A1A) bg with decorative blobs, testimonial quote,
  avatar, copyright — NOT coral
- Right panel: cream bg, no card/shadow, form directly on page
- editorial-tag (red uppercase label) + Fraunces heading per page
- Google button: outline style (white bg + border)
- Inputs: full border h-11, white bg, focus:border-ink, no border-radius
- Submit CTA: black full-width h-12 → hover accent
- Login/Register as separate form components (not tab toggle)
- Responsive: left panel hidden on mobile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Michele
2026-04-01 16:34:43 +02:00
parent 3139468c92
commit b38419f3ee

View File

@@ -1,25 +1,149 @@
import { useState } from 'react' import { useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate, Link } from 'react-router-dom'
import { useAuth } from '../AuthContext' import { useAuth } from '../AuthContext'
import { BASE_URL } from '../api' import { BASE_URL } from '../api'
const ACCENT = '#E85A4F'
const ACCENT_HOVER= '#D14940'
const CREAM = '#FFFBF5'
const INK = '#1A1A1A'
const INK_MUTED = '#7A7A7A'
const BORDER = '#E5E0D8'
export default function LoginPage() { export default function LoginPage() {
const [mode, setMode] = useState('login') // 'login' | 'register' const [mode, setMode] = useState('login') // 'login' | 'register'
return (
<div style={{ minHeight: '100vh', display: 'flex', fontFamily: "'DM Sans', sans-serif" }}>
{/* ── LEFT — dark ink branding panel ─────────────────────── */}
<div style={{
display: 'none',
width: '50%',
backgroundColor: '#1A1A1A',
padding: '3rem',
flexDirection: 'column',
justifyContent: 'space-between',
position: 'relative',
overflow: 'hidden',
// show on large screens via media query (inlined below)
}}
className="auth-left-panel"
>
{/* Decorative blobs */}
<div style={{
position: 'absolute', top: 0, right: 0,
width: 256, height: 256,
borderRadius: '50%',
backgroundColor: 'rgba(232,90,79,0.10)',
transform: 'translate(50%, -50%)',
}} />
<div style={{
position: 'absolute', bottom: 0, left: 0,
width: 384, height: 384,
borderRadius: '50%',
backgroundColor: 'rgba(232,90,79,0.05)',
transform: 'translate(-50%, 50%)',
}} />
{/* Logo */}
<span style={{
fontFamily: "'Fraunces', serif",
fontSize: '1.75rem',
fontWeight: 600,
color: '#FFFBF5',
position: 'relative',
zIndex: 1,
}}>
Leopost
</span>
{/* Testimonial */}
<div style={{ position: 'relative', zIndex: 1, maxWidth: 400 }}>
<div style={{ width: 48, height: 4, backgroundColor: '#E85A4F', marginBottom: '2rem' }} />
<blockquote style={{
fontFamily: "'Fraunces', serif",
fontSize: '1.75rem',
color: 'rgba(255,251,245,0.9)',
lineHeight: 1.35,
fontWeight: 500,
fontStyle: 'italic',
margin: 0,
}}>
"Finalmente un assistente che capisce il mio brand e mi fa risparmiare ore ogni settimana."
</blockquote>
<div style={{ marginTop: '2rem', display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div style={{
width: 48, height: 48,
borderRadius: '50%',
backgroundColor: 'rgba(255,251,245,0.2)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<span style={{ fontFamily: "'Fraunces', serif", color: '#FFFBF5', fontWeight: 600 }}>MR</span>
</div>
<div>
<p style={{ color: '#FFFBF5', fontWeight: 500, margin: 0 }}>Marco Rossi</p>
<p style={{ color: 'rgba(255,251,245,0.6)', fontSize: '0.875rem', margin: 0 }}>Social Media Manager</p>
</div>
</div>
</div>
{/* Copyright */}
<p style={{ color: 'rgba(255,251,245,0.4)', fontSize: '0.875rem', position: 'relative', zIndex: 1 }}>
© 2026 Leopost
</p>
</div>
{/* ── RIGHT — form panel ──────────────────────────────────── */}
<div style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
backgroundColor: '#FFFBF5',
}}>
{/* Mobile header */}
<div style={{
padding: '1.5rem',
borderBottom: '1px solid #E5E0D8',
}}
className="auth-mobile-header"
>
<span style={{ fontFamily: "'Fraunces', serif", fontSize: '1.5rem', fontWeight: 600, color: '#1A1A1A' }}>
Leopost
</span>
</div>
{/* Form area */}
<div style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '2rem 1.5rem',
}}>
<div style={{ width: '100%', maxWidth: 448, animation: 'fade-up 0.5s ease-out both' }}>
{mode === 'login' ? (
<LoginForm onSwitchMode={() => setMode('register')} />
) : (
<RegisterForm onSwitchMode={() => setMode('login')} />
)}
</div>
</div>
</div>
{/* CSS for responsive left panel */}
<style>{`
@media (min-width: 1024px) {
.auth-left-panel { display: flex !important; }
.auth-mobile-header { display: none !important; }
}
`}</style>
</div>
)
}
// ── Login form ─────────────────────────────────────────────────────────────────
function LoginForm({ onSwitchMode }) {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [displayName, setDisplayName] = useState('')
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [showRedeemModal, setShowRedeemModal] = useState(false)
const { login, register } = useAuth() const { login } = useAuth()
const navigate = useNavigate() const navigate = useNavigate()
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
@@ -27,475 +151,364 @@ export default function LoginPage() {
setError('') setError('')
setLoading(true) setLoading(true)
try { try {
if (mode === 'login') { await login(email, password)
await login(email, password)
} else {
await register(email, password, displayName)
}
navigate('/') navigate('/')
} catch (err) { } catch (err) {
setError(err.message || (mode === 'login' ? 'Credenziali non valide' : 'Errore durante la registrazione')) setError(err.message || 'Email o password errata')
} finally { } finally {
setLoading(false) setLoading(false)
} }
} }
const handleGoogleLogin = () => { return (
window.location.href = `${BASE_URL}/auth/oauth/google` <div>
{/* Header */}
<div style={{ marginBottom: '2rem' }}>
<span style={{
fontSize: '0.72rem',
fontWeight: 700,
letterSpacing: '0.12em',
textTransform: 'uppercase',
color: '#E85A4F',
}}>
Bentornato
</span>
<h1 style={{
fontFamily: "'Fraunces', serif",
fontSize: '1.875rem',
fontWeight: 600,
color: '#1A1A1A',
letterSpacing: '-0.02em',
margin: '1rem 0 0.5rem',
}}>
Accedi a Leopost
</h1>
<p style={{ color: '#4A4A4A', fontSize: '0.9rem', margin: 0 }}>
Inserisci le tue credenziali per continuare
</p>
</div>
{/* Google button */}
<GoogleButton onClick={() => { window.location.href = `${BASE_URL}/auth/oauth/google` }} />
{/* Divider */}
<Divider />
{/* Form */}
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
{error && <ErrorBox message={error} />}
<Field label="Email">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="tu@esempio.it"
required
style={inputStyle}
onFocus={(e) => e.target.style.borderColor = '#1A1A1A'}
onBlur={(e) => e.target.style.borderColor = '#E5E0D8'}
/>
</Field>
<Field label="Password">
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="La tua password"
required
style={inputStyle}
onFocus={(e) => e.target.style.borderColor = '#1A1A1A'}
onBlur={(e) => e.target.style.borderColor = '#E5E0D8'}
/>
</Field>
<div style={{ textAlign: 'right', marginTop: '-0.5rem' }}>
<span style={{ fontSize: '0.875rem', color: '#E85A4F', cursor: 'pointer' }}>
Password dimenticata?
</span>
</div>
<SubmitButton loading={loading}>
{loading ? 'Accesso in corso...' : 'Accedi'}
</SubmitButton>
</form>
{/* Footer */}
<div style={{ marginTop: '2rem', paddingTop: '1.5rem', borderTop: '1px solid #E5E0D8', textAlign: 'center' }}>
<p style={{ fontSize: '0.875rem', color: '#4A4A4A', margin: 0 }}>
Non hai un account?{' '}
<button
onClick={onSwitchMode}
style={{ background: 'none', border: 'none', color: '#E85A4F', cursor: 'pointer', fontWeight: 600, fontSize: '0.875rem', padding: 0, fontFamily: "'DM Sans', sans-serif" }}
>
Registrati gratis
</button>
</p>
</div>
</div>
)
}
// ── Register form ──────────────────────────────────────────────────────────────
function RegisterForm({ onSwitchMode }) {
const [email, setEmail] = useState('')
const [displayName, setDisplayName] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const { register } = useAuth()
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
setLoading(true)
try {
await register(email, password, displayName)
navigate('/')
} catch (err) {
setError(err.message || 'Errore durante la registrazione')
} finally {
setLoading(false)
}
} }
return ( return (
<div style={{ <div>
display: 'flex', {/* Header */}
height: '100vh', <div style={{ marginBottom: '2rem' }}>
fontFamily: "'DM Sans', sans-serif", <span style={{
overflow: 'hidden', fontSize: '0.72rem',
}}> fontWeight: 700,
{/* ── LEFT PANEL ─────────────────────────────────────────── */} letterSpacing: '0.12em',
<div style={{ textTransform: 'uppercase',
width: '45%', color: '#E85A4F',
backgroundColor: ACCENT,
padding: '3rem',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
color: 'white',
flexShrink: 0,
}}>
<div style={{ animation: 'fade-up 0.6s ease-out both' }}>
{/* Tag editoriale */}
<span style={{
display: 'inline-block',
fontSize: '0.7rem',
fontWeight: 700,
letterSpacing: '0.15em',
textTransform: 'uppercase',
color: 'rgba(255,255,255,0.7)',
marginBottom: '0.5rem',
}}>
LEOPOST
</span>
{/* editorial-line bianco */}
<div style={{ width: 60, height: 3, backgroundColor: 'rgba(255,255,255,0.6)', marginBottom: '1.5rem' }} />
<h1 style={{
fontFamily: "'Fraunces', serif",
fontSize: '2.6rem',
fontWeight: 700,
margin: 0,
lineHeight: 1.15,
letterSpacing: '-0.02em',
}}>
Il tuo studio<br />editoriale AI
</h1>
<p style={{
fontFamily: "'DM Sans', sans-serif",
fontSize: '1rem',
marginTop: '1rem',
opacity: 0.85,
lineHeight: 1.6,
fontWeight: 400,
maxWidth: 300,
}}>
Genera, schedula e pubblica contenuti su tutti i social con l'AI al tuo fianco.
</p>
{/* Benefit list */}
<ul style={{ marginTop: '2.5rem', listStyle: 'none', padding: 0, display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
{[
'Personaggi AI con voce e stile unico',
'Contenuti su Facebook, Instagram, YouTube, TikTok',
'Schedulazione automatica con piani editoriali',
].map((txt) => (
<li key={txt} style={{
display: 'flex',
alignItems: 'flex-start',
gap: '0.75rem',
fontSize: '0.9rem',
lineHeight: 1.5,
padding: '0.6rem 0.9rem',
backgroundColor: 'rgba(255,255,255,0.12)',
backdropFilter: 'blur(4px)',
}}>
<span style={{ fontSize: '0.8rem', marginTop: '0.15rem', flexShrink: 0 }}>✦</span>
{txt}
</li>
))}
</ul>
</div>
{/* Badge in basso */}
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.6rem 1rem',
border: '1px solid rgba(255,255,255,0.4)',
fontSize: '0.8rem',
fontWeight: 600,
letterSpacing: '0.05em',
animation: 'fade-up 0.6s ease-out 0.3s both',
alignSelf: 'flex-start',
}}> }}>
<span style={{ opacity: 0.7 }}>★</span> EARLY ADOPTER BETA Inizia gratis
</div> </span>
<h1 style={{
fontFamily: "'Fraunces', serif",
fontSize: '1.875rem',
fontWeight: 600,
color: '#1A1A1A',
letterSpacing: '-0.02em',
margin: '1rem 0 0.5rem',
}}>
Crea il tuo account
</h1>
<p style={{ color: '#4A4A4A', fontSize: '0.9rem', margin: 0 }}>
Inizia a gestire i tuoi social in modo intelligente
</p>
</div> </div>
{/* ── RIGHT PANEL ────────────────────────────────────────── */} {/* Google button */}
<div style={{ <GoogleButton onClick={() => { window.location.href = `${BASE_URL}/auth/oauth/google` }} />
flex: 1,
backgroundColor: CREAM, {/* Divider */}
<Divider />
{/* Form */}
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
{error && <ErrorBox message={error} />}
<Field label="Nome visualizzato">
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="Il tuo nome o nickname"
style={inputStyle}
onFocus={(e) => e.target.style.borderColor = '#1A1A1A'}
onBlur={(e) => e.target.style.borderColor = '#E5E0D8'}
/>
</Field>
<Field label="Email">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="tu@esempio.it"
required
style={inputStyle}
onFocus={(e) => e.target.style.borderColor = '#1A1A1A'}
onBlur={(e) => e.target.style.borderColor = '#E5E0D8'}
/>
</Field>
<Field label="Password">
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Minimo 8 caratteri"
required
style={inputStyle}
onFocus={(e) => e.target.style.borderColor = '#1A1A1A'}
onBlur={(e) => e.target.style.borderColor = '#E5E0D8'}
/>
</Field>
<SubmitButton loading={loading}>
{loading ? 'Creazione account...' : 'Crea account'}
</SubmitButton>
</form>
{/* Footer */}
<div style={{ marginTop: '2rem', paddingTop: '1.5rem', borderTop: '1px solid #E5E0D8', textAlign: 'center' }}>
<p style={{ fontSize: '0.875rem', color: '#4A4A4A', margin: 0 }}>
Hai già un account?{' '}
<button
onClick={onSwitchMode}
style={{ background: 'none', border: 'none', color: '#E85A4F', cursor: 'pointer', fontWeight: 600, fontSize: '0.875rem', padding: 0, fontFamily: "'DM Sans', sans-serif" }}
>
Accedi
</button>
</p>
</div>
</div>
)
}
// ── Shared sub-components ──────────────────────────────────────────────────────
function GoogleButton({ onClick }) {
return (
<button
type="button"
onClick={onClick}
style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
padding: '2rem', gap: '0.75rem',
overflowY: 'auto', width: '100%',
}}> height: 48,
<div style={{ width: '100%', maxWidth: '420px', animation: 'fade-up 0.6s ease-out 0.1s both' }}> backgroundColor: 'white',
border: '1px solid #E5E0D8',
borderRadius: 0,
cursor: 'pointer',
fontFamily: "'DM Sans', sans-serif",
fontWeight: 500,
fontSize: '1rem',
color: '#1A1A1A',
transition: 'border-color 0.15s',
}}
onMouseEnter={(e) => e.currentTarget.style.borderColor = '#1A1A1A'}
onMouseLeave={(e) => e.currentTarget.style.borderColor = '#E5E0D8'}
>
<GoogleIcon />
Continua con Google
</button>
)
}
{/* ── Mode toggle ── */} function Divider() {
<div style={{ return (
display: 'flex', <div style={{ position: 'relative', margin: '2rem 0' }}>
borderBottom: `2px solid ${BORDER}`, <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center' }}>
marginBottom: '2rem', <div style={{ width: '100%', borderTop: '1px solid #E5E0D8' }} />
}}> </div>
{[ <div style={{ position: 'relative', display: 'flex', justifyContent: 'center' }}>
{ key: 'login', label: 'Accedi' }, <span style={{
{ key: 'register', label: 'Registrati' }, backgroundColor: '#FFFBF5',
].map(({ key, label }) => ( padding: '0 1rem',
<button fontSize: '0.78rem',
key={key} color: '#7A7A7A',
onClick={() => { setMode(key); setError('') }} letterSpacing: '0.1em',
style={{ textTransform: 'uppercase',
flex: 1, }}>
padding: '0.75rem', oppure
background: 'none', </span>
border: 'none',
borderBottom: mode === key ? `2px solid ${ACCENT}` : '2px solid transparent',
marginBottom: '-2px',
cursor: 'pointer',
fontFamily: "'DM Sans', sans-serif",
fontWeight: 600,
fontSize: '0.9rem',
color: mode === key ? ACCENT : INK_MUTED,
transition: 'color 0.2s, border-color 0.2s',
}}
>
{label}
</button>
))}
</div>
{/* ── Form ── */}
<form onSubmit={handleSubmit} style={{ marginBottom: '1.5rem' }}>
{error && (
<div style={{
marginBottom: '1.25rem',
padding: '0.75rem 1rem',
backgroundColor: '#FFF5F5',
border: '1px solid #FED7D7',
color: '#C53030',
fontSize: '0.875rem',
}}>
{error}
</div>
)}
{mode === 'register' && (
<div style={{ marginBottom: '1rem' }}>
<label style={labelStyle}>Nome visualizzato</label>
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="Il tuo nome o nickname"
style={inputStyle}
/>
</div>
)}
<div style={{ marginBottom: '1rem' }}>
<label style={labelStyle}>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="tu@esempio.it"
required
style={inputStyle}
/>
</div>
<div style={{ marginBottom: '1.5rem' }}>
<label style={labelStyle}>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
style={inputStyle}
/>
</div>
<button
type="submit"
disabled={loading}
style={{
width: '100%',
padding: '0.8rem',
backgroundColor: loading ? '#888' : INK,
color: 'white',
border: 'none',
borderRadius: 0,
fontFamily: "'DM Sans', sans-serif",
fontWeight: 600,
fontSize: '0.9rem',
cursor: loading ? 'not-allowed' : 'pointer',
transition: 'background-color 0.2s, transform 0.15s',
}}
onMouseEnter={(e) => { if (!loading) { e.target.style.backgroundColor = ACCENT; e.target.style.transform = 'translateY(-1px)' } }}
onMouseLeave={(e) => { if (!loading) { e.target.style.backgroundColor = INK; e.target.style.transform = 'translateY(0)' } }}
>
{loading ? 'Caricamento...' : mode === 'login' ? 'Accedi' : 'Crea account'}
</button>
</form>
{/* ── Divider ── */}
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '1.25rem' }}>
<div style={{ flex: 1, height: 1, backgroundColor: BORDER }} />
<span style={{ color: INK_MUTED, fontSize: '0.8rem' }}>oppure</span>
<div style={{ flex: 1, height: 1, backgroundColor: BORDER }} />
</div>
{/* ── Google ── */}
<button
onClick={handleGoogleLogin}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.75rem',
width: '100%',
padding: '0.75rem',
backgroundColor: 'white',
border: `1px solid ${BORDER}`,
borderRadius: 0,
cursor: 'pointer',
fontFamily: "'DM Sans', sans-serif",
fontWeight: 500,
fontSize: '0.875rem',
color: INK,
marginBottom: '0.75rem',
transition: 'border-color 0.2s',
}}
onMouseEnter={(e) => e.currentTarget.style.borderColor = INK}
onMouseLeave={(e) => e.currentTarget.style.borderColor = BORDER}
>
<GoogleIcon />
Continua con Google
</button>
{/* ── Coming soon ── */}
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'center', marginBottom: '0.5rem' }}>
{[
{ name: 'Facebook', icon: '📘' },
{ name: 'Microsoft', icon: '🪟' },
{ name: 'Apple', icon: '🍎' },
].map(({ name, icon }) => (
<button
key={name}
title={`${name} — Disponibile a breve`}
style={{
padding: '0.5rem 0.85rem',
backgroundColor: '#F5F5F5',
border: `1px solid ${BORDER}`,
borderRadius: 0,
cursor: 'not-allowed',
fontSize: '1rem',
opacity: 0.45,
}}
>
{icon}
</button>
))}
</div>
<p style={{ textAlign: 'center', fontSize: '0.73rem', color: INK_MUTED, marginBottom: '1.5rem' }}>
Altri provider disponibili a breve
</p>
{/* ── Redeem link ── */}
<p style={{ textAlign: 'center', fontSize: '0.85rem', color: INK_MUTED }}>
Hai un codice Pro?{' '}
<button
onClick={() => setShowRedeemModal(true)}
style={{
background: 'none',
border: 'none',
color: ACCENT,
cursor: 'pointer',
fontWeight: 600,
fontSize: '0.85rem',
padding: 0,
textDecoration: 'underline',
textUnderlineOffset: '3px',
}}
>
Riscattalo →
</button>
</p>
</div>
</div> </div>
{showRedeemModal && <RedeemModal onClose={() => setShowRedeemModal(false)} />}
</div> </div>
) )
} }
// ── Redeem modal ─────────────────────────────────────────────────────────────── function Field({ label, children }) {
return (
function RedeemModal({ onClose }) { <div>
const [code, setCode] = useState('') <label style={{
const [loading, setLoading] = useState(false) display: 'block',
const [message, setMessage] = useState('') fontSize: '0.875rem',
const [error, setError] = useState('') fontWeight: 500,
color: '#1A1A1A',
const handleRedeem = async (e) => { marginBottom: '0.5rem',
e.preventDefault() }}>
setLoading(true) {label}
setError('') </label>
setMessage('') {children}
try { </div>
const { api } = await import('../api') )
const result = await api.post('/auth/redeem', { code }) }
setMessage(`Codice riscattato! Piano Pro attivo fino al ${new Date(result.subscription_expires_at).toLocaleDateString('it-IT')}.`)
} catch (err) {
setError(err.message || 'Codice non valido o già utilizzato.')
} finally {
setLoading(false)
}
}
function ErrorBox({ message }) {
return ( return (
<div style={{ <div style={{
position: 'fixed', inset: 0, padding: '1rem',
backgroundColor: 'rgba(0,0,0,0.45)', backgroundColor: '#FFF5F5',
display: 'flex', alignItems: 'center', justifyContent: 'center', borderLeft: '4px solid #E85A4F',
zIndex: 1000,
}}> }}>
<div style={{ <p style={{ color: '#C53030', fontSize: '0.875rem', margin: 0 }}>{message}</p>
position: 'relative',
backgroundColor: 'white',
borderTop: `4px solid ${ACCENT}`,
padding: '2rem',
width: '380px',
maxWidth: '90vw',
boxShadow: '0 20px 60px rgba(0,0,0,0.2)',
}}>
<h3 style={{ margin: '0 0 0.4rem', fontSize: '1.1rem', color: INK, fontFamily: "'Fraunces', serif" }}>
Riscatta Codice Pro
</h3>
<p style={{ margin: '0 0 1.25rem', fontSize: '0.85rem', color: INK_MUTED }}>
Inserisci il tuo codice di attivazione (es. LP-XXXXXXXX)
</p>
{message ? (
<div style={{ padding: '0.75rem', backgroundColor: '#F0FDF4', border: '1px solid #86EFAC', color: '#16A34A', fontSize: '0.875rem', marginBottom: '1rem' }}>
{message}
</div>
) : (
<form onSubmit={handleRedeem}>
{error && (
<div style={{ padding: '0.75rem', backgroundColor: '#FFF5F5', border: '1px solid #FED7D7', color: '#C53030', fontSize: '0.875rem', marginBottom: '0.75rem' }}>
{error}
</div>
)}
<input
type="text"
value={code}
onChange={(e) => setCode(e.target.value.toUpperCase())}
placeholder="LP-XXXXXXXXXXXXXXXX"
required
style={{ ...inputStyle, marginBottom: '0.75rem', fontFamily: 'monospace', letterSpacing: '0.05em' }}
/>
<button
type="submit"
disabled={loading}
style={{
width: '100%',
padding: '0.7rem',
backgroundColor: loading ? '#888' : INK,
color: 'white',
border: 'none',
borderRadius: 0,
fontFamily: "'DM Sans', sans-serif",
fontWeight: 600,
cursor: loading ? 'not-allowed' : 'pointer',
}}
>
{loading ? 'Verifica...' : 'Riscatta'}
</button>
</form>
)}
<button
onClick={onClose}
style={{
marginTop: '1rem',
width: '100%',
padding: '0.6rem',
backgroundColor: 'transparent',
border: `1px solid ${BORDER}`,
borderRadius: 0,
cursor: 'pointer',
color: INK_MUTED,
fontFamily: "'DM Sans', sans-serif",
fontSize: '0.875rem',
}}
>
Chiudi
</button>
</div>
</div> </div>
) )
} }
// ── Google Icon SVG ──────────────────────────────────────────────────────────── function SubmitButton({ loading, children }) {
return (
<button
type="submit"
disabled={loading}
style={{
width: '100%',
height: 48,
backgroundColor: loading ? '#888' : '#1A1A1A',
color: 'white',
border: 'none',
borderRadius: 0,
fontFamily: "'DM Sans', sans-serif",
fontWeight: 600,
fontSize: '1rem',
cursor: loading ? 'not-allowed' : 'pointer',
transition: 'background-color 0.2s, transform 0.15s',
}}
onMouseEnter={(e) => { if (!loading) { e.target.style.backgroundColor = '#E85A4F'; e.target.style.transform = 'translateY(-1px)' } }}
onMouseLeave={(e) => { if (!loading) { e.target.style.backgroundColor = '#1A1A1A'; e.target.style.transform = 'translateY(0)' } }}
>
{children}
</button>
)
}
function GoogleIcon() { function GoogleIcon() {
return ( return (
<svg width="18" height="18" viewBox="0 0 48 48"> <svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#FFC107" d="M43.6 20H24v8h11.3C33.6 33.1 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.8 1.2 8 3l5.7-5.7C34.2 6.6 29.3 4.5 24 4.5 12.7 4.5 3.5 13.7 3.5 25S12.7 45.5 24 45.5c10.5 0 19.5-7.6 19.5-21 0-1.2-.1-2.4-.4-3.5z" /> <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
<path fill="#FF3D00" d="M6.3 14.7l6.6 4.8C14.6 15.1 19 12 24 12c3.1 0 5.8 1.2 8 3l5.7-5.7C34.2 6.6 29.3 4.5 24 4.5c-7.7 0-14.4 4.4-17.7 10.2z" /> <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
<path fill="#4CAF50" d="M24 45.5c5.2 0 9.9-1.9 13.5-5l-6.2-5.2C29.3 37 26.8 38 24 38c-5.3 0-9.7-3-11.3-7.4l-6.6 5.1C9.6 41.1 16.3 45.5 24 45.5z" /> <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
<path fill="#1976D2" d="M43.6 20H24v8h11.3c-.7 2.1-2 3.9-3.7 5.2l6.2 5.2c3.7-3.4 5.7-8.4 5.7-13.4 0-1.2-.1-2.4-.4-3.5z" /> <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
</svg> </svg>
) )
} }
// ── Shared inline styles ─────────────────────────────────────────────────────── // ── Shared style ───────────────────────────────────────────────────────────────
const labelStyle = {
display: 'block',
fontSize: '0.78rem',
fontWeight: 600,
color: INK,
marginBottom: '0.4rem',
letterSpacing: '0.03em',
textTransform: 'uppercase',
}
const inputStyle = { const inputStyle = {
display: 'flex',
width: '100%', width: '100%',
padding: '0.65rem 0.875rem', height: 44,
border: `1px solid ${BORDER}`, border: '1px solid #E5E0D8',
borderRadius: 0, borderRadius: 0,
fontSize: '0.875rem', backgroundColor: 'white',
padding: '0 1rem',
fontSize: '1rem',
color: '#1A1A1A',
outline: 'none', outline: 'none',
boxSizing: 'border-box', boxSizing: 'border-box',
color: INK,
backgroundColor: '#FFFFFF',
fontFamily: "'DM Sans', sans-serif", fontFamily: "'DM Sans', sans-serif",
transition: 'border-color 0.15s', transition: 'border-color 0.15s',
} }