Merge branch 'main' into sgunter/feat-preserve-email-on-feedback-form
This commit is contained in:
15
flake.nix
15
flake.nix
@@ -1,20 +1,25 @@
|
|||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
inputs:
|
{
|
||||||
inputs.flake-utils.lib.eachDefaultSystem (
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
pkgs = (import (inputs.nixpkgs) { inherit system; });
|
pkgs = (import nixpkgs { inherit system; });
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
formatter = pkgs.nixfmt-rfc-style;
|
formatter = pkgs.nixfmt-rfc-style;
|
||||||
|
|
||||||
devShell = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
|
name = "utrp-dev";
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
nodejs_20 # v20.19.4
|
nodejs_20 # v20.19.4
|
||||||
pnpm_10 # v10.14.0
|
pnpm_10 # v10.14.0
|
||||||
|
|||||||
Binary file not shown.
@@ -15,6 +15,8 @@ import type { SiteSupportType } from '@views/lib/getSiteSupport';
|
|||||||
import { populateSearchInputs } from '@views/lib/populateSearchInputs';
|
import { populateSearchInputs } from '@views/lib/populateSearchInputs';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import DialogProvider from './common/DialogProvider/DialogProvider';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
support: Extract<SiteSupportType, 'COURSE_CATALOG_DETAILS' | 'COURSE_CATALOG_LIST'>;
|
support: Extract<SiteSupportType, 'COURSE_CATALOG_DETAILS' | 'COURSE_CATALOG_LIST'>;
|
||||||
}
|
}
|
||||||
@@ -82,6 +84,7 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ExtensionRoot>
|
<ExtensionRoot>
|
||||||
|
<DialogProvider>
|
||||||
<NewSearchLink />
|
<NewSearchLink />
|
||||||
<RecruitmentBanner />
|
<RecruitmentBanner />
|
||||||
<TableHead>Plus</TableHead>
|
<TableHead>Plus</TableHead>
|
||||||
@@ -104,6 +107,7 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul
|
|||||||
afterLeave={() => setSelectedCourse(null)}
|
afterLeave={() => setSelectedCourse(null)}
|
||||||
/>
|
/>
|
||||||
{enableScrollToLoad && <AutoLoad addRows={addRows} />}
|
{enableScrollToLoad && <AutoLoad addRows={addRows} />}
|
||||||
|
</DialogProvider>
|
||||||
</ExtensionRoot>
|
</ExtensionRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,11 @@
|
|||||||
@apply font-sans;
|
@apply font-sans;
|
||||||
color: #303030;
|
color: #303030;
|
||||||
|
|
||||||
|
// fix font-family on injected pages
|
||||||
|
* {
|
||||||
|
@apply font-sans;
|
||||||
|
}
|
||||||
|
|
||||||
[data-rfd-drag-handle-context-id=':r1:'] {
|
[data-rfd-drag-handle-context-id=':r1:'] {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import createSchedule from '@pages/background/lib/createSchedule';
|
||||||
|
import switchSchedule from '@pages/background/lib/switchSchedule';
|
||||||
import {
|
import {
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
CalendarDots,
|
CalendarDots,
|
||||||
@@ -14,8 +16,10 @@ import { background } from '@shared/messages';
|
|||||||
import type { Course } from '@shared/types/Course';
|
import type { Course } from '@shared/types/Course';
|
||||||
import type Instructor from '@shared/types/Instructor';
|
import type Instructor from '@shared/types/Instructor';
|
||||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
|
import { englishStringifyList } from '@shared/util/string';
|
||||||
import { Button } from '@views/components/common/Button';
|
import { Button } from '@views/components/common/Button';
|
||||||
import { Chip, coreMap, flagMap } from '@views/components/common/Chip';
|
import { Chip, coreMap, flagMap } from '@views/components/common/Chip';
|
||||||
|
import { usePrompt } from '@views/components/common/DialogProvider/DialogProvider';
|
||||||
import Divider from '@views/components/common/Divider';
|
import Divider from '@views/components/common/Divider';
|
||||||
import Link from '@views/components/common/Link';
|
import Link from '@views/components/common/Link';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
@@ -60,7 +64,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
|
|
||||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||||
const lastCopyTime = useRef<number>(0);
|
const lastCopyTime = useRef<number>(0);
|
||||||
|
const showDialog = usePrompt();
|
||||||
const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' });
|
const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' });
|
||||||
|
|
||||||
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
@@ -112,10 +116,78 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddToNewSchedule = async (close: () => void) => {
|
||||||
|
const newScheduleId = await createSchedule(`${course.semester.season} ${course.semester.year}`);
|
||||||
|
switchSchedule(newScheduleId);
|
||||||
|
addCourse({ course, scheduleId: newScheduleId });
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
const handleAddOrRemoveCourse = async () => {
|
const handleAddOrRemoveCourse = async () => {
|
||||||
|
const uniqueSemesterCodes = [
|
||||||
|
...new Set(
|
||||||
|
activeSchedule.courses
|
||||||
|
.map(course => course.semester.code)
|
||||||
|
.filter((code): code is string => code !== undefined)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
uniqueSemesterCodes.sort();
|
||||||
|
const codeToReadableMap: Record<string, string> = {};
|
||||||
|
activeSchedule.courses.forEach(course => {
|
||||||
|
const { code } = course.semester;
|
||||||
|
if (code) {
|
||||||
|
const readable = `${course.semester.season} ${course.semester.year}`;
|
||||||
|
codeToReadableMap[code] = readable;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const sortedSemesters = uniqueSemesterCodes
|
||||||
|
.map(code => codeToReadableMap[code])
|
||||||
|
.filter((value): value is string => value !== undefined);
|
||||||
|
const activeSemesters = englishStringifyList(sortedSemesters);
|
||||||
|
|
||||||
if (!activeSchedule) return;
|
if (!activeSchedule) return;
|
||||||
if (!courseAdded) {
|
if (!courseAdded) {
|
||||||
|
const currentSemesterCode = course.semester.code;
|
||||||
|
// Show warning if this course is for a different semester than the selected schedule
|
||||||
|
if (
|
||||||
|
activeSchedule.courses.length > 0 &&
|
||||||
|
activeSchedule.courses.every(otherCourse => otherCourse.semester.code !== currentSemesterCode)
|
||||||
|
) {
|
||||||
|
const dialogButtons = (close: () => void) => (
|
||||||
|
<>
|
||||||
|
<Button variant='minimal' color='ut-black' onClick={close}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='filled'
|
||||||
|
color='ut-burntorange'
|
||||||
|
onClick={() => {
|
||||||
|
handleAddToNewSchedule(close);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Start a new schedule
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
showDialog({
|
||||||
|
title: 'This course section is from a different semester!',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
The section you're adding is for{' '}
|
||||||
|
<span className='text-ut-burntorange'>
|
||||||
|
{course.semester.season} {course.semester.year}
|
||||||
|
</span>
|
||||||
|
, but your current schedule contains sections in{' '}
|
||||||
|
<span className='text-ut-burntorange'>{activeSemesters}</span>. Mixing semesters in one
|
||||||
|
schedule may cause confusion.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
buttons: dialogButtons,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
addCourse({ course, scheduleId: activeSchedule.id });
|
addCourse({ course, scheduleId: activeSchedule.id });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
removeCourse({ course, scheduleId: activeSchedule.id });
|
removeCourse({ course, scheduleId: activeSchedule.id });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user