Skip to content

Commit fc07bec

Browse files
committed
fix(injected): UTRP-41: MyUT "Add all courses" button
1 parent edf20f5 commit fc07bec

2 files changed

Lines changed: 73 additions & 11 deletions

File tree

src/views/components/injected/AddAllButton.tsx

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { addCourseByURL } from '@pages/background/lib/addCourseByURL';
2+
import { ArrowsClockwise, Check } from '@phosphor-icons/react';
23
import { background } from '@shared/messages';
34
import { Button } from '@views/components/common/Button';
45
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
5-
import useSchedules from '@views/hooks/useSchedules';
6+
import { getScheduleById, switchSchedule } from '@views/hooks/useSchedules';
67
import { useEffect, useState } from 'react';
78
import ReactDOM from 'react-dom';
9+
import createSchedule from 'src/pages/background/lib/createSchedule';
810

911
/**
1012
* InjectedButton component renders a button that adds courses to UTRP from official MyUT calendar
@@ -14,13 +16,17 @@ import ReactDOM from 'react-dom';
1416
*/
1517
export default function InjectedButton(): JSX.Element | null {
1618
const [container, setContainer] = useState<HTMLDivElement | null>(null);
17-
const [activeSchedule, _] = useSchedules();
19+
const [importStatus, setImportStatus] = useState<'idle' | 'loading' | 'complete'>('idle');
1820

1921
const extractCoursesFromCalendar = async () => {
20-
const calendarElement = document.querySelector('#kgoui_Rcontent_I3_Rprimary_I1_Rcontent_I1_Rcontent_I0_Ritems');
22+
setImportStatus('loading');
23+
24+
const tableElements = document.querySelectorAll('table');
25+
const calendarElement = Array.from(tableElements).find(el => el.textContent?.includes('12PM'));
2126

2227
if (!calendarElement) {
2328
console.error('Calendar element not found');
29+
setImportStatus('idle');
2430
return [];
2531
}
2632

@@ -36,20 +42,55 @@ export default function InjectedButton(): JSX.Element | null {
3642
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/utrp_login/',
3743
});
3844

39-
if (loggedInToUT) {
40-
for (const a of uniqueAnchorTags) {
41-
await addCourseByURL(activeSchedule, a);
45+
try {
46+
if (loggedInToUT) {
47+
const scheduleId = await createSchedule('Imported Schedule');
48+
switchSchedule(scheduleId);
49+
const newSchedule = await getScheduleById(scheduleId);
50+
if (!newSchedule) {
51+
throw new Error('Failed to retrieve new schedule');
52+
}
53+
54+
for (const a of uniqueAnchorTags) {
55+
await addCourseByURL(newSchedule, a);
56+
}
57+
58+
setImportStatus('complete');
59+
return;
4260
}
43-
} else {
61+
4462
// We'll allow the alert for this WIP feature
4563
window.alert('Logged into UT Registrar.');
64+
} catch (error) {
65+
console.error('Failed to import courses from calendar', error);
4666
}
67+
68+
setImportStatus('idle');
4769
};
4870

71+
const buttonText =
72+
importStatus === 'loading'
73+
? 'Importing...'
74+
: importStatus === 'complete'
75+
? 'Imported!'
76+
: 'Add Courses to UT Registration+';
77+
78+
const buttonIcon = importStatus === 'loading' ? ArrowsClockwise : importStatus === 'complete' ? Check : undefined;
79+
80+
const buttonIconProps =
81+
importStatus === 'loading'
82+
? {
83+
className: 'animate-spin animate-duration-800',
84+
}
85+
: undefined;
86+
4987
useEffect(() => {
50-
const targetElement = document.getElementById('kgoui_Rcontent_I3_Rsecondary');
88+
const targetElements = document.querySelectorAll(
89+
'div.kgoui_object_region.kgoui_container_responsive_asymmetric2_column_secondary'
90+
);
91+
const targetElement = Array.from(targetElements).find(el => el.textContent?.includes('My Information'));
5192

52-
if (targetElement?.classList.contains('kgoui_container_responsive_asymmetric2_column_secondary')) {
93+
if (targetElement) {
5394
const buttonContainer = document.createElement('div');
5495
targetElement.appendChild(buttonContainer);
5596
setContainer(buttonContainer);
@@ -66,8 +107,16 @@ export default function InjectedButton(): JSX.Element | null {
66107

67108
return ReactDOM.createPortal(
68109
<ExtensionRoot>
69-
<Button variant='filled' color='ut-burntorange' onClick={extractCoursesFromCalendar}>
70-
Add Courses to UT Registration+
110+
<Button
111+
variant='filled'
112+
color='ut-burntorange'
113+
className='flex!'
114+
onClick={extractCoursesFromCalendar}
115+
disabled={importStatus !== 'idle'}
116+
icon={buttonIcon}
117+
iconProps={buttonIconProps}
118+
>
119+
{buttonText}
71120
</Button>
72121
</ExtensionRoot>,
73122
container

src/views/hooks/useSchedules.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ export function getActiveSchedule(): UserSchedule {
7979
return schedulesCache[activeIndexCache] ?? errorSchedule;
8080
}
8181

82+
/**
83+
* Returns the schedule with the specified id.
84+
* @param id - The id of the schedule to retrieve.
85+
* @returns The schedule with the specified id, or undefined if not found.
86+
*/
87+
export async function getScheduleById(id: string): Promise<UserSchedule | undefined> {
88+
const schedules = await UserScheduleStore.get('schedules');
89+
const found = schedules.find(s => s.id === id);
90+
if (!found) return undefined;
91+
// todo: this is jank
92+
return new UserSchedule(found);
93+
}
94+
8295
/**
8396
* Replaces the old schedule with the new schedule.
8497
* @param oldSchedule - The old schedule to be replaced.

0 commit comments

Comments
 (0)