feat: initial commit

This commit is contained in:
2025-12-02 09:00:58 +01:00
commit cee5925f25
51 changed files with 9891 additions and 0 deletions

6
src/app/(app)/index.scss Normal file
View File

@@ -0,0 +1,6 @@
.multi-tenant {
body {
margin: 0;
padding: 10px;
}
}

19
src/app/(app)/layout.tsx Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react'
import './index.scss'
const baseClass = 'multi-tenant'
export const metadata = {
description: 'Generated by Next.js',
title: 'Next.js',
}
// eslint-disable-next-line no-restricted-exports
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html className={baseClass} lang="en">
<body>{children}</body>
</html>
)
}

30
src/app/(app)/page.tsx Normal file
View File

@@ -0,0 +1,30 @@
export default async ({ params: paramsPromise }: { params: Promise<{ slug: string[] }> }) => {
return (
<div>
<h1>Multi-Tenant Example</h1>
<p>
This multi-tenant example allows you to explore multi-tenancy with domains and with slugs.
</p>
<h2>Domains</h2>
<p>When you visit a tenant by domain, the domain is used to determine the tenant.</p>
<p>
For example, visiting{' '}
<a href="http://gold.localhost:3000/tenant-domains/login">
http://gold.localhost:3000/tenant-domains/login
</a>{' '}
will show the tenant with the domain "gold.localhost".
</p>
<h2>Slugs</h2>
<p>When you visit a tenant by slug, the slug is used to determine the tenant.</p>
<p>
For example, visiting{' '}
<a href="http://localhost:3000/tenant-slugs/silver/login">
http://localhost:3000/tenant-slugs/silver/login
</a>{' '}
will show the tenant with the slug "silver".
</p>
</div>
)
}

View File

@@ -0,0 +1,111 @@
import type { Where } from 'payload'
import configPromise from '@payload-config'
import { headers as getHeaders } from 'next/headers'
import { notFound, redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import { RenderPage } from '../../../../components/RenderPage'
// eslint-disable-next-line no-restricted-exports
export default async function Page({
params: paramsPromise,
}: {
params: Promise<{ slug?: string[]; tenant: string }>
}) {
const params = await paramsPromise
let slug = undefined
if (params?.slug) {
// remove the domain route param
params.slug.splice(0, 1)
slug = params.slug
}
const headers = await getHeaders()
const payload = await getPayload({ config: configPromise })
const { user } = await payload.auth({ headers })
try {
const tenantsQuery = await payload.find({
collection: 'tenants',
overrideAccess: false,
user,
where: {
domain: {
equals: params.tenant,
},
},
})
// If no tenant is found, the user does not have access
// Show the login view
if (tenantsQuery.docs.length === 0) {
redirect(
`/tenant-domains/login?redirect=${encodeURIComponent(
`/tenant-domains${slug ? `/${slug.join('/')}` : ''}`,
)}`,
)
}
} catch (e) {
// If the query fails, it means the user did not have access to query on the domain field
// Show the login view
redirect(
`/tenant-domains/login?redirect=${encodeURIComponent(
`/tenant-domains${slug ? `/${slug.join('/')}` : ''}`,
)}`,
)
}
const slugConstraint: Where = slug
? {
slug: {
equals: slug.join('/'),
},
}
: {
or: [
{
slug: {
equals: '',
},
},
{
slug: {
equals: 'home',
},
},
{
slug: {
exists: false,
},
},
],
}
const pageQuery = await payload.find({
collection: 'pages',
overrideAccess: false,
user,
where: {
and: [
{
'tenant.domain': {
equals: params.tenant,
},
},
slugConstraint,
],
},
})
const pageData = pageQuery.docs?.[0]
// The page with the provided slug could not be found
if (!pageData) {
return notFound()
}
// The page was found, render the page with data
return <RenderPage data={pageData} />
}

View File

@@ -0,0 +1,14 @@
import React from 'react'
import { Login } from '../../../../components/Login/client.page'
type RouteParams = {
tenant: string
}
// eslint-disable-next-line no-restricted-exports
export default async function Page({ params: paramsPromise }: { params: Promise<RouteParams> }) {
const params = await paramsPromise
return <Login tenantDomain={params.tenant} />
}

View File

@@ -0,0 +1,3 @@
import Page from './[...slug]/page'
export default Page

View File

@@ -0,0 +1,106 @@
import type { Where } from 'payload'
import configPromise from '@payload-config'
import { headers as getHeaders } from 'next/headers'
import { notFound, redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import { RenderPage } from '../../../../components/RenderPage'
// eslint-disable-next-line no-restricted-exports
export default async function Page({
params: paramsPromise,
}: {
params: Promise<{ slug?: string[]; tenant: string }>
}) {
const params = await paramsPromise
const headers = await getHeaders()
const payload = await getPayload({ config: configPromise })
const { user } = await payload.auth({ headers })
const slug = params?.slug
try {
const tenantsQuery = await payload.find({
collection: 'tenants',
overrideAccess: false,
user,
where: {
slug: {
equals: params.tenant,
},
},
})
// If no tenant is found, the user does not have access
// Show the login view
if (tenantsQuery.docs.length === 0) {
redirect(
`/tenant-slugs/${params.tenant}/login?redirect=${encodeURIComponent(
`/tenant-slugs/${params.tenant}${slug ? `/${slug.join('/')}` : ''}`,
)}`,
)
}
} catch (e) {
// If the query fails, it means the user did not have access to query on the slug field
// Show the login view
redirect(
`/tenant-slugs/${params.tenant}/login?redirect=${encodeURIComponent(
`/tenant-slugs/${params.tenant}${slug ? `/${slug.join('/')}` : ''}`,
)}`,
)
}
const slugConstraint: Where = slug
? {
slug: {
equals: slug.join('/'),
},
}
: {
or: [
{
slug: {
equals: '',
},
},
{
slug: {
equals: 'home',
},
},
{
slug: {
exists: false,
},
},
],
}
const pageQuery = await payload.find({
collection: 'pages',
overrideAccess: false,
user,
where: {
and: [
{
'tenant.slug': {
equals: params.tenant,
},
},
slugConstraint,
],
},
})
const pageData = pageQuery.docs?.[0]
// The page with the provided slug could not be found
if (!pageData) {
return notFound()
}
// The page was found, render the page with data
return <RenderPage data={pageData} />
}

View File

@@ -0,0 +1,14 @@
import React from 'react'
import { Login } from '../../../../components/Login/client.page'
type RouteParams = {
tenant: string
}
// eslint-disable-next-line no-restricted-exports
export default async function Page({ params: paramsPromise }: { params: Promise<RouteParams> }) {
const params = await paramsPromise
return <Login tenantSlug={params.tenant} />
}

View File

@@ -0,0 +1,3 @@
import Page from './[...slug]/page'
export default Page