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
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
99
Frontend-Learner/composables/useCourse.ts
Normal file
99
Frontend-Learner/composables/useCourse.ts
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue