0e057bc25d
Ny knapp 'Skicka in eget recept' i Hem, direkt under 'Dina sparade recept' (samma kort-stil). Ny skarm submit-recipe: enkel mall (titel + ingredienser med mangd/gram/kilo + tillagning). Skickas som fritext -> AAMOS strukturerar -> workern AI-granskar, dubblettkollar, finskriver och auto-publicerar, markt med anvandarens namn. Hanterar 402/PREMIUM_REQUIRED snyggt (egna recept ingar i Premium/trial; gratisnivan ser uppgraderingsvy i stallet for ratt fel). Backend fanns redan (POST /v1/recipes + STRUCTURE_RECIPE_TEXT).
144 lines
4.1 KiB
TypeScript
144 lines
4.1 KiB
TypeScript
import { useState } from "react";
|
||
import { router } from "expo-router";
|
||
import { api, ApiError } from "@/lib/api";
|
||
import { t } from "@/lib/i18n";
|
||
import { Body, Button, Card, Heading, Input, Screen, Small, Spacer, Title } from "@/components/ui";
|
||
import { spacing } from "@/lib/theme";
|
||
|
||
/**
|
||
* Skicka in eget recept (spec §35). Enkel mall: titel + ingredienser
|
||
* (mängd + gram/kilo) + tillagning. Skickas som fritext → AAMOS
|
||
* (STRUCTURE_RECIPE_TEXT) tolkar och strukturerar, workern AI-granskar,
|
||
* dubblettkollar, finskriver och auto-publicerar – märkt med ditt namn.
|
||
*/
|
||
|
||
type Phase = "form" | "done" | "premium";
|
||
|
||
export default function SubmitRecipeScreen() {
|
||
const [title, setTitle] = useState("");
|
||
const [ingredients, setIngredients] = useState("");
|
||
const [steps, setSteps] = useState("");
|
||
const [busy, setBusy] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [phase, setPhase] = useState<Phase>("form");
|
||
|
||
const canSubmit =
|
||
title.trim().length >= 3 && ingredients.trim().length >= 3 && steps.trim().length >= 3;
|
||
|
||
const submit = async () => {
|
||
if (!canSubmit || busy) return;
|
||
setBusy(true);
|
||
setError(null);
|
||
// Bygg en tydlig fritext-mall som AI:n tolkar robust.
|
||
const text = [
|
||
`Titel: ${title.trim()}`,
|
||
"",
|
||
"Ingredienser:",
|
||
ingredients.trim(),
|
||
"",
|
||
"Gör så här:",
|
||
steps.trim(),
|
||
].join("\n");
|
||
try {
|
||
await api("/v1/recipes", { method: "POST", body: { mode: "free_text", text } });
|
||
setPhase("done");
|
||
} catch (err) {
|
||
if (err instanceof ApiError && err.status === 402) {
|
||
setPhase("premium");
|
||
} else {
|
||
setError(err instanceof Error ? err.message : t("common.error"));
|
||
}
|
||
} finally {
|
||
setBusy(false);
|
||
}
|
||
};
|
||
|
||
if (phase === "done") {
|
||
return (
|
||
<Screen>
|
||
<Card>
|
||
<Heading>🎉 {t("submit.successTitle")}</Heading>
|
||
<Body>{t("submit.successBody")}</Body>
|
||
<Spacer size={spacing.md} />
|
||
<Button label={t("submit.backHome")} onPress={() => router.back()} />
|
||
</Card>
|
||
</Screen>
|
||
);
|
||
}
|
||
|
||
if (phase === "premium") {
|
||
return (
|
||
<Screen>
|
||
<Card>
|
||
<Heading>⭐ {t("submit.premiumTitle")}</Heading>
|
||
<Body>{t("submit.premiumBody")}</Body>
|
||
<Spacer size={spacing.md} />
|
||
<Button
|
||
label={t("submit.seePlans")}
|
||
onPress={() => router.replace("/profile")}
|
||
/>
|
||
<Spacer size={spacing.xs} />
|
||
<Button label={t("submit.backHome")} variant="ghost" onPress={() => router.back()} />
|
||
</Card>
|
||
</Screen>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Screen>
|
||
<Title>{t("submit.title")}</Title>
|
||
<Small>{t("submit.subtitle")}</Small>
|
||
<Spacer size={spacing.md} />
|
||
|
||
<Small>{t("submit.titleLabel")}</Small>
|
||
<Input
|
||
placeholder={t("submit.titlePlaceholder")}
|
||
value={title}
|
||
onChangeText={setTitle}
|
||
maxLength={150}
|
||
/>
|
||
<Spacer size={spacing.sm} />
|
||
|
||
<Small>{t("submit.ingredientsLabel")}</Small>
|
||
<Input
|
||
placeholder={t("submit.ingredientsPlaceholder")}
|
||
value={ingredients}
|
||
onChangeText={setIngredients}
|
||
multiline
|
||
numberOfLines={6}
|
||
style={{ minHeight: 120, textAlignVertical: "top" }}
|
||
maxLength={3000}
|
||
/>
|
||
<Spacer size={spacing.sm} />
|
||
|
||
<Small>{t("submit.stepsLabel")}</Small>
|
||
<Input
|
||
placeholder={t("submit.stepsPlaceholder")}
|
||
value={steps}
|
||
onChangeText={setSteps}
|
||
multiline
|
||
numberOfLines={8}
|
||
style={{ minHeight: 150, textAlignVertical: "top" }}
|
||
maxLength={5000}
|
||
/>
|
||
<Spacer size={spacing.sm} />
|
||
|
||
{error && (
|
||
<>
|
||
<Small>{`⚠️ ${error}`}</Small>
|
||
<Spacer size={spacing.xs} />
|
||
</>
|
||
)}
|
||
|
||
<Button
|
||
label={busy ? t("submit.sending") : t("submit.button")}
|
||
onPress={() => void submit()}
|
||
loading={busy}
|
||
disabled={!canSubmit}
|
||
/>
|
||
<Spacer size={spacing.sm} />
|
||
<Small>{t("submit.reviewNote")}</Small>
|
||
</Screen>
|
||
);
|
||
}
|