├── .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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payloadcms/vercel-deploy-payload-postgres/a4cfc89c0b1468c451d7acbef015f415020a5edd/public/blur.png -------------------------------------------------------------------------------- /public/crosshair.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
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 | Payload Logo 15 | 16 | Next.js Logo 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 | --------------------------------------------------------------------------------