diff --git a/nextjs/src/app/api/canvas/[...rest]/route.ts b/nextjs/src/app/api/canvas/[...rest]/route.ts
index 3e90c88..d30e48b 100644
--- a/nextjs/src/app/api/canvas/[...rest]/route.ts
+++ b/nextjs/src/app/api/canvas/[...rest]/route.ts
@@ -44,7 +44,7 @@ const proxyResponseHeaders = (response: any) => {
};
export async function GET(
- req: NextRequest,
+ _req: NextRequest,
{ params }: { params: { rest: string[] } }
) {
return withErrorHandling(async () => {
@@ -58,7 +58,12 @@ export async function GET(
url.toString()
);
- var returnData = firstData ? [firstData] : [];
+ if(!Array.isArray(firstData))
+ {
+ return NextResponse.json(firstData)
+ }
+
+ var returnData = firstData;
var nextUrl = getNextUrl(firstHeaders);
while (nextUrl) {
@@ -77,6 +82,7 @@ export async function GET(
}
return NextResponse.json(returnData);
+
} catch (error: any) {
return new NextResponse(
JSON.stringify({ error: error.message || "Canvas GET request failed" }),
diff --git a/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx b/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx
index 968a678..0f16c19 100644
--- a/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx
+++ b/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx
@@ -1,3 +1,4 @@
+"use client";
import {
useAssignmentNamesQuery,
useAssignmentsQueries,
@@ -12,9 +13,15 @@ import {
} from "@/hooks/localCourse/quizHooks";
import { IModuleItem } from "@/models/local/IModuleItem";
import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
-import { useState } from "react";
+import { Suspense, useEffect, useState } from "react";
import Modal from "../../../../components/Modal";
import NewItemForm from "./NewItemForm";
+import { useCanvasModulesQuery } from "@/hooks/canvas/canvasModuleHooks";
+import { Spinner } from "@/components/Spinner";
+import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
+import { isServer } from "@tanstack/react-query";
+import { ModuleCanvasStatus } from "./ModuleCanvasStatus";
+import ClientOnly from "@/components/ClientOnly";
export default function ExpandableModule({
moduleName,
@@ -66,11 +73,14 @@ export default function ExpandableModule({
return (
setExpanded((e) => !e)}
>
- {moduleName}
+
{moduleName}
+
+
+
c.name === moduleName);
+
+ return (
+
+ {!canvasModule &&
Not in Canvas
}
+ {!canvasModule && (
+
+ )}
+ {canvasModule && !canvasModule.published &&
Not Published
}
+ {canvasModule && canvasModule.published &&
Published
}
+
+ );
+}
diff --git a/nextjs/src/app/newCourse/AddNewCourse.tsx b/nextjs/src/app/newCourse/AddNewCourse.tsx
index 18bbecb..4feb587 100644
--- a/nextjs/src/app/newCourse/AddNewCourse.tsx
+++ b/nextjs/src/app/newCourse/AddNewCourse.tsx
@@ -2,6 +2,7 @@
import React, { useState } from "react";
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
import NewCourseForm from "./NewCourseForm";
+import ClientOnly from "@/components/ClientOnly";
export default function AddNewCourse() {
const [showForm, setShowForm] = useState(false);
@@ -13,7 +14,7 @@ export default function AddNewCourse() {
- {showForm && }
+ {showForm && }
diff --git a/nextjs/src/app/page.tsx b/nextjs/src/app/page.tsx
index 70d6b06..0724e01 100644
--- a/nextjs/src/app/page.tsx
+++ b/nextjs/src/app/page.tsx
@@ -8,8 +8,7 @@ export default async function Home() {
-
-
+
);
diff --git a/nextjs/src/components/ClientOnly.tsx b/nextjs/src/components/ClientOnly.tsx
new file mode 100644
index 0000000..a1eb0a7
--- /dev/null
+++ b/nextjs/src/components/ClientOnly.tsx
@@ -0,0 +1,13 @@
+"use client"
+import React, { ReactNode, useEffect, useState } from "react";
+
+export default function ClientOnly({ children }: { children: ReactNode }) {
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
+ if (!isClient) return <>>;
+ return <>{children}>;
+}
diff --git a/nextjs/src/hooks/canvas/canvasHooks.ts b/nextjs/src/hooks/canvas/canvasHooks.ts
index 68b2ab0..929d45e 100644
--- a/nextjs/src/hooks/canvas/canvasHooks.ts
+++ b/nextjs/src/hooks/canvas/canvasHooks.ts
@@ -1,5 +1,6 @@
import { canvasService } from "@/services/canvas/canvasService";
-import { useSuspenseQuery } from "@tanstack/react-query";
+import { QueryClient, useSuspenseQuery } from "@tanstack/react-query";
+import { canvasCourseModuleKeys } from "./canvasModuleHooks";
export const canvasKeys = {
allTerms: ["all canvas terms"] as const,
@@ -34,4 +35,4 @@ export const useCanvasTermsQuery = (queryDate: Date) => {
return currentTerms;
},
});
-};
+};
\ No newline at end of file
diff --git a/nextjs/src/hooks/canvas/canvasModuleHooks.ts b/nextjs/src/hooks/canvas/canvasModuleHooks.ts
new file mode 100644
index 0000000..f628109
--- /dev/null
+++ b/nextjs/src/hooks/canvas/canvasModuleHooks.ts
@@ -0,0 +1,34 @@
+import { canvasModuleService } from "@/services/canvas/canvasModuleService";
+import {
+ useMutation,
+ useQueryClient,
+ useSuspenseQuery,
+} from "@tanstack/react-query";
+import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
+
+export const canvasCourseModuleKeys = {
+ modules: (canvasId: number) => ["canvas", canvasId, "module list"] as const,
+};
+
+export const useCanvasModulesQuery = () => {
+ const { data: settings } = useLocalCourseSettingsQuery();
+ return useSuspenseQuery({
+ queryKey: canvasCourseModuleKeys.modules(settings.canvasId),
+ queryFn: async () =>
+ await canvasModuleService.getCourseModules(settings.canvasId),
+ });
+};
+
+export const useAddCanvasModuleMutation = () => {
+ const { data: settings } = useLocalCourseSettingsQuery();
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: async (moduleName: string) =>
+ await canvasModuleService.createModule(settings.canvasId, moduleName),
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: canvasCourseModuleKeys.modules(settings.canvasId),
+ });
+ },
+ });
+};
diff --git a/nextjs/src/services/canvas/canvasAssignmentGroupService.ts b/nextjs/src/services/canvas/canvasAssignmentGroupService.ts
index 3ceb243..7fc285a 100644
--- a/nextjs/src/services/canvas/canvasAssignmentGroupService.ts
+++ b/nextjs/src/services/canvas/canvasAssignmentGroupService.ts
@@ -1,10 +1,9 @@
-import { canvasServiceUtils } from "./canvasServiceUtils";
+import { baseCanvasUrl, canvasServiceUtils } from "./canvasServiceUtils";
import { axiosClient } from "../axiosUtils";
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
import { rateLimitAwareDelete } from "./canvasWebRequestor";
-const baseCanvasUrl = "https://snow.instructure.com/api/v1";
export const canvasAssignmentGroupService = {
async getAll(courseId: number): Promise
{
diff --git a/nextjs/src/services/canvas/canvasModuleService.ts b/nextjs/src/services/canvas/canvasModuleService.ts
new file mode 100644
index 0000000..cd40fd3
--- /dev/null
+++ b/nextjs/src/services/canvas/canvasModuleService.ts
@@ -0,0 +1,65 @@
+import { CanvasModuleItem } from "@/models/canvas/modules/canvasModuleItems";
+import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
+import { axiosClient } from "../axiosUtils";
+import { baseCanvasUrl } from "./canvasServiceUtils";
+import { CanvasModule } from "@/models/canvas/modules/canvasModule";
+
+export const canvasModuleService = {
+ async updateModuleItem(
+ canvasCourseId: number,
+ canvasModuleId: number,
+ item: CanvasModuleItem
+ ) {
+ console.log(`Updating module item ${item.title}`);
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items/${item.id}`;
+ const body = {
+ module_item: { title: item.title, position: item.position },
+ };
+ const { data } = await axiosClient.put(url, body);
+
+ if (!data) throw new Error("Something went wrong updating module item");
+ },
+
+ async createModuleItem(
+ canvasCourseId: number,
+ canvasModuleId: number,
+ title: string,
+ type: string,
+ contentId: number | string
+ ): Promise {
+ console.log(`Creating new module item ${title}`);
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
+ const body = { module_item: { title, type, content_id: contentId } };
+ await axiosClient.post(url, body);
+ },
+
+ async createPageModuleItem(
+ canvasCourseId: number,
+ canvasModuleId: number,
+ title: string,
+ canvasPage: CanvasPage
+ ): Promise {
+ console.log(`Creating new module item ${title}`);
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
+ const body = {
+ module_item: { title, type: "Page", page_url: canvasPage.url },
+ };
+ await axiosClient.post(url, body);
+ },
+
+ async getCourseModules(canvasCourseId: number) {
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules`;
+ const response = await axiosClient.get(url);
+ return response.data;
+ },
+
+ async createModule(canvasCourseId: number, moduleName: string) {
+ const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules`;
+ const body = {
+ module: {
+ name: moduleName,
+ },
+ };
+ await axiosClient.post(url, body);
+ },
+};
diff --git a/nextjs/src/services/canvas/canvasPageService.ts b/nextjs/src/services/canvas/canvasPageService.ts
index 9837c28..6fdadc1 100644
--- a/nextjs/src/services/canvas/canvasPageService.ts
+++ b/nextjs/src/services/canvas/canvasPageService.ts
@@ -1,11 +1,10 @@
import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
import { LocalCoursePage } from "@/models/local/page/localCoursePage";
-import { canvasServiceUtils } from "./canvasServiceUtils";
+import { baseCanvasUrl, canvasServiceUtils } from "./canvasServiceUtils";
import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
import { axiosClient } from "../axiosUtils";
import { rateLimitAwareDelete } from "./canvasWebRequestor";
-const baseCanvasUrl = "https://snow.instructure.com/api/v1";
export const canvasPageService = {
async getAll(courseId: number): Promise {
diff --git a/nextjs/src/services/canvas/canvasService.ts b/nextjs/src/services/canvas/canvasService.ts
index a42c4e0..18a0461 100644
--- a/nextjs/src/services/canvas/canvasService.ts
+++ b/nextjs/src/services/canvas/canvasService.ts
@@ -1,12 +1,11 @@
import { CanvasEnrollmentTermModel } from "@/models/canvas/enrollmentTerms/canvasEnrollmentTermModel";
-import { canvasServiceUtils } from "./canvasServiceUtils";
+import { baseCanvasUrl, canvasServiceUtils } from "./canvasServiceUtils";
import { CanvasCourseModel } from "@/models/canvas/courses/canvasCourseModel";
import { CanvasModuleItem } from "@/models/canvas/modules/canvasModuleItems";
import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
import { CanvasEnrollmentModel } from "@/models/canvas/enrollments/canvasEnrollmentModel";
import { axiosClient } from "../axiosUtils";
-const baseCanvasUrl = "https://snow.instructure.com/api/v1";
const getAllTerms = async () => {
const url = `${baseCanvasUrl}/accounts/10/terms`;
@@ -23,7 +22,7 @@ export const canvasService = {
getAllTerms,
async getCourses(termId: number) {
const url = `${baseCanvasUrl}/courses`;
- const response = await axiosClient.get(url);
+ const response = await axiosClient.get(url);
const allCourses = response.data;
const coursesInTerm = allCourses
.flatMap((l) => l)
@@ -57,47 +56,6 @@ export const canvasService = {
return currentTerms;
},
- async updateModuleItem(
- canvasCourseId: number,
- canvasModuleId: number,
- item: CanvasModuleItem
- ) {
- console.log(`Updating module item ${item.title}`);
- const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items/${item.id}`;
- const body = {
- module_item: { title: item.title, position: item.position },
- };
- const { data } = await axiosClient.put(url, body);
-
- if (!data) throw new Error("Something went wrong updating module item");
- },
-
- async createModuleItem(
- canvasCourseId: number,
- canvasModuleId: number,
- title: string,
- type: string,
- contentId: number | string
- ): Promise {
- console.log(`Creating new module item ${title}`);
- const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
- const body = { module_item: { title, type, content_id: contentId } };
- const response = await axiosClient.post(url, body);
- },
-
- async createPageModuleItem(
- canvasCourseId: number,
- canvasModuleId: number,
- title: string,
- canvasPage: CanvasPage
- ): Promise {
- console.log(`Creating new module item ${title}`);
- const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules/${canvasModuleId}/items`;
- const body = {
- module_item: { title, type: "Page", page_url: canvasPage.url },
- };
- await axiosClient.post(url, body);
- },
async getEnrolledStudents(canvasCourseId: number) {
console.log(`Getting students for course ${canvasCourseId}`);
diff --git a/nextjs/src/services/canvas/canvasServiceUtils.ts b/nextjs/src/services/canvas/canvasServiceUtils.ts
index acf8a27..360b971 100644
--- a/nextjs/src/services/canvas/canvasServiceUtils.ts
+++ b/nextjs/src/services/canvas/canvasServiceUtils.ts
@@ -3,6 +3,9 @@
import { AxiosResponseHeaders, RawAxiosResponseHeaders } from "axios";
import { axiosClient } from "../axiosUtils";
+
+export const baseCanvasUrl = "https://snow.instructure.com/api/v1";
+
const getNextUrl = (
headers: AxiosResponseHeaders | RawAxiosResponseHeaders
): string | undefined => {