create Markdown

This commit is contained in:
2026-07-16 17:53:19 +03:30
parent befd53dc5b
commit 7810355783
9 changed files with 1017 additions and 68 deletions
+180
View File
@@ -0,0 +1,180 @@
راهنمای کاربری منتایم
========================
این فایل برای کپی مستقیم در Notion، Google Docs یا Microsoft Word آماده شده است.
سرفصل‌ها را با Heading 1 / Heading 2 قالب‌بندی کنید.
------------------------------------------------------------
منتایم چیست؟
منتایم پلتفرمی برای کشف کسب‌وکارهای محلی و رزرو آنلاین نوبت است (کارواش، آرایشگاه و خدمات مشابه).
نقش مشتری: پیدا کردن کسب‌وکار، رزرو، پرداخت، امتیاز
نقش صاحب کسب‌وکار: مدیریت خدمات، ساعات، رزروها و صفحه کسب‌وکار
------------------------------------------------------------
۱) شروع کار و ورود
ورود با کد یکبارمصرف (پیشنهادی)
• منو → ورود
• حالت «کد یکبارمصرف» را انتخاب کنید
• شماره موبایل را وارد کنید (مثال: 09121234567)
• دریافت کد تأیید
• کد ۴ رقمی پیامک‌شده را وارد کنید
ورود با رمز عبور
• حالت «رمز عبور» را انتخاب کنید
• موبایل و رمز را وارد کنید → ورود
نکته: اولین بار با کد یکبارمصرف وارد شوید و بعداً از پروفایل رمز بسازید.
می‌توانید با «ادامه بدون ورود» فقط مرور کنید؛ برای رزرو باید وارد شوید.
------------------------------------------------------------
۲) کشف کسب‌وکارها
• از «خانه» لیست کسب‌وکارها را ببینید
• در صورت نیاز جستجو یا فیلتر دسته را استفاده کنید
• روی کارت بزنید تا صفحه کسب‌وکار باز شود
روی کارت معمولاً می‌بینید: تصویر کاور، نام، امتیاز، زیرعنوان، دسته، آدرس
------------------------------------------------------------
۳) صفحه کسب‌وکار و رزرو
در صفحه کسب‌وکار:
• نام، توضیحات، گالری، نکات برجسته، تخفیف (در صورت فعال بودن)، خدمات
• دکمه «رزرو نوبت»
مراحل رزرو:
1. کلاس خدمات (فقط اگر کلاس‌بندی فعال باشد)
2. انتخاب خدمات
3. تاریخ و ساعت
4. اطلاعات و یادداشت اختیاری
5. تایید نهایی → ثبت نهایی رزرو
بعد از ثبت:
• اگر تأیید خودکار فعال باشد → آماده پرداخت آنلاین
• اگر تأیید دستی باشد → در انتظار تأیید صاحب کسب‌وکار
------------------------------------------------------------
۴) پرداخت
• مسیر: رزروها → پرداخت (یا لینک پرداخت)
• مبلغ و جزئیات را بررسی کنید
• پرداخت آنلاین را انجام دهید
• تخفیف درصدی معمولاً خودکار اعمال می‌شود
• اگر نوبت رایگان باشگاه مشتریان باشد، مبلغ رایگان نشان داده می‌شود
------------------------------------------------------------
۵) رزروهای مشتری
از منو «رزروها»:
• در انتظار تأیید
• منتظر پرداخت → دکمه پرداخت
• تأیید شده
• انجام شده
• لغو شده → امکان لغو در وضعیت‌های مجاز
اگر خالی باشد: «هنوز رزروی ثبت نکردید.»
------------------------------------------------------------
۶) امتیاز و حسن انجام کار
بعد از «انجام شد» توسط صاحب کسب‌وکار:
1. امتیاز ۱ تا ۵ ستاره
2. اگر حسن انجام کار فعال باشد: تمایل ندارم / تمایل دارم (+ مبلغ)
3. در غیر این صورت فقط ثبت امتیاز
------------------------------------------------------------
۷) پروفایل
• موبایل و نقش
• تنظیم / تغییر رمز عبور
• مشتری: ثبت کسب‌وکار من
• صاحب: ورود به پنل کسب‌وکار
• خروج از حساب
------------------------------------------------------------
۸) ثبت کسب‌وکار
• وارد شوید و «ثبت کسب‌وکار» را بزنید
• نام، دسته، توضیحات، آدرس، تلفن را پر کنید
• ارسال درخواست
• پس از تأیید ادمین، پنل صاحب کسب‌وکار فعال می‌شود
------------------------------------------------------------
۹) پنل صاحب کسب‌وکار (کسب‌وکار من)
اطلاعات: نام، زیرعنوان، توضیحات، تلفن، آدرس، نکات برجسته لندینگ (حداکثر ۳ مورد)
لوگو و تصاویر: لوگوی روشن/تاریک + گالری و کاور
تنظیمات نوبت‌دهی:
• پذیرش همزمان
• گام نوبت‌دهی (دقیقه)
• تأیید خودکار درخواست‌ها
• حسن انجام کار
• تخفیف درصدی (عنوان، درصد، کد نمایشی، اولین رزرو یا همه رزروها)
ساعات پذیرش آنلاین: بازه‌های قابل رزرو در روزهای هفته
خدمات:
• افزودن خدمت و قیمت/مدت
• خدمت رایگان دوره‌ای (اختیاری)
• کلاس‌بندی خدمات (اختیاری)
• خدمت سفارشی → پس از تأیید ادمین
------------------------------------------------------------
۱۰) رزروهای صاحب کسب‌وکار
تب‌ها: در انتظار تأیید | تأیید شده | لغو شده
اقدامات:
• تأیید
• انجام شد (باعث باز شدن امتیاز برای مشتری می‌شود)
• لغو (با علت لغو)
نکته: صاحب کسب‌وکار کسب‌وکار خودش را در لیست کشف نمی‌بیند و برای خودش رزرو نمی‌کند.
------------------------------------------------------------
۱۱) منوی پایین موبایل
خانه | رزروها | ثبت کسب‌وکار یا کسب‌وکار من | ورود یا خروج
------------------------------------------------------------
۱۲) سؤالات متداول
س: بدون ورود می‌توانم رزرو کنم؟
ج: خیر. مرور آزاد است؛ رزرو نیاز به ورود دارد.
س: کد تخفیف را کجا وارد کنم؟
ج: معمولاً لازم نیست؛ تخفیف فعال خودکار اعمال می‌شود.
س: چرا کسب‌وکار خودم را در لیست نمی‌بینم؟
ج: عمدی است. از «کسب‌وکار من» مدیریت کنید.
س: خدمت سفارشی کی دیده می‌شود؟
ج: بعد از تأیید ادمین.
س: چطور رمز بسازم؟
ج: ورود با OTP → پروفایل → تنظیم رمز عبور.
------------------------------------------------------------
نسخه آنلاین داخل اپ: /guide
نسخه Markdown: docs/user-guide.md
منتایم — رزرو آنلاین خدمات محلی
+284
View File
@@ -0,0 +1,284 @@
# راهنمای کاربری منتایم
راهنمای کامل استفاده از پلتفرم رزرو آنلاین خدمات محلی **منتایم** برای مشتریان و صاحبان کسب‌وکار.
> این فایل نسخه Markdown است. نسخه آنلاین در مسیر `/guide` و نسخه مناسب کپی در Notion/Docs در فایل `user-guide-notion.txt` قرار دارد.
---
## فهرست مطالب
1. [منتایم چیست؟](#۱-منتایم-چیست)
2. [شروع کار و ورود](#۲-شروع-کار-و-ورود)
3. [کشف کسب‌وکارها](#۳-کشف-کسب‌وکارها)
4. [صفحه کسب‌وکار و رزرو نوبت](#۴-صفحه-کسب‌وکار-و-رزرو-نوبت)
5. [پرداخت](#۵-پرداخت)
6. [مدیریت رزروها (مشتری)](#۶-مدیریت-رزروها-مشتری)
7. [امتیازدهی و حسن انجام کار](#۷-امتیازدهی-و-حسن-انجام-کار)
8. [پروفایل کاربری](#۸-پروفایل-کاربری)
9. [ثبت کسب‌وکار](#۹-ثبت-کسب‌وکار)
10. [پنل صاحب کسب‌وکار](#۱۰-پنل-صاحب-کسب‌وکار)
11. [مدیریت رزروها (صاحب کسب‌وکار)](#۱۱-مدیریت-رزروها-صاحب-کسب‌وکار)
12. [منوی پایین موبایل](#۱۲-منوی-پایین-موبایل)
13. [سؤالات متداول](#۱۳-سؤالات-متداول)
---
## ۱. منتایم چیست؟
منتایم پلتفرمی برای **کشف کسب‌وکارهای محلی** و **رزرو آنلاین نوبت** است؛ مثلاً کارواش، آرایشگاه و خدمات مشابه.
| نقش | کار اصلی |
|-----|----------|
| **مشتری** | پیدا کردن کسب‌وکار، رزرو، پرداخت، امتیاز |
| **صاحب کسب‌وکار** | مدیریت خدمات، ساعات، رزروها و تنظیمات لندینگ |
---
## ۲. شروع کار و ورود
### ورود با کد یکبارمصرف (پیشنهادی)
1. از منو **ورود** را بزنید (یا مسیر `/login`).
2. حالت **کد یکبارمصرف** را انتخاب کنید.
3. **شماره موبایل** خود را وارد کنید (مثال: `09121234567`).
4. **دریافت کد تأیید** را بزنید.
5. در صفحه **کد تأیید**، کد ۴ رقمی پیامک‌شده را وارد کنید.
### ورود با رمز عبور
1. در صفحه ورود، حالت **رمز عبور** را انتخاب کنید.
2. موبایل و رمز را وارد کنید و **ورود** را بزنید.
> **نکته:** اگر اولین بار است، با کد یکبارمصرف وارد شوید و بعداً از پروفایل رمز بسازید.
### ادامه بدون ورود
می‌توانید با **ادامه بدون ورود** کسب‌وکارها را ببینید؛ برای ثبت رزرو باید وارد شوید.
---
## ۳. کشف کسب‌وکارها
از **خانه** (`/app`):
1. لیست کسب‌وکارهای فعال را ببینید.
2. در صورت نیاز، با جستجو یا فیلتر **دسته** محدود کنید.
3. روی کارت کسب‌وکار بزنید تا صفحه آن باز شود.
### ظاهر کارت‌ها
| عرض صفحه | چیدمان |
|----------|--------|
| کمتر از ۴۲۵px | لیست افقی |
| ۴۲۵px به بالا | کارت با تصویر کاور (۲ تا ۵ ستون بسته به عرض) |
روی کارت معمولاً می‌بینید: تصویر کاور، نام، امتیاز، زیرعنوان، دسته و آدرس.
---
## ۴. صفحه کسب‌وکار و رزرو نوبت
### صفحه لندینگ
در صفحه کسب‌وکار می‌توانید ببینید:
- نام، زیرعنوان و توضیحات
- گالری تصاویر
- نکات برجسته (مثلاً رضایت مشتریان، رزرو آنلاین آسان)
- بنر تخفیف (در صورت فعال بودن)
- لیست خدمات
دکمه اصلی: **رزرو نوبت**
> اگر کسب‌وکار غیرفعال باشد پیام **این کسب‌وکار فعلاً غیرفعال است.** نمایش داده می‌شود.
### مراحل رزرو (`/app/businesses/:id/book`)
بسته به تنظیمات کسب‌وکار، مراحل ممکن است شامل این‌ها باشد:
1. **کلاس خدمات** (فقط اگر کلاس‌بندی فعال باشد؛ مثلاً اندازه خودرو)
2. **انتخاب خدمات** (می‌توانید چند خدمت انتخاب کنید؛ دوباره زدن = لغو انتخاب)
3. **تاریخ و ساعت**
4. **اطلاعات و پرداخت** — یادداشت اختیاری
5. **تایید نهایی** → **ثبت نهایی رزرو**
اگر بین راه وارد نباشید، به ورود هدایت می‌شوید و بعد به همین مسیر برمی‌گردید.
### بعد از ثبت رزرو
| حالت کسب‌وکار | نتیجه |
|---------------|--------|
| **تأیید خودکار** فعال | رزرو تأیید می‌شود و آماده **پرداخت آنلاین** است |
| تأیید دستی | وضعیت **در انتظار تأیید**؛ پس از تأیید صاحب، لینک پرداخت می‌آید |
---
## ۵. پرداخت
مسیر: `/app/bookings/pay`
1. مبلغ، کسب‌وکار و تاریخ را بررسی کنید.
2. **پرداخت آنلاین** را بزنید.
3. پس از موفقیت به لیست رزروها برگردید.
اگر خدمت با باشگاه مشتریان **رایگان** شده باشد، مبلغ رایگان نمایش داده می‌شود.
تخفیف درصدی (در صورت فعال بودن) معمولاً **خودکار** اعمال می‌شود و نیازی به وارد کردن کد نیست.
---
## ۶. مدیریت رزروها (مشتری)
از منو **رزروها** (`/app/bookings`):
| وضعیت | معنی تقریبی |
|--------|-------------|
| در انتظار تأیید | منتظر تأیید صاحب کسب‌وکار |
| منتظر پرداخت | باید پرداخت کنید |
| تأیید شده | پرداخت شده / تأیید نهایی |
| انجام شده | خدمت انجام شده |
| لغو شده | لغو شده |
**اقدامات مشتری:**
- **پرداخت** (وقتی منتظر پرداخت است)
- **لغو** (در وضعیت‌های مجاز)
اگر رزروی نباشد: **هنوز رزروی ثبت نکردید.**
---
## ۷. امتیازدهی و حسن انجام کار
بعد از اینکه صاحب کسب‌وکار رزرو را **انجام شد** علامت بزند:
1. پنجره **امتیاز** (۱ تا ۵ ستاره) باز می‌شود.
2. اگر **حسن انجام کار** فعال باشد:
- **تمایل ندارم** → فقط امتیاز ثبت می‌شود
- **تمایل دارم** → مبلغ قدردانی را وارد و پرداخت می‌کنید
3. اگر حسن انجام کار غیرفعال باشد، فقط **ثبت امتیاز** دارید.
---
## ۸. پروفایل کاربری
از آیکون پروفایل در هدر:
- مشاهده موبایل و نقش
- **تنظیم / تغییر رمز عبور**
- مشتری‌ها: لینک **ثبت کسب‌وکار من**
- صاحبان: **ورود به پنل کسب‌وکار**
- **خروج از حساب**
---
## ۹. ثبت کسب‌وکار
مسیر: `/app/register-business` (باید مشتری باشید و وارد شده باشید)
1. **نام کسب‌وکار**، **دسته‌بندی**، توضیحات، آدرس و تلفن را پر کنید.
2. **ارسال درخواست** را بزنید.
3. پس از بررسی ادمین، در صورت تأیید، نقش شما به صاحب کسب‌وکار تبدیل می‌شود و پنل فعال می‌شود.
> اگر از قبل کسب‌وکار داشته باشید یا درخواست در انتظار بررسی داشته باشید، امکان ارسال مجدد نیست.
---
## ۱۰. پنل صاحب کسب‌وکار
مسیر: `/app/owner` — عنوان منو: **کسب‌وکار من**
### اطلاعات کسب‌وکار
نام، زیرعنوان، توضیحات، تلفن، آدرس و **نکات برجسته لندینگ** (حداکثر ۳ مورد از لیست عمومی).
### لوگو و تصاویر
- لوگوی **حالت روشن** و **حالت تاریک** (اختیاری؛ اگر یکی باشد برای هر دو استفاده می‌شود)
- گالری تصاویر + انتخاب **کاور**
### تنظیمات نوبت‌دهی
- **پذیرش همزمان**
- **گام نوبت‌دهی (دقیقه)**
- **تأیید خودکار درخواست‌ها**
- **حسن انجام کار**
- **تخفیف درصدی** (عنوان، درصد، کد نمایشی، فقط اولین رزرو یا همه رزروها)
### ساعات پذیرش آنلاین
تعیین بازه‌های قابل رزرو برای روزهای هفته (همه روزها یکسان یا جداگانه).
### خدمات و قیمت‌ها
- افزودن خدمت از قالب‌ها یا پیشنهاد **خدمت سفارشی** (نیاز به تأیید ادمین)
- قیمت و مدت
- اختیاری: **خدمت رایگان دوره‌ای** (هر N بار یک‌بار رایگان)
- اختیاری: **کلاس‌بندی خدمات**
---
## ۱۱. مدیریت رزروها (صاحب کسب‌وکار)
در **رزروها** تب‌ها:
- **در انتظار تأیید**
- **تأیید شده**
- **لغو شده**
اقدامات:
| دکمه | کار |
|------|-----|
| **تأیید** | تأیید درخواست مشتری |
| **انجام شد** | پایان خدمت (امتیاز برای مشتری باز می‌شود) |
| **لغو** | لغو با وارد کردن **علت لغو** |
> صاحب کسب‌وکار کسب‌وکار خودش را در لیست کشف نمی‌بیند و نمی‌تواند برای خودش رزرو کند.
---
## ۱۲. منوی پایین موبایل
| آیتم | چه زمانی |
|------|----------|
| **خانه** | همیشه |
| **رزروها** | همیشه (بدون ورود → صفحه ورود) |
| **ثبت کسب‌وکار** | اگر صاحب کسب‌وکار نباشید |
| **کسب‌وکار من** | اگر صاحب کسب‌وکار باشید |
| **ورود** | مهمان |
| **خروج** | وارد شده |
---
## ۱۳. سؤالات متداول
**آیا بدون ورود می‌توانم رزرو کنم؟**
خیر. مرور آزاد است؛ ثبت رزرو نیاز به ورود دارد.
**کد تخفیف را کجا وارد کنم؟**
معمولاً نیاز نیست؛ اگر تخفیف فعال باشد هنگام رزرو/پرداخت خودکار اعمال می‌شود.
**چرا کسب‌وکار خودم را در لیست نمی‌بینم؟**
عمدی است تا صاحب کسب‌وکار برای خودش رزرو نکند. از **کسب‌وکار من** مدیریت کنید.
**خدمت سفارشی کی در لیست مشتری می‌آید؟**
پس از تأیید ادمین در پنل مدیریت پلتفرم.
**چطور رمز بسازم؟**
با OTP وارد شوید → پروفایل → تنظیم رمز عبور.
---
## نسخه آنلاین و کپی
| خروجی | مسیر |
|--------|------|
| صفحه وب داخل اپ | `/guide` |
| این فایل Markdown | `docs/user-guide.md` |
| متن ساخت‌یافته برای Notion/Docs | `docs/user-guide-notion.txt` |
---
*منتایم — رزرو آنلاین خدمات محلی*
-1
View File
@@ -1 +0,0 @@
<html><body>404</body></html>
@@ -122,7 +122,7 @@ const features: { label: string; description: string[]; icon: AppIconName }[] =
function onImageError() {
if (fallbackUsed.value) return;
fallbackUsed.value = true;
heroSrc.value = '/landing/hero-car-wash.jpg';
heroSrc.value = '/landing/hero-reference.png';
}
</script>
+10 -1
View File
@@ -24,6 +24,15 @@
</div>
</dl>
<RouterLink
to="/guide"
class="flex w-full items-center justify-between rounded-2xl border border-border bg-surface-2 px-4 py-3 text-sm font-semibold text-text transition hover:border-brand-500/40"
@click="profileModal.close()"
>
<span>راهنمای کاربری</span>
<span class="text-xs font-normal text-muted">مشاهده</span>
</RouterLink>
<section
v-if="auth.isCustomer"
class="rounded-2xl border border-border bg-surface-2 p-4"
@@ -90,7 +99,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { RouterLink, useRouter } from 'vue-router';
import { setPassword } from '@/api/auth';
import { ApiError } from '@/api/client';
import AppButton from '@/components/ui/AppButton.vue';
+1 -1
View File
@@ -89,7 +89,7 @@ watch(loginHeroSrc, (src) => {
function onImageError() {
if (fallbackUsed.value) return;
fallbackUsed.value = true;
heroSrc.value = '/landing/hero-car-wash.jpg';
heroSrc.value = '/landing/hero-reference.png';
}
onMounted(() => {
+5
View File
@@ -68,6 +68,11 @@ const router = createRouter({
component: () => import('@/views/app/AppOwnerView.vue'),
meta: { requiresAuth: true, requiresBusinessOwner: true },
},
{
path: '/guide',
name: 'user-guide',
component: () => import('@/views/guide/UserGuideView.vue'),
},
{ path: '/home', redirect: '/app' },
],
});
+64 -64
View File
@@ -1,64 +1,64 @@
import { defineStore } from 'pinia';␍
import { computed, ref } from 'vue';␍
import { fetchPlatformSettings } from '@/api/platform-settings';␍
import {␍
DEFAULT_LOGO_DARK,␍
DEFAULT_LOGO_LIGHT,␍
resolveLogoPair,␍
uploadUrl,␍
} from '@/utils/media';␍
␍
const DEFAULT_LOGIN_HERO = '/landing/hero-car-wash.jpg';␍
␍
export const usePlatformSettingsStore = defineStore('platformSettings', () => {␍
const loginHeroImageUrl = ref<string | null>(null);␍
const logoLightUrl = ref<string | null>(null);␍
const logoDarkUrl = ref<string | null>(null);␍
const loaded = ref(false);␍
const loading = ref(false);␍
␍
const defaultLogoLight = DEFAULT_LOGO_LIGHT;␍
const defaultLogoDark = DEFAULT_LOGO_DARK;␍
␍
const loginHeroSrc = computed(␍
() => uploadUrl(loginHeroImageUrl.value) || DEFAULT_LOGIN_HERO,␍
);␍
␍
const logoSources = computed(() =>␍
resolveLogoPair(logoLightUrl.value, logoDarkUrl.value),␍
);␍
␍
async function load(force = false) {␍
if (loaded.value && !force) return;␍
if (loading.value) return;␍
␍
loading.value = true;␍
try {␍
const data = await fetchPlatformSettings();␍
loginHeroImageUrl.value = data.loginHeroImageUrl;␍
logoLightUrl.value = data.logoLightUrl;␍
logoDarkUrl.value = data.logoDarkUrl;␍
loaded.value = true;␍
} catch {␍
loginHeroImageUrl.value = null;␍
logoLightUrl.value = null;␍
logoDarkUrl.value = null;␍
loaded.value = true;␍
} finally {␍
loading.value = false;␍
}␍
}␍
␍
return {␍
loginHeroImageUrl,␍
logoLightUrl,␍
logoDarkUrl,␍
defaultLogoLight,␍
defaultLogoDark,␍
loginHeroSrc,␍
logoSources,␍
loaded,␍
loading,␍
load,␍
};␍
});␍
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import { fetchPlatformSettings } from '@/api/platform-settings';
import {
DEFAULT_LOGO_DARK,
DEFAULT_LOGO_LIGHT,
resolveLogoPair,
uploadUrl,
} from '@/utils/media';
const DEFAULT_LOGIN_HERO = '/landing/hero-reference.png';
export const usePlatformSettingsStore = defineStore('platformSettings', () => {
const loginHeroImageUrl = ref<string | null>(null);
const logoLightUrl = ref<string | null>(null);
const logoDarkUrl = ref<string | null>(null);
const loaded = ref(false);
const loading = ref(false);
const defaultLogoLight = DEFAULT_LOGO_LIGHT;
const defaultLogoDark = DEFAULT_LOGO_DARK;
const loginHeroSrc = computed(
() => uploadUrl(loginHeroImageUrl.value) || DEFAULT_LOGIN_HERO,
);
const logoSources = computed(() =>
resolveLogoPair(logoLightUrl.value, logoDarkUrl.value),
);
async function load(force = false) {
if (loaded.value && !force) return;
if (loading.value) return;
loading.value = true;
try {
const data = await fetchPlatformSettings();
loginHeroImageUrl.value = data.loginHeroImageUrl;
logoLightUrl.value = data.logoLightUrl;
logoDarkUrl.value = data.logoDarkUrl;
loaded.value = true;
} catch {
loginHeroImageUrl.value = null;
logoLightUrl.value = null;
logoDarkUrl.value = null;
loaded.value = true;
} finally {
loading.value = false;
}
}
return {
loginHeroImageUrl,
logoLightUrl,
logoDarkUrl,
defaultLogoLight,
defaultLogoDark,
loginHeroSrc,
logoSources,
loaded,
loading,
load,
};
});
+472
View File
@@ -0,0 +1,472 @@
<template>
<div
class="guide-page h-[100dvh] overflow-y-auto overscroll-y-contain bg-[#f5f7fa] text-slate-800 [-webkit-overflow-scrolling:touch] dark:bg-[#0d1117] dark:text-slate-100"
>
<header
class="sticky top-0 z-20 border-b border-slate-200/80 bg-white/95 backdrop-blur dark:border-slate-700 dark:bg-[#121a24]/95"
>
<div class="mx-auto flex max-w-3xl items-center justify-between gap-4 px-4 py-3 sm:px-6">
<RouterLink to="/app" class="flex items-center gap-2">
<AppLogo variant="horizontal" class="h-8 shrink-0" />
<span class="text-sm font-semibold text-slate-600 dark:text-slate-300">راهنما</span>
</RouterLink>
<div class="flex items-center gap-2">
<ThemeToggle />
<RouterLink
to="/app"
class="rounded-lg bg-brand-600 px-3 py-2 text-xs font-semibold text-white hover:bg-brand-700"
>
بازگشت به اپ
</RouterLink>
</div>
</div>
</header>
<div class="relative min-h-[200px] overflow-hidden">
<img
:src="bannerSrc"
alt=""
class="absolute inset-0 h-full w-full object-cover opacity-40"
@error="onBannerError"
/>
<div class="absolute inset-0 bg-gradient-to-b from-slate-950/75 via-slate-900/60 to-[#f5f7fa] dark:to-[#0d1117]" />
<div class="relative mx-auto max-w-3xl px-4 py-12 text-center sm:px-6 sm:py-16">
<AppLogo
variant="horizontal"
class="mx-auto mb-4 h-12 shrink-0"
:light-src="logoSources.dark"
:dark-src="logoSources.dark"
alt="منتایم"
/>
<h1 class="text-2xl font-black text-white sm:text-3xl">راهنمای کاربری منتایم</h1>
<p class="mx-auto mt-3 max-w-xl text-sm leading-7 text-white/85 sm:text-base">
از ورود و رزرو تا مدیریت کسب‌وکار — همه چیز به‌صورت گام‌به‌گام
</p>
</div>
</div>
<main class="mx-auto max-w-3xl px-4 pb-16 sm:px-6">
<nav class="mb-10 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-700 dark:bg-[#121a24]">
<h2 class="mb-3 text-sm font-bold text-brand-600">فهرست</h2>
<ol class="grid gap-2 text-sm sm:grid-cols-2">
<li v-for="(section, index) in sections" :key="section.id">
<a
:href="`#${section.id}`"
class="text-slate-600 hover:text-brand-600 dark:text-slate-300 dark:hover:text-brand-400"
>
{{ index + 1 }}. {{ section.title }}
</a>
</li>
</ol>
</nav>
<article class="space-y-10">
<section
v-for="section in sections"
:id="section.id"
:key="section.id"
class="scroll-mt-24 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-700 dark:bg-[#121a24] sm:p-7"
>
<div class="mb-4 flex items-start gap-3">
<div
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-500/10 text-brand-600"
>
<AppIcon :name="section.icon" size="sm" />
</div>
<div>
<h2 class="text-lg font-bold text-slate-900 dark:text-white">{{ section.title }}</h2>
<p v-if="section.lead" class="mt-1 text-sm leading-7 text-slate-500 dark:text-slate-400">
{{ section.lead }}
</p>
</div>
</div>
<img
v-if="section.image"
:src="section.image"
:alt="section.title"
class="mb-5 w-full rounded-xl object-cover"
:class="section.imageContain ? 'aspect-[21/9] object-contain bg-slate-100 p-6 dark:bg-slate-800/50' : 'aspect-[16/9]'"
@error="onSectionImageError"
/>
<div class="space-y-4 text-sm leading-7 text-slate-700 dark:text-slate-300">
<template v-for="(block, blockIndex) in section.blocks" :key="blockIndex">
<h3 v-if="block.type === 'h3'" class="pt-1 text-base font-semibold text-slate-900 dark:text-white">
{{ block.text }}
</h3>
<p v-else-if="block.type === 'p'">{{ block.text }}</p>
<ol v-else-if="block.type === 'ol'" class="list-decimal space-y-1 ps-5">
<li v-for="(item, i) in block.items" :key="i">{{ item }}</li>
</ol>
<ul v-else-if="block.type === 'ul'" class="list-disc space-y-1 ps-5">
<li v-for="(item, i) in block.items" :key="i">{{ item }}</li>
</ul>
<div
v-else-if="block.type === 'note'"
class="rounded-xl border border-brand-500/30 bg-brand-500/10 px-4 py-3 text-brand-800 dark:text-brand-400"
>
{{ block.text }}
</div>
<div
v-else-if="block.type === 'table'"
class="overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-700"
>
<table class="w-full min-w-[280px] text-start text-sm">
<thead class="bg-slate-50 dark:bg-slate-800/60">
<tr>
<th
v-for="(head, hi) in block.headers"
:key="hi"
class="px-3 py-2 font-semibold"
>
{{ head }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(row, ri) in block.rows"
:key="ri"
class="border-t border-slate-200 dark:border-slate-700"
>
<td v-for="(cell, ci) in row" :key="ci" class="px-3 py-2">{{ cell }}</td>
</tr>
</tbody>
</table>
</div>
</template>
</div>
</section>
</article>
<footer class="mt-12 rounded-2xl border border-dashed border-slate-300 p-5 text-center text-sm text-slate-500 dark:border-slate-600 dark:text-slate-400">
<p>نسخه Markdown: <code class="text-xs">docs/user-guide.md</code></p>
<p class="mt-1">نسخه Notion/Docs: <code class="text-xs">docs/user-guide-notion.txt</code></p>
<p class="mt-3 font-medium text-slate-700 dark:text-slate-200">منتایم — رزرو آنلاین خدمات محلی</p>
</footer>
</main>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { onMounted, ref, watch } from 'vue';
import AppLogo from '@/components/brand/AppLogo.vue';
import AppIcon from '@/components/ui/AppIcon.vue';
import ThemeToggle from '@/components/ui/ThemeToggle.vue';
import { usePlatformSettingsStore } from '@/stores/platform-settings';
import type { AppIconName } from '@/types/icons';
const FALLBACK_BANNER = '/landing/hero-reference.png';
type GuideBlock =
| { type: 'h3'; text: string }
| { type: 'p'; text: string }
| { type: 'note'; text: string }
| { type: 'ol'; items: string[] }
| { type: 'ul'; items: string[] }
| { type: 'table'; headers: string[]; rows: string[][] };
type GuideSection = {
id: string;
title: string;
lead?: string;
icon: AppIconName;
image?: string;
imageContain?: boolean;
blocks: GuideBlock[];
};
const platform = usePlatformSettingsStore();
const { loginHeroSrc, logoSources } = storeToRefs(platform);
const bannerSrc = ref(FALLBACK_BANNER);
const bannerFailed = ref(false);
watch(
loginHeroSrc,
(src) => {
if (bannerFailed.value) return;
bannerSrc.value = src || FALLBACK_BANNER;
},
{ immediate: true },
);
function onBannerError() {
if (bannerSrc.value === FALLBACK_BANNER) return;
bannerFailed.value = true;
bannerSrc.value = FALLBACK_BANNER;
}
function onSectionImageError(event: Event) {
const img = event.target as HTMLImageElement | null;
if (img) img.style.display = 'none';
}
onMounted(() => {
void platform.load();
});
const sections: GuideSection[] = [
{
id: 'intro',
title: 'منتایم چیست؟',
lead: 'پلتفرم رزرو آنلاین خدمات محلی',
icon: 'info',
image: '/landing/hero-reference.png',
blocks: [
{
type: 'p',
text: 'منتایم به شما کمک می‌کند کسب‌وکارهای محلی را پیدا کنید و آنلاین نوبت بگیرید؛ مثلاً کارواش یا آرایشگاه.',
},
{
type: 'table',
headers: ['نقش', 'کار اصلی'],
rows: [
['مشتری', 'کشف کسب‌وکار، رزرو، پرداخت، امتیاز'],
['صاحب کسب‌وکار', 'مدیریت خدمات، ساعات، رزروها و صفحه لندینگ'],
],
},
],
},
{
id: 'login',
title: 'شروع کار و ورود',
icon: 'user',
image: '/brand/welcome-lite.png',
imageContain: true,
blocks: [
{ type: 'h3', text: 'ورود با کد یکبارمصرف' },
{
type: 'ol',
items: [
'از منو «ورود» را بزنید',
'حالت «کد یکبارمصرف» را انتخاب کنید',
'شماره موبایل را وارد کنید (مثال: 09121234567)',
'«دریافت کد تأیید» را بزنید',
'کد ۴ رقمی پیامک‌شده را وارد کنید',
],
},
{ type: 'h3', text: 'ورود با رمز عبور' },
{
type: 'p',
text: 'حالت «رمز عبور» را انتخاب کنید، موبایل و رمز را وارد کنید و «ورود» را بزنید.',
},
{
type: 'note',
text: 'اولین بار با کد یکبارمصرف وارد شوید و بعداً از پروفایل رمز بسازید. با «ادامه بدون ورود» فقط می‌توانید مرور کنید.',
},
],
},
{
id: 'discover',
title: 'کشف کسب‌وکارها',
icon: 'store',
blocks: [
{
type: 'ol',
items: [
'از «خانه» لیست کسب‌وکارها را ببینید',
'در صورت نیاز جستجو یا فیلتر دسته را استفاده کنید',
'روی کارت بزنید تا صفحه کسب‌وکار باز شود',
],
},
{
type: 'p',
text: 'روی کارت معمولاً تصویر کاور، نام، امتیاز، زیرعنوان، دسته و آدرس دیده می‌شود.',
},
],
},
{
id: 'booking',
title: 'صفحه کسب‌وکار و رزرو نوبت',
icon: 'calendar',
image: '/brand/login-hero-default.png',
blocks: [
{
type: 'p',
text: 'در صفحه کسب‌وکار: نام، توضیحات، گالری، نکات برجسته، تخفیف (در صورت فعال بودن) و خدمات را می‌بینید. دکمه اصلی «رزرو نوبت» است.',
},
{ type: 'h3', text: 'مراحل رزرو' },
{
type: 'ol',
items: [
'کلاس خدمات (فقط اگر کلاس‌بندی فعال باشد)',
'انتخاب خدمات',
'تاریخ و ساعت',
'اطلاعات و یادداشت اختیاری',
'تایید نهایی → ثبت نهایی رزرو',
],
},
{
type: 'table',
headers: ['حالت کسب‌وکار', 'نتیجه'],
rows: [
['تأیید خودکار فعال', 'رزرو تأیید می‌شود و آماده پرداخت است'],
['تأیید دستی', 'در انتظار تأیید صاحب کسب‌وکار'],
],
},
],
},
{
id: 'payment',
title: 'پرداخت',
icon: 'badge-percent',
blocks: [
{
type: 'ol',
items: [
'مبلغ، کسب‌وکار و تاریخ را بررسی کنید',
'پرداخت آنلاین را انجام دهید',
'پس از موفقیت به لیست رزروها برگردید',
],
},
{
type: 'note',
text: 'تخفیف درصدی معمولاً خودکار اعمال می‌شود. اگر نوبت رایگان باشگاه مشتریان باشد، مبلغ رایگان نمایش داده می‌شود.',
},
],
},
{
id: 'customer-bookings',
title: 'مدیریت رزروها (مشتری)',
icon: 'clipboard-list',
blocks: [
{
type: 'ul',
items: [
'در انتظار تأیید',
'منتظر پرداخت → دکمه پرداخت',
'تأیید شده / انجام شده / لغو شده',
'لغو در وضعیت‌های مجاز',
],
},
{ type: 'p', text: 'اگر رزروی نباشد پیام «هنوز رزروی ثبت نکردید.» نمایش داده می‌شود.' },
],
},
{
id: 'review',
title: 'امتیازدهی و حسن انجام کار',
icon: 'sparkles',
blocks: [
{
type: 'ol',
items: [
'پس از «انجام شد» توسط صاحب کسب‌وکار، پنجره امتیاز باز می‌شود',
'ستاره ۱ تا ۵ را انتخاب کنید',
'اگر حسن انجام کار فعال باشد: تمایل ندارم یا تمایل دارم (+ مبلغ)',
'در غیر این صورت فقط ثبت امتیاز',
],
},
],
},
{
id: 'profile',
title: 'پروفایل کاربری',
icon: 'settings',
blocks: [
{
type: 'ul',
items: [
'مشاهده موبایل و نقش',
'تنظیم یا تغییر رمز عبور',
'مشتری: ثبت کسب‌وکار من',
'صاحب: ورود به پنل کسب‌وکار',
'خروج از حساب',
],
},
],
},
{
id: 'register-business',
title: 'ثبت کسب‌وکار',
icon: 'plus',
blocks: [
{
type: 'ol',
items: [
'وارد شوید و «ثبت کسب‌وکار» را بزنید',
'نام، دسته‌بندی، توضیحات، آدرس و تلفن را پر کنید',
'ارسال درخواست',
'پس از تأیید ادمین، پنل صاحب کسب‌وکار فعال می‌شود',
],
},
],
},
{
id: 'owner',
title: 'پنل صاحب کسب‌وکار',
lead: 'مسیر: کسب‌وکار من',
icon: 'layout-dashboard',
blocks: [
{
type: 'ul',
items: [
'اطلاعات کسب‌وکار و نکات برجسته لندینگ (حداکثر ۳ مورد)',
'لوگوی روشن/تاریک و گالری تصاویر + کاور',
'تنظیمات نوبت‌دهی: پذیرش همزمان، گام نوبت، تأیید خودکار، حسن انجام کار، تخفیف',
'ساعات پذیرش آنلاین',
'خدمات، قیمت، خدمت رایگان دوره‌ای، کلاس‌بندی، خدمت سفارشی',
],
},
{
type: 'note',
text: 'صاحب کسب‌وکار کسب‌وکار خودش را در لیست کشف نمی‌بیند و نمی‌تواند برای خودش رزرو کند.',
},
],
},
{
id: 'owner-bookings',
title: 'مدیریت رزروها (صاحب کسب‌وکار)',
icon: 'calendar',
blocks: [
{
type: 'table',
headers: ['دکمه', 'کار'],
rows: [
['تأیید', 'تأیید درخواست مشتری'],
['انجام شد', 'پایان خدمت و باز شدن امتیاز برای مشتری'],
['لغو', 'لغو با وارد کردن علت'],
],
},
{ type: 'p', text: 'تب‌ها: در انتظار تأیید | تأیید شده | لغو شده' },
],
},
{
id: 'nav',
title: 'منوی پایین موبایل',
icon: 'home',
blocks: [
{
type: 'table',
headers: ['آیتم', 'چه زمانی'],
rows: [
['خانه', 'همیشه'],
['رزروها', 'همیشه (بدون ورود → صفحه ورود)'],
['ثبت کسب‌وکار', 'اگر صاحب نباشید'],
['کسب‌وکار من', 'اگر صاحب باشید'],
['ورود / خروج', 'مهمان / وارد شده'],
],
},
],
},
{
id: 'faq',
title: 'سؤالات متداول',
icon: 'message-circle',
blocks: [
{
type: 'ul',
items: [
'بدون ورود نمی‌توان رزرو کرد؛ فقط مرور آزاد است.',
'کد تخفیف معمولاً لازم نیست؛ تخفیف فعال خودکار اعمال می‌شود.',
'کسب‌وکار خودتان در لیست کشف دیده نمی‌شود؛ از «کسب‌وکار من» مدیریت کنید.',
'خدمت سفارشی بعد از تأیید ادمین در لیست مشتری می‌آید.',
'برای ساخت رمز: ورود با OTP → پروفایل → تنظیم رمز عبور.',
],
},
],
},
];
</script>