diff --git a/prisma/migrations/20250120153105_add_pinned_courses/migration.sql b/prisma/migrations/20250120153105_add_pinned_courses/migration.sql new file mode 100644 index 000000000..d2472d2c0 --- /dev/null +++ b/prisma/migrations/20250120153105_add_pinned_courses/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "PinnedCourses" ( + "userId" TEXT NOT NULL, + "courseId" INTEGER NOT NULL, + + CONSTRAINT "PinnedCourses_pkey" PRIMARY KEY ("userId","courseId") +); + +-- AddForeignKey +ALTER TABLE "PinnedCourses" ADD CONSTRAINT "PinnedCourses_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PinnedCourses" ADD CONSTRAINT "PinnedCourses_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20250124185937_add_is_pinned/migration.sql b/prisma/migrations/20250124185937_add_is_pinned/migration.sql new file mode 100644 index 000000000..8e48533fe --- /dev/null +++ b/prisma/migrations/20250124185937_add_is_pinned/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "UserPurchases" ADD COLUMN "isPinned" BOOLEAN NOT NULL DEFAULT false; + \ No newline at end of file diff --git a/prisma/migrations/20250124190548_delete_is_pinned/migration.sql b/prisma/migrations/20250124190548_delete_is_pinned/migration.sql new file mode 100644 index 000000000..29e7200c9 --- /dev/null +++ b/prisma/migrations/20250124190548_delete_is_pinned/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `isPinned` on the `UserPurchases` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "UserPurchases" DROP COLUMN "isPinned"; diff --git a/prisma/migrations/20250124190616_add_is_pinned/migration.sql b/prisma/migrations/20250124190616_add_is_pinned/migration.sql new file mode 100644 index 000000000..5fecc493a --- /dev/null +++ b/prisma/migrations/20250124190616_add_is_pinned/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "UserPurchases" ADD COLUMN "isPinned" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20250131185345_remove_pinned_courses_table/migration.sql b/prisma/migrations/20250131185345_remove_pinned_courses_table/migration.sql new file mode 100644 index 000000000..6e786f56f --- /dev/null +++ b/prisma/migrations/20250131185345_remove_pinned_courses_table/migration.sql @@ -0,0 +1,18 @@ +/* + Warnings: + + - You are about to drop the column `isPinned` on the `UserPurchases` table. All the data in the column will be lost. + - You are about to drop the `PinnedCourses` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "PinnedCourses" DROP CONSTRAINT "PinnedCourses_courseId_fkey"; + +-- DropForeignKey +ALTER TABLE "PinnedCourses" DROP CONSTRAINT "PinnedCourses_userId_fkey"; + +-- AlterTable +ALTER TABLE "UserPurchases" DROP COLUMN "isPinned"; + +-- DropTable +DROP TABLE "PinnedCourses"; diff --git a/src/components/CourseCard.tsx b/src/components/CourseCard.tsx index ecf8cc6ae..940024517 100644 --- a/src/components/CourseCard.tsx +++ b/src/components/CourseCard.tsx @@ -6,21 +6,40 @@ import { Button } from './ui/button'; import { Card, CardContent } from "@/components/ui/card"; import { motion } from "framer-motion"; import { MessageCircle, PlayCircle } from "lucide-react"; +import { TbPinned } from "react-icons/tb"; +import { TbPinnedFilled } from "react-icons/tb"; export const CourseCard = ({ course, onClick, + isPinned, + onPinToggle, }: { course: Course; onClick: () => void; + isPinned?: boolean; + onPinToggle?: () => void; }) => { const router = useRouter(); + const imageUrl = course.imageUrl ? course.imageUrl : 'banner_placeholder.png'; const percentage = course.totalVideos !== undefined ? Math.ceil(((course.totalVideosWatched ?? 0) / course?.totalVideos) * 100) : 0; return (
+
{ + e.stopPropagation(); + onPinToggle(); + }} + > + { + isPinned ? : + } +
+ {course.title} { + const [pinnedCourseIds, setPinnedCourseIds] = useState([]); const session = useSession(); + useEffect(() => { + if (typeof window !== 'undefined') { + const stored = localStorage.getItem('pinnedCourses'); + setPinnedCourseIds(stored ? JSON.parse(stored) : []); + } + }, []); + + const togglePin = (courseId: number) => { + if (typeof window === 'undefined') return; + setPinnedCourseIds(prev => { + const newPinned = prev.includes(courseId) + ? prev.filter(id => id !== courseId) + : [...prev, courseId]; + localStorage.setItem('pinnedCourses', JSON.stringify(newPinned)); + return newPinned; + }); + }; + + const sortedCourses = [...courses].sort((a: Course, b: Course) : any => { + if (!pinnedCourseIds) return; + const aPinned = pinnedCourseIds.includes(a.id); + const bPinned = pinnedCourseIds.includes(b.id); + if (aPinned === bPinned) return 0; + return -1; + }); + const handleClick = async () => { // @ts-ignore const res = await refreshDb({ userId: session.data.user.id }); @@ -24,10 +52,12 @@ export const Courses = ({ courses }: { courses: Course[] }) => { const router = useRouter(); return (
- {courses?.map((course) => ( + {sortedCourses?.map((course) => ( togglePin(course.id)} onClick={() => { if ( course.title.includes('Machine Learning') ||