302 lines
7.9 KiB
Vue
302 lines
7.9 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import AdminIcon from '@/components/icons/AdminIcon.vue'
|
||
import { useAuthStore } from '@/stores/auth'
|
||
import { useToast } from '@/composables/useToast'
|
||
import { isMockAuthEnabled, MOCK_ADMIN } from '@/services/authService'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const auth = useAuthStore()
|
||
const toast = useToast()
|
||
|
||
const phone = ref(isMockAuthEnabled() ? MOCK_ADMIN.phone : '')
|
||
const password = ref(isMockAuthEnabled() ? MOCK_ADMIN.password : '')
|
||
const remember = ref(true)
|
||
const showPassword = ref(false)
|
||
const mockAuth = isMockAuthEnabled()
|
||
|
||
const isFormValid = computed(() => phone.value.trim().length > 0 && password.value.length > 0)
|
||
|
||
function toAsciiDigits(value: string) {
|
||
return value.replace(/[۰-۹]/g, (d) => String('۰۱۲۳۴۵۶۷۸۹'.indexOf(d))).replace(/[٠-٩]/g, (d) =>
|
||
String('٠١٢٣٤٥٦٧٨٩'.indexOf(d)),
|
||
)
|
||
}
|
||
|
||
async function submit() {
|
||
if (!isFormValid.value) {
|
||
auth.error = 'لطفاً شماره موبایل و رمز عبور را وارد کنید.'
|
||
return
|
||
}
|
||
|
||
const ok = await auth.login(toAsciiDigits(phone.value.trim()), password.value, remember.value)
|
||
if (!ok) return
|
||
|
||
toast.success(`خوش آمدید، ${auth.fullName}`)
|
||
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : '/'
|
||
router.replace(redirect)
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="login-page">
|
||
<div class="login-page__form-wrap">
|
||
<div class="login-page__card admin-card">
|
||
<div class="login-page__header">
|
||
<h2 class="login-page__title">ورود به پنل مدیریت</h2>
|
||
<p class="login-page__subtitle">برای ادامه، اطلاعات حساب کاربری خود را وارد کنید.</p>
|
||
<p v-if="mockAuth" class="login-page__mock-hint">
|
||
حالت موک فعال است —
|
||
<span dir="ltr">{{ MOCK_ADMIN.phone }}</span>
|
||
/
|
||
<span dir="ltr">{{ MOCK_ADMIN.password }}</span>
|
||
</p>
|
||
</div>
|
||
|
||
<form class="admin-form login-page__form" @submit.prevent="submit">
|
||
<p v-if="auth.error" class="admin-form__error" role="alert">{{ auth.error }}</p>
|
||
|
||
<div class="admin-form__field">
|
||
<label for="login-phone">شماره موبایل</label>
|
||
<div class="login-page__input-wrap">
|
||
<span class="login-page__field-icon" aria-hidden="true">
|
||
<AdminIcon name="phone" :size="18" />
|
||
</span>
|
||
<input
|
||
id="login-phone"
|
||
v-model="phone"
|
||
type="tel"
|
||
class="admin-input login-page__input"
|
||
placeholder="۰۹۱۲۱۱۱۱۲۲۲۲"
|
||
autocomplete="username"
|
||
inputmode="tel"
|
||
dir="ltr"
|
||
@input="auth.clearError()"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="admin-form__field">
|
||
<label for="login-password">رمز عبور</label>
|
||
<div class="login-page__input-wrap login-page__input-wrap--password">
|
||
<span class="login-page__field-icon" aria-hidden="true">
|
||
<AdminIcon name="lock" :size="18" />
|
||
</span>
|
||
<input
|
||
id="login-password"
|
||
v-model="password"
|
||
:type="showPassword ? 'text' : 'password'"
|
||
class="admin-input login-page__input login-page__input--with-toggle"
|
||
placeholder="رمز عبور"
|
||
autocomplete="current-password"
|
||
@input="auth.clearError()"
|
||
/>
|
||
<button
|
||
type="button"
|
||
class="login-page__toggle-password"
|
||
:aria-label="showPassword ? 'پنهان کردن رمز' : 'نمایش رمز'"
|
||
@click="showPassword = !showPassword"
|
||
>
|
||
<AdminIcon :name="showPassword ? 'eye-off' : 'eye'" :size="18" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<label class="login-page__remember">
|
||
<input v-model="remember" type="checkbox" />
|
||
<span>مرا به خاطر بسپار</span>
|
||
</label>
|
||
|
||
<button type="submit" class="admin-btn admin-btn--primary login-page__submit" :disabled="!isFormValid || auth.loading">
|
||
<span v-if="auth.loading" class="admin-btn__spinner" aria-hidden="true" />
|
||
ورود به پنل
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="login-page__brand">
|
||
<div class="login-page__brand-inner">
|
||
<p class="login-page__brand-badge">پارچه</p>
|
||
<h1 class="login-page__brand-title">پنل مدیریت سفارشهای عمده</h1>
|
||
<p class="login-page__brand-text">
|
||
مدیریت سفارشها، مشتریان، قیمتگذاری، چکها و گزارشهای فروش در یک محیط یکپارچه.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.login-page {
|
||
min-height: 100dvh;
|
||
display: grid;
|
||
grid-template-columns: 1.05fr 1fr;
|
||
background: var(--color-bg);
|
||
}
|
||
|
||
.login-page__brand {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 48px 40px;
|
||
background: linear-gradient(160deg, #062428 0%, #0a353c 55%, #0e454e 100%);
|
||
color: #fff;
|
||
}
|
||
|
||
.login-page__brand-inner {
|
||
max-width: 420px;
|
||
}
|
||
|
||
.login-page__brand-badge {
|
||
display: inline-flex;
|
||
margin: 0 0 16px;
|
||
padding: 6px 12px;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.12);
|
||
font-size: 0.8125rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.login-page__brand-title {
|
||
margin: 0 0 12px;
|
||
font-size: clamp(1.5rem, 2.4vw, 2rem);
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.login-page__brand-text {
|
||
margin: 0;
|
||
color: rgba(255, 255, 255, 0.78);
|
||
line-height: 1.8;
|
||
font-size: 0.9375rem;
|
||
}
|
||
|
||
.login-page__form-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 32px 24px;
|
||
}
|
||
|
||
.login-page__card {
|
||
width: min(100%, 420px);
|
||
padding: 28px 24px;
|
||
}
|
||
|
||
.login-page__header {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.login-page__title {
|
||
margin: 0 0 6px;
|
||
font-size: 1.25rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.login-page__subtitle {
|
||
margin: 0;
|
||
font-size: 0.875rem;
|
||
color: var(--color-text-muted);
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.login-page__mock-hint {
|
||
margin: 10px 0 0;
|
||
padding: 8px 10px;
|
||
border-radius: 8px;
|
||
background: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
||
color: var(--color-primary);
|
||
font-size: 0.8125rem;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.login-page__input-wrap {
|
||
position: relative;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.login-page__field-icon {
|
||
position: absolute;
|
||
right: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
display: inline-flex;
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
}
|
||
|
||
.login-page__input {
|
||
padding-right: 42px;
|
||
}
|
||
|
||
.login-page__input--with-toggle {
|
||
padding-left: 44px;
|
||
}
|
||
|
||
.login-page__toggle-password {
|
||
position: absolute;
|
||
left: 6px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
z-index: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 8px;
|
||
background: transparent;
|
||
color: var(--color-text-muted);
|
||
cursor: pointer;
|
||
transition: color 0.15s ease, background-color 0.15s ease;
|
||
}
|
||
|
||
.login-page__toggle-password:hover {
|
||
color: var(--color-primary);
|
||
background: var(--color-bg-muted, rgba(0, 0, 0, 0.04));
|
||
}
|
||
|
||
.login-page__remember {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 0.8125rem;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.login-page__remember input {
|
||
width: 16px;
|
||
height: 16px;
|
||
accent-color: var(--color-primary);
|
||
}
|
||
|
||
.login-page__submit {
|
||
width: 100%;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.login-page {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.login-page__form-wrap {
|
||
order: 2;
|
||
}
|
||
|
||
.login-page__brand {
|
||
order: 1;
|
||
padding: 28px 20px;
|
||
min-height: auto;
|
||
}
|
||
|
||
.login-page__brand-text {
|
||
font-size: 0.875rem;
|
||
}
|
||
}
|
||
</style>
|