diff --git a/nextjs/src/app/CourseDetailsWrapper.tsx b/nextjs/src/app/CourseDetailsWrapper.tsx
new file mode 100644
index 0000000..4680818
--- /dev/null
+++ b/nextjs/src/app/CourseDetailsWrapper.tsx
@@ -0,0 +1,18 @@
+"use client";
+import { useLocalCourseDetailsQuery } from "@/hooks/localCoursesHooks";
+import { CourseContext } from "./course/[courseName]/courseContext";
+import CourseDetails from "./course/[courseName]/CourseDetails";
+
+export default function CourseDetailsWrapper({
+ courseName,
+}: {
+ courseName: string;
+}) {
+ const { data: course } = useLocalCourseDetailsQuery(courseName);
+ console.log("courseName", courseName);
+ return (
+
+
+
+ );
+}
diff --git a/nextjs/src/app/CourseList.tsx b/nextjs/src/app/CourseList.tsx
new file mode 100644
index 0000000..2d52137
--- /dev/null
+++ b/nextjs/src/app/CourseList.tsx
@@ -0,0 +1,16 @@
+"use client"
+import { useLocalCoursesQuery } from "@/hooks/localCoursesHooks";
+import Link from "next/link";
+
+export default function CourseList() {
+ const { data: courses } = useLocalCoursesQuery();
+ return (
+
+ {courses.map((c) => (
+
+ {c.settings.name}{" "}
+
+ ))}
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/calendarMonth.tsx b/nextjs/src/app/course/[courseName]/CalendarMonth.tsx
similarity index 94%
rename from nextjs/src/app/course/[courseName]/calendarMonth.tsx
rename to nextjs/src/app/course/[courseName]/CalendarMonth.tsx
index bba1e24..f5bec1d 100644
--- a/nextjs/src/app/course/[courseName]/calendarMonth.tsx
+++ b/nextjs/src/app/course/[courseName]/CalendarMonth.tsx
@@ -1,7 +1,8 @@
+"use client"
import { useState } from "react";
import { CalendarMonthModel } from "./calendarMonthUtils";
import { DayOfWeek, LocalCourse } from "@/models/local/localCourse";
-import Day from "./day";
+import Day from "./Day";
export default function CalendarMonth({
month,
@@ -55,7 +56,7 @@ export default function CalendarMonth({
{month.daysByWeek.map((week, weekIndex) => (
{week.map((day, dayIndex) => (
-
+
))}
))}
diff --git a/nextjs/src/app/course/[courseName]/CourseContextProvider.tsx b/nextjs/src/app/course/[courseName]/CourseContextProvider.tsx
new file mode 100644
index 0000000..df2c4e0
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/CourseContextProvider.tsx
@@ -0,0 +1,19 @@
+"use client"
+import { ReactNode } from "react";
+import { CourseContext } from "./courseContext";
+import { useLocalCourseDetailsQuery } from "@/hooks/localCoursesHooks";
+
+export default function CourseContextProvider({
+ localCourseName,
+ children,
+}: {
+ children: ReactNode;
+ localCourseName: string;
+}) {
+ const { data: course } = useLocalCourseDetailsQuery(localCourseName);
+ return (
+
+ {children}
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/CourseDetails.tsx b/nextjs/src/app/course/[courseName]/CourseDetails.tsx
new file mode 100644
index 0000000..4234ca5
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/CourseDetails.tsx
@@ -0,0 +1,42 @@
+"use client";
+import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
+import { useCourseContext } from "./courseContext";
+import { getMonthsBetweenDates } from "./calendarMonthUtils";
+import CalendarMonth from "./CalendarMonth";
+
+export default function CourseDetails() {
+ const context = useCourseContext();
+
+ const startDate = getDateFromStringOrThrow(
+ context.localCourse.settings.startDate,
+ "course start date"
+ );
+ const endDate = getDateFromStringOrThrow(
+ context.localCourse.settings.endDate,
+ "course end date"
+ );
+
+ const months = getMonthsBetweenDates(startDate, endDate);
+ return (
+
+ {context.localCourse.settings.name}
+
+
+ {months.map((month) => (
+
+ ))}
+
+
+
+ first module
+ here are the module items
+
+
+
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/Day.tsx b/nextjs/src/app/course/[courseName]/Day.tsx
new file mode 100644
index 0000000..417fb1b
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/Day.tsx
@@ -0,0 +1,13 @@
+"use client"
+export default function Day({ day, month }: { day: Date; month: number }) {
+ const classes = "border rounded rounded-3 p-2 pb-4 m-1 ";
+
+ const backgroundClass = day.getMonth() + 1 != month ? "" : "bg-slate-900";
+
+ return (
+
+ {day.getDate()}
+ {/*
{day.getMonth()}
*/}
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/ModuleList.tsx b/nextjs/src/app/course/[courseName]/ModuleList.tsx
new file mode 100644
index 0000000..628fc69
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/ModuleList.tsx
@@ -0,0 +1,4 @@
+"use client"
+export default function ModuleList() {
+ return
+}
\ No newline at end of file
diff --git a/nextjs/src/app/course/[courseName]/calendarMonthUtils.ts b/nextjs/src/app/course/[courseName]/calendarMonthUtils.ts
index a2ae318..8e039f1 100644
--- a/nextjs/src/app/course/[courseName]/calendarMonthUtils.ts
+++ b/nextjs/src/app/course/[courseName]/calendarMonthUtils.ts
@@ -1,8 +1,9 @@
+"use client"
export interface CalendarMonthModel {
year: number;
month: number;
- weeks: (number | undefined)[][];
- daysByWeek: (Date | undefined)[][];
+ weeks: number[][];
+ daysByWeek: (Date)[][];
}
function weeksInMonth(year: number, month: number): number {
@@ -17,42 +18,27 @@ function weeksInMonth(year: number, month: number): number {
}
function createCalendarMonth(year: number, month: number): CalendarMonthModel {
- const daysByWeek: (Date | undefined)[][] = [];
const weeksNumber = weeksInMonth(year, month);
const daysInMonth = new Date(year, month, 0).getDate();
let currentDay = 1;
const firstDayOfMonth = new Date(year, month - 1, 1).getDay();
- for (let i = 0; i < weeksNumber; i++) {
- const thisWeek: (Date | undefined)[] = [];
- if (i === 0 && firstDayOfMonth !== 0) {
- // 0 is Sunday in JavaScript
- for (let j = 0; j < 7; j++) {
- if (j < firstDayOfMonth) {
- thisWeek.push(undefined);
- } else {
- thisWeek.push(new Date(year, month - 1, currentDay));
- currentDay++;
- }
+ const daysByWeek = Array.from({ length: weeksNumber }).map((_, weekIndex) =>
+ Array.from({ length: 7 }).map((_, dayIndex) => {
+ if (weekIndex === 0 && dayIndex < firstDayOfMonth) {
+ return new Date(year, month - 1, dayIndex - firstDayOfMonth + 1);
+ } else if (currentDay <= daysInMonth) {
+ return new Date(year, month - 1, currentDay++);
+ } else {
+ currentDay++;
+ return new Date(year, month, currentDay - daysInMonth);
}
- } else {
- for (let j = 0; j < 7; j++) {
- if (currentDay <= daysInMonth) {
- thisWeek.push(new Date(year, month - 1, currentDay));
- currentDay++;
- } else {
- thisWeek.push(undefined);
- }
- }
- }
- daysByWeek.push(thisWeek);
- }
-
- const weeks = daysByWeek.map((week) =>
- week.map((day) => (day ? day.getDate() : undefined))
+ })
);
+ const weeks = daysByWeek.map((week) => week.map((day) => day.getDate()));
+
return { year, month, weeks, daysByWeek };
}
diff --git a/nextjs/src/app/course/[courseName]/courseContext.ts b/nextjs/src/app/course/[courseName]/courseContext.ts
new file mode 100644
index 0000000..e298bda
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/courseContext.ts
@@ -0,0 +1,31 @@
+"use client";
+import { LocalCourse } from "@/models/local/localCourse";
+import { createContext, useContext } from "react";
+
+export interface CourseContextInterface {
+ localCourse: LocalCourse;
+}
+
+const defaultValue: CourseContextInterface = {
+ localCourse: {
+ modules: [],
+ settings: {
+ name: "",
+ assignmentGroups: [],
+ daysOfWeek: [],
+ startDate: "",
+ endDate: "",
+ defaultDueTime: {
+ hour: 0,
+ minute: 0,
+ },
+ },
+ },
+};
+
+export const CourseContext =
+ createContext(defaultValue);
+
+export function useCourseContext() {
+ return useContext(CourseContext);
+}
diff --git a/nextjs/src/app/course/[courseName]/day.tsx b/nextjs/src/app/course/[courseName]/day.tsx
deleted file mode 100644
index cd4db52..0000000
--- a/nextjs/src/app/course/[courseName]/day.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-export default function Day({ day }: { day?: Date }) {
- const classes = "border rounded rounded-3 p-2 pb-4 m-1 ";
- if (!day) return ;
-
- return {day.getDate()}
;
-}
diff --git a/nextjs/src/app/course/[courseName]/page.tsx b/nextjs/src/app/course/[courseName]/page.tsx
index 9fae88b..6ef89bc 100644
--- a/nextjs/src/app/course/[courseName]/page.tsx
+++ b/nextjs/src/app/course/[courseName]/page.tsx
@@ -1,29 +1,16 @@
-"use client";
-import { useLocalCourseDetailsQuery } from "@/hooks/localCoursesHooks";
-import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
-import { getMonthsBetweenDates } from "./calendarMonthUtils";
-import CalendarMonth from "./calendarMonth";
+import CourseDetailsWrapper from "@/app/CourseDetailsWrapper";
+import { getDehydratedClient } from "@/app/layout";
+import { HydrationBoundary } from "@tanstack/react-query";
-
-export default function Page({
+export default async function CoursePage({
params: { courseName },
}: {
params: { courseName: string };
}) {
- const { data: course } = useLocalCourseDetailsQuery(courseName);
-
- const startDate = getDateFromStringOrThrow(course.settings.startDate);
- const endDate = getDateFromStringOrThrow(course.settings.endDate);
-
- const months = getMonthsBetweenDates(startDate, endDate);
+ const dehydratedState = await getDehydratedClient();
return (
-
- {course.settings.name}
-
- {months.map((month) => (
-
- ))}
-
-
+
+
+
);
}
diff --git a/nextjs/src/app/getDehydratedState.ts b/nextjs/src/app/getDehydratedState.ts
new file mode 100644
index 0000000..209bf06
--- /dev/null
+++ b/nextjs/src/app/getDehydratedState.ts
@@ -0,0 +1,11 @@
+import { hydrateCourses } from "@/hooks/hookHydration";
+import { createQueryClientForServer } from "@/services/utils/queryClientServer";
+import { dehydrate } from "@tanstack/react-query";
+
+export async function getDehydratedState() {
+ const queryClient = createQueryClientForServer();
+
+ await hydrateCourses(queryClient);
+ const dehydratedState = dehydrate(queryClient);
+ return dehydratedState;
+}
diff --git a/nextjs/src/app/globals.css b/nextjs/src/app/globals.css
index 875c01e..a88506d 100644
--- a/nextjs/src/app/globals.css
+++ b/nextjs/src/app/globals.css
@@ -18,12 +18,7 @@
body {
color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
+
}
@layer utilities {
diff --git a/nextjs/src/app/layout.tsx b/nextjs/src/app/layout.tsx
index 1c01047..dd51ed9 100644
--- a/nextjs/src/app/layout.tsx
+++ b/nextjs/src/app/layout.tsx
@@ -3,8 +3,7 @@ import "./globals.css";
import { dehydrate } from "@tanstack/react-query";
import { hydrateCourses } from "@/hooks/hookHydration";
import { createQueryClientForServer } from "@/services/utils/queryClientServer";
-import MyQueryClientProvider from "@/services/utils/MyQueryClientProvider";
-
+import Providers from "./providers";
export const metadata: Metadata = {
title: "Canvas Manager 2.0",
@@ -18,18 +17,16 @@ export async function getDehydratedClient() {
return dehydratedState;
}
-export default async function RootLayout({
+export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
- const dehydratedState = await getDehydratedClient();
-
return (
-
- {children}
-
+
+ {children}
+
);
}
diff --git a/nextjs/src/app/page.tsx b/nextjs/src/app/page.tsx
index 67f1a01..7d51850 100644
--- a/nextjs/src/app/page.tsx
+++ b/nextjs/src/app/page.tsx
@@ -1,14 +1,14 @@
-"use client"
-import { useLocalCoursesQuery } from "@/hooks/localCoursesHooks";
-import Link from "next/link";
+import { HydrationBoundary } from "@tanstack/react-query";
+import CourseList from "./CourseList";
+import { getDehydratedClient } from "./layout";
-export default function Home() {
- const { data: courses } = useLocalCoursesQuery();
+export default async function Home() {
+ const dehydratedState = await getDehydratedClient();
return (
- {courses.map((c) => (
- {c.settings.name}
- ))}
+
+
+
);
}
diff --git a/nextjs/src/app/providers.tsx b/nextjs/src/app/providers.tsx
new file mode 100644
index 0000000..968f494
--- /dev/null
+++ b/nextjs/src/app/providers.tsx
@@ -0,0 +1,47 @@
+"use client";
+import {
+ isServer,
+ QueryClient,
+ QueryClientProvider,
+} from "@tanstack/react-query";
+import { ReactNode } from "react";
+
+function makeQueryClient() {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ // With SSR, we usually want to set some default staleTime
+ // above 0 to avoid refetching immediately on the client
+ staleTime: 60 * 1000,
+ },
+ },
+ });
+}
+
+let browserQueryClient: QueryClient | undefined = undefined;
+
+function getQueryClient() {
+ if (isServer) {
+ // Server: always make a new query client
+ return makeQueryClient();
+ } else {
+ // Browser: make a new query client if we don't already have one
+ // This is very important, so we don't re-make a new client if React
+ // suspends during the initial render. This may not be needed if we
+ // have a suspense boundary BELOW the creation of the query client
+ if (!browserQueryClient) browserQueryClient = makeQueryClient();
+ return browserQueryClient;
+ }
+}
+
+export default function Providers({ children }: { children: ReactNode }) {
+ // NOTE: Avoid useState when initializing the query client if you don't
+ // have a suspense boundary between this and the code that may
+ // suspend because React will throw away the client on the initial
+ // render if it suspends and there is no boundary
+ const queryClient = getQueryClient();
+
+ return (
+ {children}
+ );
+}
diff --git a/nextjs/src/models/local/timeUtils.ts b/nextjs/src/models/local/timeUtils.ts
index 0e3176c..06dc7e8 100644
--- a/nextjs/src/models/local/timeUtils.ts
+++ b/nextjs/src/models/local/timeUtils.ts
@@ -56,9 +56,9 @@ export const getDateFromString = (value: string): Date | undefined => {
}
};
-export const getDateFromStringOrThrow = (value: string): Date => {
+export const getDateFromStringOrThrow = (value: string, labelForError: string): Date => {
const d = getDateFromString(value);
- if (!d) throw Error(`Invalid date format, ${value}`);
+ if (!d) throw Error(`Invalid date format for ${labelForError}, ${value}`);
return d;
};
diff --git a/nextjs/src/services/canvas/canvasServiceUtils.ts b/nextjs/src/services/canvas/canvasServiceUtils.ts
index db2c537..f0444d1 100644
--- a/nextjs/src/services/canvas/canvasServiceUtils.ts
+++ b/nextjs/src/services/canvas/canvasServiceUtils.ts
@@ -23,9 +23,8 @@ export const canvasServiceUtils = {
const url = new URL(request.url!, BASE_URL);
url.searchParams.set("per_page", "100");
- const [firstData, firstResponse] = await webRequestor.get(
- url.toString()
- );
+ const { data: firstData, response: firstResponse } =
+ await webRequestor.get(url.toString());
if (!firstResponse.ok) {
console.error(
@@ -41,7 +40,7 @@ export const canvasServiceUtils = {
while (nextUrl) {
requestCount += 1;
- const [data, response] = await webRequestor.get(nextUrl);
+ const {data, response} = await webRequestor.get(nextUrl);
if (data) {
returnData = [...returnData, data];
}
diff --git a/nextjs/src/services/utils/MyQueryClientProvider.tsx b/nextjs/src/services/utils/MyQueryClientProvider.tsx
index e771645..320692d 100644
--- a/nextjs/src/services/utils/MyQueryClientProvider.tsx
+++ b/nextjs/src/services/utils/MyQueryClientProvider.tsx
@@ -1,12 +1,11 @@
-"use client";
-
+"use client"
import {
DehydratedState,
hydrate,
QueryClientProvider,
} from "@tanstack/react-query";
import React from "react";
-import { FC, ReactNode, useState } from "react";
+import { ReactNode, useState } from "react";
import { createQueryClient } from "./queryClient";
export default function MyQueryClientProvider({