Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93548627a6 | ||
|
|
638ee88c96 | ||
|
|
50e88fa015 | ||
|
|
b3ae91d8f3 | ||
|
|
0077ae70d2 | ||
|
|
94744e01b9 | ||
|
|
8de88d6ad7 | ||
|
|
2d0804f90e | ||
|
|
d3577358d0 | ||
|
|
7346720894 | ||
|
|
b00bf6c180 | ||
|
|
eb306787ae | ||
|
|
643ea13207 | ||
|
|
6f1afc5b25 | ||
|
|
83d76f72da | ||
|
|
768ac776ed | ||
|
|
9995b93d2a | ||
|
|
4f609aeec7 | ||
|
|
b6eccaca6a | ||
|
|
cef99c2d72 | ||
|
|
86792eb56f | ||
|
|
a715bbd933 | ||
|
|
c2007ef090 | ||
|
|
b967240f8f | ||
|
|
839f9c6d6a |
@@ -172,7 +172,14 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@typescript-eslint/space-before-function-paren': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
|
||||
1
@types/vite-env.d.ts
vendored
1
@types/vite-env.d.ts
vendored
@@ -2,6 +2,7 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_PACKAGE_VERSION: string;
|
||||
readonly VITE_SENTRY_ENVIRONMENT: string;
|
||||
readonly VITE_BETA_BUILD?: 'true';
|
||||
}
|
||||
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,3 +1,32 @@
|
||||
## [2.0.2](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.0.1...v2.0.2) (2024-11-05)
|
||||
|
||||
### Features
|
||||
|
||||
- add core curriculum chips to injected popup ([#372](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/372)) ([6f1afc5](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/6f1afc5b25441c6a1fbfdf57b3c8b5b74e36f5a0))
|
||||
- Add linkedin social to calendar ([#368](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/368)) ([b6eccac](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b6eccaca6a2cdba9b57d2f49f064ae8504bbd5cb))
|
||||
- add more relevant links to the From the Team section ([#380](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/380)) ([643ea13](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/643ea1320798aabb7783d267f5e6fd7c00fc2e3f))
|
||||
- bold course number in grade distribution chart, change text to ut-black ([#406](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/406)) ([638ee88](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/638ee88c96510a779c157b524903caaeffc9ef19))
|
||||
- disable/some actions when no instructor ([#319](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/319)) ([839f9c6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/839f9c6d6afd4a1eae1a0bdf8893ab2e19b9fdff))
|
||||
- **ui:** changed popup close icon to ut-black ([#394](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/394)) ([0077ae7](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/0077ae70d22f24549c4c3b243188d19adbfbac14)), closes [#333F48](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/333F48)
|
||||
- update senior swe admins ([#326](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/326)) ([b967240](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b967240f8fbb7a790a78f4aa256f0a77a491abb8))
|
||||
- update useful links ([#367](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/367)) ([cef99c2](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/cef99c2d72d3a2800f8a918d01cb116f8795d0c8))
|
||||
- use "copy of" for duplicated schedules and place them under the original schedule [#358](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/358) ([#397](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/397)) ([94744e0](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/94744e01b94819fb4f5d64616ea56857b906c2dd))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- added descending sort for commits on contributor section in settings page ([#365](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/365)) ([a715bbd](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/a715bbd933a87742e7bce3a44e8ba1bd419ad5eb)), closes [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363) [#363](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/363)
|
||||
- change schedule total courses text color to UTRP black ([#369](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/369)) ([b00bf6c](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b00bf6c180f1c6c3a61c5ef855e160ddf4af3ea4))
|
||||
- changed the font-weight of h1-course ([#370](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/370)) ([4f609ae](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/4f609aeec797c1f99f0a57e5aeef7b82756ea4bc)), closes [#347](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/347)
|
||||
- ensure input elements take full width of parent ([#364](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/364)) ([c2007ef](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/c2007ef090aab3bbfcb8bca1ebc476255d09cb90))
|
||||
- remove screenshot padding class for png download for [#344](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/344) ([#376](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/376)) ([768ac77](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/768ac776ed4d5ca2113a032a93c2dc7432915aa1)), closes [#334](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/334)
|
||||
- sentry issues ([#389](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/389)) ([2d0804f](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/2d0804f90e5d7a9ff83f7fd5c5acfdc7c1b1cc84))
|
||||
- typo in settings page ([#386](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/386)) ([d357735](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/d3577358d0d1fb60f2c776ae4b01e255fcf9109e))
|
||||
- **ui:** add space before/after forward slash in "ASYNC/OTHER" text ([#366](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/366)) ([86792eb](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/86792eb56f04b615f7d52b2f417b88f4cb9a82ec))
|
||||
- **ui:** duplicate schedule warning ([#295](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/295)) ([7346720](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/73467208947e0116ce8538052ee75dea1d8038f9))
|
||||
- **ui:** main popup now shows 0 for empty schedule ([#395](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/395)) ([8de88d6](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/8de88d6ad7d4c2b5c3aa08e1efc59f7226b40c6b))
|
||||
- **ui:** multiple instructors are formatted properly, displays last name only, and are capitalized in all course blocks ([#342](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/342)) ([#403](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/403)) ([50e88fa](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/50e88fa015e0290fbe0dab8a19f8fcdbc4dd02b0))
|
||||
- **ui:** placeholder text for no instructor course [#400](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/400) ([#402](https://github.com/Longhorn-Developers/UT-Registration-Plus/issues/402)) ([b3ae91d](https://github.com/Longhorn-Developers/UT-Registration-Plus/commit/b3ae91d8f3cebb89e5e5cea7f1200d28326afb4d))
|
||||
|
||||
## [2.0.1](https://github.com/Longhorn-Developers/UT-Registration-Plus/compare/v2.0.0...v2.0.1) (2024-10-17)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ut-registration-plus",
|
||||
"displayName": "UT Registration Plus",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"description": "UT Registration Plus is a Chrome extension that allows students to easily register for classes.",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/Longhorn-Developers/UT-Registration-Plus",
|
||||
@@ -10,7 +10,7 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:watch": "NODE_ENV='development' vite build --mode development -w",
|
||||
"zip": "pnpm build && pnpm gulp zip",
|
||||
"zip": "PROD=true pnpm build && pnpm gulp zip",
|
||||
"prettier": "prettier src --check",
|
||||
"prettier:fix": "prettier src --write",
|
||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
|
||||
|
||||
@@ -32,7 +32,7 @@ chrome.runtime.onInstalled.addListener(details => {
|
||||
});
|
||||
|
||||
// migration/login logic
|
||||
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
||||
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
|
||||
// console.log(changeInfo);
|
||||
if (changeInfo.url === 'https://utdirect.utexas.edu/apps/registrar/course_schedule/utrp_login/') {
|
||||
function openPopupAction() {
|
||||
@@ -59,15 +59,11 @@ messageListener.listen();
|
||||
UserScheduleStore.listen('schedules', async schedules => {
|
||||
const index = await UserScheduleStore.get('activeIndex');
|
||||
const numCourses = schedules.newValue[index]?.courses?.length;
|
||||
if (!numCourses) return;
|
||||
|
||||
updateBadgeText(numCourses);
|
||||
updateBadgeText(numCourses || 0);
|
||||
});
|
||||
|
||||
UserScheduleStore.listen('activeIndex', async ({ newValue }) => {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
const numCourses = schedules[newValue]?.courses?.length;
|
||||
if (!numCourses) return;
|
||||
|
||||
updateBadgeText(numCourses);
|
||||
updateBadgeText(numCourses || 0);
|
||||
});
|
||||
|
||||
@@ -10,15 +10,18 @@ import handleDuplicate from './handleDuplicate';
|
||||
*/
|
||||
export default async function duplicateSchedule(scheduleId: string): Promise<string | undefined> {
|
||||
const schedules = await UserScheduleStore.get('schedules');
|
||||
const schedule = schedules.find(schedule => schedule.id === scheduleId);
|
||||
const scheduleIndex = schedules.findIndex(schedule => schedule.id === scheduleId);
|
||||
|
||||
if (schedule === undefined) {
|
||||
if (scheduleIndex === -1) {
|
||||
throw new Error(`Schedule ${scheduleId} does not exist`);
|
||||
}
|
||||
|
||||
const updatedName = await handleDuplicate(schedule.name);
|
||||
const schedule = schedules[scheduleIndex]!;
|
||||
|
||||
schedules.push({
|
||||
const copyOfName = `Copy of ${schedule.name}`;
|
||||
const updatedName = await handleDuplicate(copyOfName);
|
||||
|
||||
schedules.splice(scheduleIndex + 1, 0, {
|
||||
id: generateRandomId(),
|
||||
name: updatedName,
|
||||
courses: JSON.parse(JSON.stringify(schedule.courses)),
|
||||
|
||||
15
src/shared/types/CRXPages.ts
Normal file
15
src/shared/types/CRXPages.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* An object containing the paths to various pages used in the CRX application.
|
||||
*/
|
||||
export const CRX_PAGES = {
|
||||
DEBUG: '/debug.html',
|
||||
CALENDAR: '/calendar.html',
|
||||
OPTIONS: '/options.html',
|
||||
REPORT: '/report.html',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Represents a type that corresponds to the keys of the `CRX_PAGES` object.
|
||||
* This type is used to ensure that only valid page keys are used within the application.
|
||||
*/
|
||||
export type CRX_Page = keyof typeof CRX_PAGES;
|
||||
@@ -79,6 +79,8 @@ export class Course {
|
||||
scrapedAt!: number;
|
||||
/** The colors of the course when displayed */
|
||||
colors: CourseColors;
|
||||
/** The core curriculum requirements the course satisfies */
|
||||
core: string[];
|
||||
|
||||
constructor(course: Serialized<Course>) {
|
||||
Object.assign(this, course);
|
||||
@@ -88,6 +90,7 @@ export class Course {
|
||||
this.scrapedAt = Date.now();
|
||||
}
|
||||
this.colors = course.colors ? structuredClone(course.colors) : getCourseColors('emerald', 500);
|
||||
this.core = course.core ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,10 +15,9 @@ export const colors = {
|
||||
gray: '#9CADB7',
|
||||
offwhite: '#D6D2C4',
|
||||
concrete: '#95A5A6',
|
||||
red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green
|
||||
},
|
||||
theme: {
|
||||
red: '#BF0000',
|
||||
red: '#D10000',
|
||||
black: '#1A2024',
|
||||
},
|
||||
} as const satisfies Record<string, Record<string, string>>;
|
||||
|
||||
@@ -15,7 +15,7 @@ import CancelledIcon from '~icons/material-symbols/warning';
|
||||
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): JSX.Element | null {
|
||||
const { status, ...rest } = props;
|
||||
|
||||
switch (props.status) {
|
||||
switch (status) {
|
||||
case Status.WAITLISTED:
|
||||
return <WaitlistIcon {...rest} />;
|
||||
case Status.CLOSED:
|
||||
|
||||
@@ -14,7 +14,7 @@ export const BADGE_LIMIT = 10;
|
||||
*/
|
||||
export default function updateBadgeText(value: number): void {
|
||||
let badgeText = '';
|
||||
if (value > 0) {
|
||||
if (value >= 0) {
|
||||
if (value > BADGE_LIMIT) {
|
||||
badgeText = `${BADGE_LIMIT}+`;
|
||||
} else {
|
||||
|
||||
@@ -16,8 +16,16 @@ export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
export const FlagChip: Story = {
|
||||
args: {
|
||||
label: 'QR',
|
||||
variant: 'flag',
|
||||
},
|
||||
};
|
||||
|
||||
export const CoreChip: Story = {
|
||||
args: {
|
||||
label: 'SB',
|
||||
variant: 'core',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ export const ExampleCourse: Course = new Course({
|
||||
'Taught as a Web-based course.',
|
||||
],
|
||||
flags: ['Quantitative Reasoning'],
|
||||
core: ['Natural Science and Technology, Part I'],
|
||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||
instructionMode: 'Online',
|
||||
instructors: [
|
||||
@@ -60,6 +61,7 @@ export const ExampleCourse2: Course = new Course({
|
||||
'May be counted toward the Independent Inquiry flag requirement.',
|
||||
],
|
||||
flags: ['Independent Inquiry'],
|
||||
core: ['Natural Science and Technology, Part II'],
|
||||
fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
|
||||
instructionMode: 'In Person',
|
||||
instructors: [
|
||||
|
||||
@@ -34,6 +34,7 @@ const generateCourses = (count: number): Course[] => {
|
||||
'Taught as a Web-based course.',
|
||||
],
|
||||
flags: ['Quantitative Reasoning'],
|
||||
core: ['Natural Science and Technology, Part I'],
|
||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||
instructionMode: 'Online',
|
||||
instructors: [
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ExampleCourse: Course = new Course({
|
||||
'Taught as a Web-based course.',
|
||||
],
|
||||
flags: ['Quantitative Reasoning'],
|
||||
core: ['Natural Science and Technology, Part I'],
|
||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||
instructionMode: 'Online',
|
||||
instructors: [
|
||||
|
||||
@@ -11,6 +11,7 @@ const exampleGovCourse: Course = new Course({
|
||||
department: 'GOV',
|
||||
description: ['nah', 'aint typing this', 'corndog'],
|
||||
flags: ['no flag for you >:)'],
|
||||
core: ['American and Texas Government'],
|
||||
fullName: 'GOV 312L Something something',
|
||||
instructionMode: 'Online',
|
||||
instructors: [
|
||||
@@ -43,6 +44,7 @@ const examplePsyCourse: Course = new Course({
|
||||
department: 'PSY',
|
||||
description: ['nah', 'aint typing this', 'corndog'],
|
||||
flags: ['no flag for you >:)'],
|
||||
core: ['Social and Behavioral Sciences'],
|
||||
fullName: 'PSY 317L Yada yada',
|
||||
instructionMode: 'Online',
|
||||
scrapedAt: Date.now(),
|
||||
|
||||
@@ -16,6 +16,7 @@ export const exampleCourse: Course = new Course({
|
||||
'Taught as a Web-based course.',
|
||||
],
|
||||
flags: ['Quantitative Reasoning'],
|
||||
core: ['Natural Science and Technology, Part I'],
|
||||
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
|
||||
instructionMode: 'Online',
|
||||
scrapedAt: Date.now(),
|
||||
@@ -99,6 +100,7 @@ export const bevoCourse: Course = new Course({
|
||||
},
|
||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
|
||||
flags: ['Independent Inquiry', 'Writing'],
|
||||
core: ['Humanities'],
|
||||
instructionMode: 'In Person',
|
||||
semester: {
|
||||
code: '12345',
|
||||
@@ -154,6 +156,7 @@ export const mikeScottCS314Course: Course = new Course({
|
||||
},
|
||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/50825/',
|
||||
flags: ['Writing', 'Independent Inquiry'],
|
||||
core: ['Natural Science and Technology, Part II'],
|
||||
instructionMode: 'In Person',
|
||||
semester: {
|
||||
code: '12345',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { openReportWindow } from '@shared/util/openReportWindow';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import List from '@views/components/common/List';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
||||
import useKC_DABR_WASM from 'kc-dabr-wasm';
|
||||
@@ -59,9 +60,16 @@ export default function PopupMain(): JSX.Element {
|
||||
}, []);
|
||||
|
||||
const [activeSchedule, schedules] = useSchedules();
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
// const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [funny, setFunny] = useState<string>('');
|
||||
|
||||
const enforceScheduleLimit = useEnforceScheduleLimit();
|
||||
const handleAddSchedule = () => {
|
||||
if (enforceScheduleLimit()) {
|
||||
createSchedule('New Schedule');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const randomIndex = Math.floor(Math.random() * splashText.length);
|
||||
setFunny(
|
||||
@@ -128,7 +136,7 @@ export default function PopupMain(): JSX.Element {
|
||||
variant='filled'
|
||||
color='ut-burntorange'
|
||||
className='h-fit p-0 btn'
|
||||
onClick={() => createSchedule('New Schedule')}
|
||||
onClick={handleAddSchedule}
|
||||
>
|
||||
<AddSchedule className='h-6 w-6' />
|
||||
</Button>
|
||||
|
||||
@@ -81,6 +81,7 @@ export default function ReportIssueMain(): JSX.Element {
|
||||
<label htmlFor='email' className='mb-1 block text-sm text-ut-black font-medium'>
|
||||
Your @utexas.edu email
|
||||
</label>
|
||||
<div className='flex'>
|
||||
<input
|
||||
type='email'
|
||||
id='email'
|
||||
@@ -91,11 +92,13 @@ export default function ReportIssueMain(): JSX.Element {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-4'>
|
||||
<label htmlFor='feedback' className='mb-1 block text-sm text-ut-black font-medium'>
|
||||
Your feedback
|
||||
</label>
|
||||
<div className='flex'>
|
||||
<textarea
|
||||
id='feedback'
|
||||
value={feedback}
|
||||
@@ -105,6 +108,7 @@ export default function ReportIssueMain(): JSX.Element {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={submitFeedback}
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function CalendarCourseCell({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'h-full w-0 flex justify-center rounded p-x-2 p-y-1.2 cursor-pointer screenshot:p-1.5 hover:shadow-md transition-shadow-100 ease-out',
|
||||
'h-full w-0 flex justify-center rounded p-x-2 p-y-1.2 cursor-pointer hover:shadow-md transition-shadow-100 ease-out',
|
||||
{
|
||||
'min-w-full': timeAndLocation,
|
||||
'w-full': !timeAndLocation,
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import DiscordIcon from '~icons/bi/discord';
|
||||
import GithubIcon from '~icons/ri/github-fill';
|
||||
import InstagramIcon from '~icons/ri/instagram-line';
|
||||
import LinkedinIcon from '~icons/ri/linkedin-box-fill';
|
||||
|
||||
import Link from '../common/Link';
|
||||
|
||||
@@ -23,6 +24,12 @@ export default function CalendarFooter(): JSX.Element {
|
||||
<Link className='linkanimate' href='https://github.com/Longhorn-Developers'>
|
||||
<GithubIcon className='h-6 w-6' />
|
||||
</Link>
|
||||
<Link
|
||||
className='linkanimate'
|
||||
href='https://www.linkedin.com/company/longhorn-developers/posts/?feedView=all'
|
||||
>
|
||||
<LinkedinIcon className='h-6 w-6 -mx-0.75' />
|
||||
</Link>
|
||||
</div>
|
||||
<p className='text-2.5 text-ut-concrete font-light tracking-wide'>
|
||||
UT Registration Plus is a project under Longhorn Developers, a student-led organization aimed at
|
||||
|
||||
@@ -47,7 +47,7 @@ function makeGridRow(row: number, cols: number): JSX.Element {
|
||||
*/
|
||||
export default function CalendarGrid({
|
||||
courseCells,
|
||||
saturdayClass, // TODO: implement/move away from props
|
||||
saturdayClass: _saturdayClass, // TODO: implement/move away from props
|
||||
setCourse,
|
||||
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||
return (
|
||||
|
||||
@@ -4,13 +4,12 @@ import { Button } from '@views/components/common/Button';
|
||||
import List from '@views/components/common/List';
|
||||
import ScheduleListItem from '@views/components/common/ScheduleListItem';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||
import React from 'react';
|
||||
|
||||
import AddSchedule from '~icons/material-symbols/add';
|
||||
|
||||
import { usePrompt } from '../common/DialogProvider/DialogProvider';
|
||||
|
||||
/**
|
||||
* Renders a component that displays a list of schedules.
|
||||
*
|
||||
@@ -19,32 +18,12 @@ import { usePrompt } from '../common/DialogProvider/DialogProvider';
|
||||
*/
|
||||
export function CalendarSchedules() {
|
||||
const [, schedules] = useSchedules();
|
||||
const showDialog = usePrompt();
|
||||
|
||||
const enforceScheduleLimit = useEnforceScheduleLimit();
|
||||
const handleAddSchedule = () => {
|
||||
if (schedules.length >= 10) {
|
||||
showDialog({
|
||||
title: `You have 10 active schedules!`,
|
||||
|
||||
description: (
|
||||
<>
|
||||
To encourage organization,{' '}
|
||||
<span className='text-ut-burntorange'>please consider removing some unused schedules</span> you
|
||||
may have.
|
||||
</>
|
||||
),
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
buttons: close => (
|
||||
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||
I Understand
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (enforceScheduleLimit()) {
|
||||
createSchedule('New Schedule');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,29 +19,29 @@ const links: LinkItem[] = [
|
||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
|
||||
},
|
||||
{
|
||||
text: "Fall '24 Course Schedule",
|
||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20249/',
|
||||
text: 'Course Schedule Archives',
|
||||
url: 'https://registrar.utexas.edu/schedules/archive',
|
||||
},
|
||||
// {
|
||||
// text: "Summer '24 Course Schedule",
|
||||
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20246/',
|
||||
// },
|
||||
{
|
||||
text: "'24-'25 Academic Calendar",
|
||||
url: 'https://registrar.utexas.edu/calendars/24-25',
|
||||
},
|
||||
{
|
||||
text: 'Registration Info Sheet',
|
||||
url: 'https://utdirect.utexas.edu/registrar/ris.WBX',
|
||||
},
|
||||
{
|
||||
text: 'Register For Courses',
|
||||
text: 'Register for Courses',
|
||||
url: 'https://utdirect.utexas.edu/registration/chooseSemester.WBX',
|
||||
},
|
||||
{
|
||||
text: 'Degree Audit',
|
||||
url: 'https://utdirect.utexas.edu/apps/degree/audits/',
|
||||
},
|
||||
{
|
||||
text: 'My Registered Courses',
|
||||
url: 'https://utdirect.utexas.edu/registration/classlist.WBX',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CRX_PAGES } from '@shared/types/CRXPages';
|
||||
import { openReportWindow } from '@shared/util/openReportWindow';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
@@ -13,26 +15,41 @@ interface LinkItem {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const links: LinkItem[] = [
|
||||
// {
|
||||
// text: 'Feedback Form',
|
||||
// url: '#',
|
||||
// },
|
||||
// {
|
||||
// text: 'Apply to Longhorn Developers',
|
||||
// url: '#',
|
||||
// },
|
||||
const links = [
|
||||
{
|
||||
text: 'Rate us on Chrome Web Store',
|
||||
url: 'https://chromewebstore.google.com/detail/ut-registration-plus/hboadpjkoaieogjimneceaahlppnipaa',
|
||||
},
|
||||
{
|
||||
text: 'Send us Feedback & Ideas',
|
||||
url: CRX_PAGES.REPORT,
|
||||
},
|
||||
{
|
||||
text: 'Become a Beta Tester',
|
||||
url: 'https://forms.gle/Y9dmQAb1yzW5PRg48',
|
||||
},
|
||||
];
|
||||
{
|
||||
text: 'Credits – Meet the team',
|
||||
url: CRX_PAGES.OPTIONS,
|
||||
},
|
||||
{
|
||||
text: 'Apply to Longhorn Developers',
|
||||
url: 'https://forms.gle/cdkLKmFwPmvHmiBe9',
|
||||
},
|
||||
] as const satisfies LinkItem[];
|
||||
|
||||
/**
|
||||
* The "From The Team" section of the calendar website
|
||||
* @returns
|
||||
*/
|
||||
export default function TeamLinks({ className }: Props): JSX.Element {
|
||||
const handleClick = (link: LinkItem, event: React.MouseEvent) => {
|
||||
if (link.url === CRX_PAGES.REPORT) {
|
||||
event.preventDefault();
|
||||
openReportWindow();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<article className={clsx(className, 'flex flex-col gap-2')}>
|
||||
<Text variant='h3'>From the Team</Text>
|
||||
@@ -43,6 +60,7 @@ export default function TeamLinks({ className }: Props): JSX.Element {
|
||||
className='flex items-center gap-0.5 text-ut-burntorange underline-offset-2 hover:underline'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
onClick={event => handleClick(link, event)}
|
||||
>
|
||||
<Text variant='p'>{link.text}</Text>
|
||||
<OutwardArrowIcon className='h-3 w-3' />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
@@ -14,26 +15,60 @@ export const flagMap = {
|
||||
'Independent Inquiry': 'II',
|
||||
} as const satisfies Record<string, Flag>;
|
||||
|
||||
interface Props {
|
||||
label: Flag;
|
||||
/**
|
||||
* A type that represents the core curriculum aspects that a course can satisfy.
|
||||
*/
|
||||
export type Core = 'ID' | 'C1' | 'HU' | 'GO' | 'HI' | 'SB' | 'MA' | 'N1' | 'N2' | 'VP';
|
||||
export const coreMap = {
|
||||
'First-Year Signature Course': 'ID',
|
||||
'English Composition': 'C1',
|
||||
Humanities: 'HU',
|
||||
'American and Texas Government': 'GO',
|
||||
'U.S. History': 'HI',
|
||||
'Social and Behavioral Sciences': 'SB',
|
||||
'Natural Science and Technology, Part I': 'N1',
|
||||
'Natural Science and Technology, Part II': 'N2',
|
||||
Mathematics: 'MA',
|
||||
'Visual and Performing Arts': 'VP',
|
||||
} as const satisfies Record<string, Core>;
|
||||
|
||||
type Props =
|
||||
| {
|
||||
variant: 'core';
|
||||
label: Core;
|
||||
}
|
||||
| {
|
||||
variant: 'flag';
|
||||
label: Flag;
|
||||
};
|
||||
|
||||
/**
|
||||
* A reusable chip component that follows the design system of the extension.
|
||||
* @returns
|
||||
*/
|
||||
export function Chip({ label }: React.PropsWithChildren<Props>): JSX.Element {
|
||||
const longFlagName = Object.entries(flagMap).find(([full, short]) => short === label)?.[0] ?? label;
|
||||
export function Chip({ variant, label }: React.PropsWithChildren<Props>): JSX.Element {
|
||||
let labelMap;
|
||||
switch (variant) {
|
||||
case 'core':
|
||||
labelMap = coreMap;
|
||||
break;
|
||||
case 'flag':
|
||||
labelMap = flagMap;
|
||||
break;
|
||||
default:
|
||||
labelMap = {};
|
||||
}
|
||||
const longName = Object.entries(labelMap).find(([_full, short]) => short === label)?.[0] ?? label;
|
||||
|
||||
return (
|
||||
<Text
|
||||
as='div'
|
||||
variant='h4'
|
||||
className='min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1.5 py-0.5'
|
||||
style={{
|
||||
backgroundColor: '#FFD600',
|
||||
}}
|
||||
title={`${longFlagName} flag`}
|
||||
className={clsx('min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1.5 py-0.5', {
|
||||
'bg-ut-yellow text-black': variant === 'flag',
|
||||
'bg-ut-blue text-white': variant === 'core',
|
||||
})}
|
||||
title={variant === 'flag' ? `${longName} flag` : `${longName} core curriculum requirement`}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
|
||||
@@ -14,7 +14,7 @@ type Props = TextProps<'a'> & {
|
||||
* A reusable Text component with props that build on top of the design system for the extension
|
||||
*/
|
||||
export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
||||
let { className, href, ...passedProps } = props;
|
||||
const { className, href, ...passedProps } = props;
|
||||
|
||||
if (href && !props.onClick) {
|
||||
passedProps.onClick = e => {
|
||||
@@ -37,7 +37,7 @@ export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
||||
'underline cursor-pointer': !isDisabled,
|
||||
'cursor-not-allowed color-ut-gray': isDisabled,
|
||||
},
|
||||
props.className
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ function MigrationButtons({ close }: { close: () => void }): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{error && (
|
||||
<Text variant='p' className='text-ut-red'>
|
||||
<Text variant='p' className='text-theme-red'>
|
||||
An error occurred while migrating your courses. Please try again later in settings. (
|
||||
{error.substring(0, 8)})
|
||||
</Text>
|
||||
|
||||
@@ -78,8 +78,9 @@ export default function PopupCourseBlock({
|
||||
<DragIndicatorIcon className='h-6 w-6 text-white' />
|
||||
</div>
|
||||
<Text className={clsx('flex-1 py-3.5 truncate', fontColor)} variant='h1-course'>
|
||||
<span className='px-0.5 font-450'>{formattedUniqueId}</span> {course.department} {course.number} –{' '}
|
||||
{course.instructors.length === 0 ? 'Unknown' : course.instructors.map(v => v.lastName)}
|
||||
<span className='px-0.5 font-450'>{formattedUniqueId}</span> {course.department} {course.number}
|
||||
{course.instructors.length > 0 ? <> – </> : ''}
|
||||
{course.instructors.map(v => v.toString({ format: 'last', case: 'capitalize' })).join('; ')}
|
||||
</Text>
|
||||
{enableCourseStatusChips && course.status !== Status.OPEN && (
|
||||
<div
|
||||
|
||||
@@ -4,6 +4,7 @@ import duplicateSchedule from '@pages/background/lib/duplicateSchedule';
|
||||
import renameSchedule from '@pages/background/lib/renameSchedule';
|
||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useEnforceScheduleLimit } from '@views/hooks/useEnforceScheduleLimit';
|
||||
import useSchedules from '@views/hooks/useSchedules';
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
@@ -34,6 +35,12 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
const [editorValue, setEditorValue] = useState(schedule.name);
|
||||
|
||||
const showDialog = usePrompt();
|
||||
const enforceScheduleLimit = useEnforceScheduleLimit();
|
||||
const handleDuplicateSchedule = (scheduleId: string) => {
|
||||
if (enforceScheduleLimit()) {
|
||||
duplicateSchedule(scheduleId);
|
||||
}
|
||||
};
|
||||
|
||||
const editorRef = React.useRef<HTMLInputElement>(null);
|
||||
useEffect(() => {
|
||||
@@ -92,7 +99,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
</Button>
|
||||
<Button
|
||||
variant='filled'
|
||||
color='ut-red'
|
||||
color='theme-red'
|
||||
onClick={() => {
|
||||
close();
|
||||
deleteSchedule(schedule.id);
|
||||
@@ -180,7 +187,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
<Text
|
||||
as='button'
|
||||
variant='small'
|
||||
onClick={() => duplicateSchedule(schedule.id)}
|
||||
onClick={() => handleDuplicateSchedule(schedule.id)}
|
||||
className='w-full rounded bg-transparent p-2 text-left data-[focus]:bg-gray-200/40'
|
||||
>
|
||||
Duplicate
|
||||
@@ -191,7 +198,7 @@ export default function ScheduleListItem({ schedule, dragHandleProps, onClick }:
|
||||
as='button'
|
||||
variant='small'
|
||||
onClick={handleDelete}
|
||||
className='w-full rounded bg-transparent p-2 text-left text-ut-red data-[focus]:bg-red-200/40'
|
||||
className='w-full rounded bg-transparent p-2 text-left text-theme-red data-[focus]:bg-red-200/40'
|
||||
>
|
||||
Delete
|
||||
</Text>
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function ScheduleTotalHoursAndCourses({
|
||||
</Text>
|
||||
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
|
||||
{totalHours} {totalHours === 1 ? 'Hour' : 'Hours'}
|
||||
<Text variant='h4' as='span' className='hidden text-ut-black capitalize screenshot:inline sm:inline'>
|
||||
<Text variant='h4' as='span' className='hidden capitalize screenshot:inline sm:inline'>
|
||||
{totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'}
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
.h1-course {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
font-weight: 550;
|
||||
text-transform: capitalize;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
@@ -211,8 +211,11 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
|
||||
{status === DataStatus.FOUND && (
|
||||
<>
|
||||
<div className='flex flex-wrap content-center items-center self-stretch justify-center gap-3'>
|
||||
<Text variant='small'>
|
||||
Grade Distribution for {course.department} {course.number}
|
||||
<Text variant='small' className='text-ut-black'>
|
||||
Grade Distribution for{' '}
|
||||
<Text variant='small' className='font-extrabold!' as='strong'>
|
||||
{course.department} {course.number}
|
||||
</Text>
|
||||
</Text>
|
||||
<select
|
||||
className='border border rounded border-solid px-3 py-2'
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Course } from '@shared/types/Course';
|
||||
import type Instructor from '@shared/types/Instructor';
|
||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import { Chip, flagMap } from '@views/components/common/Chip';
|
||||
import { Chip, coreMap, flagMap } from '@views/components/common/Chip';
|
||||
import Divider from '@views/components/common/Divider';
|
||||
import Link from '@views/components/common/Link';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
@@ -49,16 +49,13 @@ const capitalizeString = (str: string) => str.charAt(0).toUpperCase() + str.slic
|
||||
* @returns {JSX.Element} The rendered component.
|
||||
*/
|
||||
export default function HeadingAndActions({ course, activeSchedule, onClose }: HeadingAndActionProps): JSX.Element {
|
||||
const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule } = course;
|
||||
const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule, core } = course;
|
||||
const courseAdded = activeSchedule.courses.some(ourCourse => ourCourse.uniqueId === uniqueId);
|
||||
const formattedUniqueId = uniqueId.toString().padStart(5, '0');
|
||||
const isInCalendar = useCalendar();
|
||||
|
||||
const getInstructorFullName = (instructor: Instructor) => {
|
||||
const { firstName = '', lastName = '' } = instructor;
|
||||
if (firstName === '') return capitalizeString(lastName);
|
||||
return `${capitalizeString(firstName)} ${capitalizeString(lastName)}`;
|
||||
};
|
||||
const getInstructorFullName = (instructor: Instructor) =>
|
||||
instructor.toString({ format: 'first_last', case: 'capitalize' });
|
||||
|
||||
const getBuildingUrl = (building: string) =>
|
||||
`https://utdirect.utexas.edu/apps/campus/buildings/nlogon/maps/UTM/${building}`;
|
||||
@@ -95,6 +92,12 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
||||
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=&unique=&instructor_first=${firstName}&instructor_last=${lastName}&course_type=In+Residence&search=Search`;
|
||||
openNewTab({ url });
|
||||
}
|
||||
|
||||
// Show the course's syllabi when no instructors listed
|
||||
if (instructors.length === 0) {
|
||||
const url = `https://utdirect.utexas.edu/apps/student/coursedocs/nlogon/?year=&semester=&department=${department}&course_number=${courseNumber}&course_title=&unique=&instructor_first=&instructor_last=&course_type=In+Residence&search=Search`;
|
||||
openNewTab({ url });
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddOrRemoveCourse = async () => {
|
||||
@@ -119,13 +122,13 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
||||
<Button color='ut-burntorange' variant='single' icon={Copy} onClick={handleCopy}>
|
||||
{formattedUniqueId}
|
||||
</Button>
|
||||
<button className='bg-transparent p-0 text-theme-black btn' onClick={onClose}>
|
||||
<button className='bg-transparent p-0 text-ut-black btn' onClick={onClose}>
|
||||
<CloseIcon className='h-7 w-7' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
{instructors.length > 0 && (
|
||||
<Text variant='h4' as='p' className='items-center justify-center'>
|
||||
{instructors.length > 0 ? (
|
||||
<Text variant='h4' as='p'>
|
||||
with{' '}
|
||||
{instructors
|
||||
.map(instructor => (
|
||||
@@ -140,12 +143,24 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
||||
))
|
||||
.flatMap((el, i) => (i === 0 ? [el] : [', ', el]))}
|
||||
</Text>
|
||||
) : (
|
||||
<Text variant='h4' as='p'>
|
||||
(No instructor has been provided)
|
||||
</Text>
|
||||
)}
|
||||
<div className='flex items-center gap-1'>
|
||||
{flags.map((flag: string) => (
|
||||
<Chip
|
||||
key={flagMap[flag as keyof typeof flagMap]}
|
||||
label={flagMap[flag as keyof typeof flagMap]}
|
||||
variant='flag'
|
||||
/>
|
||||
))}
|
||||
{core.map((coreVal: string) => (
|
||||
<Chip
|
||||
key={coreMap[coreVal as keyof typeof coreMap]}
|
||||
label={coreMap[coreVal as keyof typeof coreMap]}
|
||||
variant='core'
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -199,10 +214,22 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
|
||||
}}
|
||||
/>
|
||||
<Divider size='1.75rem' orientation='vertical' />
|
||||
<Button variant='outline' color='ut-blue' icon={Reviews} onClick={handleOpenRateMyProf}>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='ut-blue'
|
||||
icon={Reviews}
|
||||
onClick={handleOpenRateMyProf}
|
||||
disabled={instructors.length === 0}
|
||||
>
|
||||
RateMyProf
|
||||
</Button>
|
||||
<Button variant='outline' color='ut-teal' icon={Mood} onClick={handleOpenCES}>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='ut-teal'
|
||||
icon={Mood}
|
||||
onClick={handleOpenCES}
|
||||
disabled={instructors.length === 0}
|
||||
>
|
||||
CES
|
||||
</Button>
|
||||
<Button variant='outline' color='ut-orange' icon={Description} onClick={handleOpenPastSyllabi}>
|
||||
|
||||
@@ -79,11 +79,11 @@ const useDevMode = (targetCount: number): [boolean, () => void] => {
|
||||
* @returns The Settings component.
|
||||
*/
|
||||
export default function Settings(): JSX.Element {
|
||||
const [enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||
const [showTimeLocation, setShowTimeLocation] = useState<boolean>(false);
|
||||
const [_enableCourseStatusChips, setEnableCourseStatusChips] = useState<boolean>(false);
|
||||
const [_showTimeLocation, setShowTimeLocation] = useState<boolean>(false);
|
||||
const [highlightConflicts, setHighlightConflicts] = useState<boolean>(false);
|
||||
const [loadAllCourses, setLoadAllCourses] = useState<boolean>(false);
|
||||
const [enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||
const [_enableDataRefreshing, setEnableDataRefreshing] = useState<boolean>(false);
|
||||
|
||||
const showMigrationDialog = useMigrationDialog();
|
||||
|
||||
@@ -216,6 +216,7 @@ export default function Settings(): JSX.Element {
|
||||
try {
|
||||
response = await fetch(link);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(`Failed to fetch url '${link}'`);
|
||||
return;
|
||||
}
|
||||
@@ -410,7 +411,7 @@ export default function Settings(): JSX.Element {
|
||||
</div>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='ut-red'
|
||||
color='theme-red'
|
||||
icon={DeleteForeverIcon}
|
||||
onClick={handleEraseAll}
|
||||
>
|
||||
@@ -427,7 +428,7 @@ export default function Settings(): JSX.Element {
|
||||
</div>
|
||||
<Text
|
||||
variant='h2-course'
|
||||
className={clsx('text-center text-ut-red !font-normal', {
|
||||
className={clsx('text-center text-theme-red !font-normal', {
|
||||
'line-through': highlightConflicts,
|
||||
})}
|
||||
>
|
||||
@@ -489,7 +490,7 @@ export default function Settings(): JSX.Element {
|
||||
<p className='text-xs text-ut-green'>
|
||||
{githubStats.adminGitHubStats[admin.githubUsername]?.linesAdded} ++
|
||||
</p>
|
||||
<p className='text-xs text-ut-red'>
|
||||
<p className='text-xs text-theme-red'>
|
||||
{githubStats.adminGitHubStats[admin.githubUsername]?.linesDeleted} --
|
||||
</p>
|
||||
</div>
|
||||
@@ -499,9 +500,13 @@ export default function Settings(): JSX.Element {
|
||||
</div>
|
||||
</section>
|
||||
<section className='my-8'>
|
||||
<h2 className='mb-4 text-xl text-ut-black font-semibold'>UTRP CONTRIBUTERS</h2>
|
||||
<h2 className='mb-4 text-xl text-ut-black font-semibold'>UTRP CONTRIBUTORS</h2>
|
||||
<div className='grid grid-cols-2 gap-4 2xl:grid-cols-4 md:grid-cols-3 xl:grid-cols-3'>
|
||||
{LONGHORN_DEVELOPERS_SWE.map(swe => (
|
||||
{LONGHORN_DEVELOPERS_SWE.sort(
|
||||
(a, b) =>
|
||||
(githubStats?.userGitHubStats[b.githubUsername]?.commits ?? 0) -
|
||||
(githubStats?.userGitHubStats[a.githubUsername]?.commits ?? 0)
|
||||
).map(swe => (
|
||||
<div
|
||||
key={swe.githubUsername}
|
||||
className='border border-gray-300 rounded bg-ut-gray/10 p-4'
|
||||
@@ -531,7 +536,7 @@ export default function Settings(): JSX.Element {
|
||||
<p className='text-xs text-ut-green'>
|
||||
{githubStats.userGitHubStats[swe.githubUsername]?.linesAdded} ++
|
||||
</p>
|
||||
<p className='text-xs text-ut-red'>
|
||||
<p className='text-xs text-theme-red'>
|
||||
{githubStats.userGitHubStats[swe.githubUsername]?.linesDeleted} --
|
||||
</p>
|
||||
</div>
|
||||
@@ -546,6 +551,11 @@ export default function Settings(): JSX.Element {
|
||||
admin => admin.githubUsername === username
|
||||
) && !LONGHORN_DEVELOPERS_SWE.some(swe => swe.githubUsername === username)
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(githubStats.userGitHubStats[b]?.commits ?? 0) -
|
||||
(githubStats.userGitHubStats[a]?.commits ?? 0)
|
||||
)
|
||||
.map(username => (
|
||||
<div
|
||||
key={username}
|
||||
@@ -574,7 +584,7 @@ export default function Settings(): JSX.Element {
|
||||
<p className='text-xs text-ut-green'>
|
||||
{githubStats.userGitHubStats[username]?.linesAdded} ++
|
||||
</p>
|
||||
<p className='text-xs text-ut-red'>
|
||||
<p className='text-xs text-theme-red'>
|
||||
{githubStats.userGitHubStats[username]?.linesDeleted} --
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -65,8 +65,9 @@ export default function SentryProvider({
|
||||
integrations,
|
||||
transport: makeFetchTransport,
|
||||
stackParser: defaultStackParser,
|
||||
// debug: true,
|
||||
debug: import.meta.env.DEV,
|
||||
release: import.meta.env.VITE_PACKAGE_VERSION,
|
||||
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT,
|
||||
};
|
||||
|
||||
let client: Client;
|
||||
|
||||
45
src/views/hooks/useEnforceScheduleLimit.tsx
Normal file
45
src/views/hooks/useEnforceScheduleLimit.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import useSchedules from '@views/hooks/useSchedules';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Button } from '../components/common/Button';
|
||||
import { usePrompt } from '../components/common/DialogProvider/DialogProvider';
|
||||
|
||||
const SCHEDULE_LIMIT = 10;
|
||||
|
||||
/**
|
||||
* Hook that creates a function that enforces a maximum amount of schedules
|
||||
*
|
||||
* If a new schedule can be created without exceeding the limit, the function returns true
|
||||
* Otherwise, display a prompt explaining the limit, and returns false
|
||||
*
|
||||
* @returns a function, () => boolean
|
||||
*/
|
||||
export function useEnforceScheduleLimit(): () => boolean {
|
||||
const [, schedules] = useSchedules();
|
||||
const showDialog = usePrompt();
|
||||
|
||||
return useCallback(() => {
|
||||
if (schedules.length >= SCHEDULE_LIMIT) {
|
||||
showDialog({
|
||||
title: `You have ${SCHEDULE_LIMIT} active schedules!`,
|
||||
|
||||
description: (
|
||||
<>
|
||||
To encourage organization,{' '}
|
||||
<span className='text-ut-burntorange'>please consider removing some unused schedules</span> you
|
||||
may have.
|
||||
</>
|
||||
),
|
||||
|
||||
buttons: close => (
|
||||
<Button variant='filled' color='ut-burntorange' onClick={close}>
|
||||
I Understand
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, [schedules, showDialog]);
|
||||
}
|
||||
@@ -95,9 +95,11 @@ function extractCourseInfo(course: Course) {
|
||||
|
||||
let courseDeptAndInstr = `${course.department} ${course.number}`;
|
||||
|
||||
const mainInstructor = course.instructors[0];
|
||||
if (mainInstructor) {
|
||||
courseDeptAndInstr += ` – ${mainInstructor.toString({ format: 'first_last', case: 'capitalize' })}`;
|
||||
if (course.instructors.length > 0) {
|
||||
courseDeptAndInstr += ' \u2013 ';
|
||||
courseDeptAndInstr += course.instructors
|
||||
.map(instructor => instructor.toString({ format: 'last', case: 'capitalize' }))
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
return { status, courseDeptAndInstr, meetings, course };
|
||||
|
||||
@@ -19,10 +19,9 @@ const TableDataSelector = {
|
||||
SCHEDULE_HOURS: 'td[data-th="Hour"]>span',
|
||||
SCHEDULE_LOCATION: 'td[data-th="Room"]>span',
|
||||
FLAGS: 'td[data-th="Flags"] ul li',
|
||||
CORE_CURRICULUM: 'td[data-th="Core"] ul li',
|
||||
} as const satisfies Record<string, string>;
|
||||
|
||||
type TableDataSelectorType = (typeof TableDataSelector)[keyof typeof TableDataSelector];
|
||||
|
||||
/**
|
||||
* The selectors that we use to scrape the course details page for an individual course (https://utdirect.utexas.edu/apps/registrar/course_schedule/20239/52700/)
|
||||
*/
|
||||
@@ -31,8 +30,6 @@ const DetailsSelector = {
|
||||
COURSE_DESCRIPTION: '#details p',
|
||||
} as const;
|
||||
|
||||
type DetailsSelectorType = (typeof DetailsSelector)[keyof typeof DetailsSelector];
|
||||
|
||||
/**
|
||||
* A class that allows us to scrape information from UT's course catalog to create our internal representation of a course
|
||||
*/
|
||||
@@ -99,6 +96,7 @@ export class CourseCatalogScraper {
|
||||
semester: this.getSemester(),
|
||||
scrapedAt: Date.now(),
|
||||
colors: getCourseColors('emerald', 500),
|
||||
core: this.getCore(row),
|
||||
});
|
||||
courses.push({
|
||||
element: row,
|
||||
@@ -337,6 +335,21 @@ export class CourseCatalogScraper {
|
||||
return Array.from(lis).map(li => li.textContent || '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of core curriculum requirements the course satisfies
|
||||
* @param row
|
||||
* @returns an array of core curriculum codes
|
||||
*/
|
||||
getCore(row: HTMLTableRowElement): string[] {
|
||||
const lis = row.querySelectorAll(TableDataSelector.CORE_CURRICULUM);
|
||||
return (
|
||||
Array.from(lis)
|
||||
// ut schedule is weird and puts a blank core curriculum element even if there aren't any core requirements so filter those out
|
||||
.filter(li => li.getAttribute('title') !== ' core curriculum requirement')
|
||||
.map(li => li.textContent || '')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will scrape all the time information from the course catalog table row and return it as a CourseSchedule object, which represents all of the meeting timiestimes/places of the course.
|
||||
* @param row the row of the course catalog table
|
||||
|
||||
@@ -43,11 +43,11 @@ export const LONGHORN_DEVELOPERS_ADMINS = [
|
||||
{ name: 'Diego Perez', role: 'Staff Engineer', githubUsername: 'doprz' },
|
||||
{ name: 'Lukas Zenick', role: 'Senior Software Engineer', githubUsername: 'Lukas-Zenick' },
|
||||
{ name: 'Isaiah Rodriguez', role: 'Chief Operations and Design Officer', githubUsername: 'IsaDavRod' },
|
||||
{ name: 'Samuel Gunter', role: 'Senior Software Engineer', githubUsername: 'Samathingamajig' },
|
||||
{ name: 'Derek Chen', role: 'Senior Software Engineer', githubUsername: 'DereC4' },
|
||||
] as const satisfies TeamMember[];
|
||||
|
||||
export const LONGHORN_DEVELOPERS_SWE = [
|
||||
{ name: 'Samuel Gunter', role: 'Software Engineer', githubUsername: 'Samathingamajig' },
|
||||
{ name: 'Derek Chen', role: 'Software Engineer', githubUsername: 'DereC4' },
|
||||
{ name: 'Casey Charleston', role: 'Software Engineer', githubUsername: 'caseycharleston' },
|
||||
{ name: 'Vinson', role: 'Software Engineer', githubUsername: 'vinsonzheng499' },
|
||||
{ name: 'Vivek', role: 'Software Engineer', githubUsername: 'vivek12311' },
|
||||
|
||||
@@ -10,7 +10,7 @@ $turquoise: #00a9b7;
|
||||
$bluebonnet: #005f86;
|
||||
$shade: #9cadb7;
|
||||
$limestone: #d6d2c4;
|
||||
$speedway_brick: #af2e2d;
|
||||
$speedway_brick: #d10000;
|
||||
|
||||
//scss hover active focus color calculation
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ if (isBeta) {
|
||||
process.env.VITE_BETA_BUILD = 'true';
|
||||
}
|
||||
process.env.VITE_PACKAGE_VERSION = packageJson.version;
|
||||
if (process.env.PROD) {
|
||||
process.env.VITE_SENTRY_ENVIRONMENT = 'production';
|
||||
} else if (isBeta) {
|
||||
process.env.VITE_SENTRY_ENVIRONMENT = 'beta';
|
||||
} else {
|
||||
process.env.VITE_SENTRY_ENVIRONMENT = 'development';
|
||||
}
|
||||
|
||||
export const preambleCode = `
|
||||
import RefreshRuntime from "__BASE__@react-refresh"
|
||||
@@ -89,10 +96,13 @@ export default defineConfig({
|
||||
apply: 'serve',
|
||||
transform(code, id) {
|
||||
if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
|
||||
return code.replace(
|
||||
return {
|
||||
code: code.replace(
|
||||
/(['"])(\/public\/.*?)(['"])/g,
|
||||
(_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
|
||||
);
|
||||
),
|
||||
map: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -101,10 +111,13 @@ export default defineConfig({
|
||||
apply: 'build',
|
||||
transform(code, id) {
|
||||
if (id.endsWith('.tsx') || id.endsWith('.ts') || id.endsWith('?url')) {
|
||||
return code.replace(
|
||||
return {
|
||||
code: code.replace(
|
||||
/(['"])(__VITE_ASSET__.*?__)(['"])/g,
|
||||
(_, quote1, path, quote2) => `chrome.runtime.getURL(${quote1}${path}${quote2})`
|
||||
);
|
||||
),
|
||||
map: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -114,13 +127,16 @@ export default defineConfig({
|
||||
enforce: 'post',
|
||||
transform(code, id) {
|
||||
if (process.env.NODE_ENV === 'development' && (id.endsWith('.css') || id.endsWith('.scss'))) {
|
||||
return code.replace(
|
||||
return {
|
||||
code: code.replace(
|
||||
/url\((.*?)\)/g,
|
||||
(_, path) =>
|
||||
`url(\\"" + chrome.runtime.getURL(${path
|
||||
.replaceAll(`\\"`, `"`)
|
||||
.replace(/public\//, '')}) + "\\")`
|
||||
);
|
||||
),
|
||||
map: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -133,9 +149,8 @@ export default defineConfig({
|
||||
/(__VITE_ASSET__.*?__)/g,
|
||||
(_, path) => `chrome-extension://__MSG_@@extension_id__${path}`
|
||||
);
|
||||
return transformedCode;
|
||||
return { code: transformedCode, map: null };
|
||||
}
|
||||
return code;
|
||||
},
|
||||
},
|
||||
renameFile('src/pages/debug/index.html', 'debug.html'),
|
||||
@@ -143,7 +158,7 @@ export default defineConfig({
|
||||
renameFile('src/pages/calendar/index.html', 'calendar.html'),
|
||||
renameFile('src/pages/report/index.html', 'report.html'),
|
||||
vitePluginRunCommandOnDemand({
|
||||
afterServerStart: 'pnpm gulp forceDisableUseDynamicUrl',
|
||||
// afterServerStart: 'pnpm gulp forceDisableUseDynamicUrl',
|
||||
closeBundle: 'pnpm gulp forceDisableUseDynamicUrl',
|
||||
}),
|
||||
],
|
||||
@@ -206,4 +221,11 @@ export default defineConfig({
|
||||
provider: 'v8',
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user