Skip to content

Commit

Permalink
fix(plugin-multi-tenant): selected tenant could become incorrect when…
Browse files Browse the repository at this point in the history
… navigating out of doc (#10723)

### What?
When switching tenants from within a document and then navigating back
out to the list view, the tenant would not be set correctly.

### Why?
This was because we handle the tenant selector selection differently
when viewing a document.

### How?
Now when you navigate out, the page will refresh the cookie.

Also adds test suite config that shows how the dom can be used to
manipulate styles per tenant.
  • Loading branch information
JarrodMFlesch authored Jan 22, 2025
1 parent c1b912d commit e6d0260
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 33 deletions.
9 changes: 9 additions & 0 deletions examples/multi-tenant/src/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,17 @@ export interface Page {
export interface Tenant {
id: string;
name: string;
/**
* Used for domain-based tenant handling
*/
domain?: string | null;
/**
* Used for url paths, example: /tenant-slug/page-slug
*/
slug: string;
/**
* If checked, logging in is not required to read. Useful for building public pages.
*/
allowPublicRead?: boolean | null;
updatedAt: string;
createdAt: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CollectionSlug, ServerProps, ViewTypes } from 'payload'

import { headers as getHeaders } from 'next/headers.js'
import { redirect } from 'next/navigation.js'

import { getGlobalViewRedirect } from '../../utilities/getGlobalViewRedirect.js'
Expand All @@ -16,9 +17,11 @@ export const GlobalViewRedirect = async (args: Args) => {
const collectionSlug = args?.collectionSlug

if (collectionSlug && args.globalSlugs?.includes(collectionSlug)) {
const headers = await getHeaders()
const redirectRoute = await getGlobalViewRedirect({
slug: collectionSlug,
docID: args.docID,
headers,
payload: args.payload,
tenantFieldName: args.tenantFieldName,
view: args.viewType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export const TenantSelectionProviderClient = ({
const [preventRefreshOnChange, setPreventRefreshOnChange] = React.useState(false)
const { user } = useAuth()
const userID = React.useMemo(() => user?.id, [user?.id])
const selectedTenantLabel = React.useMemo(
() => tenantOptions.find((option) => option.value === selectedTenantID)?.label,
[selectedTenantID, tenantOptions],
)

const router = useRouter()

Expand Down Expand Up @@ -80,16 +84,21 @@ export const TenantSelectionProviderClient = ({
}, [userID, router])

return (
<Context.Provider
value={{
options: tenantOptions,
selectedTenantID,
setPreventRefreshOnChange,
setTenant,
}}
<span
data-selected-tenant-id={selectedTenantID}
data-selected-tenant-title={selectedTenantLabel}
>
{children}
</Context.Provider>
<Context.Provider
value={{
options: tenantOptions,
selectedTenantID,
setPreventRefreshOnChange,
setTenant,
}}
>
{children}
</Context.Provider>
</span>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,10 @@ export const TenantSelectionProvider = async ({

const cookies = await getCookies()
const tenantCookie = cookies.get('payload-tenant')?.value
const selectedTenant =
tenantOptions.find((option) => option.value === tenantCookie)?.label || tenantCookie

return (
<span data-selected-tenant-id={tenantCookie} data-selected-tenant-title={selectedTenant}>
<TenantSelectionProviderClient initialValue={tenantCookie} tenantOptions={tenantOptions}>
{children}
</TenantSelectionProviderClient>
</span>
<TenantSelectionProviderClient initialValue={tenantCookie} tenantOptions={tenantOptions}>
{children}
</TenantSelectionProviderClient>
)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { Payload, ViewTypes } from 'payload'

import { headers as getHeaders } from 'next/headers.js'

import { SELECT_ALL } from '../constants.js'
import { getTenantFromCookie } from './getTenantFromCookie.js'

type Args = {
docID?: number | string
headers: Headers
payload: Payload
slug: string
tenantFieldName: string
Expand All @@ -15,11 +14,11 @@ type Args = {
export async function getGlobalViewRedirect({
slug,
docID,
headers,
payload,
tenantFieldName,
view,
}: Args): Promise<string | void> {
const headers = await getHeaders()
const tenant = getTenantFromCookie(headers, payload.db.defaultIDType)
let redirectRoute

Expand Down
17 changes: 7 additions & 10 deletions test/plugin-multi-tenant/collections/Posts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CollectionConfig } from 'payload'

import { postsSlug } from '../shared.js'
import { userFilterOptions } from './Users/filterOptions.js'

export const Posts: CollectionConfig = {
slug: postsSlug,
Expand All @@ -18,20 +19,16 @@ export const Posts: CollectionConfig = {
type: 'text',
required: true,
},
{
name: 'excerpt',
label: 'Excerpt',
type: 'text',
},
{
type: 'text',
name: 'slug',
localized: true,
},
{
name: 'relatedLinks',
relationTo: 'links',
type: 'relationship',
},
{
name: 'author',
relationTo: 'users',
type: 'relationship',
filterOptions: userFilterOptions,
},
],
}
25 changes: 25 additions & 0 deletions test/plugin-multi-tenant/collections/Users/filterOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { FilterOptions, Where } from 'payload'

import { getTenantFromCookie } from '@payloadcms/plugin-multi-tenant/utilities'

export const userFilterOptions: FilterOptions = ({ req }) => {
const selectedTenant = getTenantFromCookie(req.headers, req.payload.db.defaultIDType)
if (!selectedTenant) {
return false
}

return {
or: [
{
'tenants.tenant': {
equals: selectedTenant,
},
},
{
roles: {
in: ['admin'],
},
},
],
} as Where
}
34 changes: 34 additions & 0 deletions test/plugin-multi-tenant/collections/Users/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { CollectionConfig } from 'payload'

import { usersSlug } from '../../shared.js'

export const Users: CollectionConfig = {
slug: usersSlug,
auth: true,
admin: {
useAsTitle: 'email',
},
access: {
read: () => true,
},
fields: [
// Email added by default
// Add more fields as needed
{
type: 'select',
name: 'roles',
hasMany: true,
options: [
{
label: 'Admin',
value: 'admin',
},
{
label: 'User',
value: 'user',
},
],
saveToJWT: true,
},
],
}
5 changes: 5 additions & 0 deletions test/plugin-multi-tenant/components/Icon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import './styles.css'

export function Icon() {
return <div id="tenant-icon" />
}
13 changes: 13 additions & 0 deletions test/plugin-multi-tenant/components/Icon/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#tenant-icon {
border-radius: 100%;
height: 18px;
width: 18px;
}

[data-selected-tenant-title="Blue Dog"] #tenant-icon {
background-color: var(--theme-success-300);
}

[data-selected-tenant-title="Steel Cat"] #tenant-icon {
background-color: var(--theme-warning-300);
}
5 changes: 5 additions & 0 deletions test/plugin-multi-tenant/components/Logo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import './styles.css'

export function Logo() {
return <div id="tenant-logo" />
}
13 changes: 13 additions & 0 deletions test/plugin-multi-tenant/components/Logo/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#tenant-logo {
border-radius: 100%;
height: 18px;
width: 18px;
}

[data-selected-tenant-title="Blue Dog"] #tenant-logo {
background-color: var(--theme-success-300);
}

[data-selected-tenant-title="Steel Cat"] #tenant-logo {
background-color: var(--theme-warning-300);
}
6 changes: 6 additions & 0 deletions test/plugin-multi-tenant/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export default buildConfigWithDefaults({
importMap: {
baseDir: path.resolve(dirname),
},
components: {
graphics: {
Logo: '/components/Logo/index.js#Logo',
Icon: '/components/Icon/index.js#Icon',
},
},
},
onInit: seed,
plugins: [
Expand Down
6 changes: 2 additions & 4 deletions test/plugin-multi-tenant/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,8 @@ export interface Post {
id: string;
tenant?: (string | null) | Tenant;
title: string;
excerpt?: string | null;
slug?: string | null;
relatedLinks?: (string | null) | Link;
author?: (string | null) | User;
updatedAt: string;
createdAt: string;
}
Expand Down Expand Up @@ -247,9 +246,8 @@ export interface TenantsSelect<T extends boolean = true> {
export interface PostsSelect<T extends boolean = true> {
tenant?: T;
title?: T;
excerpt?: T;
slug?: T;
relatedLinks?: T;
author?: T;
updatedAt?: T;
createdAt?: T;
}
Expand Down

0 comments on commit e6d0260

Please sign in to comment.