Compare commits

...

12 commits

Author SHA1 Message Date
b68a75d0b0 Merge pull request 'fix/billing-checkbox-alignment' (#17) from fix/billing-checkbox-alignment into master
Some checks are pending
CI / checks (push) Waiting to run
Reviewed-on: #17
2025-12-21 16:43:12 +02:00
Tero Halla-aho
ab559b1a66 Align VAT toggle with grid columns
Some checks failed
CI / checks (push) Waiting to run
CI / checks (pull_request) Has been cancelled
2025-12-21 15:08:05 +02:00
Tero Halla-aho
cfbe570fb2 Use flex row for billing toggles
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 14:50:36 +02:00
Tero Halla-aho
5b08ca33b3 Force 3-column grid for billing toggles
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 14:19:45 +02:00
Tero Halla-aho
6446b93950 Center billing toggle checkbox within card
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 14:12:57 +02:00
Tero Halla-aho
1882d58474 Align billing toggles with flex layout
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 14:01:57 +02:00
Tero Halla-aho
e79f4c0966 Grid-align billing toggle cards
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 13:53:46 +02:00
Tero Halla-aho
34b429c50f Prevent billing toggles from wrapping
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 13:21:13 +02:00
Tero Halla-aho
e211550569 Add icon + larger toggles for billing cards
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 13:12:22 +02:00
Tero Halla-aho
3bf1b67256 Refactor profile page layout
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 12:29:57 +02:00
Tero Halla-aho
c0ae6cc9a0 Restyle billing toggles like amenity cards
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 12:12:38 +02:00
Tero Halla-aho
8b2546faaf Tighten billing checkbox alignment
Some checks are pending
CI / checks (push) Waiting to run
2025-12-21 11:59:12 +02:00

View file

@ -157,73 +157,89 @@ export default function ProfilePage() {
}
return (
<main className="panel" style={{ maxWidth: 640, margin: '40px auto' }}>
<h1>{t('myProfileTitle')}</h1>
{message ? <p style={{ color: 'green' }}>{message}</p> : null}
<main style={{ maxWidth: 1040, margin: '32px auto', display: 'grid', gap: 18 }}>
<header className="panel" style={{ display: 'grid', gap: 12, padding: 18 }}>
<div>
<h1 style={{ margin: 0 }}>{t('myProfileTitle')}</h1>
{message ? <p style={{ color: 'green', margin: '6px 0 0' }}>{message}</p> : null}
</div>
{user ? (
<>
<div style={{ display: 'grid', gap: 6, gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))' }}>
<div><strong>{t('profileEmail')}:</strong> {user.email}</div>
<div><strong>{t('profileName')}:</strong> {user.name ?? '—'}</div>
<div><strong>{t('profilePhone')}:</strong> {user.phone ?? '—'}</div>
<div><strong>{t('profileRole')}:</strong> {user.role}</div>
<div><strong>{t('profileStatus')}:</strong> {user.status}</div>
<div><strong>{t('profileEmailVerified')}:</strong> {user.emailVerifiedAt ? t('yes') : t('no')}</div>
<div><strong>{t('profileApproved')}:</strong> {user.approvedAt ? t('yes') : t('no')}</div>
</div>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
<a className="button secondary" href="/listings/mine">
{t('navMyListings')}
</a>
<a className="button secondary" href="/listings/new">
{t('navNewListing')}
</a>
</div>
</>
) : (
<p style={{ color: 'red', margin: 0 }}>{error ?? t('notLoggedIn')}</p>
)}
</header>
{user ? (
<>
<ul>
<li>
<strong>{t('profileEmail')}:</strong> {user.email}
</li>
<li>
<strong>{t('profileName')}:</strong> {user.name ?? '—'}
</li>
<li>
<strong>{t('profilePhone')}:</strong> {user.phone ?? '—'}
</li>
<li>
<strong>{t('profileRole')}:</strong> {user.role}
</li>
<li>
<strong>{t('profileStatus')}:</strong> {user.status}
</li>
<li>
<strong>{t('profileEmailVerified')}:</strong> {user.emailVerifiedAt ? t('yes') : t('no')}
</li>
<li>
<strong>{t('profileApproved')}:</strong> {user.approvedAt ? t('yes') : t('no')}
</li>
</ul>
<div style={{ marginTop: 16, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
<a className="button secondary" href="/listings/mine">
{t('navMyListings')}
</a>
<a className="button secondary" href="/listings/new">
{t('navNewListing')}
</a>
</div>
<form onSubmit={onSave} style={{ marginTop: 20, display: 'grid', gap: 10, maxWidth: 420 }}>
<label>
{t('profileName')}
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
<label>
{t('profilePhone')}
<input value={phone} onChange={(e) => setPhone(e.target.value)} />
</label>
<label>
{t('passwordLabel')} ({t('passwordHint')})
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} minLength={8} />
</label>
<p style={{ fontSize: 12, color: '#666' }}>{t('emailLocked')}</p>
<button className="button" type="submit" disabled={saving}>
{saving ? t('saving') : t('save')}
</button>
</form>
<section style={{ marginTop: 30 }}>
<h2 style={{ marginBottom: 6 }}>{t('billingSettingsTitle')}</h2>
<p style={{ color: '#444', marginBottom: 12 }}>{t('billingSettingsLead')}</p>
{billingMessage ? <p style={{ color: 'green' }}>{billingMessage}</p> : null}
{billingError ? <p style={{ color: 'red' }}>{billingError}</p> : null}
<form onSubmit={onSaveBilling} style={{ display: 'grid', gap: 14, marginTop: 8 }}>
<label style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<input type="checkbox" checked={billingEnabled} onChange={(e) => setBillingEnabled(e.target.checked)} />
<span>{t('billingEnableLabel')}</span>
<section className="panel" style={{ padding: 18, display: 'grid', gap: 12 }}>
<h2 style={{ margin: 0 }}>{t('myProfileTitle')}</h2>
<form onSubmit={onSave} style={{ display: 'grid', gap: 12 }}>
<div style={{ display: 'grid', gap: 10, gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', maxWidth: 760 }}>
<label>
{t('profileName')}
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
<label>
{t('profilePhone')}
<input value={phone} onChange={(e) => setPhone(e.target.value)} />
</label>
<label>
{t('passwordLabel')} ({t('passwordHint')})
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} minLength={8} />
</label>
</div>
<p style={{ fontSize: 12, color: '#666', margin: 0 }}>{t('emailLocked')}</p>
<div>
<button className="button" type="submit" disabled={saving} style={{ minWidth: 160 }}>
{saving ? t('saving') : t('save')}
</button>
</div>
</form>
</section>
<section className="panel" style={{ padding: 18, display: 'grid', gap: 12 }}>
<div>
<h2 style={{ margin: 0 }}>{t('billingSettingsTitle')}</h2>
<p style={{ color: '#444', margin: '6px 0 0' }}>{t('billingSettingsLead')}</p>
</div>
{billingMessage ? <p style={{ color: 'green', margin: 0 }}>{billingMessage}</p> : null}
{billingError ? <p style={{ color: 'red', margin: 0 }}>{billingError}</p> : null}
<form onSubmit={onSaveBilling} style={{ display: 'grid', gap: 14 }}>
<label
className="amenity-option"
style={{ maxWidth: 520, display: 'grid', gridTemplateColumns: 'auto 1fr auto', alignItems: 'center', columnGap: 10, minHeight: 52 }}
>
<span aria-hidden className="amenity-emoji">💸</span>
<span className="amenity-name" style={{ fontWeight: 600 }}>{t('billingEnableLabel')}</span>
<input
type="checkbox"
checked={billingEnabled}
onChange={(e) => setBillingEnabled(e.target.checked)}
style={{ width: 22, height: 22, margin: 0, justifySelf: 'end' }}
/>
</label>
{billingEnabled ? (
<>
<div style={{ display: 'grid', gap: 8 }}>
<div style={{ display: 'grid', gap: 10, gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', maxWidth: 760 }}>
<label>
{t('billingAccountNameLabel')}
<input value={billingAccountName} onChange={(e) => setBillingAccountName(e.target.value)} placeholder="Example Oy" />
@ -232,16 +248,21 @@ export default function ProfilePage() {
{t('billingIbanLabel')}
<input value={billingIban} onChange={(e) => setBillingIban(e.target.value)} placeholder="FI00 1234 5600 0007 85" />
</label>
<label style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<label
className="amenity-option"
style={{ maxWidth: 520, display: 'grid', gridTemplateColumns: 'auto 1fr auto', alignItems: 'center', columnGap: 10, minHeight: 52 }}
>
<span aria-hidden className="amenity-emoji">🧾</span>
<span className="amenity-name" style={{ fontWeight: 600 }}>{t('billingIncludeVat')}</span>
<input
type="checkbox"
checked={billingIncludeVatLine}
onChange={(e) => setBillingIncludeVatLine(e.target.checked)}
style={{ width: 22, height: 22, margin: 0, justifySelf: 'end' }}
/>
<span>{t('billingIncludeVat')}</span>
</label>
</div>
<div style={{ border: '1px solid #eee', padding: 12, borderRadius: 6 }}>
<div style={{ border: '1px solid rgba(148,163,184,0.3)', padding: 12, borderRadius: 8, background: 'rgba(255,255,255,0.02)' }}>
<div style={{ marginBottom: 8 }}>
<strong>{t('billingListingsTitle')}</strong>
<div style={{ color: '#555', fontSize: 13 }}>{t('billingListingsLead')}</div>
@ -260,11 +281,11 @@ export default function ProfilePage() {
? 'yes'
: 'no';
return (
<div key={listing.id} style={{ border: '1px solid #f0f0f0', padding: 10, borderRadius: 4 }}>
<div style={{ fontWeight: 600, marginBottom: 6 }}>
<div key={listing.id} style={{ border: '1px solid #f0f0f0', padding: 10, borderRadius: 6, display: 'grid', gap: 8 }}>
<div style={{ fontWeight: 600 }}>
{listing.title} ({listing.slug})
</div>
<div style={{ display: 'grid', gap: 8 }}>
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))' }}>
<label>
{t('billingAccountNameLabel')}
<input
@ -321,19 +342,17 @@ export default function ProfilePage() {
</div>
</>
) : (
<p style={{ color: '#666' }}>{t('billingDisabledHint')}</p>
<p style={{ color: '#666', margin: 0 }}>{t('billingDisabledHint')}</p>
)}
<div>
<button className="button" type="submit" disabled={billingSaving}>
<button className="button" type="submit" disabled={billingSaving} style={{ minWidth: 160 }}>
{billingSaving ? t('saving') : t('save')}
</button>
</div>
</form>
</section>
</>
) : (
<p style={{ color: 'red' }}>{error ?? t('notLoggedIn')}</p>
)}
) : null}
</main>
);
}