├── .env.example
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── README.md
├── next.config.mjs
├── package.json
├── pnpm-lock.yaml
├── postcss.config.cjs
├── public
├── arrow.svg
├── blur.png
├── crosshair.svg
├── gradient.webp
├── next.svg
├── payload.svg
└── scanline-light.png
├── src
├── app
│ ├── (app)
│ │ ├── globals.scss
│ │ ├── icon.svg
│ │ ├── layout.tsx
│ │ └── page.tsx
│ └── (payload)
│ │ ├── admin
│ │ └── [[...segments]]
│ │ │ ├── not-found.tsx
│ │ │ └── page.tsx
│ │ ├── api
│ │ ├── [...slug]
│ │ │ └── route.ts
│ │ ├── graphql-playground
│ │ │ └── route.ts
│ │ └── graphql
│ │ │ └── route.ts
│ │ ├── custom.scss
│ │ └── layout.tsx
├── collections
│ ├── Media.ts
│ └── Users.ts
├── components
│ ├── Background
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── Links
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ └── Logos
│ │ ├── index.tsx
│ │ └── styles.module.scss
├── migrations
│ ├── 20240709_153941_initial.json
│ └── 20240709_153941_initial.ts
├── payload-types.ts
└── payload.config.ts
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | POSTGRES_URL=postgres://127.0.0.1:5432/payload
2 | PAYLOAD_SECRET=
3 | BLOB_READ_WRITE_TOKEN=vercel_blob_rw_examplestoreid_somethingelse
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | .env
4 |
5 | # dependencies
6 | /node_modules
7 | /.pnp
8 | .pnp.js
9 | .yarn/install-state.gz
10 |
11 | # testing
12 | /coverage
13 |
14 | # next.js
15 | /.next/
16 | /out/
17 |
18 | # production
19 | /build
20 |
21 | # misc
22 | .DS_Store
23 | *.pem
24 |
25 | # debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
30 | # local env files
31 | .env*.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
40 | /.idea/*
41 | !/.idea/runConfigurations
42 | !/.idea/payload.iml
43 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 100,
5 | "semi": false
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repo has been deprecated in favor this the [with-vercel-postgres](https://github.com/payloadcms/payload/tree/beta/templates/with-vercel-postgres) template. Use the deploy button located there.
2 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | import { withPayload } from '@payloadcms/next/withPayload'
2 | /** @type {import('next').NextConfig} */
3 | const nextConfig = {};
4 |
5 | export default withPayload(nextConfig);
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vercel-deploy-payload-postgres",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint",
11 | "ci": "payload migrate && pnpm build",
12 | "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
13 | "generate:types": "payload generate:types"
14 | },
15 | "dependencies": {
16 | "@payloadcms/db-postgres": "3.0.0-beta.58",
17 | "@payloadcms/next": "3.0.0-beta.58",
18 | "@payloadcms/plugin-cloud-storage": "3.0.0-beta.58",
19 | "@payloadcms/richtext-lexical": "3.0.0-beta.58",
20 | "@payloadcms/storage-vercel-blob": "3.0.0-beta.58",
21 | "@vercel/blob": "^0.22.3",
22 | "cross-env": "^7.0.3",
23 | "next": "15.0.0-rc.0",
24 | "payload": "3.0.0-beta.58",
25 | "react": "^19.0.0-rc-f994737d14-20240522",
26 | "react-dom": "^19.0.0-rc-f994737d14-20240522",
27 | "sharp": "0.32.6"
28 | },
29 | "devDependencies": {
30 | "@types/node": "^20",
31 | "@types/react": "npm:types-react@19.0.0-beta.2",
32 | "@types/react-dom": "npm:types-react-dom@19.0.0-beta.2",
33 | "autoprefixer": "^10.0.1",
34 | "eslint": "^8",
35 | "eslint-config-next": "15.0.0-rc.0",
36 | "postcss": "^8",
37 | "tailwindcss": "^3.3.0",
38 | "typescript": "^5"
39 | },
40 | "pnpm": {
41 | "overrides": {
42 | "@types/react": "npm:types-react@19.0.0-beta.2",
43 | "@types/react-dom": "npm:types-react-dom@19.0.0-beta.2"
44 | }
45 | },
46 | "overrides": {
47 | "@types/react": "npm:types-react@19.0.0-beta.2",
48 | "@types/react-dom": "npm:types-react-dom@19.0.0-beta.2"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/arrow.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/blur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/payloadcms/vercel-deploy-payload-postgres/a4cfc89c0b1468c451d7acbef015f415020a5edd/public/blur.png
--------------------------------------------------------------------------------
/public/crosshair.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/gradient.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/payloadcms/vercel-deploy-payload-postgres/a4cfc89c0b1468c451d7acbef015f415020a5edd/public/gradient.webp
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/payload.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/public/scanline-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/payloadcms/vercel-deploy-payload-postgres/a4cfc89c0b1468c451d7acbef015f415020a5edd/public/scanline-light.png
--------------------------------------------------------------------------------
/src/app/(app)/globals.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | :root {
6 | --gutter: calc(50vw - 720px);
7 | --gridline: linear-gradient(
8 | rgba(255, 255, 255, 0.1),
9 | rgba(255, 255, 255, 0.025),
10 | rgba(255, 255, 255, 0.025),
11 | rgba(255, 255, 255, 0.1),
12 | rgba(255, 255, 255, 0.1)
13 | );
14 |
15 | @media screen and (max-width: 1600px) {
16 | --gutter: 80px;
17 | }
18 |
19 | @media screen and (max-width: 1200px) {
20 | --gutter: 40px;
21 | --gridline: rgba(255, 255, 255, 0.1);
22 | }
23 |
24 | @media screen and (max-width: 600px) {
25 | --gutter: 20px;
26 | }
27 | }
28 |
29 | html {
30 | width: 100%;
31 | height: 100%;
32 | background-color: #000000;
33 | }
34 |
35 | body {
36 | color: #ffffff;
37 | margin: 0;
38 | width: 100%;
39 | padding: 0;
40 | overflow-x: hidden;
41 | }
42 |
43 | main {
44 | display: flex;
45 | flex-direction: column;
46 | align-items: center;
47 | justify-content: space-between;
48 | width: 100%;
49 | height: 100vh;
50 | pointer-events: none;
51 | padding: 80px var(--gutter);
52 |
53 | @media screen and (max-width: 1200px) {
54 | height: auto;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/app/(app)/icon.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/app/(app)/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Inter } from 'next/font/google'
3 | import './globals.scss'
4 |
5 | const inter = Inter({ subsets: ['latin'] })
6 |
7 | export const metadata: Metadata = {
8 | title: 'Payload Vercel Starter',
9 | description: 'A Payload starter project with Next.js, Vercel Postgres, and Vercel Blob Storage.',
10 | }
11 |
12 | export default function RootLayout({
13 | children,
14 | }: Readonly<{
15 | children: React.ReactNode
16 | }>) {
17 | return (
18 |
19 |
{children}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/(app)/page.tsx:
--------------------------------------------------------------------------------
1 | import { Logos } from '@/components/Logos'
2 | import { Links } from '@/components/Links'
3 | import { Background } from '@/components/Background'
4 |
5 | export default function Home() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/(payload)/admin/[[...segments]]/not-found.tsx:
--------------------------------------------------------------------------------
1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2 | import type { Metadata } from 'next'
3 |
4 | import config from '@payload-config'
5 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
6 | import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
7 |
8 | type Args = {
9 | params: {
10 | segments: string[]
11 | }
12 | searchParams: {
13 | [key: string]: string | string[]
14 | }
15 | }
16 |
17 | export const generateMetadata = ({ params, searchParams }: Args): Promise =>
18 | generatePageMetadata({ config, params, searchParams })
19 |
20 | const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })
21 |
22 | export default NotFound
23 |
--------------------------------------------------------------------------------
/src/app/(payload)/admin/[[...segments]]/page.tsx:
--------------------------------------------------------------------------------
1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2 | import type { Metadata } from 'next'
3 |
4 | import config from '@payload-config'
5 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
6 | import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
7 |
8 | type Args = {
9 | params: {
10 | segments: string[]
11 | }
12 | searchParams: {
13 | [key: string]: string | string[]
14 | }
15 | }
16 |
17 | export const generateMetadata = ({ params, searchParams }: Args): Promise =>
18 | generatePageMetadata({ config, params, searchParams })
19 |
20 | const Page = ({ params, searchParams }: Args) => RootPage({ config, params, searchParams })
21 |
22 | export default Page
23 |
--------------------------------------------------------------------------------
/src/app/(payload)/api/[...slug]/route.ts:
--------------------------------------------------------------------------------
1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2 | /* DO NOT MODIFY it because it could be re-written at any time. */
3 | import config from '@payload-config'
4 | import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
5 |
6 | export const GET = REST_GET(config)
7 | export const POST = REST_POST(config)
8 | export const DELETE = REST_DELETE(config)
9 | export const PATCH = REST_PATCH(config)
10 | export const OPTIONS = REST_OPTIONS(config)
11 |
--------------------------------------------------------------------------------
/src/app/(payload)/api/graphql-playground/route.ts:
--------------------------------------------------------------------------------
1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2 | /* DO NOT MODIFY it because it could be re-written at any time. */
3 | import config from '@payload-config'
4 | import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
5 |
6 | export const GET = GRAPHQL_PLAYGROUND_GET(config)
7 |
--------------------------------------------------------------------------------
/src/app/(payload)/api/graphql/route.ts:
--------------------------------------------------------------------------------
1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2 | /* DO NOT MODIFY it because it could be re-written at any time. */
3 | import config from '@payload-config'
4 | import { GRAPHQL_POST } from '@payloadcms/next/routes'
5 |
6 | export const POST = GRAPHQL_POST(config)
7 |
--------------------------------------------------------------------------------
/src/app/(payload)/custom.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/payloadcms/vercel-deploy-payload-postgres/a4cfc89c0b1468c451d7acbef015f415020a5edd/src/app/(payload)/custom.scss
--------------------------------------------------------------------------------
/src/app/(payload)/layout.tsx:
--------------------------------------------------------------------------------
1 | /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2 | import configPromise from '@payload-config'
3 | import '@payloadcms/next/css'
4 | import { RootLayout } from '@payloadcms/next/layouts'
5 | /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
6 | import React from 'react'
7 |
8 | import './custom.scss'
9 |
10 | type Args = {
11 | children: React.ReactNode
12 | }
13 |
14 | const Layout = ({ children }: Args) => {children}
15 |
16 | export default Layout
17 |
--------------------------------------------------------------------------------
/src/collections/Media.ts:
--------------------------------------------------------------------------------
1 | import type { CollectionConfig } from 'payload'
2 |
3 | export const Media: CollectionConfig = {
4 | slug: 'media',
5 | access: {
6 | read: () => true,
7 | },
8 | fields: [
9 | {
10 | name: 'alt',
11 | type: 'text',
12 | required: true,
13 | },
14 | ],
15 | }
16 |
--------------------------------------------------------------------------------
/src/collections/Users.ts:
--------------------------------------------------------------------------------
1 | import type { CollectionConfig } from 'payload'
2 |
3 | export const Users: CollectionConfig = {
4 | slug: 'users',
5 | admin: {
6 | useAsTitle: 'email',
7 | },
8 | auth: true,
9 | fields: [
10 | // Email added by default
11 | // Add more fields as needed
12 | ],
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Background/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './styles.module.scss'
2 |
3 | export const Background = () => {
4 | return (
5 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Background/styles.module.scss:
--------------------------------------------------------------------------------
1 | .background {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | z-index: -2;
8 | }
9 |
10 | .blur {
11 | display: block;
12 | position: absolute;
13 | width: 100%;
14 | height: 100%;
15 | background: url('/blur.png');
16 | background-repeat: repeat;
17 | background-size: 400px 400px;
18 | background-blend-mode: soft-light, normal;
19 | backdrop-filter: blur(60px);
20 | z-index: -1;
21 | }
22 |
23 | .gradient {
24 | display: block;
25 | position: absolute;
26 | width: 100%;
27 | height: 100%;
28 | background: url('/gradient.webp');
29 | background-size: cover;
30 | background-position: center;
31 | z-index: -3;
32 | }
33 |
34 | .gridlineContainer {
35 | position: fixed;
36 | top: 0;
37 | left: 0;
38 | padding: 0 var(--gutter);
39 | width: 100vw;
40 | height: 100vh;
41 | display: flex;
42 | align-items: center;
43 | z-index: 0;
44 | & div {
45 | position: relative;
46 | display: block;
47 | width: 100%;
48 | height: 100%;
49 |
50 | &::before {
51 | content: '';
52 | display: block;
53 | position: absolute;
54 | width: 1px;
55 | height: 100%;
56 | left: 0;
57 | top: 0;
58 | background: var(--gridline);
59 | }
60 |
61 | &:last-of-type::after {
62 | content: '';
63 | display: block;
64 | position: absolute;
65 | width: 1px;
66 | height: 100%;
67 | right: 0;
68 | top: 0;
69 | background: var(--gridline);
70 | }
71 |
72 | &.hideMed {
73 | @media screen and (max-width: 1200px) {
74 | display: none;
75 | }
76 | }
77 |
78 | &.hideSmall {
79 | @media screen and (max-width: 600px) {
80 | display: none;
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/components/Links/index.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import styles from './styles.module.scss'
3 |
4 | export const Links = () => {
5 | return (
6 |
7 |
8 |
Admin Panel
9 |
Manage your site's content from the admin panel.
10 |
11 |
12 |
13 |
Payload Docs
14 |
Learn about how to build your backend with Payload.
15 |
16 |
17 |
18 |
Next.js Docs
19 |
Find in-depth information about Next.js features and API.
20 |
21 |
22 |
23 |
Need help?
24 |
Join our Discord to ask questions and get help from the community.
25 |
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/Links/styles.module.scss:
--------------------------------------------------------------------------------
1 | .links {
2 | display: grid;
3 | grid-template-columns: repeat(4, 1fr);
4 | width: 100%;
5 | margin: 0 var(--gutter);
6 | border-block: 1px solid rgba(255, 255, 255, 0.1);
7 | gap: 1px;
8 | pointer-events: all;
9 |
10 | @media screen and (max-width: 1200px) {
11 | grid-template-columns: repeat(2, 1fr);
12 | border-bottom: none;
13 | }
14 |
15 | @media screen and (max-width: 600px) {
16 | grid-template-columns: 1fr;
17 | }
18 |
19 | a {
20 | position: relative;
21 | display: flex;
22 | width: 100%;
23 | padding: 24px;
24 | padding-right: 48px;
25 | flex-direction: column;
26 | align-items: flex-start;
27 | gap: 12px;
28 | text-decoration: none;
29 | color: rgba(255, 255, 255, 0.75);
30 |
31 | @media screen and (max-width: 1200px) {
32 | border-bottom: 1px solid rgba(255, 255, 255, 0.1);
33 | }
34 |
35 | h6 {
36 | margin: 0;
37 | color: #fff;
38 | font-size: 20px;
39 | font-style: normal;
40 | font-weight: 600;
41 | line-height: normal;
42 | letter-spacing: -0.02em;
43 |
44 | @media screen and (max-width: 1200px) {
45 | font-size: 16px;
46 | }
47 | }
48 |
49 | span {
50 | line-height: 1.5;
51 | }
52 |
53 | &::before {
54 | display: block;
55 | position: absolute;
56 | content: url('/arrow.svg');
57 | width: 12px;
58 | height: 12px;
59 | top: 24px;
60 | right: 24px;
61 | opacity: 0.25;
62 | transition-property: top, right, opacity;
63 | transition-duration: 0.3s;
64 | }
65 |
66 | &::after {
67 | position: absolute;
68 | left: 0;
69 | bottom: 0;
70 | content: ('');
71 | display: block;
72 | width: 0;
73 | height: 2px;
74 | background-color: #fff;
75 | transition: width 0.3s;
76 | }
77 |
78 | &:hover {
79 | &::before {
80 | top: 20px;
81 | right: 20px;
82 | opacity: 1;
83 | }
84 |
85 | &::after {
86 | width: 100%;
87 | }
88 |
89 | .scanlines {
90 | opacity: 0.1;
91 | }
92 | }
93 | }
94 | }
95 |
96 | .scanlines {
97 | position: absolute;
98 | top: 0px;
99 | left: 0px;
100 | right: 0px;
101 | bottom: 0px;
102 | background: url('/scanline-light.png');
103 | opacity: 0;
104 | transition: opacity 0.3s;
105 | }
106 |
--------------------------------------------------------------------------------
/src/components/Logos/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import styles from './styles.module.scss'
3 |
4 | export const Logos = () => {
5 | return (
6 |
7 |
15 |
16 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Logos/styles.module.scss:
--------------------------------------------------------------------------------
1 | .logos {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: center;
5 | align-items: center;
6 | height: 100%;
7 | min-height: 50vh;
8 | width: 100%;
9 | gap: 80px;
10 |
11 | @media screen and (max-width: 600px) {
12 | gap: 40px;
13 | flex-direction: column;
14 | }
15 | }
16 |
17 | .payloadLogo {
18 | width: 100%;
19 | height: auto;
20 | max-width: 320px;
21 | display: flex;
22 | justify-content: flex-end;
23 | align-items: center;
24 | }
25 |
26 | .nextLogo {
27 | width: 100%;
28 | height: auto;
29 | max-width: 320px;
30 | display: flex;
31 | justify-content: flex-start;
32 | align-items: center;
33 | color: #fff;
34 | }
35 |
36 | .payloadLogo,
37 | .nextLogo {
38 | @media screen and (max-width: 1200px) {
39 | width: 25vw;
40 | height: auto;
41 | }
42 |
43 | @media screen and (max-width: 600px) {
44 | width: 50vw;
45 | justify-content: center;
46 | align-items: center;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/migrations/20240709_153941_initial.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "d0abacea-6447-48ed-8d01-c88b0bd12f20",
3 | "prevId": "00000000-0000-0000-0000-000000000000",
4 | "version": "5",
5 | "dialect": "pg",
6 | "tables": {
7 | "users": {
8 | "name": "users",
9 | "schema": "",
10 | "columns": {
11 | "id": {
12 | "name": "id",
13 | "type": "serial",
14 | "primaryKey": true,
15 | "notNull": true
16 | },
17 | "updated_at": {
18 | "name": "updated_at",
19 | "type": "timestamp(3) with time zone",
20 | "primaryKey": false,
21 | "notNull": true,
22 | "default": "now()"
23 | },
24 | "created_at": {
25 | "name": "created_at",
26 | "type": "timestamp(3) with time zone",
27 | "primaryKey": false,
28 | "notNull": true,
29 | "default": "now()"
30 | },
31 | "email": {
32 | "name": "email",
33 | "type": "varchar",
34 | "primaryKey": false,
35 | "notNull": true
36 | },
37 | "reset_password_token": {
38 | "name": "reset_password_token",
39 | "type": "varchar",
40 | "primaryKey": false,
41 | "notNull": false
42 | },
43 | "reset_password_expiration": {
44 | "name": "reset_password_expiration",
45 | "type": "timestamp(3) with time zone",
46 | "primaryKey": false,
47 | "notNull": false
48 | },
49 | "salt": {
50 | "name": "salt",
51 | "type": "varchar",
52 | "primaryKey": false,
53 | "notNull": false
54 | },
55 | "hash": {
56 | "name": "hash",
57 | "type": "varchar",
58 | "primaryKey": false,
59 | "notNull": false
60 | },
61 | "login_attempts": {
62 | "name": "login_attempts",
63 | "type": "numeric",
64 | "primaryKey": false,
65 | "notNull": false
66 | },
67 | "lock_until": {
68 | "name": "lock_until",
69 | "type": "timestamp(3) with time zone",
70 | "primaryKey": false,
71 | "notNull": false
72 | }
73 | },
74 | "indexes": {
75 | "users_created_at_idx": {
76 | "name": "users_created_at_idx",
77 | "columns": [
78 | "created_at"
79 | ],
80 | "isUnique": false
81 | },
82 | "users_email_idx": {
83 | "name": "users_email_idx",
84 | "columns": [
85 | "email"
86 | ],
87 | "isUnique": true
88 | }
89 | },
90 | "foreignKeys": {},
91 | "compositePrimaryKeys": {},
92 | "uniqueConstraints": {}
93 | },
94 | "media": {
95 | "name": "media",
96 | "schema": "",
97 | "columns": {
98 | "id": {
99 | "name": "id",
100 | "type": "serial",
101 | "primaryKey": true,
102 | "notNull": true
103 | },
104 | "alt": {
105 | "name": "alt",
106 | "type": "varchar",
107 | "primaryKey": false,
108 | "notNull": true
109 | },
110 | "updated_at": {
111 | "name": "updated_at",
112 | "type": "timestamp(3) with time zone",
113 | "primaryKey": false,
114 | "notNull": true,
115 | "default": "now()"
116 | },
117 | "created_at": {
118 | "name": "created_at",
119 | "type": "timestamp(3) with time zone",
120 | "primaryKey": false,
121 | "notNull": true,
122 | "default": "now()"
123 | },
124 | "url": {
125 | "name": "url",
126 | "type": "varchar",
127 | "primaryKey": false,
128 | "notNull": false
129 | },
130 | "thumbnail_u_r_l": {
131 | "name": "thumbnail_u_r_l",
132 | "type": "varchar",
133 | "primaryKey": false,
134 | "notNull": false
135 | },
136 | "filename": {
137 | "name": "filename",
138 | "type": "varchar",
139 | "primaryKey": false,
140 | "notNull": false
141 | },
142 | "mime_type": {
143 | "name": "mime_type",
144 | "type": "varchar",
145 | "primaryKey": false,
146 | "notNull": false
147 | },
148 | "filesize": {
149 | "name": "filesize",
150 | "type": "numeric",
151 | "primaryKey": false,
152 | "notNull": false
153 | },
154 | "width": {
155 | "name": "width",
156 | "type": "numeric",
157 | "primaryKey": false,
158 | "notNull": false
159 | },
160 | "height": {
161 | "name": "height",
162 | "type": "numeric",
163 | "primaryKey": false,
164 | "notNull": false
165 | },
166 | "focal_x": {
167 | "name": "focal_x",
168 | "type": "numeric",
169 | "primaryKey": false,
170 | "notNull": false
171 | },
172 | "focal_y": {
173 | "name": "focal_y",
174 | "type": "numeric",
175 | "primaryKey": false,
176 | "notNull": false
177 | }
178 | },
179 | "indexes": {
180 | "media_created_at_idx": {
181 | "name": "media_created_at_idx",
182 | "columns": [
183 | "created_at"
184 | ],
185 | "isUnique": false
186 | },
187 | "media_filename_idx": {
188 | "name": "media_filename_idx",
189 | "columns": [
190 | "filename"
191 | ],
192 | "isUnique": true
193 | }
194 | },
195 | "foreignKeys": {},
196 | "compositePrimaryKeys": {},
197 | "uniqueConstraints": {}
198 | },
199 | "payload_preferences": {
200 | "name": "payload_preferences",
201 | "schema": "",
202 | "columns": {
203 | "id": {
204 | "name": "id",
205 | "type": "serial",
206 | "primaryKey": true,
207 | "notNull": true
208 | },
209 | "key": {
210 | "name": "key",
211 | "type": "varchar",
212 | "primaryKey": false,
213 | "notNull": false
214 | },
215 | "value": {
216 | "name": "value",
217 | "type": "jsonb",
218 | "primaryKey": false,
219 | "notNull": false
220 | },
221 | "updated_at": {
222 | "name": "updated_at",
223 | "type": "timestamp(3) with time zone",
224 | "primaryKey": false,
225 | "notNull": true,
226 | "default": "now()"
227 | },
228 | "created_at": {
229 | "name": "created_at",
230 | "type": "timestamp(3) with time zone",
231 | "primaryKey": false,
232 | "notNull": true,
233 | "default": "now()"
234 | }
235 | },
236 | "indexes": {
237 | "payload_preferences_key_idx": {
238 | "name": "payload_preferences_key_idx",
239 | "columns": [
240 | "key"
241 | ],
242 | "isUnique": false
243 | },
244 | "payload_preferences_created_at_idx": {
245 | "name": "payload_preferences_created_at_idx",
246 | "columns": [
247 | "created_at"
248 | ],
249 | "isUnique": false
250 | }
251 | },
252 | "foreignKeys": {},
253 | "compositePrimaryKeys": {},
254 | "uniqueConstraints": {}
255 | },
256 | "payload_preferences_rels": {
257 | "name": "payload_preferences_rels",
258 | "schema": "",
259 | "columns": {
260 | "id": {
261 | "name": "id",
262 | "type": "serial",
263 | "primaryKey": true,
264 | "notNull": true
265 | },
266 | "order": {
267 | "name": "order",
268 | "type": "integer",
269 | "primaryKey": false,
270 | "notNull": false
271 | },
272 | "parent_id": {
273 | "name": "parent_id",
274 | "type": "integer",
275 | "primaryKey": false,
276 | "notNull": true
277 | },
278 | "path": {
279 | "name": "path",
280 | "type": "varchar",
281 | "primaryKey": false,
282 | "notNull": true
283 | },
284 | "users_id": {
285 | "name": "users_id",
286 | "type": "integer",
287 | "primaryKey": false,
288 | "notNull": false
289 | }
290 | },
291 | "indexes": {
292 | "payload_preferences_rels_order_idx": {
293 | "name": "payload_preferences_rels_order_idx",
294 | "columns": [
295 | "order"
296 | ],
297 | "isUnique": false
298 | },
299 | "payload_preferences_rels_parent_idx": {
300 | "name": "payload_preferences_rels_parent_idx",
301 | "columns": [
302 | "parent_id"
303 | ],
304 | "isUnique": false
305 | },
306 | "payload_preferences_rels_path_idx": {
307 | "name": "payload_preferences_rels_path_idx",
308 | "columns": [
309 | "path"
310 | ],
311 | "isUnique": false
312 | }
313 | },
314 | "foreignKeys": {
315 | "payload_preferences_rels_parent_fk": {
316 | "name": "payload_preferences_rels_parent_fk",
317 | "tableFrom": "payload_preferences_rels",
318 | "tableTo": "payload_preferences",
319 | "columnsFrom": [
320 | "parent_id"
321 | ],
322 | "columnsTo": [
323 | "id"
324 | ],
325 | "onDelete": "cascade",
326 | "onUpdate": "no action"
327 | },
328 | "payload_preferences_rels_users_fk": {
329 | "name": "payload_preferences_rels_users_fk",
330 | "tableFrom": "payload_preferences_rels",
331 | "tableTo": "users",
332 | "columnsFrom": [
333 | "users_id"
334 | ],
335 | "columnsTo": [
336 | "id"
337 | ],
338 | "onDelete": "cascade",
339 | "onUpdate": "no action"
340 | }
341 | },
342 | "compositePrimaryKeys": {},
343 | "uniqueConstraints": {}
344 | },
345 | "payload_migrations": {
346 | "name": "payload_migrations",
347 | "schema": "",
348 | "columns": {
349 | "id": {
350 | "name": "id",
351 | "type": "serial",
352 | "primaryKey": true,
353 | "notNull": true
354 | },
355 | "name": {
356 | "name": "name",
357 | "type": "varchar",
358 | "primaryKey": false,
359 | "notNull": false
360 | },
361 | "batch": {
362 | "name": "batch",
363 | "type": "numeric",
364 | "primaryKey": false,
365 | "notNull": false
366 | },
367 | "updated_at": {
368 | "name": "updated_at",
369 | "type": "timestamp(3) with time zone",
370 | "primaryKey": false,
371 | "notNull": true,
372 | "default": "now()"
373 | },
374 | "created_at": {
375 | "name": "created_at",
376 | "type": "timestamp(3) with time zone",
377 | "primaryKey": false,
378 | "notNull": true,
379 | "default": "now()"
380 | }
381 | },
382 | "indexes": {
383 | "payload_migrations_created_at_idx": {
384 | "name": "payload_migrations_created_at_idx",
385 | "columns": [
386 | "created_at"
387 | ],
388 | "isUnique": false
389 | }
390 | },
391 | "foreignKeys": {},
392 | "compositePrimaryKeys": {},
393 | "uniqueConstraints": {}
394 | }
395 | },
396 | "enums": {},
397 | "schemas": {},
398 | "_meta": {
399 | "schemas": {},
400 | "tables": {},
401 | "columns": {}
402 | }
403 | }
--------------------------------------------------------------------------------
/src/migrations/20240709_153941_initial.ts:
--------------------------------------------------------------------------------
1 | import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
2 |
3 | export async function up({ payload, req }: MigrateUpArgs): Promise {
4 | await payload.db.drizzle.execute(sql`
5 | CREATE TABLE IF NOT EXISTS "users" (
6 | "id" serial PRIMARY KEY NOT NULL,
7 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
8 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
9 | "email" varchar NOT NULL,
10 | "reset_password_token" varchar,
11 | "reset_password_expiration" timestamp(3) with time zone,
12 | "salt" varchar,
13 | "hash" varchar,
14 | "login_attempts" numeric,
15 | "lock_until" timestamp(3) with time zone
16 | );
17 |
18 | CREATE TABLE IF NOT EXISTS "media" (
19 | "id" serial PRIMARY KEY NOT NULL,
20 | "alt" varchar NOT NULL,
21 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
22 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
23 | "url" varchar,
24 | "thumbnail_u_r_l" varchar,
25 | "filename" varchar,
26 | "mime_type" varchar,
27 | "filesize" numeric,
28 | "width" numeric,
29 | "height" numeric,
30 | "focal_x" numeric,
31 | "focal_y" numeric
32 | );
33 |
34 | CREATE TABLE IF NOT EXISTS "payload_preferences" (
35 | "id" serial PRIMARY KEY NOT NULL,
36 | "key" varchar,
37 | "value" jsonb,
38 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
39 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
40 | );
41 |
42 | CREATE TABLE IF NOT EXISTS "payload_preferences_rels" (
43 | "id" serial PRIMARY KEY NOT NULL,
44 | "order" integer,
45 | "parent_id" integer NOT NULL,
46 | "path" varchar NOT NULL,
47 | "users_id" integer
48 | );
49 |
50 | CREATE TABLE IF NOT EXISTS "payload_migrations" (
51 | "id" serial PRIMARY KEY NOT NULL,
52 | "name" varchar,
53 | "batch" numeric,
54 | "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
55 | "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
56 | );
57 |
58 | CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at");
59 | CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");
60 | CREATE INDEX IF NOT EXISTS "media_created_at_idx" ON "media" ("created_at");
61 | CREATE UNIQUE INDEX IF NOT EXISTS "media_filename_idx" ON "media" ("filename");
62 | CREATE INDEX IF NOT EXISTS "payload_preferences_key_idx" ON "payload_preferences" ("key");
63 | CREATE INDEX IF NOT EXISTS "payload_preferences_created_at_idx" ON "payload_preferences" ("created_at");
64 | CREATE INDEX IF NOT EXISTS "payload_preferences_rels_order_idx" ON "payload_preferences_rels" ("order");
65 | CREATE INDEX IF NOT EXISTS "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" ("parent_id");
66 | CREATE INDEX IF NOT EXISTS "payload_preferences_rels_path_idx" ON "payload_preferences_rels" ("path");
67 | CREATE INDEX IF NOT EXISTS "payload_migrations_created_at_idx" ON "payload_migrations" ("created_at");
68 | DO $$ BEGIN
69 | ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
70 | EXCEPTION
71 | WHEN duplicate_object THEN null;
72 | END $$;
73 |
74 | DO $$ BEGIN
75 | ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
76 | EXCEPTION
77 | WHEN duplicate_object THEN null;
78 | END $$;
79 | `)
80 | };
81 |
82 | export async function down({ payload, req }: MigrateDownArgs): Promise {
83 | await payload.db.drizzle.execute(sql`
84 | DROP TABLE "users";
85 | DROP TABLE "media";
86 | DROP TABLE "payload_preferences";
87 | DROP TABLE "payload_preferences_rels";
88 | DROP TABLE "payload_migrations";`)
89 | };
90 |
--------------------------------------------------------------------------------
/src/payload-types.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | /**
4 | * This file was automatically generated by Payload.
5 | * DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
6 | * and re-run `payload generate:types` to regenerate this file.
7 | */
8 |
9 | export interface Config {
10 | auth: {
11 | users: UserAuthOperations;
12 | };
13 | collections: {
14 | users: User;
15 | media: Media;
16 | 'payload-preferences': PayloadPreference;
17 | 'payload-migrations': PayloadMigration;
18 | };
19 | globals: {};
20 | locale: null;
21 | user: User & {
22 | collection: 'users';
23 | };
24 | }
25 | export interface UserAuthOperations {
26 | forgotPassword: {
27 | email: string;
28 | };
29 | login: {
30 | password: string;
31 | email: string;
32 | };
33 | registerFirstUser: {
34 | email: string;
35 | password: string;
36 | };
37 | }
38 | /**
39 | * This interface was referenced by `Config`'s JSON-Schema
40 | * via the `definition` "users".
41 | */
42 | export interface User {
43 | id: number;
44 | updatedAt: string;
45 | createdAt: string;
46 | email: string;
47 | resetPasswordToken?: string | null;
48 | resetPasswordExpiration?: string | null;
49 | salt?: string | null;
50 | hash?: string | null;
51 | loginAttempts?: number | null;
52 | lockUntil?: string | null;
53 | password?: string | null;
54 | }
55 | /**
56 | * This interface was referenced by `Config`'s JSON-Schema
57 | * via the `definition` "media".
58 | */
59 | export interface Media {
60 | id: number;
61 | alt: string;
62 | updatedAt: string;
63 | createdAt: string;
64 | url?: string | null;
65 | thumbnailURL?: string | null;
66 | filename?: string | null;
67 | mimeType?: string | null;
68 | filesize?: number | null;
69 | width?: number | null;
70 | height?: number | null;
71 | focalX?: number | null;
72 | focalY?: number | null;
73 | }
74 | /**
75 | * This interface was referenced by `Config`'s JSON-Schema
76 | * via the `definition` "payload-preferences".
77 | */
78 | export interface PayloadPreference {
79 | id: number;
80 | user: {
81 | relationTo: 'users';
82 | value: number | User;
83 | };
84 | key?: string | null;
85 | value?:
86 | | {
87 | [k: string]: unknown;
88 | }
89 | | unknown[]
90 | | string
91 | | number
92 | | boolean
93 | | null;
94 | updatedAt: string;
95 | createdAt: string;
96 | }
97 | /**
98 | * This interface was referenced by `Config`'s JSON-Schema
99 | * via the `definition` "payload-migrations".
100 | */
101 | export interface PayloadMigration {
102 | id: number;
103 | name?: string | null;
104 | batch?: number | null;
105 | updatedAt: string;
106 | createdAt: string;
107 | }
108 | /**
109 | * This interface was referenced by `Config`'s JSON-Schema
110 | * via the `definition` "auth".
111 | */
112 | export interface Auth {
113 | [k: string]: unknown;
114 | }
115 |
116 |
117 | declare module 'payload' {
118 | export interface GeneratedTypes extends Config {}
119 | }
--------------------------------------------------------------------------------
/src/payload.config.ts:
--------------------------------------------------------------------------------
1 | import { postgresAdapter } from '@payloadcms/db-postgres'
2 | import { lexicalEditor } from '@payloadcms/richtext-lexical'
3 | import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
4 | import path from 'path'
5 | import sharp from 'sharp'
6 | import { buildConfig } from 'payload'
7 | import { fileURLToPath } from 'url'
8 |
9 | import { Users } from './collections/Users'
10 | import { Media } from './collections/Media'
11 |
12 | const filename = fileURLToPath(import.meta.url)
13 | const dirname = path.dirname(filename)
14 |
15 | export default buildConfig({
16 | admin: {
17 | user: Users.slug,
18 | },
19 | collections: [Users, Media],
20 | editor: lexicalEditor({}),
21 | secret: process.env.PAYLOAD_SECRET || '',
22 | typescript: {
23 | outputFile: path.resolve(dirname, 'payload-types.ts'),
24 | },
25 | db: postgresAdapter({
26 | pool: {
27 | connectionString: process.env.POSTGRES_URL,
28 | },
29 | }),
30 |
31 | sharp,
32 |
33 | plugins: [
34 | vercelBlobStorage({
35 | collections: {
36 | [Media.slug]: true,
37 | },
38 | token: process.env.BLOB_READ_WRITE_TOKEN || '',
39 | }),
40 | ],
41 | })
42 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "noEmit": true,
12 | "esModuleInterop": true,
13 | "module": "esnext",
14 | "moduleResolution": "bundler",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "jsx": "preserve",
18 | "incremental": true,
19 | "plugins": [
20 | {
21 | "name": "next"
22 | }
23 | ],
24 | "paths": {
25 | "@/*": [
26 | "./src/*"
27 | ],
28 | "@payload-config": [
29 | "./src/payload.config.ts"
30 | ]
31 | },
32 | "target": "ES2017"
33 | },
34 | "include": [
35 | "next-env.d.ts",
36 | "**/*.ts",
37 | "**/*.tsx",
38 | ".next/types/**/*.ts"
39 | ],
40 | "exclude": [
41 | "node_modules"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------