feat: initial commit
This commit is contained in:
6
src/app/(app)/index.scss
Normal file
6
src/app/(app)/index.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.multi-tenant {
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
19
src/app/(app)/layout.tsx
Normal file
19
src/app/(app)/layout.tsx
Normal 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
30
src/app/(app)/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
111
src/app/(app)/tenant-domains/[tenant]/[...slug]/page.tsx
Normal file
111
src/app/(app)/tenant-domains/[tenant]/[...slug]/page.tsx
Normal 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} />
|
||||
}
|
||||
14
src/app/(app)/tenant-domains/[tenant]/login/page.tsx
Normal file
14
src/app/(app)/tenant-domains/[tenant]/login/page.tsx
Normal 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} />
|
||||
}
|
||||
3
src/app/(app)/tenant-domains/[tenant]/page.tsx
Normal file
3
src/app/(app)/tenant-domains/[tenant]/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import Page from './[...slug]/page'
|
||||
|
||||
export default Page
|
||||
106
src/app/(app)/tenant-slugs/[tenant]/[...slug]/page.tsx
Normal file
106
src/app/(app)/tenant-slugs/[tenant]/[...slug]/page.tsx
Normal 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} />
|
||||
}
|
||||
14
src/app/(app)/tenant-slugs/[tenant]/login/page.tsx
Normal file
14
src/app/(app)/tenant-slugs/[tenant]/login/page.tsx
Normal 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} />
|
||||
}
|
||||
3
src/app/(app)/tenant-slugs/[tenant]/page.tsx
Normal file
3
src/app/(app)/tenant-slugs/[tenant]/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import Page from './[...slug]/page'
|
||||
|
||||
export default Page
|
||||
Reference in New Issue
Block a user