diff --git a/nextjs/package.json b/nextjs/package.json
index 352683f..e83d66f 100644
--- a/nextjs/package.json
+++ b/nextjs/package.json
@@ -10,20 +10,14 @@
"test": "vitest"
},
"dependencies": {
+ "jsdom": "^25.0.0",
"next": "^14.2.7",
"react": "^18",
- "jsdom": "^25.0.0",
"react-dom": "^18"
},
"devDependencies": {
- "yaml": "^2.5.0",
- "axios": "^1.7.5",
- "marked": "^14.1.2",
"@monaco-editor/react": "^4.6.0",
"@tanstack/react-query": "^5.54.1",
- "isomorphic-dompurify": "^2.15.0",
- "react-error-boundary": "^4.0.13",
- "react-hot-toast": "^2.4.1",
"@tanstack/react-query-devtools": "^5.54.1",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.0.0",
@@ -31,10 +25,16 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"@vitejs/plugin-react": "^4.3.1",
+ "axios": "^1.7.5",
"eslint-config-next": "^14.2.7",
+ "isomorphic-dompurify": "^2.15.0",
+ "marked": "^14.1.2",
"postcss": "^8",
+ "react-error-boundary": "^4.0.13",
+ "react-hot-toast": "^2.4.1",
"tailwindcss": "^3.4.1",
"typescript": "^5",
- "vitest": "^2.0.5"
+ "vitest": "^2.0.5",
+ "yaml": "^2.5.0"
}
}
diff --git a/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx b/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx
index 1bcb4ec..f738b1d 100644
--- a/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx
+++ b/nextjs/src/app/course/[courseName]/modules/CreateModule.tsx
@@ -1,17 +1,20 @@
+import { Expandable } from "@/components/Expandable";
import TextInput from "@/components/form/TextInput";
import { useCreateModuleMutation } from "@/hooks/localCourse/localCourseModuleHooks";
import React, { useState } from "react";
export default function CreateModule() {
const createModule = useCreateModuleMutation();
- const [showForm, setShowForm] = useState(false);
const [moduleName, setModuleName] = useState("");
return (
<>
-
-
+ (
+
+ )}
+ >
-
+
>
);
}
diff --git a/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx b/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx
index 2ec9321..f7b7c15 100644
--- a/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx
+++ b/nextjs/src/app/course/[courseName]/modules/ModuleList.tsx
@@ -7,10 +7,12 @@ export default function ModuleList() {
const { data: moduleNames } = useModuleNamesQuery();
return (
-
{moduleNames.map((m) => (
))}
+
+
+
diff --git a/nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx b/nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx
new file mode 100644
index 0000000..6219f6b
--- /dev/null
+++ b/nextjs/src/app/course/[courseName]/settings/HolidayConfig.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import TextInput from "@/components/form/TextInput";
+import {
+ useLocalCourseSettingsQuery,
+ useUpdateLocalCourseSettingsMutation,
+} from "@/hooks/localCourse/localCoursesHooks";
+import {
+ dateToMarkdownString,
+ getDateFromStringOrThrow,
+} from "@/models/local/timeUtils";
+import { useState } from "react";
+
+const exampleString = `springBreak:
+- 10/12/2024
+- 10/13/2024
+- 10/14/2024
+laborDay:
+- 9/1/2024`;
+
+export default function HolidayConfig() {
+ const { data: settings } = useLocalCourseSettingsQuery();
+ const updateSettings = useUpdateLocalCourseSettingsMutation();
+
+ const [rawText, setRawText] = useState("");
+
+ const parsedText = parseHolidays(rawText);
+
+ return (
+
+
+
+ Format your holidays like so:
+
+ {exampleString}
+
+
+
+ {Object.keys(parsedText).map((k) => (
+
+
{k}
+
+ {parsedText[k].map((day) => {
+ const parsedDate = getDateFromStringOrThrow(
+ day,
+ "holiday preview display"
+ );
+ return
{dateToMarkdownString(parsedDate)}
;
+ })}
+
+
+ ))}
+
+
+ );
+}
+
+const parseHolidays = (
+ inputText: string
+): { [holidayName: string]: string[] } => {
+ const holidays: { [holidayName: string]: string[] } = {};
+
+ const lines = inputText.split("\n").filter(line => line.trim() !== "");
+ let currentHoliday: string | null = null;
+
+ lines.forEach(line => {
+ if (line.includes(":")) {
+ // It's a holiday name
+ const holidayName = line.split(":")[0].trim();
+ currentHoliday = holidayName;
+ holidays[currentHoliday] = [];
+ } else if (currentHoliday && line.startsWith("-")) {
+ // It's a date under the current holiday
+ const date = line.replace("-", "").trim();
+ holidays[currentHoliday].push(date);
+ }
+ });
+
+ return holidays;
+};
diff --git a/nextjs/src/app/course/[courseName]/settings/page.tsx b/nextjs/src/app/course/[courseName]/settings/page.tsx
index a0c9f63..152cac3 100644
--- a/nextjs/src/app/course/[courseName]/settings/page.tsx
+++ b/nextjs/src/app/course/[courseName]/settings/page.tsx
@@ -7,6 +7,7 @@ import DaysOfWeekSettings from "./DaysOfWeekSettings";
import AssignmentGroupManagement from "./AssignmentGroupManagement";
import SubmissionDefaults from "./SubmissionDefaults";
import DefaultFileUploadTypes from "./DefaultFileUploadTypes";
+import HolidayConfig from "./HolidayConfig";
export default function page() {
return (
@@ -19,6 +20,7 @@ export default function page() {
+
diff --git a/nextjs/src/components/form/TextInput.tsx b/nextjs/src/components/form/TextInput.tsx
index ad471bd..d961279 100644
--- a/nextjs/src/components/form/TextInput.tsx
+++ b/nextjs/src/components/form/TextInput.tsx
@@ -5,21 +5,32 @@ export default function TextInput({
setValue,
label,
className,
+ isTextArea = false,
}: {
value: string;
setValue: (newValue: string) => void;
label: string;
className?: string;
+ isTextArea?: boolean;
}) {
return (
-