mitlist/fe/src/pages/SignupPage.vue

204 lines
6.4 KiB
Vue

<template>
<main class="flex items-center justify-center page-container">
<div class="card signup-card">
<div class="card-header">
<h3>Sign Up</h3>
</div>
<div class="card-body">
<form @submit.prevent="onSubmit" class="form-layout">
<div class="form-group mb-2">
<label for="name" class="form-label">Full Name</label>
<input type="text" id="name" v-model="name" class="form-input" required autocomplete="name" />
<p v-if="formErrors.name" class="form-error-text">{{ formErrors.name }}</p>
</div>
<div class="form-group mb-2">
<label for="email" class="form-label">Email</label>
<input type="email" id="email" v-model="email" class="form-input" required autocomplete="email" />
<p v-if="formErrors.email" class="form-error-text">{{ formErrors.email }}</p>
</div>
<div class="form-group mb-2">
<label for="password" class="form-label">Password</label>
<div class="input-with-icon-append">
<input :type="isPwdVisible ? 'text' : 'password'" id="password" v-model="password" class="form-input"
required autocomplete="new-password" />
<button type="button" @click="isPwdVisible = !isPwdVisible" class="icon-append-btn"
aria-label="Toggle password visibility">
<svg class="icon icon-sm">
<use :xlink:href="isPwdVisible ? '#icon-settings' : '#icon-settings'"></use>
</svg> <!-- Placeholder for visibility icons -->
</button>
</div>
<p v-if="formErrors.password" class="form-error-text">{{ formErrors.password }}</p>
</div>
<div class="form-group mb-3">
<label for="confirmPassword" class="form-label">Confirm Password</label>
<input :type="isPwdVisible ? 'text' : 'password'" id="confirmPassword" v-model="confirmPassword"
class="form-input" required autocomplete="new-password" />
<p v-if="formErrors.confirmPassword" class="form-error-text">{{ formErrors.confirmPassword }}</p>
</div>
<p v-if="formErrors.general" class="alert alert-error form-error-text">{{ formErrors.general }}</p>
<button type="submit" class="btn btn-primary w-full mt-2" :disabled="loading">
<span v-if="loading" class="spinner-dots-sm" role="status"><span /><span /><span /></span>
Sign Up
</button>
<div class="text-center mt-2">
<router-link to="auth/login" class="link-styled">Already have an account? Login</router-link>
</div>
</form>
</div>
</div>
</main>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useAuthStore } from '@/stores/auth'; // Assuming path is correct
import { useNotificationStore } from '@/stores/notifications';
const router = useRouter();
const authStore = useAuthStore();
const notificationStore = useNotificationStore();
const name = ref('');
const email = ref('');
const password = ref('');
const confirmPassword = ref('');
const isPwdVisible = ref(false);
const loading = ref(false);
const formErrors = ref<{ name?: string; email?: string; password?: string; confirmPassword?: string; general?: string }>({});
const isValidEmail = (val: string): boolean => {
const emailPattern = /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/;
return emailPattern.test(val);
};
const validateForm = (): boolean => {
formErrors.value = {};
if (!name.value.trim()) {
formErrors.value.name = 'Name is required';
}
if (!email.value.trim()) {
formErrors.value.email = 'Email is required';
} else if (!isValidEmail(email.value)) {
formErrors.value.email = 'Invalid email format';
}
if (!password.value) {
formErrors.value.password = 'Password is required';
} else if (password.value.length < 8) {
formErrors.value.password = 'Password must be at least 8 characters';
}
if (!confirmPassword.value) {
formErrors.value.confirmPassword = 'Please confirm your password';
} else if (password.value !== confirmPassword.value) {
formErrors.value.confirmPassword = 'Passwords do not match';
}
return Object.keys(formErrors.value).length === 0;
};
const onSubmit = async () => {
if (!validateForm()) {
return;
}
loading.value = true;
formErrors.value.general = undefined;
try {
await authStore.signup({
name: name.value,
email: email.value,
password: password.value,
});
notificationStore.addNotification({ message: 'Account created successfully. Please login.', type: 'success' });
router.push('auth/login');
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Signup failed. Please try again.';
formErrors.value.general = message;
console.error(message, error);
notificationStore.addNotification({ message, type: 'error' });
} finally {
loading.value = false;
}
};
</script>
<style scoped>
/* Using styles from LoginPage.vue where applicable */
.page-container {
min-height: 100vh;
min-height: 100dvh;
padding: 1rem;
}
.signup-card {
width: 100%;
max-width: 400px;
}
/* form-layout, w-full, mt-2, text-center styles were removed as utility classes from Valerie UI are used directly or are available. */
.link-styled {
color: var(--primary);
text-decoration: none;
border-bottom: 2px solid transparent;
transition: border-color var(--transition-speed) var(--transition-ease-out);
}
.link-styled:hover,
.link-styled:focus {
border-bottom-color: var(--primary);
}
.form-error-text {
color: var(--danger);
font-size: 0.85rem;
margin-top: 0.25rem;
}
.alert.form-error-text {
/* For general error message */
padding: 0.75rem 1rem;
margin-bottom: 1rem;
}
.input-with-icon-append {
position: relative;
display: flex;
}
.input-with-icon-append .form-input {
padding-right: 3rem;
}
.icon-append-btn {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 3rem;
background: transparent;
border: none;
border-left: var(--border);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--dark);
opacity: 0.7;
}
.icon-append-btn:hover,
.icon-append-btn:focus {
opacity: 1;
background-color: rgba(0, 0, 0, 0.03);
}
.icon-append-btn .icon {
margin: 0;
}
</style>