There are nearly a dozen independent implementations of the same modal
spread throughout the admin panel and various plugins. These modals are
used to confirm or cancel an action, such as deleting a document, bulk
publishing, etc. Each of these instances is nearly identical, leading to
unnecessary development efforts when creating them, inconsistent UI, and
duplicative stylesheets.
Everything is now standardized behind a new `ConfirmationModal`
component. This modal comes with a standard API that is flexible enough
to replace nearly every instance. This component has also been exported
for reuse.
Here is a basic example of how to use it:
```tsx
'use client'
import { ConfirmationModal, useModal } from '@payloadcms/ui'
import React, { Fragment } from 'react'
const modalSlug = 'my-confirmation-modal'
export function MyComponent() {
const { openModal } = useModal()
return (
<Fragment>
<button
onClick={() => {
openModal(modalSlug)
}}
type="button"
>
Do something
</button>
<ConfirmationModal
heading="Are you sure?"
body="Confirm or cancel before proceeding."
modalSlug={modalSlug}
onConfirm={({ closeConfirmationModal, setConfirming }) => {
// do something
setConfirming(false)
closeConfirmationModal()
}}
/>
</Fragment>
)
}
```
135 lines
3.5 KiB
TypeScript
135 lines
3.5 KiB
TypeScript
'use client'
|
|
import type { OnConfirm } from '@payloadcms/ui'
|
|
|
|
import { getTranslation } from '@payloadcms/translations'
|
|
import {
|
|
Button,
|
|
ConfirmationModal,
|
|
PopupList,
|
|
useConfig,
|
|
useModal,
|
|
useRouteTransition,
|
|
useTranslation,
|
|
} from '@payloadcms/ui'
|
|
import { formatAdminURL, requests } from '@payloadcms/ui/shared'
|
|
import { useRouter } from 'next/navigation.js'
|
|
import React, { Fragment, useCallback, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
import type { Props } from './types.js'
|
|
|
|
import './index.scss'
|
|
|
|
const baseClass = 'restore-version'
|
|
const modalSlug = 'restore-version'
|
|
|
|
const Restore: React.FC<Props> = ({
|
|
className,
|
|
collectionSlug,
|
|
globalSlug,
|
|
label,
|
|
originalDocID,
|
|
status,
|
|
versionDate,
|
|
versionID,
|
|
}) => {
|
|
const {
|
|
config: {
|
|
routes: { admin: adminRoute, api: apiRoute },
|
|
serverURL,
|
|
},
|
|
getEntityConfig,
|
|
} = useConfig()
|
|
|
|
const collectionConfig = getEntityConfig({ collectionSlug })
|
|
|
|
const { toggleModal } = useModal()
|
|
const router = useRouter()
|
|
const { i18n, t } = useTranslation()
|
|
const [draft, setDraft] = useState(false)
|
|
const { startRouteTransition } = useRouteTransition()
|
|
|
|
const restoreMessage = t('version:aboutToRestoreGlobal', {
|
|
label: getTranslation(label, i18n),
|
|
versionDate,
|
|
})
|
|
|
|
let fetchURL = `${serverURL}${apiRoute}`
|
|
let redirectURL: string
|
|
|
|
const canRestoreAsDraft = status !== 'draft' && collectionConfig?.versions?.drafts
|
|
|
|
if (collectionSlug) {
|
|
fetchURL = `${fetchURL}/${collectionSlug}/versions/${versionID}?draft=${draft}`
|
|
redirectURL = formatAdminURL({
|
|
adminRoute,
|
|
path: `/collections/${collectionSlug}/${originalDocID}`,
|
|
})
|
|
}
|
|
|
|
if (globalSlug) {
|
|
fetchURL = `${fetchURL}/globals/${globalSlug}/versions/${versionID}?draft=${draft}`
|
|
redirectURL = formatAdminURL({
|
|
adminRoute,
|
|
path: `/globals/${globalSlug}`,
|
|
})
|
|
}
|
|
|
|
const handleRestore: OnConfirm = useCallback(
|
|
async ({ closeConfirmationModal, setConfirming }) => {
|
|
const res = await requests.post(fetchURL, {
|
|
headers: {
|
|
'Accept-Language': i18n.language,
|
|
},
|
|
})
|
|
|
|
setConfirming(false)
|
|
closeConfirmationModal()
|
|
|
|
if (res.status === 200) {
|
|
const json = await res.json()
|
|
toast.success(json.message)
|
|
startRouteTransition(() => router.push(redirectURL))
|
|
} else {
|
|
toast.error(t('version:problemRestoringVersion'))
|
|
}
|
|
},
|
|
[fetchURL, redirectURL, t, i18n, router, startRouteTransition],
|
|
)
|
|
|
|
return (
|
|
<Fragment>
|
|
<div className={[baseClass, className].filter(Boolean).join(' ')}>
|
|
<Button
|
|
buttonStyle="pill"
|
|
className={[canRestoreAsDraft && `${baseClass}__button`].filter(Boolean).join(' ')}
|
|
onClick={() => toggleModal(modalSlug)}
|
|
size="small"
|
|
SubMenuPopupContent={
|
|
canRestoreAsDraft
|
|
? () => (
|
|
<PopupList.ButtonGroup>
|
|
<PopupList.Button onClick={() => [setDraft(true), toggleModal(modalSlug)]}>
|
|
{t('version:restoreAsDraft')}
|
|
</PopupList.Button>
|
|
</PopupList.ButtonGroup>
|
|
)
|
|
: null
|
|
}
|
|
>
|
|
{t('version:restoreThisVersion')}
|
|
</Button>
|
|
</div>
|
|
<ConfirmationModal
|
|
body={restoreMessage}
|
|
confirmingLabel={t('version:restoring')}
|
|
heading={t('version:confirmVersionRestoration')}
|
|
modalSlug={modalSlug}
|
|
onConfirm={handleRestore}
|
|
/>
|
|
</Fragment>
|
|
)
|
|
}
|
|
|
|
export default Restore
|