Files
minabot/src/components/AccountSettings.tsx
syntaxbullet 833d5673eb Refactor HabitColorPicker to use HABIT_COLOR_CHOICES, update styles and tests
- Replaced HABIT_PALETTES with HABIT_COLOR_CHOICES in HabitColorPicker component and related tests.
- Adjusted the layout and styles of the color picker for improved usability.
- Updated tests to reflect changes in color selection logic.
- Modified WorkspacePanel and SettingRow components to allow optional eyebrow and description props.
- Enhanced accessibility tests for calendar navigation.
- Updated calendar model to include a 1-month view option.
- Refined styles across various components for consistency and improved responsiveness.
2026-09-08 15:42:47 +02:00

54 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ThemeControl } from "./ThemeProvider";
import { ReminderSettings } from "./ReminderSettings";
import { useId, useRef, useState } from "react";
import type { PublicUser } from "../shared/user";
import { habitRequest } from "../lib/dashboard";
import { Button, ButtonLink } from "./design-system/primitives";
import { Download, Trash2 } from "lucide-react";
import { FormMessage, SettingRow, WorkspacePanel } from "./design-system/WorkspacePanel";
import { Field } from "./design-system/Field";
export function AccountSettings({ user, onChanged, onClose }: { user: PublicUser; onChanged: () => void; onClose: () => void }) {
const [timezone, setTimezone] = useState(user.timezone);
const [confirmation, setConfirmation] = useState("");
const [deleting, setDeleting] = useState(false);
const [busy, setBusy] = useState(false);
const [reminderBusy, setReminderBusy] = useState(false);
const saving = useRef(false);
const [error, setError] = useState("");
const listId = useId();
async function run(action: () => Promise<unknown>) {
if (saving.current || reminderBusy) return;
saving.current = true; setBusy(true); setError("");
try { await action(); onChanged(); }
catch (error) { setError(error instanceof Error ? error.message : "Could not save. Try again."); }
finally { saving.current = false; setBusy(false); }
}
return <WorkspacePanel id="account-settings" title="Account settings" onClose={onClose} busy={busy || reminderBusy} closeLabel="Close settings">
<SettingRow title="Appearance"><ThemeControl /></SettingRow>
<SettingRow title="Daily timezone" description={<p>Sets day boundaries. Earlier deadlines stay unchanged.</p>}>
<form className="ds-form-stack" onSubmit={event => { event.preventDefault(); void run(() => habitRequest("/me", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ timezone }) })); }}>
<Field label="Timezone">
{id => <input id={id} list={listId} value={timezone} required maxLength={100} disabled={busy || reminderBusy} onChange={event => setTimezone(event.target.value)} />}
</Field>
<datalist id={listId}>{["UTC", ...Intl.supportedValuesOf("timeZone")].map(zone => <option key={zone} value={zone} />)}</datalist>
<div className="ds-action-row"><Button type="submit" variant="secondary" disabled={busy || reminderBusy}>{busy && !deleting ? "Saving…" : "Save timezone"}</Button></div>
</form>
</SettingRow>
<ReminderSettings timezone={user.timezone} onBusyChange={setReminderBusy} disabled={busy} />
<SettingRow title="Export data" description={<p>Download your profile, habits, and history.</p>}>
<div className="ds-action-row"><ButtonLink variant="secondary" href="/api/account/export" download="mina-habits-export.json"><Download size={16} aria-hidden="true" />Export my data</ButtonLink><span className="ds-meta-label">JSON file</span></div>
</SettingRow>
<SettingRow title="Delete account" description={<p>Permanently remove your account and habit history, and sign out every device.</p>}>
{!deleting ? <div className="ds-action-row"><Button variant="text" disabled={busy || reminderBusy} onClick={() => setDeleting(true)} aria-expanded={false}><Trash2 size={16} aria-hidden="true" />Delete account</Button></div> :
<form className="ds-form-stack ds-confirmation" onSubmit={event => { event.preventDefault(); void run(() => habitRequest("/account", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ confirmation }) })); }}>
<p className="type-ui-heading">This cannot be undone.</p>
<p className="ds-supporting-copy">Download an export first if you want a copy. Images already posted to Discord remain there; server backups expire under the operator’s retention policy.</p>
<Field label="Type DELETE to confirm">{id => <input id={id} value={confirmation} disabled={busy || reminderBusy} autoComplete="off" onChange={event => setConfirmation(event.target.value)} />}</Field>
<div className="ds-action-row"><Button type="submit" disabled={busy || reminderBusy || confirmation !== "DELETE"}>Permanently delete my account</Button><Button variant="text" disabled={busy || reminderBusy} onClick={() => { setDeleting(false); setConfirmation(""); }}>Cancel deletion</Button></div>
</form>}
</SettingRow>
<FormMessage error>{error}</FormMessage>
</WorkspacePanel>;
}