Files
UT-Registration-Plus/src/views/components/common/DialogProvider/DialogProvider.tsx
doprz d22237d561 feat: UTRP v2 migration (#292)
* feat: wip add course by url

* chore: update imports

* feat: add useCourseFromUrl hook

* chore: extract logic into async function

* feat: add checkLoginStatus.ts

* feat: add useCourseMigration hook

* feat: working course migration

* fix: active schedule bug

* feat: refactor logic and add to onUpdate

* feat: update ui style

* feat: add changelog functionality to settings

* chore: update packages

* feat: migration + sentry stuffs

* feat: improve migration flow

* docs: add sentry jsdocs

* chore: fix lint and format

* chore: cleanup + fix race condition

---------

Co-authored-by: Samuel Gunter <sgunter@utexas.edu>
Co-authored-by: Razboy20 <razboy20@gmail.com>
2024-10-14 21:30:37 -05:00

123 lines
3.7 KiB
TypeScript

import type { CloseWrapper, DialogInfo, DialogOptions, ShowDialogFn } from '@views/contexts/DialogContext';
import { DialogContext, useDialog } from '@views/contexts/DialogContext';
import type { ReactNode } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import Dialog from '../Dialog';
import Text from '../Text/Text';
type DialogElement = (show: boolean) => ReactNode;
/**
* Represents information for a prompt dialog
*/
export interface PromptInfo extends Omit<DialogInfo, 'buttons' | 'className' | 'title' | 'description'> {
title: JSX.Element | string;
description: JSX.Element | string;
onClose?: () => void;
buttons: NonNullable<DialogInfo['buttons']>;
}
function unwrapCloseWrapper<T>(obj: T | CloseWrapper<T>, close: () => void): T {
if (typeof obj === 'function') {
return (obj as CloseWrapper<T>)(close);
}
return obj;
}
/**
* Hook to show prompt with default stylings.
*/
export function usePrompt(): (info: PromptInfo, options?: DialogOptions) => void {
const showDialog = useDialog();
return (info, options) => {
showDialog(
{
...info,
title: (
<Text variant='h2' as='h1' className='text-theme-black'>
{info.title}
</Text>
),
description: (
<Text variant='p' as='p' className='text-ut-black'>
{info.description}
</Text>
),
className: 'max-w-[400px] flex flex-col gap-2.5 p-6.25',
},
options
);
};
}
// Unique ID counter is safe to be global
let nextId = 1;
/**
* Allows descendant to show dialogs via a function, handling animations and stacking.
*/
export default function DialogProvider(props: { children: ReactNode }): JSX.Element {
const dialogQueue = useRef<DialogElement[]>([]);
const [openDialog, setOpenDialog] = useState<DialogElement | undefined>();
const openRef = useRef<typeof openDialog>();
openRef.current = openDialog;
const [isOpen, setIsOpen] = useState(false);
const showDialog = useCallback<ShowDialogFn>((info, options) => {
const id = nextId++;
const handleClose = () => {
setIsOpen(false);
};
const infoUnwrapped = unwrapCloseWrapper(info, handleClose);
const buttons = unwrapCloseWrapper(infoUnwrapped.buttons, handleClose);
const onLeave = () => {
setOpenDialog(undefined);
if (dialogQueue.current.length > 0) {
const newOpen = dialogQueue.current.pop();
setOpenDialog(() => newOpen);
setIsOpen(true);
}
infoUnwrapped.onClose?.();
};
const dialogElement = (show: boolean) => (
<Dialog
key={id}
onClose={(options?.closeOnClickOutside ?? true) ? handleClose : () => {}}
afterLeave={onLeave}
title=<>{infoUnwrapped.title}</>
description=<>{infoUnwrapped.description}</>
appear={!(options?.immediate ?? false)}
show={show}
className={infoUnwrapped.className}
>
<div className='mt-0.75 w-full flex justify-end gap-2.5'>{buttons}</div>
</Dialog>
);
if (openRef.current) {
dialogQueue.current.push(openRef.current);
}
setOpenDialog(() => dialogElement);
setIsOpen(true);
}, []);
return (
<DialogContext.Provider value={showDialog}>
{props.children}
{openDialog?.(isOpen)}
</DialogContext.Provider>
);
}