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

395 lines
14 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-md 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">
<!-- Name & Last Name -->
<div class="grid grid-cols-2 gap-3">
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>نام</label
>
<input
v-model.trim="name"
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"
/>
<p v-if="showError(v$.name)" class="mt-1 text-xs text-red-500">
الزامی
</p>
</div>
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>نام خانوادگی</label
>
<input
v-model.trim="last_name"
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"
/>
<p v-if="showError(v$.last_name)" class="mt-1 text-xs text-red-500">
الزامی
</p>
</div>
</div>
<!-- Username -->
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>نام کاربری</label
>
<input
v-model.trim="username"
type="text"
placeholder="نام کاربری"
class="w-full px-5 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"
dir="rtl"
/>
<p v-if="showError(v$.username)" class="mt-1 text-xs text-red-500">
الزامی
</p>
</div>
<!-- Email -->
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>ایمیل</label
>
<input
v-model.trim="email"
type="email"
placeholder="example@domain.com"
class="w-full px-5 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"
dir="rtl"
/>
<p v-if="showError(v$.email)" class="mt-1 text-xs text-red-500">
ایمیل معتبر
</p>
</div>
<!-- Password -->
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>رمز عبور</label
>
<div class="relative">
<input
v-model.trim="password"
:type="passwordFieldType"
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"
@focus="isFocusedOnPassword = true"
@blur="isFocusedOnPassword = false"
/>
<button
type="button"
@click="togglePasswordVisibility"
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"
>
<!-- <span v-if="isPasswordVisible">👁</span>
<span v-else>👁‍🗨</span> -->
<UIcon
:name="
isPasswordVisible
? 'i-heroicons-eye-slash'
: 'i-heroicons-eye'
"
class="w-4 h-4 mt-1"
/>
</button>
</div>
<!-- Password rules -->
<div
v-if="isFocusedOnPassword"
class="mt-2 p-2 border border-primary-200 dark:border-primary-700 rounded bg-primary-50 dark:bg-primary-800 text-xs space-y-1"
>
<div :class="ruleClass(v$.password.minLength)">
• حداقل ۸ کاراکتر
</div>
<div :class="ruleClass(v$.password.hasLowerCase)">
• حرف کوچک انگلیسی
</div>
<div :class="ruleClass(v$.password.hasUpperCase)">
• حرف بزرگ انگلیسی
</div>
<div :class="ruleClass(v$.password.hasSpecialChar)">
• کاراکتر خاص (!@#$%^&*)
</div>
</div>
</div>
<!-- Repeat Password -->
<div>
<label
class="block text-xs font-medium text-primary-700 dark:text-primary-300 mb-1"
>تکرار رمز عبور</label
>
<div class="relative">
<input
v-model.trim="repassword"
:type="repasswordFieldType"
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="toggleRepasswordVisibility"
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"
>
<!-- <span v-if="isRepasswordVisible">👁</span>
<span v-else>👁‍🗨</span> -->
<UIcon
:name="
isRepasswordVisible
? '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.trim="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="getCaptcha"
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 text-primary-700 dark:text-primary-300"
>
</button>
</div>
</div>
<p v-if="showError(v$.captcha)" class="mt-1 text-xs text-red-500">
الزامی
</p>
</div>
<!-- Errors -->
<div
v-if="submitStatus === 'ERROR'"
class="text-xs text-red-500 space-y-0.5"
>
<p v-if="v$.password.required.$invalid">• رمز عبور الزامی است</p>
<p v-if="v$.password.minLength.$invalid">
• رمز عبور حداقل ۸ کاراکتر
</p>
<p v-if="v$.repassword.required.$invalid">• تکرار رمز الزامی است</p>
<p v-if="v$.repassword.sameAsPassword.$invalid">
• رمزها یکسان نیستند
</p>
</div>
<!-- Register Error -->
<p v-if="registerError" class="text-xs text-red-500">
{{ registerError }}
</p>
<!-- Submit Button -->
<button
:disabled="loading"
@click="submitRegister"
class="w-full py-2.5 bg-primary-900 dark:bg-primary-800 text-white text-sm font-medium rounded hover:bg-primary-800 dark:hover:bg-primary-700 active:bg-primary-900 dark:active:bg-primary-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{{ loading ? "در حال ایجاد حساب..." : "ایجاد حساب" }}
</button>
<!-- Login Link -->
<div
class="text-center pt-4 border-t border-primary-200 dark:border-primary-700"
>
<p class="text-xs text-primary-500 dark:text-primary-400">
قبلاً حساب دارید؟
<a
href="/login"
class="text-primary-700 dark:text-primary-300 hover:text-primary-900 dark:hover:text-white font-medium"
>
ورود
</a>
</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import useVuelidate from "@vuelidate/core";
import { required, minLength, sameAs } from "@vuelidate/validators";
import { useNuxtApp } from "#app";
import { navigateTo } from "#imports";
import { composSystemTheme } from "@/composables/composSystemTheme";
const useSystemTheme = composSystemTheme();
const name = ref("");
const last_name = ref("");
const username = ref("");
const email = ref("");
const password = ref("");
const repassword = ref("");
const captcha = ref("");
const captchaImage = ref("");
const isFocusedOnPassword = ref(false);
const isPasswordVisible = ref(false);
const isRepasswordVisible = ref(false);
const submitStatus = ref(null);
const loading = ref(false);
const registerError = ref("");
const rules = {
name: { required },
last_name: { required },
username: { required },
email: { required },
password: {
required,
minLength: minLength(8),
hasLowerCase: (v) => /[a-z]/.test(v),
hasUpperCase: (v) => /[A-Z]/.test(v),
hasSpecialChar: (v) => /[!@#$%^&*]/.test(v),
},
repassword: {
required,
sameAsPassword: sameAs(password),
},
captcha: { required },
};
const v$ = useVuelidate(rules, {
name,
last_name,
username,
email,
password,
repassword,
captcha,
});
const passwordFieldType = computed(() =>
isPasswordVisible.value ? "text" : "password"
);
const repasswordFieldType = computed(() =>
isRepasswordVisible.value ? "text" : "password"
);
const togglePasswordVisibility = () =>
(isPasswordVisible.value = !isPasswordVisible.value);
const toggleRepasswordVisibility = () =>
(isRepasswordVisible.value = !isRepasswordVisible.value);
const showError = (field) => submitStatus.value === "ERROR" && field.$invalid;
const ruleClass = (rule) => (rule.$invalid ? "text-red-500" : "text-green-600");
const getCaptcha = async () => {
try {
const { $http } = useNuxtApp();
const res = await $http.getRequest("/login/captcha/makeimage");
captchaImage.value = `data:image/jpeg;base64,${res}`;
} catch (err) {
console.error(err);
captchaImage.value = "";
}
};
onMounted(getCaptcha);
const submitRegister = async () => {
const isValid = await v$.value.$validate();
if (!isValid) {
submitStatus.value = "ERROR";
return;
}
loading.value = true;
submitStatus.value = "PENDING";
registerError.value = "";
try {
const { $http } = useNuxtApp();
const baseUrl = import.meta.env.VITE_AUTH_BASE_URL;
const res = await $http.postRequest("login/user/register", {
name: name.value,
last_name: last_name.value,
username: username.value,
email: email.value,
password: password.value,
captcha: captcha.value,
});
// ذخیره تو localStorage
const id_token = useStorage("id_token", "");
id_token.value = res.data.token;
const userStorage = useStorage("user", {});
userStorage.value = res.data;
navigateTo({
name: "DashboardBasePage",
});
} catch (err) {
registerError.value = err?.response?.data?.message || err.message;
await getCaptcha();
} finally {
loading.value = false;
}
};
</script>
<style scoped>
input:focus {
outline: none;
}
</style>