feat: Implement core authentication and course management logic with new discovery and profile pages.

This commit is contained in:
supalerk-ar66 2026-01-16 10:03:04 +07:00
parent 1aa3190ca4
commit 2ffcc36fe4
12 changed files with 397 additions and 89 deletions

View file

@ -19,15 +19,29 @@ const { currentUser } = useAuth()
const { errors, validate, clearFieldError } = useFormValidation()
const isEditing = ref(false)
const formatDate = (dateString?: string) => {
if (!dateString) return '-'
try {
const date = new Date(dateString)
return new Intl.DateTimeFormat('th-TH', {
day: 'numeric',
month: 'short',
year: 'numeric'
}).format(date)
} catch (e) {
return dateString
}
}
// User Profile Data Management
const userData = ref({
firstName: currentUser.value?.firstName || '',
lastName: currentUser.value?.lastName || '',
email: currentUser.value?.email || '',
phone: '0812345678',
joinDate: '12 ธ.ค. 2024',
photoURL: '',
prefix: 'นาย'
phone: currentUser.value?.phone || '',
createdAt: formatDate(currentUser.value?.createdAt),
photoURL: currentUser.value?.photoURL || '',
prefix: currentUser.value?.prefix?.th || ''
})
// Password Form (Separate from userData for security/logic)
@ -47,6 +61,10 @@ const validationRules = {
confirmPassword: { rules: { match: 'newPassword' }, label: 'ยืนยันรหัสผ่าน' }
}
const showCurrentPassword = ref(false)
const showNewPassword = ref(false)
const showConfirmPassword = ref(false)
const fileInput = ref<HTMLInputElement | null>(null)
const toggleEdit = (edit: boolean) => {
@ -73,23 +91,71 @@ const handleFileUpload = (event: Event) => {
}
}
// Save Profile Updates (Mock Implementation)
const saveProfile = () => {
// Save Profile Updates
const saveProfile = async () => {
// Combine data for validation
const formData = {
...userData.value,
...passwordForm
}
if (!validate(formData, validationRules)) return
// TODO: Add password validation if changing password is implemented via this API or handle separately
// For now focused on profile fields
currentUser.value.firstName = userData.value.firstName
currentUser.value.lastName = userData.value.lastName
currentUser.value.email = userData.value.email
// Successful save
alert('บันทึกข้อมูลเรียบร้อยแล้ว')
isEditing.value = false
const prefixMap: Record<string, string> = {
'นาย': 'Mr.',
'นาง': 'Mrs.',
'นางสาว': 'Ms.'
}
if (currentUser.value) {
const { updateUserProfile, changePassword } = useAuth()
const payload = {
first_name: userData.value.firstName,
last_name: userData.value.lastName,
// email: userData.value.email, // Email should not be updated via this endpoint as it causes backend 500 error (not in UserProfile schema)
phone: userData.value.phone,
prefix: {
th: userData.value.prefix,
en: prefixMap[userData.value.prefix] || ''
}
}
// 1. Update Profile
const profileResult = await updateUserProfile(payload)
if (!profileResult?.success) {
alert(profileResult?.error || 'เกิดข้อผิดพลาดในการบันทึกข้อมูลส่วนตัว')
return
}
// 2. Change Password (if filled)
if (passwordForm.currentPassword && passwordForm.newPassword) {
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
alert('รหัสผ่านใหม่ไม่ตรงกัน')
return
}
const passwordResult = await changePassword({
oldPassword: passwordForm.currentPassword,
newPassword: passwordForm.newPassword
})
if (!passwordResult.success) {
alert(passwordResult.error || 'เปลี่ยนรหัสผ่านไม่สำเร็จ')
// Don't return here, maybe we still want to show profile success?
// But usually we stop. Let's alert only.
} else {
// Clear password form on success
passwordForm.currentPassword = ''
passwordForm.newPassword = ''
passwordForm.confirmPassword = ''
}
}
alert('บันทึกข้อมูลเรียบร้อยแล้ว')
isEditing.value = false
}
}
</script>
@ -143,7 +209,7 @@ const saveProfile = () => {
</div>
<div class="info-group">
<span class="label">สมครสมาชกเม</span>
<p class="value">{{ userData.joinDate }}</p>
<p class="value">{{ userData.createdAt }}</p>
</div>
</div>
</div>
@ -250,28 +316,81 @@ const saveProfile = () => {
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="space-y-2">
<label class="text-xs font-black uppercase tracking-widest text-slate-500 dark:text-slate-300">รหสผานปจจ</label>
<input type="password" class="premium-input w-full" placeholder="••••••••">
<div class="relative">
<input
v-model="passwordForm.currentPassword"
:type="showCurrentPassword ? 'text' : 'password'"
class="premium-input w-full pr-12"
placeholder="••••••••"
>
<button
type="button"
@click="showCurrentPassword = !showCurrentPassword"
class="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
tabindex="-1"
>
<svg v-if="!showCurrentPassword" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
</button>
</div>
</div>
<div class="space-y-2">
<label class="text-xs font-black uppercase tracking-widest text-slate-500 dark:text-slate-300">รหสผานใหม</label>
<input
v-model="passwordForm.newPassword"
type="password"
class="premium-input w-full"
:class="{ '!border-red-500': errors.newPassword }"
@input="clearFieldError('newPassword')"
>
<div class="relative">
<input
v-model="passwordForm.newPassword"
:type="showNewPassword ? 'text' : 'password'"
class="premium-input w-full pr-12"
:class="{ '!border-red-500': errors.newPassword }"
@input="clearFieldError('newPassword')"
>
<button
type="button"
@click="showNewPassword = !showNewPassword"
class="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
tabindex="-1"
>
<svg v-if="!showNewPassword" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
</button>
</div>
<span v-if="errors.newPassword" class="text-red-500 text-[10px] mt-1 font-bold">{{ errors.newPassword }}</span>
</div>
<div class="space-y-2">
<label class="text-xs font-black uppercase tracking-widest text-slate-500 dark:text-slate-300">นยนรหสผานใหม</label>
<input
v-model="passwordForm.confirmPassword"
type="password"
class="premium-input w-full"
:class="{ '!border-red-500': errors.confirmPassword }"
@input="clearFieldError('confirmPassword')"
>
<div class="relative">
<input
v-model="passwordForm.confirmPassword"
:type="showConfirmPassword ? 'text' : 'password'"
class="premium-input w-full pr-12"
:class="{ '!border-red-500': errors.confirmPassword }"
@input="clearFieldError('confirmPassword')"
>
<button
type="button"
@click="showConfirmPassword = !showConfirmPassword"
class="absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
tabindex="-1"
>
<svg v-if="!showConfirmPassword" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
</button>
</div>
<span v-if="errors.confirmPassword" class="text-red-500 text-[10px] mt-1 font-bold">{{ errors.confirmPassword }}</span>
</div>
</div>