diff --git a/nextjs/src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx b/nextjs/src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx
index 902c1fc..5ffc339 100644
--- a/nextjs/src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx
+++ b/nextjs/src/app/course/[courseName]/settings/AssignmentGroupManagement.tsx
@@ -6,7 +6,7 @@ import {
} from "@/hooks/localCourse/localCoursesHooks";
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
import { useEffect, useState } from "react";
-import TextInput from "./TextInput";
+import TextInput from "../../../../components/form/TextInput";
import { useSetAssignmentGroupsMutation } from "@/hooks/canvas/canvasCourseHooks";
export default function AssignmentGroupManagement() {
diff --git a/nextjs/src/app/page.tsx b/nextjs/src/app/page.tsx
index d1c929f..41b4311 100644
--- a/nextjs/src/app/page.tsx
+++ b/nextjs/src/app/page.tsx
@@ -1,9 +1,16 @@
+import AddNewCourse from "./AddNewCourse";
import CourseList from "./CourseList";
export default async function Home() {
return (
-
-
+
+
);
}
diff --git a/nextjs/src/components/SuspenseAndErrorHandling.tsx b/nextjs/src/components/SuspenseAndErrorHandling.tsx
new file mode 100644
index 0000000..38fd739
--- /dev/null
+++ b/nextjs/src/components/SuspenseAndErrorHandling.tsx
@@ -0,0 +1,32 @@
+import { getErrorMessage } from "@/services/utils/queryClient";
+import { QueryErrorResetBoundary } from "@tanstack/react-query";
+import { FC, ReactNode, Suspense } from "react";
+import { ErrorBoundary } from "react-error-boundary";
+import { Spinner } from "./Spinner";
+
+export const SuspenseAndErrorHandling: FC<{ children: ReactNode }> = ({
+ children,
+}) => {
+ return (
+
+ {({ reset }) => (
+ (
+
+
{getErrorMessage(props.error)}
+
+
+ )}
+ >
+ }>{children}
+
+ )}
+
+ );
+};
diff --git a/nextjs/src/components/form/SelectInput.tsx b/nextjs/src/components/form/SelectInput.tsx
new file mode 100644
index 0000000..96fb88c
--- /dev/null
+++ b/nextjs/src/components/form/SelectInput.tsx
@@ -0,0 +1,33 @@
+export default function SelectInput({
+ value,
+ setValue,
+ label,
+ options,
+ getOptionName,
+}: {
+ value: T | undefined;
+ setValue: (newValue: T | undefined) => void;
+ label: string;
+ options: T[];
+ getOptionName: (item: T) => string;
+}) {
+ return (
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/settings/TextInput.tsx b/nextjs/src/components/form/TextInput.tsx
similarity index 100%
rename from nextjs/src/app/course/[courseName]/settings/TextInput.tsx
rename to nextjs/src/components/form/TextInput.tsx
diff --git a/nextjs/src/hooks/canvas/canvasHooks.ts b/nextjs/src/hooks/canvas/canvasHooks.ts
new file mode 100644
index 0000000..68b2ab0
--- /dev/null
+++ b/nextjs/src/hooks/canvas/canvasHooks.ts
@@ -0,0 +1,37 @@
+import { canvasService } from "@/services/canvas/canvasService";
+import { useSuspenseQuery } from "@tanstack/react-query";
+
+export const canvasKeys = {
+ allTerms: ["all canvas terms"] as const,
+ allAroundDate: (date: Date) => ["all canvas terms", date] as const,
+};
+
+export const useAllCanvasTermsQuery = () =>
+ useSuspenseQuery({
+ queryKey: canvasKeys.allTerms,
+ queryFn: canvasService.getAllTerms,
+ });
+export const useCanvasTermsQuery = (queryDate: Date) => {
+ const { data: terms } = useAllCanvasTermsQuery();
+
+ return useSuspenseQuery({
+ queryKey: canvasKeys.allAroundDate(queryDate),
+ queryFn: () => {
+ const currentTerms = terms
+ .filter((t) => {
+ if (!t.end_at) return false;
+
+ const endDate = new Date(t.end_at);
+ return endDate > queryDate;
+ })
+ .sort(
+ (a, b) =>
+ new Date(a.start_at ?? "").getTime() -
+ new Date(b.start_at ?? "").getTime()
+ )
+ .slice(0, 3);
+
+ return currentTerms;
+ },
+ });
+};
diff --git a/nextjs/src/services/canvas/canvasService.ts b/nextjs/src/services/canvas/canvasService.ts
index 81cc922..168b870 100644
--- a/nextjs/src/services/canvas/canvasService.ts
+++ b/nextjs/src/services/canvas/canvasService.ts
@@ -6,19 +6,21 @@ import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
import { CanvasEnrollmentModel } from "@/models/canvas/enrollments/canvasEnrollmentModel";
import { axiosClient } from "../axiosUtils";
-const getTerms = async () => {
- const url = `accounts/10/terms`;
- const data = await canvasServiceUtils.paginatedRequest<{
+const baseCanvasUrl = "https://snow.instructure.com/api/v1";
+
+const getAllTerms = async () => {
+ const url = `${baseCanvasUrl}/accounts/10/terms`;
+ const {data} = await axiosClient.get<{
enrollment_terms: CanvasEnrollmentTermModel[];
- }>({ url });
+ }[]>(url);
const terms = data.flatMap((r) => r.enrollment_terms);
return terms;
};
export const canvasService = {
- getTerms,
+ getAllTerms,
async getCourses(termId: number) {
- const url = `courses`;
+ const url = `${baseCanvasUrl}/courses`;
const coursesResponse =
await canvasServiceUtils.paginatedRequest({ url });
return coursesResponse
@@ -27,13 +29,13 @@ export const canvasService = {
},
async getCourse(courseId: number): Promise {
- const url = `courses/${courseId}`;
+ const url = `${baseCanvasUrl}/courses/${courseId}`;
const { data } = await axiosClient.get(url);
return data;
},
async getCurrentTermsFor(queryDate: Date = new Date()) {
- const terms = await getTerms();
+ const terms = await getAllTerms();
const currentTerms = terms
.filter(
(t) =>
@@ -58,7 +60,7 @@ export const canvasService = {
item: CanvasModuleItem
) {
console.log(`Updating module item ${item.title}`);
- const url = `courses/${canvasCourseId}/modules/${canvasModuleId}/items/${item.id}`;
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items/${item.id}`;
const body = {
module_item: { title: item.title, position: item.position },
};
@@ -75,7 +77,7 @@ export const canvasService = {
contentId: number | string
): Promise {
console.log(`Creating new module item ${title}`);
- const url = `courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
const body = { module_item: { title, type, content_id: contentId } };
const response = await axiosClient.post(url, body);
},
@@ -87,7 +89,7 @@ export const canvasService = {
canvasPage: CanvasPage
): Promise {
console.log(`Creating new module item ${title}`);
- const url = `courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
const body = {
module_item: { title, type: "Page", page_url: canvasPage.url },
};
@@ -96,7 +98,7 @@ export const canvasService = {
async getEnrolledStudents(canvasCourseId: number) {
console.log(`Getting students for course ${canvasCourseId}`);
- const url = `courses/${canvasCourseId}/enrollments?enrollment_type=student`;
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/enrollments?enrollment_type=student`;
const { data } = await axiosClient.get(url);
if (!data)
diff --git a/nextjs/src/services/canvas/canvasServiceUtils.ts b/nextjs/src/services/canvas/canvasServiceUtils.ts
index d418009..acf8a27 100644
--- a/nextjs/src/services/canvas/canvasServiceUtils.ts
+++ b/nextjs/src/services/canvas/canvasServiceUtils.ts
@@ -34,6 +34,7 @@ export const canvasServiceUtils = {
var returnData: T[] = firstData ? [firstData] : [];
var nextUrl = getNextUrl(firstHeaders);
+ console.log("got first request", nextUrl, firstHeaders);
while (nextUrl) {
requestCount += 1;