Skip to content

Commit 1cade17

Browse files
authored
feat(next): adds support for resetting preferences (#10304)
This PR adds a button to the `/account` view which allows users to reset their preferences on-demand. This is to that editors can quickly reset their preferences via the admin ui without the need of accessing the db directly, which was simply not possible before. To do this, we add a new button at the bottom of the account view which performs a standard `DELETE` request using the REST API to the `'payload-preferences'` collection. Related: #9949 Demo: [Posts-reset-prefs--Payload.webm](https://github.com/user-attachments/assets/560cbbe3-06ef-4b7c-b3c2-9702883b1fc6)
1 parent 5997aa1 commit 1cade17

File tree

43 files changed

+319
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+319
-4
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@import '../../../../scss/styles.scss';
2+
3+
@layer payload-default {
4+
.reset-preferences-modal {
5+
@include blur-bg;
6+
display: flex;
7+
align-items: center;
8+
justify-content: center;
9+
height: 100%;
10+
11+
&__wrapper {
12+
z-index: 1;
13+
position: relative;
14+
display: flex;
15+
flex-direction: column;
16+
gap: base(2);
17+
padding: base(2);
18+
}
19+
20+
&__content {
21+
display: flex;
22+
flex-direction: column;
23+
gap: base(1);
24+
25+
> * {
26+
margin: 0;
27+
}
28+
}
29+
30+
&__controls {
31+
display: flex;
32+
gap: base(0.4);
33+
34+
.btn {
35+
margin: 0;
36+
margin-block: 0;
37+
}
38+
}
39+
}
40+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use client'
2+
import { Button, Modal, useModal, useTranslation } from '@payloadcms/ui'
3+
4+
import './index.scss'
5+
6+
const baseClass = 'reset-preferences-modal'
7+
8+
export const ConfirmResetModal: React.FC<{
9+
readonly onConfirm: () => void
10+
readonly slug: string
11+
}> = ({ slug, onConfirm }) => {
12+
const { closeModal } = useModal()
13+
const { t } = useTranslation()
14+
15+
const handleClose = () => closeModal(slug)
16+
17+
const handleConfirm = () => {
18+
handleClose()
19+
if (typeof onConfirm === 'function') {
20+
onConfirm()
21+
}
22+
}
23+
24+
return (
25+
<Modal className={baseClass} slug={slug}>
26+
<div className={`${baseClass}__wrapper`}>
27+
<div className={`${baseClass}__content`}>
28+
<h1>{t('general:resetPreferences')}?</h1>
29+
<p>{t('general:resetPreferencesDescription')}</p>
30+
</div>
31+
<div className={`${baseClass}__controls`}>
32+
<Button buttonStyle="secondary" onClick={handleClose} size="large">
33+
{t('general:cancel')}
34+
</Button>
35+
<Button onClick={handleConfirm} size="large">
36+
{t('general:confirm')}
37+
</Button>
38+
</div>
39+
</div>
40+
</Modal>
41+
)
42+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use client'
2+
import type { User } from 'payload'
3+
4+
import { Button, LoadingOverlay, toast, useModal, useTranslation } from '@payloadcms/ui'
5+
import * as qs from 'qs-esm'
6+
import { Fragment, useCallback, useState } from 'react'
7+
8+
import { ConfirmResetModal } from './ConfirmResetModal/index.js'
9+
10+
const confirmResetModalSlug = 'confirm-reset-modal'
11+
12+
export const ResetPreferences: React.FC<{
13+
readonly apiRoute: string
14+
readonly user?: User
15+
}> = ({ apiRoute, user }) => {
16+
const { openModal } = useModal()
17+
const { t } = useTranslation()
18+
19+
const [loading, setLoading] = useState(false)
20+
21+
const handleResetPreferences = useCallback(async () => {
22+
if (!user || loading) {
23+
return
24+
}
25+
setLoading(true)
26+
27+
const stringifiedQuery = qs.stringify(
28+
{
29+
depth: 0,
30+
where: {
31+
user: {
32+
id: {
33+
equals: user.id,
34+
},
35+
},
36+
},
37+
},
38+
{ addQueryPrefix: true },
39+
)
40+
41+
try {
42+
const res = await fetch(`${apiRoute}/payload-preferences${stringifiedQuery}`, {
43+
credentials: 'include',
44+
headers: {
45+
'Content-Type': 'application/json',
46+
},
47+
method: 'DELETE',
48+
})
49+
50+
const json = await res.json()
51+
const message = json.message
52+
53+
if (res.ok) {
54+
toast.success(message)
55+
} else {
56+
toast.error(message)
57+
}
58+
} catch (e) {
59+
// swallow error
60+
} finally {
61+
setLoading(false)
62+
}
63+
}, [apiRoute, loading, user])
64+
65+
return (
66+
<Fragment>
67+
<div>
68+
<Button buttonStyle="secondary" onClick={() => openModal(confirmResetModalSlug)}>
69+
{t('general:resetPreferences')}
70+
</Button>
71+
</div>
72+
<ConfirmResetModal onConfirm={handleResetPreferences} slug={confirmResetModalSlug} />
73+
{loading && <LoadingOverlay loadingText={t('general:resettingPreferences')} />}
74+
</Fragment>
75+
)
76+
}
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { I18n } from '@payloadcms/translations'
2-
import type { Config, LanguageOptions } from 'payload'
2+
import type { BasePayload, Config, LanguageOptions, User } from 'payload'
33

44
import { FieldLabel } from '@payloadcms/ui'
55
import React from 'react'
66

7-
import { ToggleTheme } from '../ToggleTheme/index.js'
7+
import { ResetPreferences } from '../ResetPreferences/index.js'
88
import './index.scss'
9+
import { ToggleTheme } from '../ToggleTheme/index.js'
910
import { LanguageSelector } from './LanguageSelector.js'
1011

1112
const baseClass = 'payload-settings'
@@ -14,9 +15,13 @@ export const Settings: React.FC<{
1415
readonly className?: string
1516
readonly i18n: I18n
1617
readonly languageOptions: LanguageOptions
18+
readonly payload: BasePayload
1719
readonly theme: Config['admin']['theme']
20+
readonly user?: User
1821
}> = (props) => {
19-
const { className, i18n, languageOptions, theme } = props
22+
const { className, i18n, languageOptions, payload, theme, user } = props
23+
24+
const apiRoute = payload.config.routes.api
2025

2126
return (
2227
<div className={[baseClass, className].filter(Boolean).join(' ')}>
@@ -26,6 +31,7 @@ export const Settings: React.FC<{
2631
<LanguageSelector languageOptions={languageOptions} />
2732
</div>
2833
{theme === 'all' && <ToggleTheme />}
34+
<ResetPreferences apiRoute={apiRoute} user={user} />
2935
</div>
3036
)
3137
}

packages/next/src/views/Account/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,15 @@ export const Account: React.FC<AdminViewProps> = async ({
111111

112112
return (
113113
<DocumentInfoProvider
114-
AfterFields={<Settings i18n={i18n} languageOptions={languageOptions} theme={theme} />}
114+
AfterFields={
115+
<Settings
116+
i18n={i18n}
117+
languageOptions={languageOptions}
118+
payload={payload}
119+
theme={theme}
120+
user={user}
121+
/>
122+
}
115123
apiURL={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
116124
collectionSlug={userSlug}
117125
currentEditor={currentEditor}

packages/translations/src/clientKeys.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ export const clientTranslationKeys = createClientTranslationKeys([
236236
'general:reindexingAll',
237237
'general:remove',
238238
'general:reset',
239+
'general:resetPreferences',
240+
'general:resetPreferencesDescription',
241+
'general:resettingPreferences',
239242
'general:row',
240243
'general:rows',
241244
'general:save',

packages/translations/src/languages/ar.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ export const arTranslations: DefaultTranslationsObject = {
295295
reindexingAll: 'جاري إعادة فهرسة جميع {{collections}}.',
296296
remove: 'إزالة',
297297
reset: 'إعادة تعيين',
298+
resetPreferences: 'إعادة تعيين التفضيلات',
299+
resetPreferencesDescription:
300+
'سيؤدي ذلك إلى إعادة تعيين جميع تفضيلاتك إلى الإعدادات الافتراضية.',
301+
resettingPreferences: 'إعادة تعيين التفضيلات.',
298302
row: 'سطر',
299303
rows: 'أسطُر',
300304
save: 'حفظ',

packages/translations/src/languages/az.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@ export const azTranslations: DefaultTranslationsObject = {
299299
reindexingAll: 'Bütün {{collections}} yenidən indekslənir.',
300300
remove: 'Sil',
301301
reset: 'Yenidən başlat',
302+
resetPreferences: 'Təhlükəsizlik parametrlərini sıfırlamaq',
303+
resetPreferencesDescription: 'Bu, bütün parametrlərinizi standart vəziyyətlərinə sıfırlayacaq.',
304+
resettingPreferences: 'Təhlükəsizlik parametrləri sıfırlanır.',
302305
row: 'Sətir',
303306
rows: 'Sətirlər',
304307
save: 'Saxla',

packages/translations/src/languages/bg.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ export const bgTranslations: DefaultTranslationsObject = {
298298
reindexingAll: 'Преиндексиране на всички {{collections}}.',
299299
remove: 'Премахни',
300300
reset: 'Нулиране',
301+
resetPreferences: 'Нулиране на предпочитанията',
302+
resetPreferencesDescription:
303+
'Това ще нулира всички ваши предпочитания до техните настройки по подразбиране.',
304+
resettingPreferences: 'Нулиране на предпочитанията.',
301305
row: 'ред',
302306
rows: 'Редове',
303307
save: 'Запази',

packages/translations/src/languages/ca.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ export const caTranslations: DefaultTranslationsObject = {
299299
reindexingAll: 'Reindexa tots el {{collections}}.',
300300
remove: 'Elimina',
301301
reset: 'Restableix',
302+
resetPreferences: 'Restablir les preferències',
303+
resetPreferencesDescription:
304+
'Això restablirà totes les teves preferències a les configuracions per defecte.',
305+
resettingPreferences: 'Restablint les preferències.',
302306
row: 'Fila',
303307
rows: 'Files',
304308
save: 'Desa',

0 commit comments

Comments
 (0)