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

View file

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