feat(mobile): redigera kost/allergier/religion i profilen när som helst (MIKRO 63)
CI / Typecheck, test & build (push) Failing after 43s

This commit is contained in:
Sven (AAMOS AI)
2026-08-14 21:17:55 +07:00
parent f102288676
commit cd6a9eb556
+66
View File
@@ -37,6 +37,11 @@ interface Consent {
kind: string;
status: string;
}
interface Prefs {
dietPattern?: string | null;
allergens?: string[] | null;
religiousRule?: string | null;
}
/** Samtyckesetiketter ur i18n-katalogen: profile.consent.<kind> (12 språk). */
const CONSENT_KINDS = [
@@ -48,6 +53,9 @@ const CONSENT_KINDS = [
"push_notifications",
"product_analytics",
] as const;
const DIETS = ["omnivore", "flexitarian", "pescatarian", "vegetarian", "vegan"] as const;
const ALLERGENS = ["gluten", "milk", "eggs", "tree_nuts", "peanuts", "fish", "crustaceans", "soy", "sesame"] as const;
const RELIGIOUS = ["none", "halal", "kosher", "hindu_no_beef", "buddhist_vegetarian"] as const;
export default function ProfileScreen() {
const queryClient = useQueryClient();
@@ -62,6 +70,17 @@ export default function ProfileScreen() {
queryKey: ["consents"],
queryFn: () => api<Consent[]>("/v1/me/consents"),
});
const prefs = useQuery({
queryKey: ["preferences"],
queryFn: () => api<Prefs | null>("/v1/me/preferences"),
});
const savePrefs = useMutation({
mutationFn: (body: Record<string, unknown>) =>
api("/v1/me/preferences", { method: "PATCH", body }),
onSuccess: () => void queryClient.invalidateQueries({ queryKey: ["preferences"] }),
onError: (err) =>
Alert.alert(t("common.oops"), err instanceof Error ? err.message : t("common.error")),
});
const setConsent = useMutation({
mutationFn: ({ kind, granted }: { kind: string; granted: boolean }) =>
@@ -111,6 +130,9 @@ export default function ProfileScreen() {
if (me.isError) return <ErrorView onRetry={() => void me.refetch()} />;
const consentMap = new Map((consents.data ?? []).map((c) => [c.kind, c.status]));
const curDiet = prefs.data?.dietPattern ?? "omnivore";
const curAllergens = prefs.data?.allergens ?? [];
const curReligious = prefs.data?.religiousRule ?? "none";
return (
<Screen>
@@ -162,6 +184,50 @@ export default function ProfileScreen() {
</Row>
</Card>
<Card>
<Heading>{t("onboarding.diet")}</Heading>
<Row style={{ flexWrap: "wrap" }}>
{DIETS.map((id) => (
<Button
key={id}
label={t(`onboarding.diet.${id}` as never)}
variant={curDiet === id ? "secondary" : "ghost"}
onPress={() => savePrefs.mutate({ dietPattern: id })}
/>
))}
</Row>
<Spacer size={8} />
<Heading>{t("onboarding.allergies")}</Heading>
<Row style={{ flexWrap: "wrap" }}>
{ALLERGENS.map((id) => (
<Button
key={id}
label={t(`onboarding.allergen.${id}` as never)}
variant={curAllergens.includes(id) ? "secondary" : "ghost"}
onPress={() =>
savePrefs.mutate({
allergens: curAllergens.includes(id)
? curAllergens.filter((a) => a !== id)
: [...curAllergens, id],
})
}
/>
))}
</Row>
<Spacer size={8} />
<Heading>{t("onboarding.religious")}</Heading>
<Row style={{ flexWrap: "wrap" }}>
{RELIGIOUS.map((id) => (
<Button
key={id}
label={t(`onboarding.religious.${id}` as never)}
variant={curReligious === id ? "secondary" : "ghost"}
onPress={() => savePrefs.mutate({ religiousRule: id })}
/>
))}
</Row>
</Card>
{entitlements.data && (
<Card onPress={() => router.push("/paywall")}>
<Row style={{ justifyContent: "space-between" }}>