feat: Add initial frontend setup including authentication, instructor, and admin course management modules.
This commit is contained in:
parent
9fd217e1db
commit
19844f343b
16 changed files with 2065 additions and 293 deletions
|
|
@ -31,6 +31,127 @@ export interface UsersListResponse {
|
|||
data: AdminUserResponse[];
|
||||
}
|
||||
|
||||
// Pending Course interfaces
|
||||
export interface PendingCourseUser {
|
||||
id: number;
|
||||
email: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface PendingCourseInstructor {
|
||||
user_id: number;
|
||||
is_primary: boolean;
|
||||
user: PendingCourseUser;
|
||||
}
|
||||
|
||||
export interface PendingCourseSubmission {
|
||||
id: number;
|
||||
submitted_by: number;
|
||||
created_at: string;
|
||||
submitter: PendingCourseUser;
|
||||
}
|
||||
|
||||
export interface PendingCourse {
|
||||
id: number;
|
||||
title: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
slug: string;
|
||||
description: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
thumbnail_url: string | null;
|
||||
status: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by: number;
|
||||
creator: PendingCourseUser;
|
||||
instructors: PendingCourseInstructor[];
|
||||
chapters_count: number;
|
||||
lessons_count: number;
|
||||
latest_submission: PendingCourseSubmission | null;
|
||||
}
|
||||
|
||||
export interface PendingCoursesListResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
data: PendingCourse[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
// Course Detail interfaces for admin review
|
||||
export interface CourseCategory {
|
||||
id: number;
|
||||
name: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CourseLesson {
|
||||
id: number;
|
||||
title: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
type: string;
|
||||
sort_order: number;
|
||||
is_published: boolean;
|
||||
}
|
||||
|
||||
export interface CourseChapter {
|
||||
id: number;
|
||||
title: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
sort_order: number;
|
||||
is_published: boolean;
|
||||
lessons: CourseLesson[];
|
||||
}
|
||||
|
||||
export interface ApprovalHistory {
|
||||
id: number;
|
||||
action: string;
|
||||
comment: string | null;
|
||||
created_at: string;
|
||||
submitter: PendingCourseUser;
|
||||
reviewer: PendingCourseUser | null;
|
||||
}
|
||||
|
||||
export interface CourseDetailForReview {
|
||||
id: number;
|
||||
title: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
slug: string;
|
||||
description: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
thumbnail_url: string | null;
|
||||
price: number;
|
||||
is_free: boolean;
|
||||
have_certificate: boolean;
|
||||
status: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
category: CourseCategory;
|
||||
creator: PendingCourseUser;
|
||||
instructors: PendingCourseInstructor[];
|
||||
chapters: CourseChapter[];
|
||||
approval_history: ApprovalHistory[];
|
||||
}
|
||||
|
||||
export interface CourseDetailForReviewResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
data: CourseDetailForReview;
|
||||
}
|
||||
|
||||
// Mock data for development
|
||||
const MOCK_USERS: AdminUserResponse[] = [
|
||||
{
|
||||
|
|
@ -83,6 +204,121 @@ const MOCK_USERS: AdminUserResponse[] = [
|
|||
}
|
||||
];
|
||||
|
||||
// Mock pending courses data
|
||||
const MOCK_PENDING_COURSES: PendingCourse[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: { th: 'พื้นฐาน JavaScript', en: 'JavaScript Fundamentals' },
|
||||
slug: 'javascript-fundamentals',
|
||||
description: { th: 'เรียนรู้ JavaScript ตั้งแต่เริ่มต้น', en: 'Learn JavaScript from scratch' },
|
||||
thumbnail_url: null,
|
||||
status: 'PENDING',
|
||||
created_at: '2024-02-01T00:00:00Z',
|
||||
updated_at: '2024-02-10T00:00:00Z',
|
||||
created_by: 2,
|
||||
creator: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
||||
instructors: [
|
||||
{
|
||||
user_id: 2,
|
||||
is_primary: true,
|
||||
user: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
||||
}
|
||||
],
|
||||
chapters_count: 5,
|
||||
lessons_count: 15,
|
||||
latest_submission: {
|
||||
id: 1,
|
||||
submitted_by: 2,
|
||||
created_at: '2024-02-10T00:00:00Z',
|
||||
submitter: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: { th: 'React สำหรับผู้เริ่มต้น', en: 'React for Beginners' },
|
||||
slug: 'react-for-beginners',
|
||||
description: { th: 'เรียนรู้ React ตั้งแต่พื้นฐาน', en: 'Learn React from basics' },
|
||||
thumbnail_url: null,
|
||||
status: 'PENDING',
|
||||
created_at: '2024-02-05T00:00:00Z',
|
||||
updated_at: '2024-02-12T00:00:00Z',
|
||||
created_by: 2,
|
||||
creator: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
||||
instructors: [
|
||||
{
|
||||
user_id: 2,
|
||||
is_primary: true,
|
||||
user: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
||||
}
|
||||
],
|
||||
chapters_count: 8,
|
||||
lessons_count: 24,
|
||||
latest_submission: {
|
||||
id: 2,
|
||||
submitted_by: 2,
|
||||
created_at: '2024-02-12T00:00:00Z',
|
||||
submitter: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Mock course detail for review
|
||||
const MOCK_COURSE_DETAIL: CourseDetailForReview = {
|
||||
id: 1,
|
||||
title: { th: 'พื้นฐาน JavaScript', en: 'JavaScript Fundamentals' },
|
||||
slug: 'javascript-fundamentals',
|
||||
description: { th: 'เรียนรู้ JavaScript ตั้งแต่เริ่มต้น รวมถึง ES6+ features และ best practices', en: 'Learn JavaScript from scratch including ES6+ features and best practices' },
|
||||
thumbnail_url: null,
|
||||
price: 990,
|
||||
is_free: false,
|
||||
have_certificate: true,
|
||||
status: 'PENDING',
|
||||
created_at: '2024-02-01T00:00:00Z',
|
||||
updated_at: '2024-02-10T00:00:00Z',
|
||||
category: { id: 1, name: { th: 'การพัฒนาเว็บไซต์', en: 'Web Development' } },
|
||||
creator: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
||||
instructors: [
|
||||
{
|
||||
user_id: 2,
|
||||
is_primary: true,
|
||||
user: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
||||
}
|
||||
],
|
||||
chapters: [
|
||||
{
|
||||
id: 1,
|
||||
title: { th: 'แนะนำ JavaScript', en: 'Introduction to JavaScript' },
|
||||
sort_order: 1,
|
||||
is_published: true,
|
||||
lessons: [
|
||||
{ id: 1, title: { th: 'JavaScript คืออะไร', en: 'What is JavaScript' }, type: 'VIDEO', sort_order: 1, is_published: true },
|
||||
{ id: 2, title: { th: 'ติดตั้ง Development Environment', en: 'Setup Development Environment' }, type: 'VIDEO', sort_order: 2, is_published: true },
|
||||
{ id: 3, title: { th: 'แบบทดสอบบทที่ 1', en: 'Chapter 1 Quiz' }, type: 'QUIZ', sort_order: 3, is_published: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: { th: 'Variables และ Data Types', en: 'Variables and Data Types' },
|
||||
sort_order: 2,
|
||||
is_published: true,
|
||||
lessons: [
|
||||
{ id: 4, title: { th: 'var, let และ const', en: 'var, let and const' }, type: 'VIDEO', sort_order: 1, is_published: true },
|
||||
{ id: 5, title: { th: 'Primitive Data Types', en: 'Primitive Data Types' }, type: 'VIDEO', sort_order: 2, is_published: true }
|
||||
]
|
||||
}
|
||||
],
|
||||
approval_history: [
|
||||
{
|
||||
id: 1,
|
||||
action: 'SUBMITTED',
|
||||
comment: null,
|
||||
created_at: '2024-02-10T10:00:00Z',
|
||||
submitter: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
||||
reviewer: null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Helper function to get auth token from cookie or localStorage
|
||||
const getAuthToken = (): string => {
|
||||
const tokenCookie = useCookie('token');
|
||||
|
|
@ -174,6 +410,87 @@ export const adminService = {
|
|||
});
|
||||
},
|
||||
|
||||
// ============ Pending Courses ============
|
||||
async getPendingCourses(): Promise<PendingCourse[]> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return MOCK_PENDING_COURSES;
|
||||
}
|
||||
|
||||
const token = getAuthToken();
|
||||
const response = await $fetch<PendingCoursesListResponse>('/api/admin/courses/pending', {
|
||||
baseURL: config.public.apiBaseUrl as string,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async getCourseForReview(courseId: number): Promise<CourseDetailForReview> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return MOCK_COURSE_DETAIL;
|
||||
}
|
||||
|
||||
const token = getAuthToken();
|
||||
const response = await $fetch<CourseDetailForReviewResponse>(`/api/admin/courses/${courseId}`, {
|
||||
baseURL: config.public.apiBaseUrl as string,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async approveCourse(courseId: number, comment?: string): Promise<void> {
|
||||
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/courses/${courseId}/approve`, {
|
||||
method: 'POST',
|
||||
baseURL: config.public.apiBaseUrl as string,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: { comment: comment || '' }
|
||||
});
|
||||
},
|
||||
|
||||
async rejectCourse(courseId: number, comment: string): Promise<void> {
|
||||
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/courses/${courseId}/reject`, {
|
||||
method: 'POST',
|
||||
baseURL: config.public.apiBaseUrl as string,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: { comment }
|
||||
});
|
||||
},
|
||||
|
||||
// ============ Categories ============
|
||||
async getCategories(): Promise<CategoryResponse[]> {
|
||||
const config = useRuntimeConfig();
|
||||
|
|
|
|||
|
|
@ -504,6 +504,48 @@ export const instructorService = {
|
|||
);
|
||||
},
|
||||
|
||||
async reorderQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, sortOrder: number): Promise<void> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
return;
|
||||
}
|
||||
|
||||
await authRequest(
|
||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}/reorder`,
|
||||
{ method: 'PUT', body: { sort_order: sortOrder } }
|
||||
);
|
||||
},
|
||||
|
||||
// Quiz Settings
|
||||
async updateQuizSettings(courseId: number, chapterId: number, lessonId: number, data: UpdateQuizSettingsRequest): Promise<QuizResponse> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return {
|
||||
id: 1,
|
||||
lesson_id: lessonId,
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
passing_score: data.passing_score,
|
||||
time_limit: data.time_limit,
|
||||
shuffle_questions: data.shuffle_questions,
|
||||
shuffle_choices: data.shuffle_choices,
|
||||
show_answers_after_completion: data.show_answers_after_completion
|
||||
};
|
||||
}
|
||||
|
||||
const response = await authRequest<{ code: number; data: QuizResponse }>(
|
||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/quiz`,
|
||||
{ method: 'PUT', body: data }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Video Upload
|
||||
async uploadLessonVideo(courseId: number, chapterId: number, lessonId: number, video: File, attachments?: File[]): Promise<LessonResponse> {
|
||||
const config = useRuntimeConfig();
|
||||
|
|
@ -567,7 +609,7 @@ export const instructorService = {
|
|||
|
||||
const formData = new FormData();
|
||||
files.forEach(file => {
|
||||
formData.append('attachments', file);
|
||||
formData.append('attachment', file);
|
||||
});
|
||||
|
||||
const response = await authRequest<{ code: number; data: LessonResponse }>(
|
||||
|
|
@ -590,6 +632,130 @@ export const instructorService = {
|
|||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments/${attachmentId}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
},
|
||||
|
||||
// Announcements
|
||||
async getAnnouncements(courseId: number): Promise<AnnouncementResponse[]> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return MOCK_ANNOUNCEMENTS;
|
||||
}
|
||||
|
||||
const response = await authRequest<AnnouncementsListResponse>(
|
||||
`/api/instructors/courses/${courseId}/announcements`
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async createAnnouncement(courseId: number, data: CreateAnnouncementRequest, files?: File[]): Promise<AnnouncementResponse> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return {
|
||||
id: Date.now(),
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
status: data.status || 'DRAFT',
|
||||
is_pinned: data.is_pinned || false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
attachments: []
|
||||
};
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('data', JSON.stringify(data));
|
||||
|
||||
if (files && files.length > 0) {
|
||||
files.forEach(file => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
}
|
||||
|
||||
const response = await authRequest<{ code: number; data: AnnouncementResponse }>(
|
||||
`/api/instructors/courses/${courseId}/announcements`,
|
||||
{ method: 'POST', body: formData }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async updateAnnouncement(courseId: number, announcementId: number, data: CreateAnnouncementRequest): Promise<AnnouncementResponse> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return {
|
||||
id: announcementId,
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
status: data.status || 'DRAFT',
|
||||
is_pinned: data.is_pinned || false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
attachments: []
|
||||
};
|
||||
}
|
||||
|
||||
const response = await authRequest<{ code: number; data: AnnouncementResponse }>(
|
||||
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
|
||||
{ method: 'PUT', body: data }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async deleteAnnouncement(courseId: number, announcementId: number): Promise<void> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return;
|
||||
}
|
||||
|
||||
await authRequest(
|
||||
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
},
|
||||
|
||||
async uploadAnnouncementAttachment(courseId: number, announcementId: number, file: File): Promise<AnnouncementResponse> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return MOCK_ANNOUNCEMENTS[0];
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await authRequest<{ code: number; data: AnnouncementResponse }>(
|
||||
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments`,
|
||||
{ method: 'POST', body: formData }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async deleteAnnouncementAttachment(courseId: number, announcementId: number, attachmentId: number): Promise<void> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return;
|
||||
}
|
||||
|
||||
await authRequest(
|
||||
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments/${attachmentId}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -683,6 +849,16 @@ export interface QuizResponse {
|
|||
questions?: QuizQuestionResponse[];
|
||||
}
|
||||
|
||||
export interface UpdateQuizSettingsRequest {
|
||||
title: { th: string; en: string };
|
||||
description: { th: string; en: string };
|
||||
passing_score: number;
|
||||
time_limit: number;
|
||||
shuffle_questions: boolean;
|
||||
shuffle_choices: boolean;
|
||||
show_answers_after_completion: boolean;
|
||||
}
|
||||
|
||||
export interface CreateChapterRequest {
|
||||
title: { th: string; en: string };
|
||||
description: { th: string; en: string };
|
||||
|
|
@ -732,6 +908,84 @@ export interface LessonDetailResponse extends LessonResponse {
|
|||
attachments: AttachmentResponse[];
|
||||
}
|
||||
|
||||
// Announcement interfaces
|
||||
export interface AnnouncementAttachment {
|
||||
id: number;
|
||||
file_name: string;
|
||||
file_path: string;
|
||||
file_size: number;
|
||||
mime_type: string;
|
||||
}
|
||||
|
||||
export interface AnnouncementResponse {
|
||||
id: number;
|
||||
title: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
content: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
status: 'DRAFT' | 'PUBLISHED';
|
||||
is_pinned: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
attachments: AnnouncementAttachment[];
|
||||
}
|
||||
|
||||
export interface AnnouncementsListResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
data: AnnouncementResponse[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface CreateAnnouncementRequest {
|
||||
title: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
content: {
|
||||
th: string;
|
||||
en: string;
|
||||
};
|
||||
status?: 'DRAFT' | 'PUBLISHED';
|
||||
is_pinned?: boolean;
|
||||
}
|
||||
|
||||
// Mock announcements
|
||||
const MOCK_ANNOUNCEMENTS: AnnouncementResponse[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: { th: 'ยินดีต้อนรับสู่คอร์ส', en: 'Welcome to the Course' },
|
||||
content: {
|
||||
th: 'ยินดีต้อนรับทุกคนสู่คอร์ส JavaScript Fundamentals! เราจะเริ่มเรียนในสัปดาห์หน้า',
|
||||
en: 'Welcome everyone to JavaScript Fundamentals! We will start next week'
|
||||
},
|
||||
status: 'PUBLISHED',
|
||||
is_pinned: true,
|
||||
created_at: '2024-01-15T10:00:00Z',
|
||||
updated_at: '2024-01-15T10:00:00Z',
|
||||
attachments: []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: { th: 'อัพเดทตารางเรียน', en: 'Schedule Update' },
|
||||
content: {
|
||||
th: 'มีการเปลี่ยนแปลงตารางเรียนสำหรับบทที่ 3',
|
||||
en: 'There is a schedule change for Chapter 3'
|
||||
},
|
||||
status: 'PUBLISHED',
|
||||
is_pinned: false,
|
||||
created_at: '2024-01-20T14:00:00Z',
|
||||
updated_at: '2024-01-20T14:00:00Z',
|
||||
attachments: []
|
||||
}
|
||||
];
|
||||
|
||||
// Mock course detail
|
||||
const MOCK_COURSE_DETAIL: CourseDetailResponse = {
|
||||
...MOCK_COURSES[0],
|
||||
|
|
|
|||
|
|
@ -178,5 +178,31 @@ export const userService = {
|
|||
newPassword
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async uploadAvatar(file: File): Promise<{ avatar_url: string; id: number }> {
|
||||
const config = useRuntimeConfig();
|
||||
const useMockData = config.public.useMockData as boolean;
|
||||
|
||||
if (useMockData) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
// Return mock URL
|
||||
return { avatar_url: URL.createObjectURL(file), id: 1 };
|
||||
}
|
||||
|
||||
const token = getAuthToken();
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await $fetch<{ code: number; message: string; data: { avatar_url: string; id: number } }>('/api/user/upload-avatar', {
|
||||
method: 'POST',
|
||||
baseURL: config.public.apiBaseUrl as string,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue