Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/assets/insideJokes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const splashText: string[] = [
`It's ${new Date().toLocaleString('en-US', { month: 'long', day: 'numeric' })} and OU still sucks`,
'As seen on TV! ',
"Should you major in Compsci? well, here's a better question. do you wanna have a bad time?",
"https://i.redd.it/4y3fc2bzva1e1.gif",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not related to this PR in addition that we shouldn't include random links. Please remove.

];

export default splashText;
67 changes: 67 additions & 0 deletions src/pages/background/handler/courseStatusChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { UserScheduleStore } from "src/shared/storage/UserScheduleStore";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use path alias.

import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
import { Course, StatusType } from '@shared/types/Course';
import { resolve } from "path";


export default async function checkCourseStatusChanges() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs jsdoc.

const activeIndex = await UserScheduleStore.get('activeIndex');
const schedules = await UserScheduleStore.get('schedules');
const currentSchedule = schedules[activeIndex]?.courses;


if (!currentSchedule || currentSchedule.length === 0) return;

let newStatus = currentSchedule[0]?.status;
for (const course of currentSchedule) {
// Get the latest status from the course catalog or API
// You might need to import your CourseCatalogScraper here
const uniqueId = course.uniqueId;
// gets the new status
// console.log(uniqueId, 'UNIQUE ID');
try {
const response = await fetch(`https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/${uniqueId}/`);
const html = await response.text();

const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const scraper = new CourseCatalogScraper('COURSE_CATALOG_LIST', doc);
const rows = doc.querySelectorAll('tr');
rows.forEach(row => {
try {
const id = scraper.getUniqueId(row);
if (id && id === uniqueId) {
const scrapedStatus = scraper.getStatus(row)[0];
newStatus = scrapedStatus as StatusType;
Comment on lines +34 to +35
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we should have some validation here.

}
console.log('Unique ID:', uniqueId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have some further context if needed for logging. Otherwise this is just verbose and should be removed.

} catch (error) {
console.error(error);
}
});

}
catch (error) {
console.error(`Failed to check status for course ${uniqueId}:`, error);
}

if (newStatus && newStatus !== currentSchedule[0]?.status) {
const updatedCourses = currentSchedule.map(c => {
if (c.uniqueId === uniqueId) {
return { ...c, status: newStatus } as Course;
}
return c as Course;
Comment on lines +50 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try to prevent the use of as in TS as it's equivalent to saying that we know better than TS. Instead let's use satisfies, type narrowing, and validation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Course is a class and it has associated instance methods, it's not just a type/interface. Any use of as or satisfies is wrong here, use the Course constructor

});
console.log(updatedCourses);
const updatedSchedules = [...schedules];
console.log("UPDATED", updatedSchedules)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit verbose. Let's refactor this to be both helpful for logging and brief.

{
...updatedSchedules[activeIndex],
courses: updatedCourses,
};


await UserScheduleStore.set('schedules', updatedSchedules);
}
}
}
108 changes: 108 additions & 0 deletions src/pages/background/handler/userScheduleHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,40 @@ import switchSchedule from '@pages/background/lib/switchSchedule';
import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import { Course } from '@shared/types/Course';
import type { MessageHandler } from 'chrome-extension-toolkit';
import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore';
import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper';
import { StatusType } from '@shared/types/Course';
import { Serialized } from 'chrome-extension-toolkit';


const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
addCourse({ data, sendResponse }) {
checkCourseStatusChanges();
addCourse(data.scheduleId, new Course(data.course)).then(sendResponse);
},
removeCourse({ data, sendResponse }) {
console.log("TEST")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra console.log

checkCourseStatusChanges();
removeCourse(data.scheduleId, new Course(data.course)).then(sendResponse);
},
clearCourses({ data, sendResponse }) {
checkCourseStatusChanges();
clearCourses(data.scheduleId).then(sendResponse);
},
switchSchedule({ data, sendResponse }) {
checkCourseStatusChanges();
switchSchedule(data.scheduleId).then(sendResponse);
},
createSchedule({ data, sendResponse }) {
checkCourseStatusChanges();
createSchedule(data.scheduleName).then(sendResponse);
},
deleteSchedule({ data, sendResponse }) {
checkCourseStatusChanges();
deleteSchedule(data.scheduleId).then(sendResponse);
},
renameSchedule({ data, sendResponse }) {
checkCourseStatusChanges();
renameSchedule(data.scheduleId, data.newName).then(sendResponse);
},
// proxy so we can add courses
Expand All @@ -42,4 +55,99 @@ const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
},
};

async function checkCourseStatusChanges() {
const activeIndex = await UserScheduleStore.get('activeIndex');
const schedules = await UserScheduleStore.get('schedules');
const currentSchedule = schedules[activeIndex]?.courses;


if (!currentSchedule || currentSchedule.length === 0) return;

let newStatus = currentSchedule[0]?.status;
for (const course of currentSchedule) {
// Get the latest status from the course catalog or API
// You might need to import your CourseCatalogScraper here
const uniqueId = course.uniqueId;
// gets the new status
try {
const response = await fetch(`https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/${uniqueId}/`);
const html = await response.text();

// Parse the HTML using DOMParser
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');

const scraper = new CourseCatalogScraper('COURSE_CATALOG_LIST', doc);
const rows = Array.from(doc.querySelectorAll('tr'));

for (const row of rows) {
const id = scraper.getUniqueId(row);
if (id === uniqueId) {
const scrapedStatus = scraper.getStatus(row)[0];
newStatus = scrapedStatus as StatusType;
}
}

}
catch (error) {
console.error(`Failed to check status for course ${uniqueId}:`, error);
}

if (newStatus && newStatus !== currentSchedule[0]?.status) {
const updatedCourses = currentSchedule.map(c => {
if (c.uniqueId === uniqueId) {
return { ...c, status: newStatus } as Serialized<Course>; // Explicitly cast here
}
return c as Serialized<Course>; // Ensure the rest match expected type
});

const updatedSchedules = [...schedules];
// updatedSchedules[activeIndex] = {
// ...updatedSchedules[activeIndex],
// courses: updatedCourses,
// };


await UserScheduleStore.set('schedules', updatedSchedules);
}


// if (updatedCourseInfo && updatedCourseInfo.status !== course.status) {
// // Status has changed
// const updatedCourses = currentSchedule.map(c =>
// c.uniqueNumber === course.uniqueNumber
// ? { ...c, status: updatedCourseInfo.status }
// : c
// );

// // Update the store with new course status
// const updatedSchedules = [...schedules];
// updatedSchedules[activeIndex] = {
// ...schedules[activeIndex],
// courses: updatedCourses
// };

// await UserScheduleStore.set('schedules', updatedSchedules);

// // Notify user of status change
// chrome.notifications.create({
// type: 'basic',
// title: 'Course Status Change',
// message: `${course.courseName} status changed from ${course.status} to ${updatedCourseInfo.status}`,
// iconUrl: '/icons/icon48.png' // Make sure this path is correct
//});
}
}
Comment on lines +58 to +140
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is defined previously.


// You'll need to implement this function to fetch the latest course status
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write a proper jsdoc that uses tsdoc standards.

async function fetchLatestCourseStatus(course: Course) {
// Implement the logic to fetch the latest course status
// This might involve using your CourseCatalogScraper or making an API call
// Return the updated course information
return null; // Replace with actual implementation
}
Comment on lines +143 to +148
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO





export default userScheduleHandler;
2 changes: 1 addition & 1 deletion src/shared/storage/OptionsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface IOptionsStore {
}

export const OptionsStore = createSyncStore<IOptionsStore>({
enableCourseStatusChips: false,
enableCourseStatusChips: true, // true for dev purposes, will make switch in settings - ali v
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: remove comment later

enableTimeAndLocationInPopup: false,
enableHighlightConflicts: true,
enableScrollToLoad: true,
Expand Down
7 changes: 6 additions & 1 deletion src/views/components/calendar/CalendarCourseCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export default function CalendarCourseCell({

useEffect(() => {
initSettings().then(({ enableCourseStatusChips }) => setEnableCourseStatusChips(enableCourseStatusChips));

console.log("useEffect");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug console.log. Should be removed later on.

initSettings().then((res) => {
console.log(res);
})
Comment on lines +49 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => {
setEnableCourseStatusChips(newValue);
// console.log('enableCourseStatusChips', newValue);
Expand All @@ -57,6 +60,8 @@ export default function CalendarCourseCell({
}, []);

let rightIcon: React.ReactNode | null = null;
console.log("enabledCourseStatusChips", enableCourseStatusChips);
console.log("status", status)
Comment on lines +63 to +64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug console.log. Should be removed later on.

if (enableCourseStatusChips) {
if (status === Status.WAITLISTED) {
rightIcon = <WaitlistIcon className='h-5 w-5' />;
Expand Down
12 changes: 9 additions & 3 deletions src/views/components/calendar/CalenderHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotal
import Text from '@views/components/common/Text/Text';
import useSchedules from '@views/hooks/useSchedules';
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';

import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';

import React, { useEffect, useState } from 'react';

import MenuIcon from '~icons/material-symbols/menu';
// import RefreshIcon from '~icons/material-symbols/refresh';
import RefreshIcon from '~icons/material-symbols/refresh';
import SettingsIcon from '~icons/material-symbols/settings';
import checkCourseStatusChanges from 'src/pages/background/handler/courseStatusChange';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use path alias.


/**
* Opens the options page in a new tab.
Expand Down Expand Up @@ -81,9 +84,12 @@ export default function CalendarHeader({ onSidebarToggle }: CalendarHeaderProps)
<Text variant='mini' className='text-nowrap text-ut-gray font-normal!'>
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
</Text>
{/* <button className='inline-block h-4 w-4 bg-transparent p-0 btn'>
<button
className='inline-block h-4 w-4 bg-transparent p-0 btn'
onClick={async () => await checkCourseStatusChanges()}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. I would extract this into a dedicated function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is this any different from onClick={checkCourseStatusChanges}?

>
<RefreshIcon className='h-4 w-4 animate-duration-800 text-ut-black' />
</button> */}
</button>
</div>
)}
</div>
Expand Down
25 changes: 23 additions & 2 deletions src/views/components/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const useDevMode = (targetCount: number): [boolean, () => void] => {
* @returns The Settings component.
*/
export default function Settings(): JSX.Element {
const [_enableCourseStatusChips, setEnableCourseStatusChips] = 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);
Expand All @@ -101,6 +101,9 @@ export default function Settings(): JSX.Element {
const showDialog = usePrompt();
const handleChangelogOnClick = useChangelog();

useEffect(() => {
console.log(enableCourseStatusChips, "settings");
},[enableCourseStatusChips])
Comment on lines +104 to +106
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug console.log. Should be removed later on.

useEffect(() => {
const fetchGitHubStats = async () => {
try {
Expand Down Expand Up @@ -140,7 +143,7 @@ export default function Settings(): JSX.Element {
// Listen for changes in the settings
const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => {
setEnableCourseStatusChips(newValue);
// console.log('enableCourseStatusChips', newValue);
console.log('enableCourseStatusChips', newValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug console.log. Should be removed later on.

});

const l2 = OptionsStore.listen('enableTimeAndLocationInPopup', async ({ newValue }) => {
Expand Down Expand Up @@ -364,6 +367,24 @@ export default function Settings(): JSX.Element {

<Divider size='auto' orientation='horizontal' />

<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<h3 className='text-ut-burntorange font-semibold'>Show Course Status</h3>
<p className='text-sm text-gray-600'>
Shows an indicator for waitlisted, cancelled, and closed courses.
</p>
</div>
<SwitchButton
isChecked={enableCourseStatusChips}
onChange={() => {
setEnableCourseStatusChips(!enableCourseStatusChips);
OptionsStore.set('enableCourseStatusChips', !enableCourseStatusChips);
}}
/>
</div>

<Divider size='auto' orientation='horizontal' />

<div className='flex items-center justify-between'>
<div className='max-w-xs'>
<Text variant='h4' className='text-ut-burntorange font-semibold'>
Expand Down
2 changes: 1 addition & 1 deletion src/views/lib/CourseCatalogScraper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export class CourseCatalogScraper {
*/
getInstructionMode(row: HTMLTableRowElement): InstructionMode {
const text = (row.querySelector(TableDataSelector.INSTRUCTION_MODE)?.textContent || '').toLowerCase();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random indentation.

if (text.includes('internet')) {
return 'Online';
}
Expand Down
Loading