mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 15:48:32 -06:00
client vs server render issues
This commit is contained in:
@@ -44,7 +44,7 @@ const proxyResponseHeaders = (response: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
req: NextRequest,
|
_req: NextRequest,
|
||||||
{ params }: { params: { rest: string[] } }
|
{ params }: { params: { rest: string[] } }
|
||||||
) {
|
) {
|
||||||
return withErrorHandling(async () => {
|
return withErrorHandling(async () => {
|
||||||
@@ -58,7 +58,12 @@ export async function GET(
|
|||||||
url.toString()
|
url.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
var returnData = firstData ? [firstData] : [];
|
if(!Array.isArray(firstData))
|
||||||
|
{
|
||||||
|
return NextResponse.json(firstData)
|
||||||
|
}
|
||||||
|
|
||||||
|
var returnData = firstData;
|
||||||
var nextUrl = getNextUrl(firstHeaders);
|
var nextUrl = getNextUrl(firstHeaders);
|
||||||
|
|
||||||
while (nextUrl) {
|
while (nextUrl) {
|
||||||
@@ -77,6 +82,7 @@ export async function GET(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json(returnData);
|
return NextResponse.json(returnData);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new NextResponse(
|
return new NextResponse(
|
||||||
JSON.stringify({ error: error.message || "Canvas GET request failed" }),
|
JSON.stringify({ error: error.message || "Canvas GET request failed" }),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"use client";
|
||||||
import {
|
import {
|
||||||
useAssignmentNamesQuery,
|
useAssignmentNamesQuery,
|
||||||
useAssignmentsQueries,
|
useAssignmentsQueries,
|
||||||
@@ -12,9 +13,15 @@ import {
|
|||||||
} from "@/hooks/localCourse/quizHooks";
|
} from "@/hooks/localCourse/quizHooks";
|
||||||
import { IModuleItem } from "@/models/local/IModuleItem";
|
import { IModuleItem } from "@/models/local/IModuleItem";
|
||||||
import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
|
import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
|
||||||
import { useState } from "react";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
import Modal from "../../../../components/Modal";
|
import Modal from "../../../../components/Modal";
|
||||||
import NewItemForm from "./NewItemForm";
|
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({
|
export default function ExpandableModule({
|
||||||
moduleName,
|
moduleName,
|
||||||
@@ -66,11 +73,14 @@ export default function ExpandableModule({
|
|||||||
return (
|
return (
|
||||||
<div className="bg-slate-800 rounded-lg p-3 border border-slate-600 mb-3">
|
<div className="bg-slate-800 rounded-lg p-3 border border-slate-600 mb-3">
|
||||||
<div
|
<div
|
||||||
className="font-bold "
|
className="font-bold flex flex-row justify-between "
|
||||||
role="button"
|
role="button"
|
||||||
onClick={() => setExpanded((e) => !e)}
|
onClick={() => setExpanded((e) => !e)}
|
||||||
>
|
>
|
||||||
{moduleName}
|
<div>{moduleName}</div>
|
||||||
|
<ClientOnly>
|
||||||
|
<ModuleCanvasStatus moduleName={moduleName} />
|
||||||
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"use client";
|
||||||
|
import { Spinner } from "@/components/Spinner";
|
||||||
|
import {
|
||||||
|
useAddCanvasModuleMutation,
|
||||||
|
useCanvasModulesQuery,
|
||||||
|
} from "@/hooks/canvas/canvasModuleHooks";
|
||||||
|
|
||||||
|
export function ModuleCanvasStatus({ moduleName }: { moduleName: string }) {
|
||||||
|
const { data: canvasModules } = useCanvasModulesQuery();
|
||||||
|
const addToCanvas = useAddCanvasModuleMutation();
|
||||||
|
|
||||||
|
const canvasModule = canvasModules.find((c) => c.name === moduleName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-slate-400 text-end">
|
||||||
|
{!canvasModule && <div>Not in Canvas</div>}
|
||||||
|
{!canvasModule && (
|
||||||
|
<button
|
||||||
|
disabled={addToCanvas.isPending}
|
||||||
|
onClick={() => addToCanvas.mutate(moduleName)}
|
||||||
|
>
|
||||||
|
{addToCanvas.isPending ? <Spinner /> : <div>Add</div>}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{canvasModule && !canvasModule.published && <div>Not Published</div>}
|
||||||
|
{canvasModule && canvasModule.published && <div>Published</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
|
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
|
||||||
import NewCourseForm from "./NewCourseForm";
|
import NewCourseForm from "./NewCourseForm";
|
||||||
|
import ClientOnly from "@/components/ClientOnly";
|
||||||
|
|
||||||
export default function AddNewCourse() {
|
export default function AddNewCourse() {
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
@@ -13,7 +14,7 @@ export default function AddNewCourse() {
|
|||||||
<div className={" collapsible " + (showForm && "expand")}>
|
<div className={" collapsible " + (showForm && "expand")}>
|
||||||
<div className="border rounded-md p-3 m-3">
|
<div className="border rounded-md p-3 m-3">
|
||||||
<SuspenseAndErrorHandling>
|
<SuspenseAndErrorHandling>
|
||||||
{showForm && <NewCourseForm />}
|
<ClientOnly>{showForm && <NewCourseForm />}</ClientOnly>
|
||||||
</SuspenseAndErrorHandling>
|
</SuspenseAndErrorHandling>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ export default async function Home() {
|
|||||||
<CourseList />
|
<CourseList />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
<AddNewCourse />
|
||||||
<AddNewCourse />
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
13
nextjs/src/components/ClientOnly.tsx
Normal file
13
nextjs/src/components/ClientOnly.tsx
Normal file
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { canvasService } from "@/services/canvas/canvasService";
|
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 = {
|
export const canvasKeys = {
|
||||||
allTerms: ["all canvas terms"] as const,
|
allTerms: ["all canvas terms"] as const,
|
||||||
@@ -34,4 +35,4 @@ export const useCanvasTermsQuery = (queryDate: Date) => {
|
|||||||
return currentTerms;
|
return currentTerms;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
34
nextjs/src/hooks/canvas/canvasModuleHooks.ts
Normal file
34
nextjs/src/hooks/canvas/canvasModuleHooks.ts
Normal file
@@ -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),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import { canvasServiceUtils } from "./canvasServiceUtils";
|
import { baseCanvasUrl, canvasServiceUtils } from "./canvasServiceUtils";
|
||||||
import { axiosClient } from "../axiosUtils";
|
import { axiosClient } from "../axiosUtils";
|
||||||
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
|
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
|
||||||
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
|
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
|
||||||
import { rateLimitAwareDelete } from "./canvasWebRequestor";
|
import { rateLimitAwareDelete } from "./canvasWebRequestor";
|
||||||
|
|
||||||
const baseCanvasUrl = "https://snow.instructure.com/api/v1";
|
|
||||||
|
|
||||||
export const canvasAssignmentGroupService = {
|
export const canvasAssignmentGroupService = {
|
||||||
async getAll(courseId: number): Promise<CanvasAssignmentGroup[]> {
|
async getAll(courseId: number): Promise<CanvasAssignmentGroup[]> {
|
||||||
|
|||||||
65
nextjs/src/services/canvas/canvasModuleService.ts
Normal file
65
nextjs/src/services/canvas/canvasModuleService.ts
Normal file
@@ -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<CanvasModuleItem>(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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<CanvasModuleItem>(url, body);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCourseModules(canvasCourseId: number) {
|
||||||
|
const url = `${baseCanvasUrl}/courses/${canvasCourseId}/modules`;
|
||||||
|
const response = await axiosClient.get<CanvasModule[]>(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);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
|
import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
|
||||||
import { LocalCoursePage } from "@/models/local/page/localCoursePage";
|
import { LocalCoursePage } from "@/models/local/page/localCoursePage";
|
||||||
import { canvasServiceUtils } from "./canvasServiceUtils";
|
import { baseCanvasUrl, canvasServiceUtils } from "./canvasServiceUtils";
|
||||||
import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
|
import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
|
||||||
import { axiosClient } from "../axiosUtils";
|
import { axiosClient } from "../axiosUtils";
|
||||||
import { rateLimitAwareDelete } from "./canvasWebRequestor";
|
import { rateLimitAwareDelete } from "./canvasWebRequestor";
|
||||||
|
|
||||||
const baseCanvasUrl = "https://snow.instructure.com/api/v1";
|
|
||||||
|
|
||||||
export const canvasPageService = {
|
export const canvasPageService = {
|
||||||
async getAll(courseId: number): Promise<CanvasPage[]> {
|
async getAll(courseId: number): Promise<CanvasPage[]> {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { CanvasEnrollmentTermModel } from "@/models/canvas/enrollmentTerms/canvasEnrollmentTermModel";
|
import { CanvasEnrollmentTermModel } from "@/models/canvas/enrollmentTerms/canvasEnrollmentTermModel";
|
||||||
import { canvasServiceUtils } from "./canvasServiceUtils";
|
import { baseCanvasUrl, canvasServiceUtils } from "./canvasServiceUtils";
|
||||||
import { CanvasCourseModel } from "@/models/canvas/courses/canvasCourseModel";
|
import { CanvasCourseModel } from "@/models/canvas/courses/canvasCourseModel";
|
||||||
import { CanvasModuleItem } from "@/models/canvas/modules/canvasModuleItems";
|
import { CanvasModuleItem } from "@/models/canvas/modules/canvasModuleItems";
|
||||||
import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
|
import { CanvasPage } from "@/models/canvas/pages/canvasPageModel";
|
||||||
import { CanvasEnrollmentModel } from "@/models/canvas/enrollments/canvasEnrollmentModel";
|
import { CanvasEnrollmentModel } from "@/models/canvas/enrollments/canvasEnrollmentModel";
|
||||||
import { axiosClient } from "../axiosUtils";
|
import { axiosClient } from "../axiosUtils";
|
||||||
|
|
||||||
const baseCanvasUrl = "https://snow.instructure.com/api/v1";
|
|
||||||
|
|
||||||
const getAllTerms = async () => {
|
const getAllTerms = async () => {
|
||||||
const url = `${baseCanvasUrl}/accounts/10/terms`;
|
const url = `${baseCanvasUrl}/accounts/10/terms`;
|
||||||
@@ -23,7 +22,7 @@ export const canvasService = {
|
|||||||
getAllTerms,
|
getAllTerms,
|
||||||
async getCourses(termId: number) {
|
async getCourses(termId: number) {
|
||||||
const url = `${baseCanvasUrl}/courses`;
|
const url = `${baseCanvasUrl}/courses`;
|
||||||
const response = await axiosClient.get<CanvasCourseModel[][]>(url);
|
const response = await axiosClient.get<CanvasCourseModel[]>(url);
|
||||||
const allCourses = response.data;
|
const allCourses = response.data;
|
||||||
const coursesInTerm = allCourses
|
const coursesInTerm = allCourses
|
||||||
.flatMap((l) => l)
|
.flatMap((l) => l)
|
||||||
@@ -57,47 +56,6 @@ export const canvasService = {
|
|||||||
return currentTerms;
|
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<CanvasModuleItem>(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<void> {
|
|
||||||
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<void> {
|
|
||||||
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<CanvasModuleItem>(url, body);
|
|
||||||
},
|
|
||||||
|
|
||||||
async getEnrolledStudents(canvasCourseId: number) {
|
async getEnrolledStudents(canvasCourseId: number) {
|
||||||
console.log(`Getting students for course ${canvasCourseId}`);
|
console.log(`Getting students for course ${canvasCourseId}`);
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
import { AxiosResponseHeaders, RawAxiosResponseHeaders } from "axios";
|
import { AxiosResponseHeaders, RawAxiosResponseHeaders } from "axios";
|
||||||
import { axiosClient } from "../axiosUtils";
|
import { axiosClient } from "../axiosUtils";
|
||||||
|
|
||||||
|
|
||||||
|
export const baseCanvasUrl = "https://snow.instructure.com/api/v1";
|
||||||
|
|
||||||
const getNextUrl = (
|
const getNextUrl = (
|
||||||
headers: AxiosResponseHeaders | RawAxiosResponseHeaders
|
headers: AxiosResponseHeaders | RawAxiosResponseHeaders
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
|
|||||||
Reference in New Issue
Block a user