Files
nademitex_admin/src/views/auth/LoginView.vue
T

302 lines
7.9 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>