024a455ab3
Dashboard & API bug fixes for owner-reported breakage: - MenuController validators (PosValidators): NameEn was required but the dashboard sends null when blank, so every manual menu-item create failed and category create failed 100% (the form never sends nameEn). Now optional. - DemoDataBanner: only showed when a cafe was exactly empty, so showcase-seeded cafes (2-3 cats / 3-5 items) could never trigger the one-click seed. Widened gate to sparse menus (<5 cats && <10 items) and added a clear "nothing to add" message when already populated. - client.ts: added one-time JWT refresh-and-retry on 401 (shared in-flight promise) before bouncing to /login. Expired access tokens silently broke ticket list, add-table, and other reads. - Surface API errors as toasts on menu + table mutations (were swallowed silently, so failures looked like "nothing happens"). - Admin blog editor: saving an edit dropped IsPublished (defaulted false, silently unpublishing the post on every save); now persisted with a toggle. Also hoisted the inner Field component to module scope - it was remounting every input on each keystroke and dropping focus. - Admin integrations: replaced raw radio gateway selector with a styled RadioDot matching the iOS toggles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||
import { Sparkles, Loader2 } from "lucide-react";
|
||
import { apiPost } from "@/lib/api/client";
|
||
import { useAuthStore } from "@/lib/stores/auth.store";
|
||
import { Button } from "@/components/ui/button";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
interface DemoSeedResult {
|
||
categoriesAdded: number;
|
||
itemsAdded: number;
|
||
tablesAdded: number;
|
||
ingredientsAdded: number;
|
||
taxCreated: boolean;
|
||
}
|
||
|
||
interface Props {
|
||
/** Which queries to invalidate after seeding. */
|
||
invalidateKeys: string[][];
|
||
className?: string;
|
||
}
|
||
|
||
export function DemoDataBanner({ invalidateKeys, className }: Props) {
|
||
const cafeId = useAuthStore((s) => s.user?.cafeId);
|
||
const role = useAuthStore((s) => s.user?.role);
|
||
const qc = useQueryClient();
|
||
const [done, setDone] = useState(false);
|
||
const [summary, setSummary] = useState<DemoSeedResult | null>(null);
|
||
|
||
const seed = useMutation({
|
||
mutationFn: () =>
|
||
apiPost<DemoSeedResult>(`/api/cafes/${cafeId}/demo/seed`, {}),
|
||
onSuccess: (result) => {
|
||
setSummary(result);
|
||
setDone(true);
|
||
for (const key of invalidateKeys) {
|
||
qc.invalidateQueries({ queryKey: key });
|
||
}
|
||
},
|
||
});
|
||
|
||
if (!cafeId || (role !== "Owner" && role !== "Manager")) return null;
|
||
if (done && summary) {
|
||
const nothingAdded =
|
||
summary.categoriesAdded === 0 &&
|
||
summary.itemsAdded === 0 &&
|
||
summary.tablesAdded === 0 &&
|
||
summary.ingredientsAdded === 0 &&
|
||
!summary.taxCreated;
|
||
return (
|
||
<div
|
||
className={cn(
|
||
"flex items-center gap-3 rounded-xl border border-[#0F6E56]/30 bg-[#E1F5EE] px-4 py-3 text-sm text-[#0F6E56]",
|
||
className
|
||
)}
|
||
>
|
||
<Sparkles className="size-4 shrink-0" />
|
||
<span>
|
||
{nothingAdded ? (
|
||
"همه دادههای نمونه از قبل موجود بودند — موردی اضافه نشد."
|
||
) : (
|
||
<>
|
||
دادههای نمونه اضافه شد — {summary.categoriesAdded} دسته،{" "}
|
||
{summary.itemsAdded} آیتم، {summary.tablesAdded} میز،{" "}
|
||
{summary.ingredientsAdded} ماده اولیه
|
||
{summary.taxCreated ? "، مالیات ۹٪" : ""}.
|
||
</>
|
||
)}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className={cn(
|
||
"flex flex-col gap-3 rounded-xl border border-dashed border-[#0F6E56]/40 bg-[#E1F5EE]/40 px-5 py-4 sm:flex-row sm:items-center sm:justify-between",
|
||
className
|
||
)}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<Sparkles className="size-5 shrink-0 text-[#0F6E56]" />
|
||
<div>
|
||
<p className="text-sm font-semibold text-[#0F6E56]">شروع سریع با دادههای نمونه</p>
|
||
<p className="text-xs text-muted-foreground mt-0.5">
|
||
۷ دسته، ۵۹+ آیتم منو، ۱۰ میز، ۱۵ ماده اولیه و مالیات ۹٪ بهصورت خودکار اضافه میشود.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<Button
|
||
size="sm"
|
||
className="shrink-0 bg-[#0F6E56] hover:bg-[#0c5a46]"
|
||
disabled={seed.isPending}
|
||
onClick={() => seed.mutate()}
|
||
>
|
||
{seed.isPending ? (
|
||
<Loader2 className="me-1.5 size-4 animate-spin" />
|
||
) : (
|
||
<Sparkles className="me-1.5 size-4" />
|
||
)}
|
||
افزودن دادههای نمونه
|
||
</Button>
|
||
</div>
|
||
);
|
||
}
|