feat: introduce user profile page with options to edit personal details and change password.

This commit is contained in:
supalerk-ar66 2026-01-26 16:29:37 +07:00
parent 78a26fc2e1
commit 92ff037150
3 changed files with 75 additions and 48 deletions

View file

@ -100,7 +100,16 @@
"security": "Security",
"currentPassword": "Current Password",
"newPassword": "New Password",
"confirmNewPassword": "Confirm New Password"
"confirmNewPassword": "Confirm New Password",
"securityDesc": "Update your password for better account security. Please choose a strong password.",
"newPasswordHint": "At least 6 characters",
"confirmPasswordHint": "Confirm your password again",
"changePasswordBtn": "Change Password",
"emailHint": "Contact admin to change email",
"updateSuccess": "Profile updated successfully",
"updateError": "Failed to update profile",
"passwordSuccess": "Password changed successfully",
"passwordError": "Failed to change password"
},
"userMenu": {
"home": "Home",
@ -128,6 +137,11 @@
"newBadge": "New",
"popularBadge": "Popular",
"save": "Save",
"cancel": "Cancel"
"cancel": "Cancel",
"required": "This field is required",
"invalidEmail": "Invalid email address",
"invalidPhone": "Invalid phone number",
"passwordTooShort": "At least 6 characters",
"passwordsDoNotMatch": "Passwords do not match"
}
}

View file

@ -100,7 +100,16 @@
"security": "ความปลอดภัย",
"currentPassword": "รหัสผ่านปัจจุบัน",
"newPassword": "รหัสผ่านใหม่",
"confirmNewPassword": "ยืนยันรหัสผ่านใหม่"
"confirmNewPassword": "ยืนยันรหัสผ่านใหม่",
"securityDesc": "เปลี่ยนรหัสผ่านเพื่อความปลอดภัยของบัญชี กรุณาตั้งรหัสผ่านที่คาดเดายาก",
"newPasswordHint": "อย่างน้อย 6 ตัวอักษร",
"confirmPasswordHint": "ยืนยันรหัสผ่านอีกครั้ง",
"changePasswordBtn": "เปลี่ยนรหัสผ่าน",
"emailHint": "ติดต่อผู้ดูแลระบบเพื่อเปลี่ยนอีเมล",
"updateSuccess": "บันทึกข้อมูลส่วนตัวเรียบร้อยแล้ว",
"updateError": "เกิดข้อผิดพลาดในการบันทึกข้อมูลส่วนตัว",
"passwordSuccess": "เปลี่ยนรหัสผ่านเรียบร้อยแล้ว",
"passwordError": "เปลี่ยนรหัสผ่านไม่สำเร็จ"
},
"userMenu": {
"home": "หน้าหลัก",
@ -128,6 +137,11 @@
"newBadge": "ใหม่",
"popularBadge": "ยอดนิยม",
"save": "บันทึก",
"cancel": "ยกเลิก"
"cancel": "ยกเลิก",
"required": "กรุณากรอกข้อมูล",
"invalidEmail": "อีเมลไม่ถูกต้อง",
"invalidPhone": "เบอร์โทรศัพท์ไม่ถูกต้อง",
"passwordTooShort": "รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร",
"passwordsDoNotMatch": "รหัสผ่านใหม่ไม่ตรงกัน"
}
}

View file

@ -9,8 +9,7 @@ useHead({
})
const { currentUser, updateUserProfile, changePassword } = useAuth()
// Removed useFormValidation destructuring if not strictly used in template to avoid unused var warnings,
// or keep it if logically needed. Keeping minimalist.
const { t } = useI18n()
const { errors, validate, clearFieldError } = useFormValidation()
const isEditing = ref(false)
@ -48,22 +47,22 @@ const passwordForm = reactive({
confirmPassword: ''
})
const nameRules = [(val: string) => !!val || 'กรุณากรอกข้อมูล']
const nameRules = [(val: string) => !!val || t('common.required')]
const emailRules = [
(val: string) => !!val || 'กรุณากรอกอีเมล',
(val: string) => /.+@.+\..+/.test(val) || ''
(val: string) => !!val || t('common.required'),
(val: string) => /.+@.+\..+/.test(val) || t('common.invalidEmail')
]
const phoneRules = [
(val: string) => !!val || 'กรุณากรอกเบอร์โทรศัพท์',
(val: string) => /^0[0-9]{8,9}$/.test(val) || 'เบอร์โทรศัพท์ไม่ถูกต้อง'
(val: string) => !!val || t('common.required'),
(val: string) => /^0[0-9]{8,9}$/.test(val) || t('common.invalidPhone')
]
const passwordRules = [
(val: string) => !!val || 'กรุณากรอกรหัสผ่าน',
(val: string) => val.length >= 6 || 'รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร'
(val: string) => !!val || t('common.required'),
(val: string) => val.length >= 6 || t('common.passwordTooShort')
]
const confirmPasswordRules = [
(val: string) => !!val || 'กรุณายืนยันรหัสผ่าน',
(val: string) => val === passwordForm.newPassword || 'รหัสผ่านใหม่ไม่ตรงกัน'
(val: string) => !!val || t('common.required'),
(val: string) => val === passwordForm.newPassword || t('common.passwordsDoNotMatch')
]
const showCurrentPassword = ref(false)
@ -115,7 +114,7 @@ const handleUpdateProfile = async () => {
if (result?.success) {
// success logic
} else {
alert(result?.error || 'เกิดข้อผิดพลาดในการบันทึกข้อมูลส่วนตัว')
alert(result?.error || t('profile.updateError'))
}
isProfileSaving.value = false
@ -135,12 +134,12 @@ const handleUpdatePassword = async () => {
})
if (result.success) {
alert('เปลี่ยนรหัสผ่านเรียบร้อยแล้ว')
alert(t('profile.passwordSuccess'))
passwordForm.currentPassword = ''
passwordForm.newPassword = ''
passwordForm.confirmPassword = ''
} else {
alert(result.error || 'เปลี่ยนรหัสผ่านไม่สำเร็จ')
alert(result.error || t('profile.passwordError'))
}
isPasswordSaving.value = false
@ -311,10 +310,10 @@ onMounted(() => {
outlined dense rounded
class="premium-q-input"
:rules="emailRules"
hide-bottom-space
disable
hint="ติดต่อผู้ดูแลระบบเพื่อเปลี่ยนอีเมล"
/>
hide-bottom-space
disable
:hint="$t('profile.emailHint')"
/>
</div>
<div>
@ -350,10 +349,10 @@ onMounted(() => {
{{ $t('profile.security') }}
</h2>
<q-form @submit="handleUpdatePassword" class="flex flex-col gap-6">
<div class="text-sm text-slate-500 dark:text-slate-400 mb-2">
เปลยนรหสผานเพอความปลอดภยของบญช กรณาตงรหสผานทคาดเดายาก
</div>
<q-form @submit="handleUpdatePassword" class="flex flex-col gap-6">
<div class="text-sm text-slate-500 dark:text-slate-400 mb-2">
{{ $t('profile.securityDesc') }}
</div>
<div class="space-y-4">
<div>
@ -361,11 +360,11 @@ onMounted(() => {
<q-input
v-model="passwordForm.currentPassword"
:type="showCurrentPassword ? 'text' : 'password'"
outlined dense rounded
class="premium-q-input"
placeholder="••••••••"
:rules="[val => !!val || 'กรุณากรอกรหัสผ่านปัจจุบัน']"
>
outlined dense rounded
class="premium-q-input"
placeholder="••••••••"
:rules="[val => !!val || $t('common.required')]"
>
<template v-slot:append>
<q-icon
:name="showCurrentPassword ? 'visibility_off' : 'visibility'"
@ -383,11 +382,11 @@ onMounted(() => {
<q-input
v-model="passwordForm.newPassword"
:type="showNewPassword ? 'text' : 'password'"
outlined dense rounded
class="premium-q-input"
placeholder="อย่างน้อย 6 ตัวอักษร"
:rules="passwordRules"
>
outlined dense rounded
class="premium-q-input"
:placeholder="$t('profile.newPasswordHint')"
:rules="passwordRules"
>
<template v-slot:append>
<q-icon
:name="showNewPassword ? 'visibility_off' : 'visibility'"
@ -403,11 +402,11 @@ onMounted(() => {
<q-input
v-model="passwordForm.confirmPassword"
:type="showConfirmPassword ? 'text' : 'password'"
outlined dense rounded
class="premium-q-input"
placeholder="ยืนยันรหัสผ่านอีกครั้ง"
:rules="confirmPasswordRules"
>
outlined dense rounded
class="premium-q-input"
:placeholder="$t('profile.confirmPasswordHint')"
:rules="confirmPasswordRules"
>
<template v-slot:append>
<q-icon
:name="showConfirmPassword ? 'visibility_off' : 'visibility'"
@ -422,13 +421,13 @@ onMounted(() => {
<div class="pt-2">
<q-btn
type="submit"
unelevated
rounded
class="w-full py-3 font-bold text-base shadow-lg shadow-amber-500/20"
style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white;"
label="เปลี่ยนรหัสผ่าน"
:loading="isPasswordSaving"
/>
unelevated
rounded
class="w-full py-3 font-bold text-base shadow-lg shadow-amber-500/20"
style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white;"
:label="$t('profile.changePasswordBtn')"
:loading="isPasswordSaving"
/>
</div>
</q-form>
</div>