hot reloading is better

This commit is contained in:
2024-08-27 17:14:21 -06:00
parent bc1449f537
commit eb18f93875
19 changed files with 248 additions and 89 deletions

View File

@@ -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 (
<CourseContext.Provider value={{ localCourse: course }}>
<CourseDetails />
</CourseContext.Provider>
);
}

View File

@@ -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 (
<div>
{courses.map((c) => (
<Link href={`/course/${c.settings.name}`} key={c.settings.name}>
{c.settings.name}{" "}
</Link>
))}
</div>
);
}

View File

@@ -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) => (
<div className="grid grid-cols-7 m-3" key={weekIndex}>
{week.map((day, dayIndex) => (
<Day key={dayIndex} day={day} />
<Day key={dayIndex} day={day} month={month.month} />
))}
</div>
))}

View File

@@ -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 (
<CourseContext.Provider value={{ localCourse: course }}>
{children}
</CourseContext.Provider>
);
}

View File

@@ -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 (
<div>
{context.localCourse.settings.name}
<div className="flex flex-row">
<div className="grow">
{months.map((month) => (
<CalendarMonth
key={month.month + "" + month.year}
month={month}
localCourse={context.localCourse}
/>
))}
</div>
<div className="w-96">
<details>
<summary role="button">first module</summary>
<div>here are the module items</div>
</details>
</div>
</div>
</div>
);
}

View File

@@ -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 (
<div className={classes + " " + backgroundClass}>
{day.getDate()}
{/* <div>{day.getMonth()}</div> */}
</div>
);
}

View File

@@ -0,0 +1,4 @@
"use client"
export default function ModuleList() {
return <div></div>
}

View File

@@ -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 };
}

View File

@@ -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<CourseContextInterface>(defaultValue);
export function useCourseContext() {
return useContext(CourseContext);
}

View File

@@ -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 <div className={classes + ""}></div>;
return <div className={classes + " bg-slate-900"}>{day.getDate()}</div>;
}

View File

@@ -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 (
<div>
{course.settings.name}
<div>
{months.map((month) => (
<CalendarMonth key={month.month + "" + month.year} month={month} localCourse={course} />
))}
</div>
</div>
<HydrationBoundary state={dehydratedState}>
<CourseDetailsWrapper courseName={courseName} />
</HydrationBoundary>
);
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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 (
<html lang="en">
<MyQueryClientProvider dehydratedState={dehydratedState}>
<body className="bg-slate-950">{children}</body>
</MyQueryClientProvider>
<Providers>
<body className="bg-slate-950">{children}</body>
</Providers>
</html>
);
}

View File

@@ -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 (
<main className="min-h-screen">
{courses.map((c) => (
<Link href={`/course/${c.settings.name}`} key={c.settings.name}>{c.settings.name} </Link>
))}
<HydrationBoundary state={dehydratedState}>
<CourseList />
</HydrationBoundary>
</main>
);
}

View File

@@ -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 (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}

View File

@@ -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;
};

View File

@@ -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<T>(
url.toString()
);
const { data: firstData, response: firstResponse } =
await webRequestor.get<T>(url.toString());
if (!firstResponse.ok) {
console.error(
@@ -41,7 +40,7 @@ export const canvasServiceUtils = {
while (nextUrl) {
requestCount += 1;
const [data, response] = await webRequestor.get<T>(nextUrl);
const {data, response} = await webRequestor.get<T>(nextUrl);
if (data) {
returnData = [...returnData, data];
}

View File

@@ -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({