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

feat: add pin course feature #1729

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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