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

302 lines
7.9 KiB
Vue
Raw Normal View History

<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>