Skip to content

Commit 6387d22

Browse files
authored
Merge pull request #4 from VLD-dev-team/dashboard-creation
Création de la page de dashboard
2 parents 675d988 + 1d8c7cf commit 6387d22

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1262
-314
lines changed

app/api/stripe-webhook/route.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use server";
22

3-
import fulfill_checkout from '@/app/services/checkoutSession/fulfill_session';
3+
import fulfill_checkout from '@/functions/checkoutSession/fulfill_session';
44
import { stripe } from '@/stripe';
55

66
export async function POST(request: Request) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use client";
2+
3+
import { useChat } from "../context/chatContext";
4+
5+
export default function ChatList() {
6+
7+
const rooms = useChat();
8+
9+
return (
10+
<div>
11+
{
12+
rooms.rooms.map((room) => {
13+
return <p>{room.name}</p>
14+
})
15+
}
16+
</div>
17+
)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"use client";
2+
3+
import React, { Dispatch, createContext, useContext, useReducer } from "react";
4+
import { ChatRoom } from "@/types/chat";
5+
6+
interface ChatStateTypes {
7+
rooms: ChatRoom[],
8+
unreadCount: number,
9+
error: string,
10+
loading: boolean,
11+
}
12+
13+
const initialChatState: ChatStateTypes = {
14+
rooms: new Array<ChatRoom>,
15+
unreadCount: 0,
16+
error: "",
17+
loading: false,
18+
}
19+
20+
const ChatContext = createContext<ChatStateTypes>(initialChatState);
21+
const ChatDispatchContext = createContext<Dispatch<any>>(() => { });
22+
23+
export const CHATROOMS_LOADED = "CHATROOMS_LOADED";
24+
export const ERROR_LOADING_ROOMS = "ERROR_LOADING_ROOMS";
25+
export const NEW_CHATROOM = "NEW_CHATROOM";
26+
export const CHATROOM_DELETED = "CHATROOM_DELETED";
27+
export const NEW_MESSAGE_IN_CHATROOM = "NEW_MESSAGE_IN_CHATROOM";
28+
export const CHATROOM_READ = "CHATROOM_READ"
29+
30+
function StudentCourseReducer(ChatState: ChatStateTypes, actionPayload: any): ChatStateTypes {
31+
switch (actionPayload.type) {
32+
case CHATROOMS_LOADED:
33+
return {
34+
rooms: actionPayload.rooms,
35+
error: "",
36+
loading: false,
37+
unreadCount: actionPayload.unreadCount,
38+
}
39+
case ERROR_LOADING_ROOMS:
40+
return {
41+
...ChatState,
42+
error: actionPayload.error,
43+
}
44+
case NEW_CHATROOM:
45+
return {
46+
...ChatState,
47+
}
48+
case CHATROOM_DELETED:
49+
return {
50+
...ChatState,
51+
}
52+
case NEW_MESSAGE_IN_CHATROOM:
53+
return {
54+
...ChatState,
55+
}
56+
case CHATROOM_READ:
57+
return {
58+
...ChatState,
59+
}
60+
default:
61+
return ChatState;
62+
}
63+
}
64+
65+
export function ChatProvider({ children }: {
66+
children: React.ReactElement
67+
}) {
68+
const [Chat, dispatch] = useReducer(StudentCourseReducer, initialChatState);
69+
70+
return (
71+
<ChatContext.Provider value={Chat}>
72+
<ChatDispatchContext.Provider value={dispatch}>
73+
{children}
74+
</ChatDispatchContext.Provider>
75+
</ChatContext.Provider>
76+
);
77+
}
78+
79+
export function useChat() {
80+
return useContext(ChatContext);
81+
}
82+
83+
export function useChatDispatch() {
84+
return useContext(ChatDispatchContext);
85+
}
86+

app/dashboard/chats/page.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import ChatList from "./components/chatList";
2+
import getChatRooms from "@/functions/chats/getChatRooms";
3+
4+
export default async function ChatPage() {
5+
return (
6+
<div>
7+
<ChatList></ChatList>
8+
</div>
9+
)
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { getStudentCourses } from "@/functions/courses/getStudentCourses"
2+
import { Course } from "@/types/course";
3+
import Link from "next/link";
4+
5+
export default async function FavoriteCourses() {
6+
7+
const favoriteCourses: Course[] | null = await getStudentCourses("favorite");
8+
9+
return (
10+
<div>
11+
<p className="uppercase text-sm pb-4">Vos cours favoris</p>
12+
<div className="bg-[var(--surface)] rounded p-4 md:p-6 overflow-x-scroll shadow">
13+
{
14+
(favoriteCourses == null || favoriteCourses.length == 0)
15+
? <div className="py-4 grid place-items-center md:flex md:px-4 gap-2">
16+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="size-5">
17+
<path fillRule="evenodd" d="m5.965 4.904 9.131 9.131a6.5 6.5 0 0 0-9.131-9.131Zm8.07 10.192L4.904 5.965a6.5 6.5 0 0 0 9.131 9.131ZM4.343 4.343a8 8 0 1 1 11.314 11.314A8 8 0 0 1 4.343 4.343Z" clipRule="evenodd" />
18+
</svg>
19+
<p>Pas de cours mis en favoris</p>
20+
</div>
21+
: (
22+
<div className="inline-block w-max">
23+
{
24+
favoriteCourses.map(course => {
25+
return (
26+
<DashCourseCard course={course} />
27+
)
28+
})
29+
}
30+
</div>
31+
)
32+
}
33+
</div>
34+
</div>
35+
)
36+
}
37+
38+
async function DashCourseCard({ course }: { course: Course }) {
39+
return (
40+
<Link className="inline-flex items-center bg-[var(--primary)] text-[var(--on-primary)] px-5 py-3 md:py-5 rounded-xl gap-4 mr-5 md:min-w-[400px]" href={`/course/${course.courseID}`}>
41+
<div className="shrink-0">
42+
<div className="md:border-2 rounded-full md:p-5">
43+
<img src={course.iconURL} alt="" className="size-8 md:size-10" />
44+
</div>
45+
</div>
46+
<div>
47+
<p className="font-semibold md:text-xl">{course.name}</p>
48+
<p className="truncate text-sm md:text-base line-clamp-1 md:line-clamp-2">{course.desc}</p>
49+
<span className="hidden md:flex items-center mt-2">
50+
<p className="uppercase text-sm">Page du cours</p>
51+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="size-5">
52+
<path fillRule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clipRule="evenodd" />
53+
</svg>
54+
</span>
55+
</div>
56+
</Link>
57+
)
58+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import ChatList from "@/app/dashboard/chats/components/chatList"
2+
import getChatRooms from "@/functions/chats/getChatRooms"
3+
import { ChatRoom } from "@/types/chat"
4+
5+
export default async function DashBoardMessages() {
6+
7+
const chatRooms: ChatRoom[] | null = await getChatRooms()
8+
9+
return (
10+
<div>
11+
<p className="uppercase text-sm pb-4">CORRESPONDANCES AVEC VOS PROFESSEURS</p>
12+
<div className="flex flex-wrap md:flex gap-3 bg-[var(--surface)] rounded p-4 md:p-8 shadow">
13+
<ChatList></ChatList>
14+
</div>
15+
</div>
16+
)
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client";
2+
3+
import { registerCharts } from "@/functions/charts/registerCharts";
4+
import { Bar } from "react-chartjs-2";
5+
6+
registerCharts();
7+
8+
export default function ProgressChart({ progressData }: { progressData: { totalXP: number, days: { progressDate: string; dailyProgressScore: number }[] } | null }) {
9+
10+
const options = {
11+
responsive: true,
12+
plugins: {
13+
legend: {
14+
position: "top" as const,
15+
},
16+
title: {
17+
display: true,
18+
text: "Progression sur les 7 derniers jours.",
19+
},
20+
},
21+
}
22+
23+
let labels: string[] = [];
24+
let columnData: number[] = [];
25+
progressData?.days.forEach(day => {
26+
labels.push(day.progressDate.toString());
27+
columnData.push(day.dailyProgressScore);
28+
})
29+
30+
const data = {
31+
labels,
32+
datasets: [
33+
{
34+
label: "XP",
35+
data: columnData,
36+
backgroundColor: "var(--primary)",
37+
}
38+
]
39+
}
40+
41+
return (
42+
<Bar options={options} data={data} height={"200px"}/>
43+
)
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getProgressSummary } from "@/functions/progress/getProgressSummary";
2+
import ProgressChart from "./progressChart";
3+
4+
export default async function progressSummary() {
5+
6+
const progressData = await getProgressSummary();
7+
8+
return (
9+
<div>
10+
<p className="uppercase text-sm pb-4">RÉSUMÉ DE VOTRE PROGRESSION</p>
11+
<div className="flex flex-wrap md:flex gap-3 bg-[var(--surface)] rounded p-4 md:p-8 shadow">
12+
<ProgressChart progressData={progressData}></ProgressChart>
13+
</div>
14+
</div>
15+
)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Link from "next/link";
2+
import ShortcutButton from "../shortcutButton";
3+
import { getLastViewedChapterURL } from "@/functions/courses/getLastViewedChapter";
4+
5+
export default async function Shortcuts() {
6+
7+
const LastViewedChapter = await getLastViewedChapterURL();
8+
9+
return (
10+
<div className="">
11+
<p className="uppercase text-sm pb-4">Raccourcis rapides</p>
12+
<div className="flex flex-wrap md:flex gap-3">
13+
<Link href={LastViewedChapter ?? "/dashboard/"}>
14+
<ShortcutButton icon={"history"} text={"Reprendre mon dernier cours"}></ShortcutButton>
15+
</Link>
16+
<Link href={"/privatelessons"}>
17+
<ShortcutButton icon={"contact_phone"} text={"Calendrier des visioconférences"}></ShortcutButton>
18+
</Link>
19+
</div>
20+
</div>
21+
)
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { ChatProvider, CHATROOMS_LOADED, ERROR_LOADING_ROOMS, NEW_MESSAGE_IN_CHATROOM, useChat, useChatDispatch } from "../../chats/context/chatContext";
5+
import TabButton from "./tabButton";
6+
import { getSocket } from "@/app/utils/getSocket";
7+
import getChatRooms from "@/functions/chats/getChatRooms";
8+
9+
export default function MessageButtonIndicator() {
10+
11+
const chat = useChat();
12+
const dispatch = useChatDispatch();
13+
14+
useEffect(() => {
15+
if (chat.rooms.length == 0) {
16+
getChatRooms().then((loadedRooms) => {
17+
dispatch({
18+
type: CHATROOMS_LOADED,
19+
rooms: loadedRooms.rooms,
20+
unreadCount: loadedRooms.unreadCount
21+
})
22+
}).catch((error) => {
23+
dispatch({
24+
type: ERROR_LOADING_ROOMS,
25+
error: "Erreur de chargement de la liste de discussion."
26+
})
27+
});
28+
}
29+
}, [getChatRooms, dispatch]);
30+
31+
return (
32+
<ChatProvider>
33+
<span className="relative">
34+
{
35+
(chat.unreadCount > 0)
36+
? <div className="absolute top-0 right-0 bg-red-500 p-2 w-4 h-4 rounded-full"></div>
37+
: null
38+
}
39+
<TabButton href={"/dashboard/chats"} iconNameOrPath={"chat"} isGoogleIcon={true} />
40+
</span>
41+
</ChatProvider>
42+
)
43+
}

app/dashboard/components/navBar/navBar.tsx

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
import TabButton from "./tabButton";
2-
import { Course } from "@/app/types/course";
3-
import { getStudentCourses } from "@/app/services/courses/getStudentCourses";
2+
import { Course } from "@/types/course";
3+
import { getStudentCourses } from "@/functions/courses/getStudentCourses";
4+
import MessageButtonIndicator from "./messageIndicator";
45

56
export default async function NavBar() {
67
// On importe les cours
78
const courses = await getStudentCourses() ?? [];
89

910
return (
10-
<div className="w-full h-full bg-[var(--primary-container)] flex flex-col gap-5 overflow-y-scroll items-center">
11-
<img src="/logos/vldminiwhite.png" alt="VLDschool" width="80%" height={30} className="invert dark:invert-0 mt-4 p-1" />
11+
<div className="w-full h-full bg-[var(--primary-container)] rounded-t-xl md:rounded-none flex md:flex-col md:gap-5 md:overflow-y-scroll items-center justify-evenly md:justify-start">
12+
<img src="/logos/vldminiwhite.png" alt="VLDschool" width="80%" height={30} className="hidden md:block mt-4 p-1" />
1213
<TabButton href={"/dashboard"} iconNameOrPath={"home"} isGoogleIcon={true} />
1314
<TabButton href={"/dashboard/profile"} iconNameOrPath={"person"} isGoogleIcon={true} />
14-
<div className="h-[2px] bg-[var(--primary)] w-1/6 mx-1"></div>
15+
<MessageButtonIndicator></MessageButtonIndicator>
16+
<div className="shrink-0 hidden md:block h-[2px] bg-[var(--primary)] w-1/6 mx-1"></div>
1517
<TabButton href={"/dashboard/courses"} iconNameOrPath={"view_list"} isGoogleIcon={true} />
16-
{
17-
courses.filter((course: Course) => course.isFavorite !== false).map((course: Course) => (
18-
<TabButton href={`/dashboard/courses/${course.courseID}`} iconNameOrPath={`${course.iconURL}`} isGoogleIcon={false} key={`key-${course.courseID}`} />
19-
))
20-
}
21-
<div className="h-[2px] bg-[var(--primary)] w-1/6 mx-1"></div>
18+
<span className="flex-col gap-5 hidden md:flex">
19+
{
20+
courses.filter((course: Course) => course.isFavorite !== false).map((course: Course) => (
21+
<TabButton href={`/dashboard/courses/${course.courseID}`} iconNameOrPath={`${course.iconURL}`} isGoogleIcon={false} key={`key-${course.courseID}`} />
22+
))
23+
}
24+
</span>
25+
<div className="shrink-0 hidden md:block h-[2px] bg-[var(--primary)] w-1/6 mx-1"></div>
2226
<TabButton href={"/shop"} iconNameOrPath={"shop"} isGoogleIcon={true} />
2327
</div>
2428
)

app/dashboard/components/navBar/tabButton.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ export default function TabButton(
1414
const selectedProperties = (pathname.endsWith(href)) ? "bg-[var(--primary)]" : "";
1515

1616
return (
17-
<Link href={href} className={"p-[20px] rounded-full cursor-pointer border-[2px] border-[var(--primary)] aspect-square flex items-center justify-center hover:bg-[var(--primary-hover)] " + selectedProperties}>
17+
<Link href={href} className={"p-[20px] basis-1/4 md:basis-auto rounded-t-xl md:rounded-full cursor-pointer md:border-[2px] border-[var(--primary)] md:aspect-square flex items-center justify-center hover:bg-[var(--primary-hover)] " + selectedProperties}>
1818
{
1919
(isGoogleIcon)
20-
? <span className="material-symbols-rounded text-[24px]">{iconNameOrPath}</span>
20+
? <span className="material-symbols-rounded text-[24px] text-[--on-primary]">{iconNameOrPath}</span>
2121
: <img src={iconNameOrPath} className="w-[24px] h-[24px] text-[--on-primary]" />
2222
}
2323
</Link>

0 commit comments

Comments
 (0)