diff --git a/.prettierrc.json b/.prettierrc.json index cb8ee26..23ba391 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,6 +1,6 @@ { - "singleQuote": true, + "singleQuote": false, "trailingComma": "all", "printWidth": 100, - "semi": false + "semi": true } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 1d7ac85..7efca3f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,6 @@ { - "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 572ee15..0edf511 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,8 +9,12 @@ "type": "node", "request": "launch", "program": "${workspaceFolder}/node_modules/next/dist/bin/next", - "runtimeArgs": ["--inspect"], - "skipFiles": ["/**"], + "runtimeArgs": [ + "--inspect" + ], + "skipFiles": [ + "/**" + ], "serverReadyAction": { "action": "debugWithChrome", "killOnServerStop": true, diff --git a/README.md b/README.md index 4a2dde6..56b4c64 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ This template comes configured with the bare minimum to get started on anything ## 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 @@ -12,21 +13,27 @@ To spin up this template locally, follow these steps: ### 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 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 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) -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: @@ -40,28 +47,35 @@ The Payload config is tailored specifically to the needs of most websites. It is ### 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 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 - 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 -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. 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 -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). diff --git a/eslint.config.mjs b/eslint.config.mjs index 7acd77d..099a8d7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,38 +1,38 @@ -import { dirname } from 'path' -import { fileURLToPath } from 'url' -import { FlatCompat } from '@eslint/eslintrc' +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); const compat = new FlatCompat({ baseDirectory: __dirname, -}) +}); const eslintConfig = [ - ...compat.extends('next/core-web-vitals', 'next/typescript'), + ...compat.extends("next/core-web-vitals", "next/typescript"), { rules: { - '@typescript-eslint/ban-ts-comment': 'warn', - '@typescript-eslint/no-empty-object-type': 'warn', - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': [ - 'warn', + "@typescript-eslint/ban-ts-comment": "warn", + "@typescript-eslint/no-empty-object-type": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": [ + "warn", { - vars: 'all', - args: 'after-used', + vars: "all", + args: "after-used", ignoreRestSiblings: false, - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - destructuredArrayIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^(_|ignore)', + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^(_|ignore)", }, ], }, }, { - ignores: ['.next/'], + ignores: [".next/"], }, -] +]; -export default eslintConfig +export default eslintConfig; diff --git a/next.config.mjs b/next.config.mjs index 8e22d20..a1502a6 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,17 +1,17 @@ -import { withPayload } from '@payloadcms/next/withPayload' +import { withPayload } from "@payloadcms/next/withPayload"; /** @type {import('next').NextConfig} */ const nextConfig = { // Your Next.js config here webpack: (webpackConfig) => { webpackConfig.resolve.extensionAlias = { - '.cjs': ['.cts', '.cjs'], - '.js': ['.ts', '.tsx', '.js', '.jsx'], - '.mjs': ['.mts', '.mjs'], - } + ".cjs": [".cts", ".cjs"], + ".js": [".ts", ".tsx", ".js", ".jsx"], + ".mjs": [".mts", ".mjs"], + }; - return webpackConfig + return webpackConfig; }, -} +}; -export default withPayload(nextConfig, { devBundleServerPackages: false }) +export default withPayload(nextConfig, { devBundleServerPackages: false }); diff --git a/playwright.config.ts b/playwright.config.ts index 747395f..7a5a8b3 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,16 +1,16 @@ -import { defineConfig, devices } from '@playwright/test' +import { defineConfig, devices } from "@playwright/test"; /** * Read environment variables from file. * https://github.com/motdotla/dotenv */ -import 'dotenv/config' +import "dotenv/config"; /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './tests/e2e', + testDir: "./tests/e2e", /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ @@ -18,24 +18,24 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* 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. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: "on-first-retry", }, projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, ], webServer: { - command: 'pnpm dev', + command: "pnpm dev", reuseExistingServer: true, - url: 'http://localhost:3000', + url: "http://localhost:3000", }, -}) +}); diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index e7681f7..6e56b43 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -1,13 +1,13 @@ -import React from 'react' -import './styles.css' +import React from "react"; +import "./styles.css"; export const metadata = { - description: 'A blank template using Payload in a Next.js app.', - title: 'Payload Blank Template', -} + description: "A blank template using Payload in a Next.js app.", + title: "Payload Blank Template", +}; export default async function RootLayout(props: { children: React.ReactNode }) { - const { children } = props + const { children } = props; return ( @@ -15,5 +15,5 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
{children}
- ) + ); } diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index d0e8d64..f97270a 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -1,19 +1,19 @@ -import { headers as getHeaders } from 'next/headers.js' -import Image from 'next/image' -import { getPayload } from 'payload' -import React from 'react' -import { fileURLToPath } from 'url' +import { headers as getHeaders } from "next/headers.js"; +import Image from "next/image"; +import { getPayload } from "payload"; +import React from "react"; +import { fileURLToPath } from "url"; -import config from '@/payload.config' -import './styles.css' +import config from "@/payload.config"; +import "./styles.css"; export default async function HomePage() { - const headers = await getHeaders() - const payloadConfig = await config - const payload = await getPayload({ config: payloadConfig }) - const { user } = await payload.auth({ headers }) + const headers = await getHeaders(); + const payloadConfig = await config; + const payload = await getPayload({ config: payloadConfig }); + const { user } = await payload.auth({ headers }); - const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}` + const fileURL = `webstorm://file/${fileURLToPath(import.meta.url)}`; return (
@@ -27,8 +27,10 @@ export default async function HomePage() { width={65} /> - {!user &&

Welcome to your new project.

} - {user &&

Welcome back, {user.email}

} +

Polaris Task Force

+ {/*{!user &&

Welcome to your new project.

} + {user &&

Welcome back, {user.email}

}*/} +

This site is under construction.

-
+ {/*

Update this page by editing

app/(frontend)/page.tsx -
+
*/} - ) + ); } diff --git a/src/app/(frontend)/styles.css b/src/app/(frontend)/styles.css index d1fb941..d95853e 100644 --- a/src/app/(frontend)/styles.css +++ b/src/app/(frontend)/styles.css @@ -1,164 +1,164 @@ :root { - --font-mono: 'Roboto Mono', monospace; + --font-mono: 'Roboto Mono', monospace; } * { - box-sizing: border-box; + box-sizing: border-box; } html { - font-size: 18px; - line-height: 32px; + font-size: 18px; + line-height: 32px; - background: rgb(0, 0, 0); - -webkit-font-smoothing: antialiased; + background: rgb(0, 0, 0); + -webkit-font-smoothing: antialiased; } html, body, #app { - height: 100%; + height: 100%; } body { - font-family: system-ui; - font-size: 18px; - line-height: 32px; + font-family: system-ui; + font-size: 18px; + line-height: 32px; - margin: 0; - color: rgb(1000, 1000, 1000); + margin: 0; + color: rgb(1000, 1000, 1000); - @media (max-width: 1024px) { - font-size: 15px; - line-height: 24px; - } + @media (max-width: 1024px) { + font-size: 15px; + line-height: 24px; + } } img { - max-width: 100%; - height: auto; - display: block; + max-width: 100%; + height: auto; + display: block; } h1 { - margin: 40px 0; - font-size: 64px; - line-height: 70px; - font-weight: bold; + margin: 40px 0; + font-size: 64px; + line-height: 70px; + font-weight: bold; - @media (max-width: 1024px) { - margin: 24px 0; - font-size: 42px; - line-height: 42px; - } + @media (max-width: 1024px) { + margin: 24px 0; + font-size: 42px; + line-height: 42px; + } - @media (max-width: 768px) { - font-size: 38px; - line-height: 38px; - } + @media (max-width: 768px) { + font-size: 38px; + line-height: 38px; + } - @media (max-width: 400px) { - font-size: 32px; - line-height: 32px; - } + @media (max-width: 400px) { + font-size: 32px; + line-height: 32px; + } } p { - margin: 24px 0; + margin: 24px 0; - @media (max-width: 1024px) { - margin: calc(var(--base) * 0.75) 0; - } + @media (max-width: 1024px) { + margin: calc(var(--base) * 0.75) 0; + } } a { - color: currentColor; + color: currentColor; - &:focus { - opacity: 0.8; - outline: none; - } + &:focus { + opacity: 0.8; + outline: none; + } - &:active { - opacity: 0.7; - outline: none; - } + &:active { + opacity: 0.7; + outline: none; + } } svg { - vertical-align: middle; + vertical-align: middle; } .home { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - height: 100vh; - padding: 45px; - max-width: 1024px; - margin: 0 auto; - overflow: hidden; - - @media (max-width: 400px) { - padding: 24px; - } - - .content { display: flex; flex-direction: column; + justify-content: space-between; align-items: center; - justify-content: center; - flex-grow: 1; + height: 100vh; + padding: 45px; + max-width: 1024px; + margin: 0 auto; + overflow: hidden; - h1 { - text-align: center; - } - } - - .links { - display: flex; - align-items: center; - gap: 12px; - - a { - text-decoration: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; + @media (max-width: 400px) { + padding: 24px; } - .admin { - color: rgb(0, 0, 0); - background: rgb(1000, 1000, 1000); - border: 1px solid rgb(0, 0, 0); + .content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex-grow: 1; + + h1 { + text-align: center; + } } - .docs { - color: rgb(1000, 1000, 1000); - background: rgb(0, 0, 0); - border: 1px solid rgb(1000, 1000, 1000); - } - } + .links { + display: flex; + align-items: center; + gap: 12px; - .footer { - display: flex; - align-items: center; - gap: 8px; + a { + text-decoration: none; + padding: 0.25rem 0.5rem; + border-radius: 4px; + } - @media (max-width: 1024px) { - flex-direction: column; - gap: 6px; + .admin { + color: rgb(0, 0, 0); + background: rgb(1000, 1000, 1000); + border: 1px solid rgb(0, 0, 0); + } + + .docs { + color: rgb(1000, 1000, 1000); + background: rgb(0, 0, 0); + border: 1px solid rgb(1000, 1000, 1000); + } } - p { - margin: 0; - } + .footer { + display: flex; + align-items: center; + gap: 8px; - .codeLink { - text-decoration: none; - padding: 0 0.5rem; - background: rgb(60, 60, 60); - border-radius: 4px; + @media (max-width: 1024px) { + flex-direction: column; + gap: 6px; + } + + p { + margin: 0; + } + + .codeLink { + text-decoration: none; + padding: 0 0.5rem; + background: rgb(60, 60, 60); + border-radius: 4px; + } } - } } diff --git a/src/app/(payload)/admin/[[...segments]]/not-found.tsx b/src/app/(payload)/admin/[[...segments]]/not-found.tsx index 6410836..f5e4078 100644 --- a/src/app/(payload)/admin/[[...segments]]/not-found.tsx +++ b/src/app/(payload)/admin/[[...segments]]/not-found.tsx @@ -1,24 +1,24 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* 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 { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views' -import { importMap } from '../importMap' +import config from "@payload-config"; +import { generatePageMetadata, NotFoundPage } from "@payloadcms/next/views"; +import { importMap } from "../importMap"; type Args = { params: Promise<{ - segments: string[] - }> + segments: string[]; + }>; searchParams: Promise<{ - [key: string]: string | string[] - }> -} + [key: string]: string | string[]; + }>; +}; export const generateMetadata = ({ params, searchParams }: Args): Promise => - generatePageMetadata({ config, params, searchParams }) + generatePageMetadata({ config, params, searchParams }); const NotFound = ({ params, searchParams }: Args) => - NotFoundPage({ config, params, searchParams, importMap }) + NotFoundPage({ config, params, searchParams, importMap }); -export default NotFound +export default NotFound; diff --git a/src/app/(payload)/admin/[[...segments]]/page.tsx b/src/app/(payload)/admin/[[...segments]]/page.tsx index 0de685c..00c8aa6 100644 --- a/src/app/(payload)/admin/[[...segments]]/page.tsx +++ b/src/app/(payload)/admin/[[...segments]]/page.tsx @@ -1,24 +1,24 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* 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 { RootPage, generatePageMetadata } from '@payloadcms/next/views' -import { importMap } from '../importMap' +import config from "@payload-config"; +import { generatePageMetadata, RootPage } from "@payloadcms/next/views"; +import { importMap } from "../importMap"; type Args = { params: Promise<{ - segments: string[] - }> + segments: string[]; + }>; searchParams: Promise<{ - [key: string]: string | string[] - }> -} + [key: string]: string | string[]; + }>; +}; export const generateMetadata = ({ params, searchParams }: Args): Promise => - generatePageMetadata({ config, params, searchParams }) + generatePageMetadata({ config, params, searchParams }); const Page = ({ params, searchParams }: Args) => - RootPage({ config, params, searchParams, importMap }) + RootPage({ config, params, searchParams, importMap }); -export default Page +export default Page; diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index 8ef7021..5bc8ec3 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -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 +} diff --git a/src/app/(payload)/api/[...slug]/route.ts b/src/app/(payload)/api/[...slug]/route.ts index e58c50f..64aa3a0 100644 --- a/src/app/(payload)/api/[...slug]/route.ts +++ b/src/app/(payload)/api/[...slug]/route.ts @@ -1,19 +1,12 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ -import config from '@payload-config' -import '@payloadcms/next/css' -import { - REST_DELETE, - REST_GET, - REST_OPTIONS, - REST_PATCH, - REST_POST, - REST_PUT, -} from '@payloadcms/next/routes' +import config from "@payload-config"; +import "@payloadcms/next/css"; +import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST, REST_PUT, } from "@payloadcms/next/routes"; -export const GET = REST_GET(config) -export const POST = REST_POST(config) -export const DELETE = REST_DELETE(config) -export const PATCH = REST_PATCH(config) -export const PUT = REST_PUT(config) -export const OPTIONS = REST_OPTIONS(config) +export const GET = REST_GET(config); +export const POST = REST_POST(config); +export const DELETE = REST_DELETE(config); +export const PATCH = REST_PATCH(config); +export const PUT = REST_PUT(config); +export const OPTIONS = REST_OPTIONS(config); diff --git a/src/app/(payload)/api/graphql-playground/route.ts b/src/app/(payload)/api/graphql-playground/route.ts index 17d2954..8990e21 100644 --- a/src/app/(payload)/api/graphql-playground/route.ts +++ b/src/app/(payload)/api/graphql-playground/route.ts @@ -4,4 +4,4 @@ import config from '@payload-config' import '@payloadcms/next/css' import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes' -export const GET = GRAPHQL_PLAYGROUND_GET(config) +export const GET = GRAPHQL_PLAYGROUND_GET(config); diff --git a/src/app/(payload)/api/graphql/route.ts b/src/app/(payload)/api/graphql/route.ts index 2069ff8..4534269 100644 --- a/src/app/(payload)/api/graphql/route.ts +++ b/src/app/(payload)/api/graphql/route.ts @@ -3,6 +3,6 @@ import config from '@payload-config' 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); diff --git a/src/app/(payload)/layout.tsx b/src/app/(payload)/layout.tsx index 8df141a..3c6b6e4 100644 --- a/src/app/(payload)/layout.tsx +++ b/src/app/(payload)/layout.tsx @@ -1,31 +1,31 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ -import config from '@payload-config' -import '@payloadcms/next/css' -import type { ServerFunctionClient } from 'payload' -import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts' -import React from 'react' +import config from "@payload-config"; +import "@payloadcms/next/css"; +import type { ServerFunctionClient } from "payload"; +import { handleServerFunctions, RootLayout } from "@payloadcms/next/layouts"; +import React from "react"; -import { importMap } from './admin/importMap.js' -import './custom.scss' +import { importMap } from "./admin/importMap.js"; +import "./custom.scss"; type Args = { - children: React.ReactNode -} + children: React.ReactNode; +}; const serverFunction: ServerFunctionClient = async function (args) { - 'use server' + "use server"; return handleServerFunctions({ ...args, config, importMap, - }) -} + }); +}; const Layout = ({ children }: Args) => ( {children} -) +); -export default Layout +export default Layout; diff --git a/src/app/my-route/route.ts b/src/app/my-route/route.ts index 0755886..0231326 100644 --- a/src/app/my-route/route.ts +++ b/src/app/my-route/route.ts @@ -1,12 +1,12 @@ -import configPromise from '@payload-config' -import { getPayload } from 'payload' +import configPromise from "@payload-config"; +import { getPayload } from "payload"; export const GET = async (request: Request) => { const payload = await getPayload({ config: configPromise, - }) + }); return Response.json({ - message: 'This is an example of a custom route.', - }) -} + message: "This is an example of a custom route.", + }); +}; diff --git a/src/collections/Media.ts b/src/collections/Media.ts index 568cf42..cdfafb7 100644 --- a/src/collections/Media.ts +++ b/src/collections/Media.ts @@ -1,16 +1,16 @@ -import type { CollectionConfig } from 'payload' +import type { CollectionConfig } from "payload"; export const Media: CollectionConfig = { - slug: 'media', + slug: "media", access: { read: () => true, }, fields: [ { - name: 'alt', - type: 'text', + name: "alt", + type: "text", required: true, }, ], upload: true, -} +}; diff --git a/src/collections/Users.ts b/src/collections/Users.ts deleted file mode 100644 index c683d0e..0000000 --- a/src/collections/Users.ts +++ /dev/null @@ -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 - ], -} diff --git a/src/collections/users/Awards.ts b/src/collections/users/Awards.ts new file mode 100644 index 0000000..79516ea --- /dev/null +++ b/src/collections/users/Awards.ts @@ -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, + }, + ], +}; diff --git a/src/collections/users/Profiles.ts b/src/collections/users/Profiles.ts new file mode 100644 index 0000000..c36d622 --- /dev/null +++ b/src/collections/users/Profiles.ts @@ -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", + }, + }, + ], + }, + ], +}; diff --git a/src/collections/users/Ranks.ts b/src/collections/users/Ranks.ts new file mode 100644 index 0000000..2422b87 --- /dev/null +++ b/src/collections/users/Ranks.ts @@ -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", + }, + ], +}; diff --git a/src/collections/users/Users.ts b/src/collections/users/Users.ts new file mode 100644 index 0000000..a3f7bbf --- /dev/null +++ b/src/collections/users/Users.ts @@ -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", + }, + ], + }, + ], +}; diff --git a/src/payload-types.ts b/src/payload-types.ts index a9606af..9f8e92c 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -69,6 +69,9 @@ export interface Config { collections: { users: User; media: Media; + ranks: Rank; + profiles: Profile; + awards: Award; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; @@ -77,12 +80,15 @@ export interface Config { collectionsSelect: { users: UsersSelect | UsersSelect; media: MediaSelect | MediaSelect; + ranks: RanksSelect | RanksSelect; + profiles: ProfilesSelect | ProfilesSelect; + awards: AwardsSelect | AwardsSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; }; db: { - defaultIDType: string; + defaultIDType: number; }; globals: {}; globalsSelect: {}; @@ -97,20 +103,18 @@ export interface Config { } export interface UserAuthOperations { forgotPassword: { - email: string; - password: string; + username: string; }; login: { - email: string; password: string; + username: string; }; registerFirstUser: { - email: string; password: string; + username: string; }; unlock: { - email: string; - password: string; + username: string; }; } /** @@ -118,10 +122,14 @@ export interface UserAuthOperations { * via the `definition` "users". */ export interface User { - id: string; + id: number; + discordUsername: string; + displayName: string; + roles?: ('guest' | 'user' | 'admin' | 'developer')[] | null; updatedAt: string; createdAt: string; - email: string; + email?: string | null; + username: string; resetPasswordToken?: string | null; resetPasswordExpiration?: string | null; salt?: string | null; @@ -142,7 +150,7 @@ export interface User { * via the `definition` "media". */ export interface Media { - id: string; + id: number; alt: string; updatedAt: string; createdAt: string; @@ -156,25 +164,113 @@ export interface Media { focalX?: 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 * via the `definition` "payload-locked-documents". */ export interface PayloadLockedDocument { - id: string; + id: number; document?: | ({ relationTo: 'users'; - value: string | User; + value: number | User; } | null) | ({ 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); globalSlug?: string | null; user: { relationTo: 'users'; - value: string | User; + value: number | User; }; updatedAt: string; createdAt: string; @@ -184,10 +280,10 @@ export interface PayloadLockedDocument { * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: string; + id: number; user: { relationTo: 'users'; - value: string | User; + value: number | User; }; key?: string | null; value?: @@ -207,7 +303,7 @@ export interface PayloadPreference { * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: string; + id: number; name?: string | null; batch?: number | null; updatedAt: string; @@ -218,9 +314,13 @@ export interface PayloadMigration { * via the `definition` "users_select". */ export interface UsersSelect { + discordUsername?: T; + displayName?: T; + roles?: T; updatedAt?: T; createdAt?: T; email?: T; + username?: T; resetPasswordToken?: T; resetPasswordExpiration?: T; salt?: T; @@ -253,6 +353,55 @@ export interface MediaSelect { focalX?: T; focalY?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ranks_select". + */ +export interface RanksSelect { + _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 { + 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 { + name?: T; + description?: T; + type?: T; + image?: T; + experiencePoints?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents_select". diff --git a/src/payload.config.ts b/src/payload.config.ts index 5f2c20c..bec4524 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -1,17 +1,20 @@ // storage-adapter-import-placeholder -import { postgresAdapter } from '@payloadcms/db-postgres' -import { payloadCloudPlugin } from '@payloadcms/payload-cloud' -import { lexicalEditor } from '@payloadcms/richtext-lexical' -import path from 'path' -import { buildConfig } from 'payload' -import { fileURLToPath } from 'url' -import sharp from 'sharp' +import { postgresAdapter } from "@payloadcms/db-postgres"; +import { payloadCloudPlugin } from "@payloadcms/payload-cloud"; +import { lexicalEditor } from "@payloadcms/richtext-lexical"; +import path from "path"; +import { buildConfig } from "payload"; +import { fileURLToPath } from "url"; +import sharp from "sharp"; -import { Users } from './collections/Users' -import { Media } from './collections/Media' +import { Users } from "@/collections/users/Users"; +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 dirname = path.dirname(filename) +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); export default buildConfig({ admin: { @@ -20,15 +23,15 @@ export default buildConfig({ baseDir: path.resolve(dirname), }, }, - collections: [Users, Media], + collections: [Users, Media, Ranks, Profiles, Awards], editor: lexicalEditor(), - secret: process.env.PAYLOAD_SECRET || '', + secret: process.env.PAYLOAD_SECRET || "", typescript: { - outputFile: path.resolve(dirname, 'payload-types.ts'), + outputFile: path.resolve(dirname, "payload-types.ts"), }, db: postgresAdapter({ pool: { - connectionString: process.env.DATABASE_URI || '', + connectionString: process.env.DATABASE_URI || "", }, }), sharp, @@ -36,4 +39,4 @@ export default buildConfig({ payloadCloudPlugin(), // storage-adapter-placeholder ], -}) +}); diff --git a/src/tools/seed/seedRanks.ts b/src/tools/seed/seedRanks.ts new file mode 100644 index 0000000..db9740a --- /dev/null +++ b/src/tools/seed/seedRanks.ts @@ -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(); diff --git a/src/utils/access-control/hasRoles.ts b/src/utils/access-control/hasRoles.ts new file mode 100644 index 0000000..c9dfd25 --- /dev/null +++ b/src/utils/access-control/hasRoles.ts @@ -0,0 +1,7 @@ +import { User } from "@/payload-types"; + +export default function hasRoles(roles: NonNullable, user: User | null | undefined) { + if (!user) return false; + + return roles.some((role) => user.roles?.some((urole) => urole === role || urole === "developer")); +} diff --git a/src/utils/access-control/isRole.ts b/src/utils/access-control/isRole.ts new file mode 100644 index 0000000..9a3cee0 --- /dev/null +++ b/src/utils/access-control/isRole.ts @@ -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); +} diff --git a/tests/e2e/frontend.e2e.spec.ts b/tests/e2e/frontend.e2e.spec.ts index 65c31fd..dfb3e99 100644 --- a/tests/e2e/frontend.e2e.spec.ts +++ b/tests/e2e/frontend.e2e.spec.ts @@ -1,20 +1,20 @@ -import { test, expect, Page } from '@playwright/test' +import { expect, Page, test } from "@playwright/test"; -test.describe('Frontend', () => { - let page: Page +test.describe("Frontend", () => { + let page: Page; test.beforeAll(async ({ browser }, testInfo) => { - const context = await browser.newContext() - page = await context.newPage() - }) + const context = await browser.newContext(); + page = await context.newPage(); + }); - test('can go on homepage', async ({ page }) => { - await page.goto('http://localhost:3000') + test("can go on homepage", async ({ page }) => { + 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."); + }); +}); diff --git a/tests/int/api.int.spec.ts b/tests/int/api.int.spec.ts index 9bd5adb..da53d62 100644 --- a/tests/int/api.int.spec.ts +++ b/tests/int/api.int.spec.ts @@ -1,20 +1,20 @@ -import { getPayload, Payload } from 'payload' -import config from '@/payload.config' +import { getPayload, Payload } from "payload"; +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 () => { - const payloadConfig = await config - payload = await getPayload({ config: payloadConfig }) - }) + const payloadConfig = await config; + payload = await getPayload({ config: payloadConfig }); + }); - it('fetches users', async () => { + it("fetches users", async () => { const users = await payload.find({ - collection: 'users', - }) - expect(users).toBeDefined() - }) -}) + collection: "users", + }); + expect(users).toBeDefined(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 477c25a..7a477a3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,7 @@ "./src/payload.config.ts" ] }, - "target": "ES2022", + "target": "ES2022" }, "include": [ "next-env.d.ts", @@ -40,5 +40,5 @@ ], "exclude": [ "node_modules" - ], + ] } diff --git a/vitest.config.mts b/vitest.config.mts index f4d8caa..e4f454b 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,12 +1,12 @@ -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react' -import tsconfigPaths from 'vite-tsconfig-paths' +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; +import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { - environment: 'jsdom', - setupFiles: ['./vitest.setup.ts'], - include: ['tests/int/**/*.int.spec.ts'], + environment: "jsdom", + setupFiles: ["./vitest.setup.ts"], + include: ["tests/int/**/*.int.spec.ts"], }, -}) +}); diff --git a/vitest.setup.ts b/vitest.setup.ts index 1a76c85..95514a3 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,4 +1,4 @@ // Any setup scripts you might need go here // Load .env files -import 'dotenv/config' +import "dotenv/config";