Files
payload/packages/next/src/views/Version/Restore/index.tsx
Jacob Fletcher bd8ced1b60 feat(ui): confirmation modal (#11271)
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>
  )
}
```
2025-02-19 02:27:03 -05:00

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