Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,110 @@ function SaveButton({ label = "Save changes" }: { label?: string }) {
);
}

type InsuranceReminder = {
policyId: string;
name: string;
nextPaymentDate: string;
monthlyPremium: number;
};

function InsuranceReminderPreview() {
const [status, setStatus] = useState<
"loading" | "loaded" | "empty" | "unauthorized" | "error"
>("loading");
const [reminders, setReminders] = useState<InsuranceReminder[]>([]);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
let active = true;

async function loadReminders() {
try {
const response = await fetch("/api/insurance/reminders");
if (!active) return;

if (response.status === 401 || response.status === 403) {
setStatus("unauthorized");
setError("Connect your wallet to view upcoming insurance reminders.");
return;
}

if (!response.ok) {
const body = await response.text();
throw new Error(body || response.statusText);
}

const data = (await response.json()) as InsuranceReminder[];
if (!active) return;

setReminders(data);
setStatus(data.length > 0 ? "loaded" : "empty");
} catch (err) {
if (!active) return;
setStatus("error");
setError(err instanceof Error ? err.message : String(err));
}
}

loadReminders();
return () => {
active = false;
};
}, []);

return (
<div className="mx-6 mt-4 rounded-2xl border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-950 p-4">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="text-sm font-semibold text-gray-900 dark:text-white">
Insurance reminders
</p>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Show upcoming or overdue premium payments for your active policies.
</p>
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{status === "loading" && "Loading…"}
{status === "empty" && "No reminders in the next 7 days."}
{status === "loaded" && `${reminders.length} reminder${reminders.length === 1 ? "" : "s"}`}
{status === "unauthorized" && "Sign in to view reminders."}
{status === "error" && "Unable to load reminders."}
</div>
</div>

<div className="mt-4 space-y-3">
{status === "loaded" &&
reminders.map((reminder) => (
<div
key={reminder.policyId}
className="rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 px-4 py-3"
>
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm font-medium text-gray-900 dark:text-white">
{reminder.name}
</p>
<span className="text-xs text-gray-500 dark:text-gray-400">
Due {new Date(reminder.nextPaymentDate).toLocaleDateString()}
</span>
</div>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Premium: ${reminder.monthlyPremium.toFixed(2)}
</p>
</div>
))}
{status === "unauthorized" && (
<p className="text-sm text-gray-500 dark:text-gray-400">{error}</p>
)}
{status === "error" && (
<p className="text-sm text-red-600 dark:text-red-400">
{error ?? "Could not load insurance reminders."}
</p>
)}
</div>
</div>
);
}

// ─── Sections ────────────────────────────────────────────────────────────────

function ProfileSection() {
Expand Down Expand Up @@ -302,6 +406,12 @@ function NotificationsSection() {
description="When you hit 25 %, 50 %, 75 %, or 100 % of a goal"
defaultChecked
/>
<Toggle
label="Insurance premium reminders"
description="Receive alerts when insurance premiums are due or overdue."
defaultChecked
/>
<InsuranceReminderPreview />
<p className="px-6 pt-5 pb-2 text-xs font-medium uppercase tracking-widest text-gray-400 dark:text-gray-500">
Channels
</p>
Expand Down
Loading