Rework app bar, notes layout, Settings/Admin/login restyle
Build and Push Docker Image / build-and-push (push) Failing after 20s
Build and Push Docker Image / build-and-push (push) Failing after 20s
App bar: brand, Studies / Read Bible / Reading Plans, and one account menu (theme, Settings, Admin, Log out). Study stage: notes get their own box above the resources in the right column. Settings, Admin and the login page use the reader themes; add a theme-aware danger colour. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
31abee0695
commit
ecb1c701fa
22 files changed
+351
-289
No files matched your search
+72
-92
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useApp } from '../context/AppContext.js';
|
||||
import AuthStatus from '../components/AuthStatus.jsx';
|
||||
import PageShell from '../components/study/PageShell.jsx';
|
||||
import {
|
||||
startMfaSetup,
|
||||
confirmMfaSetup,
|
||||
@@ -160,52 +160,32 @@ export default function SettingsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 text-slate-900">
|
||||
<header className="border-b border-slate-200 bg-slate-900 text-white shadow-sm">
|
||||
<div className="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-4 px-4 py-5 sm:px-6 lg:px-8">
|
||||
<div>
|
||||
<p className="text-sm uppercase tracking-[0.24em] text-slate-300">Bible Study Project</p>
|
||||
<h1 className="mt-2 text-2xl font-semibold">Account Settings</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={goHome}
|
||||
className="rounded-xl border border-slate-500 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-slate-700"
|
||||
>
|
||||
← Back
|
||||
</button>
|
||||
<AuthStatus />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="mx-auto max-w-2xl px-4 py-8 sm:px-6 lg:px-8 space-y-6">
|
||||
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-2">
|
||||
<h2 className="text-lg font-semibold text-slate-900">Account</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
Signed in as <span className="font-medium text-slate-700">{authUser.email}</span>
|
||||
<PageShell title="Account Settings" onBack={goHome} maxWidth="max-w-2xl">
|
||||
<main className="mx-auto max-w-2xl space-y-5 px-4 py-6">
|
||||
<section className="rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-6 space-y-2">
|
||||
<h2 className="text-base font-medium text-[var(--r-text)]">Account</h2>
|
||||
<p className="text-sm text-[var(--r-sub)]">
|
||||
Signed in as <span className="font-medium text-[var(--r-text)]">{authUser.email}</span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-4">
|
||||
<section className="rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900">Change password</h2>
|
||||
<p className="text-sm text-slate-500">Changing your password signs you out of every other device — this one stays signed in.</p>
|
||||
<h2 className="text-base font-medium text-[var(--r-text)]">Change password</h2>
|
||||
<p className="text-sm text-[var(--r-sub)]">Changing your password signs you out of every other device — this one stays signed in.</p>
|
||||
</div>
|
||||
<form onSubmit={submitChangePassword} className="space-y-3">
|
||||
<label className="block text-sm font-medium text-slate-700">
|
||||
Current password
|
||||
<label className="block text-sm">Current password
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
autoComplete="current-password"
|
||||
value={changePasswordForm.current}
|
||||
onChange={(e) => setChangePasswordForm((f) => ({ ...f, current: e.target.value }))}
|
||||
className="mt-1 block w-full max-w-xs rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full max-w-xs rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-slate-700">
|
||||
<label className="block text-sm font-medium text-[var(--r-text)]">
|
||||
New password
|
||||
<input
|
||||
type="password"
|
||||
@@ -214,10 +194,10 @@ export default function SettingsPage() {
|
||||
autoComplete="new-password"
|
||||
value={changePasswordForm.next}
|
||||
onChange={(e) => setChangePasswordForm((f) => ({ ...f, next: e.target.value }))}
|
||||
className="mt-1 block w-full max-w-xs rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full max-w-xs rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-slate-700">
|
||||
<label className="block text-sm font-medium text-[var(--r-text)]">
|
||||
Confirm new password
|
||||
<input
|
||||
type="password"
|
||||
@@ -226,26 +206,26 @@ export default function SettingsPage() {
|
||||
autoComplete="new-password"
|
||||
value={changePasswordForm.confirm}
|
||||
onChange={(e) => setChangePasswordForm((f) => ({ ...f, confirm: e.target.value }))}
|
||||
className="mt-1 block w-full max-w-xs rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full max-w-xs rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
</label>
|
||||
{changePasswordError && <p className="text-sm text-rose-600">{changePasswordError}</p>}
|
||||
{changePasswordSaved && <p className="text-sm text-emerald-600">Password changed.</p>}
|
||||
{changePasswordError && <p className="text-sm text-[var(--r-danger,#b42318)]">{changePasswordError}</p>}
|
||||
{changePasswordSaved && <p className="text-sm text-[var(--r-accent)]">Password changed.</p>}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={changePasswordBusy}
|
||||
className="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-slate-300"
|
||||
className="rounded-full bg-[var(--r-accent)] px-5 py-2 text-sm font-medium text-[var(--r-on)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{changePasswordBusy ? 'Saving…' : 'Change password'}
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-4">
|
||||
<section className="rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900">Podcast / show name</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
Only needed if you use "🎙 Prepare for Podcast" on the study page — it fills in your show's
|
||||
<h2 className="text-base font-medium text-[var(--r-text)]">Podcast / show name</h2>
|
||||
<p className="text-sm text-[var(--r-sub)]">
|
||||
Only needed if you use "Podcast → Claude" on a study's Script stage — it fills in your show's
|
||||
name when asking Claude to write an episode script. Leave blank if you're just doing personal
|
||||
study; that button still works, it just won't name a specific show.
|
||||
</p>
|
||||
@@ -256,23 +236,23 @@ export default function SettingsPage() {
|
||||
value={podcastNameInput}
|
||||
onChange={(e) => setPodcastNameInput(e.target.value)}
|
||||
placeholder="e.g. Verse by Verse with Nate: A Journey Through Scripture"
|
||||
className="w-full flex-1 rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="w-full flex-1 rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={podcastNameSaving}
|
||||
className="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-slate-300"
|
||||
className="rounded-full bg-[var(--r-accent)] px-5 py-2 text-sm font-medium text-[var(--r-on)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{podcastNameSaving ? 'Saving…' : 'Save'}
|
||||
</button>
|
||||
</form>
|
||||
{podcastNameSaved && <p className="text-sm text-emerald-600">Saved.</p>}
|
||||
{podcastNameSaved && <p className="text-sm text-[var(--r-accent)]">Saved.</p>}
|
||||
</section>
|
||||
|
||||
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-4">
|
||||
<section className="rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900">My reading journal</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
<h2 className="text-base font-medium text-[var(--r-text)]">My reading journal</h2>
|
||||
<p className="text-sm text-[var(--r-sub)]">
|
||||
A document of your bookmarked verses, highlighted passages, and completed reading plans.
|
||||
Bookmarks and highlights only live on this device (they're not synced), so this reflects
|
||||
what's saved here.
|
||||
@@ -283,7 +263,7 @@ export default function SettingsPage() {
|
||||
type="button"
|
||||
onClick={downloadJournalDocx}
|
||||
disabled={journalBusy !== ''}
|
||||
className="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-slate-300"
|
||||
className="rounded-full bg-[var(--r-accent)] px-5 py-2 text-sm font-medium text-[var(--r-on)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{journalBusy === 'docx' ? 'Building…' : 'Download as Word (.docx)'}
|
||||
</button>
|
||||
@@ -291,83 +271,83 @@ export default function SettingsPage() {
|
||||
type="button"
|
||||
onClick={printJournal}
|
||||
disabled={journalBusy !== ''}
|
||||
className="rounded-md border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="rounded-full border border-[var(--r-line)] px-5 py-2 text-sm font-medium transition hover:bg-black/5 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{journalBusy === 'pdf' ? 'Building…' : 'Print / Save as PDF'}
|
||||
</button>
|
||||
</div>
|
||||
{journalError && <p className="text-sm text-rose-600">{journalError}</p>}
|
||||
{journalError && <p className="text-sm text-[var(--r-danger,#b42318)]">{journalError}</p>}
|
||||
</section>
|
||||
|
||||
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-4">
|
||||
<section className="rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900">Reading reminders</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
<h2 className="text-base font-medium text-[var(--r-text)]">Reading reminders</h2>
|
||||
<p className="text-sm text-[var(--r-sub)]">
|
||||
Get a daily nudge via{' '}
|
||||
<a href="https://ntfy.sh" target="_blank" rel="noreferrer" className="text-sky-600 underline">ntfy.sh</a>
|
||||
<a href="https://ntfy.sh" target="_blank" rel="noreferrer" className="text-[var(--r-accent)] underline">ntfy.sh</a>
|
||||
{' '}— a free push-notification service. Install the ntfy app (or open{' '}
|
||||
<span className="font-mono text-xs">{(reminderForm.ntfyUrl || 'https://ntfy.sh').replace(/\/+$/, '')}/{reminderForm.ntfyTopic || '<topic>'}</span>
|
||||
{' '}in a browser) and subscribe to the topic below to receive it.
|
||||
</p>
|
||||
</div>
|
||||
<form onSubmit={submitReminderSettings} className="space-y-3">
|
||||
<label className="flex items-center gap-2 text-sm font-medium text-slate-700">
|
||||
<label className="flex items-center gap-2 text-sm font-medium text-[var(--r-text)]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={reminderForm.reminderEnabled}
|
||||
onChange={(e) => setReminderForm((f) => ({ ...f, reminderEnabled: e.target.checked }))}
|
||||
className="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
|
||||
className="h-4 w-4 accent-[var(--r-accent)]"
|
||||
/>
|
||||
Enable daily reading reminder
|
||||
</label>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="block max-w-xs text-sm font-medium text-slate-700">
|
||||
<label className="block max-w-xs text-sm font-medium text-[var(--r-text)]">
|
||||
Topic
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. nate-bible-reading-8f2"
|
||||
value={reminderForm.ntfyTopic}
|
||||
onChange={(e) => setReminderForm((f) => ({ ...f, ntfyTopic: e.target.value }))}
|
||||
className="mt-1 block w-full rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
<span className="mt-1 block text-xs text-slate-400">Pick something unique — anyone who knows it can subscribe.</span>
|
||||
<span className="mt-1 block text-xs text-[var(--r-sub)]">Pick something unique — anyone who knows it can subscribe.</span>
|
||||
</label>
|
||||
<label className="block max-w-xs text-sm font-medium text-slate-700">
|
||||
<label className="block max-w-xs text-sm font-medium text-[var(--r-text)]">
|
||||
Reminder time
|
||||
<input
|
||||
type="time"
|
||||
value={reminderForm.reminderTime}
|
||||
onChange={(e) => setReminderForm((f) => ({ ...f, reminderTime: e.target.value }))}
|
||||
className="mt-1 block w-full rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<label className="block max-w-xs text-sm font-medium text-slate-700">
|
||||
ntfy server <span className="text-slate-400">(optional — defaults to ntfy.sh)</span>
|
||||
<label className="block max-w-xs text-sm font-medium text-[var(--r-text)]">
|
||||
ntfy server <span className="text-[var(--r-sub)]">(optional — defaults to ntfy.sh)</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://ntfy.sh"
|
||||
value={reminderForm.ntfyUrl}
|
||||
onChange={(e) => setReminderForm((f) => ({ ...f, ntfyUrl: e.target.value }))}
|
||||
className="mt-1 block w-full rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
</label>
|
||||
{reminderError && <p className="text-sm text-rose-600">{reminderError}</p>}
|
||||
{reminderSaved && <p className="text-sm text-emerald-600">Saved.</p>}
|
||||
{reminderError && <p className="text-sm text-[var(--r-danger,#b42318)]">{reminderError}</p>}
|
||||
{reminderSaved && <p className="text-sm text-[var(--r-accent)]">Saved.</p>}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={reminderBusy}
|
||||
className="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-slate-300"
|
||||
className="rounded-full bg-[var(--r-accent)] px-5 py-2 text-sm font-medium text-[var(--r-on)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{reminderBusy ? 'Saving…' : 'Save'}
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="rounded-3xl border border-slate-200 bg-white p-8 shadow-panel space-y-4">
|
||||
<section className="rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900">Two-factor authentication</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
<h2 className="text-base font-medium text-[var(--r-text)]">Two-factor authentication</h2>
|
||||
<p className="text-sm text-[var(--r-sub)]">
|
||||
{authUser.totpEnabled
|
||||
? "Enabled — you'll need a code from your authenticator app to sign in."
|
||||
: 'Add a 6-digit code from an authenticator app (Google Authenticator, Authy, 1Password, etc.) as a second step at sign-in.'}
|
||||
@@ -375,39 +355,39 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
|
||||
{mfaBackupCodes ? (
|
||||
<div className="rounded-2xl border border-amber-200 bg-amber-50 p-4 space-y-3">
|
||||
<p className="text-sm font-semibold text-amber-800">
|
||||
<div className="rounded-2xl border border-[var(--r-accent)] bg-[var(--r-bg)] p-4 space-y-3">
|
||||
<p className="text-sm font-medium text-[var(--r-accent)]">
|
||||
Save these backup codes now — each works once if you ever lose your device. They won't be shown again.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-2 font-mono text-sm text-slate-800">
|
||||
<div className="grid grid-cols-2 gap-2 font-mono text-sm text-[var(--r-text)]">
|
||||
{mfaBackupCodes.map((code) => (
|
||||
<div key={code} className="rounded-lg border border-amber-200 bg-white px-3 py-1.5">{code}</div>
|
||||
<div key={code} className="rounded-lg border border-[var(--r-line)] bg-[var(--r-bar)] px-3 py-1.5">{code}</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMfaBackupCodes(null)}
|
||||
className="rounded-md bg-amber-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-amber-400"
|
||||
className="rounded-full bg-[var(--r-accent)] px-5 py-2 text-sm font-medium text-[var(--r-on)] transition hover:opacity-90"
|
||||
>
|
||||
I've saved these codes
|
||||
</button>
|
||||
</div>
|
||||
) : authUser.totpEnabled ? (
|
||||
<form onSubmit={submitDisable} className="space-y-3">
|
||||
<label className="block max-w-xs text-sm font-medium text-slate-700">
|
||||
<label className="block max-w-xs text-sm font-medium text-[var(--r-text)]">
|
||||
Enter your password to disable 2FA
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
value={mfaDisablePassword}
|
||||
onChange={(e) => setMfaDisablePassword(e.target.value)}
|
||||
className="mt-1 block w-full rounded-xl border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
</label>
|
||||
{mfaDisableError && <p className="text-sm text-rose-600">{mfaDisableError}</p>}
|
||||
{mfaDisableError && <p className="text-sm text-[var(--r-danger,#b42318)]">{mfaDisableError}</p>}
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-md bg-rose-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-rose-400"
|
||||
className="rounded-full bg-red-700 px-5 py-2 text-sm font-medium text-white transition hover:opacity-90"
|
||||
>
|
||||
Disable 2FA
|
||||
</button>
|
||||
@@ -415,13 +395,13 @@ export default function SettingsPage() {
|
||||
) : mfaSetup ? (
|
||||
<form onSubmit={confirmSetup} className="space-y-4">
|
||||
<div className="flex flex-col items-center gap-3 sm:flex-row sm:items-start">
|
||||
<img src={mfaSetup.qrCodeDataUrl} alt="2FA QR code" className="h-40 w-40 rounded-xl border border-slate-200" />
|
||||
<div className="space-y-1 text-sm text-slate-600">
|
||||
<img src={mfaSetup.qrCodeDataUrl} alt="2FA QR code" className="h-40 w-40 rounded-xl border border-[var(--r-line)]" />
|
||||
<div className="space-y-1 text-sm text-[var(--r-sub)]">
|
||||
<p>Scan this with your authenticator app, or enter the code manually:</p>
|
||||
<p className="break-all rounded-lg bg-slate-100 px-2 py-1 font-mono text-xs">{mfaSetup.secret}</p>
|
||||
<p className="break-all rounded-lg bg-[var(--r-bg)] px-2 py-1 font-mono text-xs">{mfaSetup.secret}</p>
|
||||
</div>
|
||||
</div>
|
||||
<label className="block max-w-xs text-sm font-medium text-slate-700">
|
||||
<label className="block max-w-xs text-sm font-medium text-[var(--r-text)]">
|
||||
Enter the 6-digit code it shows
|
||||
<input
|
||||
type="text"
|
||||
@@ -430,22 +410,22 @@ export default function SettingsPage() {
|
||||
placeholder="123456"
|
||||
value={mfaSetupCode}
|
||||
onChange={(e) => setMfaSetupCode(e.target.value)}
|
||||
className="mt-1 block w-full rounded-xl border border-slate-300 px-3 py-2 text-center text-lg tracking-widest shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200"
|
||||
className="mt-1 block w-full rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-[var(--r-text)] text-center text-lg tracking-widest focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]"
|
||||
/>
|
||||
</label>
|
||||
{mfaSetupError && <p className="text-sm text-rose-600">{mfaSetupError}</p>}
|
||||
{mfaSetupError && <p className="text-sm text-[var(--r-danger,#b42318)]">{mfaSetupError}</p>}
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={mfaSetupBusy}
|
||||
className="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-slate-300"
|
||||
className="rounded-full bg-[var(--r-accent)] px-5 py-2 text-sm font-medium text-[var(--r-on)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{mfaSetupBusy ? 'Verifying…' : 'Confirm & enable'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cancelSetup}
|
||||
className="rounded-md border border-slate-300 px-4 py-2 text-sm font-medium text-slate-600 transition hover:bg-slate-50"
|
||||
className="rounded-full border border-[var(--r-line)] px-5 py-2 text-sm font-medium transition hover:bg-black/5"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -457,15 +437,15 @@ export default function SettingsPage() {
|
||||
type="button"
|
||||
onClick={startSetup}
|
||||
disabled={mfaSetupBusy}
|
||||
className="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-sky-400 disabled:cursor-not-allowed disabled:bg-slate-300"
|
||||
className="rounded-full bg-[var(--r-accent)] px-5 py-2 text-sm font-medium text-[var(--r-on)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{mfaSetupBusy ? 'Starting…' : 'Enable 2FA'}
|
||||
</button>
|
||||
{mfaSetupError && <p className="text-sm text-rose-600">{mfaSetupError}</p>}
|
||||
{mfaSetupError && <p className="text-sm text-[var(--r-danger,#b42318)]">{mfaSetupError}</p>}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user