diff --git a/index.html b/index.html
index 67a50dc..e69de29 100644
--- a/index.html
+++ b/index.html
@@ -1,24 +0,0 @@
-
-
-
-
-
-
- منتایم — پنل مدیریت
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/font/iranYekanX/IRANYekanXFaNum-Black.woff2 b/public/font/iranYekanX/IRANYekanXFaNum-Black.woff2
new file mode 100644
index 0000000..53ae709
Binary files /dev/null and b/public/font/iranYekanX/IRANYekanXFaNum-Black.woff2 differ
diff --git a/public/font/iranYekanX/IRANYekanXFaNum-Bold.woff2 b/public/font/iranYekanX/IRANYekanXFaNum-Bold.woff2
new file mode 100644
index 0000000..8f5b1cb
Binary files /dev/null and b/public/font/iranYekanX/IRANYekanXFaNum-Bold.woff2 differ
diff --git a/public/font/iranYekanX/IRANYekanXFaNum-ExtraBlack.woff2 b/public/font/iranYekanX/IRANYekanXFaNum-ExtraBlack.woff2
new file mode 100644
index 0000000..8faf1a4
Binary files /dev/null and b/public/font/iranYekanX/IRANYekanXFaNum-ExtraBlack.woff2 differ
diff --git a/public/font/iranYekanX/IRANYekanXFaNum-Light.woff2 b/public/font/iranYekanX/IRANYekanXFaNum-Light.woff2
new file mode 100644
index 0000000..bae7f99
Binary files /dev/null and b/public/font/iranYekanX/IRANYekanXFaNum-Light.woff2 differ
diff --git a/public/font/iranYekanX/IRANYekanXFaNum-Regular.woff2 b/public/font/iranYekanX/IRANYekanXFaNum-Regular.woff2
new file mode 100644
index 0000000..6467890
Binary files /dev/null and b/public/font/iranYekanX/IRANYekanXFaNum-Regular.woff2 differ
diff --git a/public/landing/hero-car-wash.jpg b/public/landing/hero-car-wash.jpg
new file mode 100644
index 0000000..331f800
--- /dev/null
+++ b/public/landing/hero-car-wash.jpg
@@ -0,0 +1 @@
+404
\ No newline at end of file
diff --git a/src/api/platform-settings.ts b/src/api/platform-settings.ts
new file mode 100644
index 0000000..3b6c905
--- /dev/null
+++ b/src/api/platform-settings.ts
@@ -0,0 +1,21 @@
+import { api, apiFormData } from './client';
+
+export type PlatformSettings = {
+ loginHeroImageUrl: string | null;
+};
+
+export function fetchPlatformSettings() {
+ return api('/platform-settings');
+}
+
+export function uploadLoginHeroImage(file: File) {
+ const form = new FormData();
+ form.append('file', file);
+ return apiFormData('/platform-settings/login-hero-image', form);
+}
+
+export function removeLoginHeroImage() {
+ return api('/platform-settings/login-hero-image', {
+ method: 'DELETE',
+ });
+}
diff --git a/src/api/types.ts b/src/api/types.ts
index 6faa7a2..af37416 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -1,178 +1,4 @@
-export type UserRole = 'customer' | 'business_owner' | 'admin';
-export type User = {
- id: string;
- mobile: string;
- fullName: string | null;
- role: UserRole;
- isActive: boolean;
- hasPassword?: boolean;
- createdAt?: string;
- updatedAt?: string;
-};
-
-export type BusinessCategory = {
- id: string;
- slug: string;
- title: string;
- isActive: boolean;
- createdAt?: string;
- updatedAt?: string;
-};
-
-export type BusinessImage = {
- id: string;
- businessId: string;
- url: string;
- originalName: string | null;
- sortOrder: number;
- isCover: boolean;
-};
-
-export type Business = {
- id: string;
- title: string;
- slug: string;
- description: string | null;
- contactPhone: string | null;
- address: string | null;
- latitude: number | null;
- longitude: number | null;
- isActive: boolean;
- categoryId: string;
- ownerId: string | null;
- category?: BusinessCategory;
- images?: BusinessImage[];
- owner?: User | null;
- createdAt?: string;
- updatedAt?: string;
-};
-
-export type CreateBusinessPayload = {
- title: string;
- slug?: string;
- categoryId: string;
- description?: string;
- contactPhone?: string;
- address?: string;
- latitude?: number;
- longitude?: number;
- isActive?: boolean;
-};
-
-export type UpdateBusinessPayload = Partial & {
- description?: string | null;
- contactPhone?: string | null;
- address?: string | null;
- latitude?: number | null;
- longitude?: number | null;
- ownerId?: string | null;
-};
-
-export type CreateCategoryPayload = {
- title: string;
- slug?: string;
- isActive?: boolean;
-};
-
-export type UpdateCategoryPayload = Partial;
-
-export type ServiceTemplate = {
- id: string;
- title: string;
- slug: string;
- description: string | null;
- defaultDurationMinutes: number | null;
- isActive: boolean;
- categoryId: string;
- category?: BusinessCategory;
- createdAt?: string;
- updatedAt?: string;
-};
-
-export type CreateServiceTemplatePayload = {
- title: string;
- slug?: string;
- categoryId: string;
- description?: string;
- isActive?: boolean;
-};
-
-export type UpdateServiceTemplatePayload = Partial<
- Omit
->;
-
-export type UpdateUserPayload = {
- fullName?: string | null;
- role?: UserRole;
- isActive?: boolean;
-};
-
-export type AdminSetUserPasswordPayload = {
- password: string;
- confirmPassword: string;
-};
-
-export type AssignOwnerPayload = {
- mobile: string;
- fullName?: string | null;
-};
-
-export type BookingStatus =
- | 'pending'
- | 'confirmed'
- | 'cancelled'
- | 'completed';
-
-export type BusinessRegistrationStatus = 'pending' | 'approved' | 'rejected';
-
-export type BusinessRegistrationRequest = {
- id: string;
- applicantId: string;
- status: BusinessRegistrationStatus;
- title: string;
- slug: string;
- categoryId: string;
- description: string | null;
- contactPhone: string | null;
- address: string | null;
- adminNote: string | null;
- businessId: string | null;
- createdAt: string;
- category?: BusinessCategory;
- applicant?: User;
-};
-
-export type BusinessServiceRequestStatus = 'pending' | 'approved' | 'rejected';
-
-export type BusinessServiceRequest = {
- id: string;
- businessId: string;
- requestedById: string;
- status: BusinessServiceRequestStatus;
- title: string;
- description: string | null;
- price: string;
- durationMinutes: number | null;
- adminNote: string | null;
- serviceTemplateId: string | null;
- businessServiceId: string | null;
- createdAt: string;
- business?: Business;
- requestedBy?: User;
-};
-
-export type Booking = {
- id: string;
- customerId: string;
- businessId: string;
- businessServiceId: string;
- scheduledAt: string;
- status: BookingStatus;
- note: string | null;
- customer?: User;
- business?: Business;
- businessService?: {
- serviceTemplate?: { title: string };
- };
-};
+ subtitle: string | null;
+ subtitle?: string;
+ subtitle?: string | null;
\ No newline at end of file
diff --git a/src/assets/fonts/IRANYekanXFaNum-Black.woff2 b/src/assets/fonts/IRANYekanXFaNum-Black.woff2
new file mode 100644
index 0000000..53ae709
Binary files /dev/null and b/src/assets/fonts/IRANYekanXFaNum-Black.woff2 differ
diff --git a/src/assets/fonts/IRANYekanXFaNum-Bold.woff2 b/src/assets/fonts/IRANYekanXFaNum-Bold.woff2
new file mode 100644
index 0000000..8f5b1cb
Binary files /dev/null and b/src/assets/fonts/IRANYekanXFaNum-Bold.woff2 differ
diff --git a/src/assets/fonts/IRANYekanXFaNum-ExtraBlack.woff2 b/src/assets/fonts/IRANYekanXFaNum-ExtraBlack.woff2
new file mode 100644
index 0000000..8faf1a4
Binary files /dev/null and b/src/assets/fonts/IRANYekanXFaNum-ExtraBlack.woff2 differ
diff --git a/src/assets/fonts/IRANYekanXFaNum-Light.woff2 b/src/assets/fonts/IRANYekanXFaNum-Light.woff2
new file mode 100644
index 0000000..bae7f99
Binary files /dev/null and b/src/assets/fonts/IRANYekanXFaNum-Light.woff2 differ
diff --git a/src/assets/fonts/IRANYekanXFaNum-Regular.woff2 b/src/assets/fonts/IRANYekanXFaNum-Regular.woff2
new file mode 100644
index 0000000..6467890
Binary files /dev/null and b/src/assets/fonts/IRANYekanXFaNum-Regular.woff2 differ
diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue
index badede1..c40100c 100644
--- a/src/layouts/AdminLayout.vue
+++ b/src/layouts/AdminLayout.vue
@@ -1,83 +1,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+ تنظیمات
+
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index 89fe64e..0bb9662 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,10 +1,2 @@
-import { createApp } from 'vue';
-import { createPinia } from 'pinia';
-import App from './App.vue';
-import router from './router';
-import './styles/main.css';
-const app = createApp(App);
-app.use(createPinia());
-app.use(router);
-app.mount('#app');
+import './styles/fonts.css';
\ No newline at end of file
diff --git a/src/router/index.ts b/src/router/index.ts
index b3d4c4a..bc77409 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -1,97 +1,7 @@
-import { createRouter, createWebHistory } from 'vue-router';
-import { useAuthStore } from '@/stores/auth';
-const router = createRouter({
- history: createWebHistory(),
- routes: [
{
- path: '/login',
- name: 'login',
- component: () => import('@/views/LoginView.vue'),
- meta: { guest: true },
- },
- {
- path: '/verify',
- name: 'verify',
- component: () => import('@/views/VerifyOtpView.vue'),
- meta: { guest: true },
- },
- {
- path: '/',
- name: 'dashboard',
- component: () => import('@/views/DashboardView.vue'),
+ path: '/settings',
+ name: 'settings',
+ component: () => import('@/views/PlatformSettingsView.vue'),
meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/categories',
- name: 'categories',
- component: () => import('@/views/CategoriesView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/service-templates',
- name: 'service-templates',
- component: () => import('@/views/ServiceTemplatesView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/businesses',
- name: 'businesses',
- component: () => import('@/views/BusinessesView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/users',
- name: 'users',
- component: () => import('@/views/UsersView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/business-requests',
- name: 'business-requests',
- component: () => import('@/views/BusinessRequestsView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/service-requests',
- name: 'service-requests',
- component: () => import('@/views/ServiceRequestsView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/bookings',
- name: 'bookings',
- component: () => import('@/views/BookingsView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- {
- path: '/profile',
- name: 'profile',
- component: () => import('@/views/ProfileView.vue'),
- meta: { requiresAuth: true, requiresAdmin: true },
- },
- ],
-});
-
-router.beforeEach(async (to) => {
- const auth = useAuthStore();
-
- if (auth.token && !auth.user) {
- await auth.loadUser();
- }
-
- if (to.meta.requiresAuth && !auth.isLoggedIn) {
- return { name: 'login' };
- }
-
- if (to.meta.requiresAdmin && auth.isLoggedIn && !auth.isAdmin) {
- auth.logout();
- return { name: 'login' };
- }
-
- if (to.meta.guest && auth.isLoggedIn && auth.isAdmin) {
- return { name: 'dashboard' };
- }
-});
-
-export default router;
+ },
\ No newline at end of file
diff --git a/src/styles/fonts.css b/src/styles/fonts.css
new file mode 100644
index 0000000..679707f
--- /dev/null
+++ b/src/styles/fonts.css
@@ -0,0 +1,55 @@
+@font-face {
+ font-family: IRANYekanX;
+ src: url('../assets/fonts/IRANYekanXFaNum-Light.woff2') format('woff2');
+ font-weight: 300;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: IRANYekanX;
+ src: url('../assets/fonts/IRANYekanXFaNum-Regular.woff2') format('woff2');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: IRANYekanX;
+ src: url('../assets/fonts/IRANYekanXFaNum-Regular.woff2') format('woff2');
+ font-weight: 500;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: IRANYekanX;
+ src: url('../assets/fonts/IRANYekanXFaNum-Bold.woff2') format('woff2');
+ font-weight: 600;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: IRANYekanX;
+ src: url('../assets/fonts/IRANYekanXFaNum-Bold.woff2') format('woff2');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: IRANYekanX;
+ src: url('../assets/fonts/IRANYekanXFaNum-Black.woff2') format('woff2');
+ font-weight: 900;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: IRANYekanX;
+ src: url('../assets/fonts/IRANYekanXFaNum-ExtraBlack.woff2') format('woff2');
+ font-weight: 950;
+ font-style: normal;
+ font-display: swap;
+}
diff --git a/src/styles/main.css b/src/styles/main.css
index 30d2e35..b8b4f0f 100644
--- a/src/styles/main.css
+++ b/src/styles/main.css
@@ -1,37 +1,11 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-:root {
- --color-surface: #ffffff;
- --color-surface-2: #fffbeb;
- --color-surface-3: #fef3c7;
- --color-border: #fde68a;
- --color-text: #0f172a;
- --color-muted: #64748b;
- --color-placeholder: rgb(100 116 139 / 0.35);
-}
-
-.dark {
- --color-surface: #0f1419;
- --color-surface-2: #1a2332;
- --color-surface-3: #243044;
- --color-border: #334155;
- --color-text: #f1f5f9;
- --color-muted: #94a3b8;
- --color-placeholder: rgb(148 163 184 / 0.35);
-}
-
-@layer base {
- html {
- @apply antialiased;
+ @apply antialiased font-sans;
+ font-family: IRANYekanX, ui-sans-serif, system-ui, sans-serif;
}
- body {
- @apply bg-surface-2 font-sans text-text;
- }
-
- #app {
- @apply min-h-dvh;
- }
-}
+ body,
+ button,
+ input,
+ textarea,
+ select {
+ font-family: inherit;
\ No newline at end of file
diff --git a/src/views/BusinessesView.vue b/src/views/BusinessesView.vue
index 3009f7f..2916d11 100644
--- a/src/views/BusinessesView.vue
+++ b/src/views/BusinessesView.vue
@@ -1,528 +1,12 @@
-
-
-
-
- جدید
-
-
-
-
-
-
-
- {{ error }}
-
-
-
-
-
- {{
- searchQuery.trim()
- ? 'نتیجهای برای جستجوی شما یافت نشد.'
- : 'کسبوکاری یافت نشد.'
- }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ subtitle: '',
+ subtitle: '',
+ subtitle: biz.subtitle ?? '',
+ subtitle: form.value.subtitle.trim() || null,
+ subtitle: form.value.subtitle.trim() || undefined,
\ No newline at end of file
diff --git a/src/views/DashboardView.vue b/src/views/DashboardView.vue
index 420a626..0e753a1 100644
--- a/src/views/DashboardView.vue
+++ b/src/views/DashboardView.vue
@@ -1,127 +1,2 @@
-
-
-
-
-
خوش آمدید
-
- {{ auth.user?.fullName ?? 'سوپر ادمین' }}
-
-
{{ auth.user?.mobile }}
-
-
-
- {{ error }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
آخرین کسبوکارها
-
- همه
-
-
-
-
- هنوز کسبوکاری ثبت نشده.
-
- افزودن کسبوکار
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ { label: 'تنظیمات', value: '—', icon: 'camera' as const, to: '/settings', tone: 'sky' as const },
\ No newline at end of file
diff --git a/src/views/PlatformSettingsView.vue b/src/views/PlatformSettingsView.vue
new file mode 100644
index 0000000..bec6452
--- /dev/null
+++ b/src/views/PlatformSettingsView.vue
@@ -0,0 +1,163 @@
+
+
+
+ تنظیمات پلتفرم
+
+ تصویر پسزمینه صفحه ورود دسکتاپ را از اینجا مدیریت کنید.
+
+
+
+
+ {{ error }}
+
+
+
+ {{ success }}
+
+
+
+ تصویر صفحه ورود (دسکتاپ)
+
+ JPG، PNG یا WebP — حداکثر ۵ مگابایت. پیشنهاد: تصویر افقی با کیفیت بالا.
+
+
+
+ در حال بارگذاری...
+
+
+
+
+
![پیشنمایش تصویر ورود]()
+
+
+
+ {{
+ settings?.loginHeroImageUrl
+ ? 'تصویر سفارشی فعال است.'
+ : 'در حال حاضر تصویر پیشفرض نمایش داده میشود.'
+ }}
+
+
+
+
+
+
+ حذف تصویر سفارشی
+
+
+
+
+
+
+
+
diff --git a/tailwind.config.js b/tailwind.config.js
index 19452d5..5d69f51 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,33 +1,4 @@
-/** @type {import('tailwindcss').Config} */
-export default {
- darkMode: 'class',
- content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
- theme: {
- extend: {
- fontFamily: {
- sans: ['Vazirmatn', 'ui-sans-serif', 'system-ui', 'sans-serif'],
- },
- colors: {
- brand: {
- 50: '#fffbeb',
- 100: '#fef3c7',
- 500: '#f59e0b',
- 600: '#d97706',
- 700: '#b45309',
- },
- surface: {
- DEFAULT: 'var(--color-surface)',
- 2: 'var(--color-surface-2)',
- 3: 'var(--color-surface-3)',
- },
- border: 'var(--color-border)',
- text: 'var(--color-text)',
- muted: 'var(--color-muted)',
- },
- maxWidth: {
- app: '768px',
- },
- },
- },
- plugins: [],
-};
+
+ fontFamily: {
+ sans: ['IRANYekanX', 'ui-sans-serif', 'system-ui', 'sans-serif'],
+ },
\ No newline at end of file