feat: adds i18n functionality within Rest API, Local and Client contexts (#4749)
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import type { Create } from 'payload/database'
|
||||
import type { Document, PayloadRequest } from 'payload/types'
|
||||
|
||||
import { ValidationError } from 'payload/errors'
|
||||
import { i18nInit } from 'payload/utilities'
|
||||
|
||||
import type { MongooseAdapter } from '.'
|
||||
|
||||
import { withSession } from './withSession'
|
||||
import { ValidationError } from 'payload/errors'
|
||||
import { i18nInit } from 'payload/utilities'
|
||||
|
||||
export const create: Create = async function create(
|
||||
this: MongooseAdapter,
|
||||
@@ -26,7 +27,7 @@ export const create: Create = async function create(
|
||||
message: req.t('error:valueMustBeUnique'),
|
||||
},
|
||||
],
|
||||
req?.t ?? i18nInit(this.payload.config.i18n).t,
|
||||
req.t,
|
||||
)
|
||||
: error
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { UpdateOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { ValidationError } from 'payload/errors'
|
||||
import { i18nInit } from 'payload/utilities'
|
||||
|
||||
import type { MongooseAdapter } from '.'
|
||||
|
||||
@@ -40,7 +39,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
||||
message: 'Value must be unique',
|
||||
},
|
||||
],
|
||||
req?.t ?? i18nInit(this.payload.config.i18n).t,
|
||||
req.t,
|
||||
)
|
||||
: error
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
|
||||
export { GET, POST, DELETE, PATCH } from '@payloadcms/next/dist/routes/index'
|
||||
// use the next line for easier local development
|
||||
// export { GET, POST, DELETE, PATCH } from '@payloadcms/next/routes/index'
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"@payloadcms/next/layouts/*": ["../next/src/layouts/*"],
|
||||
"@payloadcms/next/pages/*": ["../next/src/pages/*"],
|
||||
"@payloadcms/next/routes/*": ["../next/src/routes/*"],
|
||||
"@payloadcms/next/utilities/*": ["../next/src/utilities/*"]
|
||||
"@payloadcms/next/utilities/*": ["../next/src/utilities/*"],
|
||||
"@payloadcms/next/translations/*": ["../next/src/translations/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
"sass": "^1.69.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/translations": "workspace:^",
|
||||
"deepmerge": "^4.3.1",
|
||||
"@faceless-ui/modal": "2.0.1",
|
||||
"jsonwebtoken": "9.0.1",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
|
||||
7
packages/next/src/createClientConfig.d.ts
vendored
Normal file
7
packages/next/src/createClientConfig.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Field, ClientConfig, SanitizedConfig } from 'payload/types'
|
||||
export declare const sanitizeField: (f: any) => any
|
||||
export declare const sanitizeFields: (fields: Field[]) => Field[]
|
||||
export declare const createClientConfig: (
|
||||
configPromise: SanitizedConfig | Promise<SanitizedConfig>,
|
||||
) => Promise<ClientConfig>
|
||||
//# sourceMappingURL=createClientConfig.d.ts.map
|
||||
1
packages/next/src/createClientConfig.d.ts.map
Normal file
1
packages/next/src/createClientConfig.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"createClientConfig.d.ts","sourceRoot":"","sources":["createClientConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEzE,eAAO,MAAM,aAAa,iBAmBzB,CAAA;AAED,eAAO,MAAM,cAAc,WAAY,KAAK,EAAE,KAAG,KAAK,EAA+B,CAAA;AAErF,eAAO,MAAM,kBAAkB,kBACd,eAAe,GAAG,QAAQ,eAAe,CAAC,KACxD,QAAQ,YAAY,CA6BtB,CAAA"}
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
import { DefaultTemplate } from '@payloadcms/ui'
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { initPage } from '../../utilities/initPage'
|
||||
|
||||
import '@payloadcms/ui/scss/app.scss'
|
||||
import { initPage } from '../../utilities/initPage'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Next.js',
|
||||
@@ -17,10 +17,10 @@ export const AdminLayout = async ({
|
||||
children: React.ReactNode
|
||||
config: Promise<SanitizedConfig>
|
||||
}) => {
|
||||
const { user, permissions } = await initPage({ configPromise })
|
||||
const { user, permissions, i18n } = await initPage({ configPromise })
|
||||
|
||||
return (
|
||||
<DefaultTemplate config={configPromise} user={user} permissions={permissions}>
|
||||
<DefaultTemplate config={configPromise} user={user} permissions={permissions} i18n={i18n}>
|
||||
{children}
|
||||
</DefaultTemplate>
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ export const DocumentLayout = async ({
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
}) => {
|
||||
const { config, collectionConfig, globalConfig } = await initPage({
|
||||
const { config, collectionConfig, globalConfig, i18n } = await initPage({
|
||||
configPromise,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
@@ -30,6 +30,7 @@ export const DocumentLayout = async ({
|
||||
return (
|
||||
<Fragment>
|
||||
<DocumentHeader
|
||||
i18n={i18n}
|
||||
config={config}
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React from 'react'
|
||||
import { headers, cookies } from 'next/headers'
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
import { RootProvider } from '@payloadcms/ui/providers'
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { deepMerge } from 'payload/utilities'
|
||||
import { createClientConfig } from '../../createClientConfig'
|
||||
import { getRequestLanguage } from '../../utilities/getRequestLanguage'
|
||||
|
||||
import '@payloadcms/ui/scss/app.scss'
|
||||
|
||||
@@ -10,6 +14,8 @@ export const metadata = {
|
||||
description: 'Generated by Next.js',
|
||||
}
|
||||
|
||||
const rtlLanguages = ['ar', 'fa', 'ha', 'ku', 'ur', 'ps', 'dv', 'ks', 'khw', 'he', 'yi']
|
||||
|
||||
export const RootLayout = async ({
|
||||
children,
|
||||
config: configPromise,
|
||||
@@ -18,11 +24,32 @@ export const RootLayout = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
}) => {
|
||||
const clientConfig = await createClientConfig(configPromise)
|
||||
const lang =
|
||||
getRequestLanguage({
|
||||
headers: headers(),
|
||||
cookies: cookies(),
|
||||
}) ?? clientConfig.i18n.fallbackLanguage
|
||||
const dir = rtlLanguages.includes(lang) ? 'RTL' : 'LTR'
|
||||
|
||||
const mergedTranslations = deepMerge(translations, clientConfig.i18n.translations)
|
||||
|
||||
const languageOptions = Object.entries(translations || {}).map(([language, translations]) => ({
|
||||
label: translations.general.thisLanguage,
|
||||
value: language,
|
||||
}))
|
||||
|
||||
return (
|
||||
<html lang="en" dir="LTR">
|
||||
<html lang={lang} dir={dir}>
|
||||
<body>
|
||||
<RootProvider config={clientConfig}>{children}</RootProvider>
|
||||
<RootProvider
|
||||
config={clientConfig}
|
||||
translations={mergedTranslations[lang]}
|
||||
lang={lang}
|
||||
fallbackLang={clientConfig.i18n.fallbackLanguage}
|
||||
languageOptions={languageOptions}
|
||||
>
|
||||
{children}
|
||||
</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ export const APIView = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) => {
|
||||
const { config, payload, user, locale, collectionConfig, globalConfig } = await initPage({
|
||||
const { config, payload, user, locale, collectionConfig, globalConfig, i18n } = await initPage({
|
||||
configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
collectionSlug,
|
||||
@@ -140,8 +140,13 @@ export const APIView = async ({
|
||||
>
|
||||
<div className={`${baseClass}__form-fields`}>
|
||||
<div className={`${baseClass}__filter-query-checkboxes`}>
|
||||
{draftsEnabled && <Checkbox name="draft" path="draft" label="Draft" />}
|
||||
<Checkbox name="authenticated" path="authenticated" label="Authenticated" />
|
||||
{draftsEnabled && <Checkbox i18n={i18n} name="draft" path="draft" label="Draft" />}
|
||||
<Checkbox
|
||||
i18n={i18n}
|
||||
name="authenticated"
|
||||
path="authenticated"
|
||||
label="Authenticated"
|
||||
/>
|
||||
</div>
|
||||
{localeOptions && (
|
||||
<Select label="Locale" name="locale" options={localeOptions} path="locale" />
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Translation } from 'payload/dist/translations/type'
|
||||
import { useTranslation } from '@payloadcms/ui/providers'
|
||||
|
||||
import { ReactSelect, Label } from '@payloadcms/ui'
|
||||
import { ToggleTheme } from '../ToggleTheme'
|
||||
@@ -15,14 +13,7 @@ export const Settings: React.FC<{
|
||||
}> = (props) => {
|
||||
const { className } = props
|
||||
|
||||
const { i18n, t } = useTranslation('authentication')
|
||||
|
||||
const languageOptions = Object.entries(i18n.options.resources || {}).map(
|
||||
([language, resource]) => ({
|
||||
label: (resource as Translation).general.thisLanguage,
|
||||
value: language,
|
||||
}),
|
||||
)
|
||||
const { i18n, t, languageOptions } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')}>
|
||||
@@ -31,7 +22,8 @@ export const Settings: React.FC<{
|
||||
<Label htmlFor="language-select" label={t('general:language')} />
|
||||
<ReactSelect
|
||||
inputId="language-select"
|
||||
onChange={({ value }) => i18n.changeLanguage(value)}
|
||||
// TODO(i18n): wire up onChange / changeLanguage fn
|
||||
// onChange={({ value }) => i18n.changeLanguage(value)}
|
||||
options={languageOptions}
|
||||
value={languageOptions.find((language) => language.value === i18n.language)}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
|
||||
import type { OnChange, Theme } from '@payloadcms/ui'
|
||||
import { useTheme, RadioGroupInput } from '@payloadcms/ui'
|
||||
@@ -11,7 +11,7 @@ export const ToggleTheme: React.FC = () => {
|
||||
setTheme,
|
||||
theme,
|
||||
} = useTheme()
|
||||
const { t } = useTranslation('general')
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onChange = useCallback<OnChange<Theme>>(
|
||||
(newTheme) => {
|
||||
@@ -22,20 +22,20 @@ export const ToggleTheme: React.FC = () => {
|
||||
|
||||
return (
|
||||
<RadioGroupInput
|
||||
label={t('adminTheme')}
|
||||
label={t('general:adminTheme')}
|
||||
name="theme"
|
||||
onChange={onChange}
|
||||
options={[
|
||||
{
|
||||
label: t('automatic'),
|
||||
label: t('general:automatic'),
|
||||
value: 'auto',
|
||||
},
|
||||
{
|
||||
label: t('light'),
|
||||
label: t('general:light'),
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
label: t('dark'),
|
||||
label: t('general:dark'),
|
||||
value: 'dark',
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -34,6 +34,7 @@ export const DefaultAccount: React.FC<DefaultAccountViewProps> = (props) => {
|
||||
onSave: onSaveFromProps,
|
||||
permissions,
|
||||
user,
|
||||
i18n,
|
||||
} = props
|
||||
|
||||
const { auth, fields } = collectionConfig
|
||||
@@ -71,6 +72,7 @@ export const DefaultAccount: React.FC<DefaultAccountViewProps> = (props) => {
|
||||
config={config}
|
||||
collectionConfig={collectionConfig}
|
||||
data={data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
@@ -80,6 +82,7 @@ export const DefaultAccount: React.FC<DefaultAccountViewProps> = (props) => {
|
||||
hasSavePermission={hasSavePermission}
|
||||
isAccountView
|
||||
permissions={permissions}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<DocumentFields
|
||||
AfterFields={<Settings className={`${baseClass}__settings`} />}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const Account = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) => {
|
||||
const { config, payload, permissions, user } = await initPage({
|
||||
const { config, payload, permissions, user, i18n } = await initPage({
|
||||
configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
})
|
||||
@@ -89,7 +89,7 @@ export const Account = async ({
|
||||
locale,
|
||||
operation: 'update',
|
||||
preferences,
|
||||
// t,
|
||||
t: i18n.t,
|
||||
user,
|
||||
})
|
||||
|
||||
@@ -107,6 +107,7 @@ export const Account = async ({
|
||||
user,
|
||||
updatedAt: '', // TODO
|
||||
id: user?.id,
|
||||
i18n,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,7 @@ export const CollectionList = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) => {
|
||||
const { config, payload, permissions, user, collectionConfig } = await initPage({
|
||||
const { config, payload, permissions, user, collectionConfig, i18n } = await initPage({
|
||||
configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
collectionSlug,
|
||||
@@ -63,6 +63,7 @@ export const CollectionList = async ({
|
||||
setLimit: () => {},
|
||||
setListControls: () => {},
|
||||
setSort: () => {},
|
||||
i18n,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import { getNextT } from '../../utilities/getNextT'
|
||||
import { MinimalTemplate, FormSubmit, Form, RenderFields, fieldTypes } from '@payloadcms/ui'
|
||||
import './index.scss'
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import i18n from 'i18next'
|
||||
import { Metadata } from 'next'
|
||||
import { meta } from '../../utilities/meta'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'create-first-user'
|
||||
|
||||
export const generateMetadata = async ({
|
||||
config,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> =>
|
||||
meta({
|
||||
title: i18n.t('createFirstUser'),
|
||||
description: i18n.t('createFirstUser'),
|
||||
keywords: i18n.t('general:create'),
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
return meta({
|
||||
title: t('createFirstUser'),
|
||||
description: t('createFirstUser'),
|
||||
keywords: t('general:create'),
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
export const CreateFirstUser: React.FC<{
|
||||
config: Promise<SanitizedConfig>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { EntityToGroup, Group } from '@payloadcms/ui'
|
||||
import {
|
||||
@@ -11,9 +10,10 @@ import {
|
||||
useAuth,
|
||||
useConfig,
|
||||
useActions,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'dashboard'
|
||||
@@ -37,7 +37,7 @@ export const DefaultDashboardClient: React.FC<{
|
||||
|
||||
const { permissions, user } = useAuth()
|
||||
|
||||
const { i18n, t } = useTranslation('general')
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const [groups, setGroups] = useState<Group[]>([])
|
||||
|
||||
@@ -84,7 +84,7 @@ export const DefaultDashboardClient: React.FC<{
|
||||
i18n,
|
||||
),
|
||||
)
|
||||
}, [collectionsConfig, globalsConfig, i18n, permissions, user])
|
||||
}, [collectionsConfig, globalsConfig, permissions, user])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
@@ -7,10 +7,8 @@ import { DefaultDashboard } from './Default'
|
||||
|
||||
export const Dashboard = async ({
|
||||
config: configPromise,
|
||||
searchParams,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) => {
|
||||
const { config, user, permissions } = await initPage({
|
||||
configPromise,
|
||||
|
||||
@@ -31,6 +31,7 @@ export const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
|
||||
fieldTypes,
|
||||
config,
|
||||
user,
|
||||
i18n,
|
||||
} = props
|
||||
|
||||
// const { setViewActions } = useActions()
|
||||
@@ -123,6 +124,7 @@ export const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
|
||||
hasSavePermission={hasSavePermission}
|
||||
isEditing
|
||||
permissions={permissions}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<DocumentFields
|
||||
description={description}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { meta } from '../../utilities/meta'
|
||||
import { DefaultGlobalView } from './Default'
|
||||
import { DefaultGlobalViewProps } from './Default/types'
|
||||
// import i18n from 'i18next'
|
||||
// import { getTranslation } from 'payload/utilities'
|
||||
// import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
export const generateMetadata = async ({
|
||||
config,
|
||||
|
||||
@@ -2,13 +2,13 @@ import React, { Fragment } from 'react'
|
||||
|
||||
import { Logo } from '@payloadcms/ui/graphics'
|
||||
import { MinimalTemplate, LoginForm } from '@payloadcms/ui'
|
||||
import './index.scss'
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
import i18n from 'i18next'
|
||||
import { meta } from '../../utilities/meta'
|
||||
import { Metadata } from 'next'
|
||||
import { initPage } from '../../utilities/initPage'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getNextT } from '../../utilities/getNextT'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'login'
|
||||
|
||||
@@ -16,13 +16,18 @@ export const generateMetadata = async ({
|
||||
config,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> =>
|
||||
meta({
|
||||
title: i18n.t('login'),
|
||||
description: `${i18n.t('login')}`,
|
||||
keywords: `${i18n.t('login')}`,
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
return meta({
|
||||
title: t('authentication:login'),
|
||||
description: `${t('authentication:login')}`,
|
||||
keywords: `${t('authentication:login')}`,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
export const Login: React.FC<{
|
||||
config: Promise<SanitizedConfig>
|
||||
@@ -49,9 +54,7 @@ export const Login: React.FC<{
|
||||
<Logo config={config} />
|
||||
</div>
|
||||
{Array.isArray(beforeLogin) && beforeLogin.map((Component, i) => <Component key={i} />)}
|
||||
{!collectionConfig?.auth?.disableLocalStrategy && (
|
||||
<LoginForm config={config} searchParams={searchParams} />
|
||||
)}
|
||||
{!collectionConfig?.auth?.disableLocalStrategy && <LoginForm searchParams={searchParams} />}
|
||||
{Array.isArray(afterLogin) && afterLogin.map((Component, i) => <Component key={i} />)}
|
||||
</Fragment>
|
||||
</MinimalTemplate>
|
||||
|
||||
@@ -26,6 +26,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
id,
|
||||
versionID,
|
||||
locale,
|
||||
i18n,
|
||||
}) => {
|
||||
const {
|
||||
routes: { admin },
|
||||
@@ -88,10 +89,12 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
url: `${admin}/globals/${globalConfig.slug}`,
|
||||
},
|
||||
{
|
||||
// TODO(i18n)
|
||||
label: 'Versions',
|
||||
url: `${admin}/globals/${globalConfig.slug}/versions`,
|
||||
},
|
||||
{
|
||||
// TODO(i18n)
|
||||
label: '[Date]',
|
||||
// label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
|
||||
},
|
||||
@@ -110,6 +113,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
|
||||
const formattedCreatedAt = doc?.createdAt
|
||||
// const formattedCreatedAt = doc?.createdAt
|
||||
// TODO(i18n)
|
||||
// ? formatDate(doc.createdAt, dateFormat, i18n?.language)
|
||||
// : ''
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
import {
|
||||
CollectionPermission,
|
||||
FieldPermissions,
|
||||
@@ -36,4 +37,5 @@ export type DefaultVersionsViewProps = {
|
||||
versionID?: string
|
||||
docPermissions: CollectionPermission | GlobalPermission
|
||||
locale: string
|
||||
i18n: I18n
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
|
||||
import type { ArrayField, BlockField, Field } from 'payload/types'
|
||||
@@ -5,7 +6,7 @@ import type { Props } from '../types'
|
||||
|
||||
import RenderFieldsToDiff from '../..'
|
||||
import { fieldAffectsData } from 'payload/types'
|
||||
import { getTranslation, getUniqueListBy } from 'payload/utilities'
|
||||
import { getUniqueListBy } from 'payload/utilities'
|
||||
import Label from '../../Label'
|
||||
import './index.scss'
|
||||
|
||||
@@ -80,7 +81,8 @@ const Iterable: React.FC<Props & { field: ArrayField | BlockField }> = ({
|
||||
fieldComponents={fieldComponents}
|
||||
fieldPermissions={permissions}
|
||||
fields={subFields.filter(
|
||||
(subField) => !(fieldAffectsData(subField) && subField.name === 'id'),
|
||||
(subField) =>
|
||||
!(fieldAffectsData(subField) && 'name' in subField && subField.name === 'id'),
|
||||
)}
|
||||
locales={locales}
|
||||
version={versionRow}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import type { FieldWithSubFields } from 'payload/types'
|
||||
import type { Props } from '../types'
|
||||
|
||||
import RenderFieldsToDiff from '../..'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import Label from '../../Label'
|
||||
import './index.scss'
|
||||
|
||||
@@ -28,6 +28,7 @@ const Nested: React.FC<Props & { field: FieldWithSubFields }> = ({
|
||||
<Label>
|
||||
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
|
||||
{field.label}
|
||||
// TODO(i18n)
|
||||
{/* {getTranslation(field.label, i18n)} */}
|
||||
</Label>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import ReactDiffViewer from 'react-diff-viewer-continued'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -7,12 +8,12 @@ import type { RelationshipField, SanitizedCollectionConfig } from 'payload/types
|
||||
import type { Props } from '../types'
|
||||
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import Label from '../../Label'
|
||||
import { diffStyles } from '../styles'
|
||||
import './index.scss'
|
||||
import { useConfig, useLocale } from '@payloadcms/ui'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'relationship-diff'
|
||||
|
||||
type RelationshipValue = Record<string, any>
|
||||
@@ -101,7 +102,8 @@ const Relationship: React.FC<Props & { field: RelationshipField }> = ({
|
||||
<div className={baseClass}>
|
||||
<Label>
|
||||
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
|
||||
{getTranslation(field.label, i18n)}
|
||||
// TODO(i18n)
|
||||
{/* {getTranslation(field.label, i18n)} */}
|
||||
</Label>
|
||||
<ReactDiffViewer
|
||||
hideLineNumbers
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { i18n as Ii18n } from 'i18next'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
import { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import type { OptionObject, SelectField } from 'payload/types'
|
||||
import type { Props } from '../types'
|
||||
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import Label from '../../Label'
|
||||
import { diffStyles } from '../styles'
|
||||
import './index.scss'
|
||||
import { DiffViewer } from './DiffViewer'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'select-diff'
|
||||
|
||||
const getOptionsToRender = (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import type { Props } from '../types'
|
||||
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import Label from '../../Label'
|
||||
import { diffStyles } from '../styles'
|
||||
import './index.scss'
|
||||
@@ -37,6 +37,7 @@ const Text: React.FC<Props> = ({
|
||||
<Label>
|
||||
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
|
||||
{typeof field.label === 'string' ? field.label : '[field-label]' /* TODO */}
|
||||
// TODO(i18n)
|
||||
{/* {getTranslation(field.label, i18n)} */}
|
||||
</Label>
|
||||
<DiffViewer
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
import { Modal, useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { Button, MinimalTemplate, Pill, useConfig } from '@payloadcms/ui'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import { Button, MinimalTemplate, Pill, useConfig, useTranslation } from '@payloadcms/ui'
|
||||
// import { requests } from '../../../../api'
|
||||
import './index.scss'
|
||||
|
||||
@@ -29,9 +28,9 @@ const Restore: React.FC<Props> = ({
|
||||
} = useConfig()
|
||||
const { toggleModal } = useModal()
|
||||
const [processing, setProcessing] = useState(false)
|
||||
const { i18n, t } = useTranslation('version')
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const restoreMessage = t('aboutToRestoreGlobal', {
|
||||
const restoreMessage = t('version:aboutToRestoreGlobal', {
|
||||
label: getTranslation(label, i18n),
|
||||
versionDate,
|
||||
})
|
||||
@@ -65,7 +64,7 @@ const Restore: React.FC<Props> = ({
|
||||
toast.success(json.message)
|
||||
// history.push(redirectURL)
|
||||
} else {
|
||||
toast.error(t('problemRestoringVersion'))
|
||||
toast.error(t('version:problemRestoringVersion'))
|
||||
}
|
||||
}, [fetchURL, redirectURL, t, i18n])
|
||||
|
||||
@@ -75,11 +74,11 @@ const Restore: React.FC<Props> = ({
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
onClick={() => toggleModal(modalSlug)}
|
||||
>
|
||||
{t('restoreThisVersion')}
|
||||
{t('version:restoreThisVersion')}
|
||||
</Pill>
|
||||
<Modal className={`${baseClass}__modal`} slug={modalSlug}>
|
||||
<MinimalTemplate className={`${baseClass}__modal-template`}>
|
||||
<h1>{t('confirmVersionRestoration')}</h1>
|
||||
<h1>{t('version:confirmVersionRestoration')}</h1>
|
||||
<p>{restoreMessage}</p>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
@@ -89,7 +88,7 @@ const Restore: React.FC<Props> = ({
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
<Button onClick={processing ? undefined : handleRestore}>
|
||||
{processing ? t('restoring') : t('general:confirm')}
|
||||
{processing ? t('version:restoring') : t('general:confirm')}
|
||||
</Button>
|
||||
</MinimalTemplate>
|
||||
</Modal>
|
||||
|
||||
@@ -27,7 +27,8 @@ export const VersionView = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) => {
|
||||
const { config, payload, permissions, user, collectionConfig, globalConfig } = await initPage({
|
||||
const { config, payload, permissions, user, collectionConfig, globalConfig, i18n } =
|
||||
await initPage({
|
||||
configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
collectionSlug,
|
||||
@@ -162,6 +163,7 @@ export const VersionView = async ({
|
||||
versionID={versionID}
|
||||
docPermissions={docPermissions}
|
||||
locale="" // TODO
|
||||
i18n={i18n}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,17 @@ import './index.scss'
|
||||
const baseClass = 'versions'
|
||||
|
||||
export const DefaultVersionsView: React.FC<DefaultVersionsViewProps> = (props) => {
|
||||
const { id, config, collectionConfig, data, entityLabel, globalConfig, versionsData, limit } =
|
||||
props
|
||||
const {
|
||||
id,
|
||||
config,
|
||||
collectionConfig,
|
||||
data,
|
||||
entityLabel,
|
||||
globalConfig,
|
||||
versionsData,
|
||||
limit,
|
||||
i18n,
|
||||
} = props
|
||||
|
||||
// const useAsTitle = collectionConfig?.admin?.useAsTitle || 'id'
|
||||
|
||||
@@ -42,30 +51,35 @@ export const DefaultVersionsView: React.FC<DefaultVersionsViewProps> = (props) =
|
||||
globalSlug={globalConfig?.slug}
|
||||
id={id}
|
||||
isEditing
|
||||
view="Versions" // TODO; i18n
|
||||
view={i18n.t('general:versions')}
|
||||
pluralLabel={collectionConfig?.labels?.plural}
|
||||
// view={t('versions')}
|
||||
/>
|
||||
{/* <LoadingOverlayToggle name="versions" show={isLoadingVersions} /> */}
|
||||
<main className={baseClass}>
|
||||
{/* <Meta description={metaDesc} title={metaTitle} /> */}
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
{versionCount === 0 && (
|
||||
<div className={`${baseClass}__no-versions`}>{/* {t('noFurtherVersionsFound')} */}</div>
|
||||
<div className={`${baseClass}__no-versions`}>
|
||||
{i18n.t('version:noFurtherVersionsFound')}
|
||||
</div>
|
||||
)}
|
||||
{versionCount > 0 && (
|
||||
<React.Fragment>
|
||||
{/* <div className={`${baseClass}__version-count`}>
|
||||
{t(versionCount === 1 ? 'versionCount_one' : 'versionCount_many', {
|
||||
<div className={`${baseClass}__version-count`}>
|
||||
{i18n.t(
|
||||
versionCount === 1 ? 'version:versionCount_one' : 'version:versionCount_many',
|
||||
{
|
||||
count: versionCount,
|
||||
})}
|
||||
</div> */}
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
columns={buildVersionColumns({
|
||||
config,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
docID: id,
|
||||
t: i18n.t,
|
||||
})}
|
||||
data={versionsData?.docs}
|
||||
/>
|
||||
@@ -87,7 +101,7 @@ export const DefaultVersionsView: React.FC<DefaultVersionsViewProps> = (props) =
|
||||
{versionsData.totalPages > 1 && versionsData.totalPages !== versionsData.page
|
||||
? versionsData.limit * versionsData.page
|
||||
: versionsData.totalDocs}{' '}
|
||||
{/* {t('of')} {versionsData.totalDocs} */}
|
||||
{i18n.t('general:of')} {versionsData.totalDocs}
|
||||
</div>
|
||||
<PerPage
|
||||
limit={limit ? Number(limit) : 10}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
import type { PaginatedDocs } from 'payload/database'
|
||||
import type { Version } from '@payloadcms/ui'
|
||||
import { User } from 'payload/auth'
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
|
||||
export type DefaultVersionsViewProps = {
|
||||
canAccessAdmin: boolean
|
||||
@@ -19,4 +20,5 @@ export type DefaultVersionsViewProps = {
|
||||
id: string
|
||||
user: User
|
||||
limit: number
|
||||
i18n: I18n
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// import type { TFunction } from 'react-i18next'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import type {
|
||||
@@ -15,6 +13,7 @@ import {
|
||||
SortColumn,
|
||||
} from '@payloadcms/ui'
|
||||
import Link from 'next/link'
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
|
||||
type CreatedAtCellProps = {
|
||||
config: SanitizedConfig
|
||||
@@ -23,6 +22,7 @@ type CreatedAtCellProps = {
|
||||
date: string
|
||||
versionID: string
|
||||
docID: string
|
||||
i18n: I18n
|
||||
}
|
||||
|
||||
const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
|
||||
@@ -32,6 +32,7 @@ const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
|
||||
collectionConfig,
|
||||
// date,
|
||||
globalConfig,
|
||||
i18n,
|
||||
}) => {
|
||||
const {
|
||||
routes: { admin },
|
||||
@@ -47,6 +48,7 @@ const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
|
||||
return (
|
||||
<Link href={to}>
|
||||
Date here
|
||||
{/* TODO(i18n) */}
|
||||
{/* {date && formatDate(date, dateFormat, i18n?.language)} */}
|
||||
</Link>
|
||||
)
|
||||
@@ -59,25 +61,21 @@ export const buildVersionColumns = ({
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
docID,
|
||||
i18n: { t },
|
||||
i18n,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
docID?: string
|
||||
// t: TFunction,
|
||||
i18n: I18n
|
||||
}): Column[] => [
|
||||
{
|
||||
name: '',
|
||||
accessor: 'updatedAt',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: (
|
||||
<SortColumn
|
||||
label="Updated At"
|
||||
// label={t('general:updatedAt')}
|
||||
name="updatedAt"
|
||||
/>
|
||||
),
|
||||
Heading: <SortColumn label={t('general:updatedAt')} name="updatedAt" />,
|
||||
renderCell: (row, data) => (
|
||||
<CreatedAtCell
|
||||
config={config}
|
||||
@@ -86,6 +84,7 @@ export const buildVersionColumns = ({
|
||||
globalConfig={globalConfig}
|
||||
versionID={row?.id}
|
||||
docID={docID}
|
||||
i18n={i18n}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -96,14 +95,7 @@ export const buildVersionColumns = ({
|
||||
accessor: 'id',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: (
|
||||
<SortColumn
|
||||
disable
|
||||
label="ID"
|
||||
// label={t('versionID')}
|
||||
name="id"
|
||||
/>
|
||||
),
|
||||
Heading: <SortColumn disable label={t('version:versionID')} name="id" />,
|
||||
renderCell: (row, data) => <TextCell>{data}</TextCell>,
|
||||
},
|
||||
label: '',
|
||||
@@ -113,40 +105,25 @@ export const buildVersionColumns = ({
|
||||
accessor: 'autosave',
|
||||
active: true,
|
||||
components: {
|
||||
Heading: (
|
||||
<SortColumn
|
||||
disable
|
||||
label="Type"
|
||||
// label={t('type')}
|
||||
name="autosave"
|
||||
/>
|
||||
),
|
||||
Heading: <SortColumn disable label={t('version:type')} name="autosave" />,
|
||||
renderCell: (row) => (
|
||||
<TextCell>
|
||||
{row?.autosave && (
|
||||
<React.Fragment>
|
||||
<Pill>
|
||||
Autosave
|
||||
{/* {t('autosave')} */}
|
||||
{t('version:autosave')}
|
||||
</Pill>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{row?.version._status === 'published' && (
|
||||
<React.Fragment>
|
||||
<Pill pillStyle="success">
|
||||
Published
|
||||
{/* {t('published')} */}
|
||||
</Pill>
|
||||
<Pill pillStyle="success">{t('version:published')}</Pill>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{row?.version._status === 'draft' && (
|
||||
<Pill>
|
||||
Draft
|
||||
{/* {t('draft')} */}
|
||||
</Pill>
|
||||
)}
|
||||
{row?.version._status === 'draft' && <Pill>{t('version:draft')}</Pill>}
|
||||
</TextCell>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ export const VersionsView = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
searchParams: { [key: string]: string | string[] | undefined }
|
||||
}) => {
|
||||
const { config, payload, permissions, user } = await initPage({
|
||||
const { config, payload, permissions, user, i18n } = await initPage({
|
||||
configPromise,
|
||||
redirectUnauthenticatedUser: true,
|
||||
collectionSlug,
|
||||
@@ -193,6 +193,7 @@ export const VersionsView = async ({
|
||||
user,
|
||||
versionsData,
|
||||
limit: limit ? parseInt(limit as string, 10) : undefined,
|
||||
i18n,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,7 +16,7 @@ export const me = async ({ req }: { req: PayloadRequest }): Promise<Response> =>
|
||||
return Response.json(
|
||||
{
|
||||
...result,
|
||||
message: 'Successfully retrieved me user.',
|
||||
message: req.t('authentication:account'),
|
||||
},
|
||||
{
|
||||
status: httpStatus.OK,
|
||||
|
||||
@@ -28,6 +28,6 @@ export const create = async ({ req }: { req: PayloadRequest }): Promise<Response
|
||||
// 'message',
|
||||
// )
|
||||
return Response.json(doc, {
|
||||
status: httpStatus.OK,
|
||||
status: httpStatus.CREATED,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import type { PayloadRequest, Where } from 'payload/types'
|
||||
|
||||
import { isNumber } from 'payload/utilities'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { deleteOperation } from 'payload/operations'
|
||||
|
||||
// TODO(JARROD): pattern to catch errors and return correct Response
|
||||
@@ -19,21 +19,23 @@ export const deleteDoc = async ({ req }: { req: PayloadRequest }): Promise<Respo
|
||||
})
|
||||
|
||||
if (result.errors.length === 0) {
|
||||
// const message = req.t('general:deletedCountSuccessfully', {
|
||||
// count: result.docs.length,
|
||||
// label: getTranslation(
|
||||
// req.collection.config.labels[result.docs.length > 1 ? 'plural' : 'singular'],
|
||||
// req.i18n,
|
||||
// ),
|
||||
// })
|
||||
|
||||
// res.status(httpStatus.OK).json({
|
||||
// ...formatSuccessResponse(message, 'message'),
|
||||
// ...result,
|
||||
// })
|
||||
return Response.json(result, {
|
||||
status: httpStatus.OK,
|
||||
const message = req.t('general:deletedCountSuccessfully', {
|
||||
count: result.docs.length,
|
||||
label: getTranslation(
|
||||
req.collection.config.labels[result.docs.length > 1 ? 'plural' : 'singular'],
|
||||
req.i18n,
|
||||
),
|
||||
})
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
...result,
|
||||
message,
|
||||
},
|
||||
{
|
||||
status: httpStatus.OK,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// const total = result.docs.length + result.errors.length
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { isNumber } from 'payload/utilities'
|
||||
import { deleteByIDOperation } from 'payload/operations'
|
||||
|
||||
@@ -35,8 +34,8 @@ export const deleteByID = async ({
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
...doc,
|
||||
// ...formatSuccessResponse(req.t('general:successfullyDeleted'), 'message'),
|
||||
doc,
|
||||
message: req.t('general:deletedSuccessfully'),
|
||||
},
|
||||
{
|
||||
status: httpStatus.OK,
|
||||
|
||||
@@ -15,6 +15,7 @@ export const findByID = async ({
|
||||
const { searchParams } = new URL(req.url)
|
||||
const depth = searchParams.get('depth')
|
||||
|
||||
try {
|
||||
const result = await findByIDOperation({
|
||||
id,
|
||||
collection: req.collection,
|
||||
@@ -26,4 +27,14 @@ export const findByID = async ({
|
||||
return Response.json(result, {
|
||||
status: httpStatus.OK,
|
||||
})
|
||||
} catch (error) {
|
||||
return Response.json(
|
||||
{
|
||||
message: req.t('error:notFound'),
|
||||
},
|
||||
{
|
||||
status: httpStatus.NOT_FOUND,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,8 @@ const handleCustomEndpoints = ({
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const GET = async (
|
||||
@@ -141,12 +143,13 @@ export const GET = async (
|
||||
})
|
||||
|
||||
if (req?.collection) {
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
entitySlug: slug1,
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.collection.config?.endpoints || [],
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
|
||||
switch (slug.length) {
|
||||
case 1:
|
||||
@@ -176,12 +179,13 @@ export const GET = async (
|
||||
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
|
||||
|
||||
if (slug2) {
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
entitySlug: `${slug1}/${slug2}`,
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: globalConfig?.endpoints || [],
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
}
|
||||
|
||||
switch (slug.length) {
|
||||
@@ -200,15 +204,18 @@ export const GET = async (
|
||||
}
|
||||
} else {
|
||||
// root routes
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.payload.config.endpoints,
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
|
||||
if (slug.length === 1 && slug1 === 'access') {
|
||||
return endpoints.root.GET.access({ req })
|
||||
}
|
||||
|
||||
return new Response('Route Not Found', { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,12 +228,13 @@ export const POST = async (
|
||||
const req = await createPayloadRequest({ request, config, params: { collection: slug1 } })
|
||||
|
||||
if (req?.collection) {
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
entitySlug: slug1,
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.collection.config?.endpoints || [],
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
|
||||
switch (slug.length) {
|
||||
case 1:
|
||||
@@ -256,12 +264,13 @@ export const POST = async (
|
||||
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
|
||||
|
||||
if (slug2) {
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
entitySlug: `${slug1}/${slug2}`,
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: globalConfig?.endpoints || [],
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
}
|
||||
|
||||
switch (slug.length) {
|
||||
@@ -279,11 +288,12 @@ export const POST = async (
|
||||
}
|
||||
} else {
|
||||
// root routes
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.payload.config.endpoints,
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,12 +312,13 @@ export const DELETE = async (
|
||||
})
|
||||
|
||||
if (req?.collection) {
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
entitySlug: slug1,
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.collection.config?.endpoints || [],
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
|
||||
switch (slug.length) {
|
||||
case 1:
|
||||
@@ -321,11 +332,12 @@ export const DELETE = async (
|
||||
}
|
||||
} else {
|
||||
// root routes
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.payload.config.endpoints,
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,12 +356,13 @@ export const PATCH = async (
|
||||
})
|
||||
|
||||
if (req?.collection) {
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
entitySlug: slug1,
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.collection.config?.endpoints || [],
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
|
||||
switch (slug.length) {
|
||||
case 1:
|
||||
@@ -363,10 +376,11 @@ export const PATCH = async (
|
||||
}
|
||||
} else {
|
||||
// root routes
|
||||
await handleCustomEndpoints({
|
||||
const customEndpointResponse = await handleCustomEndpoints({
|
||||
payloadRequest: req,
|
||||
request,
|
||||
endpoints: req.payload.config.endpoints,
|
||||
})
|
||||
if (customEndpointResponse) return customEndpointResponse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,21 @@ import { SanitizedConfig } from 'payload/types'
|
||||
import { cache } from 'react'
|
||||
|
||||
export const auth = cache(
|
||||
async ({ headers, config }: { headers: any; config: Promise<SanitizedConfig> }) => {
|
||||
async ({
|
||||
headers,
|
||||
config,
|
||||
cookies,
|
||||
}: {
|
||||
headers: any
|
||||
config: Promise<SanitizedConfig>
|
||||
cookies: Map<string, string>
|
||||
}) => {
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
const user = await getAuthenticatedUser({
|
||||
headers,
|
||||
payload,
|
||||
cookies,
|
||||
})
|
||||
|
||||
const permissions = await getAccessResults({
|
||||
|
||||
@@ -2,7 +2,9 @@ import type { SanitizedConfig, PayloadRequest, CustomPayloadRequest } from 'payl
|
||||
import { getAuthenticatedUser } from 'payload/auth'
|
||||
import { PayloadT, getPayload } from 'payload'
|
||||
import { URL } from 'url'
|
||||
import { i18nInit } from 'payload/utilities'
|
||||
import { parseCookies } from './cookies'
|
||||
import { getRequestLanguage } from './getRequestLanguage'
|
||||
import { getNextI18n } from './getNextI18n'
|
||||
|
||||
type GetRequestLocalesArgs = {
|
||||
localization: Exclude<PayloadT['config']['localization'], false>
|
||||
@@ -90,7 +92,17 @@ export const createPayloadRequest = async ({
|
||||
requestFallbackLocale = locales.fallbackLocale
|
||||
}
|
||||
|
||||
const i18n = i18nInit(config.i18n)
|
||||
const cookies = parseCookies(request.headers)
|
||||
const language = getRequestLanguage({
|
||||
headers: request.headers,
|
||||
cookies,
|
||||
})
|
||||
|
||||
const i18n = getNextI18n({
|
||||
config,
|
||||
language,
|
||||
translationContext: 'api',
|
||||
})
|
||||
|
||||
const customRequest: CustomPayloadRequest = {
|
||||
payload,
|
||||
@@ -119,6 +131,7 @@ export const createPayloadRequest = async ({
|
||||
payload,
|
||||
headers: req.headers,
|
||||
isGraphQL,
|
||||
cookies,
|
||||
})
|
||||
|
||||
return req
|
||||
|
||||
20
packages/next/src/utilities/getNextI18n.ts
Normal file
20
packages/next/src/utilities/getNextI18n.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations as clientTranslations } from '@payloadcms/translations/client'
|
||||
import { translations as apiTranslations } from '@payloadcms/translations/api'
|
||||
|
||||
export const getNextI18n = ({
|
||||
config,
|
||||
language,
|
||||
translationContext = 'client',
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
language: string
|
||||
translationContext?: 'api' | 'client'
|
||||
}): ReturnType<typeof initI18n> => {
|
||||
return initI18n({
|
||||
config: config.i18n,
|
||||
language,
|
||||
translations: translationContext === 'api' ? apiTranslations : clientTranslations,
|
||||
})
|
||||
}
|
||||
24
packages/next/src/utilities/getNextT.ts
Normal file
24
packages/next/src/utilities/getNextT.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { initTFunction } from '@payloadcms/translations'
|
||||
import { cookies, headers } from 'next/headers'
|
||||
import { getRequestLanguage } from './getRequestLanguage'
|
||||
|
||||
export const getNextT = ({
|
||||
config,
|
||||
language,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
language?: string
|
||||
}): TFunction => {
|
||||
const lang = language || getRequestLanguage({ cookies: cookies(), headers: headers() })
|
||||
|
||||
return initTFunction({
|
||||
language: lang,
|
||||
config: config.i18n,
|
||||
translations,
|
||||
})
|
||||
}
|
||||
24
packages/next/src/utilities/getRequestLanguage.ts
Normal file
24
packages/next/src/utilities/getRequestLanguage.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies'
|
||||
import { matchLanguage } from '@payloadcms/translations'
|
||||
|
||||
type GetRequestLanguageArgs = {
|
||||
cookies: Map<string, string> | ReadonlyRequestCookies
|
||||
headers: Request['headers']
|
||||
defaultLanguage?: string
|
||||
}
|
||||
|
||||
export const getRequestLanguage = ({
|
||||
headers,
|
||||
cookies,
|
||||
defaultLanguage = 'en',
|
||||
}: GetRequestLanguageArgs): string => {
|
||||
const acceptLanguage = headers.get('Accept-Language')
|
||||
const cookieLanguage = cookies.get('lng')
|
||||
|
||||
let reqLanguage =
|
||||
acceptLanguage ||
|
||||
(typeof cookieLanguage === 'string' ? cookieLanguage : cookieLanguage.value) ||
|
||||
defaultLanguage
|
||||
|
||||
return matchLanguage(reqLanguage)
|
||||
}
|
||||
@@ -9,6 +9,9 @@ import type {
|
||||
SanitizedGlobalConfig,
|
||||
} from 'payload/types'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { parseCookies } from './cookies'
|
||||
import { getNextI18n } from './getNextI18n'
|
||||
import { getRequestLanguage } from './getRequestLanguage'
|
||||
import { findLocaleFromCode } from '../../../ui/src/utilities/findLocaleFromCode'
|
||||
|
||||
export const initPage = async ({
|
||||
@@ -28,16 +31,20 @@ export const initPage = async ({
|
||||
permissions: Awaited<ReturnType<typeof auth>>['permissions']
|
||||
user: Awaited<ReturnType<typeof auth>>['user']
|
||||
config: SanitizedConfig
|
||||
i18n: ReturnType<typeof getNextI18n>
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
locale: ReturnType<typeof findLocaleFromCode>
|
||||
}> => {
|
||||
const headers = getHeaders()
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const { permissions, user } = await auth({
|
||||
headers,
|
||||
config: configPromise,
|
||||
cookies,
|
||||
})
|
||||
const language = getRequestLanguage({ cookies, headers })
|
||||
|
||||
const config = await configPromise
|
||||
|
||||
@@ -52,6 +59,7 @@ export const initPage = async ({
|
||||
config: configPromise,
|
||||
})
|
||||
|
||||
const i18n = getNextI18n({ config, language })
|
||||
let collectionConfig: SanitizedCollectionConfig
|
||||
let globalConfig: SanitizedGlobalConfig
|
||||
|
||||
@@ -75,6 +83,7 @@ export const initPage = async ({
|
||||
permissions,
|
||||
user,
|
||||
config,
|
||||
i18n,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
locale,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { AuthStrategyFunctionArgs } from 'payload/auth'
|
||||
import { parseCookies } from './cookies'
|
||||
|
||||
export const extractJWT = (
|
||||
args: Pick<AuthStrategyFunctionArgs, 'headers' | 'payload'>,
|
||||
args: Pick<AuthStrategyFunctionArgs, 'headers' | 'payload' | 'cookies'>,
|
||||
): null | string => {
|
||||
const { headers, payload } = args
|
||||
const { headers, payload, cookies } = args
|
||||
|
||||
const jwtFromHeader = headers.get('Authorization')
|
||||
const origin = headers.get('Origin')
|
||||
@@ -18,7 +17,6 @@ export const extractJWT = (
|
||||
return jwtFromHeader.replace('Bearer ', '')
|
||||
}
|
||||
|
||||
const cookies = parseCookies(headers)
|
||||
const tokenCookieName = `${payload.config.cookiePrefix}-token`
|
||||
const cookieToken = cookies?.get(tokenCookieName)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"src/**/*.spec.tsx"
|
||||
],
|
||||
"include": [
|
||||
"src/translations/*.json",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.d.ts",
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"@faceless-ui/scroll-info": "1.3.0",
|
||||
"@faceless-ui/window-info": "2.1.1",
|
||||
"@monaco-editor/react": "4.5.1",
|
||||
"@payloadcms/translations": "workspace:^",
|
||||
"body-scroll-lock": "4.0.0-beta.0",
|
||||
"bson-objectid": "2.0.4",
|
||||
"conf": "10.2.0",
|
||||
@@ -76,9 +77,6 @@
|
||||
"graphql-scalars": "1.22.2",
|
||||
"graphql-type-json": "0.3.2",
|
||||
"http-status": "1.6.2",
|
||||
"i18next": "22.5.1",
|
||||
"i18next-browser-languagedetector": "6.1.8",
|
||||
"i18next-http-middleware": "3.3.2",
|
||||
"is-hotkey": "0.2.0",
|
||||
"is-plain-object": "5.0.0",
|
||||
"json-schema-to-typescript": "11.0.3",
|
||||
@@ -101,7 +99,6 @@
|
||||
"react-animate-height": "2.1.2",
|
||||
"react-datepicker": "4.16.0",
|
||||
"react-diff-viewer-continued": "3.2.6",
|
||||
"react-i18next": "11.18.6",
|
||||
"react-image-crop": "10.1.8",
|
||||
"react-select": "5.7.4",
|
||||
"react-toastify": "8.2.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -8,7 +9,6 @@ import type { SanitizedGlobalConfig } from '../../../../globals/config/types'
|
||||
import type { FieldTypes } from '../../forms/field-types'
|
||||
import type { EditViewProps } from '../types'
|
||||
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
import { DocumentControls } from '../../elements/DocumentControls'
|
||||
import { DocumentFields } from '../../elements/DocumentFields'
|
||||
import { LeaveWithoutSaving } from '../../modals/LeaveWithoutSaving'
|
||||
|
||||
@@ -54,7 +54,6 @@ export const forgotPasswordOperation = async (incomingArgs: Arguments): Promise<
|
||||
req: {
|
||||
payload: { config, emailOptions, sendEmail: email },
|
||||
payload,
|
||||
t,
|
||||
},
|
||||
req,
|
||||
} = args
|
||||
@@ -103,11 +102,11 @@ export const forgotPasswordOperation = async (incomingArgs: Arguments): Promise<
|
||||
? config.serverURL
|
||||
: `${protocol}://${req.headers.get('host')}`
|
||||
|
||||
let html = `${t('authentication:youAreReceivingResetPassword')}
|
||||
let html = `${req.t('authentication:youAreReceivingResetPassword')}
|
||||
<a href="${serverURL}${config.routes.admin}/reset/${token}">
|
||||
${serverURL}${config.routes.admin}/reset/${token}
|
||||
</a>
|
||||
${t('authentication:youDidNotRequestPassword')}`
|
||||
${req.t('authentication:youDidNotRequestPassword')}`
|
||||
|
||||
if (typeof collectionConfig.auth.forgotPassword.generateEmailHTML === 'function') {
|
||||
html = await collectionConfig.auth.forgotPassword.generateEmailHTML({
|
||||
@@ -117,7 +116,7 @@ export const forgotPasswordOperation = async (incomingArgs: Arguments): Promise<
|
||||
})
|
||||
}
|
||||
|
||||
let subject = t('authentication:resetYourPassword')
|
||||
let subject = req.t('authentication:resetYourPassword')
|
||||
|
||||
if (typeof collectionConfig.auth.forgotPassword.generateEmailSubject === 'function') {
|
||||
subject = await collectionConfig.auth.forgotPassword.generateEmailSubject({
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Result } from '../forgotPassword'
|
||||
|
||||
import { getDataLoader } from '../../../collections/dataloader'
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { forgotPasswordOperation } from '../forgotPassword'
|
||||
|
||||
@@ -43,11 +43,13 @@ async function localForgotPassword<T extends keyof GeneratedTypes['collections']
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.payload = payload
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
return forgotPasswordOperation({
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Result } from '../login'
|
||||
|
||||
import { getDataLoader } from '../../../collections/dataloader'
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { loginOperation } from '../login'
|
||||
|
||||
@@ -49,13 +49,15 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.payload = payload
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
req.locale = undefined
|
||||
req.fallbackLocale = undefined
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
const args = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Result } from '../resetPassword'
|
||||
|
||||
import { getDataLoader } from '../../../collections/dataloader'
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { resetPasswordOperation } from '../resetPassword'
|
||||
|
||||
@@ -44,11 +44,13 @@ async function localResetPassword<T extends keyof GeneratedTypes['collections']>
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payload = payload
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
return resetPasswordOperation({
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PayloadRequest } from '../../../types'
|
||||
|
||||
import { getDataLoader } from '../../../collections/dataloader'
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { unlockOperation } from '../unlock'
|
||||
|
||||
@@ -39,11 +39,13 @@ async function localUnlock<T extends keyof GeneratedTypes['collections']>(
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payload = payload
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
return unlockOperation({
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { GeneratedTypes } from '../../../'
|
||||
import type { PayloadRequest } from '../../../types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { verifyEmailOperation } from '../verifyEmail'
|
||||
|
||||
@@ -29,9 +29,12 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payload = payload
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
|
||||
return verifyEmailOperation({
|
||||
collection,
|
||||
|
||||
@@ -40,7 +40,6 @@ async function sendVerificationEmail(args: Args): Promise<void> {
|
||||
const verificationURL = `${serverURL}${config.routes.admin}/${collectionConfig.slug}/verify/${token}`
|
||||
|
||||
let html = `${req.t('authentication:newAccountCreated', {
|
||||
interpolation: { escapeValue: false },
|
||||
serverURL: config.serverURL,
|
||||
verificationURL,
|
||||
})}`
|
||||
|
||||
@@ -90,6 +90,7 @@ type GenerateForgotPasswordEmailSubject = (args?: {
|
||||
}) => Promise<string> | string
|
||||
|
||||
export type AuthStrategyFunctionArgs = {
|
||||
cookies?: Map<string, string>
|
||||
headers: Request['headers']
|
||||
isGraphQL?: boolean
|
||||
payload: PayloadT
|
||||
|
||||
@@ -209,7 +209,7 @@ export const createOperation = async <TSlug extends keyof GeneratedTypes['collec
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!collectionConfig.upload.disableLocalStorage) {
|
||||
// await uploadFiles(payload, filesToUpload, req.t) // TODO: this was temporarily commented out bc it throws Sharp compilation errors in Next.js
|
||||
// await uploadFiles(payload, filesToUpload, req) // TODO: this was temporarily commented out bc it throws Sharp compilation errors in Next.js
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -66,7 +66,6 @@ export const deleteOperation = async <TSlug extends keyof GeneratedTypes['collec
|
||||
locale,
|
||||
payload: { config },
|
||||
payload,
|
||||
t,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -139,7 +138,7 @@ export const deleteOperation = async <TSlug extends keyof GeneratedTypes['collec
|
||||
config,
|
||||
doc,
|
||||
overrideDelete: true,
|
||||
t,
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -57,7 +57,6 @@ export const deleteByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
||||
req: {
|
||||
payload: { config },
|
||||
payload,
|
||||
t,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -101,15 +100,15 @@ export const deleteByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
||||
where: combineQueries({ id: { equals: id } }, accessResults),
|
||||
})
|
||||
|
||||
if (!docToDelete && !hasWhereAccess) throw new NotFound(t)
|
||||
if (!docToDelete && hasWhereAccess) throw new Forbidden(t)
|
||||
if (!docToDelete && !hasWhereAccess) throw new NotFound(req.t)
|
||||
if (!docToDelete && hasWhereAccess) throw new Forbidden(req.t)
|
||||
|
||||
await deleteAssociatedFiles({
|
||||
collectionConfig,
|
||||
config,
|
||||
doc: docToDelete,
|
||||
overrideDelete: true,
|
||||
t,
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -56,7 +56,7 @@ export const findByIDOperation = async <T extends TypeWithID>(
|
||||
disableErrors,
|
||||
draft: draftEnabled = false,
|
||||
overrideAccess = false,
|
||||
req: { locale, t },
|
||||
req: { locale },
|
||||
req,
|
||||
showHiddenFields,
|
||||
} = args
|
||||
@@ -89,7 +89,7 @@ export const findByIDOperation = async <T extends TypeWithID>(
|
||||
// Find by ID
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (!findOneArgs.where.and[0].id) throw new NotFound(t)
|
||||
if (!findOneArgs.where.and[0].id) throw new NotFound(req.t)
|
||||
|
||||
if (!req.findByID) {
|
||||
req.findByID = { [transactionID]: {} }
|
||||
@@ -113,7 +113,7 @@ export const findByIDOperation = async <T extends TypeWithID>(
|
||||
|
||||
if (!result) {
|
||||
if (!disableErrors) {
|
||||
throw new NotFound(t)
|
||||
throw new NotFound(req.t)
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -34,7 +34,7 @@ export const findVersionByIDOperation = async <T extends TypeWithID = any>(
|
||||
depth,
|
||||
disableErrors,
|
||||
overrideAccess,
|
||||
req: { locale, payload, t },
|
||||
req: { locale, payload },
|
||||
req,
|
||||
showHiddenFields,
|
||||
} = args
|
||||
@@ -78,8 +78,8 @@ export const findVersionByIDOperation = async <T extends TypeWithID = any>(
|
||||
|
||||
if (!result) {
|
||||
if (!disableErrors) {
|
||||
if (!hasWhereAccess) throw new NotFound(t)
|
||||
if (hasWhereAccess) throw new Forbidden(t)
|
||||
if (!hasWhereAccess) throw new NotFound(req.t)
|
||||
if (hasWhereAccess) throw new Forbidden(req.t)
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { Document } from '../../../types'
|
||||
import type { File } from '../../../uploads/types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import getFileByPath from '../../../uploads/getFileByPath'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
@@ -72,18 +72,20 @@ export default async function createLocal<TSlug extends keyof GeneratedTypes['co
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.locale = locale ?? req?.locale ?? defaultLocale
|
||||
req.fallbackLocale = fallbackLocale !== 'undefined' ? fallbackLocale : defaultLocale
|
||||
req.payload = payload
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
req.files = {
|
||||
file: (file ?? (await getFileByPath(filePath))) as UploadedFile, // TODO(NATIVE_REQUEST): fix this type
|
||||
}
|
||||
|
||||
if (typeof user !== 'undefined') req.user = user
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
return createOperation<TSlug>({
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Document, Where } from '../../../types'
|
||||
import type { BulkOperationResult } from '../../config/types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
import { deleteOperation } from '../delete'
|
||||
@@ -81,18 +81,20 @@ async function deleteLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = incomingReq?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
const req = {
|
||||
fallbackLocale: typeof fallbackLocale !== 'undefined' ? fallbackLocale : defaultLocale,
|
||||
i18n: i18nInit(payload.config.i18n),
|
||||
i18n,
|
||||
locale: locale ?? defaultLocale,
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req, context)
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
const args = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PayloadRequest, RequestContext } from '../../../types'
|
||||
import type { Document, Where } from '../../../types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
import { findOperation } from '../find'
|
||||
@@ -78,13 +78,15 @@ export default async function findLocal<T extends keyof GeneratedTypes['collecti
|
||||
fallbackLocaleToUse = fallbackLocale
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.locale = locale ?? req?.locale ?? defaultLocale
|
||||
req.fallbackLocale = fallbackLocaleToUse
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
req.payload = payload
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
if (typeof user !== 'undefined') req.user = user
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PayloadRequest, RequestContext } from '../../../types'
|
||||
import type { Document } from '../../../types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
import { findByIDOperation } from '../findByID'
|
||||
@@ -70,15 +70,17 @@ export default async function findByIDLocal<T extends keyof GeneratedTypes['coll
|
||||
fallbackLocaleToUse = fallbackLocale
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.locale = locale ?? req?.locale ?? defaultLocale
|
||||
req.fallbackLocale = fallbackLocaleToUse
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
req.payload = payload
|
||||
|
||||
if (typeof user !== 'undefined') req.user = user
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
return findByIDOperation<GeneratedTypes['collections'][T]>({
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Document } from '../../../types'
|
||||
import type { TypeWithVersion } from '../../../versions/types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
import { findVersionByIDOperation } from '../findVersionByID'
|
||||
@@ -68,13 +68,15 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
||||
fallbackLocaleToUse = fallbackLocale
|
||||
}
|
||||
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.locale = locale ?? req?.locale ?? defaultLocale
|
||||
req.fallbackLocale = fallbackLocaleToUse
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
req.i18n = i18n
|
||||
req.t = i18n.t
|
||||
req.payload = payload
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
return findVersionByIDOperation({
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Document, Where } from '../../../types'
|
||||
import type { TypeWithVersion } from '../../../versions/types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
import { findVersionsOperation } from '../findVersions'
|
||||
@@ -61,19 +61,20 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = i18nInit(payload.config.i18n)
|
||||
const req = {
|
||||
const i18n = incomingReq?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
const req: PayloadRequest = {
|
||||
fallbackLocale: typeof fallbackLocale !== 'undefined' ? fallbackLocale : defaultLocale,
|
||||
i18n,
|
||||
locale: locale ?? defaultLocale,
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req, context)
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
return findVersionsOperation({
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { PayloadRequest, RequestContext } from '../../../types'
|
||||
import type { Document } from '../../../types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
import { restoreVersionOperation } from '../restoreVersion'
|
||||
@@ -52,7 +52,8 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = i18nInit(payload.config.i18n)
|
||||
const i18n = incomingReq?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
const req = {
|
||||
fallbackLocale,
|
||||
i18n,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { File } from '../../../uploads/types'
|
||||
import type { BulkOperationResult } from '../../config/types'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import { getLocalI18n } from '../../../translations/getLocalI18n'
|
||||
import getFileByPath from '../../../uploads/getFileByPath'
|
||||
import { setRequestContext } from '../../../utilities/setRequestContext'
|
||||
import { getDataLoader } from '../../dataloader'
|
||||
@@ -86,7 +86,6 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
} = options
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
const i18n = i18nInit(payload.config.i18n)
|
||||
const defaultLocale = payload.config.localization
|
||||
? payload.config.localization?.defaultLocale
|
||||
: null
|
||||
@@ -97,6 +96,8 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
)
|
||||
}
|
||||
|
||||
const i18n = incomingReq?.i18n || getLocalI18n({ config: payload.config })
|
||||
|
||||
const req = {
|
||||
fallbackLocale: typeof fallbackLocale !== 'undefined' ? fallbackLocale : defaultLocale,
|
||||
files: {
|
||||
@@ -106,12 +107,12 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
locale: locale ?? defaultLocale,
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as unknown as PayloadRequest
|
||||
setRequestContext(req, context)
|
||||
|
||||
if (!req.t) req.t = req.i18n.t
|
||||
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
|
||||
|
||||
const args = {
|
||||
|
||||
@@ -36,7 +36,7 @@ export const restoreVersionOperation = async <T extends TypeWithID = any>(
|
||||
depth,
|
||||
overrideAccess = false,
|
||||
req,
|
||||
req: { locale, payload, t },
|
||||
req: { locale, payload },
|
||||
showHiddenFields,
|
||||
} = args
|
||||
|
||||
@@ -62,7 +62,7 @@ export const restoreVersionOperation = async <T extends TypeWithID = any>(
|
||||
const [rawVersion] = versionDocs
|
||||
|
||||
if (!rawVersion) {
|
||||
throw new NotFound(t)
|
||||
throw new NotFound(req.t)
|
||||
}
|
||||
|
||||
const parentDocID = rawVersion.parent
|
||||
@@ -89,8 +89,8 @@ export const restoreVersionOperation = async <T extends TypeWithID = any>(
|
||||
|
||||
const doc = await req.payload.db.findOne(findOneArgs)
|
||||
|
||||
if (!doc && !hasWherePolicy) throw new NotFound(t)
|
||||
if (!doc && hasWherePolicy) throw new Forbidden(t)
|
||||
if (!doc && !hasWherePolicy) throw new NotFound(req.t)
|
||||
if (!doc && hasWherePolicy) throw new Forbidden(req.t)
|
||||
|
||||
// /////////////////////////////////////
|
||||
// fetch previousDoc
|
||||
|
||||
@@ -74,7 +74,6 @@ export const updateOperation = async <TSlug extends keyof GeneratedTypes['collec
|
||||
locale,
|
||||
payload: { config },
|
||||
payload,
|
||||
t,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -186,7 +185,7 @@ export const updateOperation = async <TSlug extends keyof GeneratedTypes['collec
|
||||
// doc,
|
||||
// // files: filesToUpload,
|
||||
// overrideDelete: false,
|
||||
// t,
|
||||
// req,
|
||||
// })
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -228,7 +227,7 @@ export const updateOperation = async <TSlug extends keyof GeneratedTypes['collec
|
||||
// /////////////////////////////////////
|
||||
|
||||
// if (!collectionConfig.upload.disableLocalStorage) {
|
||||
// await uploadFiles(payload, filesToUpload, t)
|
||||
// await uploadFiles(payload, filesToUpload, req)
|
||||
// }
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -75,7 +75,6 @@ export const updateByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
||||
locale,
|
||||
payload: { config },
|
||||
payload,
|
||||
t,
|
||||
},
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -121,8 +120,8 @@ export const updateByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
||||
req,
|
||||
})
|
||||
|
||||
if (!docWithLocales && !hasWherePolicy) throw new NotFound(t)
|
||||
if (!docWithLocales && hasWherePolicy) throw new Forbidden(t)
|
||||
if (!docWithLocales && !hasWherePolicy) throw new NotFound(req.t)
|
||||
if (!docWithLocales && hasWherePolicy) throw new Forbidden(req.t)
|
||||
|
||||
const originalDoc = await afterRead({
|
||||
collection: collectionConfig,
|
||||
@@ -202,7 +201,7 @@ export const updateByIDOperation = async <TSlug extends keyof GeneratedTypes['co
|
||||
// /////////////////////////////////////
|
||||
|
||||
// if (!collectionConfig.upload.disableLocalStorage) {
|
||||
// await uploadFiles(payload, filesToUpload, t)
|
||||
// await uploadFiles(payload, filesToUpload, req)
|
||||
// }
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from 'path'
|
||||
|
||||
import type { Config } from './types'
|
||||
|
||||
export const defaults: Omit<Config, 'db' | 'editor'> = {
|
||||
export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
|
||||
admin: {
|
||||
avatar: 'default',
|
||||
buildPath: path.resolve(process.cwd(), './build'),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import merge from 'deepmerge'
|
||||
import { isPlainObject } from 'is-plain-object'
|
||||
|
||||
@@ -45,7 +46,7 @@ export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
|
||||
isMergeableObject: isPlainObject,
|
||||
}) as Config
|
||||
|
||||
if (!configWithDefaults.serverURL) {
|
||||
if (!configWithDefaults?.serverURL) {
|
||||
configWithDefaults.serverURL = ''
|
||||
}
|
||||
|
||||
@@ -84,6 +85,13 @@ export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
|
||||
}
|
||||
}
|
||||
|
||||
config.i18n = {
|
||||
fallbackLanguage: 'en',
|
||||
supportedLanguages: Object.keys(translations),
|
||||
translations,
|
||||
...(incomingConfig?.i18n ?? {}),
|
||||
}
|
||||
|
||||
configWithDefaults.collections.push(getPreferencesCollection(configWithDefaults))
|
||||
configWithDefaults.collections.push(migrationsCollection)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { I18nOptions } from '@payloadcms/translations'
|
||||
import type { Express } from 'express'
|
||||
import type { Options as ExpressFileUploadOptions } from 'express-fileupload'
|
||||
import type GraphQL from 'graphql'
|
||||
import type { InitOptions as i18nInitOptions } from 'i18next'
|
||||
import type { Transporter } from 'nodemailer'
|
||||
import type SMTPConnection from 'nodemailer/lib/smtp-connection'
|
||||
import type { DestinationStream, LoggerOptions } from 'pino'
|
||||
@@ -614,20 +614,8 @@ export type Config = {
|
||||
hooks?: {
|
||||
afterError?: AfterErrorHook
|
||||
}
|
||||
/**
|
||||
* Control the behaviour of the admin internationalisation.
|
||||
*
|
||||
* See i18next options.
|
||||
*
|
||||
* @default
|
||||
* {
|
||||
* fallbackLng: 'en',
|
||||
* debug: false,
|
||||
* supportedLngs: Object.keys(translations),
|
||||
* resources: translations,
|
||||
* }
|
||||
*/
|
||||
i18n?: i18nInitOptions
|
||||
/** i18n config settings */
|
||||
i18n?: I18nOptions
|
||||
/** Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. */
|
||||
indexSortableFields?: boolean
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
@@ -7,7 +8,7 @@ import APIError from './APIError'
|
||||
class AuthenticationError extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(
|
||||
t ? t('error:emailOrPasswordIncorrect') : 'The email or password provided is incorrect.',
|
||||
t ? t('error:emailOrPasswordIncorrect') : translations.en.error.emailOrPasswordIncorrect,
|
||||
httpStatus.UNAUTHORIZED,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
@@ -7,7 +8,7 @@ import APIError from './APIError'
|
||||
class ErrorDeletingFile extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(
|
||||
t ? t('error:deletingFile') : 'There was an error deleting file.',
|
||||
t ? t('error:deletingFile') : translations.en.error.deletingFile,
|
||||
httpStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
@@ -7,7 +8,7 @@ import APIError from './APIError'
|
||||
class FileUploadError extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(
|
||||
t ? t('error:problemUploadingFile') : 'There was a problem while uploading the file.',
|
||||
t ? t('error:problemUploadingFile') : translations.en.error.problemUploadingFile,
|
||||
httpStatus.BAD_REQUEST,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
@@ -7,7 +8,7 @@ import APIError from './APIError'
|
||||
class Forbidden extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(
|
||||
t ? t('error:notAllowedToPerformAction') : 'You are not allowed to perform this action.',
|
||||
t ? t('error:notAllowedToPerformAction') : translations.en.error.notAllowedToPerformAction,
|
||||
httpStatus.FORBIDDEN,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
|
||||
class LockedAuth extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(
|
||||
t
|
||||
? t('error:userLocked')
|
||||
: 'This user is locked due to having too many failed login attempts.',
|
||||
httpStatus.UNAUTHORIZED,
|
||||
)
|
||||
super(t ? t('error:userLocked') : translations.en.error.userLocked, httpStatus.UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
|
||||
class MissingFile extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(t ? t('error:noFilesUploaded') : 'No files were uploaded.', httpStatus.BAD_REQUEST)
|
||||
super(
|
||||
t ? t('error:noFilesUploaded') : translations.en.error.noFilesUploaded,
|
||||
httpStatus.BAD_REQUEST,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
|
||||
class NotFound extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(t ? t('error:notFound') : 'The requested resource was not found.', httpStatus.NOT_FOUND)
|
||||
super(t ? t('general:notFound') : translations.en.general.notFound, httpStatus.NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
|
||||
class QueryError extends APIError<{ path: string }[]> {
|
||||
constructor(results: { path: string }[], t?: TFunction) {
|
||||
const message = t
|
||||
? t('error:unspecific', { count: results.length })
|
||||
: `The following path${results.length === 1 ? '' : 's'} cannot be queried:`
|
||||
constructor(results: { path: string }[]) {
|
||||
const message = `The following path${results.length === 1 ? '' : 's'} cannot be queried:`
|
||||
|
||||
super(
|
||||
`${message} ${results.map((err) => err.path).join(', ')}`,
|
||||
httpStatus.BAD_REQUEST,
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
|
||||
class UnauthorizedError extends APIError {
|
||||
constructor(t?: TFunction) {
|
||||
super(
|
||||
t ? t('error:unauthorized') : 'Unauthorized, you must be logged in to make this request.',
|
||||
httpStatus.UNAUTHORIZED,
|
||||
)
|
||||
super(t ? t('error:unauthorized') : translations.en.error.unauthorized, httpStatus.UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import APIError from './APIError'
|
||||
@@ -8,7 +9,10 @@ class ValidationError extends APIError<{ field: string; message: string }[]> {
|
||||
constructor(results: { field: string; message: string }[], t?: TFunction) {
|
||||
const message = t
|
||||
? t('error:followingFieldsInvalid', { count: results.length })
|
||||
: `The following field${results.length === 1 ? ' is' : 's are'} invalid:`
|
||||
: results.length === 1
|
||||
? translations.en.error.followingFieldsInvalid_one
|
||||
: translations.en.error.followingFieldsInvalid_other
|
||||
|
||||
super(`${message} ${results.map((f) => f.field).join(', ')}`, httpStatus.BAD_REQUEST, results)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as errorHandler } from '../express/middleware/errorHandler'
|
||||
export { default as APIError } from './APIError'
|
||||
export { default as AuthenticationError } from './AuthenticationError'
|
||||
export { default as DuplicateCollection } from './DuplicateCollection'
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { defaultOptions } from '../translations/defaultOptions'
|
||||
@@ -3,7 +3,6 @@ export { promise as afterReadPromise } from '../fields/hooks/afterRead/promise'
|
||||
export { traverseFields as afterReadTraverseFields } from '../fields/hooks/afterRead/traverseFields'
|
||||
|
||||
export { extractTranslations } from '../translations/extractTranslations'
|
||||
export { i18nInit } from '../translations/init'
|
||||
export { combineMerge } from '../utilities/combineMerge'
|
||||
|
||||
export {
|
||||
@@ -22,10 +21,9 @@ export { formatLabels, formatNames, toWords } from '../utilities/formatLabels'
|
||||
export { getIDType } from '../utilities/getIDType'
|
||||
export { getObjectDotNotation } from '../utilities/getObjectDotNotation'
|
||||
|
||||
export { getTranslation } from '../utilities/getTranslation'
|
||||
export { default as getUniqueListBy } from '../utilities/getUniqueListBy'
|
||||
|
||||
export { isNumber } from '../utilities/isNumber'
|
||||
|
||||
export { isValidID } from '../utilities/isValidID'
|
||||
export { setsAreEqual } from '../utilities/setsAreEqual'
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { Payload } from '../payload'
|
||||
|
||||
async function initAdmin(ctx: Payload): Promise<void> {
|
||||
if (!ctx.config.admin.disable) {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
ctx.express.use(ctx.config.routes.admin, await ctx.config.admin.bundler.serve(ctx))
|
||||
} else {
|
||||
ctx.express.use(ctx.config.routes.admin, await ctx.config.admin.bundler.dev(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default initAdmin
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
|
||||
import passport from 'passport'
|
||||
|
||||
import type { SanitizedConfig } from '../../config/types'
|
||||
|
||||
export type PayloadAuthenticate = (req: Request, res: Response, next: NextFunction) => NextFunction
|
||||
|
||||
export default (config: SanitizedConfig): PayloadAuthenticate => {
|
||||
const defaultMethods = ['jwt', 'anonymous']
|
||||
|
||||
const methods = config.collections.reduce((enabledMethods, collection) => {
|
||||
if (typeof collection.auth === 'object') {
|
||||
const collectionMethods = [...enabledMethods]
|
||||
|
||||
if (Array.isArray(collection.auth.strategies)) {
|
||||
collection.auth.strategies.forEach(({ name, strategy }) => {
|
||||
collectionMethods.unshift(`${collection.slug}-${name ?? strategy.name}`)
|
||||
})
|
||||
}
|
||||
|
||||
if (collection.auth.useAPIKey) {
|
||||
collectionMethods.unshift(`${collection.slug}-api-key`)
|
||||
}
|
||||
|
||||
return collectionMethods
|
||||
}
|
||||
|
||||
return enabledMethods
|
||||
}, defaultMethods)
|
||||
|
||||
const authenticate = passport.authenticate(methods, { session: false })
|
||||
return authenticate
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
|
||||
export default (req: Request, _: Response, next: NextFunction): void => {
|
||||
if (req.body?._payload) {
|
||||
const payloadJSON = JSON.parse(req.body._payload)
|
||||
|
||||
req.body = {
|
||||
...req.body,
|
||||
...payloadJSON,
|
||||
}
|
||||
|
||||
delete req.body?._payload
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
|
||||
import type { SanitizedConfig } from '../../config/types'
|
||||
|
||||
export default (config: SanitizedConfig) => (req: Request, res: Response, next: NextFunction) => {
|
||||
if (config.cors) {
|
||||
res.header('Access-Control-Allow-Methods', 'PUT, PATCH, POST, GET, DELETE, OPTIONS')
|
||||
res.header(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Content-Encoding, x-apollo-tracing',
|
||||
)
|
||||
|
||||
if (config.cors === '*') {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
} else if (Array.isArray(config.cors) && config.cors.indexOf(req.headers.origin) > -1) {
|
||||
res.header('Access-Control-Allow-Credentials', 'true')
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { NextFunction, Response } from 'express'
|
||||
|
||||
import type { PayloadRequest } from '../types'
|
||||
|
||||
import { setRequestContext } from '../setRequestContext'
|
||||
|
||||
function defaultPayload(req: PayloadRequest, res: Response, next: NextFunction) {
|
||||
setRequestContext(req)
|
||||
next()
|
||||
}
|
||||
|
||||
export default defaultPayload
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { NextFunction, Response } from 'express'
|
||||
import type { Logger } from 'pino'
|
||||
|
||||
import httpStatus from 'http-status'
|
||||
|
||||
import type { SanitizedConfig } from '../../config/types'
|
||||
import type { ErrorResponse } from '../responses/formatError'
|
||||
import type { PayloadRequest } from '../types'
|
||||
|
||||
import APIError from '../../errors/APIError'
|
||||
import formatErrorResponse from '../responses/formatError'
|
||||
|
||||
export type ErrorHandler = (
|
||||
err: APIError,
|
||||
req: PayloadRequest,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => Promise<Response<ErrorResponse> | void>
|
||||
|
||||
// NextFunction must be passed for Express to use this middleware as error handler
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const errorHandler =
|
||||
(config: SanitizedConfig, logger: Logger) =>
|
||||
async (
|
||||
err: APIError,
|
||||
req: PayloadRequest,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
): Promise<Response<ErrorResponse> | void> => {
|
||||
let response = formatErrorResponse(err)
|
||||
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR
|
||||
|
||||
logger.error(err.stack)
|
||||
|
||||
// Internal server errors can contain anything, including potentially sensitive data.
|
||||
// Therefore, error details will be hidden from the response unless `config.debug` is `true`
|
||||
if (!config.debug && status === httpStatus.INTERNAL_SERVER_ERROR) {
|
||||
response = formatErrorResponse(new APIError('Something went wrong.'))
|
||||
}
|
||||
|
||||
if (config.debug && config.debug === true) {
|
||||
response.stack = err.stack
|
||||
}
|
||||
|
||||
if (req.collection && typeof req.collection.config.hooks.afterError === 'function') {
|
||||
;({ response, status } = (await req.collection.config.hooks.afterError(
|
||||
err,
|
||||
response,
|
||||
req.context,
|
||||
req.collection.config,
|
||||
)) || { response, status })
|
||||
}
|
||||
|
||||
if (typeof config.hooks.afterError === 'function') {
|
||||
;({ response, status } = (await config.hooks.afterError(
|
||||
err,
|
||||
response,
|
||||
req.context,
|
||||
req.collection.config,
|
||||
)) || {
|
||||
response,
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
res.status(status).send(response)
|
||||
}
|
||||
|
||||
export default errorHandler
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { Handler } from 'express'
|
||||
import type { InitOptions } from 'i18next'
|
||||
|
||||
import deepmerge from 'deepmerge'
|
||||
import i18next from 'i18next'
|
||||
import i18nHTTPMiddleware from 'i18next-http-middleware'
|
||||
|
||||
import { defaultOptions } from '../../translations/defaultOptions'
|
||||
|
||||
const i18nMiddleware = (options: InitOptions): Handler => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
i18next.use(new i18nHTTPMiddleware.LanguageDetector(defaultOptions.detection)).init({
|
||||
preload: defaultOptions.supportedLngs,
|
||||
...deepmerge(defaultOptions, options || {}),
|
||||
})
|
||||
|
||||
return i18nHTTPMiddleware.handle(i18next)
|
||||
}
|
||||
|
||||
export { i18nMiddleware }
|
||||
@@ -1,8 +0,0 @@
|
||||
const identifyAPI = (api) => {
|
||||
return (req, _, next) => {
|
||||
req.payloadAPI = api
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export default identifyAPI
|
||||
@@ -1,65 +0,0 @@
|
||||
import bodyParser from 'body-parser'
|
||||
import compression from 'compression'
|
||||
import express from 'express'
|
||||
import fileUpload from 'express-fileupload'
|
||||
import rateLimit from 'express-rate-limit'
|
||||
import methodOverride from 'method-override'
|
||||
import passport from 'passport'
|
||||
import qsMiddleware from 'qs-middleware'
|
||||
|
||||
import type { Payload } from '../../payload'
|
||||
import type { PayloadRequest } from '../types'
|
||||
|
||||
import localizationMiddleware from '../../localization/middleware'
|
||||
import authenticate from './authenticate'
|
||||
import convertPayload from './convertPayload'
|
||||
import corsHeaders from './corsHeaders'
|
||||
import defaultPayload from './defaultPayload'
|
||||
import { i18nMiddleware } from './i18n'
|
||||
import identifyAPI from './identifyAPI'
|
||||
|
||||
const middleware = (payload: Payload): any => {
|
||||
const rateLimitOptions: {
|
||||
max?: number
|
||||
skip?: (req: PayloadRequest) => boolean
|
||||
windowMs?: number
|
||||
} = {
|
||||
max: payload.config.rateLimit.max,
|
||||
windowMs: payload.config.rateLimit.window,
|
||||
}
|
||||
|
||||
if (typeof payload.config.rateLimit.skip === 'function')
|
||||
rateLimitOptions.skip = payload.config.rateLimit.skip
|
||||
|
||||
if (payload.config.express.middleware?.length) {
|
||||
payload.logger.warn(
|
||||
'express.middleware is deprecated. Please migrate to express.postMiddleware.',
|
||||
)
|
||||
}
|
||||
|
||||
return [
|
||||
defaultPayload,
|
||||
...(payload.config.express.preMiddleware || []),
|
||||
rateLimit(rateLimitOptions),
|
||||
passport.initialize(),
|
||||
i18nMiddleware(payload.config.i18n),
|
||||
identifyAPI('REST'),
|
||||
methodOverride('X-HTTP-Method-Override'),
|
||||
qsMiddleware({ arrayLimit: 1000, depth: 10 }),
|
||||
bodyParser.urlencoded({ extended: true }),
|
||||
compression(payload.config.express.compression),
|
||||
localizationMiddleware(payload.config.localization),
|
||||
express.json(payload.config.express.json),
|
||||
fileUpload({
|
||||
parseNested: true,
|
||||
...payload.config.upload,
|
||||
}),
|
||||
convertPayload,
|
||||
corsHeaders(payload.config),
|
||||
authenticate(payload.config),
|
||||
...(payload.config.express.middleware || []),
|
||||
...(payload.config.express.postMiddleware || []),
|
||||
]
|
||||
}
|
||||
|
||||
export default middleware
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user