refactor(ts): SettingsPage → TypeScript (auto-save settings + tracker layout toggles)
TS flagged a dead prop: onSaved was passed to NotificationPreferences (which only takes settings) — removed it. Notification components imported from ProfilePage (still .jsx) type as any until that page converts.
This commit is contained in:
parent
b912fd74be
commit
18d0c0bdb9
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, type ComponentType, type ReactNode } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'sonner';
|
||||
import { AlertCircle, Moon, RefreshCw, Sun, Users } from 'lucide-react';
|
||||
import { api } from '@/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, errMessage } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { CalendarFeedManager } from '@/components/CalendarFeedManager';
|
||||
|
|
@ -18,7 +18,7 @@ import { SaveStatus } from '@/components/ui/save-status';
|
|||
import { NotificationPreferences, PushNotifications, asSettings } from '@/pages/ProfilePage';
|
||||
|
||||
export const LINK_IMPORT_PREF_KEY = 'link_import_ask';
|
||||
export function getLinkImportPref() {
|
||||
export function getLinkImportPref(): boolean {
|
||||
return localStorage.getItem(LINK_IMPORT_PREF_KEY) !== 'false';
|
||||
}
|
||||
|
||||
|
|
@ -26,12 +26,12 @@ export function getLinkImportPref() {
|
|||
// preferences; Profile keeps identity, security, and privacy) ──────────────
|
||||
|
||||
function NotificationsSection() {
|
||||
const [settings, setSettings] = useState(null);
|
||||
const [settings, setSettings] = useState<Record<string, unknown> | null>(null);
|
||||
|
||||
const load = useCallback(() => {
|
||||
api.profileSettings()
|
||||
.then((data) => setSettings(asSettings(data)))
|
||||
.catch((err) => toast.error(err.message || 'Failed to load notification settings.'));
|
||||
.catch((err) => toast.error(errMessage(err, 'Failed to load notification settings.')));
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
|
@ -39,7 +39,7 @@ function NotificationsSection() {
|
|||
if (!settings) return null;
|
||||
return (
|
||||
<div className="space-y-4 mb-4">
|
||||
<NotificationPreferences settings={settings} onSaved={setSettings} />
|
||||
<NotificationPreferences settings={settings} />
|
||||
<PushNotifications settings={settings} onSaved={load} />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -47,7 +47,7 @@ function NotificationsSection() {
|
|||
|
||||
// ─── Card wrapper ─────────────────────────────────────────────────────────────
|
||||
|
||||
function SectionCard({ title, children }) {
|
||||
function SectionCard({ title, children }: { title: ReactNode; children: ReactNode }) {
|
||||
return (
|
||||
<div className="table-surface mb-4">
|
||||
<div className="px-6 py-4 border-b border-border/50">
|
||||
|
|
@ -62,7 +62,7 @@ function SectionCard({ title, children }) {
|
|||
|
||||
// ─── Setting Row ──────────────────────────────────────────────────────────────
|
||||
|
||||
function SettingRow({ label, description, children }) {
|
||||
function SettingRow({ label, description, children }: { label: ReactNode; description?: ReactNode; children: ReactNode }) {
|
||||
return (
|
||||
<div className="px-4 py-4 flex flex-col gap-3 sm:px-6 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex-1 min-w-0 sm:mr-8">
|
||||
|
|
@ -80,7 +80,13 @@ function SettingRow({ label, description, children }) {
|
|||
|
||||
// ─── Theme Card ───────────────────────────────────────────────────────────────
|
||||
|
||||
function ThemeCard({ value, label, icon: Icon, currentTheme, onSelect }) {
|
||||
function ThemeCard({ value, label, icon: Icon, currentTheme, onSelect }: {
|
||||
value: string;
|
||||
label: string;
|
||||
icon: ComponentType<{ className?: string }>;
|
||||
currentTheme: string;
|
||||
onSelect: (v: string) => void;
|
||||
}) {
|
||||
const selected = currentTheme === value;
|
||||
return (
|
||||
<button
|
||||
|
|
@ -132,7 +138,7 @@ function LoginModeRecoverySection() {
|
|||
refresh();
|
||||
navigate('/login', { replace: true });
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'Failed to restore multi-user mode.');
|
||||
toast.error(errMessage(err, 'Failed to restore multi-user mode.'));
|
||||
} finally {
|
||||
setRestoring(false);
|
||||
}
|
||||
|
|
@ -246,7 +252,7 @@ function SettingsSkeleton() {
|
|||
function LinkImportToggle() {
|
||||
const [enabled, setEnabled] = useState(getLinkImportPref);
|
||||
|
||||
function toggle(next) {
|
||||
function toggle(next: boolean) {
|
||||
localStorage.setItem(LINK_IMPORT_PREF_KEY, String(next));
|
||||
setEnabled(next);
|
||||
toast.success(next
|
||||
|
|
@ -264,15 +270,30 @@ function LinkImportToggle() {
|
|||
);
|
||||
}
|
||||
|
||||
function settingsBool(value, fallback = true) {
|
||||
function settingsBool(value: unknown, fallback = true): boolean {
|
||||
if (value === undefined || value === null || value === '') return fallback;
|
||||
return value === true || value === 'true';
|
||||
}
|
||||
|
||||
interface SettingsState {
|
||||
currency: string;
|
||||
date_format: string;
|
||||
grace_period_days: number | string;
|
||||
drift_threshold_pct: string;
|
||||
tracker_show_bank_projection_banner: string;
|
||||
tracker_bank_projection_banner_snoozed_until: string;
|
||||
tracker_show_search_sort: string;
|
||||
tracker_show_summary_cards: string;
|
||||
tracker_show_safe_to_spend: string;
|
||||
tracker_show_overdue_command_center: string;
|
||||
tracker_show_drift_insights: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// ─── SettingsPage ─────────────────────────────────────────────────────────────
|
||||
|
||||
export default function SettingsPage() {
|
||||
const DEFAULTS = {
|
||||
const DEFAULTS: SettingsState = {
|
||||
currency: 'USD',
|
||||
date_format: 'MM/DD/YYYY',
|
||||
grace_period_days: 3,
|
||||
|
|
@ -286,22 +307,22 @@ export default function SettingsPage() {
|
|||
tracker_show_drift_insights: 'true',
|
||||
};
|
||||
|
||||
const [settings, setSettings] = useState(DEFAULTS);
|
||||
const [settings, setSettings] = useState<SettingsState>(DEFAULTS);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState(null);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
|
||||
const loadSettings = useCallback(() => {
|
||||
setLoading(true);
|
||||
setLoadError(null);
|
||||
api.settings()
|
||||
.then((d) => setSettings({ ...DEFAULTS, ...d }))
|
||||
.catch((err) => setLoadError(err.message || 'Failed to load settings'))
|
||||
.then((d) => setSettings({ ...DEFAULTS, ...(d as Partial<SettingsState>) }))
|
||||
.catch((err) => setLoadError(errMessage(err, 'Failed to load settings')))
|
||||
.finally(() => setLoading(false));
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => { loadSettings(); }, [loadSettings]);
|
||||
|
||||
const buildPayload = (s) => ({
|
||||
const buildPayload = (s: SettingsState) => ({
|
||||
currency: s.currency,
|
||||
date_format: s.date_format,
|
||||
grace_period_days: s.grace_period_days,
|
||||
|
|
@ -320,17 +341,17 @@ export default function SettingsPage() {
|
|||
// don't save half-typed numbers.
|
||||
const { status: saveStatus, schedule } = useAutoSave(
|
||||
(payload) => api.saveSettings(payload).catch((err) => {
|
||||
toast.error(err.message || 'Failed to save settings.');
|
||||
toast.error(errMessage(err, 'Failed to save settings.'));
|
||||
throw err;
|
||||
}),
|
||||
);
|
||||
|
||||
const set = (k, v, delay) => setSettings((p) => {
|
||||
const set = (k: string, v: unknown, delay?: number) => setSettings((p) => {
|
||||
const next = { ...p, [k]: v };
|
||||
schedule(buildPayload(next), delay);
|
||||
return next;
|
||||
});
|
||||
const setTyped = (k, v) => set(k, v, 900); // for keystroke-driven inputs
|
||||
const setTyped = (k: string, v: unknown) => set(k, v, 900); // for keystroke-driven inputs
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
Loading…
Reference in New Issue