1
0
Fork 0

feat: initial payload scaffolding

This commit is contained in:
Z8MB1E 2025-10-24 02:07:13 -04:00
parent e4a367489f
commit 4d639387f4
34 changed files with 948 additions and 339 deletions

View file

@ -1,6 +1,6 @@
{ {
"singleQuote": true, "singleQuote": false,
"trailingComma": "all", "trailingComma": "all",
"printWidth": 100, "printWidth": 100,
"semi": false "semi": true
} }

View file

@ -1,3 +1,6 @@
{ {
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] "recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
} }

8
.vscode/launch.json vendored
View file

@ -9,8 +9,12 @@
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/node_modules/next/dist/bin/next", "program": "${workspaceFolder}/node_modules/next/dist/bin/next",
"runtimeArgs": ["--inspect"], "runtimeArgs": [
"skipFiles": ["<node_internals>/**"], "--inspect"
],
"skipFiles": [
"<node_internals>/**"
],
"serverReadyAction": { "serverReadyAction": {
"action": "debugWithChrome", "action": "debugWithChrome",
"killOnServerStop": true, "killOnServerStop": true,

View file

@ -4,7 +4,8 @@ This template comes configured with the bare minimum to get started on anything
## Quick start ## Quick start
This template can be deployed directly from our Cloud hosting and it will setup MongoDB and cloud S3 object storage for media. This template can be deployed directly from our Cloud hosting and it will setup MongoDB and cloud S3 object storage for
media.
## Quick Start - local setup ## Quick Start - local setup
@ -12,21 +13,27 @@ To spin up this template locally, follow these steps:
### Clone ### Clone
After you click the `Deploy` button above, you'll want to have standalone copy of this repo on your machine. If you've already cloned this repo, skip to [Development](#development). After you click the `Deploy` button above, you'll want to have standalone copy of this repo on your machine. If you've
already cloned this repo, skip to [Development](#development).
### Development ### Development
1. First [clone the repo](#clone) if you have not done so already 1. First [clone the repo](#clone) if you have not done so already
2. `cd my-project && cp .env.example .env` to copy the example environment variables. You'll need to add the `MONGODB_URI` from your Cloud project to your `.env` if you want to use S3 storage and the MongoDB database that was created for you. 2. `cd my-project && cp .env.example .env` to copy the example environment variables. You'll need to add the
`MONGODB_URI` from your Cloud project to your `.env` if you want to use S3 storage and the MongoDB database that was
created for you.
3. `pnpm install && pnpm dev` to install dependencies and start the dev server 3. `pnpm install && pnpm dev` to install dependencies and start the dev server
4. open `http://localhost:3000` to open the app in your browser 4. open `http://localhost:3000` to open the app in your browser
That's it! Changes made in `./src` will be reflected in your app. Follow the on-screen instructions to login and create your first admin user. Then check out [Production](#production) once you're ready to build and serve your app, and [Deployment](#deployment) when you're ready to go live. That's it! Changes made in `./src` will be reflected in your app. Follow the on-screen instructions to login and create
your first admin user. Then check out [Production](#production) once you're ready to build and serve your app,
and [Deployment](#deployment) when you're ready to go live.
#### Docker (Optional) #### Docker (Optional)
If you prefer to use Docker for local development instead of a local MongoDB instance, the provided docker-compose.yml file can be used. If you prefer to use Docker for local development instead of a local MongoDB instance, the provided docker-compose.yml
file can be used.
To do so, follow these steps: To do so, follow these steps:
@ -40,28 +47,35 @@ The Payload config is tailored specifically to the needs of most websites. It is
### Collections ### Collections
See the [Collections](https://payloadcms.com/docs/configuration/collections) docs for details on how to extend this functionality. See the [Collections](https://payloadcms.com/docs/configuration/collections) docs for details on how to extend this
functionality.
- #### Users (Authentication) - #### Users (Authentication)
Users are auth-enabled collections that have access to the admin panel. Users are auth-enabled collections that have access to the admin panel.
For additional help, see the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth) or the [Authentication](https://payloadcms.com/docs/authentication/overview#authentication-overview) docs. For additional help, see the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth) or
the [Authentication](https://payloadcms.com/docs/authentication/overview#authentication-overview) docs.
- #### Media - #### Media
This is the uploads enabled collection. It features pre-configured sizes, focal point and manual resizing to help you manage your pictures. This is the uploads enabled collection. It features pre-configured sizes, focal point and manual resizing to help you
manage your pictures.
### Docker ### Docker
Alternatively, you can use [Docker](https://www.docker.com) to spin up this template locally. To do so, follow these steps: Alternatively, you can use [Docker](https://www.docker.com) to spin up this template locally. To do so, follow these
steps:
1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in your project root 1. Follow [steps 1 and 2 from above](#development), the docker-compose file will automatically use the `.env` file in
your project root
1. Next run `docker-compose up` 1. Next run `docker-compose up`
1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user 1. Follow [steps 4 and 5 from above](#development) to login and create your first admin user
That's it! The Docker instance will help you get up and running quickly while also standardizing the development environment across your teams. That's it! The Docker instance will help you get up and running quickly while also standardizing the development
environment across your teams.
## Questions ## Questions
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions). If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start
a [GitHub discussion](https://github.com/payloadcms/payload/discussions).

View file

@ -1,38 +1,38 @@
import { dirname } from 'path' import { dirname } from "path";
import { fileURLToPath } from 'url' import { fileURLToPath } from "url";
import { FlatCompat } from '@eslint/eslintrc' import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url) const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename) const __dirname = dirname(__filename);
const compat = new FlatCompat({ const compat = new FlatCompat({
baseDirectory: __dirname, baseDirectory: __dirname,
}) });
const eslintConfig = [ const eslintConfig = [
...compat.extends('next/core-web-vitals', 'next/typescript'), ...compat.extends("next/core-web-vitals", "next/typescript"),
{ {
rules: { rules: {
'@typescript-eslint/ban-ts-comment': 'warn', "@typescript-eslint/ban-ts-comment": "warn",
'@typescript-eslint/no-empty-object-type': 'warn', "@typescript-eslint/no-empty-object-type": "warn",
'@typescript-eslint/no-explicit-any': 'warn', "@typescript-eslint/no-explicit-any": "warn",
'@typescript-eslint/no-unused-vars': [ "@typescript-eslint/no-unused-vars": [
'warn', "warn",
{ {
vars: 'all', vars: "all",
args: 'after-used', args: "after-used",
ignoreRestSiblings: false, ignoreRestSiblings: false,
argsIgnorePattern: '^_', argsIgnorePattern: "^_",
varsIgnorePattern: '^_', varsIgnorePattern: "^_",
destructuredArrayIgnorePattern: '^_', destructuredArrayIgnorePattern: "^_",
caughtErrorsIgnorePattern: '^(_|ignore)', caughtErrorsIgnorePattern: "^(_|ignore)",
}, },
], ],
}, },
}, },
{ {
ignores: ['.next/'], ignores: [".next/"],
}, },
] ];
export default eslintConfig export default eslintConfig;

View file

@ -1,17 +1,17 @@
import { withPayload } from '@payloadcms/next/withPayload' import { withPayload } from "@payloadcms/next/withPayload";
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
// Your Next.js config here // Your Next.js config here
webpack: (webpackConfig) => { webpack: (webpackConfig) => {
webpackConfig.resolve.extensionAlias = { webpackConfig.resolve.extensionAlias = {
'.cjs': ['.cts', '.cjs'], ".cjs": [".cts", ".cjs"],
'.js': ['.ts', '.tsx', '.js', '.jsx'], ".js": [".ts", ".tsx", ".js", ".jsx"],
'.mjs': ['.mts', '.mjs'], ".mjs": [".mts", ".mjs"],
} };
return webpackConfig return webpackConfig;
}, },
} };
export default withPayload(nextConfig, { devBundleServerPackages: false }) export default withPayload(nextConfig, { devBundleServerPackages: false });

View file

@ -1,16 +1,16 @@
import { defineConfig, devices } from '@playwright/test' import { defineConfig, devices } from "@playwright/test";
/** /**
* Read environment variables from file. * Read environment variables from file.
* https://github.com/motdotla/dotenv * https://github.com/motdotla/dotenv
*/ */
import 'dotenv/config' import "dotenv/config";
/** /**
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.
*/ */
export default defineConfig({ export default defineConfig({
testDir: './tests/e2e', testDir: "./tests/e2e",
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
/* Retry on CI only */ /* Retry on CI only */
@ -18,24 +18,24 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html', reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000', // baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry', trace: "on-first-retry",
}, },
projects: [ projects: [
{ {
name: 'chromium', name: "chromium",
use: { ...devices['Desktop Chrome'] }, use: { ...devices["Desktop Chrome"] },
}, },
], ],
webServer: { webServer: {
command: 'pnpm dev', command: "pnpm dev",
reuseExistingServer: true, reuseExistingServer: true,
url: 'http://localhost:3000', url: "http://localhost:3000",
}, },
}) });

View file

@ -1,13 +1,13 @@
import React from 'react' import React from "react";
import './styles.css' import "./styles.css";
export const metadata = { export const metadata = {
description: 'A blank template using Payload in a Next.js app.', description: "A blank template using Payload in a Next.js app.",
title: 'Payload Blank Template', title: "Payload Blank Template",
} };
export default async function RootLayout(props: { children: React.ReactNode }) { export default async function RootLayout(props: { children: React.ReactNode }) {
const { children } = props const { children } = props;
return ( return (
<html lang="en"> <html lang="en">
@ -15,5 +15,5 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
<main>{children}</main> <main>{children}</main>
</body> </body>
</html> </html>
) );
} }

View file

@ -1,19 +1,19 @@
import { headers as getHeaders } from 'next/headers.js' import { headers as getHeaders } from "next/headers.js";
import Image from 'next/image' import Image from "next/image";
import { getPayload } from 'payload' import { getPayload } from "payload";
import React from 'react' import React from "react";
import { fileURLToPath } from 'url' import { fileURLToPath } from "url";
import config from '@/payload.config' import config from "@/payload.config";
import './styles.css' import "./styles.css";
export default async function HomePage() { export default async function HomePage() {
const headers = await getHeaders() const headers = await getHeaders();
const payloadConfig = await config const payloadConfig = await config;
const payload = await getPayload({ config: payloadConfig }) const payload = await getPayload({ config: payloadConfig });
const { user } = await payload.auth({ headers }) const { user } = await payload.auth({ headers });
const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}` const fileURL = `webstorm://file/${fileURLToPath(import.meta.url)}`;
return ( return (
<div className="home"> <div className="home">
@ -27,8 +27,10 @@ export default async function HomePage() {
width={65} width={65}
/> />
</picture> </picture>
{!user && <h1>Welcome to your new project.</h1>} <h1>Polaris Task Force</h1>
{user && <h1>Welcome back, {user.email}</h1>} {/*{!user && <h1>Welcome to your new project.</h1>}
{user && <h1>Welcome back, {user.email}</h1>}*/}
<p>This site is under construction.</p>
<div className="links"> <div className="links">
<a <a
className="admin" className="admin"
@ -48,12 +50,12 @@ export default async function HomePage() {
</a> </a>
</div> </div>
</div> </div>
<div className="footer"> {/* <div className="footer">
<p>Update this page by editing</p> <p>Update this page by editing</p>
<a className="codeLink" href={fileURL}> <a className="codeLink" href={fileURL}>
<code>app/(frontend)/page.tsx</code> <code>app/(frontend)/page.tsx</code>
</a> </a>
</div>*/}
</div> </div>
</div> );
)
} }

View file

@ -1,24 +1,24 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from 'next' import type { Metadata } from "next";
import config from '@payload-config' import config from "@payload-config";
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views' import { generatePageMetadata, NotFoundPage } from "@payloadcms/next/views";
import { importMap } from '../importMap' import { importMap } from "../importMap";
type Args = { type Args = {
params: Promise<{ params: Promise<{
segments: string[] segments: string[];
}> }>;
searchParams: Promise<{ searchParams: Promise<{
[key: string]: string | string[] [key: string]: string | string[];
}> }>;
} };
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> => export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams }) generatePageMetadata({ config, params, searchParams });
const NotFound = ({ params, searchParams }: Args) => const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, params, searchParams, importMap }) NotFoundPage({ config, params, searchParams, importMap });
export default NotFound export default NotFound;

View file

@ -1,24 +1,24 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from 'next' import type { Metadata } from "next";
import config from '@payload-config' import config from "@payload-config";
import { RootPage, generatePageMetadata } from '@payloadcms/next/views' import { generatePageMetadata, RootPage } from "@payloadcms/next/views";
import { importMap } from '../importMap' import { importMap } from "../importMap";
type Args = { type Args = {
params: Promise<{ params: Promise<{
segments: string[] segments: string[];
}> }>;
searchParams: Promise<{ searchParams: Promise<{
[key: string]: string | string[] [key: string]: string | string[];
}> }>;
} };
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> => export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams }) generatePageMetadata({ config, params, searchParams });
const Page = ({ params, searchParams }: Args) => const Page = ({ params, searchParams }: Args) =>
RootPage({ config, params, searchParams, importMap }) RootPage({ config, params, searchParams, importMap });
export default Page export default Page;

View file

@ -1 +1,49 @@
export const importMap = {} import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
export const importMap = {
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864
}

View file

@ -1,19 +1,12 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config' import config from "@payload-config";
import '@payloadcms/next/css' import "@payloadcms/next/css";
import { import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST, REST_PUT, } from "@payloadcms/next/routes";
REST_DELETE,
REST_GET,
REST_OPTIONS,
REST_PATCH,
REST_POST,
REST_PUT,
} from '@payloadcms/next/routes'
export const GET = REST_GET(config) export const GET = REST_GET(config);
export const POST = REST_POST(config) export const POST = REST_POST(config);
export const DELETE = REST_DELETE(config) export const DELETE = REST_DELETE(config);
export const PATCH = REST_PATCH(config) export const PATCH = REST_PATCH(config);
export const PUT = REST_PUT(config) export const PUT = REST_PUT(config);
export const OPTIONS = REST_OPTIONS(config) export const OPTIONS = REST_OPTIONS(config);

View file

@ -4,4 +4,4 @@ import config from '@payload-config'
import '@payloadcms/next/css' import '@payloadcms/next/css'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes' import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export const GET = GRAPHQL_PLAYGROUND_GET(config) export const GET = GRAPHQL_PLAYGROUND_GET(config);

View file

@ -3,6 +3,6 @@
import config from '@payload-config' import config from '@payload-config'
import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes' import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes'
export const POST = GRAPHQL_POST(config) export const POST = GRAPHQL_POST(config);
export const OPTIONS = REST_OPTIONS(config) export const OPTIONS = REST_OPTIONS(config);

View file

@ -1,31 +1,31 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config' import config from "@payload-config";
import '@payloadcms/next/css' import "@payloadcms/next/css";
import type { ServerFunctionClient } from 'payload' import type { ServerFunctionClient } from "payload";
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts' import { handleServerFunctions, RootLayout } from "@payloadcms/next/layouts";
import React from 'react' import React from "react";
import { importMap } from './admin/importMap.js' import { importMap } from "./admin/importMap.js";
import './custom.scss' import "./custom.scss";
type Args = { type Args = {
children: React.ReactNode children: React.ReactNode;
} };
const serverFunction: ServerFunctionClient = async function (args) { const serverFunction: ServerFunctionClient = async function (args) {
'use server' "use server";
return handleServerFunctions({ return handleServerFunctions({
...args, ...args,
config, config,
importMap, importMap,
}) });
} };
const Layout = ({ children }: Args) => ( const Layout = ({ children }: Args) => (
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}> <RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
{children} {children}
</RootLayout> </RootLayout>
) );
export default Layout export default Layout;

View file

@ -1,12 +1,12 @@
import configPromise from '@payload-config' import configPromise from "@payload-config";
import { getPayload } from 'payload' import { getPayload } from "payload";
export const GET = async (request: Request) => { export const GET = async (request: Request) => {
const payload = await getPayload({ const payload = await getPayload({
config: configPromise, config: configPromise,
}) });
return Response.json({ return Response.json({
message: 'This is an example of a custom route.', message: "This is an example of a custom route.",
}) });
} };

View file

@ -1,16 +1,16 @@
import type { CollectionConfig } from 'payload' import type { CollectionConfig } from "payload";
export const Media: CollectionConfig = { export const Media: CollectionConfig = {
slug: 'media', slug: "media",
access: { access: {
read: () => true, read: () => true,
}, },
fields: [ fields: [
{ {
name: 'alt', name: "alt",
type: 'text', type: "text",
required: true, required: true,
}, },
], ],
upload: true, upload: true,
} };

View file

@ -1,13 +0,0 @@
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
// Email added by default
// Add more fields as needed
],
}

View file

@ -0,0 +1,57 @@
import { CollectionConfig } from "payload";
import { isAdmin } from "@/utils/access-control/isRole";
export const Awards: CollectionConfig = {
slug: "awards",
access: {
create: isAdmin,
update: isAdmin,
delete: isAdmin,
},
admin: {
useAsTitle: "name",
group: "Users",
},
fields: [
{
name: "name",
type: "text",
required: true,
unique: true,
},
{
name: "description",
type: "richText",
required: true,
},
{
name: "type",
type: "select",
options: [
{
label: "Commendation",
value: "commendation",
},
{
label: "Ribbon",
value: "ribbon",
},
{
label: "Medal",
value: "medal",
},
],
defaultValue: "commendation",
},
{
name: "image",
type: "upload",
relationTo: "media",
},
{
name: "experiencePoints",
type: "number",
min: 1,
},
],
};

View file

@ -0,0 +1,98 @@
import { CollectionConfig } from "payload";
import { Award } from "@/payload-types";
export const Profiles: CollectionConfig = {
slug: "profiles",
admin: {
useAsTitle: "profileTitle",
group: "Users",
defaultColumns: ["profileTitle", "user", "rank"],
},
fields: [
{
name: "user",
type: "relationship",
relationTo: "users",
},
{
name: "rank",
type: "relationship",
relationTo: "ranks",
},
{
name: "profileTitle",
type: "text",
virtual: "user.displayName",
admin: {
hidden: true,
},
label: "Operator",
},
{
name: "dossier",
label: "Personnel Dossier",
type: "group",
fields: [
{
name: "personalExcerpt",
type: "textarea",
},
{
name: "intelExcerpt",
label: "Intelligence Record",
type: "richText",
},
],
},
{
name: "progression",
label: "Progression & XP",
type: "group",
fields: [
{
name: "xp",
label: "Experience Points (XP)",
type: "number",
virtual: true,
defaultValue: 0,
admin: {
readOnly: true,
},
hooks: {
// beforeChange: [({ siblingData }) => delete siblingData["xp"]],
afterRead: [
async ({ req, siblingData }) => {
let xpAcc = 0;
await req.payload
.find({
collection: "awards",
where: {
id: {
in: (siblingData as { awards: number[] }).awards,
},
},
})
.then((res) => {
const awards: Award[] = res.docs;
xpAcc += awards.reduce((prev, cur) => prev + (cur.experiencePoints ?? 0), 0);
});
return xpAcc;
},
],
},
},
{
name: "awards",
type: "relationship",
relationTo: "awards",
hasMany: true,
admin: {
appearance: "drawer",
},
},
],
},
],
};

View file

@ -0,0 +1,40 @@
import { CollectionConfig } from "payload";
import { isAdmin } from "@/utils/access-control/isRole";
export const Ranks: CollectionConfig = {
slug: "ranks",
access: {
create: isAdmin,
update: isAdmin,
delete: isAdmin,
},
admin: {
useAsTitle: "name",
group: "Users"
},
orderable: true,
fields: [
{
name: "name",
type: "text",
required: true,
unique: true,
},
{
name: "abbreviation",
type: "text",
required: true,
unique: true,
},
{
name: "description",
type: "text",
required: true,
},
{
name: "insignia",
type: "upload",
relationTo: "media",
},
],
};

View file

@ -0,0 +1,64 @@
import type { CollectionConfig } from "payload";
import { isAdmin } from "@/utils/access-control/isRole";
export const Users: CollectionConfig = {
slug: "users",
access: {
admin: isAdmin,
unlock: isAdmin,
},
admin: {
useAsTitle: "username",
group: "Users",
},
auth: {
loginWithUsername: {
requireUsername: true,
allowEmailLogin: false,
requireEmail: false,
},
},
fields: [
// Email added by default by Payload, but essentially ignored in this application
{
name: "username",
type: "text",
required: true,
unique: true,
},
{
name: "discordUsername",
type: "text",
required: true,
unique: true,
},
{
name: "displayName",
type: "text",
required: true,
},
{
name: "roles",
type: "select",
hasMany: true,
options: [
{
label: "Guest",
value: "guest",
},
{
label: "User",
value: "user",
},
{
label: "Admin",
value: "admin",
},
{
label: "Developer",
value: "developer",
},
],
},
],
};

View file

@ -69,6 +69,9 @@ export interface Config {
collections: { collections: {
users: User; users: User;
media: Media; media: Media;
ranks: Rank;
profiles: Profile;
awards: Award;
'payload-locked-documents': PayloadLockedDocument; 'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference; 'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration; 'payload-migrations': PayloadMigration;
@ -77,12 +80,15 @@ export interface Config {
collectionsSelect: { collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>; users: UsersSelect<false> | UsersSelect<true>;
media: MediaSelect<false> | MediaSelect<true>; media: MediaSelect<false> | MediaSelect<true>;
ranks: RanksSelect<false> | RanksSelect<true>;
profiles: ProfilesSelect<false> | ProfilesSelect<true>;
awards: AwardsSelect<false> | AwardsSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>; 'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>; 'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>; 'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
}; };
db: { db: {
defaultIDType: string; defaultIDType: number;
}; };
globals: {}; globals: {};
globalsSelect: {}; globalsSelect: {};
@ -97,20 +103,18 @@ export interface Config {
} }
export interface UserAuthOperations { export interface UserAuthOperations {
forgotPassword: { forgotPassword: {
email: string; username: string;
password: string;
}; };
login: { login: {
email: string;
password: string; password: string;
username: string;
}; };
registerFirstUser: { registerFirstUser: {
email: string;
password: string; password: string;
username: string;
}; };
unlock: { unlock: {
email: string; username: string;
password: string;
}; };
} }
/** /**
@ -118,10 +122,14 @@ export interface UserAuthOperations {
* via the `definition` "users". * via the `definition` "users".
*/ */
export interface User { export interface User {
id: string; id: number;
discordUsername: string;
displayName: string;
roles?: ('guest' | 'user' | 'admin' | 'developer')[] | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
email: string; email?: string | null;
username: string;
resetPasswordToken?: string | null; resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null; resetPasswordExpiration?: string | null;
salt?: string | null; salt?: string | null;
@ -142,7 +150,7 @@ export interface User {
* via the `definition` "media". * via the `definition` "media".
*/ */
export interface Media { export interface Media {
id: string; id: number;
alt: string; alt: string;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@ -156,25 +164,113 @@ export interface Media {
focalX?: number | null; focalX?: number | null;
focalY?: number | null; focalY?: number | null;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "ranks".
*/
export interface Rank {
id: number;
_order?: string | null;
name: string;
abbreviation: string;
description: string;
insignia?: (number | null) | Media;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "profiles".
*/
export interface Profile {
id: number;
user?: (number | null) | User;
rank?: (number | null) | Rank;
profileTitle?: string | null;
dossier?: {
personalExcerpt?: string | null;
intelExcerpt?: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
};
progression?: {
xp?: number | null;
awards?: (number | Award)[] | null;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "awards".
*/
export interface Award {
id: number;
name: string;
description: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
};
type?: ('commendation' | 'ribbon' | 'medal') | null;
image?: (number | null) | Media;
experiencePoints?: number | null;
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents". * via the `definition` "payload-locked-documents".
*/ */
export interface PayloadLockedDocument { export interface PayloadLockedDocument {
id: string; id: number;
document?: document?:
| ({ | ({
relationTo: 'users'; relationTo: 'users';
value: string | User; value: number | User;
} | null) } | null)
| ({ | ({
relationTo: 'media'; relationTo: 'media';
value: string | Media; value: number | Media;
} | null)
| ({
relationTo: 'ranks';
value: number | Rank;
} | null)
| ({
relationTo: 'profiles';
value: number | Profile;
} | null)
| ({
relationTo: 'awards';
value: number | Award;
} | null); } | null);
globalSlug?: string | null; globalSlug?: string | null;
user: { user: {
relationTo: 'users'; relationTo: 'users';
value: string | User; value: number | User;
}; };
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@ -184,10 +280,10 @@ export interface PayloadLockedDocument {
* via the `definition` "payload-preferences". * via the `definition` "payload-preferences".
*/ */
export interface PayloadPreference { export interface PayloadPreference {
id: string; id: number;
user: { user: {
relationTo: 'users'; relationTo: 'users';
value: string | User; value: number | User;
}; };
key?: string | null; key?: string | null;
value?: value?:
@ -207,7 +303,7 @@ export interface PayloadPreference {
* via the `definition` "payload-migrations". * via the `definition` "payload-migrations".
*/ */
export interface PayloadMigration { export interface PayloadMigration {
id: string; id: number;
name?: string | null; name?: string | null;
batch?: number | null; batch?: number | null;
updatedAt: string; updatedAt: string;
@ -218,9 +314,13 @@ export interface PayloadMigration {
* via the `definition` "users_select". * via the `definition` "users_select".
*/ */
export interface UsersSelect<T extends boolean = true> { export interface UsersSelect<T extends boolean = true> {
discordUsername?: T;
displayName?: T;
roles?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
email?: T; email?: T;
username?: T;
resetPasswordToken?: T; resetPasswordToken?: T;
resetPasswordExpiration?: T; resetPasswordExpiration?: T;
salt?: T; salt?: T;
@ -253,6 +353,55 @@ export interface MediaSelect<T extends boolean = true> {
focalX?: T; focalX?: T;
focalY?: T; focalY?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "ranks_select".
*/
export interface RanksSelect<T extends boolean = true> {
_order?: T;
name?: T;
abbreviation?: T;
description?: T;
insignia?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "profiles_select".
*/
export interface ProfilesSelect<T extends boolean = true> {
user?: T;
rank?: T;
profileTitle?: T;
dossier?:
| T
| {
personalExcerpt?: T;
intelExcerpt?: T;
};
progression?:
| T
| {
xp?: T;
awards?: T;
};
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "awards_select".
*/
export interface AwardsSelect<T extends boolean = true> {
name?: T;
description?: T;
type?: T;
image?: T;
experiencePoints?: T;
updatedAt?: T;
createdAt?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select". * via the `definition` "payload-locked-documents_select".

View file

@ -1,17 +1,20 @@
// storage-adapter-import-placeholder // storage-adapter-import-placeholder
import { postgresAdapter } from '@payloadcms/db-postgres' import { postgresAdapter } from "@payloadcms/db-postgres";
import { payloadCloudPlugin } from '@payloadcms/payload-cloud' import { payloadCloudPlugin } from "@payloadcms/payload-cloud";
import { lexicalEditor } from '@payloadcms/richtext-lexical' import { lexicalEditor } from "@payloadcms/richtext-lexical";
import path from 'path' import path from "path";
import { buildConfig } from 'payload' import { buildConfig } from "payload";
import { fileURLToPath } from 'url' import { fileURLToPath } from "url";
import sharp from 'sharp' import sharp from "sharp";
import { Users } from './collections/Users' import { Users } from "@/collections/users/Users";
import { Media } from './collections/Media' import { Media } from "@/collections/Media";
import { Profiles } from "@/collections/users/Profiles";
import { Ranks } from "@/collections/users/Ranks";
import { Awards } from "@/collections/users/Awards";
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename) const dirname = path.dirname(filename);
export default buildConfig({ export default buildConfig({
admin: { admin: {
@ -20,15 +23,15 @@ export default buildConfig({
baseDir: path.resolve(dirname), baseDir: path.resolve(dirname),
}, },
}, },
collections: [Users, Media], collections: [Users, Media, Ranks, Profiles, Awards],
editor: lexicalEditor(), editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || '', secret: process.env.PAYLOAD_SECRET || "",
typescript: { typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'), outputFile: path.resolve(dirname, "payload-types.ts"),
}, },
db: postgresAdapter({ db: postgresAdapter({
pool: { pool: {
connectionString: process.env.DATABASE_URI || '', connectionString: process.env.DATABASE_URI || "",
}, },
}), }),
sharp, sharp,
@ -36,4 +39,4 @@ export default buildConfig({
payloadCloudPlugin(), payloadCloudPlugin(),
// storage-adapter-placeholder // storage-adapter-placeholder
], ],
}) });

122
src/tools/seed/seedRanks.ts Normal file
View file

@ -0,0 +1,122 @@
import { getPayload } from "payload";
import config from "@payload-config";
const DEFAULT_USMC_RANKS = [
{
name: "Recruit",
abbreviation: "Rct",
},
{
name: "Private",
abbreviation: "Pvt",
},
{
name: "Private First Class",
abbreviation: "PFC",
},
{
name: "Lance Corporal",
abbreviation: "LCpl",
},
{
name: "Corporal",
abbreviation: "Cpl",
},
{
name: "Sergeant",
abbreviation: "Sgt",
},
{
name: "Staff Sergeant",
abbreviation: "SSgt",
},
{
name: "Gunnery Sergeant",
abbreviation: "GySgt",
},
{
name: "Master Sergeant",
abbreviation: "MSgt",
},
{
name: "First Sergeant",
abbreviation: "1stSgt",
},
{
name: "Sergeant Major",
abbreviation: "SgtMaj",
},
{
name: "Master Gunnery Sergeant",
abbreviation: "MGySgt",
},
{
name: "Second Lieutenant",
abbreviation: "2ndLt",
},
{
name: "First Lieutenant",
abbreviation: "1stLt",
},
{
name: "Captain",
abbreviation: "Cpt",
},
{
name: "Major",
abbreviation: "Maj",
},
{
name: "Lieutenant Colonel",
abbreviation: "LtCol",
},
{
name: "Colonel",
abbreviation: "Col",
},
{
name: "Brigadier General",
abbreviation: "BGen",
},
{
name: "Major General",
abbreviation: "MajGen",
},
{
name: "Lieutenant General",
abbreviation: "LtGen",
},
{
name: "General",
abbreviation: "Gen",
},
];
export const seedRanks = async () => {
const payload = await getPayload({ config });
payload.logger.info("Seeding rank data...");
payload.logger.warn("Deleting all existing ranks...");
await payload.delete({
collection: "ranks",
where: {
name: { exists: true },
},
});
payload.logger.info(`Creating ${DEFAULT_USMC_RANKS.length} new ranks...`);
for (const rank of DEFAULT_USMC_RANKS) {
const result = await payload.create({
collection: "ranks",
data: {
...rank,
description: "A rank that needs a description. ",
},
});
payload.logger.info(`Rank '${result.name}' created.`);
}
};
await seedRanks();

View file

@ -0,0 +1,7 @@
import { User } from "@/payload-types";
export default function hasRoles(roles: NonNullable<User['roles']>, user: User | null | undefined) {
if (!user) return false;
return roles.some((role) => user.roles?.some((urole) => urole === role || urole === "developer"));
}

View file

@ -0,0 +1,18 @@
import hasRoles from "@/utils/access-control/hasRoles";
import { PayloadRequest } from "payload";
export function isDeveloper({ req: { user } }: { req: PayloadRequest }) {
return hasRoles(["developer"], user);
}
export function isGuest({ req: { user } }: { req: PayloadRequest }) {
return hasRoles(["guest"], user);
}
export function isUser({ req: { user } }: { req: PayloadRequest }) {
return hasRoles(["user"], user);
}
export function isAdmin({ req: { user } }: { req: PayloadRequest }) {
return hasRoles(["admin"], user);
}

View file

@ -1,20 +1,20 @@
import { test, expect, Page } from '@playwright/test' import { expect, Page, test } from "@playwright/test";
test.describe('Frontend', () => { test.describe("Frontend", () => {
let page: Page let page: Page;
test.beforeAll(async ({ browser }, testInfo) => { test.beforeAll(async ({ browser }, testInfo) => {
const context = await browser.newContext() const context = await browser.newContext();
page = await context.newPage() page = await context.newPage();
}) });
test('can go on homepage', async ({ page }) => { test("can go on homepage", async ({ page }) => {
await page.goto('http://localhost:3000') await page.goto("http://localhost:3000");
await expect(page).toHaveTitle(/Payload Blank Template/) await expect(page).toHaveTitle(/Payload Blank Template/);
const heading = page.locator('h1').first() const heading = page.locator("h1").first();
await expect(heading).toHaveText('Welcome to your new project.') await expect(heading).toHaveText("Welcome to your new project.");
}) });
}) });

View file

@ -1,20 +1,20 @@
import { getPayload, Payload } from 'payload' import { getPayload, Payload } from "payload";
import config from '@/payload.config' import config from "@/payload.config";
import { describe, it, beforeAll, expect } from 'vitest' import { beforeAll, describe, expect, it } from "vitest";
let payload: Payload let payload: Payload;
describe('API', () => { describe("API", () => {
beforeAll(async () => { beforeAll(async () => {
const payloadConfig = await config const payloadConfig = await config;
payload = await getPayload({ config: payloadConfig }) payload = await getPayload({ config: payloadConfig });
}) });
it('fetches users', async () => { it("fetches users", async () => {
const users = await payload.find({ const users = await payload.find({
collection: 'users', collection: "users",
}) });
expect(users).toBeDefined() expect(users).toBeDefined();
}) });
}) });

View file

@ -30,7 +30,7 @@
"./src/payload.config.ts" "./src/payload.config.ts"
] ]
}, },
"target": "ES2022", "target": "ES2022"
}, },
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",
@ -40,5 +40,5 @@
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"
], ]
} }

View file

@ -1,12 +1,12 @@
import { defineConfig } from 'vitest/config' import { defineConfig } from "vitest/config";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
import tsconfigPaths from 'vite-tsconfig-paths' import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ export default defineConfig({
plugins: [tsconfigPaths(), react()], plugins: [tsconfigPaths(), react()],
test: { test: {
environment: 'jsdom', environment: "jsdom",
setupFiles: ['./vitest.setup.ts'], setupFiles: ["./vitest.setup.ts"],
include: ['tests/int/**/*.int.spec.ts'], include: ["tests/int/**/*.int.spec.ts"],
}, },
}) });

View file

@ -1,4 +1,4 @@
// Any setup scripts you might need go here // Any setup scripts you might need go here
// Load .env files // Load .env files
import 'dotenv/config' import "dotenv/config";