conflict-nuxt-4/app/components/lazy-load/auth/LoginForm.vue
2026-02-14 15:10:22 +03:30

372 lines
12 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.

<template>
<div
class="bg-white dark:bg-dark-primary-800 flex items-center justify-center p-4"
>
<!-- Container -->
<div class="w-full max-w-sm space-y-6">
<!-- Header -->
<div class="text-center space-y-1">
<div
class="w-12 h-12 mx-auto mb-3 dark:bg-primary-800 rounded-full flex items-center justify-center"
>
<!-- <span class="text-white text-lg font-bold">پ</span> -->
<img :src="useSystemTheme.logo.value" alt="" class="h-9 w-9" />
</div>
<h1 class="text-xl font-medium text-primary-900 dark:text-white">
ورود به حساب
</h1>
<!-- <p class="text-xs text-primary-400 dark:text-primary-400">
زیستبوم پژوهشگران
</p> -->
</div>
<!-- Form -->
<div class="space-y-3">
<!-- Username -->
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>نام کاربری یا ایمیل</label
>
<div class="relative">
<UIcon
name="i-heroicons-envelope"
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-primary-400 dark:text-primary-500 w-4 h-4"
/>
<input
v-model="username"
type="text"
placeholder="example@domain.com"
class="w-full px-3 py-2 pr-10 text-sm border border-primary-300 dark:border-primary-700 rounded focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-600 focus:border-transparent bg-white dark:bg-primary-800 text-primary-900 dark:text-white placeholder-primary-400 dark:placeholder-primary-500"
dir="rtl"
/>
</div>
</div>
<!-- Password -->
<div>
<div class="flex justify-between items-center mb-1">
<label
class="text-xs font-medium text-primary-700 dark:text-primary-300"
>رمز عبور</label
>
<a
href="#"
@click.prevent="goForgotPassword"
class="text-xs text-primary-500 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
>
فراموش کرده‌اید؟
</a>
</div>
<div class="relative">
<UIcon
name="i-heroicons-lock-closed"
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-primary-400 dark:text-primary-500 w-4 h-4"
/>
<input
v-model="password"
:type="showPassword ? 'text' : 'password'"
placeholder="••••••••"
class="w-full px-3 py-2 pr-10 text-sm border border-primary-300 dark:border-primary-700 rounded focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-600 focus:border-transparent bg-white dark:bg-primary-800 text-primary-900 dark:text-white placeholder-primary-400 dark:placeholder-primary-500"
dir="rtl"
/>
<button
type="button"
@click="showPassword = !showPassword"
class="absolute left-2 top-1/2 transform -translate-y-1/2 text-primary-400 dark:text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
>
<UIcon
:name="
showPassword ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'
"
class="w-4 h-4 mt-1"
/>
</button>
</div>
</div>
<!-- CAPTCHA -->
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>کد امنیتی</label
>
<div class="flex gap-2">
<div class="flex-1">
<input
v-model="captcha"
type="text"
placeholder="کد را وارد کنید"
class="w-full px-3 py-2 text-sm border border-primary-300 dark:border-primary-700 rounded focus:outline-none focus:ring-1 focus:ring-primary-400 dark:focus:ring-primary-600 focus:border-transparent bg-white dark:bg-primary-800 text-primary-900 dark:text-white placeholder-primary-400 dark:placeholder-primary-500 text-center"
dir="rtl"
/>
</div>
<div class="flex items-center gap-1">
<div
class="w-24 h-10 border border-primary-300 dark:border-primary-700 rounded overflow-hidden bg-primary-50 dark:bg-primary-800 flex items-center justify-center"
>
<img
:src="captchaImage"
alt="کد امنیتی"
class="w-full h-full object-contain"
/>
</div>
<button
@click="resetCaptcha"
:disabled="loadingCaptcha"
class="w-8 h-8 border border-primary-300 dark:border-primary-700 rounded flex items-center justify-center hover:bg-primary-50 dark:hover:bg-primary-800 disabled:opacity-50 text-primary-700 dark:text-primary-300"
>
<UIcon
name="i-heroicons-arrow-path"
class="w-4 h-4"
:class="{ 'animate-spin': loadingCaptcha }"
/>
</button>
</div>
</div>
</div>
<!-- Login Button -->
<button
@click="handleLogin"
:disabled="loading"
class="w-full py-2.5 bg-primary dark:bg-primary-800 text-white text-sm font-medium rounded hover:bg-primary-600 dark:hover:bg-primary-700 active:bg-primary-900 dark:active:bg-dark-primary-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
<span v-if="loading" class="flex items-center justify-center gap-2">
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 animate-spin" />
در حال ورود...
</span>
<span v-else> ورود </span>
</button>
</div>
<!-- Divider -->
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div
class="w-full border-t border-primary-200 dark:border-primary-700"
></div>
</div>
<div class="relative flex justify-center text-xs">
<span
class="px-2 bg-white dark:bg-primary-900 text-primary-500 dark:text-primary-400"
>یا</span
>
</div>
</div>
<!-- Alternative Options -->
<div class="space-y-2">
<button
@click="goLoginPhonePage(false)"
class="w-full py-2 border border-primary-300 dark:border-primary-700 text-primary-700 dark:text-primary-300 text-sm font-medium rounded hover:bg-primary-50 dark:hover:bg-primary-800 transition-colors"
>
ورود با شماره تلفن
</button>
<button
@click="loginGuest"
class="w-full py-2 border border-primary-300 dark:border-primary-700 text-primary-700 dark:text-primary-300 text-sm font-medium rounded hover:bg-primary-50 dark:hover:bg-primary-800 transition-colors"
>
ورود به عنوان مهمان
</button>
</div>
<!-- Footer Note -->
<p
class="text-xs text-primary-400 dark:text-primary-500 text-center pt-4"
>
با ورود، با
<a
href="#"
class="text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-300"
>قوانین</a
>
موافقت میکنید
</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useAuthStore } from "@/stores/authStore";
import { useNuxtApp } from "#app";
import { navigateTo } from "#imports";
import { useToast } from "#imports";
import { composSystemTheme } from "@/composables/composSystemTheme";
import { getUserPermission } from "@/stores/permissionStore";
const useSystemTheme = composSystemTheme();
const toast = useToast();
// --- State ---
const username = ref("");
const password = ref("");
const captcha = ref("");
const captchaImage = ref("");
const loading = ref(false);
const loadingCaptcha = ref(false);
const showPassword = ref(false);
// --- Nuxt App & Store ---
const nuxtApp = useNuxtApp();
const { $http: httpService } = nuxtApp;
const authStore = useAuthStore();
// --- Navigation ---
const goLoginPhonePage = (isReset) => {
navigateTo("/loginphone");
};
const goForgotPassword = () => {
navigateTo("/forget");
};
const loginGuest = () => {
loading.value = true;
httpService
.getRequest("/login/user/validate", {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "GuestAccess",
},
})
.then((res) => {
authStore.setUser(res);
const userPermissionStore = getUserPermission();
userPermissionStore.fetchUserPermissions();
// console.log("Permissions:", userPermissionStore.permissions);
toast.add({
title: res.message || "ورود موفقیت‌آمیز بود",
icon: "i-lucide-calendar-days",
});
navigateTo({
name: "DashboardBasePage",
});
})
.catch((error) => {
console.log("error ==> ", error);
toast.add({
title: "خطا",
description: "خطا در ورود",
color: "red",
});
loadCaptcha();
})
.finally(() => {
loading.value = false;
});
// toast.add({
// title: "خطا",
// description: "ورود به عنوان مهمان در حال توسعه است",
// color: "red",
// });
};
// --- CAPTCHA ---
const loadCaptcha = async () => {
loadingCaptcha.value = true;
try {
const string = await httpService.getRequest("/login/captcha/makeimage");
captchaImage.value = `data:image/jpeg;base64,${string}`;
} catch {
captchaImage.value = "/assets/common/img/captcha.png";
} finally {
loadingCaptcha.value = false;
}
};
const resetCaptcha = () => {
loadCaptcha();
};
// --- Login Logic ---
const handleLogin = () => {
loading.value = true;
const formData = {
username: username.value,
password: password.value,
captcha: captcha.value,
};
httpService
.postRequest("/login/user/login", formData)
.then((res) => {
authStore.setUser(res);
const userPermissionStore = getUserPermission();
userPermissionStore.fetchUserPermissions();
// console.log("Permissions:", userPermissionStore.permissions);
toast.add({
title: res.message || "ورود موفقیت‌آمیز بود",
icon: "i-lucide-calendar-days",
});
navigateTo({
name: "DashboardBasePage",
});
})
.catch((error) => {
console.log("error ==> ", error);
toast.add({
title: "خطا",
description: "خطا در ورود",
color: "red",
});
loadCaptcha();
})
.finally(() => {
loading.value = false;
});
};
onMounted(() => {
loadCaptcha();
});
</script>
<style scoped>
input:focus {
outline: none;
}
/* بهبود نمایش در حالت دارک مود */
/* @media (prefers-color-scheme: dark) {
.dark\:bg-primary-900 {
background-color: #111827;
}
.dark\:bg-primary-800 {
background-color: #1f2937;
}
.dark\:border-primary-700 {
border-color: #374151;
}
.dark\:text-white {
color: #f9fafb;
}
.dark\:text-primary-300 {
color: #d1d5db;
}
.dark\:placeholder-primary-500 {
&::placeholder {
color: #6b7280;
}
}
} */
</style>