diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..ab1f4164
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..03d9549e
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..6adae737
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/opptaksside.iml b/.idea/opptaksside.iml
new file mode 100644
index 00000000..c956989b
--- /dev/null
+++ b/.idea/opptaksside.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SESSION_NOTES.md b/SESSION_NOTES.md
new file mode 100644
index 00000000..22227ee3
--- /dev/null
+++ b/SESSION_NOTES.md
@@ -0,0 +1,186 @@
+# Date Standardization — Session Notes
+
+**Branch:** `refactor/standardize-date-objects`
+**Session:** 2026-04-27 — 2026-05-02
+**Commit:** `b0a3a7b` — _refactor: standardize date objects across project_
+**PR URL:** https://github.com/appKom/opptak/pull/new/refactor/standardize-date-objects
+
+> This file is a working memo of what happened in the session. It is **not** committed to git (kept locally only).
+
+---
+
+## 1. Context at session start
+
+The branch had a large in-progress refactor: period / applicant / committee date fields changed from `string` to `Date` end-to-end. Already in the working tree (uncommitted) before the session:
+
+- `lib/types/types.ts` — types flipped from `string` → `Date`.
+- `lib/utils/parseDates.ts` (new) — central client-side parsing helpers (`parsePeriodDates`, `parseApplicantDates`, `parseCommitteeDates`).
+- `lib/api/{applicantApi,committeesApi,periodApi}.ts` — fetchers run `parseDates` on JSON responses.
+- `pages/api/...` — POST/PUT handlers run `parseDates` on `req.body`.
+- `lib/utils/{validators,validateApplication,convertIsoToScheduleFormat,dateUtils}.ts` — adapted to handle Dates.
+- `pages/{apply,admin,committee,committees}` — direct Date comparisons replacing string-vs-Date hacks.
+- `lib/mongo/{periods,applicants}.ts` — mongo queries now pass Date objects.
+- `scripts/migrate-dates.js` — in-place migration script (string → BSON Date) for any DB the URI points to.
+
+---
+
+## 2. Pre-test analysis — bugs identified by reading the diff
+
+### Bug 1 — `validateCommittee` rejected parsed `Date` objects
+`lib/utils/validators.ts` (in `validateCommittee`):
+
+```ts
+data.availabletimes.every(
+ (time: { start: string; end: string }) =>
+ typeof time.start === "string" && typeof time.end === "string",
+);
+```
+
+Called *after* `parseCommitteeDates(req.body)` ran, so `start`/`end` were already `Date` objects → `typeof === "string"` was always `false` → 400 "Invalid data format" on every committee submission.
+
+**Fix:** check for `Date` instances instead.
+
+### Bug 2 — `isApplicantType(req.body, ...)` saw `date: undefined`
+`pages/api/applicants/index.ts`:
+
+```ts
+const requestBody = parseApplicantDates(req.body); // new object, not mutation
+requestBody.date = new Date();
+...
+if (!isApplicantType(req.body, period)) { // ← raw req.body, no date Date
+```
+
+Pre-refactor `requestBody = req.body` was an alias, so `requestBody.date = new Date()` mutated `req.body.date`. Post-refactor `parseApplicantDates` returns a fresh object via spread, so `req.body.date` stayed `undefined`. Validator's `date instanceof Date` check failed → 400.
+
+**Fix:** call `isApplicantType(requestBody, period)`.
+
+### Two pre-existing concerns logged, not regressions of this PR:
+- Two `periods` rows with corrupted year data (`Fredrik Hansteen` year 1, `Testeeeeeee` year 567).
+- React warning `
` inside `` in `SendOutInterviews.tsx`.
+- Image aspect-ratio warning on `Online_bla.svg`.
+
+---
+
+## 3. Bugs found later during testing
+
+### Bug 3 — `formatDateNorwegian` rendered double periods (`09.. mar`)
+`lib/utils/dateUtils.ts` — Norwegian locale's `toLocaleDateString({ day: "2-digit" })` returns `"09."` (with trailing period). Template `${day}. ${month}` then added another period → `"09.. mar"`.
+
+**Fix:** strip trailing period from `day`, mirroring what was already done for `month`:
+```ts
+const day = date.toLocaleDateString("nb-NO", { day: "2-digit", timeZone: "Europe/Oslo" })
+ .replace(".", "");
+```
+
+### Bug 4 — `Schedule.tsx getDatesWithinPeriod` lost the `+2h` shift
+The original had:
+```ts
+startDate.setHours(startDate.getHours() + 2);
+```
+This was a workaround so periods stored as midnight-Oslo (`2040-07-15T22:00:00Z` = `2040-07-16 00:00 Oslo`) rendered correctly in the schedule grid. The refactor dropped the shift, causing the schedule to render days *before* the actual period start. Slots clicked on those days fell **outside** the validator's interview window → 400.
+
+**Fix:** reinstate the shift on local copies (without mutating the input Date):
+```ts
+const startDate = new Date(
+ (periodTime.start instanceof Date ? periodTime.start : new Date(periodTime.start)).getTime(),
+);
+startDate.setHours(startDate.getHours() + 2);
+// same for endDate
+```
+
+---
+
+## 4. Migration strategy chosen
+
+**Decision:** rather than running `migrate-dates.js` in place against `development`, build a parallel migrated DB via aggregation `$out`. Keeps `development` untouched while we smoke-test.
+
+**Steps actually executed:**
+
+1. Installed `mongodb-database-tools` via Homebrew tap `mongodb/brew`.
+2. Took a backup with `mongodump`:
+ ```
+ ~/mongodb-backups/opptaksside-20260427-175203/
+ ```
+ (553 docs across 5 collections, ~1.1 MB)
+3. Wrote `scripts/migrate-to-new-db.js` — Node + `mongodb` driver, runs aggregation pipelines that read from `development` and `$out` to a new DB called `Date_dev`.
+4. Ran `node scripts/migrate-to-new-db.js` — copied:
+ - `periods` (50 docs) — `applicationPeriod.{start,end}` and `interviewPeriod.{start,end}` converted via `$toDate`.
+ - `applications` (184 docs) — `selectedTimes[].{start,end}` via `$map` + `$toDate`.
+ - `committees` (65 docs) — `availabletimes[].{start,end,room}` via `$map`.
+ - `interviews` (243 docs) — straight copy.
+ - `rooms` (11 docs) — straight copy.
+5. Updated `.env.local` `MONGODB_URI` to point at `/Date_dev`. Restarted dev server.
+
+**Strict rule established mid-session (saved to memory):** never write to the `development` MongoDB without **two ⚠️⚠️ confirmations** from the user. Read-only ops fine.
+
+---
+
+## 5. Smoke test results
+
+Executed step-by-step against `Date_dev`:
+
+| Step | What was tested | Result |
+|---|---|---|
+| 1 | App connects to `Date_dev`, `/apply` cards render, dates formatted as `09. mar` | ✅ (after Bug 3 fix) |
+| 2 | `/admin` table renders all 50 periods with `DD.MM.YYYY` | ✅ |
+| 3 | POST `/api/applicants` → `201`, doc has BSON Date for `selectedTimes[].{start,end}` and `date` | ✅ (after Bug 2 + Bug 4 fixes) |
+| 4 | POST `/api/committees/times/[periodId]` → `201`, doc has BSON Date for `availabletimes[].{start,end}` | ✅ (after Bug 1 fix) |
+
+Compass verification at the end confirmed both new docs in `Date_dev.applications` and `Date_dev.committees` had unquoted `Date(...)` values for the date fields (vs. quoted strings for `name`, `committee`, `room`, etc.).
+
+---
+
+## 6. Untested paths (acknowledged gaps)
+
+**High-value:**
+- POST `/api/periods` — creating a new period. Exercises `DatePickerInput` + `isPeriodType` + `parsePeriodDates`.
+- PUT `/api/applicants` — editing an existing application. Same fix applied as POST but only POST was runtime-verified.
+- Round-trip read-back of `selectedTimes` — does **Endre søknad** correctly pre-select previous slots in the schedule?
+
+**Important:**
+- `formatDateHours` in email/SMS templates (gated by `NODE_ENV=production`, deliberately skipped per testing rule — but means production interview-email rendering is unverified).
+- Hidden-applicant placeholder — `lib/mongo/applicants.ts:214` change from `selectedTimes = [{start:"Skjult", end:"Skjult"}]` to `[{start:new Date(0), end:new Date(0)}]` only fires when application period is open or 5+ days after interview end. Will display `01.01.1970` to committees in that window.
+
+**Lower priority:**
+- DST transition periods — `+2h` shift assumes constant Oslo offset; periods spanning the last Sunday of March/October may render off-by-one for one day.
+- `getCurrentPeriods` in `lib/mongo/periods.ts` — not clearly wired into any active route; worth a `grep` to confirm dead code or active.
+- `/committees` public listing, `Søkere` tab in committee view — read paths, lowest risk but unverified.
+
+---
+
+## 7. Final commit
+
+```
+b0a3a7b refactor: standardize date objects across project
+28 files changed, 683 insertions(+), 205 deletions(-)
++ lib/utils/parseDates.ts
++ scripts/migrate-dates.js
++ scripts/migrate-to-new-db.js
++ 25 modified
+```
+
+Pushed to `origin/refactor/standardize-date-objects`. PR not opened yet — open via the URL above.
+
+`.env.local` still points at `Date_dev` locally. To return to normal dev work against `development`, swap the path segment back. (`.env.local` is gitignored.)
+
+---
+
+## 8. Outstanding decisions for production
+
+Not started in this session — needs its own conversation:
+
+1. **Backup production** (`mongodump` against the prod URI) — required.
+2. **Choose migration pattern for prod:**
+ - `migrate-dates.js` — in-place, simpler, destructive (irreversible without restore).
+ - `migrate-to-new-db.js` — non-destructive, but requires a URI swap on prod environment + cleanup of the source DB later.
+3. **Run prod migration** — needs ⚠️⚠️ double-confirm.
+4. **Deploy** the merged branch.
+5. **Optional cleanup**: drop `Date_dev`; clean up the two corrupted-year periods.
+
+---
+
+## 9. Misc observations from the session
+
+- Repo URL has moved server-side from `appKom/opptaksside` to `appKom/opptak`. Push still works via redirect; future cleanup: `git remote set-url origin https://github.com/appKom/opptak.git`.
+- 50 dependabot vulnerabilities exist on the default branch (unrelated to this PR).
+- `.idea/` is currently untracked (not in `.gitignore`).
diff --git a/components/committee/CommitteeInterviewTimes.tsx b/components/committee/CommitteeInterviewTimes.tsx
index 88a98947..e05ceece 100644
--- a/components/committee/CommitteeInterviewTimes.tsx
+++ b/components/committee/CommitteeInterviewTimes.tsx
@@ -63,8 +63,8 @@ const CommitteeInterviewTimes = ({
useEffect(() => {
if (period) {
setVisibleRange({
- start: new Date(period!.interviewPeriod.start).toISOString(),
- end: new Date(period!.interviewPeriod.end).toISOString(),
+ start: period.interviewPeriod.start.toISOString(),
+ end: period.interviewPeriod.end.toISOString(),
});
}
}, [period]);
@@ -101,8 +101,8 @@ const CommitteeInterviewTimes = ({
(at: any) => ({
id: crypto.getRandomValues(new Uint32Array(1))[0].toString(),
title: at.room,
- start: new Date(at.start).toISOString(),
- end: new Date(at.end).toISOString(),
+ start: at.start instanceof Date ? at.start.toISOString() : at.start,
+ end: at.end instanceof Date ? at.end.toISOString() : at.end,
})
);
@@ -317,10 +317,9 @@ const CommitteeInterviewTimes = ({
}, [period]);
const getSubmissionDeadline = (): string => {
- const deadlineIso = period!.applicationPeriod.end;
+ const deadlineDate = period!.applicationPeriod.end;
- if (deadlineIso != null && !deadLineHasPassed) {
- const deadlineDate = new Date(deadlineIso);
+ if (deadlineDate != null && !deadLineHasPassed) {
const now = new Date();
if (now > deadlineDate) {
diff --git a/components/committee/Schedule.tsx b/components/committee/Schedule.tsx
index 0b9f4c43..668919fd 100644
--- a/components/committee/Schedule.tsx
+++ b/components/committee/Schedule.tsx
@@ -18,9 +18,9 @@ interface TimeSlot {
available: boolean;
}
-interface IsoTimeSlot {
- start: string;
- end: string;
+interface DateTimeSlot {
+ start: Date;
+ end: Date;
}
export default function Schedule({
@@ -37,14 +37,24 @@ export default function Schedule({
periodTime: any,
): { [date: string]: string } => {
if (!periodTime) return {};
- const startDate = new Date(periodTime.start);
+ const startDate = new Date(
+ (periodTime.start instanceof Date
+ ? periodTime.start
+ : new Date(periodTime.start)
+ ).getTime(),
+ );
startDate.setHours(startDate.getHours() + 2);
- const endDate = new Date(periodTime.end);
+ const endDate = new Date(
+ (periodTime.end instanceof Date
+ ? periodTime.end
+ : new Date(periodTime.end)
+ ).getTime(),
+ );
endDate.setHours(endDate.getHours() + 2);
const dates: { [date: string]: string } = {};
const dayNames = ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør"];
- let currentDate = new Date(startDate);
+ let currentDate = new Date(startDate.getTime());
while (currentDate <= endDate) {
const dayIndex = currentDate.getDay();
@@ -72,8 +82,8 @@ export default function Schedule({
return [hour, minute];
}, []);
- const convertToIso = useCallback(
- (date: string, timeSlot: string): IsoTimeSlot => {
+ const convertToDateSlot = useCallback(
+ (date: string, timeSlot: string): DateTimeSlot => {
const [startTimeStr, endTimeStr] = timeSlot.split(" - ");
const [year, month, day] = date.split("-").map(Number);
@@ -88,8 +98,8 @@ export default function Schedule({
);
return {
- start: startTime.toISOString(),
- end: endTime.toISOString(),
+ start: startTime,
+ end: endTime,
};
},
[parseTime],
@@ -118,13 +128,13 @@ export default function Schedule({
});
});
- const isoTimeSlotsForExport = allAvailableTimes.map((slot) =>
- convertToIso(slot.date, slot.time),
+ const dateTimeSlotsForExport = allAvailableTimes.map((slot) =>
+ convertToDateSlot(slot.date, slot.time),
);
setApplicationData((prevData: any) => ({
...prevData,
- selectedTimes: isoTimeSlotsForExport,
+ selectedTimes: dateTimeSlotsForExport,
}));
setIsInitialized(true);
}, [
@@ -132,7 +142,7 @@ export default function Schedule({
applicationData.selectedTimes,
periodTime,
timeSlots,
- convertToIso,
+ convertToDateSlot,
setApplicationData,
]);
@@ -185,12 +195,12 @@ export default function Schedule({
});
});
- const isoTimeSlotsForExport = dataToSend.map((slot) =>
- convertToIso(slot.date, slot.time),
+ const dateTimeSlotsForExport = dataToSend.map((slot) =>
+ convertToDateSlot(slot.date, slot.time),
);
// Queue the update instead of calling setApplicationData directly
- setPendingUpdate(isoTimeSlotsForExport);
+ setPendingUpdate(dateTimeSlotsForExport);
return newCells;
});
@@ -198,13 +208,17 @@ export default function Schedule({
const dates = getDatesWithinPeriod(periodTime);
- let weekDates: { [date: string]: IsoTimeSlot[] } = {};
+ let weekDates: { [date: string]: DateTimeSlot[] } = {};
Object.keys(dates).forEach((date) => {
weekDates[date] = [];
applicationData.selectedTimes?.forEach((slot) => {
- if (slot?.start?.includes(date) && slot?.end) {
- weekDates[date].push({ start: slot.start, end: slot.end });
+ if (slot?.start && slot?.end) {
+ const start = new Date(slot.start as unknown as string | number | Date);
+ const end = new Date(slot.end as unknown as string | number | Date);
+ if (start.toISOString().includes(date)) {
+ weekDates[date].push({ start, end });
+ }
}
});
});
diff --git a/components/committee/ScheduleColumn.tsx b/components/committee/ScheduleColumn.tsx
index 3025ff05..5348e819 100644
--- a/components/committee/ScheduleColumn.tsx
+++ b/components/committee/ScheduleColumn.tsx
@@ -7,7 +7,7 @@ interface Props {
date: string;
weekDay: string;
interviewLength: number;
- availableSlots: { start: string; end: string }[];
+ availableSlots: { start: Date; end: Date }[];
onToggleAvailability: (
date: string,
time: string,
@@ -27,13 +27,7 @@ export default function ScheduleColumn({
const dateOfMonth = date.split("-")[2];
const month = date.split("-")[1];
- const adjustedAvailableSlots = availableSlots.map((slot) => {
- slot.start = slot.start.replace("Z", "");
- slot.end = slot.end.replace("Z", "");
- return slot;
- });
-
- const availableTimeSlots = convertIsoToScheduleFormat(adjustedAvailableSlots);
+ const availableTimeSlots = convertIsoToScheduleFormat(availableSlots);
const availableTimes = availableTimeSlots.map((slot) => {
let [firstTime, secondTime] = slot.time.split(" - ").map((s) => s.trim());
return `${firstTime} - ${secondTime}`;
diff --git a/components/form/DatePickerInput.tsx b/components/form/DatePickerInput.tsx
index 6ef4474c..a6adc6f7 100644
--- a/components/form/DatePickerInput.tsx
+++ b/components/form/DatePickerInput.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
interface Props {
label?: string;
- updateDates: (dates: { start: string; end: string }) => void;
+ updateDates: (dates: { start: Date | null; end: Date | null }) => void;
}
const DatePickerInput = (props: Props) => {
@@ -9,9 +9,9 @@ const DatePickerInput = (props: Props) => {
const [toDate, setToDate] = useState("");
useEffect(() => {
- const startDate = fromDate ? `${fromDate}T00:00` : "";
- const endDate = toDate ? `${toDate}T23:59` : "";
- props.updateDates({ start: startDate, end: endDate });
+ const start = fromDate ? new Date(`${fromDate}T00:00`) : null;
+ const end = toDate ? new Date(`${toDate}T23:59`) : null;
+ props.updateDates({ start, end });
}, [fromDate, toDate]);
return (
diff --git a/lib/api/applicantApi.ts b/lib/api/applicantApi.ts
index a8166e6e..5df8620e 100644
--- a/lib/api/applicantApi.ts
+++ b/lib/api/applicantApi.ts
@@ -1,21 +1,34 @@
import { QueryFunctionContext } from "@tanstack/react-query";
import { applicantType } from "../types/types";
+import { parseApplicantDates } from "../utils/parseDates";
export const fetchApplicantByPeriodAndId = async (
context: QueryFunctionContext
) => {
const periodId = context.queryKey[1];
const applicantId = context.queryKey[2];
- return fetch(`/api/applicants/${periodId}/${applicantId}`).then((res) =>
- res.json()
+ const data = await fetch(`/api/applicants/${periodId}/${applicantId}`).then(
+ (res) => res.json()
);
+ return {
+ ...data,
+ application: data.application
+ ? parseApplicantDates(data.application)
+ : undefined,
+ };
};
export const fetchApplicantsByPeriodId = async (
context: QueryFunctionContext
) => {
const periodId = context.queryKey[1];
- return fetch(`/api/applicants/${periodId}`).then((res) => res.json());
+ const data = await fetch(`/api/applicants/${periodId}`).then((res) =>
+ res.json()
+ );
+ return {
+ ...data,
+ applications: data.applications?.map(parseApplicantDates),
+ };
};
export const fetchApplicantsByPeriodIdAndCommittee = async (
@@ -23,9 +36,13 @@ export const fetchApplicantsByPeriodIdAndCommittee = async (
) => {
const periodId = context.queryKey[1];
const committee = context.queryKey[2];
- return fetch(`/api/committees/applicants/${periodId}/${committee}`).then(
- (res) => res.json()
- );
+ const data = await fetch(
+ `/api/committees/applicants/${periodId}/${committee}`
+ ).then((res) => res.json());
+ return {
+ ...data,
+ applicants: data.applicants?.map(parseApplicantDates),
+ };
};
export const createApplicant = async (applicant: applicantType) => {
diff --git a/lib/api/committeesApi.ts b/lib/api/committeesApi.ts
index a4fbe4c5..560cf0b4 100644
--- a/lib/api/committeesApi.ts
+++ b/lib/api/committeesApi.ts
@@ -1,5 +1,6 @@
import { QueryFunctionContext } from "@tanstack/react-query";
import { OwGroup } from "../types/types";
+import { parseCommitteeDates } from "../utils/parseDates";
export const fetchOwCommittees = async (): Promise => {
return fetch(`/api/periods/ow-committees`).then((res) => res.json());
@@ -9,7 +10,11 @@ export const fetchCommitteeTimes = async (context: QueryFunctionContext) => {
const periodId = context.queryKey[1];
const committee = context.queryKey[2];
- return fetch(`/api/committees/times/${periodId}/${committee}`).then((res) =>
- res.json(),
- );
+ const data = await fetch(
+ `/api/committees/times/${periodId}/${committee}`,
+ ).then((res) => res.json());
+ return {
+ ...data,
+ committees: data.committees?.map(parseCommitteeDates),
+ };
};
diff --git a/lib/api/periodApi.ts b/lib/api/periodApi.ts
index a5662f53..62b15bdf 100644
--- a/lib/api/periodApi.ts
+++ b/lib/api/periodApi.ts
@@ -1,13 +1,22 @@
import { QueryFunctionContext } from "@tanstack/react-query";
import { periodType } from "../types/types";
+import { parsePeriodDates } from "../utils/parseDates";
export const fetchPeriodById = async (context: QueryFunctionContext) => {
const id = context.queryKey[1];
- return fetch(`/api/periods/${id}`).then((res) => res.json());
+ const data = await fetch(`/api/periods/${id}`).then((res) => res.json());
+ return {
+ ...data,
+ period: data.period ? parsePeriodDates(data.period) : undefined,
+ };
};
export const fetchPeriods = async () => {
- return fetch(`/api/periods`).then((res) => res.json());
+ const data = await fetch(`/api/periods`).then((res) => res.json());
+ return {
+ ...data,
+ periods: data.periods?.map(parsePeriodDates),
+ };
};
export const deletePeriodById = async (id: string) => {
diff --git a/lib/mongo/applicants.ts b/lib/mongo/applicants.ts
index f76814ee..44427d49 100644
--- a/lib/mongo/applicants.ts
+++ b/lib/mongo/applicants.ts
@@ -193,16 +193,16 @@ export const getApplicantsForCommittee = async (
userCommittees.includes(preference),
);
- // Skjuler søkerinformasjon for komitéen etter syv dager etter intervjuperioden
+ // Skjuler søkerinformasjon for komitéen etter fem dager etter intervjuperioden
const today = new Date();
- const sevenDaysAfterInterviewEnd = new Date(period.interviewPeriod.end);
- sevenDaysAfterInterviewEnd.setDate(
- sevenDaysAfterInterviewEnd.getDate() + 5,
+ const fiveDaysAfterInterviewEnd = new Date(period.interviewPeriod.end.getTime());
+ fiveDaysAfterInterviewEnd.setDate(
+ fiveDaysAfterInterviewEnd.getDate() + 5,
);
if (
- new Date(period.applicationPeriod.end) > today ||
- today > sevenDaysAfterInterviewEnd
+ period.applicationPeriod.end > today ||
+ today > fiveDaysAfterInterviewEnd
) {
applicant.owId = "Skjult";
applicant.name = "Skjult";
@@ -211,7 +211,7 @@ export const getApplicantsForCommittee = async (
applicant.email = "Skjult";
applicant.about = "Skjult";
applicant.grade = "-";
- applicant.selectedTimes = [{ start: "Skjult", end: "Skjult" }];
+ applicant.selectedTimes = [{ start: new Date(0), end: new Date(0) }];
}
const isSelectedCommitteePresent =
diff --git a/lib/mongo/periods.ts b/lib/mongo/periods.ts
index dc651a61..36b9d7bd 100644
--- a/lib/mongo/periods.ts
+++ b/lib/mongo/periods.ts
@@ -59,17 +59,15 @@ export const getCurrentPeriods = async () => {
try {
if (!periods) await init();
- const currentDate = new Date().toISOString();
+ const currentDate = new Date();
const filter = {
$or: [
{
- // Check if current ISO date string is within the application period
"applicationPeriod.start": { $lte: currentDate },
"applicationPeriod.end": { $gte: currentDate },
},
{
- // Check if current ISO date string is within the interview period
"interviewPeriod.start": { $lte: currentDate },
"interviewPeriod.end": { $gte: currentDate },
},
diff --git a/lib/types/types.ts b/lib/types/types.ts
index 11ef3693..b8637f2c 100644
--- a/lib/types/types.ts
+++ b/lib/types/types.ts
@@ -23,8 +23,8 @@ export type applicantType = {
optionalCommittees: string[];
selectedTimes: [
{
- start: string;
- end: string;
+ start: Date;
+ end: Date;
},
];
date: Date;
@@ -67,8 +67,8 @@ export type periodType = {
};
export type AvailableTime = {
- start: string;
- end: string;
+ start: Date;
+ end: Date;
room: string;
};
diff --git a/lib/utils/convertIsoToScheduleFormat.ts b/lib/utils/convertIsoToScheduleFormat.ts
index c19cd532..f8468aa1 100644
--- a/lib/utils/convertIsoToScheduleFormat.ts
+++ b/lib/utils/convertIsoToScheduleFormat.ts
@@ -1,6 +1,6 @@
-interface IsoTimeSlot {
- start: string;
- end: string;
+interface TimeSlot {
+ start: Date;
+ end: Date;
}
interface ScheduleTimeSlot {
@@ -9,24 +9,24 @@ interface ScheduleTimeSlot {
}
export const convertIsoToScheduleFormat = (
- isoTimeSlots: IsoTimeSlot[],
+ timeSlots: TimeSlot[],
): ScheduleTimeSlot[] => {
- return isoTimeSlots.map((slot) => {
- const startDate = new Date(slot.start);
- const endDate = new Date(slot.end);
+ return timeSlots.map((slot) => {
+ const startDate = slot.start;
+ const endDate = slot.end;
// Convert date to YYYY-MM-DD format
const date = startDate.toISOString().split("T")[0];
// Convert times to HH:MM format
- const startTime = startDate.toLocaleTimeString([], {
+ const timeFormat: Intl.DateTimeFormatOptions = {
hour: "2-digit",
minute: "2-digit",
- });
- const endTime = endDate.toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- });
+ hour12: false,
+ timeZone: "Europe/Oslo",
+ };
+ const startTime = startDate.toLocaleTimeString("nb-NO", timeFormat);
+ const endTime = endDate.toLocaleTimeString("nb-NO", timeFormat);
const time = `${startTime} - ${endTime}`;
diff --git a/lib/utils/dateUtils.ts b/lib/utils/dateUtils.ts
index 8ab5de14..198c9cce 100644
--- a/lib/utils/dateUtils.ts
+++ b/lib/utils/dateUtils.ts
@@ -1,55 +1,54 @@
export const formatDate = (inputDate: undefined | Date) => {
- const date = new Date(inputDate || "");
+ if (!inputDate) return "";
- const day = date.getDate().toString().padStart(2, "0");
- const month = (date.getMonth() + 1).toString().padStart(2, "0");
- const year = date.getFullYear();
- const hours = date.getHours().toString().padStart(2, "0");
- const minutes = date.getMinutes().toString().padStart(2, "0");
+ const date = inputDate instanceof Date ? inputDate : new Date(inputDate);
- return `${day}.${month}.${year}`; // - ${hours}:${minutes}
+ return date.toLocaleDateString("nb-NO", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ timeZone: "Europe/Oslo",
+ });
};
export const formatDateHours = (
- start: string,
- end: string
+ start: string | Date,
+ end: string | Date,
) => {
- const startDate = new Date(Date.parse(start));
+ const startDate = start instanceof Date ? start : new Date(start);
+ const endDate = end instanceof Date ? end : new Date(end);
+
+ const timeFormat: Intl.DateTimeFormatOptions = {
+ hour: "2-digit",
+ minute: "2-digit",
+ hour12: false,
+ timeZone: "Europe/Oslo",
+ };
- const startTime = start.split("T")[1].slice(0, 5);
- const endTime = end.split("T")[1].slice(0, 5);
+ const startTime = startDate.toLocaleTimeString("nb-NO", timeFormat);
+ const endTime = endDate.toLocaleTimeString("nb-NO", timeFormat);
- return `${formatDateNorwegian(
- startDate
- )}, ${startTime} til ${endTime}`;
+ return `${formatDateNorwegian(startDate)}, ${startTime} til ${endTime}`;
};
export const formatDateNorwegian = (inputDate?: Date | string) => {
if (!inputDate) return "";
- let date: Date;
- if (inputDate instanceof Date) {
- date = inputDate;
- } else {
- date = new Date(inputDate);
- }
-
- const day = date.getUTCDate().toString().padStart(2, "0");
- const monthsNorwegian = [
- "jan",
- "feb",
- "mar",
- "apr",
- "mai",
- "jun",
- "jul",
- "aug",
- "sep",
- "okt",
- "nov",
- "des",
- ];
- const month = monthsNorwegian[date.getUTCMonth()];
+ const date = inputDate instanceof Date ? inputDate : new Date(inputDate);
+
+ const day = date
+ .toLocaleDateString("nb-NO", {
+ day: "2-digit",
+ timeZone: "Europe/Oslo",
+ })
+ .replace(".", "");
+
+ const month = date
+ .toLocaleDateString("nb-NO", {
+ month: "short",
+ timeZone: "Europe/Oslo",
+ })
+ .replace(".", "");
return `${day}. ${month}`;
};
diff --git a/lib/utils/parseDates.ts b/lib/utils/parseDates.ts
new file mode 100644
index 00000000..445baabd
--- /dev/null
+++ b/lib/utils/parseDates.ts
@@ -0,0 +1,58 @@
+import {
+ applicantType,
+ committeeInterviewType,
+ periodType,
+} from "../types/types";
+
+/**
+ * Parses date strings in a period object to Date objects.
+ * Used both server-side (after JSON.parse of req.body) and
+ * client-side (after fetch .json() response).
+ */
+export function parsePeriodDates(raw: any): periodType {
+ return {
+ ...raw,
+ applicationPeriod: {
+ start: new Date(raw.applicationPeriod.start),
+ end: new Date(raw.applicationPeriod.end),
+ },
+ interviewPeriod: {
+ start: new Date(raw.interviewPeriod.start),
+ end: new Date(raw.interviewPeriod.end),
+ },
+ };
+}
+
+/**
+ * Parses date strings in an applicant object to Date objects.
+ */
+export function parseApplicantDates(raw: any): applicantType {
+ return {
+ ...raw,
+ date: raw.date ? new Date(raw.date) : new Date(),
+ selectedTimes: Array.isArray(raw.selectedTimes)
+ ? raw.selectedTimes.map((t: any) => ({
+ start: new Date(t.start),
+ end: new Date(t.end),
+ }))
+ : [],
+ };
+}
+
+/**
+ * Parses date strings in a committee interview type to Date objects.
+ */
+export function parseCommitteeDates(
+ raw: any,
+): committeeInterviewType {
+ return {
+ ...raw,
+ availabletimes: Array.isArray(raw.availabletimes)
+ ? raw.availabletimes.map((t: any) => ({
+ start: new Date(t.start),
+ end: new Date(t.end),
+ room: t.room,
+ }))
+ : [],
+ };
+}
diff --git a/lib/utils/validateApplication.ts b/lib/utils/validateApplication.ts
index eada7a89..7d2b52d0 100644
--- a/lib/utils/validateApplication.ts
+++ b/lib/utils/validateApplication.ts
@@ -71,8 +71,8 @@ export const validateApplication = (applicationData: any) => {
}
for (const time of selectedTimes) {
- const startTime = new Date(time.start);
- const endTime = new Date(time.end);
+ const startTime = time.start instanceof Date ? time.start : new Date(time.start);
+ const endTime = time.end instanceof Date ? time.end : new Date(time.end);
if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) {
toast.error("Ugyldig start- eller sluttid");
return false;
diff --git a/lib/utils/validators.ts b/lib/utils/validators.ts
index 390faa92..fe6fd7ae 100644
--- a/lib/utils/validators.ts
+++ b/lib/utils/validators.ts
@@ -70,14 +70,19 @@ export const isApplicantType = (
const hasSelectedTimes =
Array.isArray(applicant.selectedTimes) &&
applicant.selectedTimes.every(
- (time: { start: string; end: string }) =>
- typeof time.start === "string" &&
- typeof time.end === "string" &&
- new Date(time.start) >= interviewPeriodStart &&
- new Date(time.start) <= interviewPeriodEnd &&
- new Date(time.end) <= interviewPeriodEnd &&
- new Date(time.end) >= interviewPeriodStart &&
- new Date(time.start) < new Date(time.end),
+ (time: { start: Date | string; end: Date | string }) => {
+ const start = new Date(time.start);
+ const end = new Date(time.end);
+ return (
+ !isNaN(start.getTime()) &&
+ !isNaN(end.getTime()) &&
+ start >= interviewPeriodStart &&
+ start <= interviewPeriodEnd &&
+ end <= interviewPeriodEnd &&
+ end >= interviewPeriodStart &&
+ start < end
+ );
+ },
);
const periodOptionalCommittees = period.optionalCommittees.map((committee) =>
@@ -122,8 +127,8 @@ export const validateCommittee = (data: any, period: periodType): boolean => {
typeof data.timeslot === "string" &&
Array.isArray(data.availabletimes) &&
data.availabletimes.every(
- (time: { start: string; end: string }) =>
- typeof time.start === "string" && typeof time.end === "string",
+ (time: { start: Date; end: Date }) =>
+ time.start instanceof Date && time.end instanceof Date,
);
const isPeriodNameValid = data.periodId === String(period._id);
@@ -138,16 +143,19 @@ export const validateCommittee = (data: any, period: periodType): boolean => {
return committee.toLowerCase() === data.committee.toLowerCase();
});
+ const intStart = new Date(period.interviewPeriod.start);
+ const intEnd = new Date(period.interviewPeriod.end);
+
const isWithinInterviewPeriod = data.availabletimes.every(
- (time: { start: string; end: string }) => {
+ (time: { start: string | Date; end: string | Date }) => {
const startTime = new Date(time.start);
const endTime = new Date(time.end);
return (
- startTime >= new Date(period.interviewPeriod.start) &&
- startTime <= new Date(period.interviewPeriod.end) &&
- endTime <= new Date(period.interviewPeriod.end) &&
- endTime >= new Date(period.interviewPeriod.start) &&
+ startTime >= intStart &&
+ startTime <= intEnd &&
+ endTime <= intEnd &&
+ endTime >= intStart &&
startTime < endTime
);
},
diff --git a/package-lock.json b/package-lock.json
index 65f637f7..a9b4b2e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -55,6 +55,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
"engines": {
"node": ">=10"
},
@@ -823,6 +824,7 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@@ -839,6 +841,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
"engines": {
"node": ">=12"
},
@@ -850,6 +853,7 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -864,6 +868,7 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@@ -877,6 +882,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
"engines": {
"node": ">=6.0.0"
}
@@ -885,6 +891,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
"engines": {
"node": ">=6.0.0"
}
@@ -892,12 +899,14 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@@ -1074,6 +1083,7 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@@ -1086,6 +1096,7 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
"engines": {
"node": ">= 8"
}
@@ -1094,6 +1105,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@@ -1114,6 +1126,7 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
"optional": true,
"engines": {
"node": ">=14"
@@ -1741,18 +1754,6 @@
"typescript": ">=5.7.2"
}
},
- "node_modules/@trpc/server": {
- "version": "11.6.0",
- "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.6.0.tgz",
- "integrity": "sha512-skTso0AWbOZck40jwNeYv++AMZXNWLUWdyk+pB5iVaYmEKTuEeMoPrEudR12VafbEU6tZa8HK3QhBfTYYHDCdg==",
- "funding": [
- "https://trpc.io/sponsor"
- ],
- "peer": true,
- "peerDependencies": {
- "typescript": ">=5.7.2"
- }
- },
"node_modules/@trpc/tanstack-react-query": {
"version": "11.6.0",
"resolved": "https://registry.npmjs.org/@trpc/tanstack-react-query/-/tanstack-react-query-11.6.0.tgz",
@@ -2038,6 +2039,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -2046,6 +2048,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -2059,12 +2062,14 @@
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -2076,7 +2081,8 @@
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
},
"node_modules/argparse": {
"version": "2.0.1",
@@ -2348,12 +2354,14 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
"engines": {
"node": ">=8"
},
@@ -2380,6 +2388,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -2487,6 +2496,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
"engines": {
"node": ">= 6"
}
@@ -2531,6 +2541,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -2554,6 +2565,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -2575,6 +2587,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -2585,7 +2598,8 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -2603,6 +2617,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
"engines": {
"node": ">= 6"
}
@@ -2639,6 +2654,7 @@
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
"integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
+ "dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -2662,7 +2678,8 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -2825,7 +2842,8 @@
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
},
"node_modules/dir-glob": {
"version": "3.0.1",
@@ -2842,7 +2860,8 @@
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
},
"node_modules/doctrine": {
"version": "3.0.0",
@@ -2882,7 +2901,8 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
@@ -2901,7 +2921,8 @@
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
},
"node_modules/es-abstract": {
"version": "1.23.3",
@@ -3565,6 +3586,7 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@@ -3580,6 +3602,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -3624,6 +3647,7 @@
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
"dependencies": {
"reusify": "^1.0.4"
}
@@ -3644,6 +3668,7 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -3720,6 +3745,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
@@ -3770,6 +3796,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -3894,6 +3921,7 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
"dependencies": {
"is-glob": "^4.0.3"
},
@@ -4201,6 +4229,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -4240,6 +4269,7 @@
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
"integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
+ "dev": true,
"dependencies": {
"hasown": "^2.0.2"
},
@@ -4284,6 +4314,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4304,6 +4335,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -4327,6 +4359,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -4362,6 +4395,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -4529,7 +4563,8 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
},
"node_modules/iterator.prototype": {
"version": "1.1.2",
@@ -4548,6 +4583,7 @@
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
@@ -4562,6 +4598,7 @@
"version": "1.21.6",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
+ "dev": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -4730,6 +4767,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
"engines": {
"node": ">=10"
}
@@ -4737,7 +4775,8 @@
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
},
"node_modules/locate-path": {
"version": "6.0.0",
@@ -4867,6 +4906,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
"engines": {
"node": ">= 8"
}
@@ -4875,6 +4915,7 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -4937,6 +4978,7 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -5004,6 +5046,7 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
"dependencies": {
"any-promise": "^1.0.0",
"object-assign": "^4.0.1",
@@ -5159,6 +5202,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5391,7 +5435,8 @@
"node_modules/package-json-from-dist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
- "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
+ "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
+ "dev": true
},
"node_modules/parent-module": {
"version": "1.0.1",
@@ -5427,6 +5472,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -5434,12 +5480,14 @@
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@@ -5454,7 +5502,8 @@
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true
},
"node_modules/path-type": {
"version": "4.0.0",
@@ -5474,6 +5523,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -5485,6 +5535,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5493,6 +5544,7 @@
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "dev": true,
"engines": {
"node": ">= 6"
}
@@ -5510,6 +5562,7 @@
"version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -5537,6 +5590,7 @@
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
"dependencies": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
@@ -5553,6 +5607,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
"dependencies": {
"camelcase-css": "^2.0.1"
},
@@ -5571,6 +5626,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -5605,6 +5661,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
+ "dev": true,
"engines": {
"node": ">=14"
},
@@ -5616,6 +5673,7 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -5640,6 +5698,7 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -5663,7 +5722,8 @@
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
},
"node_modules/preact": {
"version": "10.12.1",
@@ -5755,6 +5815,7 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -5850,6 +5911,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
"dependencies": {
"pify": "^2.3.0"
}
@@ -5858,6 +5920,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -5925,6 +5988,7 @@
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
@@ -5950,6 +6014,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
@@ -5975,6 +6040,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -6106,6 +6172,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -6117,6 +6184,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -6142,6 +6210,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
"engines": {
"node": ">=14"
},
@@ -6198,6 +6267,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@@ -6215,6 +6285,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -6227,12 +6298,14 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
},
"node_modules/string-width/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
"engines": {
"node": ">=12"
},
@@ -6244,6 +6317,7 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -6353,6 +6427,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -6365,6 +6440,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -6424,6 +6500,7 @@
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0",
@@ -6445,6 +6522,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -6453,6 +6531,7 @@
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
@@ -6472,6 +6551,7 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -6509,6 +6589,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -6520,6 +6601,7 @@
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz",
"integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==",
+ "dev": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -6556,6 +6638,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
"engines": {
"node": ">= 6"
}
@@ -6564,6 +6647,7 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -6582,6 +6666,7 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
"dependencies": {
"any-promise": "^1.0.0"
}
@@ -6590,6 +6675,7 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
"dependencies": {
"thenify": ">= 3.1.0 < 4"
},
@@ -6601,6 +6687,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -6622,7 +6709,8 @@
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
@@ -6780,6 +6868,7 @@
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6891,6 +6980,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
@@ -6993,6 +7083,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@@ -7010,6 +7101,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -7025,12 +7117,14 @@
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -7044,6 +7138,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
"engines": {
"node": ">=12"
},
@@ -7055,6 +7150,7 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
"engines": {
"node": ">=12"
},
@@ -7066,6 +7162,7 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -7099,6 +7196,7 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
+ "dev": true,
"bin": {
"yaml": "bin.mjs"
},
diff --git a/pages/admin/new-period.tsx b/pages/admin/new-period.tsx
index 5ac74cbd..b501b64e 100644
--- a/pages/admin/new-period.tsx
+++ b/pages/admin/new-period.tsx
@@ -84,14 +84,14 @@ const NewPeriod = () => {
start,
end,
}: {
- start: string;
- end: string;
+ start: Date | null;
+ end: Date | null;
}) => {
setPeriodData((prevData) => ({
...prevData,
applicationPeriod: {
- start: start ? new Date(start) : undefined,
- end: end ? new Date(end) : undefined,
+ start: start ?? undefined,
+ end: end ?? undefined,
},
}));
};
@@ -100,14 +100,14 @@ const NewPeriod = () => {
start,
end,
}: {
- start: string;
- end: string;
+ start: Date | null;
+ end: Date | null;
}) => {
setPeriodData((prevData) => ({
...prevData,
interviewPeriod: {
- start: start ? new Date(start) : undefined,
- end: end ? new Date(end) : undefined,
+ start: start ?? undefined,
+ end: end ?? undefined,
},
}));
};
diff --git a/pages/api/applicants/[period-id]/[id].ts b/pages/api/applicants/[period-id]/[id].ts
index 3c391e1b..f459f908 100644
--- a/pages/api/applicants/[period-id]/[id].ts
+++ b/pages/api/applicants/[period-id]/[id].ts
@@ -40,9 +40,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
return res.status(200).json({ exists, application });
} else if (req.method === "DELETE") {
- const currentDate = new Date().toISOString();
-
- if (new Date(period.applicationPeriod.end) < new Date(currentDate)) {
+ if (period.applicationPeriod.end < new Date()) {
return res.status(403).json({ error: "Application period is over" });
}
diff --git a/pages/api/applicants/index.ts b/pages/api/applicants/index.ts
index a211142b..4886a537 100644
--- a/pages/api/applicants/index.ts
+++ b/pages/api/applicants/index.ts
@@ -12,6 +12,7 @@ import { isApplicantType } from "../../../lib/utils/validators";
import { isAdmin, hasSession, checkOwId } from "../../../lib/utils/apiChecks";
import { sendConfirmationSMS } from "../../../lib/sms/sendConfirmationSMS";
import { sendConfirmationEmail } from "../../../lib/email/sendConfirmationEmail";
+import { parseApplicantDates } from "../../../lib/utils/parseDates";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -28,8 +29,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
if (req.method === "POST") {
- const requestBody: applicantType = req.body;
- requestBody.date = new Date(new Date().getTime() + 60 * 60 * 2000); // add date with norwegain time (GMT+2)
+ const requestBody = parseApplicantDates(req.body);
+ requestBody.date = new Date();
const { period } = await getPeriodById(String(requestBody.periodId));
@@ -37,7 +38,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(400).json({ error: "Invalid period id" });
}
- if (!isApplicantType(req.body, period)) {
+ if (!isApplicantType(requestBody, period)) {
return res.status(400).json({ error: "Invalid data format" });
}
@@ -79,8 +80,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
if (req.method === "PUT") {
- const requestBody: applicantType = req.body;
- requestBody.date = new Date(new Date().getTime() + 60 * 60 * 2000); // add date with norwegian time (GMT+2). TODO: Fix workaround in #415
+ const requestBody = parseApplicantDates(req.body);
+ requestBody.date = new Date();
// Remove _id field to prevent MongoDB immutable field error
if ("_id" in requestBody) {
@@ -93,7 +94,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(400).json({ error: "Invalid period id" });
}
- if (!isApplicantType(req.body, period)) {
+ if (!isApplicantType(requestBody, period)) {
return res.status(400).json({ error: "Invalid data format" });
}
diff --git a/pages/api/committees/times/[period-id]/[committee].ts b/pages/api/committees/times/[period-id]/[committee].ts
index 573b31ed..cdddf2a4 100644
--- a/pages/api/committees/times/[period-id]/[committee].ts
+++ b/pages/api/committees/times/[period-id]/[committee].ts
@@ -50,7 +50,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(400).json({ error: "Invalid periodId" });
}
- if (new Date() > new Date(period.applicationPeriod.end)) {
+ if (new Date() > period.applicationPeriod.end) {
return res.status(400).json({ error: "Application period has ended" });
}
diff --git a/pages/api/committees/times/[period-id]/index.ts b/pages/api/committees/times/[period-id]/index.ts
index 297a0016..2570c8be 100644
--- a/pages/api/committees/times/[period-id]/index.ts
+++ b/pages/api/committees/times/[period-id]/index.ts
@@ -9,6 +9,7 @@ import {
} from "../../../../../lib/utils/validators";
import { getPeriodById } from "../../../../../lib/mongo/periods";
import { committeeInterviewType } from "../../../../../lib/types/types";
+import { parseCommitteeDates } from "../../../../../lib/utils/parseDates";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -24,18 +25,18 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (!isInCommitee(res, session)) return;
if (req.method === "POST") {
- const committeeData: committeeInterviewType = req.body;
-
if (!isCommitteeType(req.body)) {
return res.status(400).json({ error: "Invalid data format" });
}
+ const committeeData = parseCommitteeDates(req.body);
+
const { period } = await getPeriodById(String(committeeData.periodId));
if (!period) {
return res.status(400).json({ error: "Invalid periodId" });
}
- if (new Date() > new Date(period.applicationPeriod.end)) {
+ if (new Date() > period.applicationPeriod.end) {
return res.status(400).json({ error: "Application period has ended" });
}
diff --git a/pages/api/periods/index.ts b/pages/api/periods/index.ts
index 19064c26..46c691af 100644
--- a/pages/api/periods/index.ts
+++ b/pages/api/periods/index.ts
@@ -5,6 +5,7 @@ import { periodType } from "../../../lib/types/types";
import { createPeriod, getPeriods } from "../../../lib/mongo/periods";
import { hasSession, isAdmin } from "../../../lib/utils/apiChecks";
import { isPeriodType } from "../../../lib/utils/validators";
+import { parsePeriodDates } from "../../../lib/utils/parseDates";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -22,12 +23,13 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "POST") {
if (!isAdmin(res, session)) return;
- const period = req.body as periodType;
if (!isPeriodType(req.body)) {
return res.status(400).json({ error: "Invalid data format" });
}
+ const period = parsePeriodDates(req.body);
+
const { error } = await createPeriod(period);
if (error) throw new Error(error);
return res.status(201).json({ message: "Period created successfully" });
diff --git a/pages/apply/[period-id].tsx b/pages/apply/[period-id].tsx
index 92d55b6a..5035e4c2 100644
--- a/pages/apply/[period-id].tsx
+++ b/pages/apply/[period-id].tsx
@@ -151,10 +151,7 @@ const Application: NextPage = () => {
setPeriod(periodData.period);
- const currentDate = new Date().toISOString();
- if (
- new Date(periodData.period.applicationPeriod.end) < new Date(currentDate)
- ) {
+ if (periodData.period.applicationPeriod.end < new Date()) {
setIsApplicationPeriodOver(true);
}
}, [periodData]);
diff --git a/pages/apply/index.tsx b/pages/apply/index.tsx
index 4a7867bf..80c7df29 100644
--- a/pages/apply/index.tsx
+++ b/pages/apply/index.tsx
@@ -28,18 +28,13 @@ const Apply = () => {
setCurrentPeriods(
periodsData.periods.filter((period: periodType) => {
- const startDate = new Date(period.applicationPeriod.start || "");
- const endDate = new Date(period.applicationPeriod.end || "");
-
- return startDate <= today && endDate >= today;
+ return period.applicationPeriod.start <= today && period.applicationPeriod.end >= today;
})
);
setUpcomingPeriods(
periodsData.periods.filter((period: periodType) => {
- const startDate = new Date(period.applicationPeriod.start || "");
-
- return startDate >= today
+ return period.applicationPeriod.start >= today;
})
)
}, [periodsData]);
diff --git a/pages/committee/[period-id]/[committee]/index.tsx b/pages/committee/[period-id]/[committee]/index.tsx
index b5f51989..ddf9ca10 100644
--- a/pages/committee/[period-id]/[committee]/index.tsx
+++ b/pages/committee/[period-id]/[committee]/index.tsx
@@ -82,9 +82,7 @@ const CommitteeApplicantOverview: NextPage = () => {
if (periodIsError || interviewTimesIsError) return ;
if (!hasAccess) return ;
- const interviewPeriodEnd = period?.interviewPeriod.end
- ? new Date(period.interviewPeriod.end)
- : null;
+ const interviewPeriodEnd = period?.interviewPeriod.end ?? null;
// Satt frist til 14 dager etter intervju perioden, så får man ikke tilgang
const interviewAccessExpired =
diff --git a/pages/committees.tsx b/pages/committees.tsx
index b658841c..97f1be2a 100644
--- a/pages/committees.tsx
+++ b/pages/committees.tsx
@@ -168,9 +168,7 @@ const committeeIsInActivePeriod = (
const today = new Date();
const activePeriods = periods.filter((period) => {
- const applicationStart = new Date(period.applicationPeriod.start);
- const applicationEnd = new Date(period.applicationPeriod.end);
- return applicationStart <= today && applicationEnd >= today;
+ return period.applicationPeriod.start <= today && period.applicationPeriod.end >= today;
});
// Bankom is always active, since you can be a representative of bankom from each committee
@@ -194,9 +192,7 @@ const committeeIsCurrentlyInterviewing = (
const today = new Date();
const periodsWithInterviewsCurrently = periods.filter((period) => {
- const interviewStart = new Date(period.interviewPeriod.start);
- const interviewEnd = new Date(period.interviewPeriod.end);
- return interviewStart <= today && interviewEnd >= today;
+ return period.interviewPeriod.start <= today && period.interviewPeriod.end >= today;
});
// Bankom is always active, since you can be a representative of bankom from each committee
diff --git a/scripts/migrate-dates.js b/scripts/migrate-dates.js
new file mode 100644
index 00000000..61ddc52d
--- /dev/null
+++ b/scripts/migrate-dates.js
@@ -0,0 +1,152 @@
+/**
+ * Migreringsscript: Konverterer streng-datoer til BSON Date i MongoDB.
+ *
+ * Kjør med: node scripts/migrate-dates.js
+ *
+ * Krever MONGODB_URI miljøvariabel (les fra .env.local eller sett manuelt).
+ *
+ * Alternativt kan du kjøre innholdet i migrateCollections() direkte i mongosh.
+ */
+
+const { MongoClient } = require("mongodb");
+const path = require("path");
+const fs = require("fs");
+
+// Forsøk å lese MONGODB_URI fra .env.local
+function loadEnv() {
+ const envPath = path.resolve(__dirname, "../.env.local");
+ if (fs.existsSync(envPath)) {
+ const content = fs.readFileSync(envPath, "utf-8");
+ for (const line of content.split("\n")) {
+ const trimmed = line.trim();
+ if (trimmed && !trimmed.startsWith("#")) {
+ const [key, ...rest] = trimmed.split("=");
+ if (key && rest.length > 0) {
+ process.env[key.trim()] = rest.join("=").trim();
+ }
+ }
+ }
+ }
+}
+
+loadEnv();
+
+const URI = process.env.MONGODB_URI;
+if (!URI) {
+ console.error("MONGODB_URI er ikke satt. Sett den i .env.local eller som miljøvariabel.");
+ process.exit(1);
+}
+
+async function migratePeriods(db) {
+ const periods = db.collection("periods");
+ const docs = await periods.find({}).toArray();
+ let updated = 0;
+
+ for (const doc of docs) {
+ const needsUpdate =
+ typeof doc.applicationPeriod?.start === "string" ||
+ typeof doc.applicationPeriod?.end === "string" ||
+ typeof doc.interviewPeriod?.start === "string" ||
+ typeof doc.interviewPeriod?.end === "string";
+
+ if (!needsUpdate) continue;
+
+ await periods.updateOne(
+ { _id: doc._id },
+ {
+ $set: {
+ "applicationPeriod.start": new Date(doc.applicationPeriod.start),
+ "applicationPeriod.end": new Date(doc.applicationPeriod.end),
+ "interviewPeriod.start": new Date(doc.interviewPeriod.start),
+ "interviewPeriod.end": new Date(doc.interviewPeriod.end),
+ },
+ }
+ );
+ updated++;
+ }
+
+ console.log(`periods: ${updated}/${docs.length} dokumenter oppdatert`);
+}
+
+async function migrateApplications(db) {
+ const applications = db.collection("applications");
+ const docs = await applications.find({}).toArray();
+ let updated = 0;
+
+ for (const doc of docs) {
+ if (!Array.isArray(doc.selectedTimes) || doc.selectedTimes.length === 0) continue;
+
+ const needsUpdate = doc.selectedTimes.some(
+ (t) => typeof t.start === "string" || typeof t.end === "string"
+ );
+
+ if (!needsUpdate) continue;
+
+ const updatedTimes = doc.selectedTimes.map((t) => ({
+ start: typeof t.start === "string" ? new Date(t.start) : t.start,
+ end: typeof t.end === "string" ? new Date(t.end) : t.end,
+ }));
+
+ await applications.updateOne(
+ { _id: doc._id },
+ { $set: { selectedTimes: updatedTimes } }
+ );
+ updated++;
+ }
+
+ console.log(`applications: ${updated}/${docs.length} dokumenter oppdatert`);
+}
+
+async function migrateCommittees(db) {
+ const committees = db.collection("committees");
+ const docs = await committees.find({}).toArray();
+ let updated = 0;
+
+ for (const doc of docs) {
+ if (!Array.isArray(doc.availabletimes) || doc.availabletimes.length === 0) continue;
+
+ const needsUpdate = doc.availabletimes.some(
+ (t) => typeof t.start === "string" || typeof t.end === "string"
+ );
+
+ if (!needsUpdate) continue;
+
+ const updatedTimes = doc.availabletimes.map((t) => ({
+ start: typeof t.start === "string" ? new Date(t.start) : t.start,
+ end: typeof t.end === "string" ? new Date(t.end) : t.end,
+ room: t.room,
+ }));
+
+ await committees.updateOne(
+ { _id: doc._id },
+ { $set: { availabletimes: updatedTimes } }
+ );
+ updated++;
+ }
+
+ console.log(`committees: ${updated}/${docs.length} dokumenter oppdatert`);
+}
+
+async function main() {
+ const client = new MongoClient(URI);
+
+ try {
+ await client.connect();
+ const db = client.db();
+
+ console.log("Starter migrering av datoer...\n");
+
+ await migratePeriods(db);
+ await migrateApplications(db);
+ await migrateCommittees(db);
+
+ console.log("\nMigrering fullført!");
+ } catch (error) {
+ console.error("Feil under migrering:", error);
+ process.exit(1);
+ } finally {
+ await client.close();
+ }
+}
+
+main();
diff --git a/scripts/migrate-to-new-db.js b/scripts/migrate-to-new-db.js
new file mode 100644
index 00000000..14a3235b
--- /dev/null
+++ b/scripts/migrate-to-new-db.js
@@ -0,0 +1,139 @@
+/**
+ * Bygger Date_dev som en migrert kopi av kilde-DB-en (fra MONGODB_URI)
+ * via aggregation + $out. Kilde-DB-en berøres ikke.
+ *
+ * Kjør med: node scripts/migrate-to-new-db.js
+ */
+
+const { MongoClient } = require("mongodb");
+const path = require("path");
+const fs = require("fs");
+
+function loadEnv() {
+ const envPath = path.resolve(__dirname, "../.env.local");
+ if (fs.existsSync(envPath)) {
+ const content = fs.readFileSync(envPath, "utf-8");
+ for (const line of content.split("\n")) {
+ const trimmed = line.trim();
+ if (trimmed && !trimmed.startsWith("#")) {
+ const [key, ...rest] = trimmed.split("=");
+ if (key && rest.length > 0) {
+ process.env[key.trim()] = rest.join("=").trim();
+ }
+ }
+ }
+ }
+}
+
+loadEnv();
+
+const URI = process.env.MONGODB_URI;
+if (!URI) {
+ console.error("MONGODB_URI er ikke satt. Sett den i .env.local eller som miljøvariabel.");
+ process.exit(1);
+}
+
+const TARGET_DB = "Date_dev";
+
+async function runPipeline(sourceDb, sourceColl, pipeline) {
+ await sourceDb.collection(sourceColl).aggregate(pipeline).toArray();
+}
+
+async function main() {
+ const client = new MongoClient(URI);
+
+ try {
+ await client.connect();
+ const sourceDb = client.db();
+ const targetDb = client.db(TARGET_DB);
+
+ console.log(`Kilde-DB: ${sourceDb.databaseName}`);
+ console.log(`Mål-DB: ${TARGET_DB}\n`);
+
+ console.log("Migrerer periods...");
+ await runPipeline(sourceDb, "periods", [
+ {
+ $addFields: {
+ "applicationPeriod.start": { $toDate: "$applicationPeriod.start" },
+ "applicationPeriod.end": { $toDate: "$applicationPeriod.end" },
+ "interviewPeriod.start": { $toDate: "$interviewPeriod.start" },
+ "interviewPeriod.end": { $toDate: "$interviewPeriod.end" },
+ },
+ },
+ { $out: { db: TARGET_DB, coll: "periods" } },
+ ]);
+ console.log(
+ ` → ${TARGET_DB}.periods: ${await targetDb.collection("periods").countDocuments()} dokumenter\n`
+ );
+
+ console.log("Migrerer applications...");
+ await runPipeline(sourceDb, "applications", [
+ {
+ $addFields: {
+ selectedTimes: {
+ $map: {
+ input: "$selectedTimes",
+ as: "t",
+ in: {
+ start: { $toDate: "$$t.start" },
+ end: { $toDate: "$$t.end" },
+ },
+ },
+ },
+ },
+ },
+ { $out: { db: TARGET_DB, coll: "applications" } },
+ ]);
+ console.log(
+ ` → ${TARGET_DB}.applications: ${await targetDb.collection("applications").countDocuments()} dokumenter\n`
+ );
+
+ console.log("Migrerer committees...");
+ await runPipeline(sourceDb, "committees", [
+ {
+ $addFields: {
+ availabletimes: {
+ $map: {
+ input: "$availabletimes",
+ as: "t",
+ in: {
+ start: { $toDate: "$$t.start" },
+ end: { $toDate: "$$t.end" },
+ room: "$$t.room",
+ },
+ },
+ },
+ },
+ },
+ { $out: { db: TARGET_DB, coll: "committees" } },
+ ]);
+ console.log(
+ ` → ${TARGET_DB}.committees: ${await targetDb.collection("committees").countDocuments()} dokumenter\n`
+ );
+
+ console.log("Kopierer interviews...");
+ await runPipeline(sourceDb, "interviews", [
+ { $out: { db: TARGET_DB, coll: "interviews" } },
+ ]);
+ console.log(
+ ` → ${TARGET_DB}.interviews: ${await targetDb.collection("interviews").countDocuments()} dokumenter\n`
+ );
+
+ console.log("Kopierer rooms...");
+ await runPipeline(sourceDb, "rooms", [
+ { $out: { db: TARGET_DB, coll: "rooms" } },
+ ]);
+ console.log(
+ ` → ${TARGET_DB}.rooms: ${await targetDb.collection("rooms").countDocuments()} dokumenter\n`
+ );
+
+ console.log("Migrering fullført!");
+ } catch (error) {
+ console.error("Feil under migrering:", error);
+ process.exit(1);
+ } finally {
+ await client.close();
+ }
+}
+
+main();