Skip to content
Open
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
13 changes: 13 additions & 0 deletions prisma/migrations/20250120153105_add_pinned_courses/migration.sql
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 3 additions & 0 deletions prisma/migrations/20250124185937_add_is_pinned/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "UserPurchases" ADD COLUMN "isPinned" BOOLEAN NOT NULL DEFAULT false;

Original file line number Diff line number Diff line change
@@ -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";
2 changes: 2 additions & 0 deletions prisma/migrations/20250124190616_add_is_pinned/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "UserPurchases" ADD COLUMN "isPinned" BOOLEAN NOT NULL DEFAULT false;
Original file line number Diff line number Diff line change
@@ -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";
19 changes: 19 additions & 0 deletions src/components/CourseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Card className="w-full max-w-sm overflow-hidden transition-all hover:shadow-lg cursor-pointer" onClick={onClick}>
<div className="relative aspect-video overflow-hidden">
<div
className="absolute right-5 top-5 z-50"
onClick={(e) => {
e.stopPropagation();
onPinToggle();
}}
>
{
isPinned ? <TbPinnedFilled color="white" size="2rem" /> : <TbPinned color="white" size="2rem" />
}
</div>

<img
alt={course.title}
className="object-cover w-full h-full transition-transform hover:scale-105"
Expand Down
32 changes: 31 additions & 1 deletion src/components/Courses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,38 @@ import { refreshDb } from '@/actions/refresh-db';
import { useSession } from 'next-auth/react';
import { toast } from 'sonner';
import Link from 'next/link';
import { useEffect, useState } from 'react';

export const Courses = ({ courses }: { courses: Course[] }) => {
const [pinnedCourseIds, setPinnedCourseIds] = useState<number[]>([]);
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 });
Expand All @@ -24,10 +52,12 @@ export const Courses = ({ courses }: { courses: Course[] }) => {
const router = useRouter();
return (
<section className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{courses?.map((course) => (
{sortedCourses?.map((course) => (
<CourseCard
key={course.id}
course={course}
isPinned={pinnedCourseIds.includes(course.id)}
onPinToggle={() => togglePin(course.id)}
onClick={() => {
if (
course.title.includes('Machine Learning') ||
Expand Down
Loading