Skip to content

Commit f29a07f

Browse files
fix(ui): invalid req.locale shows incorrect data (#14537)
If a view is loaded and the req.locale is one that you do not have access to due to filterAvailableLocales, the locale selector is set to the correct locale but the data is incorrect. This PR adjusts the locale on the req inside the applyLocaleFiltering if the incoming locale is not accessible due to filterAvailable locales.
1 parent d7f1ea2 commit f29a07f

File tree

8 files changed

+205
-33
lines changed

8 files changed

+205
-33
lines changed

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,27 @@ export const RootPage = async ({
247247
})
248248
await applyLocaleFiltering({ clientConfig, config, req })
249249

250+
// Ensure locale on req is still valid after filtering locales
251+
if (
252+
clientConfig.localization &&
253+
req.locale &&
254+
!clientConfig.localization.localeCodes.includes(req.locale)
255+
) {
256+
redirect(
257+
`${currentRoute}${qs.stringify(
258+
{
259+
...searchParams,
260+
locale: clientConfig.localization.localeCodes.includes(
261+
clientConfig.localization.defaultLocale,
262+
)
263+
? clientConfig.localization.defaultLocale
264+
: clientConfig.localization.localeCodes[0],
265+
},
266+
{ addQueryPrefix: true },
267+
)}`,
268+
)
269+
}
270+
250271
const visibleEntities = getVisibleEntities({ req })
251272

252273
const folderID = routeParams.folderID

packages/ui/src/providers/Locale/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import React, { createContext, use, useEffect, useRef, useState } from 'react'
88
import { findLocaleFromCode } from '../../utilities/findLocaleFromCode.js'
99
import { useAuth } from '../Auth/index.js'
1010
import { useConfig } from '../Config/index.js'
11-
import { usePreferences } from '../Preferences/index.js'
1211

1312
const LocaleContext = createContext({} as Locale)
1413

@@ -50,7 +49,6 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode; locale?: Loc
5049

5150
const defaultLocale = localization ? localization.defaultLocale : 'en'
5251

53-
const { getPreference, setPreference } = usePreferences()
5452
const localeFromParams = useSearchParams().get('locale')
5553

5654
const [locale, setLocale] = React.useState<Locale>(() => {
@@ -111,7 +109,7 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode; locale?: Loc
111109
}
112110

113111
void resetLocale()
114-
}, [defaultLocale, getPreference, localization, fetchURL, localeFromParams, user?.id])
112+
}, [defaultLocale, localization, fetchURL, localeFromParams, user?.id])
115113

116114
return (
117115
<LocaleContext value={locale}>

test/plugin-multi-tenant/collections/MenuItems.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export const MenuItems: CollectionConfig = {
8888
type: 'text',
8989
required: true,
9090
},
91+
{
92+
name: 'localizedName',
93+
type: 'text',
94+
localized: true,
95+
},
9196
{
9297
name: 'content',
9398
type: 'richText',

test/plugin-multi-tenant/collections/Tenants.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,29 @@ export const Tenants: CollectionConfig = {
8282
type: 'checkbox',
8383
label: 'Public Tenant',
8484
},
85+
{
86+
name: 'selectedLocales',
87+
type: 'select',
88+
hasMany: true,
89+
defaultValue: ['allLocales'],
90+
options: [
91+
{
92+
label: 'All Locales',
93+
value: 'allLocales',
94+
},
95+
{
96+
label: 'English',
97+
value: 'en',
98+
},
99+
{
100+
label: 'Spanish',
101+
value: 'es',
102+
},
103+
{
104+
label: 'French',
105+
value: 'fr',
106+
},
107+
],
108+
},
85109
],
86110
}

test/plugin-multi-tenant/config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const dirname = path.dirname(filename)
66

77
import type { Config as ConfigType } from './payload-types.js'
88

9+
import { getTenantFromCookie } from '../../packages/plugin-multi-tenant/src/utilities/getTenantFromCookie.js'
910
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
1011
import { AutosaveGlobal } from './collections/AutosaveGlobal.js'
1112
import { Menu } from './collections/Menu.js'
@@ -63,6 +64,32 @@ export default buildConfigWithDefaults({
6364
},
6465
}),
6566
],
67+
localization: {
68+
defaultLocale: 'en',
69+
locales: ['en', 'es', 'fr'],
70+
filterAvailableLocales: async ({ locales, req }) => {
71+
const tenant = getTenantFromCookie(req.headers, 'text')
72+
if (tenant) {
73+
const fullTenant = await req.payload.findByID({
74+
collection: 'tenants',
75+
id: tenant,
76+
})
77+
if (
78+
fullTenant &&
79+
Array.isArray(fullTenant.selectedLocales) &&
80+
fullTenant.selectedLocales.length > 0
81+
) {
82+
if (fullTenant.selectedLocales.includes('allLocales')) {
83+
return locales
84+
}
85+
return locales.filter((locale) =>
86+
fullTenant.selectedLocales?.includes(locale.code as any),
87+
)
88+
}
89+
}
90+
return locales
91+
},
92+
},
6693
typescript: {
6794
outputFile: path.resolve(dirname, 'payload-types.ts'),
6895
},

test/plugin-multi-tenant/e2e.spec.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import { fileURLToPath } from 'url'
99
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
1010
import type { Config } from './payload-types.js'
1111

12-
import { ensureCompilationIsDone, initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
12+
import {
13+
changeLocale,
14+
ensureCompilationIsDone,
15+
initPageConsoleErrorCatch,
16+
saveDocAndAssert,
17+
} from '../helpers.js'
1318
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
1419
import { loginClientSide } from '../helpers/e2e/auth/login.js'
1520
import { goToListDoc } from '../helpers/e2e/goToListDoc.js'
@@ -260,6 +265,34 @@ test.describe('Multi Tenant', () => {
260265
).toBeHidden()
261266
})
262267
})
268+
269+
test('should show correct filtered localized data', async () => {
270+
await loginClientSide({
271+
data: credentials.admin,
272+
page,
273+
serverURL,
274+
})
275+
276+
await setTenantFilter({
277+
page,
278+
tenant: 'Blue Dog',
279+
})
280+
281+
await changeLocale(page, 'es')
282+
283+
await setTenantFilter({
284+
page,
285+
tenant: 'Anchor Bar',
286+
})
287+
288+
await page.goto(menuItemsURL.list)
289+
290+
await expect(
291+
page.locator('.collection-list .table .cell-localizedName', {
292+
hasText: 'Popcorn EN',
293+
}),
294+
).toBeVisible()
295+
})
263296
})
264297

265298
test.describe('Documents', () => {
@@ -758,9 +791,12 @@ async function setTenantFilter({
758791
}: {
759792
page: Page
760793
tenant: string
761-
urlUtil: AdminUrlUtil
794+
urlUtil?: AdminUrlUtil
762795
}): Promise<void> {
763-
await page.goto(urlUtil.list)
796+
if (urlUtil) {
797+
await page.goto(urlUtil.list)
798+
}
799+
764800
await openNav(page)
765801
await selectInput({
766802
multiSelect: false,

0 commit comments

Comments
 (0)