From 3892f3afa2bea1fa638753c2acc7119ca1a26c90 Mon Sep 17 00:00:00 2001 From: Felix Mosheev <9304194+felixmosh@users.noreply.github.com> Date: Mon, 28 Jun 2021 14:22:02 +0300 Subject: [PATCH] Add confirmation modal on "dangerous" actions, closes #241 --- packages/ui/package.json | 3 +- packages/ui/src/components/App.tsx | 48 ++-- .../ConfirmModal/ConfirmModal.module.css | 70 +++++ .../components/ConfirmModal/ConfirmModal.tsx | 48 ++++ .../JobCard/Button/Button.module.css | 96 +++++-- .../src/components/JobCard/Button/Button.tsx | 34 ++- .../ui/src/components/QueuePage/QueuePage.tsx | 2 +- packages/ui/src/hooks/useConfirm.ts | 45 ++++ packages/ui/src/hooks/useStore.ts | 77 ++++-- packages/ui/src/index.css | 9 + packages/ui/src/index.ejs | 2 +- yarn.lock | 248 +++++++++++++++++- 12 files changed, 607 insertions(+), 75 deletions(-) create mode 100644 packages/ui/src/components/ConfirmModal/ConfirmModal.module.css create mode 100644 packages/ui/src/components/ConfirmModal/ConfirmModal.tsx create mode 100644 packages/ui/src/hooks/useConfirm.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index 5654e98bd..caea181d6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,7 +27,8 @@ "clean": "rm -rf dist" }, "dependencies": { - "@bull-board/api": "3.2.11" + "@bull-board/api": "3.2.11", + "@radix-ui/react-alert-dialog": "^0.0.19" }, "devDependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", diff --git a/packages/ui/src/components/App.tsx b/packages/ui/src/components/App.tsx index 3d700763b..ec7f80ca2 100644 --- a/packages/ui/src/components/App.tsx +++ b/packages/ui/src/components/App.tsx @@ -8,10 +8,11 @@ import { Header } from './Header/Header'; import { Menu } from './Menu/Menu'; import { QueuePage } from './QueuePage/QueuePage'; import { RedisStats } from './RedisStats/RedisStats'; +import { ConfirmModal } from './ConfirmModal/ConfirmModal'; export const App = ({ api }: { api: Api }) => { useScrollTopOnNav(); - const { state, actions, selectedStatuses } = useStore(api); + const { state, actions, selectedStatuses, confirmProps } = useStore(api); return ( <> @@ -21,27 +22,34 @@ export const App = ({ api }: { api: Api }) => { {state.loading ? ( 'Loading...' ) : ( - - { - const currentQueueName = decodeURIComponent(params.name); - const queue = state.data?.queues.find((q) => q.name === currentQueueName); + <> + + { + const currentQueueName = decodeURIComponent(params.name); + const queue = state.data?.queues.find((q) => q.name === currentQueueName); - return ( - - ); - }} - /> + return ( + + ); + }} + /> - - {!!state.data && - Array.isArray(state.data?.queues) && - state.data.queues.length > 0 && ( - - )} - - + + {!!state.data && + Array.isArray(state.data?.queues) && + state.data.queues.length > 0 && ( + + )} + + + + )} diff --git a/packages/ui/src/components/ConfirmModal/ConfirmModal.module.css b/packages/ui/src/components/ConfirmModal/ConfirmModal.module.css new file mode 100644 index 000000000..49a419f34 --- /dev/null +++ b/packages/ui/src/components/ConfirmModal/ConfirmModal.module.css @@ -0,0 +1,70 @@ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.overlay[data-state='open'], +.contentWrapper[data-state='open'] { + animation: fadeIn 250ms ease-out; +} + +.overlay[data-state='closed'], +.contentWrapper[data-state='closed'] { + animation: fadeOut 250ms ease-in; +} + +.overlay { + position: fixed; + top: 0!important; + left: 0!important; + width: 100%; + height: 100%; + text-align: center; + vertical-align: middle; + padding: 1em; + background-color: rgba(0,0,0,.85); + user-select: none; + will-change: opacity; +} + +.contentWrapper { + padding: 1rem; + position: fixed; + top: 0!important; + left: 0!important; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.content { + max-width: 450px; + width: 100%; + background-color: white; + border-radius: 0.5rem; + padding: 1rem; +} + +.actions { + background: #f9fafb; + margin: 1rem -1rem -1rem; + border-radius: 0 0 0.5rem 0.5rem; + padding: 1rem 1rem; + text-align: right; +} diff --git a/packages/ui/src/components/ConfirmModal/ConfirmModal.tsx b/packages/ui/src/components/ConfirmModal/ConfirmModal.tsx new file mode 100644 index 000000000..4c17b18b2 --- /dev/null +++ b/packages/ui/src/components/ConfirmModal/ConfirmModal.tsx @@ -0,0 +1,48 @@ +import { + Action, + Cancel, + Content, + Description, + Overlay, + Root, + Title, +} from '@radix-ui/react-alert-dialog'; +import React from 'react'; +import s from './ConfirmModal.module.css'; +import { Button } from '../JobCard/Button/Button'; + +export interface ConfirmProps { + open: boolean; + title: string; + description: string; + onCancel: () => void; + onConfirm: () => void; +} + +export const ConfirmModal = (props: ConfirmProps) => { + const closeOnOpenChange = (open: boolean) => { + if (!open) { + props.onCancel(); + } + }; + + return ( + + + +
+ {!!props.title && {props.title}} + {!!props.description && {props.description}} +
+ + Confirm + + + Cancel + +
+
+
+
+ ); +}; diff --git a/packages/ui/src/components/JobCard/Button/Button.module.css b/packages/ui/src/components/JobCard/Button/Button.module.css index 04608df08..6bffdd188 100644 --- a/packages/ui/src/components/JobCard/Button/Button.module.css +++ b/packages/ui/src/components/JobCard/Button/Button.module.css @@ -1,33 +1,83 @@ .button { - font-size: 1rem; - background: none; - border: none; - border-radius: 0.28571429rem; - cursor: pointer; - outline: none; - white-space: nowrap; - padding: 0.65em 0.92857143em; - color: inherit; + font-size: 0.9rem; + background: none; + border: none; + border-radius: 0.28571429rem; + cursor: pointer; + outline: none; + white-space: nowrap; + padding: .65em .92857143em; + color: inherit; + font-family: inherit; + vertical-align: baseline; + min-height: 1em; + display: inline-block; + text-transform: none; + text-shadow: none; + font-weight: 400; } -.button > svg { - width: 1.25em; - vertical-align: middle; - display: inline-block; - fill: #718096; +.button + .button { + margin-inline-start: 0.25em; } -.button:hover, -.button:focus { - background-color: #e2e8f0; +.button.default > svg { + width: 1.25em; + vertical-align: middle; + display: inline-block; + fill: #718096; } -.button:active, -.button.isActive { - background-color: #cbd5e0; +.button.default:hover, +.button.default:focus { + background-color: #e2e8f0; } -.button:hover > svg, -.button:focus > svg { - fill: #718096; +.button.default:active, +.button.default.isActive { + background-color: #cbd5e0; } + +.button.default:hover > svg, +.button.default:focus > svg { + fill: #718096; +} + +.button.basic { + color: rgba(0, 0, 0, 0.6); + box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.15) inset; +} + +.button.basic:focus { + background: #fff; + color: rgba(0, 0, 0, 0.8); + box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.35) inset, 0 0 0 0 rgba(34, 36, 38, 0.15) inset; +} + +.button.basic:hover { + background: #fff; + color: rgba(0, 0, 0, 0.8); + box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.15) inset, 0 0 0 0 rgba(34, 36, 38, 0.15) inset; +} + +.button.basic:active { + background: #f8f8f8; + color: rgba(0, 0, 0, 0.9); + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, 0 1px 4px 0 rgba(34, 36, 38, 0.15) inset; +} + +.button.primary { + background-color: hsl(216, 15%, 47%); + color: #fff; +} + +.button.primary:hover { + background-color: hsl(216, 15%, 42%); +} +.button.primary:active { + background-color: hsl(216, 15%, 39%); +} +.button.primary:focus { + background-color: hsl(216, 15%, 37%); +} + diff --git a/packages/ui/src/components/JobCard/Button/Button.tsx b/packages/ui/src/components/JobCard/Button/Button.tsx index 1988685d6..813b45266 100644 --- a/packages/ui/src/components/JobCard/Button/Button.tsx +++ b/packages/ui/src/components/JobCard/Button/Button.tsx @@ -2,15 +2,29 @@ import React from 'react'; import s from './Button.module.css'; import cn from 'clsx'; -export const Button = ({ - children, - className, - isActive = false, - ...rest -}: React.DetailedHTMLProps, HTMLButtonElement> & { +interface ButtonProps + extends React.DetailedHTMLProps< + React.ButtonHTMLAttributes, + HTMLButtonElement + > { isActive?: boolean; -}) => ( - + theme?: 'basic' | 'primary' | 'default'; +} + +export const Button = React.forwardRef( + ( + { children, className, isActive = false, theme = 'default', ...rest }: ButtonProps, + forwardedRef + ) => ( + + ) ); diff --git a/packages/ui/src/components/QueuePage/QueuePage.tsx b/packages/ui/src/components/QueuePage/QueuePage.tsx index a2b70ed69..ec7aadbc0 100644 --- a/packages/ui/src/components/QueuePage/QueuePage.tsx +++ b/packages/ui/src/components/QueuePage/QueuePage.tsx @@ -26,7 +26,7 @@ export const QueuePage = ({
- {!queue.readOnlyMode && ( + {queue.jobs.length > 0 && !queue.readOnlyMode && ( )}
diff --git a/packages/ui/src/hooks/useConfirm.ts b/packages/ui/src/hooks/useConfirm.ts new file mode 100644 index 000000000..c493deaaa --- /dev/null +++ b/packages/ui/src/hooks/useConfirm.ts @@ -0,0 +1,45 @@ +import { useState } from 'react'; +import { ConfirmProps } from '../components/ConfirmModal/ConfirmModal'; + +interface ConfirmState { + promise: { resolve: (value: unknown) => void; reject: () => void } | null; + opts: { title?: string; description?: string }; +} + +export interface ConfirmApi { + confirmProps: ConfirmProps; + openConfirm: (opts?: ConfirmState['opts']) => Promise; +} + +export function useConfirm(): ConfirmApi { + const [confirmData, setConfirmData] = useState(null); + + function openConfirm(opts: ConfirmState['opts'] = {}) { + return new Promise((resolve, reject) => { + setConfirmData({ promise: { resolve, reject }, opts }); + }); + } + + return { + confirmProps: { + open: !!confirmData?.promise, + title: confirmData?.opts.title || 'Are you sure?', + description: confirmData?.opts.description || '', + onCancel: () => { + setConfirmData({ + opts: { title: confirmData?.opts.title, description: confirmData?.opts.description }, + promise: null, + }); + confirmData?.promise?.reject(); + }, + onConfirm: () => { + setConfirmData({ + opts: { title: confirmData?.opts.title, description: confirmData?.opts.description }, + promise: null, + }); + confirmData?.promise?.resolve(undefined); + }, + }, + openConfirm, + }; +} diff --git a/packages/ui/src/hooks/useStore.ts b/packages/ui/src/hooks/useStore.ts index 35e39665b..c7668fbfd 100644 --- a/packages/ui/src/hooks/useStore.ts +++ b/packages/ui/src/hooks/useStore.ts @@ -7,6 +7,7 @@ import { QueueActions, SelectedStatuses } from '../../typings/app'; import { AppJob } from '@bull-board/api/typings/app'; import { GetQueuesResponse } from '@bull-board/api/typings/responses'; import { useQuery } from './useQuery'; +import { ConfirmApi, useConfirm } from './useConfirm'; const interval = 5000; @@ -19,6 +20,7 @@ export interface Store { state: State; actions: QueueActions; selectedStatuses: SelectedStatuses; + confirmProps: ConfirmApi['confirmProps']; } export const useStore = (api: Api): Store => { @@ -27,6 +29,7 @@ export const useStore = (api: Api): Store => { data: null, loading: true, }); + const { confirmProps, openConfirm } = useConfirm(); const selectedStatuses = useSelectedStatuses(); @@ -39,25 +42,64 @@ export const useStore = (api: Api): Store => { // eslint-disable-next-line no-console .catch((error) => console.error('Failed to poll', error)); - useInterval(update, interval, [selectedStatuses]); - - const promoteJob = (queueName: string) => (job: AppJob) => () => - api.promoteJob(queueName, job.id).then(update); - - const retryJob = (queueName: string) => (job: AppJob) => () => - api.retryJob(queueName, job.id).then(update); - - const cleanJob = (queueName: string) => (job: AppJob) => () => - api.cleanJob(queueName, job.id).then(update); + function withConfirmAndUpdate(action: () => Promise, description: string) { + return async () => { + try { + await openConfirm({ description }); + await action(); + await update(); + } catch (e) { + if (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + }; + } - const retryAll = (queueName: string) => () => api.retryAll(queueName).then(update); - - const cleanAllDelayed = (queueName: string) => () => api.cleanAllDelayed(queueName).then(update); - - const cleanAllFailed = (queueName: string) => () => api.cleanAllFailed(queueName).then(update); + useInterval(update, interval, [selectedStatuses]); - const cleanAllCompleted = (queueName: string) => () => - api.cleanAllCompleted(queueName).then(update); + const promoteJob = (queueName: string) => (job: AppJob) => + withConfirmAndUpdate( + () => api.promoteJob(queueName, job.id), + 'Are you sure that you want to promote this job?' + ); + + const retryJob = (queueName: string) => (job: AppJob) => + withConfirmAndUpdate( + () => api.retryJob(queueName, job.id), + 'Are you sure that you want to retry this job?' + ); + + const cleanJob = (queueName: string) => (job: AppJob) => + withConfirmAndUpdate( + () => api.cleanJob(queueName, job.id), + 'Are you sure that you want to clean this job?' + ); + + const retryAll = (queueName: string) => + withConfirmAndUpdate( + () => api.retryAll(queueName), + 'Are you sure that you want to retry all jobs?' + ); + + const cleanAllDelayed = (queueName: string) => + withConfirmAndUpdate( + () => api.cleanAllDelayed(queueName), + 'Are you sure that you want to clean all delayed jobs?' + ); + + const cleanAllFailed = (queueName: string) => + withConfirmAndUpdate( + () => api.cleanAllFailed(queueName), + 'Are you sure that you want to clean all failed jobs?' + ); + + const cleanAllCompleted = (queueName: string) => + withConfirmAndUpdate( + () => api.cleanAllCompleted(queueName), + 'Are you sure that you want to clean all completed jobs?' + ); const getJobLogs = (queueName: string) => (job: AppJob) => () => api.getJobLogs(queueName, job.id); @@ -74,6 +116,7 @@ export const useStore = (api: Api): Store => { cleanAllCompleted, getJobLogs, }, + confirmProps, selectedStatuses, }; }; diff --git a/packages/ui/src/index.css b/packages/ui/src/index.css index fe7df5d1d..b02381ee1 100644 --- a/packages/ui/src/index.css +++ b/packages/ui/src/index.css @@ -44,6 +44,15 @@ button { font-family: inherit; } +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 500; +} + h1, h2, h3, diff --git a/packages/ui/src/index.ejs b/packages/ui/src/index.ejs index 6cdc9fe61..a47dc80fc 100644 --- a/packages/ui/src/index.ejs +++ b/packages/ui/src/index.ejs @@ -1,5 +1,5 @@ - + diff --git a/yarn.lock b/yarn.lock index f513eb26d..eda0dd1dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1071,6 +1071,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.13.10": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" + integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.14.0": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" @@ -2557,6 +2564,178 @@ schema-utils "^2.6.5" source-map "^0.7.3" +"@radix-ui/primitive@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.0.5.tgz#8464fb4db04401bde72d36e27e05714080668d40" + integrity sha512-VeL6A5LpKYRJhDDj5tCTnzP3zm+FnvybsAkgBHQ4LUPPBnqRdWLoyKpZhlwFze/z22QHINaTIcE9Z/fTcrUR1g== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-alert-dialog@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-0.0.19.tgz#5b69bfe063cdb13f49630ad2705e71228505d147" + integrity sha512-SJRUT2s0/WLCvCEbfuKL5EM6QNXjZQkX9ZgkwKvgRNYu5zYEmCmlCUWDJbPIX1Y7w/a6tuEm24f3Uywd8VcBxw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "0.0.5" + "@radix-ui/react-compose-refs" "0.0.5" + "@radix-ui/react-context" "0.0.5" + "@radix-ui/react-dialog" "0.0.19" + "@radix-ui/react-polymorphic" "0.0.12" + "@radix-ui/react-primitive" "0.0.14" + "@radix-ui/react-slot" "0.0.12" + +"@radix-ui/react-compose-refs@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.0.5.tgz#0f71f0de1dec341f30cebd420b6bc3d12a3037dd" + integrity sha512-O9mH9X/2EwuAEEoZXrU4alcrRbAhhZHGpIJ5bOH6rmRcokhaoWRBY1tOEe2lgHdb/bkKrY+viLi4Zq8Ju6/09Q== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-context@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-0.0.5.tgz#7c15f46795d7765dabfaf6f9c53791ad28c521c2" + integrity sha512-bwrzAc0qc2EPepSTLBT4+93uCiI9wP78VSmPg2K+k71O/vpx7dPs0VqrewwCBNCHT54NIwaRr2hEsm2uqYi02A== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-dialog@0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.0.19.tgz#5a76fa380142a7a97c15c585ab071f63fba5297d" + integrity sha512-7FbWaj/C/TDpfJ+VJ4wNAQIjENDNfwAqNvAfeb+TEtBjgjmsfRDgA1AMenlA5N1QuRtAokRMTHUs3ukW49oQ+g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "0.0.5" + "@radix-ui/react-compose-refs" "0.0.5" + "@radix-ui/react-context" "0.0.5" + "@radix-ui/react-dismissable-layer" "0.0.14" + "@radix-ui/react-focus-guards" "0.0.7" + "@radix-ui/react-focus-scope" "0.0.14" + "@radix-ui/react-id" "0.0.6" + "@radix-ui/react-polymorphic" "0.0.12" + "@radix-ui/react-portal" "0.0.14" + "@radix-ui/react-presence" "0.0.14" + "@radix-ui/react-primitive" "0.0.14" + "@radix-ui/react-slot" "0.0.12" + "@radix-ui/react-use-controllable-state" "0.0.6" + aria-hidden "^1.1.1" + react-remove-scroll "^2.4.0" + +"@radix-ui/react-dismissable-layer@0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.0.14.tgz#9d8a3415a2830688070c6596dec18b43c33df7b2" + integrity sha512-0pmRuGYYvWlEaED1igGFLjic0+hD0OqvsnrZaN3n1nDOkoCd7H5CA2geaShSrlBF5riI2Dr9jIZPGLbDRhs4DA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "0.0.5" + "@radix-ui/react-polymorphic" "0.0.12" + "@radix-ui/react-primitive" "0.0.14" + "@radix-ui/react-use-body-pointer-events" "0.0.6" + "@radix-ui/react-use-callback-ref" "0.0.5" + "@radix-ui/react-use-escape-keydown" "0.0.6" + +"@radix-ui/react-focus-guards@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-0.0.7.tgz#285ed081c877587acd4ee7e6d8260bdf9044e922" + integrity sha512-enAsmrUunptHVzPLTuZqwTP/X3WFBnyJ/jP9W+0g+bRvI3o7V1kxNc+T2Rp1eRTFBW+lUNnt08qkugPytyTRog== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-focus-scope@0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.0.14.tgz#778e2a3ea607621d82e0139616d7ea6d517d9533" + integrity sha512-D3v6Tw8vzpIBNd2I32Q2G4LCiXMIlmc6Pl2VV9CZjSatDOjkV/ckGbhkQyQ7QxnD/0CmiSxNo5hTeGRmZDjwmA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "0.0.5" + "@radix-ui/react-polymorphic" "0.0.12" + "@radix-ui/react-primitive" "0.0.14" + "@radix-ui/react-use-callback-ref" "0.0.5" + +"@radix-ui/react-id@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.0.6.tgz#c4b27d11861805e91ac296e7758ab47e3947b65c" + integrity sha512-PzmraF34fYggsYvTIZVJ5S68WMp3aKUN3HkSmGnz4zn9zpRjkAbbg7Xn3ueQI3FQsLWKgyUfnpsmWFDndpcqYg== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-polymorphic@0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.12.tgz#bf4ae516669b68e059549538104d97322f7c876b" + integrity sha512-/GYNMicBnGzjD1d2fCAuzql1VeFrp8mqM3xfzT1kxhnV85TKdURO45jBfMgqo17XNXoNhWIAProUsCO4qFAAIg== + +"@radix-ui/react-portal@0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.14.tgz#31513d8777cf5e50a3a30ebc9deb34821e890e9e" + integrity sha512-Wi9arVwVenonjZIX6znCBYaasua03Tb+UtrBZShepJkLGtbGxDlzExijiGIaIRNetl46Oc2pw0F6Y6HffDnUww== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-polymorphic" "0.0.12" + "@radix-ui/react-primitive" "0.0.14" + "@radix-ui/react-use-layout-effect" "0.0.5" + +"@radix-ui/react-presence@0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.0.14.tgz#6a86058bbbf46234dd8840dacd620b3ac5797025" + integrity sha512-ufof9B76DHXV0sC8H7Lswh2AepdJFG8qEtF32JWrbA9N1bl2Jnf9px76KsagyC0MA8crGEZO5A96wizGuSgGWQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "0.0.5" + +"@radix-ui/react-primitive@0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.14.tgz#752a967cb05d4c5643634fe20274e7dc905d1cce" + integrity sha512-FYOWGCrxFpLdB534aWTwMK4Pjg8cxFb+745qWhPfI+cYi+aYUddJQD3ilRHHXxCBD72ve7/PufqeB7Y/QlKqgg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-polymorphic" "0.0.12" + +"@radix-ui/react-slot@0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.0.12.tgz#c4d8a75fffca561aeeca2ed9603384d86757f60a" + integrity sha512-Em8P/xYyh3O/32IhrmARJNH+J/XCAVnw6h2zGu6oeReliIX7ktU67pMSeyyIZiU2hNXzaXYB/xDdixizQe/DGA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "0.0.5" + +"@radix-ui/react-use-body-pointer-events@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.0.6.tgz#30b21301880417e7dbb345871ff5a83f2abe0d8d" + integrity sha512-ouYb7u1+K9TsiEcNs3HceNUBUgB2PV41EyD5O6y6ZPMxl1lW/QAy5dfyfJMRyaRWQ6kxwmGoKlCSb4uPTruzuQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-layout-effect" "0.0.5" + +"@radix-ui/react-use-callback-ref@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.0.5.tgz#fa8db050229cda573dfeeae213d74ef06f6130db" + integrity sha512-z1AI221vmq9f3vsDyrCsGLCatKagbM1YeCGdRMhMsUBzFFCaJ+Axyoe/ndVqW8wwsraGWr1zYVAhIEdlC0GvPg== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-controllable-state@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.0.6.tgz#c4b16bc911a25889333388a684a04df937e5fec7" + integrity sha512-fBk4hUSKc4N7X/NAaifWYfKKfNuOB9xvj0MBQQYS5oOTNRgg4y8/Ax3jZ0adsplXDm7ix75sxqWm0nrvUoAjcw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "0.0.5" + +"@radix-ui/react-use-escape-keydown@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.0.6.tgz#1ad1c81b99961b7dbe376ef54151ebc8bef627a0" + integrity sha512-MJpVj21BYwWllmp2xbXPqpKPssJ1WWrZi+Qx7PY5hVcBhQr5Jo6yKwIX677pH5Yql95ENTTT5LW3q+LVFYIISw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "0.0.5" + +"@radix-ui/react-use-layout-effect@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.0.5.tgz#cbbd059090edc765749da00d9f562a9abd43cbac" + integrity sha512-bNPW2JNOr/p2hXr0hfKKqrEy5deNSRF17sw3l9Z7qlEnvIbBtQ7iwY/wrxIz5P7XFyYGoXodIUDH5G8PEucE3A== + dependencies: + "@babel/runtime" "^7.13.10" + "@sideway/address@^4.1.0": version "4.1.2" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1" @@ -3624,6 +3803,13 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-hidden@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254" + integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA== + dependencies: + tslib "^1.0.0" + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -5482,6 +5668,11 @@ detect-newline@^3.0.0, detect-newline@^3.1.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.0.5" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" @@ -6677,6 +6868,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -7499,6 +7695,13 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ioredis-mock@^5.5.7: version "5.5.7" resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.7.tgz#61eca8974015bc52c6c214ba4971a93fcf23b249" @@ -9050,7 +9253,7 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== -loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -11440,6 +11643,25 @@ react-refresh@^0.10.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" integrity sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ== +react-remove-scroll-bar@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd" + integrity sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg== + dependencies: + react-style-singleton "^2.1.0" + tslib "^1.0.0" + +react-remove-scroll@^2.4.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.2.tgz#5b7e3eb8c3389d3636234716d2feb618729fc4d9" + integrity sha512-mMSIZYQF3jS2uRJXeFDRaVGA+BGs/hIryV64YUKsHFtpgwZloOUcdu0oW8K6OU8uLHt/kM5d0lUZbdpIVwgXtQ== + dependencies: + react-remove-scroll-bar "^2.1.0" + react-style-singleton "^2.1.0" + tslib "^1.0.0" + use-callback-ref "^1.2.3" + use-sidecar "^1.0.1" + react-router-dom@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" @@ -11469,6 +11691,15 @@ react-router@5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-style-singleton@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66" + integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^1.0.0" + react-toastify@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-7.0.4.tgz#7d0b743f2b96f65754264ca6eae31911a82378db" @@ -13294,7 +13525,7 @@ tsconfig@^7.0.0: strip-bom "^3.0.0" strip-json-comments "^2.0.0" -tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -13589,6 +13820,19 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-callback-ref@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5" + integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg== + +use-sidecar@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b" + integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA== + dependencies: + detect-node-es "^1.1.0" + tslib "^1.9.3" + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"