fix: mobile layout fixes, i18n coverage, and recipe share link
Mobile:
- Recipes search bar full-width on mobile instead of capped narrow
- Cook mode ingredients panel stacks above the step instead of
squeezing it into a narrow column
- Version history Compare/Restore buttons wrap onto their own row
- Recipe edit ingredient fields wrap instead of forcing horizontal
scroll on narrow viewports
i18n: translates remaining hardcoded strings across recipes
filter/sort, adapt-recipe and AI variations dialogs, the full
settings section (sidebar + 6 sub-pages + BYOK/model-prefs/
API-keys/webhooks managers), explore tab, collections (new/fork/
share dialogs), meal planning (planner, AI generation phases, new
shopping list, shared-plan view), photo import, recipe bulk-select
toolbar, and recipe action-button tooltips. Also fixes the recipes
page subtitle, which wasn't just unworded but missing its {count}
interpolation entirely — it always rendered as the bare word
"results" regardless of how many recipes existed.
Feature: adds a ShareRecipeButton that copies the public /r/{id}
link to the clipboard, with a notice when the recipe isn't Public
yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,7 @@ export function VariationsDialog({
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) {
|
||||
const t = useTranslations("recipe");
|
||||
const tv = useTranslations("ai.variations");
|
||||
const router = useRouter();
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [applying, setApplying] = useState<number | null>(null);
|
||||
@@ -80,7 +81,7 @@ export function VariationsDialog({
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json() as { error?: string };
|
||||
toast.error(err.error ?? "Failed to generate variations");
|
||||
toast.error(err.error ?? tv("error"));
|
||||
return;
|
||||
}
|
||||
const data = await res.json() as { variations: Variation[] };
|
||||
@@ -140,12 +141,12 @@ export function VariationsDialog({
|
||||
});
|
||||
|
||||
if (!saveRes.ok) {
|
||||
toast.error("Failed to save variation");
|
||||
toast.error(tv("saveError"));
|
||||
return;
|
||||
}
|
||||
|
||||
const saved = await saveRes.json() as { id: string };
|
||||
toast.success("Variation created — review and edit before publishing");
|
||||
toast.success(tv("success"));
|
||||
onOpenChange(false);
|
||||
router.push(`/recipes/${saved.id}/edit`);
|
||||
} finally {
|
||||
@@ -159,17 +160,17 @@ export function VariationsDialog({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-primary" />
|
||||
AI Recipe Variations
|
||||
{tv("title")}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Generate creative variations of this recipe — dietary adaptations, flavor twists, technique changes.
|
||||
{tv("description")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{variations.length === 0 ? (
|
||||
<div className="flex flex-col gap-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="directions">Directions <span className="text-muted-foreground font-normal">(optional)</span></Label>
|
||||
<Label htmlFor="directions">{tv("directions")} <span className="text-muted-foreground font-normal">({tv("optional")})</span></Label>
|
||||
<Textarea
|
||||
id="directions"
|
||||
placeholder={t("variationsDirectionsPlaceholder")}
|
||||
@@ -184,16 +185,16 @@ export function VariationsDialog({
|
||||
{generating ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Generating variations…
|
||||
{tv("generating")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
Generate 3 variations
|
||||
{tv("generate")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<FakeProgressBar active={generating} durationMs={22000} label={generating ? "Generating 3 creative variations…" : undefined} />
|
||||
<FakeProgressBar active={generating} durationMs={22000} label={generating ? tv("generating") : undefined} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
@@ -214,13 +215,13 @@ export function VariationsDialog({
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
)}
|
||||
Apply
|
||||
{tv("apply")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{v.changedIngredients.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Ingredient changes</p>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">{tv("ingredientChanges")}</p>
|
||||
<div className="space-y-1">
|
||||
{v.changedIngredients.map((c, j) => (
|
||||
<div key={j} className="flex items-center gap-2 text-sm">
|
||||
@@ -237,11 +238,11 @@ export function VariationsDialog({
|
||||
|
||||
{v.changedSteps && v.changedSteps.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Step changes</p>
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">{tv("stepChanges")}</p>
|
||||
<div className="space-y-1">
|
||||
{v.changedSteps.map((c, j) => (
|
||||
<div key={j} className="flex gap-2 text-sm">
|
||||
<Badge variant="outline" className="text-xs shrink-0">Step {c.stepNumber}</Badge>
|
||||
<Badge variant="outline" className="text-xs shrink-0">{tv("stepLabel", { n: c.stepNumber })}</Badge>
|
||||
<span className="text-muted-foreground">{c.change}</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -265,7 +266,7 @@ export function VariationsDialog({
|
||||
|
||||
<Button variant="outline" className="w-full" onClick={generate} disabled={generating}>
|
||||
{generating ? <Loader2 className="h-4 w-4 animate-spin" /> : <Sparkles className="h-4 w-4" />}
|
||||
Regenerate
|
||||
{tv("regenerate")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user