chore: Fix the sign-up form

This commit is contained in:
iamsivin
2025-11-07 16:35:14 +05:30
parent 068bd8f083
commit 44579bdcad
2 changed files with 156 additions and 73 deletions
@@ -17,7 +17,7 @@ const props = defineProps({
messageType: {
type: String,
default: 'info',
validator: value => ['info', 'error', 'success'].includes(value),
validator: value => !value || ['info', 'error', 'success'].includes(value),
},
min: { type: String, default: '' },
max: { type: String, default: '' },
@@ -39,25 +39,26 @@ const uniqueId = computed(() => props.id || `input-${uid}`);
const isFocused = ref(false);
const inputRef = ref(null);
const messageClass = computed(() => {
switch (props.messageType) {
case 'error':
return 'text-n-ruby-9 dark:text-n-ruby-9';
case 'success':
return 'text-n-teal-10 dark:text-n-teal-10';
default:
return 'text-n-slate-11 dark:text-n-slate-11';
}
});
const MESSAGE_STYLES = {
error: 'text-n-ruby-9 dark:text-n-ruby-9',
success: 'text-n-teal-10 dark:text-n-teal-10',
info: 'text-n-slate-11 dark:text-n-slate-11',
};
const inputOutlineClass = computed(() => {
switch (props.messageType) {
case 'error':
return 'outline-n-ruby-8 dark:outline-n-ruby-8 hover:outline-n-ruby-9 dark:hover:outline-n-ruby-9 disabled:outline-n-ruby-8 dark:disabled:outline-n-ruby-8';
default:
return 'outline-n-weak dark:outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 disabled:outline-n-weak dark:disabled:outline-n-weak focus:outline-n-brand dark:focus:outline-n-brand';
}
});
const OUTLINE_STYLES = {
error:
'outline-n-ruby-8 dark:outline-n-ruby-8 hover:outline-n-ruby-9 dark:hover:outline-n-ruby-9 disabled:outline-n-ruby-8 dark:disabled:outline-n-ruby-8',
default:
'outline-n-weak dark:outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 disabled:outline-n-weak dark:disabled:outline-n-weak focus:outline-n-brand dark:focus:outline-n-brand',
};
const messageClass = computed(
() => MESSAGE_STYLES[props.messageType || 'info'] || MESSAGE_STYLES.info
);
const inputOutlineClass = computed(
() => OUTLINE_STYLES[props.messageType || 'info'] || OUTLINE_STYLES.default
);
const handleInput = event => {
let value = event.target.value;
@@ -145,7 +146,7 @@ onMounted(() => {
/>
<p
v-if="message"
class="min-w-0 mt-1 mb-0 text-xs truncate transition-all duration-500 ease-in-out"
class="min-w-0 mt-0.5 mb-0 text-xs truncate transition-all duration-500 ease-in-out"
:class="messageClass"
>
{{ message }}
@@ -1,14 +1,14 @@
<script setup>
import { ref, reactive, computed } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { required, minLength, email, sameAs } from '@vuelidate/validators';
import { required, minLength, email } from '@vuelidate/validators';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import { DEFAULT_REDIRECT_URL } from 'dashboard/constants/globals';
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import SimpleDivider from '../../../../../components/Divider/SimpleDivider.vue';
import FormInput from '../../../../../components/Form/Input.vue';
import Input from 'dashboard/components-next/input/Input.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import { isValidPassword } from 'shared/helpers/Validators';
@@ -61,6 +61,25 @@ const rules = computed(() => ({
const v$ = useVuelidate(rules, { credentials });
const blurredFields = reactive({
fullName: false,
accountName: false,
email: false,
password: false,
});
const shouldShowError = fieldName => {
// only when field has content and was blurred
const field = v$.value.credentials[fieldName];
const hasValue = credentials[fieldName] && credentials[fieldName].length > 0;
return hasValue && blurredFields[fieldName] && field.$error;
};
const shouldShowPasswordRequirementError = computed(() => {
const hasValue = credentials.password && credentials.password.length > 0;
return hasValue && v$.value.credentials.password.$error;
});
const globalConfig = computed(() => store.getters['globalConfig/get']);
const termsLink = computed(() => {
@@ -131,10 +150,6 @@ const passwordRequirementItems = computed(() => {
];
});
const passwordRequirementsMet = computed(() => {
return Object.values(passwordRequirements.value).every(Boolean);
});
const resetCaptcha = () => {
if (!globalConfig.value.hCaptchaSiteKey) return;
hCaptcha.value.reset();
@@ -152,7 +167,10 @@ const submit = async () => {
// For invisible captcha, there is no user input. We have to execute it if when
// the user submits the form. If 99.9% Passive mode is enabled, the users won't
// be prompted to solve the captcha
if (globalConfig.value.hCaptchaSiteKey && !credentials.hCaptchaClientResponse) {
if (
globalConfig.value.hCaptchaSiteKey &&
!credentials.hCaptchaClientResponse
) {
hCaptcha.value.execute();
return;
}
@@ -170,7 +188,7 @@ const submit = async () => {
}
};
const onRecaptchaVerified = (token) => {
const onRecaptchaVerified = token => {
credentials.hCaptchaClientResponse = token;
didCaptchaReset.value = false;
v$.value.$touch();
@@ -183,14 +201,37 @@ const onCaptchaExpired = () => {
didCaptchaReset.value = true;
};
const onCaptchaError = (error) => {
const onCaptchaError = () => {
credentials.hCaptchaClientResponse = '';
didCaptchaReset.value = true;
useAlert(t('SET_NEW_PASSWORD.CAPTCHA.ERROR'));
};
const handleFieldFocus = fieldName => {
// Clear blur state when user focuses field again
blurredFields[fieldName] = false;
};
const handleFieldBlur = fieldName => {
// Mark field as blurred and touch for validation
blurredFields[fieldName] = true;
v$.value.credentials[fieldName].$touch();
};
const handlePasswordInput = () => {
// Touch password field for real-time validation in requirements panel
if (credentials.password && credentials.password.length > 0) {
v$.value.credentials.password.$touch();
}
};
const handlePasswordFocus = () => {
handleFieldFocus('password');
isPasswordFocused.value = true;
};
const handlePasswordBlur = () => {
v$.value.credentials.password.$touch();
handleFieldBlur('password');
// Delay hiding requirements to allow submit button click to register
setTimeout(() => {
isPasswordFocused.value = false;
@@ -201,81 +242,122 @@ const handlePasswordBlur = () => {
<template>
<div class="flex-1 px-1 overflow-auto">
<form class="space-y-3" @submit.prevent="submit">
<div class="grid grid-cols-2 gap-2">
<FormInput
<div class="flex items-start gap-2">
<Input
v-model="credentials.fullName"
name="full_name"
autocomplete="name"
class="flex-1"
:class="{ error: v$.credentials.fullName.$error }"
:label="$t('REGISTER.FULL_NAME.LABEL')"
:placeholder="$t('REGISTER.FULL_NAME.PLACEHOLDER')"
:has-error="v$.credentials.fullName.$error"
:error-message="$t('REGISTER.FULL_NAME.ERROR')"
@blur="v$.credentials.fullName.$touch"
:message-type="shouldShowError('fullName') ? 'error' : ''"
:message="
shouldShowError('fullName') ? $t('REGISTER.FULL_NAME.ERROR') : ''
"
@focus="handleFieldFocus('fullName')"
@blur="handleFieldBlur('fullName')"
/>
<FormInput
<Input
v-model="credentials.accountName"
name="account_name"
autocomplete="organization"
class="flex-1"
:class="{ error: v$.credentials.accountName.$error }"
:label="$t('REGISTER.COMPANY_NAME.LABEL')"
:placeholder="$t('REGISTER.COMPANY_NAME.PLACEHOLDER')"
:has-error="v$.credentials.accountName.$error"
:error-message="$t('REGISTER.COMPANY_NAME.ERROR')"
@blur="v$.credentials.accountName.$touch"
:message-type="shouldShowError('accountName') ? 'error' : ''"
:message="
shouldShowError('accountName')
? $t('REGISTER.COMPANY_NAME.ERROR')
: ''
"
@focus="handleFieldFocus('accountName')"
@blur="handleFieldBlur('accountName')"
/>
</div>
<FormInput
<Input
v-model="credentials.email"
type="email"
name="email_address"
:class="{ error: v$.credentials.email.$error }"
autocomplete="email"
:label="$t('REGISTER.EMAIL.LABEL')"
:placeholder="$t('REGISTER.EMAIL.PLACEHOLDER')"
:has-error="v$.credentials.email.$error"
:error-message="$t('REGISTER.EMAIL.ERROR')"
@blur="v$.credentials.email.$touch"
:message-type="shouldShowError('email') ? 'error' : ''"
:message="shouldShowError('email') ? $t('REGISTER.EMAIL.ERROR') : ''"
@focus="handleFieldFocus('email')"
@blur="handleFieldBlur('email')"
/>
<FormInput
<Input
v-model="credentials.password"
type="password"
name="password"
:class="{ error: v$.credentials.password.$error }"
autocomplete="new-password"
:label="$t('LOGIN.PASSWORD.LABEL')"
:placeholder="$t('SET_NEW_PASSWORD.PASSWORD.PLACEHOLDER')"
:has-error="v$.credentials.password.$error"
:message-type="shouldShowError('password') ? 'error' : ''"
aria-describedby="password-requirements"
@focus="isPasswordFocused = true"
@input="handlePasswordInput"
@focus="handlePasswordFocus"
@blur="handlePasswordBlur"
/>
<div
class="grid transition-all duration-300 ease-out"
:class="isPasswordFocused ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'"
class="grid transition-all duration-300 ease-in-out"
:class="[
isPasswordFocused ? 'grid-rows-[1fr] !mt-1' : 'grid-rows-[0fr] !mt-0',
]"
>
<div class="overflow-hidden">
<div
v-show="isPasswordFocused"
id="password-requirements"
class="text-xs rounded-md px-4 py-3 outline outline-1 outline-n-weak bg-n-alpha-black2"
<Transition
enter-active-class="transition-all duration-300 ease-in-out"
leave-active-class="transition-all duration-300 ease-in-out"
enter-from-class="opacity-0 -translate-y-2"
enter-to-class="opacity-100 translate-y-0"
leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 -translate-y-2"
>
<ul role="list" class="grid grid-cols-2 gap-1">
<li
v-for="item in passwordRequirementItems"
:key="item.id"
class="inline-flex gap-1 items-start"
>
<Icon
class="flex-none flex-shrink-0 w-3 mt-0.5"
:icon="item.met ? 'i-lucide-circle-check-big' : 'i-lucide-circle'"
:class="item.met ? 'text-n-teal-10' : 'text-n-slate-10'"
/>
<div
v-if="isPasswordFocused"
id="password-requirements"
class="text-xs rounded-lg px-3 py-2.5 outline outline-1 -outline-offset-1 outline-n-weak bg-n-alpha-black2"
>
<ul role="list" class="grid grid-cols-2 gap-1">
<li
v-for="item in passwordRequirementItems"
:key="item.id"
class="inline-flex gap-1 items-start"
>
<Icon
class="flex-none flex-shrink-0 w-3 mt-0.5"
:icon="
item.met
? 'i-lucide-circle-check'
: shouldShowPasswordRequirementError
? 'i-lucide-circle-x'
: 'i-lucide-circle-dot'
"
:class="
item.met
? 'text-n-teal-9'
: shouldShowPasswordRequirementError
? 'text-n-ruby-9'
: 'text-n-slate-10'
"
/>
<span :class="item.met ? 'text-n-slate-11' : 'text-n-slate-10'">
{{ item.label }}
</span>
</li>
</ul>
</div>
<span
:class="
item.met
? 'text-n-teal-11'
: shouldShowPasswordRequirementError
? 'text-n-ruby-11'
: 'text-n-slate-11'
"
>
{{ item.label }}
</span>
</li>
</ul>
</div>
</Transition>
</div>
</div>
<div v-if="globalConfig.hCaptchaSiteKey" class="mb-3">