Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: thumbnail display #308

Merged
merged 2 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions apps/api/src/courses/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ export class CourseService {
.select({
id: courses.id,
title: courses.title,
thumbnailUrl: sql<string>`COALESCE(${courses.thumbnailS3Key}, '')`,
thumbnailS3Key: sql<string>`COALESCE(${courses.thumbnailS3Key}, '')`,
category: categories.title,
categoryId: categories.id,
description: sql<string>`${courses.description}`,
Expand Down Expand Up @@ -489,7 +489,7 @@ export class CourseService {
return await this.fileService.getFileUrl(url);
};

const thumbnailUrl = await getImageUrl(course.thumbnailUrl);
const thumbnailS3SingedUrl = await getImageUrl(course.thumbnailS3Key);

const updatedCourseLessonList = await Promise.all(
courseChapterList?.map(async (chapter) => {
Expand All @@ -507,7 +507,7 @@ export class CourseService {

return {
...course,
thumbnailUrl,
thumbnailS3SingedUrl,
chapters: updatedCourseLessonList ?? [],
};
}
Expand All @@ -517,7 +517,7 @@ export class CourseService {
.select({
id: courses.id,
title: courses.title,
thumbnailUrl: sql<string>`${courses.thumbnailS3Key}`,
thumbnailS3Key: sql<string>`${courses.thumbnailS3Key}`,
category: categories.title,
categoryId: categories.id,
description: sql<string>`${courses.description}`,
Expand Down Expand Up @@ -556,11 +556,11 @@ export class CourseService {
return await this.fileService.getFileUrl(url);
};

const thumbnailUrl = await getImageUrl(course.thumbnailUrl);
const thumbnailS3SingedUrl = await getImageUrl(course.thumbnailS3Key);

return {
...course,
thumbnailUrl,
thumbnailS3SingedUrl,
chapters: courseChapterList,
};
}
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/courses/schemas/showCourseCommon.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const commonShowCourseSchema = Type.Object({
isScorm: Type.Optional(Type.Boolean()),
priceInCents: Type.Number(),
thumbnailUrl: Type.Optional(Type.String()),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I also left the old value because it is used in other places and I am not sure if it is needed for now, it will be possible to remove it when we completely switch to the new course backend

thumbnailS3Key: Type.Optional(Type.String()),
thumbnailS3SingedUrl: Type.Optional(Type.String()),
title: Type.String(),
});

Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/seed/seed-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ export async function createNiceCourses(
lessonData.type === LESSON_TYPES.presentation
? "pptx"
: lessonData.type === LESSON_TYPES.video
? "mp4"
: null,
? "mp4"
: null,
chapterId: chapter.id,
createdAt: createdAt,
updatedAt: createdAt,
Expand Down
18 changes: 18 additions & 0 deletions apps/api/src/swagger/api-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3711,6 +3711,12 @@
"thumbnailUrl": {
"type": "string"
},
"thumbnailS3Key": {
"type": "string"
},
"thumbnailS3SingedUrl": {
"type": "string"
},
"title": {
"type": "string"
}
Expand Down Expand Up @@ -3857,6 +3863,12 @@
"thumbnailUrl": {
"type": "string"
},
"thumbnailS3Key": {
"type": "string"
},
"thumbnailS3SingedUrl": {
"type": "string"
},
"title": {
"type": "string"
}
Expand Down Expand Up @@ -4003,6 +4015,12 @@
"thumbnailUrl": {
"type": "string"
},
"thumbnailS3Key": {
"type": "string"
},
"thumbnailS3SingedUrl": {
"type": "string"
},
"title": {
"type": "string"
}
Expand Down
8 changes: 6 additions & 2 deletions apps/web/app/api/generated-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,8 @@ export interface GetCourseResponse {
isScorm?: boolean;
priceInCents: number;
thumbnailUrl?: string;
thumbnailS3Key?: string;
thumbnailS3SingedUrl?: string;
title: string;
};
}
Expand Down Expand Up @@ -506,6 +508,8 @@ export interface GetCourseByIdResponse {
isScorm?: boolean;
priceInCents: number;
thumbnailUrl?: string;
thumbnailS3Key?: string;
thumbnailS3SingedUrl?: string;
title: string;
};
}
Expand Down Expand Up @@ -545,6 +549,8 @@ export interface GetBetaCourseByIdResponse {
isScorm?: boolean;
priceInCents: number;
thumbnailUrl?: string;
thumbnailS3Key?: string;
thumbnailS3SingedUrl?: string;
title: string;
};
}
Expand Down Expand Up @@ -1687,8 +1693,6 @@ export class API<SecurityDataType extends unknown> extends HttpClient<SecurityDa
*/
courseControllerGetStudentCourses: (
query?: {
/** @format uuid */
excludeCourseId?: string;
title?: string;
category?: string;
author?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";

import { useUploadFile } from "~/api/mutations/admin/useUploadFile";
import { useCategoriesSuspense } from "~/api/queries/useCategories";
Expand Down Expand Up @@ -26,30 +26,34 @@ type CourseSettingsProps = {
title?: string;
description?: string;
categoryId?: string;
imageUrl?: string;
thumbnailS3SingedUrl?: string;
thumbnailS3Key?: string;
};
const CourseSettings = ({
courseId,
title,
description,
categoryId,
imageUrl,
thumbnailS3SingedUrl,
thumbnailS3Key,
}: CourseSettingsProps) => {
const { form, onSubmit } = useCourseSettingsForm({
title,
description,
categoryId,
imageUrl,
thumbnailS3Key,
courseId: courseId || "",
});
const { data: categories } = useCategoriesSuspense();
const [isUploading, setIsUploading] = useState(false);
const { mutateAsync: uploadFile } = useUploadFile();
const isFormValid = form.formState.isValid;
const [displayThumbnailUrl, setDisplayThumbnailUrl] = useState<string | undefined>(
thumbnailS3SingedUrl || undefined,
);

const watchedTitle = form.watch("title");
const watchedDescription = form.watch("description");
const watchedImageUrl = form.watch("imageUrl");
const watchedCategoryId = form.getValues("categoryId");

const categoryName = useMemo(() => {
Expand All @@ -61,7 +65,8 @@ const CourseSettings = ({
setIsUploading(true);
try {
const result = await uploadFile({ file, resource: "course" });
form.setValue("imageUrl", result.fileUrl, { shouldValidate: true });
form.setValue("thumbnailS3Key", result.fileKey, { shouldValidate: true });
setDisplayThumbnailUrl(result.fileUrl);
} catch (error) {
console.error("Error uploading image:", error);
} finally {
Expand All @@ -71,6 +76,11 @@ const CourseSettings = ({
[form, uploadFile],
);

const removeThumbnail = () => {
form.setValue("thumbnailS3Key", "");
setDisplayThumbnailUrl(undefined);
};

return (
<div className="w-full flex h-full gap-x-6">
<div className="w-full basis-full">
Expand Down Expand Up @@ -121,16 +131,16 @@ const CourseSettings = ({
/>
<FormField
control={form.control}
name="imageUrl"
name="thumbnailS3Key"
render={({ field }) => (
<FormItem className="max-h-[440px]">
<Label htmlFor="imageUrl">Thumbnail</Label>
<Label htmlFor="thumbnailS3Key">Thumbnail</Label>
<FormControl>
<ImageUploadInput
field={field}
handleImageUpload={handleImageUpload}
isUploading={isUploading}
imageUrl={imageUrl}
imageUrl={displayThumbnailUrl}
/>
</FormControl>
{isUploading && <p>Uploading image...</p>}
Expand All @@ -139,11 +149,8 @@ const CourseSettings = ({
)}
/>
<div className="flex items-center justify-start gap-x-2">
{watchedImageUrl && (
<Button
onClick={() => form.setValue("imageUrl", "")}
className="bg-red-500 text-white py-2 px-6"
>
{displayThumbnailUrl && (
<Button onClick={removeThumbnail} className="bg-red-500 text-white py-2 px-6">
<Icon name="TrashIcon" className="mr-2" />
Remove Thumbnail
</Button>
Expand All @@ -160,7 +167,7 @@ const CourseSettings = ({
</div>
<div className="max-w-[480px] w-full">
<CourseCardPreview
imageUrl={watchedImageUrl}
imageUrl={displayThumbnailUrl}
title={watchedTitle}
description={watchedDescription}
category={categoryName}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";

import { useDeleteFile } from "~/api/mutations/admin/useDeleteFile";
import { useUpdateCourse } from "~/api/mutations/admin/useUpdateCourse";
import { courseQueryOptions } from "~/api/queries/admin/useCourseById";
import { queryClient } from "~/api/queryClient";
Expand All @@ -14,46 +13,35 @@ type CourseSettingsProps = {
title?: string;
description?: string;
categoryId?: string;
imageUrl?: string;
thumbnailS3Key?: string;
courseId: string;
};

export const useCourseSettingsForm = ({
title,
description,
categoryId,
imageUrl,
thumbnailS3Key,
courseId,
}: CourseSettingsProps) => {
const { mutateAsync: updateCourse } = useUpdateCourse();

const { mutateAsync: deleteOldFile } = useDeleteFile();
const form = useForm<CourseSettingsFormValues>({
resolver: zodResolver(courseSettingsFormSchema),
defaultValues: {
title: title || "",
description: description || "",
categoryId: categoryId || "",
imageUrl: imageUrl || "",
thumbnailS3Key: thumbnailS3Key || "",
},
});

const newImageUrl = form.getValues("imageUrl");
const oldImageUrl = imageUrl ?? "";

const oldImageExist = Boolean(oldImageUrl);
const newImageExist = Boolean(newImageUrl);
const imageUrlChanged = oldImageExist && newImageExist && newImageUrl !== oldImageUrl;

const onSubmit = async (data: UpdateCourseBody) => {
updateCourse({
data: { ...data },
courseId,
}).then(() => {
queryClient.invalidateQueries(courseQueryOptions(courseId));
if (imageUrlChanged) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it because it seems to me that it doesn't work at the moment

deleteOldFile(oldImageUrl);
}
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const courseSettingsFormSchema = z.object({
title: z.string().min(2, "Title must be at least 2 characters."),
description: z.string().min(2, "Description must be at least 2 characters."),
categoryId: z.string().min(1, "Category is required"),
imageUrl: z.union([z.string().url("Invalid image URL"), z.string().length(0)]).optional(),
thumbnailS3Key: z.string().optional(),
});

export type CourseSettingsFormValues = z.infer<typeof courseSettingsFormSchema>;
3 changes: 2 additions & 1 deletion apps/web/app/modules/Admin/EditCourse/EditCourse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ const EditCourse = () => {
title={course?.title}
description={course?.description}
categoryId={course?.categoryId}
imageUrl={course?.thumbnailUrl}
thumbnailS3SingedUrl={course?.thumbnailS3SingedUrl}
thumbnailS3Key={course?.thumbnailS3Key}
/>
</TabsContent>
<TabsContent value="Curriculum" className="h-full overflow-hidden">
Expand Down
Loading