Redesign sign-in / create-account page: split layout, benefits panel, show/hide password, real form submit; v2.10.6
Publish Container / build (push) Has been cancelled
Publish Container / build (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b708371cc5
commit
640d0c1c18
3 files changed
+232
-48
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "siteforge",
|
||||
"private": true,
|
||||
"version": "2.10.5",
|
||||
"version": "2.10.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
+181
@@ -4033,6 +4033,187 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ── Sign in / Create account (split layout) ── */
|
||||
.auth-page {
|
||||
min-height: 70vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: clamp(2rem, 6vw, 4rem) 1.25rem;
|
||||
background:
|
||||
radial-gradient(ellipse at top, rgba(201, 168, 76, 0.12) 0%, transparent 55%),
|
||||
var(--brand-black);
|
||||
}
|
||||
|
||||
.auth-shell {
|
||||
width: min(1000px, 100%);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
|
||||
align-items: start;
|
||||
gap: clamp(1.5rem, 4vw, 3rem);
|
||||
}
|
||||
|
||||
.auth-shell--single {
|
||||
grid-template-columns: minmax(0, 520px);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.auth-aside {
|
||||
padding: 1rem 0.5rem;
|
||||
}
|
||||
|
||||
.auth-aside-title {
|
||||
font-family: var(--brand-font-heading);
|
||||
font-size: clamp(1.7rem, 3.4vw, 2.3rem);
|
||||
line-height: 1.15;
|
||||
color: var(--brand-warm-white);
|
||||
margin: 0.4rem 0 1.5rem;
|
||||
}
|
||||
|
||||
.auth-benefits {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.1rem;
|
||||
}
|
||||
|
||||
.auth-benefits li {
|
||||
display: flex;
|
||||
gap: 0.9rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.auth-benefits li > span {
|
||||
flex-shrink: 0;
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: rgba(201, 168, 76, 0.12);
|
||||
border: 1px solid rgba(201, 168, 76, 0.35);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.auth-benefits strong {
|
||||
display: block;
|
||||
color: var(--brand-warm-white);
|
||||
font-family: var(--brand-font-heading);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.auth-benefits p {
|
||||
margin: 0;
|
||||
color: #b9aa86;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background: #121212;
|
||||
border: 1px solid rgba(201, 168, 76, 0.24);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
|
||||
padding: clamp(1.5rem, 4vw, 2.25rem);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.auth-card h1 {
|
||||
font-family: var(--brand-font-heading);
|
||||
font-size: clamp(1.8rem, 4vw, 2.3rem);
|
||||
color: var(--brand-warm-white);
|
||||
margin: 0.2rem 0 0.75rem;
|
||||
}
|
||||
|
||||
.auth-card p {
|
||||
font-family: var(--brand-font-body);
|
||||
color: var(--brand-muted);
|
||||
}
|
||||
|
||||
.auth-card-eyebrow { margin: 0; }
|
||||
|
||||
.auth-card .study-signup-form { margin-top: 0.5rem; }
|
||||
|
||||
.auth-card .study-signup-actions { margin-top: 0.75rem; }
|
||||
|
||||
.auth-card .study-signup-actions .btn-primary {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.auth-password-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.auth-password-wrap input {
|
||||
width: 100%;
|
||||
padding-right: 4.5rem;
|
||||
}
|
||||
|
||||
.auth-password-toggle {
|
||||
position: absolute;
|
||||
right: 0.35rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--brand-gold);
|
||||
font-family: var(--brand-font-body);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
padding: 0.4rem 0.6rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.auth-switch {
|
||||
margin: 1.1rem 0 0;
|
||||
text-align: center;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.auth-link {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--brand-gold);
|
||||
font: inherit;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.auth-footer-links {
|
||||
margin: 1.5rem 0 0;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.auth-footer-links a {
|
||||
color: var(--brand-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-footer-links a:hover {
|
||||
color: var(--brand-warm-white);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.auth-shell {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Form first on phones; the benefits follow below it */
|
||||
.auth-aside {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Study Signup Page ── */
|
||||
.study-signup-page-card {
|
||||
max-width: 680px;
|
||||
|
||||
+50
-47
@@ -1130,6 +1130,7 @@ export function StudySignupPage() {
|
||||
const [resendStatus, setResendStatus] = useState<'idle' | 'sending' | 'sent'>('idle')
|
||||
// Forgot-password state
|
||||
const [forgotSent, setForgotSent] = useState(false)
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
readJson<StudyAuthStatusResponse>('/api/study-auth/status')
|
||||
@@ -1215,10 +1216,38 @@ export function StudySignupPage() {
|
||||
}
|
||||
}, [email])
|
||||
|
||||
const showAside = !done && mode !== 'forgot' && !totpPendingToken
|
||||
|
||||
return (
|
||||
<main className="thanks-page" aria-label={mode === 'signup' ? 'Create account' : mode === 'forgot' ? 'Reset password' : 'Sign in'}>
|
||||
<div className="thanks-card study-signup-page-card">
|
||||
<main className="auth-page" aria-label={mode === 'signup' ? 'Create account' : mode === 'forgot' ? 'Reset password' : 'Sign in'}>
|
||||
<div className={`auth-shell${showAside ? '' : ' auth-shell--single'}`}>
|
||||
{showAside && (
|
||||
<aside className="auth-aside" aria-label={mode === 'signup' ? 'Why create an account' : 'Welcome back'}>
|
||||
{mode === 'signup' ? (
|
||||
<>
|
||||
<p className="eyebrow">Free Student Account</p>
|
||||
<h2 className="auth-aside-title">Make every lesson your own.</h2>
|
||||
<ul className="auth-benefits">
|
||||
<li><span aria-hidden="true">📝</span><div><strong>Notes on every lesson</strong><p>Up to 500 notes across all tracks, each holding about 2,000 words.</p></div></li>
|
||||
<li><span aria-hidden="true">✅</span><div><strong>Track your progress</strong><p>Pick up exactly where you left off, on any device.</p></div></li>
|
||||
<li><span aria-hidden="true">🔒</span><div><strong>Private to you</strong><p>Your notes are tied to your account. Nobody else can read them.</p></div></li>
|
||||
<li><span aria-hidden="true">⚡</span><div><strong>Free, no ads</strong><p>Just an email and password. You stay signed in for 30 days.</p></div></li>
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="eyebrow">Free Student Account</p>
|
||||
<h2 className="auth-aside-title">Pick up where you left off.</h2>
|
||||
<ul className="auth-benefits">
|
||||
<li><span aria-hidden="true">📝</span><div><strong>Your notes</strong><p>Every lesson note you've saved, waiting for you.</p></div></li>
|
||||
<li><span aria-hidden="true">✅</span><div><strong>Your progress</strong><p>Resume your studies right where you stopped.</p></div></li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</aside>
|
||||
)}
|
||||
<div className="auth-card">
|
||||
<p className="eyebrow auth-card-eyebrow">{mode === 'signup' ? 'Create account' : mode === 'forgot' ? 'Account recovery' : 'Sign in'}</p>
|
||||
<h1>{mode === 'signup' ? 'Create Your Account' : mode === 'forgot' ? 'Reset Your Password' : 'Welcome Back'}</h1>
|
||||
|
||||
{done ? (
|
||||
@@ -1317,7 +1346,7 @@ export function StudySignupPage() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="study-signup-form">
|
||||
<form className="study-signup-form" noValidate onSubmit={e => { e.preventDefault(); void submit() }}>
|
||||
<label htmlFor="signup-email">Email</label>
|
||||
<input
|
||||
id="signup-email"
|
||||
@@ -1337,9 +1366,10 @@ export function StudySignupPage() {
|
||||
/>
|
||||
{fieldErrors.email && <p className="study-signup-field-error">{fieldErrors.email}</p>}
|
||||
<label htmlFor="signup-password">Password</label>
|
||||
<div className="auth-password-wrap">
|
||||
<input
|
||||
id="signup-password"
|
||||
type="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={e => { setPassword(e.target.value); if (fieldErrors.password) setFieldErrors(prev => ({ ...prev, password: undefined })) }}
|
||||
onBlur={() => {
|
||||
@@ -1349,10 +1379,13 @@ export function StudySignupPage() {
|
||||
setFieldErrors(prev => ({ ...prev, password: undefined }))
|
||||
}
|
||||
}}
|
||||
placeholder="At least 8 characters"
|
||||
placeholder={mode === 'signup' ? 'At least 8 characters' : 'Your password'}
|
||||
autoComplete={mode === 'signup' ? 'new-password' : 'current-password'}
|
||||
onKeyDown={e => e.key === 'Enter' && submit()}
|
||||
/>
|
||||
<button type="button" className="auth-password-toggle" aria-pressed={showPassword} onClick={() => setShowPassword(v => !v)}>
|
||||
{showPassword ? 'Hide' : 'Show'}
|
||||
</button>
|
||||
</div>
|
||||
{fieldErrors.password && <p className="study-signup-field-error">{fieldErrors.password}</p>}
|
||||
{mode === 'login' && (
|
||||
<button type="button" onClick={() => { setMode('forgot'); setMessage(''); setForgotSent(false) }} style={{ background: 'none', border: 'none', color: '#c9a84c', cursor: 'pointer', fontSize: '0.85rem', textDecoration: 'underline', padding: 0, textAlign: 'left', marginTop: '0.5rem', alignSelf: 'flex-start' }}>
|
||||
@@ -1369,56 +1402,26 @@ export function StudySignupPage() {
|
||||
<span>Send me updates from Verse by Verse with Nate. I can unsubscribe anytime.</span>
|
||||
</label>
|
||||
)}
|
||||
{message && <p className="study-signup-error">{message}</p>}
|
||||
{message && <p className="study-signup-error" role="alert">{message}</p>}
|
||||
<div className="study-signup-actions">
|
||||
<button type="button" className="btn-primary" disabled={busy} onClick={submit}>
|
||||
<button type="submit" className="btn-primary" disabled={busy}>
|
||||
{busy ? (mode === 'signup' ? 'Creating...' : 'Signing in...') : (mode === 'signup' ? 'Create Account' : 'Sign In')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="study-signup-actions" style={{ marginTop: '1rem' }}>
|
||||
{mode === 'login' && (
|
||||
<button type="button" className="btn-secondary" onClick={() => { setMode('signup'); setMessage('') }}>
|
||||
Don't have an account? Sign up here
|
||||
<p className="auth-switch">
|
||||
{mode === 'login' ? "Don't have an account? " : 'Already have an account? '}
|
||||
<button type="button" className="auth-link" onClick={() => { setMode(mode === 'login' ? 'signup' : 'login'); setMessage('') }}>
|
||||
{mode === 'login' ? 'Sign up' : 'Sign in'}
|
||||
</button>
|
||||
)}
|
||||
{mode === 'signup' && (
|
||||
<button type="button" className="btn-secondary" onClick={() => { setMode('login'); setMessage('') }}>
|
||||
Already have an account? Sign in instead
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
{mode === 'signup' && (
|
||||
<div className="study-signup-why-grid" style={{ marginTop: '2rem' }}>
|
||||
<article className="study-signup-why-card">
|
||||
<p className="study-signup-why-icon" aria-hidden="true">📝</p>
|
||||
<h3>Notes on Every Lesson</h3>
|
||||
<p>Save your own notes per lesson — up to 500 notes across all tracks.</p>
|
||||
</article>
|
||||
<article className="study-signup-why-card">
|
||||
<p className="study-signup-why-icon" aria-hidden="true">📖</p>
|
||||
<h3>Space to Go Deep</h3>
|
||||
<p>Each note holds ~2,000 words so you can write as much as you need.</p>
|
||||
</article>
|
||||
<article className="study-signup-why-card">
|
||||
<p className="study-signup-why-icon" aria-hidden="true">🔒</p>
|
||||
<h3>Private to You</h3>
|
||||
<p>Your notes are tied to your account. Nobody else can read them.</p>
|
||||
</article>
|
||||
<article className="study-signup-why-card">
|
||||
<p className="study-signup-why-icon" aria-hidden="true">⚡</p>
|
||||
<h3>Free Forever</h3>
|
||||
<p>Just an email and password. No charge, no ads. Stay signed in 30 days.</p>
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="study-signup-actions" style={{ marginTop: '1.5rem' }}>
|
||||
<Link to="/study/account" className="btn-secondary">My Account</Link>
|
||||
<Link to="/study" className="btn-secondary">Back to Studies</Link>
|
||||
<p className="auth-footer-links">
|
||||
<Link to="/study">← Back to Studies</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in new issue
Block a user