diff --git a/messages/de.json b/messages/de.json
index c471b7159..4791cd810 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -819,7 +819,12 @@
"targetsDescription": "Setze optionale Zielwerte für Blutdruck und Gewicht.",
"targetSys": "Systolisch (mmHg)",
"targetDia": "Diastolisch (mmHg)",
- "targetWeight": "Zielgewicht (kg)"
+ "targetWeight": "Zielgewicht (kg)",
+ "errorProfile": "Dein Profil konnte nicht gespeichert werden. Bitte erneut versuchen.",
+ "errorMedication": "Das Medikament „{name}\" konnte nicht angelegt werden. Bitte erneut versuchen oder später unter „Medikamente\" hinzufügen.",
+ "errorTargets": "Deine Zielwerte konnten nicht gespeichert werden. Bitte erneut versuchen.",
+ "errorGeneric": "Beim Abschluss des Setups ist etwas schiefgelaufen. Bitte erneut versuchen.",
+ "medScheduleHint": "Wir legen ein Standard-Erinnerungsfenster an (08:00–09:00 täglich). Du kannst es jederzeit unter „Medikamente\" anpassen."
},
"settings": {
"title": "Einstellungen",
diff --git a/messages/en.json b/messages/en.json
index b1d992b0d..8ff2f5b95 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -819,7 +819,12 @@
"targetsDescription": "Set optional target values for blood pressure and weight.",
"targetSys": "Systolic (mmHg)",
"targetDia": "Diastolic (mmHg)",
- "targetWeight": "Target weight (kg)"
+ "targetWeight": "Target weight (kg)",
+ "errorProfile": "Could not save your profile. Please try again.",
+ "errorMedication": "Could not add the medication \"{name}\". Please try again or add it later in Medications.",
+ "errorTargets": "Could not save your target values. Please try again.",
+ "errorGeneric": "Something went wrong while finishing setup. Please try again.",
+ "medScheduleHint": "We'll add a default reminder window (08:00–09:00 daily). You can adjust it anytime in Medications."
},
"settings": {
"title": "Settings",
diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx
index c7871d3a5..32ee666a3 100644
--- a/src/app/onboarding/page.tsx
+++ b/src/app/onboarding/page.tsx
@@ -3,6 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -39,45 +40,63 @@ export default function OnboardingPage() {
async function finishOnboarding() {
setSaving(true);
- const body: Record = {};
- if (heightCm) body.heightCm = parseFloat(heightCm);
- if (dateOfBirth) body.dateOfBirth = dateOfBirth;
- if (gender) body.gender = gender;
+ try {
+ const body: Record = {};
+ if (heightCm) body.heightCm = parseFloat(heightCm);
+ if (dateOfBirth) body.dateOfBirth = dateOfBirth;
+ if (gender) body.gender = gender;
- await fetch("/api/onboarding/complete", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body),
- });
-
- // Create medications if any
- for (const med of meds) {
- await fetch("/api/medications", {
+ const profileRes = await fetch("/api/onboarding/complete", {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- name: med.name,
- dose: med.dose,
- schedules: [],
- }),
+ body: JSON.stringify(body),
});
- }
+ if (!profileRes.ok) {
+ throw new Error(t("onboarding.errorProfile"));
+ }
- // Save targets if set
- if (targetSys || targetDia || targetWeight) {
- const targets: Record = {};
- if (targetSys) targets.bpSysTarget = parseInt(targetSys);
- if (targetDia) targets.bpDiaTarget = parseInt(targetDia);
- if (targetWeight) targets.weightTarget = parseFloat(targetWeight);
- await fetch("/api/insights/targets", {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(targets),
- });
- }
+ // Create medications. Server validation requires at least one schedule,
+ // so we attach a sensible default — the user is informed via medScheduleHint
+ // and can refine it in Medications later. Phase 3.5 of the audit replaces
+ // this entire wizard with an empty-state-driven flow.
+ for (const med of meds) {
+ const medRes = await fetch("/api/medications", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ name: med.name,
+ dose: med.dose,
+ schedules: [{ windowStart: "08:00", windowEnd: "09:00" }],
+ }),
+ });
+ if (!medRes.ok) {
+ throw new Error(t("onboarding.errorMedication", { name: med.name }));
+ }
+ }
- await queryClient.invalidateQueries({ queryKey: ["auth"] });
- router.replace("/");
+ if (targetSys || targetDia || targetWeight) {
+ const targets: Record = {};
+ if (targetSys) targets.bpSysTarget = parseInt(targetSys);
+ if (targetDia) targets.bpDiaTarget = parseInt(targetDia);
+ if (targetWeight) targets.weightTarget = parseFloat(targetWeight);
+ const targetsRes = await fetch("/api/insights/targets", {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(targets),
+ });
+ if (!targetsRes.ok) {
+ throw new Error(t("onboarding.errorTargets"));
+ }
+ }
+
+ await queryClient.invalidateQueries({ queryKey: ["auth"] });
+ router.replace("/");
+ } catch (err) {
+ const message =
+ err instanceof Error ? err.message : t("onboarding.errorGeneric");
+ toast.error(message);
+ setSaving(false);
+ }
}
function addMedication() {
@@ -191,27 +210,40 @@ export default function OnboardingPage() {
placeholder={t("onboarding.medDosePlaceholder")}
className="w-24"
/>
-
-
📱 Telegram — {t("onboarding.telegramHint")}
-
🔔 Web Push — {t("onboarding.webPushHint")}
+
+ 📱 Telegram — {t("onboarding.telegramHint")}
+
+
+ 🔔 Web Push — {t("onboarding.webPushHint")}
+
{t("onboarding.notificationsLater")}