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 @@ - - - + 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 @@ - - - + { 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 @@ + + + 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