Add: Auth protection & error notifications

This commit is contained in:
supalerk-ar66 2026-01-14 16:47:29 +07:00
parent 5d508c4731
commit 51668bae9d
6 changed files with 53 additions and 37 deletions

View file

@ -1,4 +1,4 @@
// generated by the @nuxtjs/tailwindcss <https://github.com/nuxt-modules/tailwindcss> module at 14/1/2569 15:13:15 // generated by the @nuxtjs/tailwindcss <https://github.com/nuxt-modules/tailwindcss> module at 14/1/2569 16:44:22
import "@nuxtjs/tailwindcss/config-ctx" import "@nuxtjs/tailwindcss/config-ctx"
import configMerger from "@nuxtjs/tailwindcss/merger"; import configMerger from "@nuxtjs/tailwindcss/merger";

View file

@ -77,6 +77,11 @@ export const useAuth = () => {
} }
if (data.value) { if (data.value) {
// Validation: Only allow STUDENT role to login
if (data.value.user.role.code !== 'STUDENT') {
return { success: false, error: 'Email ไม่ถูกต้อง' }
}
token.value = data.value.token token.value = data.value.token
refreshToken.value = data.value.refreshToken // Save refresh token refreshToken.value = data.value.refreshToken // Save refresh token

View file

@ -20,29 +20,29 @@ export function useFormValidation() {
return re.test(email) return re.test(email)
} }
const validateField = (value: string, rules: ValidationRule, fieldName: string, formData?: Record<string, string>): string | null => { const validateField = (value: string, rules: ValidationRule, fieldName: string, formData?: Record<string, string>, messages?: Record<string, string>): string | null => {
if (rules.required && !value.trim()) { if (rules.required && !value.trim()) {
return `กรุณากรอก${fieldName}` return messages?.required || `กรุณากรอก${fieldName}`
} }
if (rules.minLength && value.length < rules.minLength) { if (rules.minLength && value.length < rules.minLength) {
return `${fieldName}ต้องมีอย่างน้อย ${rules.minLength} ตัวอักษร` return messages?.minLength || `${fieldName}ต้องมีอย่างน้อย ${rules.minLength} ตัวอักษร`
} }
if (rules.maxLength && value.length > rules.maxLength) { if (rules.maxLength && value.length > rules.maxLength) {
return `${fieldName}ต้องไม่เกิน ${rules.maxLength} ตัวอักษร` return messages?.maxLength || `${fieldName}ต้องไม่เกิน ${rules.maxLength} ตัวอักษร`
} }
if (rules.email && value && !validateEmail(value)) { if (rules.email && value && !validateEmail(value)) {
return 'รูปแบบอีเมลไม่ถูกต้อง' return messages?.email || 'รูปแบบอีเมลไม่ถูกต้อง'
} }
if (rules.pattern && value && !rules.pattern.test(value)) { if (rules.pattern && value && !rules.pattern.test(value)) {
return `${fieldName}รูปแบบไม่ถูกต้อง` return messages?.pattern || `${fieldName}รูปแบบไม่ถูกต้อง`
} }
if (rules.match && formData && value !== formData[rules.match]) { if (rules.match && formData && value !== formData[rules.match]) {
return 'รหัสผ่านไม่ตรงกัน' return messages?.match || 'รหัสผ่านไม่ตรงกัน'
} }
if (rules.custom) { if (rules.custom) {
@ -54,13 +54,13 @@ export function useFormValidation() {
const validate = ( const validate = (
formData: Record<string, string>, formData: Record<string, string>,
validationRules: Record<string, { rules: ValidationRule; label: string }> validationRules: Record<string, { rules: ValidationRule; label: string; messages?: Record<string, string> }>
): boolean => { ): boolean => {
const newErrors: FieldErrors = {} const newErrors: FieldErrors = {}
let isValid = true let isValid = true
for (const [fieldKey, config] of Object.entries(validationRules)) { for (const [fieldKey, config] of Object.entries(validationRules)) {
const error = validateField(formData[fieldKey] || '', config.rules, config.label, formData) const error = validateField(formData[fieldKey] || '', config.rules, config.label, formData, config.messages)
if (error) { if (error) {
newErrors[fieldKey] = error newErrors[fieldKey] = error
isValid = false isValid = false

View file

@ -11,7 +11,7 @@ useHead({
}) })
const handleError = () => { const handleError = () => {
clearError({ redirect: '/home' }) clearError({ redirect: '/' })
} }
</script> </script>

View file

@ -29,8 +29,22 @@ const loginForm = reactive({
// Validation rules definition // Validation rules definition
const loginRules = { const loginRules = {
email: { rules: { required: true, email: true }, label: 'อีเมล' }, email: {
password: { rules: { required: true, minLength: 6 }, label: 'รหัสผ่าน' } rules: { required: true, email: true },
label: 'อีเมล',
messages: {
required: 'กรุณากรอกอีเมลของคุณ',
email: 'กรุณากรอกอีเมลให้ถูกต้อง (you@example.com)'
}
},
password: {
rules: { required: true, minLength: 8 },
label: 'รหัสผ่าน',
messages: {
required: 'กรุณากรอกรหัสผ่าน',
minLength: 'กรุณากรอกรหัสผ่าน (อย่างน้อย 8 ตัวอักษร)'
}
}
} }
/** /**
@ -50,19 +64,17 @@ const handleLogin = async () => {
isLoading.value = false isLoading.value = false
if (result.success) { if (result.success) {
// Redirect based on user role router.push('/dashboard')
const role = user.value?.role?.code
if (role === 'ADMIN') {
router.push('/admin')
} else if (role === 'INSTRUCTOR') {
router.push('/instructor')
} else {
router.push('/dashboard')
}
} else { } else {
// Show error from API or fallback // Show error on specific fields
alert(result.error || 'อีเมลหรือรหัสผ่านไม่ถูกต้อง') if (result.error === 'ไม่พบอีเมลในระบบ') {
errors.value.email = 'กรุณาเช็ค Email หรือ รหัสผ่านใหม่อีกครั้ง'
errors.value.password = 'กรุณาเช็ค Email หรือ รหัสผ่านใหม่อีกครั้ง'
} else if (result.error === 'Email ไม่ถูกต้อง') {
errors.value.email = result.error
} else {
errors.value.password = 'กรอกรหัสผ่านไม่ถูกต้อง'
}
} }
} }
</script> </script>
@ -84,7 +96,7 @@ const handleLogin = async () => {
</div> </div>
<!-- Login Form --> <!-- Login Form -->
<form @submit.prevent="handleLogin"> <form @submit.prevent="handleLogin" novalidate>
<!-- Email Input --> <!-- Email Input -->
<FormInput <FormInput
v-model="loginForm.email" v-model="loginForm.email"

View file

@ -104,6 +104,16 @@ const handleRegister = async () => {
<!-- REGISTER FORM --> <!-- REGISTER FORM -->
<form @submit.prevent="handleRegister"> <form @submit.prevent="handleRegister">
<!-- Username -->
<FormInput
v-model="registerForm.username"
label="ชื่อผู้ใช้"
placeholder="username"
:error="errors.username"
required
class="dark-form-input"
@update:model-value="clearFieldError('username')"
/>
<!-- Email --> <!-- Email -->
<FormInput <FormInput
v-model="registerForm.email" v-model="registerForm.email"
@ -116,17 +126,6 @@ const handleRegister = async () => {
@update:model-value="clearFieldError('email')" @update:model-value="clearFieldError('email')"
/> />
<!-- Username -->
<FormInput
v-model="registerForm.username"
label="ชื่อผู้ใช้"
placeholder="username"
:error="errors.username"
required
class="dark-form-input"
@update:model-value="clearFieldError('username')"
/>
<!-- Password Fields --> <!-- Password Fields -->
<FormInput <FormInput
v-model="registerForm.password" v-model="registerForm.password"