Skip to content

Commit 47f63fa

Browse files
test: multi-tenant login tiles (#14940)
Just adds buttons for logging in with different types of users in the plugin-multi-tenant test config. <img width="658" height="863" alt="CleanShot 2025-12-16 at 09 31 49" src="https://github.com/user-attachments/assets/7bfda8ec-8da7-44b0-892c-4f6fabe327e7" />
1 parent ec7c192 commit 47f63fa

6 files changed

Lines changed: 171 additions & 2 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
.before-login {
2+
margin-bottom: calc(var(--base) * 1.5);
3+
4+
&__title {
5+
margin-bottom: calc(var(--base) / 2);
6+
font-size: 1.1rem;
7+
font-weight: 600;
8+
color: var(--theme-elevation-800);
9+
}
10+
11+
&__description {
12+
margin-bottom: var(--base);
13+
font-size: 0.9rem;
14+
color: var(--theme-elevation-600);
15+
}
16+
17+
&__buttons {
18+
display: flex;
19+
flex-direction: column;
20+
gap: calc(var(--base) * 0.75);
21+
}
22+
23+
&__button {
24+
padding: calc(var(--base) * 0.5) calc(var(--base) * 0.75);
25+
border: 1px solid var(--theme-elevation-150);
26+
border-radius: 4px;
27+
background-color: var(--theme-elevation-0);
28+
cursor: pointer;
29+
text-align: left;
30+
transition: all 0.2s;
31+
32+
&:hover:not(:disabled) {
33+
background-color: var(--theme-success-50);
34+
border-color: var(--theme-success-250);
35+
}
36+
37+
&:disabled {
38+
cursor: not-allowed;
39+
opacity: 0.5;
40+
}
41+
42+
&--loading {
43+
background-color: var(--theme-elevation-50);
44+
}
45+
}
46+
47+
&__button-name {
48+
font-weight: 600;
49+
margin-bottom: calc(var(--base) / 4);
50+
color: var(--theme-elevation-800);
51+
}
52+
53+
&__button-details {
54+
font-size: 0.85rem;
55+
color: var(--theme-elevation-600);
56+
}
57+
58+
&__error {
59+
margin-top: var(--base);
60+
padding: calc(var(--base) * 0.75);
61+
background-color: var(--theme-error-50);
62+
border: 1px solid var(--theme-error-200);
63+
border-radius: 4px;
64+
color: var(--theme-error-600);
65+
font-size: 0.9rem;
66+
}
67+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use client'
2+
3+
import type { PayloadClientReactComponent, SanitizedConfig } from 'payload'
4+
5+
import { useAuth, useRouteCache } from '@payloadcms/ui'
6+
import React, { useState } from 'react'
7+
8+
import { credentials } from '../../credentials.js'
9+
import './index.scss'
10+
11+
const baseClass = 'before-login'
12+
13+
const testUsers = [
14+
{
15+
name: 'Admin (All Tenants)',
16+
...credentials.admin,
17+
},
18+
{
19+
name: 'Blue Dog User',
20+
...credentials.blueDog,
21+
},
22+
{
23+
name: 'Multi-Tenant Owner',
24+
...credentials.owner,
25+
},
26+
{
27+
name: 'Steel Cat User',
28+
...credentials.steelCat,
29+
},
30+
]
31+
32+
export const BeforeLogin: PayloadClientReactComponent<
33+
SanitizedConfig['admin']['components']['beforeLogin'][0]
34+
> = () => {
35+
const { setUser } = useAuth()
36+
const [loading, setLoading] = useState<null | string>(null)
37+
const [error, setError] = useState<null | string>(null)
38+
const { clearRouteCache } = useRouteCache()
39+
40+
const login = React.useCallback(
41+
async (credentials: { email: string; password: string }) => {
42+
const response = await fetch('/api/users/login', {
43+
body: JSON.stringify(credentials),
44+
headers: {
45+
'Content-Type': 'application/json',
46+
},
47+
method: 'POST',
48+
})
49+
50+
if (!response.ok) {
51+
throw new Error(`Login failed with status ${response.status}`)
52+
}
53+
54+
const data = await response.json()
55+
setUser(data)
56+
clearRouteCache()
57+
},
58+
[setUser, clearRouteCache],
59+
)
60+
61+
const handleQuickLogin = async (email: string, password: string) => {
62+
setLoading(email)
63+
setError(null)
64+
try {
65+
await login({ email, password })
66+
} catch (err) {
67+
setError(err instanceof Error ? err.message : 'Login failed')
68+
setLoading(null)
69+
}
70+
}
71+
72+
return (
73+
<div className={baseClass}>
74+
<h3 className={`${baseClass}__title`}>Multi-Tenant Test Users</h3>
75+
<p className={`${baseClass}__description`}>
76+
Quick login for testing different tenant access patterns
77+
</p>
78+
<div className={`${baseClass}__buttons`}>
79+
{testUsers.map((user) => (
80+
<button
81+
className={`${baseClass}__button ${loading === user.email ? `${baseClass}__button--loading` : ''}`}
82+
disabled={loading !== null}
83+
key={user.email}
84+
onClick={() => handleQuickLogin(user.email, user.password)}
85+
type="button"
86+
>
87+
<div className={`${baseClass}__button-name`}>
88+
{loading === user.email ? 'Logging in...' : user.email}
89+
</div>
90+
<div className={`${baseClass}__button-details`}>{user.name}</div>
91+
</button>
92+
))}
93+
</div>
94+
{error && <div className={`${baseClass}__error`}>{error}</div>}
95+
</div>
96+
)
97+
}

test/plugin-multi-tenant/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default buildConfigWithDefaults({
2525
baseDir: path.resolve(dirname),
2626
},
2727
components: {
28+
beforeLogin: ['/components/BeforeLogin/index.js#BeforeLogin'],
2829
graphics: {
2930
Logo: '/components/Logo/index.js#Logo',
3031
Icon: '/components/Icon/index.js#Icon',

test/plugin-multi-tenant/credentials.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ export const credentials = {
1111
email: 'owner@anchorAndBlueDog.com',
1212
password: 'test',
1313
},
14+
steelCat: {
15+
email: 'huel@steel-cat.com',
16+
password: 'test',
17+
},
1418
} as const

test/plugin-multi-tenant/payload-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export interface Config {
9898
db: {
9999
defaultIDType: string;
100100
};
101+
fallbackLocale: ('false' | 'none' | 'null') | false | null | ('en' | 'es' | 'fr') | ('en' | 'es' | 'fr')[];
101102
globals: {};
102103
globalsSelect: {};
103104
locale: 'en' | 'es' | 'fr';

test/plugin-multi-tenant/seed/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,7 @@ export const seed: Config['onInit'] = async (payload) => {
239239
await payload.create({
240240
collection: usersSlug,
241241
data: {
242-
email: 'huel@steel-cat.com',
243-
password: 'test',
242+
...credentials.steelCat,
244243
roles: ['user'],
245244
tenants: [
246245
{

0 commit comments

Comments
 (0)