feat(plugin-multi-tenant): allow customization of selector label (#11418)

### What?
Allows for custom labeling of the tenant selector shown in the sidebar.

Fixes https://github.com/payloadcms/payload/issues/11262
This commit is contained in:
Jarrod Flesch
2025-02-26 22:39:51 -05:00
committed by GitHub
parent 45cee23add
commit 958e195017
9 changed files with 69 additions and 6 deletions

View File

@@ -158,6 +158,16 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
rowFields?: never rowFields?: never
tenantFieldAccess?: never tenantFieldAccess?: never
} }
/**
* Customize tenant selector label
*
* Either a string or an object where the keys are locales and the values are the string labels
*/
tenantSelectorLabel?:
| Partial<{
[key in AcceptedLanguages]?: string
}>
| string
/** /**
* The slug for the tenant collection * The slug for the tenant collection
* *

View File

@@ -78,6 +78,7 @@
}, },
"devDependencies": { "devDependencies": {
"@payloadcms/eslint-config": "workspace:*", "@payloadcms/eslint-config": "workspace:*",
"@payloadcms/translations": "workspace:*",
"@payloadcms/ui": "workspace:*", "@payloadcms/ui": "workspace:*",
"payload": "workspace:*" "payload": "workspace:*"
}, },

View File

@@ -2,17 +2,17 @@
import type { ReactSelectOption } from '@payloadcms/ui' import type { ReactSelectOption } from '@payloadcms/ui'
import type { ViewTypes } from 'payload' import type { ViewTypes } from 'payload'
import { SelectInput } from '@payloadcms/ui'
import './index.scss' import './index.scss'
import { SelectInput, useTranslation } from '@payloadcms/ui'
import React from 'react' import React from 'react'
import { SELECT_ALL } from '../../constants.js' import { SELECT_ALL } from '../../constants.js'
import { useTenantSelection } from '../../providers/TenantSelectionProvider/index.client.js' import { useTenantSelection } from '../../providers/TenantSelectionProvider/index.client.js'
export const TenantSelector = ({ viewType }: { viewType?: ViewTypes }) => { export const TenantSelector = ({ label, viewType }: { label: string; viewType?: ViewTypes }) => {
const { options, selectedTenantID, setTenant } = useTenantSelection() const { options, selectedTenantID, setTenant } = useTenantSelection()
const { t } = useTranslation()
const handleChange = React.useCallback( const handleChange = React.useCallback(
(option: ReactSelectOption | ReactSelectOption[]) => { (option: ReactSelectOption | ReactSelectOption[]) => {
@@ -33,7 +33,7 @@ export const TenantSelector = ({ viewType }: { viewType?: ViewTypes }) => {
<div className="tenant-selector"> <div className="tenant-selector">
<SelectInput <SelectInput
isClearable={viewType === 'list'} isClearable={viewType === 'list'}
label="Tenant" label={t(label as any)}
name="setTenant" name="setTenant"
onChange={handleChange} onChange={handleChange}
options={options} options={options}

View File

@@ -3,4 +3,5 @@ export const defaults = {
tenantFieldName: 'tenant', tenantFieldName: 'tenant',
tenantsArrayFieldName: 'tenants', tenantsArrayFieldName: 'tenants',
tenantsArrayTenantFieldName: 'tenant', tenantsArrayTenantFieldName: 'tenant',
tenantSelectorLabel: 'Tenant',
} }

View File

@@ -1,3 +1,4 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { CollectionConfig, Config } from 'payload' import type { CollectionConfig, Config } from 'payload'
import type { MultiTenantPluginConfig } from './types.js' import type { MultiTenantPluginConfig } from './types.js'
@@ -36,6 +37,7 @@ export const multiTenantPlugin =
pluginConfig?.tenantsArrayField?.arrayFieldName || defaults.tenantsArrayFieldName pluginConfig?.tenantsArrayField?.arrayFieldName || defaults.tenantsArrayFieldName
const tenantsArrayTenantFieldName = const tenantsArrayTenantFieldName =
pluginConfig?.tenantsArrayField?.arrayTenantFieldName || defaults.tenantsArrayTenantFieldName pluginConfig?.tenantsArrayField?.arrayTenantFieldName || defaults.tenantsArrayTenantFieldName
let tenantSelectorLabel = pluginConfig.tenantSelectorLabel || defaults.tenantSelectorLabel
/** /**
* Add defaults for admin properties * Add defaults for admin properties
@@ -63,6 +65,36 @@ export const multiTenantPlugin =
incomingConfig.collections = [] incomingConfig.collections = []
} }
/**
* Add tenant selector localized labels
*/
if (pluginConfig.tenantSelectorLabel && typeof pluginConfig.tenantSelectorLabel === 'object') {
if (!incomingConfig.i18n) {
incomingConfig.i18n = {}
}
Object.entries(pluginConfig.tenantSelectorLabel).forEach(([_locale, label]) => {
const locale = _locale as AcceptedLanguages
if (!incomingConfig.i18n) {
incomingConfig.i18n = {}
}
if (!incomingConfig.i18n.translations) {
incomingConfig.i18n.translations = {}
}
if (!incomingConfig.i18n.translations[locale]) {
incomingConfig.i18n.translations[locale] = {}
}
if (!('multiTenant' in incomingConfig.i18n.translations[locale])) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
incomingConfig.i18n.translations[locale].multiTenant = {}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
incomingConfig.i18n.translations[locale].multiTenant.selectorLabel = label
tenantSelectorLabel = 'multiTenant:selectorLabel'
})
}
/** /**
* Add tenants array field to users collection * Add tenants array field to users collection
*/ */
@@ -229,7 +261,8 @@ export const multiTenantPlugin =
if (pluginConfig.collections[collection.slug]?.useBaseListFilter !== false) { if (pluginConfig.collections[collection.slug]?.useBaseListFilter !== false) {
/** /**
* Collection baseListFilter with selected tenant constraint (if selected) * Add list filter to enabled collections
* - filters results by selected tenant
*/ */
if (!collection.admin) { if (!collection.admin) {
collection.admin = {} collection.admin = {}
@@ -295,6 +328,9 @@ export const multiTenantPlugin =
* Add tenant selector to admin UI * Add tenant selector to admin UI
*/ */
incomingConfig.admin.components.beforeNavLinks.push({ incomingConfig.admin.components.beforeNavLinks.push({
clientProps: {
label: tenantSelectorLabel,
},
path: '@payloadcms/plugin-multi-tenant/client#TenantSelector', path: '@payloadcms/plugin-multi-tenant/client#TenantSelector',
}) })

View File

@@ -1,3 +1,4 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { ArrayField, CollectionSlug, Field, RelationshipField, User } from 'payload' import type { ArrayField, CollectionSlug, Field, RelationshipField, User } from 'payload'
export type MultiTenantPluginConfig<ConfigTypes = unknown> = { export type MultiTenantPluginConfig<ConfigTypes = unknown> = {
@@ -107,6 +108,16 @@ export type MultiTenantPluginConfig<ConfigTypes = unknown> = {
rowFields?: never rowFields?: never
tenantFieldAccess?: never tenantFieldAccess?: never
} }
/**
* Customize tenant selector label
*
* Either a string or an object where the keys are locales and the values are the string labels
*/
tenantSelectorLabel?:
| Partial<{
[key in AcceptedLanguages]?: string
}>
| string
/** /**
* The slug for the tenant collection * The slug for the tenant collection
* *

View File

@@ -1,4 +1,4 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"references": [{ "path": "../payload" }, { "path": "../ui"}] "references": [{ "path": "../payload" }, { "path": "../ui"}, { "path": "../translations"}]
} }

3
pnpm-lock.yaml generated
View File

@@ -1031,6 +1031,9 @@ importers:
'@payloadcms/eslint-config': '@payloadcms/eslint-config':
specifier: workspace:* specifier: workspace:*
version: link:../eslint-config version: link:../eslint-config
'@payloadcms/translations':
specifier: workspace:*
version: link:../translations
'@payloadcms/ui': '@payloadcms/ui':
specifier: workspace:* specifier: workspace:*
version: link:../ui version: link:../ui

View File

@@ -41,6 +41,7 @@ export default buildConfigWithDefaults({
isGlobal: true, isGlobal: true,
}, },
}, },
tenantSelectorLabel: 'Sites',
}), }),
], ],
typescript: { typescript: {