feat(website): full Meezi knowledge base with per-feature wireframes
CI/CD / CI · API (dotnet build + test) (push) Successful in 40s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 29s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m8s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 1m53s
CI/CD / CI · API (dotnet build + test) (push) Successful in 40s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 29s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m8s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 1m53s
Turns the static /docs page into a real help center. Every feature now has a
detail page at /docs/{slug} with a minimal wireframe mockup + concrete Persian
how-to steps (English mirror), grouped into 6 sections.
- guide-data.tsx: typed GUIDE_FEATURES (21 features — pos, tables, kds, queue,
reservations, menu, inventory, crm, coupons, sms, reviews, reports, expenses,
shifts, taxes, hr, branches, subscription, settings, qr-menu, koja) with
fa/en title, tagline, 5–8 steps, tips, tier badge, group, wireframe variant.
- wireframes.tsx: 7 reusable minimal line-art variants (board/order/menu/list/
dashboard/form/phone), brand-colored, RTL-aware.
- docs/[slug]/page.tsx: dynamic guide page (hero, wireframe + numbered steps,
tips, prev/next, support CTA); generateStaticParams + generateMetadata; 404
for unknown slugs.
- docs/page.tsx: module cards now sourced from GUIDE_FEATURES, grouped, linking
to the detail pages.
Verified via SSR: index lists all 21, detail pages render titles + wireframe,
en mirror 200, unknown slug 404, tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,254 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { Navbar } from "@/components/layout/navbar";
|
||||||
|
import { Footer } from "@/components/layout/footer";
|
||||||
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowRight,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Lightbulb,
|
||||||
|
LifeBuoy,
|
||||||
|
} from "lucide-react";
|
||||||
|
import {
|
||||||
|
GUIDE_FEATURES,
|
||||||
|
getFeatureBySlug,
|
||||||
|
TIER_LABELS,
|
||||||
|
type GuideFeature,
|
||||||
|
} from "../guide-data";
|
||||||
|
import { Wireframe } from "../wireframes";
|
||||||
|
|
||||||
|
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://meezi.ir";
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return GUIDE_FEATURES.map((f) => ({ slug: f.slug }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: string; slug: string }>;
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale, slug } = await params;
|
||||||
|
const feature = getFeatureBySlug(slug);
|
||||||
|
if (!feature) return {};
|
||||||
|
const c = locale === "fa" ? feature.fa : feature.en;
|
||||||
|
return {
|
||||||
|
title: c.title,
|
||||||
|
description: c.tagline,
|
||||||
|
alternates: {
|
||||||
|
canonical: `${BASE_URL}/${locale}/docs/${slug}`,
|
||||||
|
languages: {
|
||||||
|
fa: `${BASE_URL}/fa/docs/${slug}`,
|
||||||
|
en: `${BASE_URL}/en/docs/${slug}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: c.title,
|
||||||
|
description: c.tagline,
|
||||||
|
url: `${BASE_URL}/${locale}/docs/${slug}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function siblings(feature: GuideFeature): { prev?: GuideFeature; next?: GuideFeature } {
|
||||||
|
const i = GUIDE_FEATURES.findIndex((f) => f.slug === feature.slug);
|
||||||
|
return {
|
||||||
|
prev: i > 0 ? GUIDE_FEATURES[i - 1] : undefined,
|
||||||
|
next: i < GUIDE_FEATURES.length - 1 ? GUIDE_FEATURES[i + 1] : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const COPY = {
|
||||||
|
fa: {
|
||||||
|
backToDocs: "راهنمای میزی",
|
||||||
|
howTo: "گامبهگام",
|
||||||
|
tips: "نکتهها",
|
||||||
|
prev: "قبلی",
|
||||||
|
next: "بعدی",
|
||||||
|
supportTitle: "نیاز به کمک؟",
|
||||||
|
supportDesc: "تیم پشتیبانی ما آماده است. از طریق داشبورد یا ایمیل با ما در ارتباط باش.",
|
||||||
|
supportBtn: "تماس با پشتیبانی",
|
||||||
|
demoBtn: "درخواست آموزش رایگان",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
backToDocs: "Help Center",
|
||||||
|
howTo: "Step by step",
|
||||||
|
tips: "Tips",
|
||||||
|
prev: "Previous",
|
||||||
|
next: "Next",
|
||||||
|
supportTitle: "Need help?",
|
||||||
|
supportDesc: "Our support team is ready. Reach us through the dashboard or by email.",
|
||||||
|
supportBtn: "Contact Support",
|
||||||
|
demoBtn: "Request free training",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function FeatureGuidePage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: string; slug: string }>;
|
||||||
|
}) {
|
||||||
|
const { locale, slug } = await params;
|
||||||
|
const feature = getFeatureBySlug(slug);
|
||||||
|
if (!feature) notFound();
|
||||||
|
|
||||||
|
const isEn = locale === "en";
|
||||||
|
const base = `/${locale}`;
|
||||||
|
const c = isEn ? feature.en : feature.fa;
|
||||||
|
const t = isEn ? COPY.en : COPY.fa;
|
||||||
|
const Icon = feature.icon;
|
||||||
|
const Arrow = isEn ? ArrowRight : ArrowLeft;
|
||||||
|
// Chevron pointing "back toward docs" — start-aligned, mirrors in RTL.
|
||||||
|
const BackChevron = isEn ? ChevronLeft : ChevronRight;
|
||||||
|
const { prev, next } = siblings(feature);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<main className="pt-16">
|
||||||
|
{/* Hero */}
|
||||||
|
<div className="bg-gradient-to-br from-brand-900 to-brand-700 pb-16 pt-14">
|
||||||
|
<div className="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||||
|
{/* Breadcrumb back to docs */}
|
||||||
|
<a
|
||||||
|
href={`${base}/docs`}
|
||||||
|
className="inline-flex items-center gap-1.5 text-sm font-medium text-white/70 transition-colors hover:text-white"
|
||||||
|
>
|
||||||
|
<BackChevron className="h-4 w-4" />
|
||||||
|
{t.backToDocs}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div className="mt-6 flex items-start gap-4">
|
||||||
|
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-white/10">
|
||||||
|
<Icon className="h-7 w-7 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<h1 className="text-3xl font-extrabold text-white sm:text-4xl">{c.title}</h1>
|
||||||
|
{feature.tier && feature.tier !== "free" && (
|
||||||
|
<span className="rounded-full border border-white/20 bg-white/10 px-3 py-1 text-xs font-semibold text-white/80">
|
||||||
|
{isEn ? TIER_LABELS[feature.tier].en : TIER_LABELS[feature.tier].fa}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 max-w-2xl text-lg leading-relaxed text-white/60">{c.tagline}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Two-column: wireframe + how-to */}
|
||||||
|
<div className="mx-auto max-w-5xl px-4 py-16 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid items-start gap-10 lg:grid-cols-2">
|
||||||
|
{/* Wireframe (sticky on desktop) */}
|
||||||
|
<div className="lg:sticky lg:top-24">
|
||||||
|
<Wireframe variant={feature.wireframe} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Numbered how-to steps */}
|
||||||
|
<div>
|
||||||
|
<h2 className="mb-6 text-2xl font-extrabold text-gray-900">{t.howTo}</h2>
|
||||||
|
<ol className="space-y-5">
|
||||||
|
{c.steps.map((step, i) => (
|
||||||
|
<li key={i} className="flex gap-4">
|
||||||
|
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-brand-700 text-sm font-extrabold text-white">
|
||||||
|
{isEn ? i + 1 : toFa(i + 1)}
|
||||||
|
</span>
|
||||||
|
<p className="pt-1 leading-relaxed text-gray-600">{step}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tips callout */}
|
||||||
|
{c.tips && c.tips.length > 0 && (
|
||||||
|
<div className="mt-12 rounded-2xl border border-brand-100 bg-brand-50 p-6">
|
||||||
|
<div className="mb-3 flex items-center gap-2">
|
||||||
|
<Lightbulb className="h-5 w-5 text-brand-700" />
|
||||||
|
<h3 className="text-base font-bold text-gray-900">{t.tips}</h3>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{c.tips.map((tip, i) => (
|
||||||
|
<li key={i} className="flex gap-2.5 text-sm leading-relaxed text-gray-600">
|
||||||
|
<span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-brand-500" />
|
||||||
|
{tip}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Prev / Next */}
|
||||||
|
<div className="mt-12 grid gap-4 sm:grid-cols-2">
|
||||||
|
{prev ? (
|
||||||
|
<a
|
||||||
|
href={`${base}/docs/${prev.slug}`}
|
||||||
|
className="group flex items-center gap-3 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm transition-all hover:border-brand-200 hover:shadow-md"
|
||||||
|
>
|
||||||
|
<BackChevron className="h-5 w-5 text-gray-400 transition-colors group-hover:text-brand-700" />
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400">{t.prev}</div>
|
||||||
|
<div className="font-semibold text-gray-900">{isEn ? prev.en.title : prev.fa.title}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="hidden sm:block" />
|
||||||
|
)}
|
||||||
|
{next && (
|
||||||
|
<a
|
||||||
|
href={`${base}/docs/${next.slug}`}
|
||||||
|
className="group flex items-center justify-end gap-3 rounded-2xl border border-gray-100 bg-white p-5 text-end shadow-sm transition-all hover:border-brand-200 hover:shadow-md"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400">{t.next}</div>
|
||||||
|
<div className="font-semibold text-gray-900">{isEn ? next.en.title : next.fa.title}</div>
|
||||||
|
</div>
|
||||||
|
<Arrow className="h-5 w-5 text-gray-400 transition-colors group-hover:text-brand-700" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Support CTA (copied from docs index) */}
|
||||||
|
<section className="mt-16 rounded-2xl bg-gradient-to-br from-brand-900 to-brand-700 p-10">
|
||||||
|
<div className="flex flex-col items-center gap-6 text-center sm:flex-row sm:text-start">
|
||||||
|
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-white/10">
|
||||||
|
<LifeBuoy className="h-7 w-7 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h2 className="mb-1 text-xl font-bold text-white">{t.supportTitle}</h2>
|
||||||
|
<p className="text-white/60">{t.supportDesc}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<a
|
||||||
|
href={`${base}/contact`}
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl bg-white px-5 py-2.5 text-sm font-semibold text-brand-700 transition-colors hover:bg-brand-50"
|
||||||
|
>
|
||||||
|
{t.supportBtn}
|
||||||
|
<Arrow className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={`${base}/demo`}
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl border border-white/20 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-white/10"
|
||||||
|
>
|
||||||
|
{t.demoBtn}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Western digits → Persian digits for step numbers. */
|
||||||
|
function toFa(n: number): string {
|
||||||
|
const map = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"];
|
||||||
|
return String(n)
|
||||||
|
.split("")
|
||||||
|
.map((d) => map[Number(d)] ?? d)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
@@ -0,0 +1,873 @@
|
|||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
import {
|
||||||
|
ShoppingCart,
|
||||||
|
LayoutGrid,
|
||||||
|
ChefHat,
|
||||||
|
ListOrdered,
|
||||||
|
CalendarClock,
|
||||||
|
BookOpen,
|
||||||
|
Package,
|
||||||
|
Users,
|
||||||
|
TicketPercent,
|
||||||
|
MessageSquareText,
|
||||||
|
Star,
|
||||||
|
BarChart3,
|
||||||
|
Receipt,
|
||||||
|
Wallet,
|
||||||
|
Percent,
|
||||||
|
UserCog,
|
||||||
|
Building2,
|
||||||
|
CreditCard,
|
||||||
|
Settings,
|
||||||
|
Smartphone,
|
||||||
|
MapPin,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
export type GuideGroup =
|
||||||
|
| "operations"
|
||||||
|
| "menu"
|
||||||
|
| "customers"
|
||||||
|
| "money"
|
||||||
|
| "management"
|
||||||
|
| "public";
|
||||||
|
|
||||||
|
export type GuideTier = "free" | "starter" | "pro" | "business";
|
||||||
|
|
||||||
|
export type WireframeVariant =
|
||||||
|
| "board"
|
||||||
|
| "order"
|
||||||
|
| "menu"
|
||||||
|
| "list"
|
||||||
|
| "dashboard"
|
||||||
|
| "form"
|
||||||
|
| "phone";
|
||||||
|
|
||||||
|
export interface GuideContent {
|
||||||
|
title: string;
|
||||||
|
tagline: string;
|
||||||
|
steps: string[];
|
||||||
|
tips?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuideFeature {
|
||||||
|
slug: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
group: GuideGroup;
|
||||||
|
/** Omit for all-tier (free) features that need no badge. */
|
||||||
|
tier?: GuideTier;
|
||||||
|
wireframe: WireframeVariant;
|
||||||
|
fa: GuideContent;
|
||||||
|
en: GuideContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GROUP_TITLES: Record<GuideGroup, { fa: string; en: string }> = {
|
||||||
|
operations: { fa: "عملیات روزانه", en: "Daily Operations" },
|
||||||
|
menu: { fa: "منو و انبار", en: "Menu & Inventory" },
|
||||||
|
customers: { fa: "مشتریان و بازاریابی", en: "Customers & Marketing" },
|
||||||
|
money: { fa: "پول و گزارش", en: "Money & Reports" },
|
||||||
|
management: { fa: "مدیریت", en: "Management" },
|
||||||
|
public: { fa: "مهمان و عمومی", en: "Guest & Public" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GROUP_ORDER: GuideGroup[] = [
|
||||||
|
"operations",
|
||||||
|
"menu",
|
||||||
|
"customers",
|
||||||
|
"money",
|
||||||
|
"management",
|
||||||
|
"public",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TIER_LABELS: Record<GuideTier, { fa: string; en: string }> = {
|
||||||
|
free: { fa: "همه پلنها", en: "All plans" },
|
||||||
|
starter: { fa: "از پلن استارتر به بالا", en: "Starter plan & up" },
|
||||||
|
pro: { fa: "از پلن حرفهای به بالا", en: "Pro plan & up" },
|
||||||
|
business: { fa: "از پلن بیزینس به بالا", en: "Business plan & up" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GUIDE_FEATURES: GuideFeature[] = [
|
||||||
|
// ─────────────── operations ───────────────
|
||||||
|
{
|
||||||
|
slug: "pos",
|
||||||
|
icon: ShoppingCart,
|
||||||
|
group: "operations",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "order",
|
||||||
|
fa: {
|
||||||
|
title: "صندوق (POS)",
|
||||||
|
tagline:
|
||||||
|
"ثبت سفارش سر میز، پیشخوان یا بیرونبر، ارسال به آشپزخانه و تسویه — همه در یک صفحه.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «صندوق» شوید. اگر پیام «شیفت باز نیست» دیدید، اول از بخش «بستن شیفت» صندوق روز را باز کنید.",
|
||||||
|
"نوع سفارش را انتخاب کنید: روی یک میز بزنید، یا گزینهٔ «پیشخوان» / «بیرونبر» را انتخاب کنید.",
|
||||||
|
"از ریل دستهبندی سمت راست یک دسته را انتخاب و روی آیتمها بزنید تا به فاکتور (ستون سمت چپ) اضافه شوند؛ با + و − تعداد را تنظیم کنید.",
|
||||||
|
"در صورت نیاز یادداشت آیتم (مثلاً «بدون شکر») یا تخفیف ردیفی اضافه کنید.",
|
||||||
|
"روی «ارسال به آشپزخانه» بزنید تا سفارش روی صفحهٔ KDS و پرینتر آشپزخانه ظاهر شود.",
|
||||||
|
"برای تسویه دکمهٔ «پرداخت» را بزنید؛ روش را انتخاب کنید: نقدی، کارتخوان، اعتباری یا تقسیم صورتحساب بین چند روش.",
|
||||||
|
"اگر کوپن دارید کد را وارد کنید و در صورت ثبت مشتری، امتیاز وفاداری را اعمال کنید.",
|
||||||
|
"برای اصلاح یک فاکتور بستهشده، از «اصلاح سند» استفاده کنید تا بهجای حذف، یک سند اصلاحی ثبت شود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"بدون باز بودن شیفت، دکمهٔ پرداخت غیرفعال است؛ این تضمین میکند صندوق روز همیشه قابل بستن باشد.",
|
||||||
|
"تقسیم صورتحساب اجازه میدهد بخشی نقدی و بخشی کارتخوان تسویه شود.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Point of Sale (POS)",
|
||||||
|
tagline:
|
||||||
|
"Take table, counter, or takeaway orders, fire them to the kitchen, and settle — all on one screen.",
|
||||||
|
steps: [
|
||||||
|
"Open “POS” from the sidebar. If you see “No open shift”, open the day’s drawer first from “Close Shift”.",
|
||||||
|
"Pick the order type: tap a table, or choose “Counter” / “Takeaway”.",
|
||||||
|
"Pick a category from the side rail and tap items to add them to the ticket (left column); adjust quantity with + and −.",
|
||||||
|
"Add an item note (e.g. “no sugar”) or a per-line discount if needed.",
|
||||||
|
"Press “Send to kitchen” so the order shows on the KDS screen and prints to the kitchen printer.",
|
||||||
|
"Press “Pay” to settle; choose a method: cash, card terminal, store credit, or split across methods.",
|
||||||
|
"Enter a coupon code if you have one, and apply loyalty points if the customer is on file.",
|
||||||
|
"To fix a closed ticket, use “Document correction” so a correcting entry is logged instead of deleting it.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Payment is disabled with no open shift — this guarantees the drawer can always be reconciled.",
|
||||||
|
"Split billing lets you settle part in cash and part on the card terminal.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "tables",
|
||||||
|
icon: LayoutGrid,
|
||||||
|
group: "operations",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "board",
|
||||||
|
fa: {
|
||||||
|
title: "میزها",
|
||||||
|
tagline: "چیدمان سالن را بسازید؛ هر میز یک کد QR دائمی میگیرد.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «میزها» شوید.",
|
||||||
|
"روی «افزودن میز» بزنید و نام/شماره میز را وارد کنید (مثلاً «میز ۷» یا «بالکن ۲»).",
|
||||||
|
"میزها را بین بخشهای سالن سازماندهی کنید تا چیدمان واقعی کافه را منعکس کند.",
|
||||||
|
"برای هر میز کد QR دائمی صادر میشود؛ روی «چاپ QR» بزنید و آن را روی میز نصب کنید.",
|
||||||
|
"وقتی میز خالی شد، کلید «تمیزکاری» را بزنید تا وضعیت میز مشخص باشد و سپس آماده پذیرش مشتری بعدی شود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"کد QR هر میز ثابت میماند؛ لازم نیست بعد از هر سفارش دوباره چاپ کنید.",
|
||||||
|
"وضعیت تمیزکاری به گارسون کمک میکند میز آمادهٔ پذیرش را سریع تشخیص دهد.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Tables",
|
||||||
|
tagline: "Lay out your floor plan; each table gets a permanent QR code.",
|
||||||
|
steps: [
|
||||||
|
"Open “Tables” from the sidebar.",
|
||||||
|
"Press “Add table” and enter a name/number (e.g. “Table 7” or “Balcony 2”).",
|
||||||
|
"Organize tables into floor sections so the layout mirrors your real café.",
|
||||||
|
"A permanent QR code is issued per table; press “Print QR” and stick it on the table.",
|
||||||
|
"When a table clears, flip the “Cleaning” toggle so its status is visible before the next guest is seated.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Each table’s QR stays fixed — no need to reprint after every order.",
|
||||||
|
"The cleaning status helps waiters spot ready-to-seat tables at a glance.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "kds",
|
||||||
|
icon: ChefHat,
|
||||||
|
group: "operations",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "board",
|
||||||
|
fa: {
|
||||||
|
title: "آشپزخانه (KDS)",
|
||||||
|
tagline: "فیشهای آشپزخانه بهصورت لحظهای در ستونهای در انتظار، در حال آمادهسازی و آماده.",
|
||||||
|
steps: [
|
||||||
|
"صفحهٔ «آشپزخانه» را روی یک نمایشگر در آشپزخانه باز و تمامصفحه کنید.",
|
||||||
|
"هر سفارش تازه بهصورت یک فیش در ستون «در انتظار» با صدا ظاهر میشود.",
|
||||||
|
"آشپز با زدن روی فیش، آن را به ستون «در حال آمادهسازی» منتقل میکند.",
|
||||||
|
"پس از آماده شدن، فیش را به ستون «آماده» ببرید تا گارسون اعلان دریافت کند.",
|
||||||
|
"میز و آیتمهای هر فیش روی کارت دیده میشود؛ یادداشتهای مشتری زیر هر آیتم نمایش داده میشوند.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"صفحهٔ KDS بهصورت بیدرنگ بهروزرسانی میشود؛ نیازی به رفرش دستی نیست.",
|
||||||
|
"اگر پرینتر آشپزخانه دارید، فیش همزمان روی کاغذ هم چاپ میشود.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Kitchen Display (KDS)",
|
||||||
|
tagline: "Real-time kitchen tickets across pending, preparing, and ready columns.",
|
||||||
|
steps: [
|
||||||
|
"Open the “Kitchen” screen on a kitchen monitor and go full-screen.",
|
||||||
|
"Each new order appears as a ticket in the “Pending” column with a sound alert.",
|
||||||
|
"The chef taps a ticket to move it into the “Preparing” column.",
|
||||||
|
"Once it’s done, move the ticket to the “Ready” column so the waiter is notified.",
|
||||||
|
"Each ticket card shows the table and items; customer notes appear under each item.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"The KDS updates live — no manual refresh needed.",
|
||||||
|
"If you have a kitchen printer, the ticket also prints on paper simultaneously.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "queue",
|
||||||
|
icon: ListOrdered,
|
||||||
|
group: "operations",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "نوبتدهی",
|
||||||
|
tagline: "صف نوبت روزانه با شماره، بههمراه یک نمایشگر تمامصفحه برای تلویزیون.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «نوبتدهی» شوید.",
|
||||||
|
"برای هر مشتری ورودی، روی «نوبت جدید» بزنید تا یک شماره گرفته شود.",
|
||||||
|
"وقتی نوبت یک مشتری رسید، روی «فراخوان» بزنید تا روی نمایشگر نشان داده شود.",
|
||||||
|
"نمایشگر تمامصفحه را روی یک تلویزیون باز کنید تا شمارهٔ نوبت جاری برای همه قابل دیدن باشد.",
|
||||||
|
"نوبتهای انجامشده را ببندید؛ صف هر روز بهصورت خودکار از صفر شروع میشود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"نمایشگر TV را روی یک مرورگر جدا تمامصفحه نگه دارید تا مدام بهروز شود.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Queue / Take-a-Number",
|
||||||
|
tagline: "A daily numbered queue plus a full-screen TV display.",
|
||||||
|
steps: [
|
||||||
|
"Open “Queue” from the sidebar.",
|
||||||
|
"Press “New ticket” for each arriving customer to issue a number.",
|
||||||
|
"When a customer’s turn comes, press “Call” so it shows on the display.",
|
||||||
|
"Open the full-screen display on a TV so the current number is visible to everyone.",
|
||||||
|
"Close served tickets; the queue resets to zero automatically each day.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Keep the TV display full-screen in a separate browser so it refreshes continuously.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "reservations",
|
||||||
|
icon: CalendarClock,
|
||||||
|
group: "operations",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "رزرو",
|
||||||
|
tagline: "رزرو دستی میز و تبدیل آن به سفارش صندوق هنگام رسیدن مشتری.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «رزرو» شوید.",
|
||||||
|
"روی «رزرو جدید» بزنید و نام مشتری، تعداد نفرات، تاریخ و ساعت را وارد کنید.",
|
||||||
|
"یک میز را به رزرو اختصاص دهید تا در زمان مشخص رزرو شده بماند.",
|
||||||
|
"هنگام رسیدن مهمان، رزرو را باز کرده و روی «تبدیل به سفارش» بزنید تا یک سفارش صندوق روی همان میز ساخته شود.",
|
||||||
|
"رزروهای لغوشده را حذف کنید تا میز دوباره آزاد شود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"تبدیل رزرو به سفارش، اطلاعات مشتری را به فاکتور منتقل میکند و نیازی به ورود دوباره نیست.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Reservations",
|
||||||
|
tagline: "Book tables manually and convert them to a POS order on arrival.",
|
||||||
|
steps: [
|
||||||
|
"Open “Reservations” from the sidebar.",
|
||||||
|
"Press “New reservation” and enter the guest name, party size, date, and time.",
|
||||||
|
"Assign a table to the reservation so it stays held at the chosen time.",
|
||||||
|
"When the guest arrives, open the reservation and press “Convert to order” to start a POS order on that table.",
|
||||||
|
"Delete cancelled reservations to free the table again.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Converting a reservation carries the guest details into the ticket — no re-entry needed.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─────────────── menu ───────────────
|
||||||
|
{
|
||||||
|
slug: "menu",
|
||||||
|
icon: BookOpen,
|
||||||
|
group: "menu",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "menu",
|
||||||
|
fa: {
|
||||||
|
title: "منو",
|
||||||
|
tagline: "دستهبندیها و آیتمها با قیمت، تخفیف، تصویر/ویدیو/۳بعدی و قیمت اختصاصی هر شعبه.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «منو» شوید.",
|
||||||
|
"ابتدا دستهبندی بسازید (مثلاً «قهوهها»، «دسرها») و ترتیب آنها را تنظیم کنید.",
|
||||||
|
"روی «افزودن آیتم» بزنید و نام، قیمت و توضیح را وارد کنید.",
|
||||||
|
"برای هر آیتم تصویر، و در صورت تمایل ویدیو یا مدل سهبعدی بارگذاری کنید تا روی منوی مهمان جذابتر شود.",
|
||||||
|
"در صورت نیاز تخفیف آیتم را تعیین کنید؛ قیمت خطخورده در منوی مهمان نمایش داده میشود.",
|
||||||
|
"اگر چند شعبه دارید، برای هر شعبه قیمت اختصاصی (Override) تعریف کنید تا قیمت پایه فقط برای آن شعبه تغییر کند.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"تغییر قیمت بلافاصله روی منوی QR مهمان اعمال میشود.",
|
||||||
|
"قیمت اختصاصی شعبه فقط همان شعبه را تحت تأثیر قرار میدهد؛ بقیه با قیمت پایه میمانند.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Menu",
|
||||||
|
tagline: "Categories and items with price, discount, image/video/3D, and per-branch price overrides.",
|
||||||
|
steps: [
|
||||||
|
"Open “Menu” from the sidebar.",
|
||||||
|
"Create categories first (e.g. “Coffee”, “Desserts”) and set their order.",
|
||||||
|
"Press “Add item” and enter a name, price, and description.",
|
||||||
|
"Upload an image for each item — and optionally a video or 3D model — to make the guest menu richer.",
|
||||||
|
"Set an item discount if needed; the strikethrough price shows on the guest menu.",
|
||||||
|
"If you run multiple branches, define a per-branch price override so the base price changes for that branch only.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Price changes apply to the guest QR menu instantly.",
|
||||||
|
"A branch override affects only that branch; the rest keep the base price.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "inventory",
|
||||||
|
icon: Package,
|
||||||
|
group: "menu",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "انبار",
|
||||||
|
tagline: "مواد اولیه، رسپی با کسر خودکار، خرید که به هزینهها میرود و هشدار کمبود موجودی.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «انبار» شوید.",
|
||||||
|
"مواد اولیه را تعریف کنید (مثلاً دانهٔ قهوه، شیر) با واحد و موجودی فعلی.",
|
||||||
|
"برای هر آیتم منو یک رسپی بسازید تا با فروش آن، مواد اولیه بهصورت خودکار از موجودی کسر شود.",
|
||||||
|
"خریدهای جدید را ثبت کنید؛ هر خرید بهصورت خودکار در «هزینهها» هم ثبت میشود.",
|
||||||
|
"آستانهٔ هشدار کمبود را تنظیم کنید تا هنگام رسیدن موجودی به حد پایین مطلع شوید.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"رسپی دقیق باعث میشود گزارش مصرف و سود واقعیتر باشد.",
|
||||||
|
"ثبت خرید در انبار، نیاز به ثبت دستی همان مبلغ در هزینهها را حذف میکند.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Inventory",
|
||||||
|
tagline: "Raw materials, recipes with auto-deduction, purchases that feed expenses, and low-stock alerts.",
|
||||||
|
steps: [
|
||||||
|
"Open “Inventory” from the sidebar.",
|
||||||
|
"Define raw materials (e.g. coffee beans, milk) with a unit and current stock.",
|
||||||
|
"Build a recipe for each menu item so selling it auto-deducts the materials from stock.",
|
||||||
|
"Record new purchases; each purchase is automatically logged under “Expenses” too.",
|
||||||
|
"Set a low-stock threshold so you’re alerted when a material runs low.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Accurate recipes make consumption and profit reports more realistic.",
|
||||||
|
"Recording a purchase in inventory removes the need to log the same amount in expenses manually.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─────────────── customers ───────────────
|
||||||
|
{
|
||||||
|
slug: "crm",
|
||||||
|
icon: Users,
|
||||||
|
group: "customers",
|
||||||
|
tier: "pro",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "مشتریان (CRM)",
|
||||||
|
tagline: "پایگاه دادهٔ مشتریان، گروهبندی و امتیاز وفاداری.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «مشتریان» شوید.",
|
||||||
|
"مشتری جدید را با نام و شمارهٔ موبایل اضافه کنید، یا بگذارید هنگام تسویه در صندوق ثبت شود.",
|
||||||
|
"مشتریان را در گروهها دستهبندی کنید (مثلاً «وفادار»، «شرکتی») برای پیامک هدفمند.",
|
||||||
|
"امتیاز وفاداری هر مشتری با خریدهایش جمع میشود و در صندوق قابل استفاده است.",
|
||||||
|
"تاریخچهٔ سفارش هر مشتری را برای شناخت سلیقهٔ او مرور کنید.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"گروهبندی مشتری پایهٔ ارسال پیامک تبلیغاتی هدفمند است.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Customers (CRM)",
|
||||||
|
tagline: "A customer database, groups, and loyalty points.",
|
||||||
|
steps: [
|
||||||
|
"Open “Customers” from the sidebar.",
|
||||||
|
"Add a new customer with name and mobile number, or let them be captured at checkout in POS.",
|
||||||
|
"Sort customers into groups (e.g. “Loyal”, “Corporate”) for targeted SMS.",
|
||||||
|
"Each customer’s loyalty points accrue with purchases and can be redeemed at the POS.",
|
||||||
|
"Review a customer’s order history to understand their taste.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Customer grouping is the basis for targeted marketing SMS.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "coupons",
|
||||||
|
icon: TicketPercent,
|
||||||
|
group: "customers",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "کوپنها",
|
||||||
|
tagline: "کدهای تخفیف درصدی یا مبلغی، قابل استفاده در صندوق و تسویهٔ مهمان.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «کوپنها» شوید.",
|
||||||
|
"روی «کوپن جدید» بزنید و کد را تعیین کنید (مثلاً WELCOME).",
|
||||||
|
"نوع تخفیف را انتخاب کنید: درصدی (مثلاً ۱۰٪) یا مبلغ ثابت.",
|
||||||
|
"در صورت نیاز محدودیت زمانی یا سقف استفاده برای کوپن بگذارید.",
|
||||||
|
"کد ساختهشده در صندوق و هنگام تسویهٔ مهمان قابل وارد کردن است.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"کوپن درصدی برای کمپین کوتاهمدت و کوپن مبلغی برای جبران یا هدیه مناسبتر است.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Coupons",
|
||||||
|
tagline: "Percent or fixed discount codes, usable at POS and guest checkout.",
|
||||||
|
steps: [
|
||||||
|
"Open “Coupons” from the sidebar.",
|
||||||
|
"Press “New coupon” and set the code (e.g. WELCOME).",
|
||||||
|
"Choose the discount type: percentage (e.g. 10%) or a fixed amount.",
|
||||||
|
"Add a time limit or usage cap if needed.",
|
||||||
|
"The code can then be entered at the POS and during guest checkout.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Percent coupons suit short campaigns; fixed coupons suit refunds or gifts.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "sms",
|
||||||
|
icon: MessageSquareText,
|
||||||
|
group: "customers",
|
||||||
|
tier: "pro",
|
||||||
|
wireframe: "form",
|
||||||
|
fa: {
|
||||||
|
title: "پیامک",
|
||||||
|
tagline: "پیامک تبلیغاتی به گروههای مشتری با اکانت کاوهنگار خودِ کافه.",
|
||||||
|
steps: [
|
||||||
|
"از تنظیمات، اطلاعات اکانت کاوهنگار خودتان (کلید API و خط ارسال) را وارد کنید.",
|
||||||
|
"از منوی کناری وارد «پیامک» شوید.",
|
||||||
|
"گروه مشتری گیرنده را انتخاب کنید (مثلاً «وفادار»).",
|
||||||
|
"متن پیام را بنویسید و پیشنمایش آن را ببینید.",
|
||||||
|
"روی «ارسال» بزنید؛ پیامک از طریق اکانت کاوهنگار خودِ شما و با اعتبار خودتان ارسال میشود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"میزی اعتبار پیامک نمیفروشد؛ هزینه و خط ارسال از اکانت کاوهنگار خودتان است (Bring-Your-Own).",
|
||||||
|
"پیام را کوتاه و دارای پیشنهاد مشخص نگه دارید تا اثرگذاری بیشتر باشد.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "SMS Marketing",
|
||||||
|
tagline: "Marketing SMS to customer groups using the café’s own Kavenegar account.",
|
||||||
|
steps: [
|
||||||
|
"In Settings, enter your own Kavenegar credentials (API key and sender line).",
|
||||||
|
"Open “SMS” from the sidebar.",
|
||||||
|
"Pick the recipient customer group (e.g. “Loyal”).",
|
||||||
|
"Write the message text and preview it.",
|
||||||
|
"Press “Send”; the SMS goes out through your own Kavenegar account on your own credit.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Meezi does not sell SMS credit — sending uses your own Kavenegar account (bring-your-own provider).",
|
||||||
|
"Keep the message short with a clear offer for better impact.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "reviews",
|
||||||
|
icon: Star,
|
||||||
|
group: "customers",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "نظرات",
|
||||||
|
tagline: "خواندن نظرات مهمانها (که در کجا نمایش داده میشوند) و پاسخ به آنها.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «نظرات» شوید.",
|
||||||
|
"نظرات ثبتشدهٔ مهمانها را که روی پروفایل کجا نمایش داده میشوند مرور کنید.",
|
||||||
|
"امتیاز و متن هر نظر را بررسی کنید تا بازخورد واقعی مشتری را بفهمید.",
|
||||||
|
"روی «پاسخ» بزنید و یک پاسخ عمومی برای نظر بنویسید (قابلیت پاسخ از پلن استارتر به بالا).",
|
||||||
|
"پاسخ شما کنار همان نظر در پروفایل عمومی کجا نمایش داده میشود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"پاسخ مودبانه به نظرات منفی، اعتماد مشتریان جدید را در کجا بالا میبرد.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Reviews",
|
||||||
|
tagline: "Read guest reviews (shown on Koja) and reply to them.",
|
||||||
|
steps: [
|
||||||
|
"Open “Reviews” from the sidebar.",
|
||||||
|
"Browse the guest reviews that appear on your Koja profile.",
|
||||||
|
"Check each review’s rating and text to understand real customer feedback.",
|
||||||
|
"Press “Reply” to write a public response (reply is available from the Starter plan up).",
|
||||||
|
"Your reply shows next to the review on your public Koja profile.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"A polite reply to a negative review builds trust with new customers on Koja.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─────────────── money ───────────────
|
||||||
|
{
|
||||||
|
slug: "reports",
|
||||||
|
icon: BarChart3,
|
||||||
|
group: "money",
|
||||||
|
tier: "pro",
|
||||||
|
wireframe: "dashboard",
|
||||||
|
fa: {
|
||||||
|
title: "گزارشها",
|
||||||
|
tagline: "تحلیل فروش و سود، تفکیک روزبهروز، اصلاح سند، لاگ ممیزی و خروجی CSV.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «گزارشها» شوید.",
|
||||||
|
"بازهٔ زمانی را انتخاب کنید تا کارتهای فروش، سود و تعداد سفارش بهروز شوند.",
|
||||||
|
"نمودار روزبهروز را برای یافتن روزها و ساعات پیک بررسی کنید.",
|
||||||
|
"برای هر تراکنش اشتباه، از «اصلاح سند» یک سند اصلاحی ثبت کنید (بهجای حذف).",
|
||||||
|
"لاگ ممیزی را برای دیدن اینکه چه کسی چه تغییری داده مرور کنید.",
|
||||||
|
"با «خروجی CSV» دادهها را برای حسابدار یا اکسل بگیرید.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"اصلاح سند تاریخچهٔ مالی را دستنخورده نگه میدارد و برای ممیزی شفاف است.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Reports",
|
||||||
|
tagline: "Sales and profit analytics, day-by-day breakdown, document correction, audit log, and CSV export.",
|
||||||
|
steps: [
|
||||||
|
"Open “Reports” from the sidebar.",
|
||||||
|
"Pick a date range so the sales, profit, and order-count cards update.",
|
||||||
|
"Review the day-by-day chart to spot peak days and hours.",
|
||||||
|
"For any wrong transaction, log a correcting entry via “Document correction” (instead of deleting).",
|
||||||
|
"Review the audit log to see who changed what.",
|
||||||
|
"Use “Export CSV” to hand data to your accountant or open it in Excel.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Document correction keeps the financial history intact and transparent for audits.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "expenses",
|
||||||
|
icon: Receipt,
|
||||||
|
group: "money",
|
||||||
|
tier: "pro",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "هزینهها",
|
||||||
|
tagline: "ثبت و پیگیری هزینههای شعبه، شامل خریدهای انبار که خودکار ثبت میشوند.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «هزینهها» شوید.",
|
||||||
|
"روی «هزینهٔ جدید» بزنید و عنوان، مبلغ و دستهبندی (مثلاً اجاره، حقوق) را وارد کنید.",
|
||||||
|
"خریدهای انبار بهصورت خودکار اینجا ثبت میشوند؛ نیازی به ورود دوباره نیست.",
|
||||||
|
"هزینهها را بر اساس دسته فیلتر کنید تا الگوی خرج هر شعبه را ببینید.",
|
||||||
|
"مجموع هزینهها در محاسبهٔ سود در گزارشها لحاظ میشود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"دستهبندی منظم هزینهها باعث میشود گزارش سود معنادار باشد.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Expenses",
|
||||||
|
tagline: "Record and track branch expenses, including auto-logged inventory purchases.",
|
||||||
|
steps: [
|
||||||
|
"Open “Expenses” from the sidebar.",
|
||||||
|
"Press “New expense” and enter a title, amount, and category (e.g. rent, payroll).",
|
||||||
|
"Inventory purchases are logged here automatically — no re-entry needed.",
|
||||||
|
"Filter expenses by category to see each branch’s spending pattern.",
|
||||||
|
"Total expenses feed into the profit calculation in Reports.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Consistent expense categories make the profit report meaningful.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "shifts",
|
||||||
|
icon: Wallet,
|
||||||
|
group: "money",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "form",
|
||||||
|
fa: {
|
||||||
|
title: "بستن شیفت",
|
||||||
|
tagline: "باز و بسته کردن صندوق روزانه هر شعبه — پیشنیاز پرداخت در صندوق.",
|
||||||
|
steps: [
|
||||||
|
"ابتدای روز از منوی کناری وارد «بستن شیفت» شوید و روی «باز کردن شیفت» بزنید و موجودی اولیهٔ صندوق را وارد کنید.",
|
||||||
|
"تا وقتی شیفت باز است، صندوق میتواند پرداخت ثبت کند.",
|
||||||
|
"پایان روز روی «بستن شیفت» بزنید؛ سیستم مجموع فروش نقدی و کارتخوان را نشان میدهد.",
|
||||||
|
"موجودی واقعی صندوق را شمرده و وارد کنید تا مغایرت (در صورت وجود) مشخص شود.",
|
||||||
|
"شیفت بسته میشود و گزارش آن در سوابق باقی میماند.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"بدون باز بودن شیفت، صندوق اجازهٔ پرداخت نمیدهد؛ این از گم شدن فروش جلوگیری میکند.",
|
||||||
|
"هر شعبه شیفت مستقل خودش را دارد.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Close Shift",
|
||||||
|
tagline: "Open and close the daily cash drawer per branch — required before POS payments.",
|
||||||
|
steps: [
|
||||||
|
"At the start of the day, open “Close Shift” from the sidebar, press “Open shift”, and enter the opening float.",
|
||||||
|
"While the shift is open, the POS can record payments.",
|
||||||
|
"At the end of the day, press “Close shift”; the system shows total cash and card sales.",
|
||||||
|
"Count the actual cash drawer and enter it so any variance is flagged.",
|
||||||
|
"The shift closes and its report stays in the records.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"With no open shift, the POS blocks payments — this prevents lost sales going unrecorded.",
|
||||||
|
"Each branch keeps its own independent shift.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "taxes",
|
||||||
|
icon: Percent,
|
||||||
|
group: "money",
|
||||||
|
tier: "pro",
|
||||||
|
wireframe: "form",
|
||||||
|
fa: {
|
||||||
|
title: "مالیات",
|
||||||
|
tagline: "تعریف نرخ مالیات روی دستهبندیها و ارسال به سامانهٔ مودیان (ترازو).",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «مالیات» شوید.",
|
||||||
|
"نرخ مالیات را تعریف کنید و آن را به دستهبندیهای منو نسبت دهید.",
|
||||||
|
"مالیات بهصورت خودکار روی فاکتورهای صندوق محاسبه و در رسید نمایش داده میشود.",
|
||||||
|
"برای سامانهٔ مودیان، از «تنظیمات» اطلاعات ترازو را وارد و فعال کنید.",
|
||||||
|
"فاکتورها مطابق الزامات سامانهٔ مودیان آمادهٔ ارسال میشوند.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"نرخ مالیات را روی دسته اعمال کنید تا نیازی به تنظیم تکتک آیتمها نباشد.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Taxes",
|
||||||
|
tagline: "Define tax rates on categories and submit to Iran’s tax system (Taraz).",
|
||||||
|
steps: [
|
||||||
|
"Open “Taxes” from the sidebar.",
|
||||||
|
"Define a tax rate and assign it to menu categories.",
|
||||||
|
"Tax is calculated automatically on POS tickets and shown on the receipt.",
|
||||||
|
"For the Moadian tax system, enter and enable the Taraz settings under “Settings”.",
|
||||||
|
"Invoices are prepared for submission per the tax-system requirements.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Apply the tax rate at the category level so you don’t configure each item one by one.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─────────────── management ───────────────
|
||||||
|
{
|
||||||
|
slug: "hr",
|
||||||
|
icon: UserCog,
|
||||||
|
group: "management",
|
||||||
|
tier: "pro",
|
||||||
|
wireframe: "form",
|
||||||
|
fa: {
|
||||||
|
title: "منابع انسانی",
|
||||||
|
tagline: "کارکنان، حضور و غیاب، مرخصی، حقوق، دسترسی شعبه و اطلاعات ورود.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «منابع انسانی» شوید.",
|
||||||
|
"کارمند جدید را با نام و نقش (گارسون، کاشیر، مدیر) اضافه کنید.",
|
||||||
|
"حضور و غیاب و مرخصی هر کارمند را ثبت و پیگیری کنید.",
|
||||||
|
"اطلاعات حقوق را وارد کنید تا محاسبهٔ پرداختی هر دوره ساده شود.",
|
||||||
|
"دسترسی هر کارمند به شعبهها و اطلاعات ورود او را تعیین کنید.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"نقشمحور بودن دسترسی باعث میشود هر کارمند فقط بخشهای مجاز را ببیند.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Human Resources",
|
||||||
|
tagline: "Staff, attendance, leave, payroll, branch access, and login credentials.",
|
||||||
|
steps: [
|
||||||
|
"Open “Human Resources” from the sidebar.",
|
||||||
|
"Add a new employee with their name and role (waiter, cashier, manager).",
|
||||||
|
"Record and track each employee’s attendance and leave.",
|
||||||
|
"Enter payroll details to simplify each period’s pay calculation.",
|
||||||
|
"Set each employee’s branch access and login credentials.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Role-based access ensures each employee only sees the sections they’re allowed to.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "branches",
|
||||||
|
icon: Building2,
|
||||||
|
group: "management",
|
||||||
|
tier: "pro",
|
||||||
|
wireframe: "list",
|
||||||
|
fa: {
|
||||||
|
title: "شعب",
|
||||||
|
tagline: "چند شعبه، هر شعبه با ورود مستقل خودش، حذف نرم با امکان بازیابی.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «شعب» شوید.",
|
||||||
|
"روی «افزودن شعبه» بزنید و نام و آدرس شعبه را وارد کنید.",
|
||||||
|
"برای هر شعبه اطلاعات ورود مستقل تعیین کنید تا تیم هر شعبه جداگانه وارد شود.",
|
||||||
|
"بین شعبهها جابهجا شوید تا منو، گزارش و تنظیمات هر کدام را جداگانه ببینید.",
|
||||||
|
"حذف شعبه از نوع «نرم» است؛ یعنی بعداً قابل بازیابی است و دادهها از بین نمیروند.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"قیمت اختصاصی هر شعبه در بخش منو تعریف میشود.",
|
||||||
|
"حذف نرم برای بستن موقت یک شعبه بدون از دست رفتن تاریخچه مفید است.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Branches",
|
||||||
|
tagline: "Multi-location, each branch with its own login, soft-delete with restore.",
|
||||||
|
steps: [
|
||||||
|
"Open “Branches” from the sidebar.",
|
||||||
|
"Press “Add branch” and enter its name and address.",
|
||||||
|
"Set independent login credentials per branch so each team signs in separately.",
|
||||||
|
"Switch between branches to view each one’s menu, reports, and settings separately.",
|
||||||
|
"Branch deletion is “soft” — it can be restored later and no data is lost.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Per-branch pricing is set in the Menu section.",
|
||||||
|
"Soft-delete is handy for temporarily closing a branch without losing its history.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "subscription",
|
||||||
|
icon: CreditCard,
|
||||||
|
group: "management",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "dashboard",
|
||||||
|
fa: {
|
||||||
|
title: "اشتراک و پلن",
|
||||||
|
tagline: "مشاهدهٔ پلن و میزان مصرف، مقایسهٔ پلنها و ارتقا از طریق زرینپال.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «اشتراک و پلن» شوید.",
|
||||||
|
"پلن فعلی و میزان مصرف (مثلاً تعداد شعبه یا آیتم منو) را نسبت به سقف پلن ببینید.",
|
||||||
|
"پلنها را با هم مقایسه کنید تا قابلیتهای هر کدام را بسنجید.",
|
||||||
|
"برای ارتقا روی «ارتقای پلن» بزنید؛ پرداخت از طریق درگاه زرینپال انجام میشود.",
|
||||||
|
"اگر دورهای از پیش پوشش داده شده باشد، ارتقا در صف اعمال قرار میگیرد و خودکار فعال میشود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"وقتی به سقف یک قابلیت نزدیک میشوید، اینجا هشدار مصرف نمایش داده میشود.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Subscription & Plan",
|
||||||
|
tagline: "View your plan and usage, compare plans, and upgrade via ZarinPal.",
|
||||||
|
steps: [
|
||||||
|
"Open “Subscription & Plan” from the sidebar.",
|
||||||
|
"See your current plan and usage (e.g. branch count or menu items) against the plan limits.",
|
||||||
|
"Compare plans to weigh each one’s capabilities.",
|
||||||
|
"To upgrade, press “Upgrade plan”; payment goes through the ZarinPal gateway.",
|
||||||
|
"If a period is already covered, the upgrade is queued and activates automatically.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"When you approach a feature limit, a usage warning shows up here.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "settings",
|
||||||
|
icon: Settings,
|
||||||
|
group: "management",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "form",
|
||||||
|
fa: {
|
||||||
|
title: "تنظیمات",
|
||||||
|
tagline: "پروفایل کافه و آدرس کجا، ظاهر و قالب منوی مهمان، پرینتر، موقعیت، ترمینالها و ترازو.",
|
||||||
|
steps: [
|
||||||
|
"از منوی کناری وارد «تنظیمات» شوید.",
|
||||||
|
"پروفایل کافه را کامل کنید و آدرس (slug) کجا را تعیین کنید — همان نشانی صفحهٔ عمومی شما در کجا.",
|
||||||
|
"از بخش ظاهر، تم و قالب منوی مهمان را انتخاب کنید.",
|
||||||
|
"پرینتر حرارتی و ترمینالهای کارتخوان را تنظیم کنید.",
|
||||||
|
"موقعیت روی نقشه را تعیین کنید و در صورت نیاز اطلاعات سامانهٔ مودیان (ترازو) را وارد کنید.",
|
||||||
|
"کلید «نمایش در کجا» را روشن کنید تا کافه در پلتفرم کشف کجا دیده شود.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"آدرس (slug) کجا را کوتاه و خوانا انتخاب کنید؛ همین در آدرس صفحهٔ عمومی شما میآید.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Settings",
|
||||||
|
tagline: "Café profile + Koja slug, appearance & guest-menu template, printer, location, terminals, and Taraz.",
|
||||||
|
steps: [
|
||||||
|
"Open “Settings” from the sidebar.",
|
||||||
|
"Complete your café profile and set your Koja slug — the address of your public page on Koja.",
|
||||||
|
"From Appearance, choose the theme and the guest-menu template.",
|
||||||
|
"Configure your thermal printer and card-terminal devices.",
|
||||||
|
"Set your location on the map and, if needed, enter the tax-system (Taraz) details.",
|
||||||
|
"Turn on “Show on Koja” so your café appears on the Koja discovery platform.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Pick a short, readable Koja slug — it becomes your public page’s address.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─────────────── public ───────────────
|
||||||
|
{
|
||||||
|
slug: "qr-menu",
|
||||||
|
icon: Smartphone,
|
||||||
|
group: "public",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "phone",
|
||||||
|
fa: {
|
||||||
|
title: "منوی مهمان QR",
|
||||||
|
tagline: "آنچه مشتری پس از اسکن کد QR میز میبیند — بدون نیاز به ورود.",
|
||||||
|
steps: [
|
||||||
|
"مشتری کد QR روی میز را با دوربین گوشی اسکن میکند؛ منو بدون نصب اپ و بدون ورود باز میشود.",
|
||||||
|
"بین دستهبندیها و آیتمها (با تصویر و قیمت) میگردد.",
|
||||||
|
"آیتمها را همراه با یادداشت (مثلاً «بدون یخ») به سبد اضافه میکند.",
|
||||||
|
"روی «ثبت سفارش» میزند؛ سفارش مستقیم به آشپزخانه و صندوق ارسال میشود.",
|
||||||
|
"وضعیت سفارش را پیگیری میکند و در صورت نیاز با دکمهٔ «صدا زدن گارسون» کمک میخواهد.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"چون منو به میز گره خورده، آشپزخانه دقیقاً میداند سفارش برای کدام میز است.",
|
||||||
|
"بدون ورود یا نصب اپ کار میکند؛ تجربهٔ مهمان سریع و بیاصطکاک است.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Guest QR Menu",
|
||||||
|
tagline: "What the customer sees after scanning a table QR — no login required.",
|
||||||
|
steps: [
|
||||||
|
"The customer scans the table QR with their phone camera; the menu opens with no app install and no login.",
|
||||||
|
"They browse categories and items (with images and prices).",
|
||||||
|
"They add items to the cart along with notes (e.g. “no ice”).",
|
||||||
|
"They press “Place order”; it goes straight to the kitchen and the POS.",
|
||||||
|
"They track the order’s status and can press “Call waiter” for help if needed.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"Because the menu is tied to the table, the kitchen knows exactly which table ordered.",
|
||||||
|
"It works with no login or app install — a fast, frictionless guest experience.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "koja",
|
||||||
|
icon: MapPin,
|
||||||
|
group: "public",
|
||||||
|
tier: "free",
|
||||||
|
wireframe: "phone",
|
||||||
|
fa: {
|
||||||
|
title: "کجا (کافهیاب)",
|
||||||
|
tagline: "پلتفرم کشف عمومی با جستجوی هوشمند، فیلتر، پروفایل کافه و مشاور قهوهٔ هوشمصنوعی.",
|
||||||
|
steps: [
|
||||||
|
"در «تنظیمات» پروفایل کشف را کامل و کلید «نمایش در کجا» را روشن کنید.",
|
||||||
|
"کافه شما در koja.meezi.ir به نمایش درمیآید و در جستجو پیدا میشود.",
|
||||||
|
"مشتریها با جستجوی هوشمند هوشمصنوعی و فیلترها (مثلاً نزدیکترین، باز، نوع) کافهها را پیدا میکنند.",
|
||||||
|
"هر کافه یک پروفایل عمومی با تصاویر، منو، نظرات و موقعیت دارد.",
|
||||||
|
"مشاور قهوهٔ هوشمصنوعی به مهمان کمک میکند بر اساس سلیقهاش انتخاب کند.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"تصاویر باکیفیت و پروفایل کامل، شانس دیدهشدن کافه در کجا را بالا میبرد.",
|
||||||
|
"نظرات و پاسخ شما به آنها روی پروفایل کجا برای مشتریان جدید قابل دیدن است.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Koja (Café Finder)",
|
||||||
|
tagline: "Public discovery with AI smart search, filters, café profiles, and an AI coffee advisor.",
|
||||||
|
steps: [
|
||||||
|
"In “Settings”, complete your discovery profile and turn on “Show on Koja”.",
|
||||||
|
"Your café appears on koja.meezi.ir and becomes findable in search.",
|
||||||
|
"Customers find cafés with AI smart search and filters (e.g. nearest, open now, type).",
|
||||||
|
"Each café has a public profile with images, menu, reviews, and location.",
|
||||||
|
"The AI coffee advisor helps guests choose based on their taste.",
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
"High-quality images and a complete profile boost your café’s visibility on Koja.",
|
||||||
|
"Reviews and your replies to them are visible on the Koja profile for new customers.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getFeatureBySlug(slug: string): GuideFeature | undefined {
|
||||||
|
return GUIDE_FEATURES.find((f) => f.slug === slug);
|
||||||
|
}
|
||||||
@@ -2,24 +2,14 @@ import type { Metadata } from "next";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { Navbar } from "@/components/layout/navbar";
|
import { Navbar } from "@/components/layout/navbar";
|
||||||
import { Footer } from "@/components/layout/footer";
|
import { Footer } from "@/components/layout/footer";
|
||||||
|
import { ArrowLeft, ArrowRight, Search, LifeBuoy } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
BookOpen,
|
GUIDE_FEATURES,
|
||||||
QrCode,
|
GROUP_ORDER,
|
||||||
LayoutGrid,
|
GROUP_TITLES,
|
||||||
ChefHat,
|
TIER_LABELS,
|
||||||
BarChart3,
|
type GuideGroup,
|
||||||
Package,
|
} from "./guide-data";
|
||||||
WifiOff,
|
|
||||||
MapPin,
|
|
||||||
UserCog,
|
|
||||||
Building2,
|
|
||||||
Printer,
|
|
||||||
Smartphone,
|
|
||||||
ArrowLeft,
|
|
||||||
ArrowRight,
|
|
||||||
Search,
|
|
||||||
LifeBuoy,
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
@@ -44,19 +34,6 @@ const fa = {
|
|||||||
{ step: "۴", title: "آموزش تیم", desc: "داشبورد را به کارکنان نشان بده — در چند ساعت یاد میگیرند." },
|
{ step: "۴", title: "آموزش تیم", desc: "داشبورد را به کارکنان نشان بده — در چند ساعت یاد میگیرند." },
|
||||||
],
|
],
|
||||||
modulesTitle: "راهنمای ماژولها",
|
modulesTitle: "راهنمای ماژولها",
|
||||||
modules: [
|
|
||||||
{ icon: QrCode, title: "منوی دیجیتال QR", desc: "ساخت و مدیریت منوی آنلاین، دستهبندی، تصاویر و قیمتها." },
|
|
||||||
{ icon: LayoutGrid, title: "سیستم POS", desc: "ثبت سفارش از صندوق، پرداختها و مدیریت میزها." },
|
|
||||||
{ icon: ChefHat, title: "آشپزخانه (KDS)", desc: "نمایش سفارشها در آشپزخانه و تایید آمادهسازی." },
|
|
||||||
{ icon: WifiOff, title: "حالت آفلاین", desc: "کار بدون اینترنت و همگامسازی خودکار سفارشها هنگام اتصال مجدد." },
|
|
||||||
{ icon: MapPin, title: "نمایش در کجا", desc: "دیدهشدن کافه در پلتفرم کشف «کجا» و جذب مشتری جدید." },
|
|
||||||
{ icon: BarChart3, title: "گزارشها", desc: "گزارش فروش روزانه، ماهانه و تحلیل پرفروشها." },
|
|
||||||
{ icon: Package, title: "انبار", desc: "کنترل موجودی مواد اولیه و هشدار کمبود." },
|
|
||||||
{ icon: UserCog, title: "منابع انسانی", desc: "حضور غیاب، شیفتبندی و سطح دسترسی کارکنان." },
|
|
||||||
{ icon: Building2, title: "چند شعبه", desc: "مدیریت تمام شعبهها از یک داشبورد مرکزی." },
|
|
||||||
{ icon: Printer, title: "پرینتر", desc: "اتصال پرینتر حرارتی، رسید مشتری و برگه آشپزخانه." },
|
|
||||||
{ icon: Smartphone, title: "اپ موبایل", desc: "اپ گارسون برای دریافت سفارش و مدیریت میزها." },
|
|
||||||
],
|
|
||||||
supportTitle: "به کمک نیاز داری؟",
|
supportTitle: "به کمک نیاز داری؟",
|
||||||
supportDesc: "تیم پشتیبانی ما آماده است. از طریق داشبورد یا ایمیل با ما در ارتباط باش.",
|
supportDesc: "تیم پشتیبانی ما آماده است. از طریق داشبورد یا ایمیل با ما در ارتباط باش.",
|
||||||
supportBtn: "تماس با پشتیبانی",
|
supportBtn: "تماس با پشتیبانی",
|
||||||
@@ -76,19 +53,6 @@ const en = {
|
|||||||
{ step: "4", title: "Train your team", desc: "Show staff the dashboard — they'll get comfortable in a few hours." },
|
{ step: "4", title: "Train your team", desc: "Show staff the dashboard — they'll get comfortable in a few hours." },
|
||||||
],
|
],
|
||||||
modulesTitle: "Module Guides",
|
modulesTitle: "Module Guides",
|
||||||
modules: [
|
|
||||||
{ icon: QrCode, title: "QR Digital Menu", desc: "Create and manage your online menu, categories, images, and prices." },
|
|
||||||
{ icon: LayoutGrid, title: "POS System", desc: "Register orders from the counter, handle payments, and manage tables." },
|
|
||||||
{ icon: ChefHat, title: "Kitchen (KDS)", desc: "Display orders in the kitchen and confirm preparation." },
|
|
||||||
{ icon: WifiOff, title: "Offline Mode", desc: "Keep working without internet; orders sync automatically when you reconnect." },
|
|
||||||
{ icon: MapPin, title: "Koja Discovery", desc: "Get your café seen on the Koja discovery platform and attract new customers." },
|
|
||||||
{ icon: BarChart3, title: "Reports", desc: "Daily and monthly sales reports and best-seller analysis." },
|
|
||||||
{ icon: Package, title: "Inventory", desc: "Track ingredient stock levels and low-stock alerts." },
|
|
||||||
{ icon: UserCog, title: "HR", desc: "Attendance, shift scheduling, and staff access levels." },
|
|
||||||
{ icon: Building2, title: "Multi-Branch", desc: "Manage all your branches from a single central dashboard." },
|
|
||||||
{ icon: Printer, title: "Printer", desc: "Connect a thermal printer, customer receipts, and kitchen slips." },
|
|
||||||
{ icon: Smartphone, title: "Mobile App", desc: "Waiter app for receiving orders and managing tables." },
|
|
||||||
],
|
|
||||||
supportTitle: "Need help?",
|
supportTitle: "Need help?",
|
||||||
supportDesc: "Our support team is ready. Reach us through the dashboard or by email.",
|
supportDesc: "Our support team is ready. Reach us through the dashboard or by email.",
|
||||||
supportBtn: "Contact Support",
|
supportBtn: "Contact Support",
|
||||||
@@ -101,10 +65,19 @@ export default async function DocsPage({
|
|||||||
params: Promise<{ locale: string }>;
|
params: Promise<{ locale: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
const c = locale === "fa" ? fa : en;
|
const isEn = locale === "en";
|
||||||
const Arrow = locale === "fa" ? ArrowLeft : ArrowRight;
|
const c = isEn ? en : fa;
|
||||||
|
const Arrow = isEn ? ArrowRight : ArrowLeft;
|
||||||
const base = `/${locale}`;
|
const base = `/${locale}`;
|
||||||
|
|
||||||
|
// Group features by their group, preserving GROUP_ORDER.
|
||||||
|
const grouped: { group: GuideGroup; items: typeof GUIDE_FEATURES }[] = GROUP_ORDER.map(
|
||||||
|
(group) => ({
|
||||||
|
group,
|
||||||
|
items: GUIDE_FEATURES.filter((f) => f.group === group),
|
||||||
|
})
|
||||||
|
).filter((g) => g.items.length > 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@@ -149,24 +122,42 @@ export default async function DocsPage({
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Modules */}
|
{/* Modules, grouped */}
|
||||||
<section className="mb-20">
|
<section className="mb-20">
|
||||||
<h2 className="mb-8 text-2xl font-extrabold text-gray-900">{c.modulesTitle}</h2>
|
<h2 className="mb-8 text-2xl font-extrabold text-gray-900">{c.modulesTitle}</h2>
|
||||||
|
<div className="space-y-12">
|
||||||
|
{grouped.map(({ group, items }) => (
|
||||||
|
<div key={group}>
|
||||||
|
<h3 className="mb-4 text-sm font-bold uppercase tracking-wider text-brand-700">
|
||||||
|
{isEn ? GROUP_TITLES[group].en : GROUP_TITLES[group].fa}
|
||||||
|
</h3>
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{c.modules.map(({ icon: Icon, title, desc }) => (
|
{items.map((f) => {
|
||||||
<button
|
const Icon = f.icon;
|
||||||
key={title}
|
const content = isEn ? f.en : f.fa;
|
||||||
type="button"
|
return (
|
||||||
className="group flex cursor-pointer items-start gap-4 rounded-2xl border border-gray-100 bg-white p-5 shadow-sm text-start transition-all hover:border-brand-200 hover:shadow-md"
|
<a
|
||||||
|
key={f.slug}
|
||||||
|
href={`${base}/docs/${f.slug}`}
|
||||||
|
className="group relative flex items-start gap-4 rounded-2xl border border-gray-100 bg-white p-5 text-start shadow-sm transition-all hover:border-brand-200 hover:shadow-md"
|
||||||
>
|
>
|
||||||
|
{f.tier && f.tier !== "free" && (
|
||||||
|
<span className="absolute end-4 top-4 rounded-full bg-brand-50 px-2 py-0.5 text-[10px] font-semibold text-brand-700">
|
||||||
|
{isEn ? TIER_LABELS[f.tier].en : TIER_LABELS[f.tier].fa}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-50 transition-colors group-hover:bg-brand-100">
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-50 transition-colors group-hover:bg-brand-100">
|
||||||
<Icon className="h-5 w-5 text-brand-700" />
|
<Icon className="h-5 w-5 text-brand-700" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<h3 className="mb-1 font-semibold text-gray-900">{title}</h3>
|
<h4 className="mb-1 font-semibold text-gray-900">{content.title}</h4>
|
||||||
<p className="text-sm text-gray-500">{desc}</p>
|
<p className="text-sm leading-relaxed text-gray-500">{content.tagline}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import type { WireframeVariant } from "./guide-data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal line-art wireframe mockups for the knowledge-base feature pages.
|
||||||
|
* Muted gray boxes with brand accents, ~16:10 aspect, RTL-aware via logical
|
||||||
|
* properties (start-/end-/ps-/pe-) so the layout mirrors in Persian.
|
||||||
|
* Pure presentational markup — safe to render on the server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const FRAME =
|
||||||
|
"relative w-full overflow-hidden rounded-2xl border border-gray-200 bg-gray-50 p-4 shadow-sm";
|
||||||
|
const ASPECT = "aspect-[16/10]";
|
||||||
|
|
||||||
|
function Bar({ w = "w-full", h = "h-2.5", tone = "bg-gray-200" }: { w?: string; h?: string; tone?: string }) {
|
||||||
|
return <div className={`${w} ${h} rounded-full ${tone}`} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Board() {
|
||||||
|
return (
|
||||||
|
<div className={FRAME}>
|
||||||
|
<div className={`${ASPECT} flex flex-col gap-3`}>
|
||||||
|
{/* header bar */}
|
||||||
|
<div className="flex items-center justify-between rounded-lg bg-white px-3 py-2 shadow-sm">
|
||||||
|
<div className="h-2.5 w-20 rounded-full bg-brand-700" />
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<div className="h-4 w-10 rounded-md bg-brand-100" />
|
||||||
|
<div className="h-4 w-10 rounded-md bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* grid of cards */}
|
||||||
|
<div className="grid flex-1 grid-cols-3 grid-rows-2 gap-2.5">
|
||||||
|
{[0, 1, 2, 3, 4, 5].map((i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`flex flex-col justify-between rounded-lg border bg-white p-2 ${
|
||||||
|
i % 3 === 0 ? "border-brand-200" : "border-gray-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="h-2 w-6 rounded-full bg-gray-300" />
|
||||||
|
<div
|
||||||
|
className={`h-2 w-2 rounded-full ${
|
||||||
|
i % 3 === 0 ? "bg-brand-500" : i % 2 === 0 ? "bg-amber-300" : "bg-gray-200"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Bar w="w-3/4" h="h-1.5" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Order() {
|
||||||
|
return (
|
||||||
|
<div className={FRAME}>
|
||||||
|
<div className={`${ASPECT} flex gap-3`}>
|
||||||
|
{/* category rail (start side = right in RTL) */}
|
||||||
|
<div className="flex w-12 flex-col gap-2">
|
||||||
|
{[0, 1, 2, 3].map((i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`h-9 rounded-lg ${i === 0 ? "bg-brand-700" : "bg-white border border-gray-200"}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* item grid */}
|
||||||
|
<div className="grid flex-1 grid-cols-3 grid-rows-3 gap-2">
|
||||||
|
{Array.from({ length: 9 }).map((_, i) => (
|
||||||
|
<div key={i} className="flex flex-col gap-1 rounded-lg border border-gray-200 bg-white p-1.5">
|
||||||
|
<div className="h-5 flex-1 rounded bg-gray-100" />
|
||||||
|
<Bar w="w-3/5" h="h-1.5" tone="bg-brand-200" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* ticket / total */}
|
||||||
|
<div className="flex w-20 flex-col gap-2 rounded-lg bg-white p-2 shadow-sm">
|
||||||
|
<div className="h-2 w-12 rounded-full bg-gray-300" />
|
||||||
|
<Bar w="w-full" h="h-1.5" />
|
||||||
|
<Bar w="w-4/5" h="h-1.5" />
|
||||||
|
<Bar w="w-3/5" h="h-1.5" />
|
||||||
|
<div className="mt-auto h-6 rounded-md bg-brand-700" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Menu() {
|
||||||
|
return (
|
||||||
|
<div className={FRAME}>
|
||||||
|
<div className={`${ASPECT} flex gap-3`}>
|
||||||
|
{/* category sidebar */}
|
||||||
|
<div className="flex w-16 flex-col gap-2">
|
||||||
|
{[0, 1, 2, 3, 4].map((i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`h-2.5 rounded-full ${i === 1 ? "bg-brand-700 w-full" : "bg-gray-200 w-4/5"}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* item cards w/ image box + price line */}
|
||||||
|
<div className="grid flex-1 grid-cols-2 gap-2.5">
|
||||||
|
{[0, 1, 2, 3].map((i) => (
|
||||||
|
<div key={i} className="flex gap-2 rounded-lg border border-gray-200 bg-white p-2">
|
||||||
|
<div className="h-10 w-10 shrink-0 rounded-md bg-gray-100" />
|
||||||
|
<div className="flex flex-1 flex-col justify-center gap-1.5">
|
||||||
|
<Bar w="w-full" h="h-1.5" />
|
||||||
|
<Bar w="w-1/2" h="h-1.5" tone="bg-brand-300" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function List() {
|
||||||
|
return (
|
||||||
|
<div className={FRAME}>
|
||||||
|
<div className={`${ASPECT} flex flex-col gap-3`}>
|
||||||
|
{/* search bar */}
|
||||||
|
<div className="flex items-center gap-2 rounded-lg bg-white px-3 py-2 shadow-sm">
|
||||||
|
<div className="h-3 w-3 rounded-full border-2 border-gray-300" />
|
||||||
|
<Bar w="w-1/3" h="h-2" />
|
||||||
|
<div className="ms-auto h-5 w-14 rounded-md bg-brand-700" />
|
||||||
|
</div>
|
||||||
|
{/* rows */}
|
||||||
|
<div className="flex flex-1 flex-col gap-2">
|
||||||
|
{[0, 1, 2, 3].map((i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="flex items-center gap-3 rounded-lg border border-gray-200 bg-white px-3 py-2"
|
||||||
|
>
|
||||||
|
<div className="h-6 w-6 rounded-full bg-brand-100" />
|
||||||
|
<div className="flex flex-1 flex-col gap-1.5">
|
||||||
|
<Bar w="w-1/2" h="h-1.5" />
|
||||||
|
<Bar w="w-1/3" h="h-1.5" />
|
||||||
|
</div>
|
||||||
|
<div className="h-2 w-10 rounded-full bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Dashboard() {
|
||||||
|
return (
|
||||||
|
<div className={FRAME}>
|
||||||
|
<div className={`${ASPECT} flex flex-col gap-3`}>
|
||||||
|
{/* KPI cards */}
|
||||||
|
<div className="grid grid-cols-3 gap-2.5">
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<div key={i} className="rounded-lg border border-gray-200 bg-white p-2">
|
||||||
|
<Bar w="w-2/3" h="h-1.5" />
|
||||||
|
<div className={`mt-2 h-3 w-1/2 rounded ${i === 0 ? "bg-brand-700" : "bg-gray-300"}`} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* chart block */}
|
||||||
|
<div className="flex flex-1 items-end gap-1.5 rounded-lg border border-gray-200 bg-white p-2.5">
|
||||||
|
{[55, 70, 45, 85, 60, 95, 75].map((h, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="flex-1 rounded-t"
|
||||||
|
style={{ height: `${h}%`, background: `rgba(15,110,86,${0.25 + i * 0.09})` }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* table */}
|
||||||
|
<div className="flex flex-col gap-1.5 rounded-lg border border-gray-200 bg-white p-2">
|
||||||
|
{[0, 1].map((i) => (
|
||||||
|
<div key={i} className="flex items-center gap-2">
|
||||||
|
<Bar w="w-1/3" h="h-1.5" />
|
||||||
|
<Bar w="w-1/4" h="h-1.5" />
|
||||||
|
<div className="ms-auto h-1.5 w-10 rounded-full bg-brand-200" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Form() {
|
||||||
|
return (
|
||||||
|
<div className={FRAME}>
|
||||||
|
<div className={`${ASPECT} flex flex-col gap-3 rounded-lg bg-white p-4 shadow-sm`}>
|
||||||
|
<div className="h-2.5 w-24 rounded-full bg-brand-700" />
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<div key={i} className="flex flex-col gap-1.5">
|
||||||
|
<Bar w="w-20" h="h-1.5" />
|
||||||
|
<div className="h-7 w-full rounded-md border border-gray-200 bg-gray-50" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="mt-auto flex justify-end gap-2">
|
||||||
|
<div className="h-7 w-16 rounded-md border border-gray-200" />
|
||||||
|
<div className="h-7 w-20 rounded-md bg-brand-700" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Phone() {
|
||||||
|
return (
|
||||||
|
<div className={FRAME}>
|
||||||
|
<div className={`${ASPECT} flex items-center justify-center`}>
|
||||||
|
<div className="flex h-full w-40 flex-col gap-2 rounded-[1.5rem] border-4 border-gray-300 bg-white p-2.5">
|
||||||
|
{/* notch */}
|
||||||
|
<div className="mx-auto h-1.5 w-10 rounded-full bg-gray-300" />
|
||||||
|
{/* header */}
|
||||||
|
<div className="flex items-center gap-2 rounded-lg bg-brand-50 p-1.5">
|
||||||
|
<div className="h-5 w-5 rounded-md bg-brand-700" />
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<Bar w="w-14" h="h-1.5" />
|
||||||
|
<Bar w="w-8" h="h-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* menu cards */}
|
||||||
|
<div className="grid flex-1 grid-cols-2 gap-1.5">
|
||||||
|
{[0, 1, 2, 3].map((i) => (
|
||||||
|
<div key={i} className="flex flex-col gap-1 rounded-md border border-gray-200 p-1">
|
||||||
|
<div className="h-6 flex-1 rounded bg-gray-100" />
|
||||||
|
<Bar w="w-full" h="h-1" />
|
||||||
|
<Bar w="w-1/2" h="h-1" tone="bg-brand-300" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* bottom bar */}
|
||||||
|
<div className="h-6 rounded-lg bg-brand-700" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VARIANTS: Record<WireframeVariant, () => React.JSX.Element> = {
|
||||||
|
board: Board,
|
||||||
|
order: Order,
|
||||||
|
menu: Menu,
|
||||||
|
list: List,
|
||||||
|
dashboard: Dashboard,
|
||||||
|
form: Form,
|
||||||
|
phone: Phone,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Wireframe({ variant }: { variant: WireframeVariant }) {
|
||||||
|
const Component = VARIANTS[variant];
|
||||||
|
return <Component />;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user