diff --git a/frontend_management/components/common/AppCard.vue b/frontend_management/components/common/AppCard.vue deleted file mode 100644 index c93dc3e3..00000000 --- a/frontend_management/components/common/AppCard.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - diff --git a/frontend_management/components/common/AppHeader.vue b/frontend_management/components/common/AppHeader.vue deleted file mode 100644 index aeed7319..00000000 --- a/frontend_management/components/common/AppHeader.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - diff --git a/frontend_management/components/common/AppModal.vue b/frontend_management/components/common/AppModal.vue deleted file mode 100644 index 59a4ffe8..00000000 --- a/frontend_management/components/common/AppModal.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - diff --git a/frontend_management/components/common/AppTable.vue b/frontend_management/components/common/AppTable.vue deleted file mode 100644 index e6d8c5d4..00000000 --- a/frontend_management/components/common/AppTable.vue +++ /dev/null @@ -1,177 +0,0 @@ - - - diff --git a/frontend_management/composables/useApi.ts b/frontend_management/composables/useApi.ts deleted file mode 100644 index 4712ed82..00000000 --- a/frontend_management/composables/useApi.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { UseFetchOptions } from 'nuxt/app'; - -export const useApi = () => { - const config = useRuntimeConfig(); - const baseURL = config.public.apiBaseUrl as string; - - const apiFetch = (url: string, options?: UseFetchOptions) => { - return $fetch(url, { - baseURL, - ...options, - headers: { - ...options?.headers, - }, - onRequest({ options }) { - // Add auth token if available - const token = localStorage.getItem('token'); - if (token) { - options.headers = { - ...options.headers, - Authorization: `Bearer ${token}` - }; - } - }, - onResponseError({ response }) { - // Handle errors globally - console.error('API Error:', response.status, response._data); - } - }); - }; - - return { - apiFetch - }; -}; diff --git a/frontend_management/middleware/admin.ts b/frontend_management/middleware/admin.ts index e2733d8d..48d5ea77 100644 --- a/frontend_management/middleware/admin.ts +++ b/frontend_management/middleware/admin.ts @@ -1,6 +1,17 @@ export default defineNuxtRouteMiddleware((to, from) => { + // Skip on server side - let client handle auth + if (process.server) { + return; + } + const authStore = useAuthStore(); - authStore.checkAuth(); + + // Restore session if not authenticated + if (!authStore.isAuthenticated) { + authStore.checkAuth(); + } + + // Check if user is admin if (!authStore.isAdmin) { return navigateTo('/login'); } diff --git a/frontend_management/middleware/auth.ts b/frontend_management/middleware/auth.ts index 7e510f86..09b0cc76 100644 --- a/frontend_management/middleware/auth.ts +++ b/frontend_management/middleware/auth.ts @@ -1,6 +1,22 @@ export default defineNuxtRouteMiddleware((to, from) => { + // Skip on server side - let client handle auth + if (process.server) { + return; + } + + // Skip middleware on login page + if (to.path === '/login') { + return; + } + const authStore = useAuthStore(); - authStore.checkAuth(); + + // Restore session if not authenticated + if (!authStore.isAuthenticated) { + authStore.checkAuth(); + } + + // Check authentication after restore if (!authStore.isAuthenticated) { return navigateTo('/login'); } diff --git a/frontend_management/pages/admin/categories/index.vue b/frontend_management/pages/admin/categories/index.vue new file mode 100644 index 00000000..c794fa60 --- /dev/null +++ b/frontend_management/pages/admin/categories/index.vue @@ -0,0 +1,328 @@ + + + diff --git a/frontend_management/pages/admin/users/index.vue b/frontend_management/pages/admin/users/index.vue new file mode 100644 index 00000000..11522589 --- /dev/null +++ b/frontend_management/pages/admin/users/index.vue @@ -0,0 +1,394 @@ + + + diff --git a/frontend_management/pages/instructor/index.vue b/frontend_management/pages/instructor/index.vue index d0db5e70..d6e06970 100644 --- a/frontend_management/pages/instructor/index.vue +++ b/frontend_management/pages/instructor/index.vue @@ -5,13 +5,13 @@

- สวัสดี, {{ authStore.user?.fullName || 'อาจารย์' }} + สวัสดี, {{ authStore.user?.firstName }} {{ authStore.user?.lastName || 'อาจารย์' }}

ยินดีต้อนรับกลับสู่ระบบ

-
{{ authStore.user?.fullName || 'อาจารย์ทดสอบ' }}
+
{{ authStore.user?.firstName }} {{ authStore.user?.lastName || 'อาจารย์ทดสอบ' }}
{{ authStore.user?.role || 'INSTRUCTOR' }}
- {{ authStore.user?.fullName }} + {{ authStore.user?.firstName }} {{ authStore.user?.lastName }} {{ authStore.user?.email }} diff --git a/frontend_management/pages/instructor/profile/index.vue b/frontend_management/pages/instructor/profile/index.vue index a7f762a5..f6014ea4 100644 --- a/frontend_management/pages/instructor/profile/index.vue +++ b/frontend_management/pages/instructor/profile/index.vue @@ -7,7 +7,7 @@
- +
@@ -49,7 +49,7 @@
ตำแหน่ง
- {{ getRoleLabel(profile.role) }} + {{ profile.roleName || getRoleLabel(profile.role) }}
@@ -77,7 +77,7 @@
-
+
+ + + +
ลืมรหัสผ่าน
+ + +
+ + +

+ กรอกอีเมลของคุณ เราจะส่งลิงก์สำหรับรีเซ็ตรหัสผ่านไปให้ +

+ + + + + +
+ + +
+
+
+
+
\ No newline at end of file diff --git a/frontend_management/pages/reset-password.vue b/frontend_management/pages/reset-password.vue new file mode 100644 index 00000000..b443a3a4 --- /dev/null +++ b/frontend_management/pages/reset-password.vue @@ -0,0 +1,138 @@ + + + diff --git a/frontend_management/plugins/01.auth.ts b/frontend_management/plugins/01.auth.ts new file mode 100644 index 00000000..791d2631 --- /dev/null +++ b/frontend_management/plugins/01.auth.ts @@ -0,0 +1,11 @@ +export default defineNuxtPlugin({ + name: 'auth-restore', + parallel: false, + setup() { + const authStore = useAuthStore(); + + // Restore auth state from cookies on app initialization + // useCookie works on both server and client + authStore.checkAuth(); + } +}); diff --git a/frontend_management/plugins/auth.ts b/frontend_management/plugins/auth.ts deleted file mode 100644 index 5562025a..00000000 --- a/frontend_management/plugins/auth.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default defineNuxtPlugin(() => { - const authStore = useAuthStore(); - - // Restore auth state from localStorage before any navigation - if (process.client) { - authStore.checkAuth(); - } -}); diff --git a/frontend_management/services/admin.service.ts b/frontend_management/services/admin.service.ts new file mode 100644 index 00000000..1f76636f --- /dev/null +++ b/frontend_management/services/admin.service.ts @@ -0,0 +1,358 @@ +// API Response structure for user list +export interface AdminUserResponse { + id: number; + username: string; + email: string; + created_at: string; + updated_at: string; + role: { + code: string; + name: { + en: string; + th: string; + }; + }; + profile: { + prefix: { + en: string; + th: string; + }; + first_name: string; + last_name: string; + avatar_url: string | null; + birth_date: string | null; + phone: string | null; + }; +} + +export interface UsersListResponse { + code: number; + message: string; + data: AdminUserResponse[]; +} + +// Mock data for development +const MOCK_USERS: AdminUserResponse[] = [ + { + id: 1, + username: 'admin', + email: 'admin@elearning.local', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + role: { code: 'ADMIN', name: { en: 'Administrator', th: 'ผู้ดูแลระบบ' } }, + profile: { + prefix: { en: 'Mr.', th: 'นาย' }, + first_name: 'Admin', + last_name: 'User', + avatar_url: null, + birth_date: null, + phone: '0812345678' + } + }, + { + id: 2, + username: 'instructor', + email: 'instructor@elearning.local', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + role: { code: 'INSTRUCTOR', name: { en: 'Instructor', th: 'ผู้สอน' } }, + profile: { + prefix: { en: 'Mr.', th: 'นาย' }, + first_name: 'John', + last_name: 'Instructor', + avatar_url: null, + birth_date: null, + phone: '0812345679' + } + }, + { + id: 3, + username: 'student', + email: 'student@elearning.local', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + role: { code: 'STUDENT', name: { en: 'Student', th: 'นักเรียน' } }, + profile: { + prefix: { en: 'Ms.', th: 'นางสาว' }, + first_name: 'Jane', + last_name: 'Student', + avatar_url: null, + birth_date: null, + phone: '0812345680' + } + } +]; + +// Helper function to get auth token from cookie or localStorage +const getAuthToken = (): string => { + const tokenCookie = useCookie('token'); + return tokenCookie.value || ''; +}; + +export const adminService = { + async getUsers(): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + return MOCK_USERS; + } + + const token = getAuthToken(); + const response = await $fetch('/api/admin/usermanagement/users', { + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + + return response.data; + }, + + async getUserById(id: number): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 300)); + const user = MOCK_USERS.find(u => u.id === id); + if (!user) throw new Error('User not found'); + return user; + } + + const token = getAuthToken(); + const response = await $fetch(`/api/admin/usermanagement/users/${id}`, { + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + + return response; + }, + + async updateUserRole(userId: number, roleId: number): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + return; + } + + const token = getAuthToken(); + await $fetch(`/api/admin/usermanagement/role/${userId}`, { + method: 'PUT', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + }, + body: { + id: userId, + role_id: roleId + } + }); + }, + + async deleteUser(userId: number): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + return; + } + + const token = getAuthToken(); + await $fetch(`/api/admin/usermanagement/users/${userId}`, { + method: 'DELETE', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + }, + + // ============ Categories ============ + async getCategories(): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + return MOCK_CATEGORIES; + } + + const token = getAuthToken(); + const response = await $fetch('/api/admin/categories', { + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + + return response.data.categories; + }, + + async createCategory(data: CreateCategoryRequest): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + return { ...MOCK_CATEGORIES[0], id: Date.now() }; + } + + const token = getAuthToken(); + const response = await $fetch('/api/admin/categories', { + method: 'POST', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + }, + body: data + }); + + return response; + }, + + async updateCategory(id: number, data: UpdateCategoryRequest): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + return { ...MOCK_CATEGORIES[0], id }; + } + + const token = getAuthToken(); + const response = await $fetch(`/api/admin/categories/${id}`, { + method: 'PUT', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + }, + body: data + }); + + return response; + }, + + async deleteCategory(id: number): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + return; + } + + const token = getAuthToken(); + await $fetch(`/api/admin/categories/${id}`, { + method: 'DELETE', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + } +}; + +// Category interfaces +export interface CategoryResponse { + id: number; + name: { + en: string; + th: string; + }; + slug: string; + description: { + en: string; + th: string; + }; + icon: string; + sort_order: number; + is_active: boolean; + created_at: string; + created_by: number; + updated_at: string; + updated_by: number | null; +} + +export interface CategoriesListResponse { + code: number; + message: string; + data: { + categories: CategoryResponse[]; + }; +} + +export interface CreateCategoryRequest { + name: { + en: string; + th: string; + }; + slug: string; + description: { + en: string; + th: string; + }; + created_by?: number; +} + +export interface UpdateCategoryRequest { + id: number; + name: { + en: string; + th: string; + }; + slug: string; + description: { + en: string; + th: string; + }; +} + +// Mock categories +const MOCK_CATEGORIES: CategoryResponse[] = [ + { + id: 1, + name: { en: 'Web Development', th: 'การพัฒนาเว็บไซต์' }, + slug: 'web-development', + description: { en: 'Learn web development', th: 'หลักสูตรเกี่ยวกับการพัฒนาเว็บไซต์และเว็บแอปพลิเคชัน' }, + icon: 'code', + sort_order: 1, + is_active: true, + created_at: '2024-01-15T00:00:00Z', + created_by: 1, + updated_at: '2024-01-15T00:00:00Z', + updated_by: null + }, + { + id: 2, + name: { en: 'Mobile Development', th: 'การพัฒนาแอปพลิเคชันมือถือ' }, + slug: 'mobile-development', + description: { en: 'Learn mobile app development', th: 'หลักสูตรเกี่ยวกับการพัฒนาแอปพลิเคชันบนมือถือ' }, + icon: 'smartphone', + sort_order: 2, + is_active: true, + created_at: '2024-01-20T00:00:00Z', + created_by: 1, + updated_at: '2024-01-20T00:00:00Z', + updated_by: null + }, + { + id: 3, + name: { en: 'Database', th: 'ฐานข้อมูล' }, + slug: 'database', + description: { en: 'Learn database management', th: 'หลักสูตรเกี่ยวกับการออกแบบและจัดการฐานข้อมูล' }, + icon: 'storage', + sort_order: 3, + is_active: true, + created_at: '2024-02-01T00:00:00Z', + created_by: 1, + updated_at: '2024-02-01T00:00:00Z', + updated_by: null + } +]; diff --git a/frontend_management/services/auth.service.ts b/frontend_management/services/auth.service.ts index 3c815dda..c7579709 100644 --- a/frontend_management/services/auth.service.ts +++ b/frontend_management/services/auth.service.ts @@ -41,14 +41,72 @@ export interface LoginResponse { user: { id: string; email: string; - fullName: string; + firstName: string; + lastName: string; role: string; }; } +// Mock data for development +const MOCK_USERS = [ + { + email: 'admin@elearning.local', + password: 'password', + user: { + id: '1', + email: 'admin@elearning.local', + firstName: 'ผู้ดูแล', + lastName: 'ระบบ', + role: 'ADMIN' + } + }, + { + email: 'instructor@elearning.local', + password: 'password', + user: { + id: '2', + email: 'instructor@elearning.local', + firstName: 'อาจารย์', + lastName: 'ทดสอบ', + role: 'INSTRUCTOR' + } + } +]; + export const authService = { async login(email: string, password: string): Promise { const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + // Use Mock Data + if (useMockData) { + return this.mockLogin(email, password); + } + + // Use Real API + return this.apiLogin(email, password); + }, + + // Mock login for development + async mockLogin(email: string, password: string): Promise { + await new Promise(resolve => setTimeout(resolve, 500)); // Simulate delay + + const mockUser = MOCK_USERS.find(u => u.email === email); + + if (!mockUser || mockUser.password !== password) { + throw new Error('อีเมลหรือรหัสผ่านไม่ถูกต้อง'); + } + + return { + token: 'mock-jwt-token', + refreshToken: 'mock-refresh-token', + user: mockUser.user + }; + }, + + // Real API login + async apiLogin(email: string, password: string): Promise { + const config = useRuntimeConfig(); try { const response = await $fetch('/api/auth/login', { @@ -60,6 +118,11 @@ export const authService = { } }); + // Check if user role is STUDENT - block login + if (response.user.role.code === 'STUDENT') { + throw new Error('ไม่สามารถเข้าสู่ระบบได้ ระบบนี้สำหรับผู้สอนและผู้ดูแลระบบเท่านั้น'); + } + // Transform API response to frontend format return { token: response.token, @@ -67,26 +130,68 @@ export const authService = { user: { id: response.user.id.toString(), email: response.user.email, - fullName: `${response.user.profile.first_name} ${response.user.profile.last_name}`, - role: response.user.role.code // Use role.code (ADMIN, INSTRUCTOR, etc.) + firstName: response.user.profile.first_name, + lastName: response.user.profile.last_name, + role: response.user.role.code } }; } catch (error: any) { + // Re-throw custom errors (like STUDENT role block) + if (error.message && !error.response) { + throw error; + } // Handle API errors if (error.response?.status === 401) { - throw new Error('Invalid credentials'); + throw new Error('อีเมลหรือรหัสผ่านไม่ถูกต้อง'); } - throw new Error('Login failed'); + throw new Error('เกิดข้อผิดพลาดในการเข้าสู่ระบบ'); } }, async logout(): Promise { - // TODO: Call logout API if available - // For now, just clear local storage - if (process.client) { - localStorage.removeItem('token'); - localStorage.removeItem('refreshToken'); - localStorage.removeItem('user'); + // Clear cookies + const tokenCookie = useCookie('token'); + const refreshTokenCookie = useCookie('refreshToken'); + const userCookie = useCookie('user'); + + tokenCookie.value = null; + refreshTokenCookie.value = null; + userCookie.value = null; + }, + + async forgotPassword(email: string): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + // Mock: simulate sending email + await new Promise(resolve => setTimeout(resolve, 1000)); + return; } + + // Real API + await $fetch('/api/auth/reset-request', { + method: 'POST', + baseURL: config.public.apiBaseUrl as string, + body: { email } + }); + }, + + async resetPassword(token: string, password: string): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + // Mock: simulate reset password + await new Promise(resolve => setTimeout(resolve, 1000)); + return; + } + + // Real API + await $fetch('/api/auth/reset-password', { + method: 'POST', + baseURL: config.public.apiBaseUrl as string, + body: { token, password } + }); } }; diff --git a/frontend_management/services/user.service.ts b/frontend_management/services/user.service.ts new file mode 100644 index 00000000..af47a259 --- /dev/null +++ b/frontend_management/services/user.service.ts @@ -0,0 +1,182 @@ +// API Response structure from /api/user/me +export interface UserProfileResponse { + id: number; + username: string; + email: string; + updated_at: string; + created_at: string; + role: { + code: string; + name: { + en: string; + th: string; + }; + }; + profile: { + prefix: { + en: string; + th: string; + }; + first_name: string; + last_name: string; + avatar_url: string | null; + birth_date: string | null; + phone: string | null; + }; +} + +// Request body for PUT /api/user/me +export interface UpdateProfileRequest { + prefix?: { + en: string; + th: string; + }; + first_name: string; + last_name: string; + phone?: string | null; + avatar_url?: string | null; + birth_date?: string | null; +} + +// Mock profile data for development +const MOCK_PROFILES: Record = { + 'admin@elearning.local': { + id: 1, + username: 'admin', + email: 'admin@elearning.local', + updated_at: '2024-01-01T00:00:00Z', + created_at: '2024-01-01T00:00:00Z', + role: { + code: 'ADMIN', + name: { en: 'Administrator', th: 'ผู้ดูแลระบบ' } + }, + profile: { + prefix: { en: 'Mr.', th: 'นาย' }, + first_name: 'ผู้ดูแล', + last_name: 'ระบบ', + avatar_url: null, + birth_date: null, + phone: '081-234-5678' + } + }, + 'instructor@elearning.local': { + id: 2, + username: 'instructor', + email: 'instructor@elearning.local', + updated_at: '2024-01-01T00:00:00Z', + created_at: '2024-01-01T00:00:00Z', + role: { + code: 'INSTRUCTOR', + name: { en: 'Instructor', th: 'ผู้สอน' } + }, + profile: { + prefix: { en: 'Mr.', th: 'นาย' }, + first_name: 'อาจารย์', + last_name: 'ทดสอบ', + avatar_url: null, + birth_date: null, + phone: '081-234-5678' + } + } +}; + +// Helper function to get auth token from cookie +const getAuthToken = (): string => { + const tokenCookie = useCookie('token'); + return tokenCookie.value || ''; +}; + +export const userService = { + async getProfile(): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + return this.mockGetProfile(); + } + + return this.apiGetProfile(); + }, + + // Mock get profile + async mockGetProfile(): Promise { + await new Promise(resolve => setTimeout(resolve, 300)); + + const userCookie = useCookie('user'); + if (!userCookie.value) throw new Error('User not found'); + + const user = typeof userCookie.value === 'string' + ? JSON.parse(userCookie.value) + : userCookie.value; + const mockProfile = MOCK_PROFILES[user.email]; + + if (!mockProfile) { + throw new Error('Profile not found'); + } + + return mockProfile; + }, + + // Real API get profile + async apiGetProfile(): Promise { + const config = useRuntimeConfig(); + const token = getAuthToken(); + + const response = await $fetch('/api/user/me', { + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + + return response; + }, + + async updateProfile(data: UpdateProfileRequest): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + // In mock mode, just return the current profile with updates + const profile = await this.mockGetProfile(); + return { ...profile, profile: { ...profile.profile, ...data } }; + } + + const token = getAuthToken(); + const response = await $fetch('/api/user/me', { + method: 'PUT', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + }, + body: data + }); + + return response; + }, + + async changePassword(oldPassword: string, newPassword: string): Promise { + const config = useRuntimeConfig(); + const useMockData = config.public.useMockData as boolean; + + if (useMockData) { + await new Promise(resolve => setTimeout(resolve, 500)); + // In mock mode, just simulate success + return; + } + + const token = getAuthToken(); + await $fetch('/api/user/change-password', { + method: 'POST', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + }, + body: { + oldPassword, + newPassword + } + }); + } +}; diff --git a/frontend_management/stores/auth.ts b/frontend_management/stores/auth.ts index 2e74bf12..bba3ca1e 100644 --- a/frontend_management/stores/auth.ts +++ b/frontend_management/stores/auth.ts @@ -4,7 +4,8 @@ import { authService } from '~/services/auth.service'; interface User { id: string; email: string; - fullName: string; + firstName: string; + lastName: string; role: 'INSTRUCTOR' | 'ADMIN' | 'STUDENT'; } @@ -24,23 +25,32 @@ export const useAuthStore = defineStore('auth', { actions: { async login(email: string, password: string) { try { - // Call real API const response = await authService.login(email, password); this.token = response.token; this.user = response.user as User; this.isAuthenticated = true; - // Save to localStorage (including refreshToken) - if (process.client) { - localStorage.setItem('token', this.token); - localStorage.setItem('refreshToken', response.refreshToken); - localStorage.setItem('user', JSON.stringify(this.user)); - } + // Save to cookies + const tokenCookie = useCookie('token', { + maxAge: 60 * 60 * 24, // 24 hours + sameSite: 'strict' + }); + const refreshTokenCookie = useCookie('refreshToken', { + maxAge: 60 * 60 * 24 * 7, // 7 days + sameSite: 'strict' + }); + const userCookie = useCookie('user', { + maxAge: 60 * 60 * 24, // 24 hours + sameSite: 'strict' + }); + + tokenCookie.value = this.token; + refreshTokenCookie.value = response.refreshToken; + userCookie.value = JSON.stringify(this.user); return { token: this.token, user: this.user }; } catch (error: any) { - // Re-throw error to be handled by login page throw error; } }, @@ -50,21 +60,30 @@ export const useAuthStore = defineStore('auth', { this.token = null; this.isAuthenticated = false; - if (process.client) { - localStorage.removeItem('token'); - localStorage.removeItem('user'); - } + // Clear cookies + const tokenCookie = useCookie('token'); + const refreshTokenCookie = useCookie('refreshToken'); + const userCookie = useCookie('user'); + + tokenCookie.value = null; + refreshTokenCookie.value = null; + userCookie.value = null; }, checkAuth() { - if (process.client) { - const token = localStorage.getItem('token'); - const user = localStorage.getItem('user'); + const tokenCookie = useCookie('token'); + const userCookie = useCookie('user'); - if (token && user) { - this.token = token; - this.user = JSON.parse(user); + if (tokenCookie.value && userCookie.value) { + this.token = tokenCookie.value; + try { + this.user = typeof userCookie.value === 'string' + ? JSON.parse(userCookie.value) + : userCookie.value; this.isAuthenticated = true; + } catch (e) { + // Invalid user data + this.logout(); } } }