feat(plugin-multi-tenant): improves tenant assignment flow (#13881)
### Improved tenant assignment flow This PR improves the tenant assignment flow. I know a lot of users liked the previous flow where the field was not injected into the document. But the original flow, confused many of users because the tenant filter (top left) was being used to set the tenant on the document _and_ filter the list view. This change shown below is aiming to solve both of those groups with a slightly different approach. As always, feedback is welcome while we try to really make this plugin work for everyone. https://github.com/user-attachments/assets/ceee8b3a-c5f5-40e9-8648-f583e2412199 Added 2 new localization strings: ``` // shown in the 3 dot menu 'assign-tenant-button-label': 'Assign Tenant', // shown when needing to assign a tenant to a NEW document 'assign-tenant-modal-title': 'Assign "{{title}}"', ``` Removed 2 localization strings: ``` 'confirm-modal-tenant-switch--body', 'confirm-modal-tenant-switch--heading' ```
This commit is contained in:
@@ -136,21 +136,27 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
translations: {
|
||||
[key in AcceptedLanguages]?: {
|
||||
/**
|
||||
* @default 'You are about to change ownership from <0>{{fromTenant}}</0> to <0>{{toTenant}}</0>'
|
||||
*/
|
||||
'confirm-modal-tenant-switch--body'?: string
|
||||
/**
|
||||
* `tenantLabel` defaults to the value of the `nav-tenantSelector-label` translation
|
||||
* Shown inside 3 dot menu on edit document view
|
||||
*
|
||||
* @default 'Confirm {{tenantLabel}} change'
|
||||
* @default 'Assign Tenant'
|
||||
*/
|
||||
'confirm-modal-tenant-switch--heading'?: string
|
||||
'assign-tenant-button-label'?: string
|
||||
/**
|
||||
* Shown as the title of the assign tenant modal
|
||||
*
|
||||
* @default 'Assign "{{title}}"'
|
||||
*/
|
||||
'assign-tenant-modal-title'?: string
|
||||
/**
|
||||
* Shown as the label for the assigned tenant field in the assign tenant modal
|
||||
*
|
||||
* @default 'Assigned Tenant'
|
||||
*/
|
||||
'field-assignedTenant-label'?: string
|
||||
/**
|
||||
* @default 'Tenant'
|
||||
* Shown as the label for the global tenant selector in the admin UI
|
||||
*
|
||||
* @default 'Filter by Tenant'
|
||||
*/
|
||||
'nav-tenantSelector-label'?: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
'use client'
|
||||
|
||||
import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
Pill,
|
||||
PopupList,
|
||||
useConfig,
|
||||
useDocumentInfo,
|
||||
useDocumentTitle,
|
||||
useModal,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { drawerZBase, useDrawerDepth } from '@payloadcms/ui/elements/Drawer'
|
||||
import React from 'react'
|
||||
|
||||
import type {
|
||||
PluginMultiTenantTranslationKeys,
|
||||
PluginMultiTenantTranslations,
|
||||
} from '../../translations/index.js'
|
||||
|
||||
import './index.scss'
|
||||
import { useTenantSelection } from '../../providers/TenantSelectionProvider/index.client.js'
|
||||
|
||||
export const assignTenantModalSlug = 'assign-tenant-field-modal'
|
||||
const baseClass = 'assign-tenant-field-modal'
|
||||
|
||||
export const AssignTenantFieldTrigger: React.FC = () => {
|
||||
const { openModal } = useModal()
|
||||
const { t } = useTranslation<PluginMultiTenantTranslations, PluginMultiTenantTranslationKeys>()
|
||||
const { options } = useTenantSelection()
|
||||
|
||||
if (options.length <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PopupList.Button onClick={() => openModal(assignTenantModalSlug)}>
|
||||
{t('plugin-multi-tenant:assign-tenant-button-label')}
|
||||
</PopupList.Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const AssignTenantFieldModal: React.FC<{
|
||||
afterModalClose: () => void
|
||||
afterModalOpen: () => void
|
||||
children: React.ReactNode
|
||||
onCancel?: () => void
|
||||
onConfirm?: () => void
|
||||
}> = ({ afterModalClose, afterModalOpen, children, onCancel, onConfirm }) => {
|
||||
const editDepth = useDrawerDepth()
|
||||
const { t } = useTranslation<PluginMultiTenantTranslations, PluginMultiTenantTranslationKeys>()
|
||||
const { collectionSlug } = useDocumentInfo()
|
||||
const { title } = useDocumentTitle()
|
||||
const { getEntityConfig } = useConfig()
|
||||
const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
|
||||
const { closeModal, isModalOpen: isModalOpenFn } = useModal()
|
||||
const isModalOpen = isModalOpenFn(assignTenantModalSlug)
|
||||
const wasModalOpenRef = React.useRef<boolean>(isModalOpen)
|
||||
|
||||
const onModalConfirm = React.useCallback(() => {
|
||||
if (typeof onConfirm === 'function') {
|
||||
onConfirm()
|
||||
}
|
||||
closeModal(assignTenantModalSlug)
|
||||
}, [onConfirm, closeModal])
|
||||
|
||||
const onModalCancel = React.useCallback(() => {
|
||||
if (typeof onCancel === 'function') {
|
||||
onCancel()
|
||||
}
|
||||
closeModal(assignTenantModalSlug)
|
||||
}, [onCancel, closeModal])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (wasModalOpenRef.current && !isModalOpen) {
|
||||
// modal was open, and now is closed
|
||||
if (typeof afterModalClose === 'function') {
|
||||
afterModalClose()
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasModalOpenRef.current && isModalOpen) {
|
||||
// modal was closed, and now is open
|
||||
if (typeof afterModalOpen === 'function') {
|
||||
afterModalOpen()
|
||||
}
|
||||
}
|
||||
wasModalOpenRef.current = isModalOpen
|
||||
}, [isModalOpen, onCancel, afterModalClose, afterModalOpen])
|
||||
|
||||
if (!collectionConfig) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={baseClass}
|
||||
slug={assignTenantModalSlug}
|
||||
style={{
|
||||
zIndex: drawerZBase + editDepth,
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__bg`} />
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<h3>
|
||||
{t('plugin-multi-tenant:assign-tenant-modal-title', {
|
||||
title,
|
||||
})}
|
||||
</h3>
|
||||
<Pill className={`${baseClass}__collection-pill`} size="small">
|
||||
<>{collectionConfig.labels.singular}</>
|
||||
</Pill>
|
||||
</div>
|
||||
<div className={`${baseClass}__content`}>{children}</div>
|
||||
<div className={`${baseClass}__actions`}>
|
||||
<Button buttonStyle="secondary" margin={false} onClick={onModalCancel}>
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
<Button margin={false} onClick={onModalConfirm}>
|
||||
{t('general:confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
@layer payload-default {
|
||||
.assign-tenant-field-modal {
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
&[open] {
|
||||
pointer-events: auto;
|
||||
|
||||
.assign-tenant-field-modal__wrapper {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&__bg {
|
||||
z-index: -1;
|
||||
|
||||
&:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: var(--theme-bg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
max-width: calc(var(--base) * 30);
|
||||
min-width: min(500px, calc(100% - (var(--base) * 2)));
|
||||
border-radius: var(--style-radius-m);
|
||||
border: 1px solid var(--theme-elevation-100);
|
||||
background-color: var(--theme-bg);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: calc(var(--base) * 0.75) var(--base);
|
||||
border-bottom: 1px solid var(--theme-elevation-100);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: calc(var(--base) * 2);
|
||||
}
|
||||
|
||||
&__collection-pill {
|
||||
align-self: flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--base) * 0.5);
|
||||
padding: var(--base) var(--base) 0 var(--base);
|
||||
}
|
||||
|
||||
&__controls {
|
||||
display: flex;
|
||||
gap: calc(var(--base) * 0.5);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: calc(var(--base) * 0.4);
|
||||
padding: var(--base);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import type { RelationshipFieldClientProps, StaticLabel } from 'payload'
|
||||
import type { RelationshipFieldClientProps } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import {
|
||||
ConfirmationModal,
|
||||
Pill,
|
||||
RelationshipField,
|
||||
Translation,
|
||||
useDocumentInfo,
|
||||
useField,
|
||||
useForm,
|
||||
useFormModified,
|
||||
useModal,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import type {
|
||||
PluginMultiTenantTranslationKeys,
|
||||
PluginMultiTenantTranslations,
|
||||
} from '../../translations/index.js'
|
||||
|
||||
import './index.scss'
|
||||
import { useTenantSelection } from '../../providers/TenantSelectionProvider/index.client.js'
|
||||
import {
|
||||
AssignTenantFieldModal,
|
||||
assignTenantModalSlug,
|
||||
} from '../AssignTenantFieldModal/index.client.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'tenantField'
|
||||
|
||||
@@ -30,17 +27,74 @@ type Props = {
|
||||
unique?: boolean
|
||||
} & RelationshipFieldClientProps
|
||||
|
||||
export const TenantField = (args: Props) => {
|
||||
export const TenantField = ({ debug, unique, ...fieldArgs }: Props) => {
|
||||
const { entityType, options, selectedTenantID, setEntityType, setTenant } = useTenantSelection()
|
||||
const { value } = useField<number | string>()
|
||||
const { setValue, showError, value } = useField<(number | string)[] | (number | string)>()
|
||||
const modified = useFormModified()
|
||||
const { isValid: isFormValid, setModified } = useForm()
|
||||
const { id: docID } = useDocumentInfo()
|
||||
const { openModal } = useModal()
|
||||
const isConfirmingRef = React.useRef<boolean>(false)
|
||||
const prevModified = React.useRef(modified)
|
||||
const prevValue = React.useRef<typeof value>(value)
|
||||
const showField =
|
||||
(options.length > 1 && !fieldArgs.field.admin?.hidden && !fieldArgs.field.hidden) || debug
|
||||
|
||||
const onConfirm = React.useCallback(() => {
|
||||
isConfirmingRef.current = true
|
||||
}, [])
|
||||
|
||||
const afterModalOpen = React.useCallback(() => {
|
||||
prevModified.current = modified
|
||||
prevValue.current = value
|
||||
}, [modified, value])
|
||||
|
||||
const afterModalClose = React.useCallback(() => {
|
||||
let didChange = true
|
||||
if (isConfirmingRef.current) {
|
||||
// did the values actually change?
|
||||
if (fieldArgs.field.hasMany) {
|
||||
const prev = (prevValue.current || []) as (number | string)[]
|
||||
const newValue = (value || []) as (number | string)[]
|
||||
if (prev.length !== newValue.length) {
|
||||
didChange = true
|
||||
} else {
|
||||
const allMatch = newValue.every((val) => prev.includes(val))
|
||||
if (allMatch) {
|
||||
didChange = false
|
||||
}
|
||||
}
|
||||
} else if (value === prevValue.current) {
|
||||
didChange = false
|
||||
}
|
||||
|
||||
if (didChange) {
|
||||
prevModified.current = true
|
||||
prevValue.current = value
|
||||
}
|
||||
}
|
||||
|
||||
setValue(prevValue.current, true)
|
||||
setModified(prevModified.current)
|
||||
|
||||
isConfirmingRef.current = false
|
||||
}, [setValue, setModified, value, fieldArgs.field.hasMany])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!entityType) {
|
||||
setEntityType(args.unique ? 'global' : 'document')
|
||||
setEntityType(unique ? 'global' : 'document')
|
||||
} else {
|
||||
// unique documents are controlled from the global TenantSelector
|
||||
if (!args.unique && value) {
|
||||
if (!selectedTenantID || value !== selectedTenantID) {
|
||||
if (!unique && value) {
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length) {
|
||||
if (!selectedTenantID) {
|
||||
setTenant({ id: value[0], refresh: false })
|
||||
} else if (!value.includes(selectedTenantID)) {
|
||||
setTenant({ id: value[0], refresh: false })
|
||||
}
|
||||
}
|
||||
} else if (selectedTenantID !== value) {
|
||||
setTenant({ id: value, refresh: false })
|
||||
}
|
||||
}
|
||||
@@ -51,137 +105,73 @@ export const TenantField = (args: Props) => {
|
||||
setEntityType(undefined)
|
||||
}
|
||||
}
|
||||
}, [args.unique, options, selectedTenantID, setTenant, value, setEntityType, entityType])
|
||||
}, [unique, options, selectedTenantID, setTenant, value, setEntityType, entityType])
|
||||
|
||||
if (options.length > 1 && !args.field.admin?.hidden && !args.field.hidden) {
|
||||
return (
|
||||
<>
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<RelationshipField
|
||||
{...args}
|
||||
field={{
|
||||
...args.field,
|
||||
required: true,
|
||||
}}
|
||||
readOnly={args.readOnly || args.field.admin?.readOnly || args.unique}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{args.unique ? (
|
||||
<SyncFormModified />
|
||||
) : (
|
||||
<ConfirmTenantChange fieldLabel={args.field.label} fieldPath={args.path} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
React.useEffect(() => {
|
||||
if (unique) {
|
||||
return
|
||||
}
|
||||
if ((!isFormValid && showError && showField) || (!value && !selectedTenantID)) {
|
||||
openModal(assignTenantModalSlug)
|
||||
}
|
||||
}, [isFormValid, showError, showField, openModal, value, docID, selectedTenantID, unique])
|
||||
|
||||
if (showField) {
|
||||
if (debug) {
|
||||
return <TenantFieldInModal debug={debug} fieldArgs={fieldArgs} unique={unique} />
|
||||
}
|
||||
|
||||
if (!unique) {
|
||||
/** Editing a non-global tenant document */
|
||||
return (
|
||||
<AssignTenantFieldModal
|
||||
afterModalClose={afterModalClose}
|
||||
afterModalOpen={afterModalOpen}
|
||||
onConfirm={onConfirm}
|
||||
>
|
||||
<TenantFieldInModal
|
||||
debug={debug}
|
||||
fieldArgs={{
|
||||
...fieldArgs,
|
||||
field: {
|
||||
...fieldArgs.field,
|
||||
},
|
||||
}}
|
||||
unique={unique}
|
||||
/>
|
||||
</AssignTenantFieldModal>
|
||||
)
|
||||
}
|
||||
|
||||
return <SyncFormModified />
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const confirmSwitchTenantSlug = 'confirm-switch-tenant'
|
||||
|
||||
const ConfirmTenantChange = ({
|
||||
fieldLabel,
|
||||
fieldPath,
|
||||
}: {
|
||||
fieldLabel?: StaticLabel
|
||||
fieldPath: string
|
||||
}) => {
|
||||
const { options, selectedTenantID, setTenant } = useTenantSelection()
|
||||
const { setValue: setTenantFormValue, value: tenantFormValue } = useField<null | number | string>(
|
||||
{ path: fieldPath },
|
||||
)
|
||||
const { setModified } = useForm()
|
||||
const modified = useFormModified()
|
||||
const { i18n, t } = useTranslation<
|
||||
PluginMultiTenantTranslations,
|
||||
PluginMultiTenantTranslationKeys
|
||||
>()
|
||||
const { isModalOpen, openModal } = useModal()
|
||||
|
||||
const prevTenantValueRef = React.useRef<null | number | string>(tenantFormValue || null)
|
||||
const [tenantToConfirm, setTenantToConfirm] = React.useState<null | number | string>(
|
||||
tenantFormValue || null,
|
||||
)
|
||||
|
||||
const fromTenantOption = React.useMemo(() => {
|
||||
if (tenantFormValue) {
|
||||
return options.find((option) => option.value === tenantFormValue)
|
||||
}
|
||||
return undefined
|
||||
}, [options, tenantFormValue])
|
||||
|
||||
const toTenantOption = React.useMemo(() => {
|
||||
if (tenantToConfirm) {
|
||||
return options.find((option) => option.value === tenantToConfirm)
|
||||
}
|
||||
return undefined
|
||||
}, [options, tenantToConfirm])
|
||||
|
||||
const modalIsOpen = isModalOpen(confirmSwitchTenantSlug)
|
||||
const testRef = React.useRef<boolean>(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
// the form value changed
|
||||
if (
|
||||
!modalIsOpen &&
|
||||
tenantFormValue &&
|
||||
prevTenantValueRef.current &&
|
||||
tenantFormValue !== prevTenantValueRef.current
|
||||
) {
|
||||
// revert the form value change temporarily
|
||||
setTenantFormValue(prevTenantValueRef.current, true)
|
||||
// save the tenant to confirm in modal
|
||||
setTenantToConfirm(tenantFormValue)
|
||||
// open confirmation modal
|
||||
openModal(confirmSwitchTenantSlug)
|
||||
}
|
||||
}, [
|
||||
tenantFormValue,
|
||||
setTenantFormValue,
|
||||
openModal,
|
||||
setTenant,
|
||||
selectedTenantID,
|
||||
modalIsOpen,
|
||||
modified,
|
||||
])
|
||||
|
||||
const TenantFieldInModal: React.FC<{
|
||||
debug?: boolean
|
||||
fieldArgs: RelationshipFieldClientProps
|
||||
unique?: boolean
|
||||
}> = ({ debug, fieldArgs, unique }) => {
|
||||
return (
|
||||
<ConfirmationModal
|
||||
body={
|
||||
<Translation
|
||||
elements={{
|
||||
0: ({ children }) => {
|
||||
return <b>{children}</b>
|
||||
},
|
||||
}}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
i18nKey="plugin-multi-tenant:confirm-modal-tenant-switch--body"
|
||||
t={t}
|
||||
variables={{
|
||||
fromTenant: fromTenantOption?.label,
|
||||
toTenant: toTenantOption?.label,
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
{debug && (
|
||||
<Pill className={`${baseClass}__debug-pill`} pillStyle="success" size="small">
|
||||
Multi-Tenant Debug Enabled
|
||||
</Pill>
|
||||
)}
|
||||
<RelationshipField
|
||||
{...fieldArgs}
|
||||
field={{
|
||||
...fieldArgs.field,
|
||||
required: true,
|
||||
}}
|
||||
readOnly={fieldArgs.readOnly || fieldArgs.field.admin?.readOnly || unique}
|
||||
/>
|
||||
}
|
||||
heading={t('plugin-multi-tenant:confirm-modal-tenant-switch--heading', {
|
||||
tenantLabel: fieldLabel
|
||||
? getTranslation(fieldLabel, i18n)
|
||||
: t('plugin-multi-tenant:nav-tenantSelector-label'),
|
||||
})}
|
||||
modalSlug={confirmSwitchTenantSlug}
|
||||
onCancel={() => {
|
||||
setModified(testRef.current)
|
||||
}}
|
||||
onConfirm={() => {
|
||||
// set the form value to the tenant to confirm
|
||||
prevTenantValueRef.current = tenantToConfirm
|
||||
setTenantFormValue(tenantToConfirm)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
margin-top: calc(var(--base) * -1.5);
|
||||
padding-top: calc(var(--base) * 1.5);
|
||||
}
|
||||
|
||||
&__debug-pill {
|
||||
margin-bottom: calc(var(--base) * 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { AssignTenantFieldTrigger } from '../components/AssignTenantFieldModal/index.client.js'
|
||||
export { TenantField } from '../components/TenantField/index.client.js'
|
||||
export { WatchTenantCollection } from '../components/WatchTenantCollection/index.js'
|
||||
export { useTenantSelection } from '../providers/TenantSelectionProvider/index.client.js'
|
||||
|
||||
@@ -339,6 +339,16 @@ export const multiTenantPlugin =
|
||||
collection.disableDuplicate = true
|
||||
}
|
||||
|
||||
if (!pluginConfig.debug && !isGlobal) {
|
||||
collection.admin ??= {}
|
||||
collection.admin.components ??= {}
|
||||
collection.admin.components.edit ??= {}
|
||||
collection.admin.components.edit.editMenuItems ??= []
|
||||
collection.admin.components.edit.editMenuItems.push({
|
||||
path: '@payloadcms/plugin-multi-tenant/client#AssignTenantFieldTrigger',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filter options to all relationship fields
|
||||
*/
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const arTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'أنت على وشك تغيير الملكية من <0>{{fromTenant}}</0> إلى <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'تأكيد تغيير {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'تعيين المستأجر',
|
||||
'assign-tenant-modal-title': 'قم بتعيين "{{title}}"',
|
||||
'field-assignedTenant-label': 'المستأجر المعين',
|
||||
'nav-tenantSelector-label': 'المستأجر',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const azTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Siz <0>{{fromTenant}}</0>-dən <0>{{toTenant}}</0>-a mülkiyyəti dəyişməyə hazırlaşırsınız',
|
||||
'confirm-modal-tenant-switch--heading': '{{tenantLabel}} dəyişikliyini təsdiqləyin',
|
||||
'assign-tenant-button-label': 'Kirayəçiyə təyin et',
|
||||
'assign-tenant-modal-title': '"{{title}}" təyin edin',
|
||||
'field-assignedTenant-label': 'Təyin edilmiş İcarəçi',
|
||||
'nav-tenantSelector-label': 'Kirayəçi',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const bgTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Предстои да промените собствеността от <0>{{fromTenant}}</0> на <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Потвърждаване на промяна в {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Назначаване на Tenant',
|
||||
'assign-tenant-modal-title': 'Назначете "{{title}}"',
|
||||
'field-assignedTenant-label': 'Назначен наемател',
|
||||
'nav-tenantSelector-label': 'Потребител',
|
||||
},
|
||||
|
||||
@@ -2,11 +2,10 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const bnBdTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'আপনি <0>{{fromTenant}}</0> থেকে <0>{{toTenant}}</0> তে মালিকানা পরিবর্তন করতে চলেছেন।',
|
||||
'confirm-modal-tenant-switch--heading': '{{tenantLabel}} পরিবর্তন নিশ্চিত করুন',
|
||||
'field-assignedTenant-label': 'নির্ধারিত টেনেন্ট',
|
||||
'nav-tenantSelector-label': 'ভাড়াটিয়া',
|
||||
'assign-tenant-button-label': 'টেনেন্ট নির্ধারণ করুন',
|
||||
'assign-tenant-modal-title': '"{{title}}" নিয়োগ করুন',
|
||||
'field-assignedTenant-label': 'নিযুক্ত টেনেন্ট',
|
||||
'nav-tenantSelector-label': 'টেনেন্ট অনুসারে ফিল্টার করুন',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const bnInTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'আপনি স্বত্বাধিকার পরিবর্তন করতে চলেছেন <0>{{fromTenant}}</0> থেকে <0>{{toTenant}}</0> এ।',
|
||||
'confirm-modal-tenant-switch--heading': '{{tenantLabel}} পরিবর্তন নিশ্চিত করুন',
|
||||
'assign-tenant-button-label': 'টেনেন্ট নিয়োগ করুন',
|
||||
'assign-tenant-modal-title': '"{{title}}" এর দায়িত্ব দিন',
|
||||
'field-assignedTenant-label': 'নির্ধারিত টেনেন্ট',
|
||||
'nav-tenantSelector-label': 'ভাড়াটিয়া',
|
||||
'nav-tenantSelector-label': 'টেনেন্ট অনুসারে ফিল্টার করুন',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const caTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Està a punt de canviar la propietat de <0>{{fromTenant}}</0> a <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirmeu el canvi de {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Assignar Tenant',
|
||||
'assign-tenant-modal-title': 'Assigna "{{title}}"',
|
||||
'field-assignedTenant-label': 'Llogater Assignat',
|
||||
'nav-tenantSelector-label': 'Inquilí',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const csTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Chystáte se změnit vlastnictví z <0>{{fromTenant}}</0> na <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Potvrďte změnu {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Přiřadit nájemce',
|
||||
'assign-tenant-modal-title': 'Přiřadit "{{title}}"',
|
||||
'field-assignedTenant-label': 'Přiřazený nájemce',
|
||||
'nav-tenantSelector-label': 'Nájemce',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const daTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Du er ved at skifte ejerskab fra <0>{{fromTenant}}</0> til <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Bekræft ændring af {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Tildel Tenant',
|
||||
'assign-tenant-modal-title': 'Tildel "{{title}}"',
|
||||
'field-assignedTenant-label': 'Tildelt Lejer',
|
||||
'nav-tenantSelector-label': 'Lejer',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const deTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Sie sind dabei, den Besitz von <0>{{fromTenant}}</0> zu <0>{{toTenant}}</0> zu ändern.',
|
||||
'confirm-modal-tenant-switch--heading': 'Bestätigung der Änderung von {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Mieter zuweisen',
|
||||
'assign-tenant-modal-title': 'Weisen Sie "{{title}}" zu',
|
||||
'field-assignedTenant-label': 'Zugewiesener Mandant',
|
||||
'nav-tenantSelector-label': 'Mieter',
|
||||
},
|
||||
|
||||
@@ -2,11 +2,10 @@ import type { PluginLanguage } from '../types.js'
|
||||
|
||||
export const enTranslations = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'You are about to change ownership from <0>{{fromTenant}}</0> to <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirm {{tenantLabel}} change',
|
||||
'assign-tenant-button-label': 'Assign Tenant',
|
||||
'assign-tenant-modal-title': 'Assign "{{title}}"',
|
||||
'field-assignedTenant-label': 'Assigned Tenant',
|
||||
'nav-tenantSelector-label': 'Tenant',
|
||||
'nav-tenantSelector-label': 'Filter by Tenant',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const esTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Está a punto de cambiar la propiedad de <0>{{fromTenant}}</0> a <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirme el cambio de {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Asignar Inquilino',
|
||||
'assign-tenant-modal-title': 'Asignar "{{title}}"',
|
||||
'field-assignedTenant-label': 'Inquilino Asignado',
|
||||
'nav-tenantSelector-label': 'Inquilino',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const etTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Te olete just muutmas omandiõigust <0>{{fromTenant}}</0> -lt <0>{{toTenant}}</0> -le.',
|
||||
'confirm-modal-tenant-switch--heading': 'Kinnita {{tenantLabel}} muutus',
|
||||
'assign-tenant-button-label': 'Määra Tenant',
|
||||
'assign-tenant-modal-title': 'Määra "{{title}}"',
|
||||
'field-assignedTenant-label': 'Määratud üürnik',
|
||||
'nav-tenantSelector-label': 'Üürnik',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const faTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'شما در حال تغییر مالکیت از <0>{{fromTenant}}</0> به <0>{{toTenant}}</0> هستید.',
|
||||
'confirm-modal-tenant-switch--heading': 'تأیید تغییر {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'اختصاص Tenant',
|
||||
'assign-tenant-modal-title': 'اختصاص "{{title}}"',
|
||||
'field-assignedTenant-label': 'مستاجر اختصاص یافته',
|
||||
'nav-tenantSelector-label': 'مستاجر',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const frTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Vous êtes sur le point de changer la propriété de <0>{{fromTenant}}</0> à <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirmer le changement de {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Attribuer un Locataire',
|
||||
'assign-tenant-modal-title': 'Attribuer "{{title}}"',
|
||||
'field-assignedTenant-label': 'Locataire Attribué',
|
||||
'nav-tenantSelector-label': 'Locataire',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const heTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'אתה עומד לשנות בעלות מ- <0>{{fromTenant}}</0> ל- <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'אשר שינוי {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'הקצה Tenant',
|
||||
'assign-tenant-modal-title': 'הקצה "{{title}}"',
|
||||
'field-assignedTenant-label': 'דייר מוקצה',
|
||||
'nav-tenantSelector-label': 'דייר',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const hrTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Na rubu ste promjene vlasništva iz <0>{{fromTenant}}</0> u <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Potvrdite promjenu {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Dodijeli Najmoprimca',
|
||||
'assign-tenant-modal-title': 'Dodijeli "{{title}}"',
|
||||
'field-assignedTenant-label': 'Dodijeljeni stanar',
|
||||
'nav-tenantSelector-label': 'Podstanar',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const huTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Közel áll ahhoz, hogy megváltoztassa a tulajdonságot <0>{{fromTenant}}</0> -ból <0>{{toTenant}}</0> -ba.',
|
||||
'confirm-modal-tenant-switch--heading': 'Erősítse meg a {{tenantLabel}} változást',
|
||||
'assign-tenant-button-label': 'Hozzárendelési bérlő',
|
||||
'assign-tenant-modal-title': 'Rendelje hozzá a "{{title}}"',
|
||||
'field-assignedTenant-label': 'Kijelölt Bérlő',
|
||||
'nav-tenantSelector-label': 'Bérlő',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const hyTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Դուք պատրաստվում եք փոխել սեփականությունը <0>{{fromTenant}}</0>-ից <0>{{toTenant}}</0>-ին:',
|
||||
'confirm-modal-tenant-switch--heading': 'Հաստատեք {{tenantLabel}}֊ի փոփոխությունը',
|
||||
'assign-tenant-button-label': 'Տեղադրել Tenant',
|
||||
'assign-tenant-modal-title': 'Հանձնել "{{title}}"',
|
||||
'field-assignedTenant-label': 'Հանձնարարված վարձակալ',
|
||||
'nav-tenantSelector-label': 'Տենանտ',
|
||||
},
|
||||
|
||||
@@ -2,11 +2,10 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const idTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Anda akan mengubah kepemilikan dari <0>{{fromTenant}}</0> ke <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Konfirmasi perubahan {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Tetapkan Tenant',
|
||||
'assign-tenant-modal-title': 'Tetapkan "{{title}}"',
|
||||
'field-assignedTenant-label': 'Penyewa yang Ditugaskan',
|
||||
'nav-tenantSelector-label': 'Penyewa',
|
||||
'nav-tenantSelector-label': 'Filter berdasarkan Tenant',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
|
||||
|
||||
export const isTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'assign-tenant-button-label': 'Úthluta leigjanda',
|
||||
'assign-tenant-modal-title': 'Úthluta "{{title}}"',
|
||||
'field-assignedTenant-label': 'Úthlutaður leigjandi',
|
||||
'nav-tenantSelector-label': 'Síaðu eftir leigjanda',
|
||||
},
|
||||
}
|
||||
|
||||
export const is: PluginLanguage = {
|
||||
dateFNSKey: 'is',
|
||||
translations: isTranslations,
|
||||
}
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const itTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Stai per cambiare il possesso da <0>{{fromTenant}}</0> a <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Conferma il cambiamento di {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Assegna Tenant',
|
||||
'assign-tenant-modal-title': 'Assegna "{{title}}"',
|
||||
'field-assignedTenant-label': 'Inquilino Assegnato',
|
||||
'nav-tenantSelector-label': 'Inquilino',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const jaTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'あなたは、<0>{{fromTenant}}</0>から<0>{{toTenant}}</0>への所有権を変更しようとしています。',
|
||||
'confirm-modal-tenant-switch--heading': '{{tenantLabel}}の変更を確認します',
|
||||
'assign-tenant-button-label': 'テナントを割り当てる',
|
||||
'assign-tenant-modal-title': '"{{title}}"を割り当てる',
|
||||
'field-assignedTenant-label': '割り当てられたテナント',
|
||||
'nav-tenantSelector-label': 'テナント',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const koTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'<0>{{fromTenant}}</0>에서 <0>{{toTenant}}</0>로 소유권을 변경하려고 합니다.',
|
||||
'confirm-modal-tenant-switch--heading': '{{tenantLabel}} 변경 확인',
|
||||
'assign-tenant-button-label': '테넌트 지정',
|
||||
'assign-tenant-modal-title': '"{{title}}"를 지정하십시오.',
|
||||
'field-assignedTenant-label': '지정된 세입자',
|
||||
'nav-tenantSelector-label': '세입자',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const ltTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Jūs ketinate pakeisti nuosavybę iš <0>{{fromTenant}}</0> į <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Patvirtinkite {{tenantLabel}} pakeitimą',
|
||||
'assign-tenant-button-label': 'Priskirkite nuomininką',
|
||||
'assign-tenant-modal-title': 'Paskirkite "{{title}}"',
|
||||
'field-assignedTenant-label': 'Paskirtas nuomininkas',
|
||||
'nav-tenantSelector-label': 'Nuomininkas',
|
||||
},
|
||||
|
||||
@@ -2,11 +2,10 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const lvTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Jūs gatavojaties mainīt īpašumtiesības no <0>{{fromTenant}}</0> uz <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Apstipriniet {{tenantLabel}} izmaiņu',
|
||||
'field-assignedTenant-label': 'Piešķirts nomnieks',
|
||||
'nav-tenantSelector-label': 'Nomnieks',
|
||||
'assign-tenant-button-label': 'Piešķirt Tenant',
|
||||
'assign-tenant-modal-title': 'Piešķirt "{{title}}"',
|
||||
'field-assignedTenant-label': 'Piešķirtais tenants',
|
||||
'nav-tenantSelector-label': 'Filtrēt pēc Nomnieka',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const myTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Anda akan menukar pemilikan dari <0>{{fromTenant}}</0> kepada <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Sahkan perubahan {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'အသစ်ထည့်သည့် Tenant',
|
||||
'assign-tenant-modal-title': 'Tetapkan "{{title}}"',
|
||||
'field-assignedTenant-label': 'ခွဲစိုက်ထားသော အငှားယူသူ',
|
||||
'nav-tenantSelector-label': 'Penyewa',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const nbTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Du er i ferd med å endre eierskap fra <0>{{fromTenant}}</0> til <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Bekreft endring av {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Tildel Leietaker',
|
||||
'assign-tenant-modal-title': 'Tildel "{{title}}"',
|
||||
'field-assignedTenant-label': 'Tildelt leietaker',
|
||||
'nav-tenantSelector-label': 'Leietaker',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const nlTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'U staat op het punt om eigenaarschap te wijzigen van <0>{{fromTenant}}</0> naar <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Bevestig wijziging van {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Toewijzen Tenant',
|
||||
'assign-tenant-modal-title': 'Wijs "{{title}}" toe',
|
||||
'field-assignedTenant-label': 'Toegewezen Huurder',
|
||||
'nav-tenantSelector-label': 'Huurder',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const plTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Za chwilę nastąpi zmiana właściciela z <0>{{fromTenant}}</0> na <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Potwierdź zmianę {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Przypisz Najemcę',
|
||||
'assign-tenant-modal-title': 'Przypisz "{{title}}"',
|
||||
'field-assignedTenant-label': 'Przypisany Najemca',
|
||||
'nav-tenantSelector-label': 'Najemca',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const ptTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Está prestes a mudar a propriedade de <0>{{fromTenant}}</0> para <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirme a alteração do {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Atribuir Inquilino',
|
||||
'assign-tenant-modal-title': 'Atribuir "{{title}}"',
|
||||
'field-assignedTenant-label': 'Inquilino Atribuído',
|
||||
'nav-tenantSelector-label': 'Inquilino',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const roTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Sunteți pe cale să schimbați proprietatea de la <0>{{fromTenant}}</0> la <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirmați modificarea {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Alocați Tenant',
|
||||
'assign-tenant-modal-title': 'Atribuiți "{{title}}"',
|
||||
'field-assignedTenant-label': 'Locatar Atribuit',
|
||||
'nav-tenantSelector-label': 'Locatar',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const rsTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Na putu ste da promenite vlasništvo od <0>{{fromTenant}}</0> do <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Potvrdite promenu {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Dodeli Tenant',
|
||||
'assign-tenant-modal-title': 'Dodelite "{{title}}"',
|
||||
'field-assignedTenant-label': 'Dodeljen stanar',
|
||||
'nav-tenantSelector-label': 'Podstanar',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const rsLatinTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Uskoro ćete promeniti vlasništvo sa <0>{{fromTenant}}</0> na <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Potvrdite promenu {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Dodeli Tenant',
|
||||
'assign-tenant-modal-title': 'Dodeli "{{title}}"',
|
||||
'field-assignedTenant-label': 'Dodeljen stanar',
|
||||
'nav-tenantSelector-label': 'Podstanar',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const ruTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Вы собираетесь изменить владельца с <0>{{fromTenant}}</0> на <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Подтвердите изменение {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Назначить Арендатора',
|
||||
'assign-tenant-modal-title': 'Назначить "{{title}}"',
|
||||
'field-assignedTenant-label': 'Назначенный Арендатор',
|
||||
'nav-tenantSelector-label': 'Арендатор',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const skTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Chystáte sa zmeniť vlastníctvo z <0>{{fromTenant}}</0> na <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Potvrďte zmenu {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Priradiť nájomcu',
|
||||
'assign-tenant-modal-title': 'Priradiť "{{title}}"',
|
||||
'field-assignedTenant-label': 'Pridelený nájomca',
|
||||
'nav-tenantSelector-label': 'Nájomca',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const slTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Pravkar ste na točki, da spremenite lastništvo iz <0>{{fromTenant}}</0> v <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Potrdite spremembo {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Dodeli najemnika',
|
||||
'assign-tenant-modal-title': 'Dodeli "{{title}}"',
|
||||
'field-assignedTenant-label': 'Dodeljen najemnik',
|
||||
'nav-tenantSelector-label': 'Najemnik',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const svTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Du är på väg att ändra ägande från <0>{{fromTenant}}</0> till <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Bekräfta ändring av {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Tilldela Hyresgäst',
|
||||
'assign-tenant-modal-title': 'Tilldela "{{title}}"',
|
||||
'field-assignedTenant-label': 'Tilldelad hyresgäst',
|
||||
'nav-tenantSelector-label': 'Hyresgäst',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const taTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'நீங்கள் உரிமையைக் <0>{{fromTenant}}</0> இலிருந்து <0>{{toTenant}}</0> க்கு மாற்ற உள்ளீர்கள்',
|
||||
'confirm-modal-tenant-switch--heading': '{{tenantLabel}} மாற்றத்தை உறுதிப்படுத்தவும்',
|
||||
'assign-tenant-button-label': 'டெனன்டை ஒதுக்குக',
|
||||
'assign-tenant-modal-title': '"{{title}}"ஐ ஒதுக்கி வைக்கவும்.',
|
||||
'field-assignedTenant-label': 'ஒதுக்கப்பட்ட Tenant',
|
||||
'nav-tenantSelector-label': 'Tenant',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const thTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'คุณกำลังจะเปลี่ยนสิทธิ์การเป็นเจ้าของจาก <0>{{fromTenant}}</0> ไปยัง <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'ยืนยันการเปลี่ยนแปลง {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'กำหนดผู้เช่า',
|
||||
'assign-tenant-modal-title': 'มอบหมาย "{{title}}"',
|
||||
'field-assignedTenant-label': 'ผู้เช่าที่ได้รับการกำหนด',
|
||||
'nav-tenantSelector-label': 'ผู้เช่า',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const trTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
"<0>{{fromTenant}}</0>'den <0>{{toTenant}}</0>'ye sahipliği değiştirmek üzeresiniz.",
|
||||
'confirm-modal-tenant-switch--heading': '{{tenantLabel}} değişikliğini onayla',
|
||||
'assign-tenant-button-label': 'Kiracı Ata',
|
||||
'assign-tenant-modal-title': '"{{title}}" atayın.',
|
||||
'field-assignedTenant-label': 'Atanan Kiracı',
|
||||
'nav-tenantSelector-label': 'Kiracı',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const ukTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Ви збираєтеся змінити власність з <0>{{fromTenant}}</0> на <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Підтвердіть зміну {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Призначити орендаря',
|
||||
'assign-tenant-modal-title': 'Призначте "{{title}}"',
|
||||
'field-assignedTenant-label': 'Призначений орендар',
|
||||
'nav-tenantSelector-label': 'Орендар',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const viTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'Bạn sắp chuyển quyền sở hữu từ <0>{{fromTenant}}</0> đến <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': 'Xác nhận thay đổi {{tenantLabel}}',
|
||||
'assign-tenant-button-label': 'Giao Tenant',
|
||||
'assign-tenant-modal-title': 'Gán "{{title}}"',
|
||||
'field-assignedTenant-label': 'Người thuê đã được chỉ định',
|
||||
'nav-tenantSelector-label': 'Người thuê',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const zhTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'您即将从<0>{{fromTenant}}</0>更改为<0>{{toTenant}}</0>的所有权',
|
||||
'confirm-modal-tenant-switch--heading': '确认更改{{tenantLabel}}',
|
||||
'assign-tenant-button-label': '分配租户',
|
||||
'assign-tenant-modal-title': '分配"{{title}}"',
|
||||
'field-assignedTenant-label': '指定租户',
|
||||
'nav-tenantSelector-label': '租户',
|
||||
},
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j
|
||||
|
||||
export const zhTwTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body':
|
||||
'您即將變更擁有者,從 <0>{{fromTenant}}</0> 切換為 <0>{{toTenant}}</0>',
|
||||
'confirm-modal-tenant-switch--heading': '確認變更 {{tenantLabel}}',
|
||||
'assign-tenant-button-label': '指派租戶',
|
||||
'assign-tenant-modal-title': '將 "{{title}}"',
|
||||
'field-assignedTenant-label': '指派的租用戶',
|
||||
'nav-tenantSelector-label': '租戶',
|
||||
},
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { enTranslations } from './languages/en.js'
|
||||
|
||||
export type PluginLanguage = Language<{
|
||||
'plugin-multi-tenant': {
|
||||
'confirm-modal-tenant-switch--body': string
|
||||
'confirm-modal-tenant-switch--heading': string
|
||||
'assign-tenant-button-label': string
|
||||
'assign-tenant-modal-title': string
|
||||
'field-assignedTenant-label': string
|
||||
'nav-tenantSelector-label': string
|
||||
}
|
||||
|
||||
@@ -92,21 +92,27 @@ export type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
translations: {
|
||||
[key in AcceptedLanguages]?: {
|
||||
/**
|
||||
* @default 'You are about to change ownership from <0>{{fromTenant}}</0> to <0>{{toTenant}}</0>'
|
||||
*/
|
||||
'confirm-modal-tenant-switch--body'?: string
|
||||
/**
|
||||
* `tenantLabel` defaults to the value of the `nav-tenantSelector-label` translation
|
||||
* Shown inside 3 dot menu on edit document view
|
||||
*
|
||||
* @default 'Confirm {{tenantLabel}} change'
|
||||
* @default 'Assign Tenant'
|
||||
*/
|
||||
'confirm-modal-tenant-switch--heading'?: string
|
||||
'assign-tenant-button-label'?: string
|
||||
/**
|
||||
* Shown as the title of the assign tenant modal
|
||||
*
|
||||
* @default 'Assign "{{title}}"'
|
||||
*/
|
||||
'assign-tenant-modal-title'?: string
|
||||
/**
|
||||
* Shown as the label for the assigned tenant field in the assign tenant modal
|
||||
*
|
||||
* @default 'Assigned Tenant'
|
||||
*/
|
||||
'field-assignedTenant-label'?: string
|
||||
/**
|
||||
* @default 'Tenant'
|
||||
* Shown as the label for the global tenant selector in the admin UI
|
||||
*
|
||||
* @default 'Filter by Tenant'
|
||||
*/
|
||||
'nav-tenantSelector-label'?: string
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ function createErrorsFromMessage(message: string): {
|
||||
if (errors.length === 1) {
|
||||
return {
|
||||
errors,
|
||||
message: `${intro}:`,
|
||||
message: `${intro}: `,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@ import type { BrowserContext, Page } from '@playwright/test'
|
||||
import type { TypeWithID } from 'payload'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { devUser } from 'credentials.js'
|
||||
import { openDocControls } from 'helpers/e2e/openDocControls.js'
|
||||
import { openNav } from 'helpers/e2e/toggleNav.js'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
@@ -12,8 +9,8 @@ import { fileURLToPath } from 'url'
|
||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||
import type { Config, ReadOnlyCollection, RestrictedVersion } from './payload-types.js'
|
||||
|
||||
import { devUser } from '../credentials.js'
|
||||
import {
|
||||
closeNav,
|
||||
ensureCompilationIsDone,
|
||||
exactText,
|
||||
initPageConsoleErrorCatch,
|
||||
@@ -21,6 +18,8 @@ import {
|
||||
} from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { login } from '../helpers/e2e/auth/login.js'
|
||||
import { openDocControls } from '../helpers/e2e/openDocControls.js'
|
||||
import { closeNav, openNav } from '../helpers/e2e/toggleNav.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
import {
|
||||
|
||||
@@ -7,15 +7,12 @@ import type {
|
||||
} from '@playwright/test'
|
||||
import type { Config } from 'payload'
|
||||
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { expect } from '@playwright/test'
|
||||
import { defaults } from 'payload'
|
||||
import { wait } from 'payload/shared'
|
||||
import shelljs from 'shelljs'
|
||||
import { setTimeout } from 'timers/promises'
|
||||
|
||||
import { devUser } from './credentials.js'
|
||||
import { openNav } from './helpers/e2e/toggleNav.js'
|
||||
import { POLL_TOPASS_TIMEOUT } from './playwright.config.js'
|
||||
|
||||
export type AdminRoutes = NonNullable<Config['admin']>['routes']
|
||||
@@ -220,14 +217,6 @@ export async function openCreateDocDrawer(page: Page, fieldSelector: string): Pr
|
||||
await wait(500) // wait for drawer form state to initialize
|
||||
}
|
||||
|
||||
export async function closeNav(page: Page): Promise<void> {
|
||||
if (!(await page.locator('.template-default.template-default--nav-open').isVisible())) {
|
||||
return
|
||||
}
|
||||
await page.locator('.nav-toggler >> visible=true').click()
|
||||
await expect(page.locator('.template-default.template-default--nav-open')).toBeHidden()
|
||||
}
|
||||
|
||||
export async function openLocaleSelector(page: Page): Promise<void> {
|
||||
const button = page.locator('.localizer button.popup-button')
|
||||
const popup = page.locator('.localizer .popup.popup--active')
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function assertToastErrors({
|
||||
}): Promise<void> {
|
||||
const isSingleError = errors.length === 1
|
||||
const message = isSingleError
|
||||
? 'The following field is invalid:'
|
||||
? 'The following field is invalid: '
|
||||
: `The following fields are invalid (${errors.length}):`
|
||||
|
||||
// Check the intro message text
|
||||
|
||||
@@ -24,3 +24,17 @@ export async function openNav(page: Page): Promise<void> {
|
||||
await expect(page.locator('.nav--nav-animate[inert], .nav--nav-hydrated[inert]')).toBeHidden()
|
||||
await expect(page.locator('.template-default.template-default--nav-open')).toBeVisible()
|
||||
}
|
||||
|
||||
export async function closeNav(page: Page): Promise<void> {
|
||||
// wait for the preferences/media queries to either open or close the nav
|
||||
await expect(page.locator('.template-default--nav-hydrated')).toBeVisible()
|
||||
|
||||
// check to see if the nav is already closed and if so, return early
|
||||
if (!(await page.locator('.template-default.template-default--nav-open').isVisible())) {
|
||||
return
|
||||
}
|
||||
|
||||
// playwright: get first element with .nav-toggler which is VISIBLE (not hidden), could be 2 elements with .nav-toggler on mobile and desktop but only one is visible
|
||||
await page.locator('.nav-toggler >> visible=true').click()
|
||||
await expect(page.locator('.template-default.template-default--nav-open')).toBeHidden()
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default buildConfigWithDefaults({
|
||||
onInit: seed,
|
||||
plugins: [
|
||||
multiTenantPlugin<ConfigType>({
|
||||
debug: true,
|
||||
// debug: true,
|
||||
userHasAccessToAllTenants: (user) => Boolean(user.roles?.includes('admin')),
|
||||
useTenantsCollectionAccess: false,
|
||||
tenantField: {
|
||||
@@ -52,9 +52,9 @@ export default buildConfigWithDefaults({
|
||||
i18n: {
|
||||
translations: {
|
||||
en: {
|
||||
'field-assignedTenant-label': 'Currently Assigned Site',
|
||||
'nav-tenantSelector-label': 'Filter By Site',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirm Site Change',
|
||||
'field-assignedTenant-label': 'Site',
|
||||
'nav-tenantSelector-label': 'Filter by Site',
|
||||
'assign-tenant-button-label': 'Assign Site',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||
import type { Config } from './payload-types.js'
|
||||
|
||||
import { ensureCompilationIsDone, initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
getSelectInputValue,
|
||||
selectInput,
|
||||
} from '../helpers/e2e/selectInput.js'
|
||||
import { openNav } from '../helpers/e2e/toggleNav.js'
|
||||
import { closeNav, openNav } from '../helpers/e2e/toggleNav.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { reInitializeDB } from '../helpers/reInitializeDB.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
@@ -37,16 +38,19 @@ test.describe('Multi Tenant', () => {
|
||||
let menuItemsURL: AdminUrlUtil
|
||||
let usersURL: AdminUrlUtil
|
||||
let tenantsURL: AdminUrlUtil
|
||||
let payload: PayloadTestSDK<Config>
|
||||
|
||||
test.beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
|
||||
const { serverURL: serverFromInit, payload } = await initPayloadE2ENoConfig<Config>({ dirname })
|
||||
const { serverURL: serverFromInit, payload: payloadFromInit } =
|
||||
await initPayloadE2ENoConfig<Config>({ dirname })
|
||||
serverURL = serverFromInit
|
||||
globalMenuURL = new AdminUrlUtil(serverURL, menuSlug)
|
||||
menuItemsURL = new AdminUrlUtil(serverURL, menuItemsSlug)
|
||||
usersURL = new AdminUrlUtil(serverURL, usersSlug)
|
||||
tenantsURL = new AdminUrlUtil(serverURL, tenantsSlug)
|
||||
payload = payloadFromInit
|
||||
autosaveGlobalURL = new AdminUrlUtil(serverURL, autosaveGlobalSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
@@ -72,7 +76,7 @@ test.describe('Multi Tenant', () => {
|
||||
data: credentials.admin,
|
||||
})
|
||||
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await page.goto(tenantsURL.list)
|
||||
|
||||
@@ -99,8 +103,8 @@ test.describe('Multi Tenant', () => {
|
||||
data: credentials.admin,
|
||||
})
|
||||
|
||||
await page.goto(tenantsURL.list)
|
||||
await selectTenant({
|
||||
await setTenantFilter({
|
||||
urlUtil: tenantsURL,
|
||||
page,
|
||||
tenant: 'Blue Dog',
|
||||
})
|
||||
@@ -127,7 +131,7 @@ test.describe('Multi Tenant', () => {
|
||||
})
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await expect(
|
||||
page.locator('.collection-list .table .cell-name', {
|
||||
@@ -147,8 +151,8 @@ test.describe('Multi Tenant', () => {
|
||||
data: credentials.admin,
|
||||
})
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await selectTenant({
|
||||
await setTenantFilter({
|
||||
urlUtil: menuItemsURL,
|
||||
page,
|
||||
tenant: 'Blue Dog',
|
||||
})
|
||||
@@ -172,7 +176,7 @@ test.describe('Multi Tenant', () => {
|
||||
})
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await expect(
|
||||
page.locator('.collection-list .table .cell-name', {
|
||||
@@ -188,7 +192,7 @@ test.describe('Multi Tenant', () => {
|
||||
})
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await expect(
|
||||
page.locator('.collection-list .table .cell-name', {
|
||||
@@ -207,7 +211,7 @@ test.describe('Multi Tenant', () => {
|
||||
})
|
||||
|
||||
await page.goto(usersURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await expect(
|
||||
page.locator('.collection-list .table .cell-email', {
|
||||
@@ -233,8 +237,8 @@ test.describe('Multi Tenant', () => {
|
||||
data: credentials.admin,
|
||||
})
|
||||
|
||||
await page.goto(usersURL.list)
|
||||
await selectTenant({
|
||||
await setTenantFilter({
|
||||
urlUtil: usersURL,
|
||||
page,
|
||||
tenant: 'Blue Dog',
|
||||
})
|
||||
@@ -267,7 +271,7 @@ test.describe('Multi Tenant', () => {
|
||||
})
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await goToListDoc({
|
||||
page,
|
||||
@@ -287,7 +291,7 @@ test.describe('Multi Tenant', () => {
|
||||
.toBe('Blue Dog')
|
||||
})
|
||||
|
||||
test('should prompt for confirmation upon tenant switching', async () => {
|
||||
test('should allow tenant switching cancellation', async () => {
|
||||
await loginClientSide({
|
||||
page,
|
||||
serverURL,
|
||||
@@ -295,7 +299,7 @@ test.describe('Multi Tenant', () => {
|
||||
})
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await goToListDoc({
|
||||
page,
|
||||
@@ -307,14 +311,46 @@ test.describe('Multi Tenant', () => {
|
||||
await selectDocumentTenant({
|
||||
page,
|
||||
tenant: 'Steel Cat',
|
||||
action: 'cancel',
|
||||
payload,
|
||||
})
|
||||
|
||||
const confirmationModal = page.locator('#confirm-switch-tenant')
|
||||
await expect(confirmationModal).toBeVisible()
|
||||
await expect(
|
||||
confirmationModal.getByText('You are about to change ownership from Blue Dog to Steel Cat'),
|
||||
).toBeVisible()
|
||||
await expect(page.locator('#action-save')).toBeDisabled()
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await getSelectedTenantFilterName({ page, payload })
|
||||
})
|
||||
.toBe('Blue Dog')
|
||||
})
|
||||
|
||||
test('should allow tenant switching confirmation', async () => {
|
||||
await loginClientSide({
|
||||
page,
|
||||
serverURL,
|
||||
data: credentials.admin,
|
||||
})
|
||||
|
||||
await page.goto(menuItemsURL.list)
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await goToListDoc({
|
||||
page,
|
||||
cellClass: '.cell-name',
|
||||
textToMatch: 'Spicy Mac',
|
||||
urlUtil: menuItemsURL,
|
||||
})
|
||||
|
||||
await selectDocumentTenant({
|
||||
page,
|
||||
payload,
|
||||
tenant: 'Steel Cat',
|
||||
})
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
})
|
||||
|
||||
test('should filter internal links in Lexical editor', async () => {
|
||||
await loginClientSide({
|
||||
page,
|
||||
@@ -324,6 +360,7 @@ test.describe('Multi Tenant', () => {
|
||||
await page.goto(menuItemsURL.create)
|
||||
await selectDocumentTenant({
|
||||
page,
|
||||
payload,
|
||||
tenant: 'Blue Dog',
|
||||
})
|
||||
const editor = page.locator('[data-lexical-editor="true"]')
|
||||
@@ -364,8 +401,8 @@ test.describe('Multi Tenant', () => {
|
||||
serverURL,
|
||||
data: credentials.admin,
|
||||
})
|
||||
await page.goto(tenantsURL.list)
|
||||
await selectTenant({
|
||||
await setTenantFilter({
|
||||
urlUtil: tenantsURL,
|
||||
page,
|
||||
tenant: 'Blue Dog',
|
||||
})
|
||||
@@ -381,8 +418,8 @@ test.describe('Multi Tenant', () => {
|
||||
data: credentials.admin,
|
||||
})
|
||||
|
||||
await page.goto(tenantsURL.list)
|
||||
await selectTenant({
|
||||
await setTenantFilter({
|
||||
urlUtil: tenantsURL,
|
||||
page,
|
||||
tenant: 'Blue Dog',
|
||||
})
|
||||
@@ -391,7 +428,7 @@ test.describe('Multi Tenant', () => {
|
||||
|
||||
// Attempt to switch tenants with unsaved changes
|
||||
await page.fill('#field-title', 'New Global Menu Name')
|
||||
await selectTenant({
|
||||
await switchGlobalDocTenant({
|
||||
page,
|
||||
tenant: 'Steel Cat',
|
||||
})
|
||||
@@ -424,15 +461,25 @@ test.describe('Multi Tenant', () => {
|
||||
data: credentials.admin,
|
||||
})
|
||||
await page.goto(tenantsURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
await page.goto(autosaveGlobalURL.list)
|
||||
await expect(page.locator('.doc-header__title')).toBeVisible()
|
||||
const globalTenant = await getGlobalTenant({ page })
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await getDocumentTenant({ page })
|
||||
})
|
||||
.toBe(globalTenant)
|
||||
const docID = (await page.locator('.render-title').getAttribute('data-doc-id')) as string
|
||||
await expect.poll(() => docID).not.toBeUndefined()
|
||||
const globalTenant = await getSelectedTenantFilterName({ page, payload })
|
||||
const autosaveGlobal = await payload.find({
|
||||
collection: autosaveGlobalSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
'tenant.name': {
|
||||
equals: globalTenant,
|
||||
},
|
||||
},
|
||||
})
|
||||
await expect.poll(() => autosaveGlobal?.totalDocs).toBe(1)
|
||||
await expect.poll(() => autosaveGlobal?.docs?.[0]?.tenant).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -515,7 +562,7 @@ test.describe('Multi Tenant', () => {
|
||||
})
|
||||
|
||||
await page.goto(tenantsURL.list)
|
||||
await clearGlobalTenant({ page })
|
||||
await clearTenantFilter({ page })
|
||||
|
||||
await expect(
|
||||
page.locator('.collection-list .table .cell-name', {
|
||||
@@ -602,24 +649,6 @@ test.describe('Multi Tenant', () => {
|
||||
/**
|
||||
* Helper Functions
|
||||
*/
|
||||
|
||||
async function getGlobalTenant({ page }: { page: Page }): Promise<string | undefined> {
|
||||
await openNav(page)
|
||||
return await getSelectInputValue<false>({
|
||||
selectLocator: page.locator('.tenant-selector'),
|
||||
multiSelect: false,
|
||||
})
|
||||
}
|
||||
|
||||
async function getDocumentTenant({ page }: { page: Page }): Promise<string | undefined> {
|
||||
await openNav(page)
|
||||
return await getSelectInputValue<false>({
|
||||
selectLocator: page.locator('#field-tenant'),
|
||||
multiSelect: false,
|
||||
valueLabelClass: '.relationship--single-value',
|
||||
})
|
||||
}
|
||||
|
||||
async function getTenantOptions({ page }: { page: Page }): Promise<string[]> {
|
||||
await openNav(page)
|
||||
return await getSelectInputOptions({
|
||||
@@ -627,33 +656,124 @@ async function getTenantOptions({ page }: { page: Page }): Promise<string[]> {
|
||||
})
|
||||
}
|
||||
|
||||
async function openAssignTenantModal({
|
||||
page,
|
||||
payload,
|
||||
}: {
|
||||
page: Page
|
||||
payload: PayloadTestSDK<Config>
|
||||
}): Promise<void> {
|
||||
const assignTenantModal = page.locator('#assign-tenant-field-modal')
|
||||
|
||||
const globalTenant = await getSelectedTenantFilterName({ page, payload })
|
||||
if (!globalTenant) {
|
||||
await expect(assignTenantModal).toBeVisible()
|
||||
return
|
||||
}
|
||||
|
||||
// Open the assign tenant modal
|
||||
const docControlsPopup = page.locator('.doc-controls__popup')
|
||||
const docControlsButton = docControlsPopup.locator('.popup-button')
|
||||
await expect(docControlsButton).toBeVisible()
|
||||
await docControlsButton.click()
|
||||
|
||||
const assignTenantButtonLocator = docControlsPopup.locator('button', { hasText: 'Assign Site' })
|
||||
await expect(assignTenantButtonLocator).toBeVisible()
|
||||
await assignTenantButtonLocator.click()
|
||||
|
||||
await expect(assignTenantModal).toBeVisible()
|
||||
}
|
||||
|
||||
async function selectDocumentTenant({
|
||||
page,
|
||||
tenant,
|
||||
action = 'confirm',
|
||||
payload,
|
||||
}: {
|
||||
action?: 'cancel' | 'confirm'
|
||||
page: Page
|
||||
payload: PayloadTestSDK<Config>
|
||||
tenant: string
|
||||
}): Promise<void> {
|
||||
await closeNav(page)
|
||||
await openAssignTenantModal({ page, payload })
|
||||
await selectInput({
|
||||
selectLocator: page.locator('.tenantField'),
|
||||
option: tenant,
|
||||
multiSelect: false,
|
||||
})
|
||||
|
||||
const assignTenantModal = page.locator('#assign-tenant-field-modal')
|
||||
if (action === 'confirm') {
|
||||
await assignTenantModal.locator('button', { hasText: 'Confirm' }).click()
|
||||
await expect(assignTenantModal).toBeHidden()
|
||||
} else {
|
||||
await assignTenantModal.locator('button', { hasText: 'Cancel' }).click()
|
||||
await expect(assignTenantModal).toBeHidden()
|
||||
}
|
||||
}
|
||||
|
||||
async function getSelectedTenantFilterName({
|
||||
page,
|
||||
payload,
|
||||
}: {
|
||||
page: Page
|
||||
payload: PayloadTestSDK<Config>
|
||||
}): Promise<string | undefined> {
|
||||
const cookies = await page.context().cookies()
|
||||
const tenantIDFromCookie = cookies.find((c) => c.name === 'payload-tenant')?.value
|
||||
if (tenantIDFromCookie) {
|
||||
const tenant = await payload.find({
|
||||
collection: 'tenants',
|
||||
where: {
|
||||
id: {
|
||||
equals: tenantIDFromCookie,
|
||||
},
|
||||
},
|
||||
})
|
||||
return tenant?.docs?.[0]?.name || undefined
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
async function setTenantFilter({
|
||||
page,
|
||||
tenant,
|
||||
urlUtil,
|
||||
}: {
|
||||
page: Page
|
||||
tenant: string
|
||||
urlUtil: AdminUrlUtil
|
||||
}): Promise<void> {
|
||||
await page.goto(urlUtil.list)
|
||||
await openNav(page)
|
||||
await selectInput({
|
||||
selectLocator: page.locator('.tenant-selector'),
|
||||
option: tenant,
|
||||
multiSelect: false,
|
||||
})
|
||||
}
|
||||
|
||||
async function switchGlobalDocTenant({
|
||||
page,
|
||||
tenant,
|
||||
}: {
|
||||
page: Page
|
||||
tenant: string
|
||||
}): Promise<void> {
|
||||
await openNav(page)
|
||||
return selectInput({
|
||||
selectLocator: page.locator('.tenantField'),
|
||||
option: tenant,
|
||||
multiSelect: false,
|
||||
})
|
||||
}
|
||||
|
||||
async function selectTenant({ page, tenant }: { page: Page; tenant: string }): Promise<void> {
|
||||
await openNav(page)
|
||||
return selectInput({
|
||||
await selectInput({
|
||||
selectLocator: page.locator('.tenant-selector'),
|
||||
option: tenant,
|
||||
multiSelect: false,
|
||||
})
|
||||
}
|
||||
|
||||
async function clearGlobalTenant({ page }: { page: Page }): Promise<void> {
|
||||
async function clearTenantFilter({ page }: { page: Page }): Promise<void> {
|
||||
await openNav(page)
|
||||
return clearSelectInput({
|
||||
await clearSelectInput({
|
||||
selectLocator: page.locator('.tenant-selector'),
|
||||
})
|
||||
await closeNav(page)
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ export interface FoodItem {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: string;
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
|
||||
Reference in New Issue
Block a user