diff --git a/index.html b/index.html index 7d5edf2..67a50dc 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,13 @@ + منتایم — پنل مدیریت + + + + - - A + + + diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png new file mode 100644 index 0000000..1f41dc5 Binary files /dev/null and b/public/icons/icon-192.png differ diff --git a/public/icons/icon-32.png b/public/icons/icon-32.png new file mode 100644 index 0000000..bc79a08 Binary files /dev/null and b/public/icons/icon-32.png differ diff --git a/public/icons/icon-36.png b/public/icons/icon-36.png new file mode 100644 index 0000000..04734ba Binary files /dev/null and b/public/icons/icon-36.png differ diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png new file mode 100644 index 0000000..9a397dd Binary files /dev/null and b/public/icons/icon-512.png differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..08ea138 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,31 @@ +{ + "name": "منتایم — پنل مدیریت", + "short_name": "منتایم", + "description": "پنل مدیریت منتایم", + "start_url": "/", + "display": "standalone", + "lang": "fa", + "dir": "rtl", + "theme_color": "#d97706", + "background_color": "#ffffff", + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/src/api/client.ts b/src/api/client.ts index 00d538f..53a5aa6 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -1,3 +1,5 @@ +import { logApiServerTiming } from './server-timing'; + const baseUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:3000'; export class ApiError extends Error { @@ -38,6 +40,8 @@ export async function api(path: string, options: ApiOptions = {}): Promise headers, }); + logApiServerTiming(path, res); + if (!res.ok) { const body = (await res.json().catch(() => null)) as | { message?: string | string[] } @@ -66,6 +70,8 @@ export async function apiFormData(path: string, form: FormData): Promise { body: form, }); + logApiServerTiming(path, res); + if (!res.ok) { const body = (await res.json().catch(() => null)) as | { message?: string | string[] } diff --git a/src/api/server-timing.ts b/src/api/server-timing.ts new file mode 100644 index 0000000..a849609 --- /dev/null +++ b/src/api/server-timing.ts @@ -0,0 +1,15 @@ +/** Log Server-Timing from API responses (DevTools Network tab also shows the header). */ +export function logApiServerTiming(path: string, res: Response): void { + const timing = res.headers.get('server-timing'); + if (!timing) { + return; + } + const enabled = + import.meta.env.DEV || import.meta.env.VITE_LOG_API_TIMING === 'true'; + if (!enabled) { + return; + } + const match = /total;dur=(\d+)/i.exec(timing); + const ms = match?.[1] ?? timing; + console.info(`[API] ${path} — server-timing: total;dur=${ms}`); +} diff --git a/src/components/auth/AuthModeSwitch.vue b/src/components/auth/AuthModeSwitch.vue index 714f8aa..d5441fb 100644 --- a/src/components/auth/AuthModeSwitch.vue +++ b/src/components/auth/AuthModeSwitch.vue @@ -1,5 +1,5 @@ + + diff --git a/src/components/brand/AppLogo.vue b/src/components/brand/AppLogo.vue new file mode 100644 index 0000000..4f41b8d --- /dev/null +++ b/src/components/brand/AppLogo.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/components/ui/AppInput.vue b/src/components/ui/AppInput.vue index d2c71f6..809bc24 100644 --- a/src/components/ui/AppInput.vue +++ b/src/components/ui/AppInput.vue @@ -6,7 +6,7 @@ diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue index f3ff8e1..d8da1e9 100644 --- a/src/layouts/AdminLayout.vue +++ b/src/layouts/AdminLayout.vue @@ -4,7 +4,7 @@ class="sticky top-0 z-10 flex items-center justify-between border-b border-brand-500/30 bg-surface/95 px-4 py-3 backdrop-blur supports-[backdrop-filter]:bg-surface/80" > -

منتایم

+

پنل مدیریت

@@ -57,6 +57,7 @@ import { useRoute, useRouter } from 'vue-router'; import AppShell from '@/components/shell/AppShell.vue'; import AppIcon from '@/components/ui/AppIcon.vue'; +import AppLogo from '@/components/brand/AppLogo.vue'; import ThemeToggle from '@/components/ui/ThemeToggle.vue'; import type { AdminNavItem } from '@/types/icons'; import { useAuthStore } from '@/stores/auth'; diff --git a/src/layouts/AuthLayout.vue b/src/layouts/AuthLayout.vue index c291864..1bcae4e 100644 --- a/src/layouts/AuthLayout.vue +++ b/src/layouts/AuthLayout.vue @@ -2,8 +2,14 @@
-

{{ title }}

-

{{ subtitle }}

+
+ +

{{ subtitle }}

+
+
@@ -14,11 +20,13 @@ diff --git a/src/stores/auth.ts b/src/stores/auth.ts index d758fc5..0000982 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -23,10 +23,13 @@ export const useAuthStore = defineStore('auth', () => { localStorage.removeItem(TOKEN_KEY); } - async function loadUser() { + async function loadUser(force = false) { if (!token.value) { return; } + if (user.value && !force) { + return; + } try { user.value = await fetchMe(token.value); } catch { diff --git a/src/styles/main.css b/src/styles/main.css index a5d70f9..30d2e35 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -9,6 +9,7 @@ --color-border: #fde68a; --color-text: #0f172a; --color-muted: #64748b; + --color-placeholder: rgb(100 116 139 / 0.35); } .dark { @@ -18,6 +19,7 @@ --color-border: #334155; --color-text: #f1f5f9; --color-muted: #94a3b8; + --color-placeholder: rgb(148 163 184 / 0.35); } @layer base { diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index 5129318..3a880cc 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -1,46 +1,60 @@ @@ -54,6 +68,7 @@ import AuthModeSwitch from '@/components/auth/AuthModeSwitch.vue'; import type { AuthLoginMode } from '@/components/auth/AuthModeSwitch.vue'; import AppButton from '@/components/ui/AppButton.vue'; import AppInput from '@/components/ui/AppInput.vue'; +import AppLogo from '@/components/brand/AppLogo.vue'; import AuthLayout from '@/layouts/AuthLayout.vue'; import { useAuthStore } from '@/stores/auth'; diff --git a/src/views/VerifyOtpView.vue b/src/views/VerifyOtpView.vue index 0b490fe..3da0d0d 100644 --- a/src/views/VerifyOtpView.vue +++ b/src/views/VerifyOtpView.vue @@ -14,19 +14,14 @@

- - ورود +

در حال تأیید...

@@ -43,12 +38,12 @@