diff --git a/src/shared/storage/OptionsStore.ts b/src/shared/storage/OptionsStore.ts index 33291ead..d0a66634 100644 --- a/src/shared/storage/OptionsStore.ts +++ b/src/shared/storage/OptionsStore.ts @@ -24,6 +24,8 @@ export interface IOptionsStore { /** whether the promo should be shown */ showUTDiningPromo: boolean; + /** whether users are allowed to bypass the 10 schedule limit */ + allowMoreSchedules: boolean; } export const OptionsStore = createSyncStore({ @@ -34,6 +36,7 @@ export const OptionsStore = createSyncStore({ alwaysOpenCalendarInNewTab: false, showCalendarSidebar: true, showUTDiningPromo: true, + allowMoreSchedules: false, }); /** @@ -50,6 +53,7 @@ export const initSettings = async () => alwaysOpenCalendarInNewTab: await OptionsStore.get('alwaysOpenCalendarInNewTab'), showCalendarSidebar: await OptionsStore.get('showCalendarSidebar'), showUTDiningPromo: await OptionsStore.get('showUTDiningPromo'), + allowMoreSchedules: await OptionsStore.get('allowMoreSchedules'), }) satisfies IOptionsStore; // Clothing retailer right diff --git a/src/views/components/settings/Settings.tsx b/src/views/components/settings/Settings.tsx index a52fb1a9..f6218d3b 100644 --- a/src/views/components/settings/Settings.tsx +++ b/src/views/components/settings/Settings.tsx @@ -92,6 +92,7 @@ export default function Settings(): JSX.Element { const [loadAllCourses, setLoadAllCourses] = useState(false); const [_enableDataRefreshing, setEnableDataRefreshing] = useState(false); const [calendarNewTab, setCalendarNewTab] = useState(false); + const [increaseScheduleLimit, setIncreaseScheduleLimit] = useState(false); const showMigrationDialog = useMigrationDialog(); @@ -126,6 +127,7 @@ export default function Settings(): JSX.Element { enableScrollToLoad, enableDataRefreshing, alwaysOpenCalendarInNewTab, + allowMoreSchedules, } = await initSettings(); setEnableCourseStatusChips(enableCourseStatusChips); // setShowTimeLocation(enableTimeAndLocationInPopup); @@ -133,6 +135,7 @@ export default function Settings(): JSX.Element { setLoadAllCourses(enableScrollToLoad); setEnableDataRefreshing(enableDataRefreshing); setCalendarNewTab(alwaysOpenCalendarInNewTab); + setIncreaseScheduleLimit(allowMoreSchedules); }; const initDS = async () => { @@ -187,6 +190,15 @@ export default function Settings(): JSX.Element { // console.log('alwaysOpenCalendarInNewTab', newValue); }); + const l6 = OptionsStore.listen('alwaysOpenCalendarInNewTab', async ({ newValue }) => { + setCalendarNewTab(newValue); + // console.log('alwaysOpenCalendarInNewTab', newValue); + }); + + const l7 = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => { + setIncreaseScheduleLimit(newValue); + }); + // Remove listeners when the component is unmounted return () => { OptionsStore.removeListener(l1); @@ -194,6 +206,8 @@ export default function Settings(): JSX.Element { OptionsStore.removeListener(l3); OptionsStore.removeListener(l4); OptionsStore.removeListener(l5); + OptionsStore.removeListener(l6); + OptionsStore.removeListener(l7); DevStore.removeListener(ds_l1); @@ -449,6 +463,25 @@ export default function Settings(): JSX.Element { /> +
+
+ + Allow more than 10 schedules + +

+ Allow bypassing the 10-schedule limit. Intended for advisors or staff who + need to create many schedules on behalf of students. +

+
+ { + setIncreaseScheduleLimit(!increaseScheduleLimit); + OptionsStore.set('allowMoreSchedules', !increaseScheduleLimit); + }} + /> +
+
diff --git a/src/views/hooks/useEnforceScheduleLimit.tsx b/src/views/hooks/useEnforceScheduleLimit.tsx index 53902896..4409fec6 100644 --- a/src/views/hooks/useEnforceScheduleLimit.tsx +++ b/src/views/hooks/useEnforceScheduleLimit.tsx @@ -1,5 +1,8 @@ +import { background } from '@shared/messages'; +import { OptionsStore } from '@shared/storage/OptionsStore'; +import { CRX_PAGES } from '@shared/types/CRXPages'; import useSchedules from '@views/hooks/useSchedules'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Button } from '../components/common/Button'; import { usePrompt } from '../components/common/DialogProvider/DialogProvider'; @@ -17,8 +20,33 @@ const SCHEDULE_LIMIT = 10; export function useEnforceScheduleLimit(): () => boolean { const [, schedules] = useSchedules(); const showDialog = usePrompt(); + const [allowMoreSchedules, setAllowMoreSchedules] = useState(false); + + useEffect(() => { + let mounted = true; + (async () => { + try { + const val = await OptionsStore.get('allowMoreSchedules'); + if (mounted) setAllowMoreSchedules(val ?? false); + } catch (err) { + console.error('Failed to read allowMoreSchedules from OptionsStore:', err); + } + })(); + + const listener = OptionsStore.listen('allowMoreSchedules', async ({ newValue }) => { + setAllowMoreSchedules(newValue); + }); + + return () => { + mounted = false; + OptionsStore.removeListener(listener); + }; + }, []); return useCallback(() => { + // If user has enabled bypass, allow creating more schedules + if (allowMoreSchedules) return true; + if (schedules.length >= SCHEDULE_LIMIT) { showDialog({ title: `You have too many schedules!`, @@ -27,19 +55,33 @@ export function useEnforceScheduleLimit(): () => boolean { <> To encourage organization,{' '} please consider deleting any unused schedules you - may have. + may have. You can increase the limit in the settings if it’s really necessary. ), buttons: close => ( - + <> + + + ), }); return false; } return true; - }, [schedules, showDialog]); + }, [schedules, showDialog, allowMoreSchedules]); }