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

@ -8,6 +8,8 @@ interface User {
id: number
username: string
email: string
created_at?: string
updated_at?: string
role: {
code: string
name: { th: string; en: string }
@ -167,6 +169,36 @@ export const useAuth = () => {
}
}
// Update User Profile
const updateUserProfile = async (payload: {
first_name: string
last_name: string
phone: string
prefix: { th: string; en: string }
}) => {
if (!token.value) return
try {
const { error } = await useFetch(`${API_BASE_URL}/user/me`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${token.value}`
},
body: payload
})
if (error.value) throw error.value
// If successful, refresh the local user data
await fetchUserProfile()
return { success: true }
} catch (err: any) {
console.error('Failed to update profile:', err)
return { success: false, error: err.data?.message || err.message || 'บันทึกข้อมูลไม่สำเร็จ' }
}
}
// Request Password Reset
const requestPasswordReset = async (email: string) => {
try {
@ -197,6 +229,26 @@ export const useAuth = () => {
}
}
// Change Password
const changePassword = async (payload: { oldPassword: string, newPassword: string }) => {
if (!token.value) return { success: false, error: 'ไม่พบ Token การใช้งาน' }
try {
const { data, error } = await useFetch(`${API_BASE_URL}/user/change-password`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token.value}`
},
body: payload
})
if (error.value) throw error.value
return { success: true }
} catch (err: any) {
return { success: false, error: err.data?.message || 'เปลี่ยนรหัสผ่านไม่สำเร็จ' }
}
}
// Refresh Access Token
const refreshAccessToken = async () => {
if (!refreshToken.value) return false
@ -243,20 +295,23 @@ export const useAuth = () => {
const lastName = user.value.profile?.last_name || ''
return {
prefix,
prefix: user.value.profile?.prefix || { th: '', en: '' },
firstName,
lastName,
email: user.value.email,
phone: user.value.profile?.phone || '',
photoURL: user.value.profile?.avatar_url || '',
role: user.value.role
role: user.value.role,
createdAt: user.value.created_at || new Date().toISOString()
}
}),
login,
register,
fetchUserProfile,
updateUserProfile,
requestPasswordReset,
confirmResetPassword,
changePassword,
refreshAccessToken,
logout
}

View file

@ -0,0 +1,99 @@
import type { H3Event } from 'h3'
// Types based on API responses
export interface Course {
id: number
title: string
slug: string
description: string
thumbnail_url: string
price: string
is_free: boolean
have_certificate: boolean
status: string // 'DRAFT' | 'PUBLISHED' | ...
category_id: number
created_at?: string
updated_at?: string
created_by?: number
updated_by?: number
approved_at?: string
approved_by?: number
rejection_reason?: string
// Helper properties for UI (may be computed or mapped)
rating?: string
lessons?: number | string
levelType?: 'neutral' | 'warning' | 'success' // For UI badging
}
interface CourseResponse {
code: number
message: string
data: Course[]
total: number
}
export const useCourse = () => {
const config = useRuntimeConfig()
const API_BASE_URL = config.public.apiBase as string
const { token } = useAuth()
const fetchCourses = async () => {
try {
const { data, error } = await useFetch<CourseResponse>(`${API_BASE_URL}/courses`, {
method: 'GET',
headers: token.value ? {
Authorization: `Bearer ${token.value}`
} : {}
})
if (error.value) throw error.value
return {
success: true,
data: data.value?.data || [],
total: data.value?.total || 0
}
} catch (err: any) {
console.error('Fetch courses failed:', err)
return {
success: false,
error: err.data?.message || err.message || 'Error fetching courses'
}
}
}
const fetchCourseById = async (id: number) => {
try {
const { data, error } = await useFetch<CourseResponse>(`${API_BASE_URL}/courses/${id}`, {
method: 'GET',
headers: token.value ? {
Authorization: `Bearer ${token.value}`
} : {}
})
if (error.value) throw error.value
// API returns data array even for single item based on schema
const courseData = data.value?.data?.[0]
if (!courseData) throw new Error('Course not found')
return {
success: true,
data: courseData
}
} catch (err: any) {
console.error('Fetch course details failed:', err)
return {
success: false,
error: err.data?.message || err.message || 'Error fetching course details'
}
}
}
return {
fetchCourses,
fetchCourseById
}
}