feat: add public instructor profiles
All checks were successful
CI / Validate frontend and API (push) Successful in 54s
CI / Build and publish Docker images (push) Successful in 26s

This commit is contained in:
Cauê Faleiros
2026-09-08 10:50:35 -03:00
parent 07fbc7d009
commit 7b2d25aabb
12 changed files with 372 additions and 7 deletions

View File

@@ -10,6 +10,23 @@ export interface ApiUser {
role: ApiRole;
}
export interface Profile {
id: string;
email?: string;
name: string;
role: ApiRole;
avatarImageUrl: string | null;
headline: string;
bio: string;
websiteUrl: string | null;
linkedinUrl: string | null;
instagramUrl: string | null;
youtubeUrl: string | null;
isPublic: boolean;
}
export type ProfileInput = Omit<Profile, 'id' | 'email' | 'role'>;
export interface ManagedUser {
id: string;
email: string;
@@ -188,6 +205,12 @@ export const adminApi = {
async updateHomeConfiguration(input: { featuredCourseId: string | null; courseOrder: string[]; defaultCoverImageUrl: string | null }) { return apiRequest<{ data: typeof input }>('/admin/home-configuration', { method: 'PUT', body: JSON.stringify(input) }); },
};
export const profileApi = {
async me() { return apiRequest<{ data: Profile }>('/profiles/me'); },
async update(input: ProfileInput) { return apiRequest<{ data: Profile }>('/profiles/me', { method: 'PATCH', body: JSON.stringify(input) }); },
async instructor(instructorId: string) { return apiRequest<{ data: Profile & { courses: unknown[] } }>(`/profiles/instructors/${instructorId}`); },
};
export const instructorApi = {
async analytics() { return apiRequest<{ data: InstructorAnalytics }>('/manage/courses/analytics'); },
async bunnyConfiguration() { return apiRequest<{ data: { configured: boolean; maxUploadBytes: number } }>('/manage/media/bunny/config'); },

View File

@@ -11,7 +11,7 @@ type ApiAsset = {
description: string;
};
type ApiCourse = {
export type ApiCourse = {
id: string;
title: string;
description: string;
@@ -23,7 +23,7 @@ type ApiCourse = {
status: 'draft' | 'published' | 'archived';
progress?: number;
lastActivity?: string | null;
instructor: { name: string };
instructor: { id: string; name: string };
lessons: Array<{
id: string;
title: string;
@@ -108,7 +108,7 @@ const toCourseDuration = (lessons: ApiCourse['lessons']) => {
return lessons.length ? 'Processando' : '—';
};
const toCourse = (course: ApiCourse): Course => ({
export const toCourse = (course: ApiCourse): Course => ({
id: course.id,
title: course.title,
description: course.description,
@@ -118,6 +118,7 @@ const toCourse = (course: ApiCourse): Course => ({
coverImagePositionX: course.coverImagePositionX ?? 50,
coverImagePositionY: course.coverImagePositionY ?? 50,
instructor: course.instructor.name,
instructorId: course.instructor.id,
status: course.status === 'draft' ? 'draft' : 'published',
progress: course.progress,
lastActivity: course.lastActivity || undefined,
@@ -206,6 +207,24 @@ export const getCourses = async (): Promise<Course[]> => {
return response.data.map(toCourse);
};
export type InstructorProfileData = {
id: string;
name: string;
avatarImageUrl: string | null;
headline: string;
bio: string;
websiteUrl: string | null;
linkedinUrl: string | null;
instagramUrl: string | null;
youtubeUrl: string | null;
courses: Course[];
};
export const getInstructorProfile = async (instructorId: string): Promise<InstructorProfileData> => {
const response = await apiRequest<{ data: Omit<InstructorProfileData, 'courses'> & { courses: ApiCourse[] } }>(`/profiles/instructors/${instructorId}`);
return { ...response.data, courses: response.data.courses.map(toCourse) };
};
export const getLearningPaths = async (): Promise<PublicLearningPath[]> => {
const response = await apiRequest<{ data: PublicLearningPath[] }>('/courses/paths');
return response.data;