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

calendar added #864

Closed
wants to merge 4 commits into from
Closed
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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"notion-client": "^6.16.0",
"pdf-lib": "^1.17.1",
"react": "^18",
"react-big-calendar": "^1.13.2",
"react-dom": "^18",
"react-hook-form": "^7.50.1",
"react-icons": "^5.1.0",
Expand Down Expand Up @@ -105,6 +106,7 @@
"@testing-library/react": "^15.0.7",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-big-calendar": "^1.8.9",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
Expand Down
54 changes: 54 additions & 0 deletions src/actions/calendar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use server';

import db from '@/db';
import { ContentType, InputTypeGetEvents, ReturnTypeGetEvents } from './types';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { getEventsSchema } from './schema';
import { createSafeAction } from '@/lib/create-safe-action';

export const getEventsHandler = async (
data: InputTypeGetEvents,
): Promise<ReturnTypeGetEvents> => {
try {
const session = await getServerSession(authOptions);

if (!session || !session.user) {
return { error: 'Unauthorized or insufficient permissions' };
}

const folders = await db.courseContent.findMany({
where: {
courseId: data.courseId,
},
include: {
content: {
select: {
children: true,
},
},
},
});

const content: ContentType[] = [];

folders.forEach((folder) => {
folder.content.children.forEach((el) => {
if (el.type === 'video') {
content.push({
id: el.id,
title: el.title,
start: el.createdAt,
end: el.createdAt,
});
}
});
});

return { data: content };
} catch (err: any) {
return { error: err.message || 'Failed to fetch events' };
}
};

export const getEvents = createSafeAction(getEventsSchema, getEventsHandler);
5 changes: 5 additions & 0 deletions src/actions/calendar/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { z } from 'zod';

export const getEventsSchema = z.object({
courseId: z.number(),
});
16 changes: 16 additions & 0 deletions src/actions/calendar/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { z } from 'zod';
import { getEventsSchema } from './schema';
import { ActionState } from '@/lib/create-safe-action';

export type InputTypeGetEvents = z.infer<typeof getEventsSchema>;
export type ReturnTypeGetEvents = ActionState<
InputTypeGetEvents,
ContentType[]
>;

export type ContentType = {
id: number;
title: string;
start: Date;
end: Date;
};
2 changes: 0 additions & 2 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
'use client';

import { Button } from '@/components/ui/button';
import { InfoIcon } from 'lucide-react';
import Link from 'next/link';
import { useEffect } from 'react';

export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
Expand Down
54 changes: 44 additions & 10 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
transform: translateX(-50%);
}


/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
Expand All @@ -87,7 +86,7 @@
display: block !important;
}

.video-js .vjs-current-time{
.video-js .vjs-current-time {
display: block !important;
}

Expand All @@ -111,30 +110,29 @@ width of the controls on screens smaller than 512px */
}

.video-js .vjs-control {
width: 2rem !important;
width: 2rem !important;
}
}

.vjs-tech:focus-visible{
.vjs-tech:focus-visible {
outline: none;
}

.timeline-segments {
.timeline-segments {
position: relative;
left: 0;
height: 5px;
background-color: #afa4a4;
transition: height 150ms ease-in-out;
}
.timeline-segments-preview{
.timeline-segments-preview {
position: absolute;
left: 0;
background: #fff;
top: 0;
bottom: 0;
z-index: 1;
pointer-events: none;

}
.timeline-segments-progress {
position: absolute;
Expand All @@ -144,8 +142,44 @@ width of the controls on screens smaller than 512px */
bottom: 0;
z-index: 2;
pointer-events: none;

}
.timeline-segments:hover{
.timeline-segments:hover {
height: 10px;
}
}

.rbc-header {
color: var(--header-color);
font-size: 14px !important;
display: flex;
align-items: center;
justify-content: center;
height: 36px !important;
background: #17405d;
}

.rbc-allday-cell {
display: none;
}

.rbc-time-slot {
color: #74a4c3;
z-index: 1;
}

.rbc-event {
min-width: auto;
}

.rbc-month-content {
border-radius: 20px;
}

.rbc-event-label {
display: none;
}

.rbc-event-content {
display: flex;
justify-content: center;
align-items: center;
}
10 changes: 9 additions & 1 deletion src/components/Appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ import SearchBar from './search/SearchBar';
import MobileScreenSearch from './search/MobileScreenSearch';
import ProfileDropdown from './profile-menu/ProfileDropdown';
import { ThemeToggler } from './ThemeToggler';
import { CalendarDialog } from './Calendar';
import { useEvents } from '@/hooks/useEvents';

export const Appbar = () => {
const { data: session, status: sessionStatus } = useSession();
const [sidebarOpen, setSidebarOpen] = useRecoilState(sidebarOpenAtom);
const currentPath = usePathname();
const courseId = currentPath.split('/')[2];
const { events } = useEvents(courseId);

const isLoading = sessionStatus === 'loading';

Expand All @@ -43,9 +47,13 @@ export const Appbar = () => {
<div className="hidden md:block">
<SearchBar />
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-7">
{/* Search Bar for smaller devices */}
<MobileScreenSearch />
<CalendarDialog
inCourse={courseId ? true : false}
events={events!}
/>
<ProfileDropdown />
</div>
</>
Expand Down
93 changes: 93 additions & 0 deletions src/components/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Calendar as CalendarIcon } from 'lucide-react';
import { Calendar, dayjsLocalizer, View, Views } from 'react-big-calendar';
import dayjs from 'dayjs';
import 'react-big-calendar/lib/css/react-big-calendar.css';
import { useCallback, useState } from 'react';

const localizer = dayjsLocalizer(dayjs);

interface CalendarProps {
inCourse: boolean;
events: { id: number; title: String; start: Date; end: Date }[];
}

const MyCalendar = ({
events,
}: {
events: {
id: number;
title: String;
start: Date;
end: Date;
}[];
}) => {
const [view, setView] = useState<View>(Views.MONTH);

const [date, setDate] = useState(new Date());

const onNavigate = useCallback(
(newDate: Date) => {
return setDate(newDate);
},
[setDate],
);

const calendarStyle = () => {
return {
style: {
backgroundColor: 'white',
color: 'black',
},
};
};

const handleOnChangeView = (selectedView: View) => {
setView(selectedView);
};

return (
<div>
<Calendar
date={date}
onNavigate={onNavigate}
localizer={localizer}
events={events}
view={view}
defaultView={Views.MONTH}
views={['month', 'week', 'day']}
showMultiDayTimes
style={{ height: 500 }}
onView={handleOnChangeView}
dayPropGetter={calendarStyle}
/>
</div>
);
};

export const CalendarDialog = ({ inCourse, events }: CalendarProps) => {
if (!inCourse) return null;

return (
<Dialog>
<DialogTrigger className="cursor-pointer" asChild>
<CalendarIcon />
</DialogTrigger>
<DialogContent className="min-w-[900px] max-w-fit">
<DialogHeader>
<DialogTitle>Track your progress</DialogTitle>
<DialogDescription>
<MyCalendar events={events} />
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
);
};
Loading
Loading