Skip to content

Commit

Permalink
Merge pull request #319 from nimit9/feat/user-profile-menu
Browse files Browse the repository at this point in the history
feat: user profile dropdown
  • Loading branch information
hkirat authored May 12, 2024
2 parents 2f319c5 + bf05949 commit ce55656
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 132 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `courseId` on the `Bookmark` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "Bookmark" DROP CONSTRAINT "Bookmark_courseId_fkey";

-- AlterTable
ALTER TABLE "Bookmark" DROP COLUMN "courseId";
1 change: 0 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ model Course {
slug String
content CourseContent[]
purchasedBy UserPurchases[]
bookmarks Bookmark[]
certificate Certificate[]
certIssued Boolean @default(false)
}
Expand Down
36 changes: 17 additions & 19 deletions src/actions/bookmark/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { createSafeAction } from '@/lib/create-safe-action';
import { BookmarkCreateSchema, BookmarkDeleteSchema } from './schema';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { rateLimit } from '@/lib/utils';
import db from '@/db';
import {
InputTypeCreateBookmark,
Expand All @@ -12,8 +11,8 @@ import {
} from './types';
import { revalidatePath } from 'next/cache';

const reloadBookmarkPage = (courseId: number) => {
revalidatePath(`/courses/${courseId}/bookmarks`);
const reloadBookmarkPage = () => {
revalidatePath('/bookmarks');
};

const createBookmarkHandler = async (
Expand All @@ -25,18 +24,22 @@ const createBookmarkHandler = async (
return { error: 'Unauthorized or insufficient permissions' };
}

const { contentId, courseId } = data;
const { contentId } = data;
const userId = session.user.id;

if (!rateLimit(userId)) {
return { error: 'Rate limit exceeded. Please try again later.' };
}

try {
const addedBookmark = await db.bookmark.create({
data: { contentId, userId, courseId },
const addedBookmark = await db.bookmark.upsert({
where: {
contentId,
userId,
},
create: {
contentId,
userId,
},
update: {},
});
reloadBookmarkPage(courseId);
reloadBookmarkPage();
return { data: addedBookmark };
} catch (error: any) {
return { error: error.message || 'Failed to create comment.' };
Expand All @@ -51,19 +54,14 @@ const deleteBookmarkHandler = async (
if (!session || !session.user) {
return { error: 'Unauthorized or insufficient permissions' };
}

const { id, courseId } = data;
const userId = session.user.id;

if (!rateLimit(userId)) {
return { error: 'Rate limit exceeded. Please try again later.' };
}
const { id } = data;

try {
const deletedBookmark = await db.bookmark.delete({
where: { id },
where: { id, userId },
});
reloadBookmarkPage(courseId);
reloadBookmarkPage();
return { data: deletedBookmark };
} catch (error: any) {
return { error: error.message || 'Failed to create comment.' };
Expand Down
2 changes: 0 additions & 2 deletions src/actions/bookmark/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { z } from 'zod';

export const BookmarkCreateSchema = z.object({
contentId: z.number(),
courseId: z.number(),
});
export const BookmarkDeleteSchema = z.object({
id: z.number(),
courseId: z.number(),
});
6 changes: 4 additions & 2 deletions src/actions/bookmark/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from 'zod';
import { BookmarkCreateSchema, BookmarkDeleteSchema } from './schema';
import { ActionState } from '@/lib/create-safe-action';
import { Bookmark, Content } from '@prisma/client';
import { Bookmark, Content, CourseContent } from '@prisma/client';

export type InputTypeCreateBookmark = z.infer<typeof BookmarkCreateSchema>;
export type ReturnTypeCreateBookmark = ActionState<
Expand All @@ -15,5 +15,7 @@ export type ReturnTypeDeleteBookmark = ActionState<
>;

export type TBookmarkWithContent = Bookmark & {
content: Content & { parent?: Content | null };
content: Content & {
parent: { id: number; courses: CourseContent[] } | null;
};
};
11 changes: 11 additions & 0 deletions src/app/bookmarks/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CourseSkeleton } from '@/components/CourseCard';

export default function Loading() {
return (
<div className="max-w-screen-xl justify-between mx-auto p-4 cursor-pointer grid grid-cols-1 gap-5 md:grid-cols-3">
{[1, 2, 3].map((v) => (
<CourseSkeleton key={v} />
))}
</div>
);
}
47 changes: 47 additions & 0 deletions src/app/bookmarks/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import db from '@/db';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { Content, CourseContent, VideoProgress } from '@prisma/client';
import { TBookmarkWithContent } from '@/actions/bookmark/types';
import BookmarkView from '@/components/bookmark/BookmarkView';

export type TWatchHistory = VideoProgress & {
content: Content & {
parent: { id: number; courses: CourseContent[] } | null;
VideoMetadata: { duration: number | null } | null;
};
};

const getBookmarkData = async (): Promise<
TBookmarkWithContent[] | { error: string }
> => {
const session = await getServerSession(authOptions);
const userId = session.user.id;

return await db.bookmark.findMany({
where: {
userId,
},
include: {
content: {
include: {
parent: {
select: {
id: true,
courses: true,
},
},
},
},
},
orderBy: {
createdAt: 'desc',
},
});
};

export default async function CoursesComponent() {
const bookmarkData = await getBookmarkData();

return <BookmarkView bookmarkData={bookmarkData} />;
}
68 changes: 4 additions & 64 deletions src/components/Appbar.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
'use client';

import Link from 'next/link';
import { JoinDiscord } from './JoinDiscord';
import { AppbarAuth } from './AppbarAuth';
import { useSession } from 'next-auth/react';
import { useRecoilState } from 'recoil';
import { sidebarOpen as sidebarOpenAtom } from '../store/atoms/sidebar';
import { ToggleButton } from './Sidebar';
import { useParams, usePathname } from 'next/navigation';
import { usePathname } from 'next/navigation';
import Logo from './landing/logo/logo';
import { Button } from './ui/button';
import { Sparkles } from 'lucide-react';
import { ThemeToggler } from './ThemeToggler';
import { NavigationMenu } from './landing/appbar/nav-menu';
import SearchBar from './search/SearchBar';
import MobileScreenSearch from './search/MobileScreenSearch';
import ProfileDropdown from './profile-menu/ProfileDropdown';
import { ThemeToggler } from './ThemeToggler';

export const Appbar = () => {
const session = useSession();
const [sidebarOpen, setSidebarOpen] = useRecoilState(sidebarOpenAtom);
const currentPath = usePathname();
const params = useParams();
let bookmarkPageUrl = null;
if (params.courseId && params.courseId[0]) {
bookmarkPageUrl = `/courses/${params.courseId[0]}/bookmarks`;
}

return (
<>
Expand All @@ -48,61 +43,8 @@ export const Appbar = () => {
<div className="flex items-center space-x-2">
{/* Search Bar for smaller devices */}
<MobileScreenSearch />
<div className="flex items-center space-x-2">
<div className="hidden sm:flex items-center justify-around md:w-auto md:block space-x-2">
{currentPath.includes('courses') && bookmarkPageUrl && (
<Button
variant="link"
className={
currentPath === bookmarkPageUrl
? 'font-bold underline'
: ''
}
size={'sm'}
asChild
>
<Link href={bookmarkPageUrl}>Bookmarks</Link>
</Button>
)}

<Button variant={'link'} size={'sm'} asChild>
<JoinDiscord isNavigated={false} />
</Button>

<Button size={'sm'} variant={'link'} asChild>
<Link
href={'https://projects.100xdevs.com/'}
target="_blank"
>
Slides
</Link>
</Button>
<ProfileDropdown />

<Button size={'sm'} variant={'link'} asChild>
<Link
href={
'https://github.com/100xdevs-cohort-2/assignments'
}
target="_blank"
>
Assignments
</Link>
</Button>
<Button size={'sm'} variant={'link'} asChild>
<Link href={'/history'}>Watch History</Link>
</Button>
<Button size={'sm'} variant={'link'} asChild>
<Link href={'/questions'}>Q&A</Link>
</Button>
<AppbarAuth />
</div>

<ThemeToggler />

<div className="block sm:hidden">
<NavigationMenu />
</div>
</div>
</div>
</>
) : (
Expand All @@ -120,9 +62,7 @@ export const Appbar = () => {
</Link>
</Button>
</div>

<ThemeToggler />

<div className="block sm:hidden">
<NavigationMenu />
</div>
Expand Down
26 changes: 12 additions & 14 deletions src/components/AppbarAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { signIn, signOut } from 'next-auth/react';
import { signIn } from 'next-auth/react';
import { useSession } from 'next-auth/react';
//import { useRouter } from 'next/navigation';
import { Button } from './ui/button';
Expand All @@ -10,19 +10,17 @@ export const AppbarAuth = ({ isInMenu = false }: { isInMenu?: boolean }) => {
//const router = useRouter();

return (
<Button
size={'sm'}
variant={isInMenu ? 'navLink' : 'outline'}
id="navbar-default"
onClick={() => {
if (session.data?.user) {
signOut();
} else {
!session?.data?.user && (
<Button
size={'sm'}
variant={isInMenu ? 'navLink' : 'outline'}
id="navbar-default"
onClick={() => {
signIn();
}
}}
>
{session.data?.user ? 'Logout' : 'Login'}
</Button>
}}
>
Login
</Button>
)
);
};
52 changes: 29 additions & 23 deletions src/components/bookmark/BookmarkList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,35 @@ const BookmarkList = ({
}
return (
<>
<div className="max-w-screen-xl justify-between mx-auto p-4 cursor-pointer grid grid-cols-1 gap-5 md:grid-cols-3 lg:grid-cols-4 auto-rows-fr">
{bookmarkData.map((bookmark) => {
const {
id,
courseId,
content: { type, parent, title, id: videoId, hidden, thumbnail },
} = bookmark;
if (type === 'video' && parent && !hidden) {
return (
<ContentCard
type={type}
key={id}
title={title}
image={thumbnail || ''}
onClick={() => {
router.push(`/courses/${courseId}/${parent.id}/${videoId}`);
}}
bookmark={bookmark}
contentId={id}
/>
);
}
})}
<div className="max-w-screen-xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-8">Bookmarks</h1>
<div className="cursor-pointer grid grid-cols-1 gap-5 md:grid-cols-3 lg:grid-cols-4 auto-rows-fr">
{bookmarkData.map((bookmark) => {
const {
id: contentId,
content: { type, parent, title, hidden, thumbnail },
} = bookmark;
if (type === 'video' && parent && !hidden) {
const { id: folderId, courses } = parent;
const courseId = courses[0].courseId;
const videoUrl = `/courses/${courseId}/${folderId}/${contentId}`;

return (
<ContentCard
type={type}
key={contentId}
title={title}
image={thumbnail || ''}
onClick={() => {
router.push(videoUrl);
}}
bookmark={bookmark}
contentId={contentId}
/>
);
}
})}
</div>
</div>
</>
);
Expand Down
Loading

0 comments on commit ce55656

Please sign in to comment.