feat: Implement core authentication and course management logic with new discovery and profile pages.
This commit is contained in:
parent
1aa3190ca4
commit
2ffcc36fe4
12 changed files with 397 additions and 89 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue