mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 13:49:09 +03:00
Merge branch 'master' into feature/database-tests
This commit is contained in:
commit
4ee87e8a0e
636 files changed
+40476
-19831
No files matched your search
@@ -45,6 +45,7 @@ jobs:
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
apps/www/.env.local.example
|
||||
examples
|
||||
supabase
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
@@ -63,7 +64,9 @@ jobs:
|
||||
- name: Update embeddings
|
||||
working-directory: ./apps/docs
|
||||
if: ${{ !inputs.refresh }}
|
||||
run: pnpm run embeddings
|
||||
run: |
|
||||
pnpm run codegen:examples
|
||||
pnpm run embeddings
|
||||
|
||||
- name: Refresh embeddings
|
||||
working-directory: ./apps/docs
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# [LOCAL ENV] - NEVER USE PROD VALUES LOCALLY
|
||||
# as editing PayloadCMS typescript config INSTANTLY CHANGES DB SCHEMA in dev mode
|
||||
# The values set here are the default values provided when running `supabase start`.
|
||||
# Do not add secrets here, this file is git-tracked. Use .env.local instead.
|
||||
DATABASE_URI=postgresql://postgres:postgres@127.0.0.1:34322/postgres
|
||||
PAYLOAD_SECRET=secret
|
||||
|
||||
S3_BUCKET=cms
|
||||
S3_ACCESS_KEY_ID=625729a08b95bf1b7ff351a663f3a23c
|
||||
S3_SECRET_ACCESS_KEY=850181e4652dd023b7a98c58ae0d2d34bd487ee0cc3254aed6eda37307425907
|
||||
S3_REGION=local
|
||||
S3_ENDPOINT=http://127.0.0.1:34321/storage/v1/s3
|
||||
|
||||
BLOG_APP_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_SERVER_URL=http://localhost:3000/blog
|
||||
CRON_SECRET=secret
|
||||
PREVIEW_SECRET=secret
|
||||
@@ -1,13 +0,0 @@
|
||||
DATABASE_URI=postgres://postgres:<password>@127.0.0.1:5432/your-database-name
|
||||
PAYLOAD_SECRET=YOUR_SECRET_HERE
|
||||
|
||||
S3_BUCKET=
|
||||
S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
S3_REGION=
|
||||
S3_ENDPOINT=
|
||||
|
||||
NEXT_PUBLIC_SERVER_URL=http://localhost:3000/blog
|
||||
CRON_SECRET=YOUR_CRON_SECRET_HERE
|
||||
PREVIEW_SECRET=YOUR_SECRET_HERE
|
||||
BLOG_APP_URL=http://localhost:3000
|
||||
File renamed without changes.
+2
-3
@@ -29,7 +29,8 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
!.env
|
||||
.env.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
@@ -38,6 +39,4 @@ yarn-error.log*
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.env
|
||||
|
||||
/media
|
||||
@@ -0,0 +1,48 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
**/node_modules
|
||||
|
||||
# Build artifacts
|
||||
.next/cache
|
||||
.turbo
|
||||
.eslintcache
|
||||
|
||||
# Development files
|
||||
*.log
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Test files
|
||||
**/*.test.*
|
||||
**/*.spec.*
|
||||
__tests__
|
||||
**/__tests__
|
||||
coverage
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
*.md
|
||||
docs
|
||||
|
||||
# IDE files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Other apps (since we're only deploying CMS)
|
||||
../studio
|
||||
../www
|
||||
../docs
|
||||
../design-system
|
||||
../ui-library
|
||||
../../examples
|
||||
../../tests
|
||||
../../supabase
|
||||
../../packages/ui-patterns
|
||||
../../packages/ui
|
||||
../../packages/shared-data
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
# Uncomment the following to use postgres
|
||||
# Uncomment the following to use postgres
|
||||
postgres:
|
||||
restart: always
|
||||
image: postgres:latest
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
|
||||
import redirects from './redirects.js'
|
||||
const redirects = async () => {
|
||||
const internetExplorerRedirect = {
|
||||
destination: '/ie-incompatible.html',
|
||||
has: [
|
||||
{
|
||||
type: 'header',
|
||||
key: 'user-agent',
|
||||
value: '(.*Trident.*)', // all ie browsers
|
||||
},
|
||||
],
|
||||
permanent: false,
|
||||
source: '/:path((?!ie-incompatible.html$).*)', // all pages except the incompatibility page
|
||||
}
|
||||
|
||||
const redirects = [internetExplorerRedirect]
|
||||
|
||||
return redirects
|
||||
}
|
||||
|
||||
const NEXT_PUBLIC_SERVER_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL
|
||||
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
||||
|
||||
+32
-16
@@ -5,43 +5,59 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_OPTIONS=--no-deprecation next build --turbopack",
|
||||
"ci": "pnpm migrate && pnpm build",
|
||||
"clean": "rimraf node_modules .next",
|
||||
"dev": "cross-env NODE_OPTIONS=--no-deprecation next dev --turbopack --port 3030",
|
||||
"dev:prod": "cross-env NODE_OPTIONS=--no-deprecation rm -rf .next && pnpm build && pnpm start",
|
||||
"devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev",
|
||||
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
|
||||
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
|
||||
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
|
||||
"migrate": "cross-env NODE_OPTIONS=--no-deprecation tsx scripts/migrate.ts",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start",
|
||||
"ci": "cross-env NODE_OPTIONS=--no-deprecation payload migrate && pnpm build",
|
||||
"clean": "rimraf node_modules .next",
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start --port 3030",
|
||||
"vercel-build": "pnpm migrate && pnpm build",
|
||||
"typecheck_IGNORED": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/db-postgres": "3.33.0",
|
||||
"@payloadcms/live-preview-react": "^3.33.0",
|
||||
"@payloadcms/next": "3.33.0",
|
||||
"@payloadcms/payload-cloud": "3.33.0",
|
||||
"@payloadcms/plugin-form-builder": "3.33.0",
|
||||
"@payloadcms/plugin-nested-docs": "3.33.0",
|
||||
"@payloadcms/plugin-seo": "3.33.0",
|
||||
"@payloadcms/richtext-lexical": "3.33.0",
|
||||
"@payloadcms/storage-s3": "3.33.0",
|
||||
"@payloadcms/ui": "3.33.0",
|
||||
"@payloadcms/admin-bar": "^3.50.0",
|
||||
"@payloadcms/db-postgres": "^3.50.0",
|
||||
"@payloadcms/live-preview-react": "^3.50.0",
|
||||
"@payloadcms/next": "3.50.0",
|
||||
"@payloadcms/payload-cloud": "3.50.0",
|
||||
"@payloadcms/plugin-form-builder": "3.50.0",
|
||||
"@payloadcms/plugin-nested-docs": "3.50.0",
|
||||
"@payloadcms/plugin-seo": "3.50.0",
|
||||
"@payloadcms/richtext-lexical": "3.50.0",
|
||||
"@payloadcms/storage-s3": "3.50.0",
|
||||
"@payloadcms/ui": "3.50.0",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^1.2.1",
|
||||
"common": "workspace:*",
|
||||
"config": "workspace:*",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-config-supabase": "workspace:*",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql": "^16.11.0",
|
||||
"image-size": "2.0.2",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "catalog:",
|
||||
"payload": "3.33.0",
|
||||
"payload": "3.50.0",
|
||||
"pg": "^8.16.3",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"sharp": "0.32.6"
|
||||
"sharp": "0.32.6",
|
||||
"tailwind-merge": "^1.13.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "5.7.3"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
const redirects = async () => {
|
||||
const internetExplorerRedirect = {
|
||||
destination: '/ie-incompatible.html',
|
||||
has: [
|
||||
{
|
||||
type: 'header',
|
||||
key: 'user-agent',
|
||||
value: '(.*Trident.*)', // all ie browsers
|
||||
},
|
||||
],
|
||||
permanent: false,
|
||||
source: '/:path((?!ie-incompatible.html$).*)', // all pages except the incompatibility page
|
||||
}
|
||||
|
||||
const redirects = [internetExplorerRedirect]
|
||||
|
||||
return redirects
|
||||
}
|
||||
|
||||
export default redirects
|
||||
@@ -0,0 +1,28 @@
|
||||
import payload from 'payload'
|
||||
// Use a minimal config for migrations to avoid importing the full app graph during build
|
||||
import payloadConfig from '../src/payload.migrate.config.ts'
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
await payload.init({ config: payloadConfig })
|
||||
|
||||
// Ensure non-interactive: remove any dev-mode migration sentinel rows
|
||||
try {
|
||||
await payload.delete({
|
||||
collection: 'payload-migrations',
|
||||
where: { batch: { equals: -1 } },
|
||||
})
|
||||
} catch {}
|
||||
|
||||
await payload.db.migrate()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('✅ Payload migrations complete')
|
||||
process.exit(0)
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('❌ Payload migrations failed:', err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AccessArgs, FieldAccess } from 'payload'
|
||||
import type { User } from '@/payload-types'
|
||||
import type { User } from '../payload-types'
|
||||
|
||||
type isAdmin = (args: AccessArgs<User>) => boolean
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AccessArgs } from 'payload'
|
||||
|
||||
import type { User } from '@/payload-types'
|
||||
import type { User } from '../payload-types'
|
||||
|
||||
type isAuthenticated = (args: AccessArgs<User>) => boolean
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import type { Post, ArchiveBlock as ArchiveBlockProps } from '@/payload-types'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
import RichText from '@/components/RichText'
|
||||
|
||||
import { CollectionArchive } from '@/components/CollectionArchive'
|
||||
|
||||
export const ArchiveBlock: React.FC<
|
||||
ArchiveBlockProps & {
|
||||
id?: string
|
||||
}
|
||||
> = async (props) => {
|
||||
const { id, categories, introContent, limit: limitFromProps, populateBy, selectedDocs } = props
|
||||
|
||||
const limit = limitFromProps || 3
|
||||
|
||||
let posts: Post[] = []
|
||||
|
||||
if (populateBy === 'collection') {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
const flattenedCategories = categories?.map((category) => {
|
||||
if (typeof category === 'object') return category.id
|
||||
else return category
|
||||
})
|
||||
|
||||
const fetchedPosts = await payload.find({
|
||||
collection: 'posts',
|
||||
depth: 1,
|
||||
limit,
|
||||
...(flattenedCategories && flattenedCategories.length > 0
|
||||
? {
|
||||
where: {
|
||||
categories: {
|
||||
in: flattenedCategories,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
|
||||
posts = fetchedPosts.docs
|
||||
} else {
|
||||
if (selectedDocs?.length) {
|
||||
const filteredSelectedPosts = selectedDocs.map((post) => {
|
||||
if (typeof post.value === 'object') return post.value
|
||||
}) as Post[]
|
||||
|
||||
posts = filteredSelectedPosts
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-16" id={`block-${id}`}>
|
||||
{introContent && (
|
||||
<div className="container mb-16">
|
||||
<RichText className="ms-0 max-w-[48rem]" data={introContent} enableGutter={false} />
|
||||
</div>
|
||||
)}
|
||||
<CollectionArchive posts={posts} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import type { Block } from 'payload'
|
||||
|
||||
import {
|
||||
FixedToolbarFeature,
|
||||
HeadingFeature,
|
||||
InlineToolbarFeature,
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const Archive: Block = {
|
||||
slug: 'archive',
|
||||
interfaceName: 'ArchiveBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'introContent',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ rootFeatures }) => {
|
||||
return [
|
||||
...rootFeatures,
|
||||
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
|
||||
FixedToolbarFeature(),
|
||||
InlineToolbarFeature(),
|
||||
]
|
||||
},
|
||||
}),
|
||||
label: 'Intro Content',
|
||||
},
|
||||
{
|
||||
name: 'populateBy',
|
||||
type: 'select',
|
||||
defaultValue: 'collection',
|
||||
options: [
|
||||
{
|
||||
label: 'Collection',
|
||||
value: 'collection',
|
||||
},
|
||||
{
|
||||
label: 'Individual Selection',
|
||||
value: 'selection',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'relationTo',
|
||||
type: 'select',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData.populateBy === 'collection',
|
||||
},
|
||||
defaultValue: 'posts',
|
||||
label: 'Collections To Show',
|
||||
options: [
|
||||
{
|
||||
label: 'Posts',
|
||||
value: 'posts',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'categories',
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData.populateBy === 'collection',
|
||||
},
|
||||
hasMany: true,
|
||||
label: 'Categories To Show',
|
||||
relationTo: 'categories',
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData.populateBy === 'collection',
|
||||
step: 1,
|
||||
},
|
||||
defaultValue: 10,
|
||||
label: 'Limit',
|
||||
},
|
||||
{
|
||||
name: 'selectedDocs',
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData.populateBy === 'selection',
|
||||
},
|
||||
hasMany: true,
|
||||
label: 'Selection',
|
||||
relationTo: ['posts'],
|
||||
},
|
||||
],
|
||||
labels: {
|
||||
plural: 'Archives',
|
||||
singular: 'Archive',
|
||||
},
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { CallToActionBlock as CTABlockProps } from '@/payload-types'
|
||||
|
||||
import RichText from '@/components/RichText'
|
||||
import { CMSLink } from '@/components/Link'
|
||||
|
||||
export const CallToActionBlock: React.FC<CTABlockProps> = ({ links, richText }) => {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center">
|
||||
<div className="max-w-[48rem] flex items-center">
|
||||
{richText && <RichText className="mb-0" data={richText} enableGutter={false} />}
|
||||
</div>
|
||||
<div className="flex flex-col gap-8">
|
||||
{(links || []).map(({ link }, i) => {
|
||||
return <CMSLink key={i} size="lg" {...link} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import type { Block } from 'payload'
|
||||
|
||||
import {
|
||||
FixedToolbarFeature,
|
||||
HeadingFeature,
|
||||
InlineToolbarFeature,
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { linkGroup } from '../../fields/linkGroup'
|
||||
|
||||
export const CallToAction: Block = {
|
||||
slug: 'cta',
|
||||
interfaceName: 'CallToActionBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ rootFeatures }) => {
|
||||
return [
|
||||
...rootFeatures,
|
||||
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
|
||||
FixedToolbarFeature(),
|
||||
InlineToolbarFeature(),
|
||||
]
|
||||
},
|
||||
}),
|
||||
label: false,
|
||||
},
|
||||
linkGroup({
|
||||
appearances: ['default', 'outline'],
|
||||
overrides: {
|
||||
maxRows: 2,
|
||||
},
|
||||
}),
|
||||
],
|
||||
labels: {
|
||||
plural: 'Calls to Action',
|
||||
singular: 'Call to Action',
|
||||
},
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export function CopyButton({ code }: { code: string }) {
|
||||
<div className="flex justify-end align-middle">
|
||||
<Button
|
||||
className="flex gap-1"
|
||||
variant={'secondary'}
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(code)
|
||||
updateCopyStatus()
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { cn } from '@/utilities/ui'
|
||||
import React from 'react'
|
||||
import RichText from '@/components/RichText'
|
||||
|
||||
import type { ContentBlock as ContentBlockProps } from '@/payload-types'
|
||||
|
||||
import { CMSLink } from '../../components/Link'
|
||||
|
||||
export const ContentBlock: React.FC<ContentBlockProps> = (props) => {
|
||||
const { columns } = props
|
||||
|
||||
const colsSpanClasses = {
|
||||
full: '12',
|
||||
half: '6',
|
||||
oneThird: '4',
|
||||
twoThirds: '8',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container my-16">
|
||||
<div className="grid grid-cols-4 lg:grid-cols-12 gap-y-8 gap-x-16">
|
||||
{columns &&
|
||||
columns.length > 0 &&
|
||||
columns.map((col, index) => {
|
||||
const { enableLink, link, richText, size } = col
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(`col-span-4 lg:col-span-${colsSpanClasses[size!]}`, {
|
||||
'md:col-span-2': size !== 'full',
|
||||
})}
|
||||
key={index}
|
||||
>
|
||||
{richText && <RichText data={richText} enableGutter={false} />}
|
||||
|
||||
{enableLink && <CMSLink {...link} />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import type { Block, Field } from 'payload'
|
||||
|
||||
import {
|
||||
FixedToolbarFeature,
|
||||
HeadingFeature,
|
||||
InlineToolbarFeature,
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { link } from '@/fields/link'
|
||||
|
||||
const columnFields: Field[] = [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'select',
|
||||
defaultValue: 'oneThird',
|
||||
options: [
|
||||
{
|
||||
label: 'One Third',
|
||||
value: 'oneThird',
|
||||
},
|
||||
{
|
||||
label: 'Half',
|
||||
value: 'half',
|
||||
},
|
||||
{
|
||||
label: 'Two Thirds',
|
||||
value: 'twoThirds',
|
||||
},
|
||||
{
|
||||
label: 'Full',
|
||||
value: 'full',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ rootFeatures }) => {
|
||||
return [
|
||||
...rootFeatures,
|
||||
HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4'] }),
|
||||
FixedToolbarFeature(),
|
||||
InlineToolbarFeature(),
|
||||
]
|
||||
},
|
||||
}),
|
||||
label: false,
|
||||
},
|
||||
{
|
||||
name: 'enableLink',
|
||||
type: 'checkbox',
|
||||
},
|
||||
link({
|
||||
overrides: {
|
||||
admin: {
|
||||
condition: (_data, siblingData) => {
|
||||
return Boolean(siblingData?.enableLink)
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
export const Content: Block = {
|
||||
slug: 'content',
|
||||
interfaceName: 'ContentBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'array',
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: columnFields,
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { CheckboxField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
|
||||
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { Checkbox as CheckboxUi } from '@/components/ui/checkbox'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import React from 'react'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
|
||||
export const Checkbox: React.FC<
|
||||
CheckboxField & {
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
register: UseFormRegister<FieldValues>
|
||||
}
|
||||
> = ({ name, defaultValue, errors, label, register, required, width }) => {
|
||||
const props = register(name, { required: required })
|
||||
const { setValue } = useFormContext()
|
||||
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckboxUi
|
||||
defaultChecked={defaultValue}
|
||||
id={name}
|
||||
{...props}
|
||||
onCheckedChange={(checked) => {
|
||||
setValue(props.name, checked)
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={name}>
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
{label}
|
||||
</Label>
|
||||
</div>
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
'use client'
|
||||
import type { FormFieldBlock, Form as FormType } from '@payloadcms/plugin-form-builder/types'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import RichText from '@/components/RichText'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { fields } from './fields'
|
||||
import { getClientSideURL } from '@/utilities/getURL'
|
||||
|
||||
export type FormBlockType = {
|
||||
blockName?: string
|
||||
blockType?: 'formBlock'
|
||||
enableIntro: boolean
|
||||
form: FormType
|
||||
introContent?: SerializedEditorState
|
||||
}
|
||||
|
||||
export const FormBlock: React.FC<
|
||||
{
|
||||
id?: string
|
||||
} & FormBlockType
|
||||
> = (props) => {
|
||||
const {
|
||||
enableIntro,
|
||||
form: formFromProps,
|
||||
form: { id: formID, confirmationMessage, confirmationType, redirect, submitButtonLabel } = {},
|
||||
introContent,
|
||||
} = props
|
||||
|
||||
const formMethods = useForm({
|
||||
defaultValues: formFromProps.fields,
|
||||
})
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
register,
|
||||
} = formMethods
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [hasSubmitted, setHasSubmitted] = useState<boolean>()
|
||||
const [error, setError] = useState<{ message: string; status?: string } | undefined>()
|
||||
const router = useRouter()
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(data: FormFieldBlock[]) => {
|
||||
let loadingTimerID: ReturnType<typeof setTimeout>
|
||||
const submitForm = async () => {
|
||||
setError(undefined)
|
||||
|
||||
const dataToSend = Object.entries(data).map(([name, value]) => ({
|
||||
field: name,
|
||||
value,
|
||||
}))
|
||||
|
||||
// delay loading indicator by 1s
|
||||
loadingTimerID = setTimeout(() => {
|
||||
setIsLoading(true)
|
||||
}, 1000)
|
||||
|
||||
try {
|
||||
const req = await fetch(`${getClientSideURL()}/api/form-submissions`, {
|
||||
body: JSON.stringify({
|
||||
form: formID,
|
||||
submissionData: dataToSend,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
const res = await req.json()
|
||||
|
||||
clearTimeout(loadingTimerID)
|
||||
|
||||
if (req.status >= 400) {
|
||||
setIsLoading(false)
|
||||
|
||||
setError({
|
||||
message: res.errors?.[0]?.message || 'Internal Server Error',
|
||||
status: res.status,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
setHasSubmitted(true)
|
||||
|
||||
if (confirmationType === 'redirect' && redirect) {
|
||||
const { url } = redirect
|
||||
|
||||
const redirectUrl = url
|
||||
|
||||
if (redirectUrl) router.push(redirectUrl)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
setIsLoading(false)
|
||||
setError({
|
||||
message: 'Something went wrong.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
void submitForm()
|
||||
},
|
||||
[router, formID, redirect, confirmationType]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="container lg:max-w-[48rem]">
|
||||
{enableIntro && introContent && !hasSubmitted && (
|
||||
<RichText className="mb-8 lg:mb-12" data={introContent} enableGutter={false} />
|
||||
)}
|
||||
<div className="p-4 lg:p-6 border border-border rounded-[0.8rem]">
|
||||
<FormProvider {...formMethods}>
|
||||
{!isLoading && hasSubmitted && confirmationType === 'message' && (
|
||||
<RichText data={confirmationMessage} />
|
||||
)}
|
||||
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
||||
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
|
||||
{!hasSubmitted && (
|
||||
<form id={formID} onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-4 last:mb-0">
|
||||
{formFromProps &&
|
||||
formFromProps.fields &&
|
||||
formFromProps.fields?.map((field, index) => {
|
||||
const Field: React.FC<any> = fields?.[field.blockType as keyof typeof fields]
|
||||
if (Field) {
|
||||
return (
|
||||
<div className="mb-6 last:mb-0" key={index}>
|
||||
<Field
|
||||
form={formFromProps}
|
||||
{...field}
|
||||
{...formMethods}
|
||||
control={control}
|
||||
errors={errors}
|
||||
register={register}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Button form={formID} type="submit" variant="default">
|
||||
{submitButtonLabel}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import type { CountryField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { Control, FieldErrorsImpl } from 'react-hook-form'
|
||||
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import React from 'react'
|
||||
import { Controller } from 'react-hook-form'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
import { countryOptions } from './options'
|
||||
|
||||
export const Country: React.FC<
|
||||
CountryField & {
|
||||
control: Control
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
}
|
||||
> = ({ name, control, errors, label, required, width }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<Label className="" htmlFor={name}>
|
||||
{label}
|
||||
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name={name}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const controlledValue = countryOptions.find((t) => t.value === value)
|
||||
|
||||
return (
|
||||
<Select onValueChange={(val) => onChange(val)} value={controlledValue?.value}>
|
||||
<SelectTrigger className="w-full" id={name}>
|
||||
<SelectValue placeholder={label} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{countryOptions.map(({ label, value }) => {
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}}
|
||||
rules={{ required }}
|
||||
/>
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,982 +0,0 @@
|
||||
export const countryOptions = [
|
||||
{
|
||||
label: 'Afghanistan',
|
||||
value: 'AF',
|
||||
},
|
||||
{
|
||||
label: 'Åland Islands',
|
||||
value: 'AX',
|
||||
},
|
||||
{
|
||||
label: 'Albania',
|
||||
value: 'AL',
|
||||
},
|
||||
{
|
||||
label: 'Algeria',
|
||||
value: 'DZ',
|
||||
},
|
||||
{
|
||||
label: 'American Samoa',
|
||||
value: 'AS',
|
||||
},
|
||||
{
|
||||
label: 'Andorra',
|
||||
value: 'AD',
|
||||
},
|
||||
{
|
||||
label: 'Angola',
|
||||
value: 'AO',
|
||||
},
|
||||
{
|
||||
label: 'Anguilla',
|
||||
value: 'AI',
|
||||
},
|
||||
{
|
||||
label: 'Antarctica',
|
||||
value: 'AQ',
|
||||
},
|
||||
{
|
||||
label: 'Antigua and Barbuda',
|
||||
value: 'AG',
|
||||
},
|
||||
{
|
||||
label: 'Argentina',
|
||||
value: 'AR',
|
||||
},
|
||||
{
|
||||
label: 'Armenia',
|
||||
value: 'AM',
|
||||
},
|
||||
{
|
||||
label: 'Aruba',
|
||||
value: 'AW',
|
||||
},
|
||||
{
|
||||
label: 'Australia',
|
||||
value: 'AU',
|
||||
},
|
||||
{
|
||||
label: 'Austria',
|
||||
value: 'AT',
|
||||
},
|
||||
{
|
||||
label: 'Azerbaijan',
|
||||
value: 'AZ',
|
||||
},
|
||||
{
|
||||
label: 'Bahamas',
|
||||
value: 'BS',
|
||||
},
|
||||
{
|
||||
label: 'Bahrain',
|
||||
value: 'BH',
|
||||
},
|
||||
{
|
||||
label: 'Bangladesh',
|
||||
value: 'BD',
|
||||
},
|
||||
{
|
||||
label: 'Barbados',
|
||||
value: 'BB',
|
||||
},
|
||||
{
|
||||
label: 'Belarus',
|
||||
value: 'BY',
|
||||
},
|
||||
{
|
||||
label: 'Belgium',
|
||||
value: 'BE',
|
||||
},
|
||||
{
|
||||
label: 'Belize',
|
||||
value: 'BZ',
|
||||
},
|
||||
{
|
||||
label: 'Benin',
|
||||
value: 'BJ',
|
||||
},
|
||||
{
|
||||
label: 'Bermuda',
|
||||
value: 'BM',
|
||||
},
|
||||
{
|
||||
label: 'Bhutan',
|
||||
value: 'BT',
|
||||
},
|
||||
{
|
||||
label: 'Bolivia',
|
||||
value: 'BO',
|
||||
},
|
||||
{
|
||||
label: 'Bosnia and Herzegovina',
|
||||
value: 'BA',
|
||||
},
|
||||
{
|
||||
label: 'Botswana',
|
||||
value: 'BW',
|
||||
},
|
||||
{
|
||||
label: 'Bouvet Island',
|
||||
value: 'BV',
|
||||
},
|
||||
{
|
||||
label: 'Brazil',
|
||||
value: 'BR',
|
||||
},
|
||||
{
|
||||
label: 'British Indian Ocean Territory',
|
||||
value: 'IO',
|
||||
},
|
||||
{
|
||||
label: 'Brunei Darussalam',
|
||||
value: 'BN',
|
||||
},
|
||||
{
|
||||
label: 'Bulgaria',
|
||||
value: 'BG',
|
||||
},
|
||||
{
|
||||
label: 'Burkina Faso',
|
||||
value: 'BF',
|
||||
},
|
||||
{
|
||||
label: 'Burundi',
|
||||
value: 'BI',
|
||||
},
|
||||
{
|
||||
label: 'Cambodia',
|
||||
value: 'KH',
|
||||
},
|
||||
{
|
||||
label: 'Cameroon',
|
||||
value: 'CM',
|
||||
},
|
||||
{
|
||||
label: 'Canada',
|
||||
value: 'CA',
|
||||
},
|
||||
{
|
||||
label: 'Cape Verde',
|
||||
value: 'CV',
|
||||
},
|
||||
{
|
||||
label: 'Cayman Islands',
|
||||
value: 'KY',
|
||||
},
|
||||
{
|
||||
label: 'Central African Republic',
|
||||
value: 'CF',
|
||||
},
|
||||
{
|
||||
label: 'Chad',
|
||||
value: 'TD',
|
||||
},
|
||||
{
|
||||
label: 'Chile',
|
||||
value: 'CL',
|
||||
},
|
||||
{
|
||||
label: 'China',
|
||||
value: 'CN',
|
||||
},
|
||||
{
|
||||
label: 'Christmas Island',
|
||||
value: 'CX',
|
||||
},
|
||||
{
|
||||
label: 'Cocos (Keeling) Islands',
|
||||
value: 'CC',
|
||||
},
|
||||
{
|
||||
label: 'Colombia',
|
||||
value: 'CO',
|
||||
},
|
||||
{
|
||||
label: 'Comoros',
|
||||
value: 'KM',
|
||||
},
|
||||
{
|
||||
label: 'Congo',
|
||||
value: 'CG',
|
||||
},
|
||||
{
|
||||
label: 'Congo, The Democratic Republic of the',
|
||||
value: 'CD',
|
||||
},
|
||||
{
|
||||
label: 'Cook Islands',
|
||||
value: 'CK',
|
||||
},
|
||||
{
|
||||
label: 'Costa Rica',
|
||||
value: 'CR',
|
||||
},
|
||||
{
|
||||
label: "Cote D'Ivoire",
|
||||
value: 'CI',
|
||||
},
|
||||
{
|
||||
label: 'Croatia',
|
||||
value: 'HR',
|
||||
},
|
||||
{
|
||||
label: 'Cuba',
|
||||
value: 'CU',
|
||||
},
|
||||
{
|
||||
label: 'Cyprus',
|
||||
value: 'CY',
|
||||
},
|
||||
{
|
||||
label: 'Czech Republic',
|
||||
value: 'CZ',
|
||||
},
|
||||
{
|
||||
label: 'Denmark',
|
||||
value: 'DK',
|
||||
},
|
||||
{
|
||||
label: 'Djibouti',
|
||||
value: 'DJ',
|
||||
},
|
||||
{
|
||||
label: 'Dominica',
|
||||
value: 'DM',
|
||||
},
|
||||
{
|
||||
label: 'Dominican Republic',
|
||||
value: 'DO',
|
||||
},
|
||||
{
|
||||
label: 'Ecuador',
|
||||
value: 'EC',
|
||||
},
|
||||
{
|
||||
label: 'Egypt',
|
||||
value: 'EG',
|
||||
},
|
||||
{
|
||||
label: 'El Salvador',
|
||||
value: 'SV',
|
||||
},
|
||||
{
|
||||
label: 'Equatorial Guinea',
|
||||
value: 'GQ',
|
||||
},
|
||||
{
|
||||
label: 'Eritrea',
|
||||
value: 'ER',
|
||||
},
|
||||
{
|
||||
label: 'Estonia',
|
||||
value: 'EE',
|
||||
},
|
||||
{
|
||||
label: 'Ethiopia',
|
||||
value: 'ET',
|
||||
},
|
||||
{
|
||||
label: 'Falkland Islands (Malvinas)',
|
||||
value: 'FK',
|
||||
},
|
||||
{
|
||||
label: 'Faroe Islands',
|
||||
value: 'FO',
|
||||
},
|
||||
{
|
||||
label: 'Fiji',
|
||||
value: 'FJ',
|
||||
},
|
||||
{
|
||||
label: 'Finland',
|
||||
value: 'FI',
|
||||
},
|
||||
{
|
||||
label: 'France',
|
||||
value: 'FR',
|
||||
},
|
||||
{
|
||||
label: 'French Guiana',
|
||||
value: 'GF',
|
||||
},
|
||||
{
|
||||
label: 'French Polynesia',
|
||||
value: 'PF',
|
||||
},
|
||||
{
|
||||
label: 'French Southern Territories',
|
||||
value: 'TF',
|
||||
},
|
||||
{
|
||||
label: 'Gabon',
|
||||
value: 'GA',
|
||||
},
|
||||
{
|
||||
label: 'Gambia',
|
||||
value: 'GM',
|
||||
},
|
||||
{
|
||||
label: 'Georgia',
|
||||
value: 'GE',
|
||||
},
|
||||
{
|
||||
label: 'Germany',
|
||||
value: 'DE',
|
||||
},
|
||||
{
|
||||
label: 'Ghana',
|
||||
value: 'GH',
|
||||
},
|
||||
{
|
||||
label: 'Gibraltar',
|
||||
value: 'GI',
|
||||
},
|
||||
{
|
||||
label: 'Greece',
|
||||
value: 'GR',
|
||||
},
|
||||
{
|
||||
label: 'Greenland',
|
||||
value: 'GL',
|
||||
},
|
||||
{
|
||||
label: 'Grenada',
|
||||
value: 'GD',
|
||||
},
|
||||
{
|
||||
label: 'Guadeloupe',
|
||||
value: 'GP',
|
||||
},
|
||||
{
|
||||
label: 'Guam',
|
||||
value: 'GU',
|
||||
},
|
||||
{
|
||||
label: 'Guatemala',
|
||||
value: 'GT',
|
||||
},
|
||||
{
|
||||
label: 'Guernsey',
|
||||
value: 'GG',
|
||||
},
|
||||
{
|
||||
label: 'Guinea',
|
||||
value: 'GN',
|
||||
},
|
||||
{
|
||||
label: 'Guinea-Bissau',
|
||||
value: 'GW',
|
||||
},
|
||||
{
|
||||
label: 'Guyana',
|
||||
value: 'GY',
|
||||
},
|
||||
{
|
||||
label: 'Haiti',
|
||||
value: 'HT',
|
||||
},
|
||||
{
|
||||
label: 'Heard Island and Mcdonald Islands',
|
||||
value: 'HM',
|
||||
},
|
||||
{
|
||||
label: 'Holy See (Vatican City State)',
|
||||
value: 'VA',
|
||||
},
|
||||
{
|
||||
label: 'Honduras',
|
||||
value: 'HN',
|
||||
},
|
||||
{
|
||||
label: 'Hong Kong',
|
||||
value: 'HK',
|
||||
},
|
||||
{
|
||||
label: 'Hungary',
|
||||
value: 'HU',
|
||||
},
|
||||
{
|
||||
label: 'Iceland',
|
||||
value: 'IS',
|
||||
},
|
||||
{
|
||||
label: 'India',
|
||||
value: 'IN',
|
||||
},
|
||||
{
|
||||
label: 'Indonesia',
|
||||
value: 'ID',
|
||||
},
|
||||
{
|
||||
label: 'Iran, Islamic Republic Of',
|
||||
value: 'IR',
|
||||
},
|
||||
{
|
||||
label: 'Iraq',
|
||||
value: 'IQ',
|
||||
},
|
||||
{
|
||||
label: 'Ireland',
|
||||
value: 'IE',
|
||||
},
|
||||
{
|
||||
label: 'Isle of Man',
|
||||
value: 'IM',
|
||||
},
|
||||
{
|
||||
label: 'Israel',
|
||||
value: 'IL',
|
||||
},
|
||||
{
|
||||
label: 'Italy',
|
||||
value: 'IT',
|
||||
},
|
||||
{
|
||||
label: 'Jamaica',
|
||||
value: 'JM',
|
||||
},
|
||||
{
|
||||
label: 'Japan',
|
||||
value: 'JP',
|
||||
},
|
||||
{
|
||||
label: 'Jersey',
|
||||
value: 'JE',
|
||||
},
|
||||
{
|
||||
label: 'Jordan',
|
||||
value: 'JO',
|
||||
},
|
||||
{
|
||||
label: 'Kazakhstan',
|
||||
value: 'KZ',
|
||||
},
|
||||
{
|
||||
label: 'Kenya',
|
||||
value: 'KE',
|
||||
},
|
||||
{
|
||||
label: 'Kiribati',
|
||||
value: 'KI',
|
||||
},
|
||||
{
|
||||
label: "Democratic People's Republic of Korea",
|
||||
value: 'KP',
|
||||
},
|
||||
{
|
||||
label: 'Korea, Republic of',
|
||||
value: 'KR',
|
||||
},
|
||||
{
|
||||
label: 'Kosovo',
|
||||
value: 'XK',
|
||||
},
|
||||
{
|
||||
label: 'Kuwait',
|
||||
value: 'KW',
|
||||
},
|
||||
{
|
||||
label: 'Kyrgyzstan',
|
||||
value: 'KG',
|
||||
},
|
||||
{
|
||||
label: "Lao People's Democratic Republic",
|
||||
value: 'LA',
|
||||
},
|
||||
{
|
||||
label: 'Latvia',
|
||||
value: 'LV',
|
||||
},
|
||||
{
|
||||
label: 'Lebanon',
|
||||
value: 'LB',
|
||||
},
|
||||
{
|
||||
label: 'Lesotho',
|
||||
value: 'LS',
|
||||
},
|
||||
{
|
||||
label: 'Liberia',
|
||||
value: 'LR',
|
||||
},
|
||||
{
|
||||
label: 'Libyan Arab Jamahiriya',
|
||||
value: 'LY',
|
||||
},
|
||||
{
|
||||
label: 'Liechtenstein',
|
||||
value: 'LI',
|
||||
},
|
||||
{
|
||||
label: 'Lithuania',
|
||||
value: 'LT',
|
||||
},
|
||||
{
|
||||
label: 'Luxembourg',
|
||||
value: 'LU',
|
||||
},
|
||||
{
|
||||
label: 'Macao',
|
||||
value: 'MO',
|
||||
},
|
||||
{
|
||||
label: 'Macedonia, The Former Yugoslav Republic of',
|
||||
value: 'MK',
|
||||
},
|
||||
{
|
||||
label: 'Madagascar',
|
||||
value: 'MG',
|
||||
},
|
||||
{
|
||||
label: 'Malawi',
|
||||
value: 'MW',
|
||||
},
|
||||
{
|
||||
label: 'Malaysia',
|
||||
value: 'MY',
|
||||
},
|
||||
{
|
||||
label: 'Maldives',
|
||||
value: 'MV',
|
||||
},
|
||||
{
|
||||
label: 'Mali',
|
||||
value: 'ML',
|
||||
},
|
||||
{
|
||||
label: 'Malta',
|
||||
value: 'MT',
|
||||
},
|
||||
{
|
||||
label: 'Marshall Islands',
|
||||
value: 'MH',
|
||||
},
|
||||
{
|
||||
label: 'Martinique',
|
||||
value: 'MQ',
|
||||
},
|
||||
{
|
||||
label: 'Mauritania',
|
||||
value: 'MR',
|
||||
},
|
||||
{
|
||||
label: 'Mauritius',
|
||||
value: 'MU',
|
||||
},
|
||||
{
|
||||
label: 'Mayotte',
|
||||
value: 'YT',
|
||||
},
|
||||
{
|
||||
label: 'Mexico',
|
||||
value: 'MX',
|
||||
},
|
||||
{
|
||||
label: 'Micronesia, Federated States of',
|
||||
value: 'FM',
|
||||
},
|
||||
{
|
||||
label: 'Moldova, Republic of',
|
||||
value: 'MD',
|
||||
},
|
||||
{
|
||||
label: 'Monaco',
|
||||
value: 'MC',
|
||||
},
|
||||
{
|
||||
label: 'Mongolia',
|
||||
value: 'MN',
|
||||
},
|
||||
{
|
||||
label: 'Montenegro',
|
||||
value: 'ME',
|
||||
},
|
||||
{
|
||||
label: 'Montserrat',
|
||||
value: 'MS',
|
||||
},
|
||||
{
|
||||
label: 'Morocco',
|
||||
value: 'MA',
|
||||
},
|
||||
{
|
||||
label: 'Mozambique',
|
||||
value: 'MZ',
|
||||
},
|
||||
{
|
||||
label: 'Myanmar',
|
||||
value: 'MM',
|
||||
},
|
||||
{
|
||||
label: 'Namibia',
|
||||
value: 'NA',
|
||||
},
|
||||
{
|
||||
label: 'Nauru',
|
||||
value: 'NR',
|
||||
},
|
||||
{
|
||||
label: 'Nepal',
|
||||
value: 'NP',
|
||||
},
|
||||
{
|
||||
label: 'Netherlands',
|
||||
value: 'NL',
|
||||
},
|
||||
{
|
||||
label: 'Netherlands Antilles',
|
||||
value: 'AN',
|
||||
},
|
||||
{
|
||||
label: 'New Caledonia',
|
||||
value: 'NC',
|
||||
},
|
||||
{
|
||||
label: 'New Zealand',
|
||||
value: 'NZ',
|
||||
},
|
||||
{
|
||||
label: 'Nicaragua',
|
||||
value: 'NI',
|
||||
},
|
||||
{
|
||||
label: 'Niger',
|
||||
value: 'NE',
|
||||
},
|
||||
{
|
||||
label: 'Nigeria',
|
||||
value: 'NG',
|
||||
},
|
||||
{
|
||||
label: 'Niue',
|
||||
value: 'NU',
|
||||
},
|
||||
{
|
||||
label: 'Norfolk Island',
|
||||
value: 'NF',
|
||||
},
|
||||
{
|
||||
label: 'Northern Mariana Islands',
|
||||
value: 'MP',
|
||||
},
|
||||
{
|
||||
label: 'Norway',
|
||||
value: 'NO',
|
||||
},
|
||||
{
|
||||
label: 'Oman',
|
||||
value: 'OM',
|
||||
},
|
||||
{
|
||||
label: 'Pakistan',
|
||||
value: 'PK',
|
||||
},
|
||||
{
|
||||
label: 'Palau',
|
||||
value: 'PW',
|
||||
},
|
||||
{
|
||||
label: 'Palestinian Territory, Occupied',
|
||||
value: 'PS',
|
||||
},
|
||||
{
|
||||
label: 'Panama',
|
||||
value: 'PA',
|
||||
},
|
||||
{
|
||||
label: 'Papua New Guinea',
|
||||
value: 'PG',
|
||||
},
|
||||
{
|
||||
label: 'Paraguay',
|
||||
value: 'PY',
|
||||
},
|
||||
{
|
||||
label: 'Peru',
|
||||
value: 'PE',
|
||||
},
|
||||
{
|
||||
label: 'Philippines',
|
||||
value: 'PH',
|
||||
},
|
||||
{
|
||||
label: 'Pitcairn',
|
||||
value: 'PN',
|
||||
},
|
||||
{
|
||||
label: 'Poland',
|
||||
value: 'PL',
|
||||
},
|
||||
{
|
||||
label: 'Portugal',
|
||||
value: 'PT',
|
||||
},
|
||||
{
|
||||
label: 'Puerto Rico',
|
||||
value: 'PR',
|
||||
},
|
||||
{
|
||||
label: 'Qatar',
|
||||
value: 'QA',
|
||||
},
|
||||
{
|
||||
label: 'Reunion',
|
||||
value: 'RE',
|
||||
},
|
||||
{
|
||||
label: 'Romania',
|
||||
value: 'RO',
|
||||
},
|
||||
{
|
||||
label: 'Russian Federation',
|
||||
value: 'RU',
|
||||
},
|
||||
{
|
||||
label: 'Rwanda',
|
||||
value: 'RW',
|
||||
},
|
||||
{
|
||||
label: 'Saint Helena',
|
||||
value: 'SH',
|
||||
},
|
||||
{
|
||||
label: 'Saint Kitts and Nevis',
|
||||
value: 'KN',
|
||||
},
|
||||
{
|
||||
label: 'Saint Lucia',
|
||||
value: 'LC',
|
||||
},
|
||||
{
|
||||
label: 'Saint Pierre and Miquelon',
|
||||
value: 'PM',
|
||||
},
|
||||
{
|
||||
label: 'Saint Vincent and the Grenadines',
|
||||
value: 'VC',
|
||||
},
|
||||
{
|
||||
label: 'Samoa',
|
||||
value: 'WS',
|
||||
},
|
||||
{
|
||||
label: 'San Marino',
|
||||
value: 'SM',
|
||||
},
|
||||
{
|
||||
label: 'Sao Tome and Principe',
|
||||
value: 'ST',
|
||||
},
|
||||
{
|
||||
label: 'Saudi Arabia',
|
||||
value: 'SA',
|
||||
},
|
||||
{
|
||||
label: 'Senegal',
|
||||
value: 'SN',
|
||||
},
|
||||
{
|
||||
label: 'Serbia',
|
||||
value: 'RS',
|
||||
},
|
||||
{
|
||||
label: 'Seychelles',
|
||||
value: 'SC',
|
||||
},
|
||||
{
|
||||
label: 'Sierra Leone',
|
||||
value: 'SL',
|
||||
},
|
||||
{
|
||||
label: 'Singapore',
|
||||
value: 'SG',
|
||||
},
|
||||
{
|
||||
label: 'Slovakia',
|
||||
value: 'SK',
|
||||
},
|
||||
{
|
||||
label: 'Slovenia',
|
||||
value: 'SI',
|
||||
},
|
||||
{
|
||||
label: 'Solomon Islands',
|
||||
value: 'SB',
|
||||
},
|
||||
{
|
||||
label: 'Somalia',
|
||||
value: 'SO',
|
||||
},
|
||||
{
|
||||
label: 'South Africa',
|
||||
value: 'ZA',
|
||||
},
|
||||
{
|
||||
label: 'South Georgia and the South Sandwich Islands',
|
||||
value: 'GS',
|
||||
},
|
||||
{
|
||||
label: 'Spain',
|
||||
value: 'ES',
|
||||
},
|
||||
{
|
||||
label: 'Sri Lanka',
|
||||
value: 'LK',
|
||||
},
|
||||
{
|
||||
label: 'Sudan',
|
||||
value: 'SD',
|
||||
},
|
||||
{
|
||||
label: 'Suriname',
|
||||
value: 'SR',
|
||||
},
|
||||
{
|
||||
label: 'Svalbard and Jan Mayen',
|
||||
value: 'SJ',
|
||||
},
|
||||
{
|
||||
label: 'Swaziland',
|
||||
value: 'SZ',
|
||||
},
|
||||
{
|
||||
label: 'Sweden',
|
||||
value: 'SE',
|
||||
},
|
||||
{
|
||||
label: 'Switzerland',
|
||||
value: 'CH',
|
||||
},
|
||||
{
|
||||
label: 'Syrian Arab Republic',
|
||||
value: 'SY',
|
||||
},
|
||||
{
|
||||
label: 'Taiwan',
|
||||
value: 'TW',
|
||||
},
|
||||
{
|
||||
label: 'Tajikistan',
|
||||
value: 'TJ',
|
||||
},
|
||||
{
|
||||
label: 'Tanzania, United Republic of',
|
||||
value: 'TZ',
|
||||
},
|
||||
{
|
||||
label: 'Thailand',
|
||||
value: 'TH',
|
||||
},
|
||||
{
|
||||
label: 'Timor-Leste',
|
||||
value: 'TL',
|
||||
},
|
||||
{
|
||||
label: 'Togo',
|
||||
value: 'TG',
|
||||
},
|
||||
{
|
||||
label: 'Tokelau',
|
||||
value: 'TK',
|
||||
},
|
||||
{
|
||||
label: 'Tonga',
|
||||
value: 'TO',
|
||||
},
|
||||
{
|
||||
label: 'Trinidad and Tobago',
|
||||
value: 'TT',
|
||||
},
|
||||
{
|
||||
label: 'Tunisia',
|
||||
value: 'TN',
|
||||
},
|
||||
{
|
||||
label: 'Turkey',
|
||||
value: 'TR',
|
||||
},
|
||||
{
|
||||
label: 'Turkmenistan',
|
||||
value: 'TM',
|
||||
},
|
||||
{
|
||||
label: 'Turks and Caicos Islands',
|
||||
value: 'TC',
|
||||
},
|
||||
{
|
||||
label: 'Tuvalu',
|
||||
value: 'TV',
|
||||
},
|
||||
{
|
||||
label: 'Uganda',
|
||||
value: 'UG',
|
||||
},
|
||||
{
|
||||
label: 'Ukraine',
|
||||
value: 'UA',
|
||||
},
|
||||
{
|
||||
label: 'United Arab Emirates',
|
||||
value: 'AE',
|
||||
},
|
||||
{
|
||||
label: 'United Kingdom',
|
||||
value: 'GB',
|
||||
},
|
||||
{
|
||||
label: 'United States',
|
||||
value: 'US',
|
||||
},
|
||||
{
|
||||
label: 'United States Minor Outlying Islands',
|
||||
value: 'UM',
|
||||
},
|
||||
{
|
||||
label: 'Uruguay',
|
||||
value: 'UY',
|
||||
},
|
||||
{
|
||||
label: 'Uzbekistan',
|
||||
value: 'UZ',
|
||||
},
|
||||
{
|
||||
label: 'Vanuatu',
|
||||
value: 'VU',
|
||||
},
|
||||
{
|
||||
label: 'Venezuela',
|
||||
value: 'VE',
|
||||
},
|
||||
{
|
||||
label: 'Viet Nam',
|
||||
value: 'VN',
|
||||
},
|
||||
{
|
||||
label: 'Virgin Islands, British',
|
||||
value: 'VG',
|
||||
},
|
||||
{
|
||||
label: 'Virgin Islands, U.S.',
|
||||
value: 'VI',
|
||||
},
|
||||
{
|
||||
label: 'Wallis and Futuna',
|
||||
value: 'WF',
|
||||
},
|
||||
{
|
||||
label: 'Western Sahara',
|
||||
value: 'EH',
|
||||
},
|
||||
{
|
||||
label: 'Yemen',
|
||||
value: 'YE',
|
||||
},
|
||||
{
|
||||
label: 'Zambia',
|
||||
value: 'ZM',
|
||||
},
|
||||
{
|
||||
label: 'Zimbabwe',
|
||||
value: 'ZW',
|
||||
},
|
||||
]
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { EmailField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
|
||||
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import React from 'react'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
|
||||
export const Email: React.FC<
|
||||
EmailField & {
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
register: UseFormRegister<FieldValues>
|
||||
}
|
||||
> = ({ name, defaultValue, errors, label, register, required, width }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<Label htmlFor={name}>
|
||||
{label}
|
||||
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Input
|
||||
defaultValue={defaultValue}
|
||||
id={name}
|
||||
type="text"
|
||||
{...register(name, { pattern: /^\S[^\s@]*@\S+$/, required })}
|
||||
/>
|
||||
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
export const Error = ({ name }: { name: string }) => {
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext()
|
||||
return (
|
||||
<div className="mt-2 text-red-500 text-sm">
|
||||
{(errors[name]?.message as string) || 'This field is required'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import RichText from '@/components/RichText'
|
||||
import React from 'react'
|
||||
|
||||
import { Width } from '../Width'
|
||||
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
export const Message: React.FC<{ message: SerializedEditorState }> = ({ message }) => {
|
||||
return (
|
||||
<Width className="my-12" width="100">
|
||||
{message && <RichText data={message} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { TextField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
|
||||
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import React from 'react'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
export const Number: React.FC<
|
||||
TextField & {
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
register: UseFormRegister<FieldValues>
|
||||
}
|
||||
> = ({ name, defaultValue, errors, label, register, required, width }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<Label htmlFor={name}>
|
||||
{label}
|
||||
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Input
|
||||
defaultValue={defaultValue}
|
||||
id={name}
|
||||
type="number"
|
||||
{...register(name, { required })}
|
||||
/>
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import type { SelectField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { Control, FieldErrorsImpl } from 'react-hook-form'
|
||||
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select as SelectComponent,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import React from 'react'
|
||||
import { Controller } from 'react-hook-form'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
|
||||
export const Select: React.FC<
|
||||
SelectField & {
|
||||
control: Control
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
}
|
||||
> = ({ name, control, errors, label, options, required, width, defaultValue }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<Label htmlFor={name}>
|
||||
{label}
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={defaultValue}
|
||||
name={name}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const controlledValue = options.find((t) => t.value === value)
|
||||
|
||||
return (
|
||||
<SelectComponent onValueChange={(val) => onChange(val)} value={controlledValue?.value}>
|
||||
<SelectTrigger className="w-full" id={name}>
|
||||
<SelectValue placeholder={label} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map(({ label, value }) => {
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</SelectContent>
|
||||
</SelectComponent>
|
||||
)
|
||||
}}
|
||||
rules={{ required }}
|
||||
/>
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import type { StateField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { Control, FieldErrorsImpl } from 'react-hook-form'
|
||||
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import React from 'react'
|
||||
import { Controller } from 'react-hook-form'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
import { stateOptions } from './options'
|
||||
|
||||
export const State: React.FC<
|
||||
StateField & {
|
||||
control: Control
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
}
|
||||
> = ({ name, control, errors, label, required, width }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<Label htmlFor={name}>
|
||||
{label}
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name={name}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const controlledValue = stateOptions.find((t) => t.value === value)
|
||||
|
||||
return (
|
||||
<Select onValueChange={(val) => onChange(val)} value={controlledValue?.value}>
|
||||
<SelectTrigger className="w-full" id={name}>
|
||||
<SelectValue placeholder={label} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{stateOptions.map(({ label, value }) => {
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}}
|
||||
rules={{ required }}
|
||||
/>
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
export const stateOptions = [
|
||||
{ label: 'Alabama', value: 'AL' },
|
||||
{ label: 'Alaska', value: 'AK' },
|
||||
{ label: 'Arizona', value: 'AZ' },
|
||||
{ label: 'Arkansas', value: 'AR' },
|
||||
{ label: 'California', value: 'CA' },
|
||||
{ label: 'Colorado', value: 'CO' },
|
||||
{ label: 'Connecticut', value: 'CT' },
|
||||
{ label: 'Delaware', value: 'DE' },
|
||||
{ label: 'Florida', value: 'FL' },
|
||||
{ label: 'Georgia', value: 'GA' },
|
||||
{ label: 'Hawaii', value: 'HI' },
|
||||
{ label: 'Idaho', value: 'ID' },
|
||||
{ label: 'Illinois', value: 'IL' },
|
||||
{ label: 'Indiana', value: 'IN' },
|
||||
{ label: 'Iowa', value: 'IA' },
|
||||
{ label: 'Kansas', value: 'KS' },
|
||||
{ label: 'Kentucky', value: 'KY' },
|
||||
{ label: 'Louisiana', value: 'LA' },
|
||||
{ label: 'Maine', value: 'ME' },
|
||||
{ label: 'Maryland', value: 'MD' },
|
||||
{ label: 'Massachusetts', value: 'MA' },
|
||||
{ label: 'Michigan', value: 'MI' },
|
||||
{ label: 'Minnesota', value: 'MN' },
|
||||
{ label: 'Mississippi', value: 'MS' },
|
||||
{ label: 'Missouri', value: 'MO' },
|
||||
{ label: 'Montana', value: 'MT' },
|
||||
{ label: 'Nebraska', value: 'NE' },
|
||||
{ label: 'Nevada', value: 'NV' },
|
||||
{ label: 'New Hampshire', value: 'NH' },
|
||||
{ label: 'New Jersey', value: 'NJ' },
|
||||
{ label: 'New Mexico', value: 'NM' },
|
||||
{ label: 'New York', value: 'NY' },
|
||||
{ label: 'North Carolina', value: 'NC' },
|
||||
{ label: 'North Dakota', value: 'ND' },
|
||||
{ label: 'Ohio', value: 'OH' },
|
||||
{ label: 'Oklahoma', value: 'OK' },
|
||||
{ label: 'Oregon', value: 'OR' },
|
||||
{ label: 'Pennsylvania', value: 'PA' },
|
||||
{ label: 'Rhode Island', value: 'RI' },
|
||||
{ label: 'South Carolina', value: 'SC' },
|
||||
{ label: 'South Dakota', value: 'SD' },
|
||||
{ label: 'Tennessee', value: 'TN' },
|
||||
{ label: 'Texas', value: 'TX' },
|
||||
{ label: 'Utah', value: 'UT' },
|
||||
{ label: 'Vermont', value: 'VT' },
|
||||
{ label: 'Virginia', value: 'VA' },
|
||||
{ label: 'Washington', value: 'WA' },
|
||||
{ label: 'West Virginia', value: 'WV' },
|
||||
{ label: 'Wisconsin', value: 'WI' },
|
||||
{ label: 'Wyoming', value: 'WY' },
|
||||
]
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { TextField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
|
||||
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import React from 'react'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
|
||||
export const Text: React.FC<
|
||||
TextField & {
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
register: UseFormRegister<FieldValues>
|
||||
}
|
||||
> = ({ name, defaultValue, errors, label, register, required, width }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<Label htmlFor={name}>
|
||||
{label}
|
||||
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
<Input defaultValue={defaultValue} id={name} type="text" {...register(name, { required })} />
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import type { TextField } from '@payloadcms/plugin-form-builder/types'
|
||||
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
|
||||
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea as TextAreaComponent } from '@/components/ui/textarea'
|
||||
import React from 'react'
|
||||
|
||||
import { Error } from '../Error'
|
||||
import { Width } from '../Width'
|
||||
|
||||
export const Textarea: React.FC<
|
||||
TextField & {
|
||||
errors: Partial<FieldErrorsImpl>
|
||||
register: UseFormRegister<FieldValues>
|
||||
rows?: number
|
||||
}
|
||||
> = ({ name, defaultValue, errors, label, register, required, rows = 3, width }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<Label htmlFor={name}>
|
||||
{label}
|
||||
|
||||
{required && (
|
||||
<span className="required">
|
||||
* <span className="sr-only">(required)</span>
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
|
||||
<TextAreaComponent
|
||||
defaultValue={defaultValue}
|
||||
id={name}
|
||||
rows={rows}
|
||||
{...register(name, { required: required })}
|
||||
/>
|
||||
|
||||
{errors[name] && <Error name={name} />}
|
||||
</Width>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export const Width: React.FC<{
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
width?: number | string
|
||||
}> = ({ children, className, width }) => {
|
||||
return (
|
||||
<div className={className} style={{ maxWidth: width ? `${width}%` : undefined }}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import type { Block } from 'payload'
|
||||
|
||||
import {
|
||||
FixedToolbarFeature,
|
||||
HeadingFeature,
|
||||
InlineToolbarFeature,
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const FormBlock: Block = {
|
||||
slug: 'formBlock',
|
||||
interfaceName: 'FormBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'form',
|
||||
type: 'relationship',
|
||||
relationTo: 'forms',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'enableIntro',
|
||||
type: 'checkbox',
|
||||
label: 'Enable Intro Content',
|
||||
},
|
||||
{
|
||||
name: 'introContent',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
condition: (_, { enableIntro }) => Boolean(enableIntro),
|
||||
},
|
||||
editor: lexicalEditor({
|
||||
features: ({ rootFeatures }) => {
|
||||
return [
|
||||
...rootFeatures,
|
||||
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
|
||||
FixedToolbarFeature(),
|
||||
InlineToolbarFeature(),
|
||||
]
|
||||
},
|
||||
}),
|
||||
label: 'Intro Content',
|
||||
},
|
||||
],
|
||||
graphQL: {
|
||||
singularName: 'FormBlock',
|
||||
},
|
||||
labels: {
|
||||
plural: 'Form Blocks',
|
||||
singular: 'Form Block',
|
||||
},
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Checkbox } from './Checkbox'
|
||||
import { Country } from './Country'
|
||||
import { Email } from './Email'
|
||||
import { Message } from './Message'
|
||||
import { Number } from './Number'
|
||||
import { Select } from './Select'
|
||||
import { State } from './State'
|
||||
import { Text } from './Text'
|
||||
import { Textarea } from './Textarea'
|
||||
|
||||
export const fields = {
|
||||
checkbox: Checkbox,
|
||||
country: Country,
|
||||
email: Email,
|
||||
message: Message,
|
||||
number: Number,
|
||||
select: Select,
|
||||
state: State,
|
||||
text: Text,
|
||||
textarea: Textarea,
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import RichText from '@/components/RichText'
|
||||
|
||||
import type { Post } from '@/payload-types'
|
||||
|
||||
import { Card } from '../../components/Card'
|
||||
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
export type RelatedPostsProps = {
|
||||
className?: string
|
||||
docs?: Post[]
|
||||
introContent?: SerializedEditorState
|
||||
}
|
||||
|
||||
export const RelatedPosts: React.FC<RelatedPostsProps> = (props) => {
|
||||
const { className, docs, introContent } = props
|
||||
|
||||
return (
|
||||
<div className={clsx('lg:container', className)}>
|
||||
{introContent && <RichText data={introContent} enableGutter={false} />}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8 items-stretch">
|
||||
{docs?.map((doc, index) => {
|
||||
if (typeof doc === 'string') return null
|
||||
|
||||
return <Card key={index} doc={doc} relationTo="posts" showCategories />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import type { Page } from '@/payload-types'
|
||||
|
||||
import { ArchiveBlock } from '@/blocks/ArchiveBlock/Component'
|
||||
import { CallToActionBlock } from '@/blocks/CallToAction/Component'
|
||||
import { ContentBlock } from '@/blocks/Content/Component'
|
||||
import { FormBlock } from '@/blocks/Form/Component'
|
||||
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
||||
|
||||
const blockComponents = {
|
||||
archive: ArchiveBlock,
|
||||
content: ContentBlock,
|
||||
cta: CallToActionBlock,
|
||||
formBlock: FormBlock,
|
||||
mediaBlock: MediaBlock,
|
||||
}
|
||||
|
||||
export const RenderBlocks: React.FC<{
|
||||
blocks: Page['layout'][0][]
|
||||
}> = (props) => {
|
||||
const { blocks } = props
|
||||
|
||||
const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0
|
||||
|
||||
if (hasBlocks) {
|
||||
return (
|
||||
<Fragment>
|
||||
{blocks.map((block, index) => {
|
||||
const { blockType } = block
|
||||
|
||||
if (blockType && blockType in blockComponents) {
|
||||
const Block = blockComponents[blockType]
|
||||
|
||||
if (Block) {
|
||||
return (
|
||||
<div className="my-16" key={index}>
|
||||
{/* @ts-expect-error there may be some mismatch between the expected types here */}
|
||||
<Block {...block} disableInnerContainer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CollectionConfig } from 'payload'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Authors: CollectionConfig = {
|
||||
slug: 'authors',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CollectionConfig } from 'payload'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Categories: CollectionConfig = {
|
||||
slug: 'categories',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload'
|
||||
|
||||
import { revalidatePath, revalidateTag } from 'next/cache'
|
||||
// Avoid importing next/cache at module scope so migrations can run in a plain Node env
|
||||
|
||||
import type { Customer } from '../../../payload-types'
|
||||
|
||||
export const revalidateCustomer: CollectionAfterChangeHook<Customer> = ({
|
||||
export const revalidateCustomer: CollectionAfterChangeHook<Customer> = async ({
|
||||
doc,
|
||||
previousDoc,
|
||||
req: { payload, context },
|
||||
@@ -14,9 +14,13 @@ export const revalidateCustomer: CollectionAfterChangeHook<Customer> = ({
|
||||
const path = `/customers/${doc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating event at path: ${path}`)
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('customers-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(path)
|
||||
revalidateTag('customers-sitemap')
|
||||
} catch {
|
||||
// no-op when not running inside Next runtime (e.g., during payload migrate)
|
||||
}
|
||||
}
|
||||
|
||||
// If the event was previously published, we need to revalidate the old path
|
||||
@@ -25,22 +29,27 @@ export const revalidateCustomer: CollectionAfterChangeHook<Customer> = ({
|
||||
|
||||
payload.logger.info(`Revalidating old event at path: ${oldPath}`)
|
||||
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('customers-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('customers-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Customer> = ({
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Customer> = async ({
|
||||
doc,
|
||||
req: { context },
|
||||
}) => {
|
||||
if (!context.disableRevalidate) {
|
||||
const path = `/customers/${doc?.slug}`
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('customers-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(path)
|
||||
revalidateTag('customers-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return doc
|
||||
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { isAnyone } from '@/access/isAnyone'
|
||||
import { isAuthenticated } from '@/access/isAuthenticated'
|
||||
import { isAnyone } from '../../access/isAnyone.ts'
|
||||
import { isAuthenticated } from '../../access/isAuthenticated.ts'
|
||||
|
||||
import { Banner } from '../../blocks/Banner/config'
|
||||
import { Code } from '../../blocks/Code/config'
|
||||
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
||||
import { Quote } from '../../blocks/Quote/config'
|
||||
import { YouTube } from '../../blocks/YouTube/config'
|
||||
import { revalidateDelete, revalidateCustomer } from './hooks/revalidateCustomer'
|
||||
import { Banner } from '../../blocks/Banner/config.ts'
|
||||
import { Code } from '../../blocks/Code/config.ts'
|
||||
import { MediaBlock } from '../../blocks/MediaBlock/config.ts'
|
||||
import { Quote } from '../../blocks/Quote/config.ts'
|
||||
import { YouTube } from '../../blocks/YouTube/config.ts'
|
||||
import { revalidateDelete, revalidateCustomer } from './hooks/revalidateCustomer.ts'
|
||||
|
||||
import {
|
||||
MetaDescriptionField,
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
OverviewField,
|
||||
PreviewField,
|
||||
} from '@payloadcms/plugin-seo/fields'
|
||||
import { slugField } from '@/fields/slug'
|
||||
import { slugField } from '../../fields/slug/index.ts'
|
||||
|
||||
const industryOptions = [
|
||||
{ label: 'Healthcare', value: 'healthcare' },
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload'
|
||||
|
||||
import { revalidatePath, revalidateTag } from 'next/cache'
|
||||
// Avoid importing next/cache at module scope so migrations can run in a plain Node env
|
||||
|
||||
import type { Event } from '../../../payload-types'
|
||||
|
||||
export const revalidateEvent: CollectionAfterChangeHook<Event> = ({
|
||||
export const revalidateEvent: CollectionAfterChangeHook<Event> = async ({
|
||||
doc,
|
||||
previousDoc,
|
||||
req: { payload, context },
|
||||
@@ -14,9 +14,11 @@ export const revalidateEvent: CollectionAfterChangeHook<Event> = ({
|
||||
const path = `/events/${doc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating event at path: ${path}`)
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('events-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(path)
|
||||
revalidateTag('events-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// If the event was previously published, we need to revalidate the old path
|
||||
@@ -25,19 +27,27 @@ export const revalidateEvent: CollectionAfterChangeHook<Event> = ({
|
||||
|
||||
payload.logger.info(`Revalidating old event at path: ${oldPath}`)
|
||||
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('events-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('events-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Event> = ({ doc, req: { context } }) => {
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Event> = async ({
|
||||
doc,
|
||||
req: { context },
|
||||
}) => {
|
||||
if (!context.disableRevalidate) {
|
||||
const path = `/events/${doc?.slug}`
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('events-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(path)
|
||||
revalidateTag('events-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return doc
|
||||
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { isAnyone } from '@/access/isAnyone'
|
||||
import { isAuthenticated } from '@/access/isAuthenticated'
|
||||
import { isAnyone } from '../../access/isAnyone.ts'
|
||||
import { isAuthenticated } from '../../access/isAuthenticated.ts'
|
||||
|
||||
import { Banner } from '@/blocks/Banner/config'
|
||||
import { Code } from '@/blocks/Code/config'
|
||||
import { MediaBlock } from '@/blocks/MediaBlock/config'
|
||||
import { Quote } from '@/blocks/Quote/config'
|
||||
import { YouTube } from '@/blocks/YouTube/config'
|
||||
import { revalidateDelete, revalidateEvent } from './hooks/revalidateEvent'
|
||||
import { Banner } from '../../blocks/Banner/config.ts'
|
||||
import { Code } from '../../blocks/Code/config.ts'
|
||||
import { MediaBlock } from '../../blocks/MediaBlock/config.ts'
|
||||
import { Quote } from '../../blocks/Quote/config.ts'
|
||||
import { YouTube } from '../../blocks/YouTube/config.ts'
|
||||
import { revalidateDelete, revalidateEvent } from './hooks/revalidateEvent.ts'
|
||||
|
||||
import {
|
||||
MetaDescriptionField,
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
OverviewField,
|
||||
PreviewField,
|
||||
} from '@payloadcms/plugin-seo/fields'
|
||||
import { slugField } from '@/fields/slug'
|
||||
import { timezoneOptions } from '../../utilities/timezones'
|
||||
import { slugField } from '../../fields/slug/index.ts'
|
||||
import { timezoneOptions } from '../../utilities/timezones.ts'
|
||||
|
||||
const eventTypeOptions = [
|
||||
{ label: 'Conference', value: 'conference' },
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { isAnyone } from '@/access/isAnyone'
|
||||
import { isAuthenticated } from '@/access/isAuthenticated'
|
||||
import { isAnyone } from '../access/isAnyone.ts'
|
||||
import { isAuthenticated } from '../access/isAuthenticated.ts'
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CollectionAfterReadHook } from 'payload'
|
||||
import { Author } from 'src/payload-types'
|
||||
import type { Author } from '../../../payload-types'
|
||||
|
||||
export const populateAuthors: CollectionAfterReadHook = async ({ doc, req: { payload } }) => {
|
||||
if (doc?.authors && doc?.authors?.length > 0) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload'
|
||||
|
||||
import { revalidatePath, revalidateTag } from 'next/cache'
|
||||
// Avoid importing next/cache at module scope so migrations can run in a plain Node env
|
||||
|
||||
import type { Post } from '../../../payload-types'
|
||||
|
||||
export const revalidatePost: CollectionAfterChangeHook<Post> = ({
|
||||
export const revalidatePost: CollectionAfterChangeHook<Post> = async ({
|
||||
doc,
|
||||
previousDoc,
|
||||
req: { payload, context },
|
||||
@@ -14,9 +14,11 @@ export const revalidatePost: CollectionAfterChangeHook<Post> = ({
|
||||
const path = `/posts/${doc.slug}`
|
||||
|
||||
payload.logger.info(`Revalidating post at path: ${path}`)
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('posts-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(path)
|
||||
revalidateTag('posts-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// If the post was previously published, we need to revalidate the old path
|
||||
@@ -25,19 +27,27 @@ export const revalidatePost: CollectionAfterChangeHook<Post> = ({
|
||||
|
||||
payload.logger.info(`Revalidating old post at path: ${oldPath}`)
|
||||
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('posts-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(oldPath)
|
||||
revalidateTag('posts-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Post> = ({ doc, req: { context } }) => {
|
||||
export const revalidateDelete: CollectionAfterDeleteHook<Post> = async ({
|
||||
doc,
|
||||
req: { context },
|
||||
}) => {
|
||||
if (!context.disableRevalidate) {
|
||||
const path = `/posts/${doc?.slug}`
|
||||
|
||||
revalidatePath(path)
|
||||
revalidateTag('posts-sitemap')
|
||||
try {
|
||||
const { revalidatePath, revalidateTag } = await import('next/cache')
|
||||
revalidatePath(path)
|
||||
revalidateTag('posts-sitemap')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return doc
|
||||
|
||||
@@ -9,16 +9,16 @@ import {
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { isAnyone } from '@/access/isAnyone'
|
||||
import { isAuthenticated } from '@/access/isAuthenticated'
|
||||
import { isAnyone } from '../../access/isAnyone.ts'
|
||||
import { isAuthenticated } from '../../access/isAuthenticated.ts'
|
||||
|
||||
import { Banner } from '@/blocks/Banner/config'
|
||||
import { Code } from '@/blocks/Code/config'
|
||||
import { MediaBlock } from '@/blocks/MediaBlock/config'
|
||||
import { Quote } from '@/blocks/Quote/config'
|
||||
import { YouTube } from '@/blocks/YouTube/config'
|
||||
import { populateAuthors } from './hooks/populateAuthors'
|
||||
import { revalidateDelete, revalidatePost } from './hooks/revalidatePost'
|
||||
import { Banner } from '../../blocks/Banner/config.ts'
|
||||
import { Code } from '../../blocks/Code/config.ts'
|
||||
import { MediaBlock } from '../../blocks/MediaBlock/config.ts'
|
||||
import { Quote } from '../../blocks/Quote/config.ts'
|
||||
import { YouTube } from '../../blocks/YouTube/config.ts'
|
||||
import { populateAuthors } from './hooks/populateAuthors.ts'
|
||||
import { revalidateDelete, revalidatePost } from './hooks/revalidatePost.ts'
|
||||
|
||||
import {
|
||||
MetaDescriptionField,
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
OverviewField,
|
||||
PreviewField,
|
||||
} from '@payloadcms/plugin-seo/fields'
|
||||
import { slugField } from '@/fields/slug'
|
||||
import { slugField } from '../../fields/slug/index.ts'
|
||||
|
||||
const launchweekOptions = [
|
||||
{ label: '6', value: '6' },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CollectionConfig } from 'payload'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Tags: CollectionConfig = {
|
||||
slug: 'tags',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isAdminFieldLevel } from '@/access/isAdmin'
|
||||
import { isAdminOrSelf } from '@/access/isAdminOrSelf'
|
||||
import { isAdmin, isAdminFieldLevel } from '../access/isAdmin.ts'
|
||||
import { isAdminOrSelf } from '../access/isAdminOrSelf.ts'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
|
||||
@@ -35,7 +35,7 @@ export const Card: React.FC<{
|
||||
'border border-border rounded-lg overflow-hidden bg-card hover:cursor-pointer',
|
||||
className
|
||||
)}
|
||||
ref={card.ref}
|
||||
ref={card.ref as React.Ref<HTMLDivElement>}
|
||||
>
|
||||
<div className="relative w-full ">
|
||||
{!metaImage && <div className="">No image</div>}
|
||||
@@ -48,15 +48,15 @@ export const Card: React.FC<{
|
||||
<div>
|
||||
{categories?.map((category, index) => {
|
||||
if (typeof category === 'object') {
|
||||
const { title: titleFromCategory } = category
|
||||
const { name: nameFromCategory } = category
|
||||
|
||||
const categoryTitle = titleFromCategory || 'Untitled category'
|
||||
const categoryName = nameFromCategory || 'Untitled category'
|
||||
|
||||
const isLast = index === categories.length - 1
|
||||
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{categoryTitle}
|
||||
{categoryName}
|
||||
{!isLast && <Fragment>, </Fragment>}
|
||||
</Fragment>
|
||||
)
|
||||
@@ -71,7 +71,11 @@ export const Card: React.FC<{
|
||||
{titleToUse && (
|
||||
<div className="prose">
|
||||
<h3>
|
||||
<Link className="not-prose" href={href} ref={link.ref}>
|
||||
<Link
|
||||
className="not-prose"
|
||||
href={href}
|
||||
ref={link.ref as React.Ref<HTMLAnchorElement>}
|
||||
>
|
||||
{titleToUse}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { cn } from '@/utilities/ui'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
import type { Page, Post } from '@/payload-types'
|
||||
import type { Post } from '@/payload-types'
|
||||
|
||||
type CMSLinkType = {
|
||||
appearance?: 'inline' | ButtonProps['variant']
|
||||
@@ -12,8 +12,8 @@ type CMSLinkType = {
|
||||
label?: string | null
|
||||
newTab?: boolean | null
|
||||
reference?: {
|
||||
relationTo: 'pages' | 'posts'
|
||||
value: Page | Post | string | number
|
||||
relationTo: 'posts'
|
||||
value: Post | string | number
|
||||
} | null
|
||||
size?: ButtonProps['size'] | null
|
||||
type?: 'custom' | 'reference' | null
|
||||
@@ -35,9 +35,7 @@ export const CMSLink: React.FC<CMSLinkType> = (props) => {
|
||||
|
||||
const href =
|
||||
type === 'reference' && typeof reference?.value === 'object' && reference.value.slug
|
||||
? `${reference?.relationTo !== 'pages' ? `/${reference?.relationTo}` : ''}/${
|
||||
reference.value.slug
|
||||
}`
|
||||
? `/${reference.value.slug}`
|
||||
: url
|
||||
|
||||
if (!href) return null
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import type React from 'react'
|
||||
import type { Post } from '@/payload-types'
|
||||
|
||||
import { getCachedDocument } from '@/utilities/getDocument'
|
||||
import { getCachedRedirects } from '@/utilities/getRedirects'
|
||||
import { notFound, redirect } from 'next/navigation'
|
||||
|
||||
interface Props {
|
||||
disableNotFound?: boolean
|
||||
url: string
|
||||
}
|
||||
|
||||
/* This component helps us with SSR based dynamic redirects */
|
||||
export const PayloadRedirects: React.FC<Props> = async ({ disableNotFound, url }) => {
|
||||
const redirects = await getCachedRedirects()()
|
||||
|
||||
const redirectItem = redirects.find((redirect) => redirect.from === url)
|
||||
|
||||
if (redirectItem) {
|
||||
if (redirectItem.to?.url) {
|
||||
redirect(redirectItem.to.url)
|
||||
}
|
||||
|
||||
let redirectUrl: string
|
||||
|
||||
if (typeof redirectItem.to?.reference?.value === 'string') {
|
||||
const collection = redirectItem.to?.reference?.relationTo
|
||||
const id = redirectItem.to?.reference?.value
|
||||
|
||||
const document = (await getCachedDocument(collection, id)()) as Post
|
||||
redirectUrl = `${redirectItem.to?.reference?.relationTo !== 'pages' ? `/${redirectItem.to?.reference?.relationTo}` : ''}/${
|
||||
document?.slug
|
||||
}`
|
||||
} else {
|
||||
redirectUrl = `${redirectItem.to?.reference?.relationTo !== 'pages' ? `/${redirectItem.to?.reference?.relationTo}` : ''}/${
|
||||
typeof redirectItem.to?.reference?.value === 'object'
|
||||
? redirectItem.to?.reference?.value?.slug
|
||||
: ''
|
||||
}`
|
||||
}
|
||||
|
||||
if (redirectUrl) redirect(redirectUrl)
|
||||
}
|
||||
|
||||
if (disableNotFound) return null
|
||||
|
||||
notFound()
|
||||
}
|
||||
@@ -15,13 +15,11 @@ import { CodeBlock, CodeBlockProps } from '@/blocks/Code/Component'
|
||||
|
||||
import type {
|
||||
BannerBlock as BannerBlockProps,
|
||||
CallToActionBlock as CTABlockProps,
|
||||
MediaBlock as MediaBlockProps,
|
||||
QuoteBlock as QuoteBlockProps,
|
||||
YouTubeBlock as YouTubeBlockProps,
|
||||
} from '@/payload-types'
|
||||
import { BannerBlock } from '@/blocks/Banner/Component'
|
||||
import { CallToActionBlock } from '@/blocks/CallToAction/Component'
|
||||
import { QuoteBlock } from '@/blocks/Quote/Component'
|
||||
import { YouTubeBlock } from '@/blocks/YouTube/Component'
|
||||
import { cn } from '@/utilities/ui'
|
||||
@@ -29,12 +27,7 @@ import { cn } from '@/utilities/ui'
|
||||
type NodeTypes =
|
||||
| DefaultNodeTypes
|
||||
| SerializedBlockNode<
|
||||
| CTABlockProps
|
||||
| MediaBlockProps
|
||||
| BannerBlockProps
|
||||
| CodeBlockProps
|
||||
| QuoteBlockProps
|
||||
| YouTubeBlockProps
|
||||
MediaBlockProps | BannerBlockProps | CodeBlockProps | QuoteBlockProps | YouTubeBlockProps
|
||||
>
|
||||
|
||||
const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {
|
||||
@@ -62,7 +55,6 @@ const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters })
|
||||
/>
|
||||
),
|
||||
code: ({ node }) => <CodeBlock className="col-start-2" {...node.fields} />,
|
||||
cta: ({ node }) => <CallToActionBlock {...node.fields} />,
|
||||
quote: ({ node }) => <QuoteBlock className="col-start-2" {...node.fields} />,
|
||||
youtube: ({ node }) => <YouTubeBlock className="col-start-2" {...node.fields} />,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Field, GroupField } from 'payload'
|
||||
|
||||
import deepMerge from '@/utilities/deepMerge'
|
||||
import deepMerge from '../utilities/deepMerge.ts'
|
||||
|
||||
export type LinkAppearances = 'default' | 'outline'
|
||||
|
||||
@@ -75,7 +75,7 @@ export const link: LinkType = ({ appearances, disableLabel = false, overrides =
|
||||
condition: (_, siblingData) => siblingData?.type === 'reference',
|
||||
},
|
||||
label: 'Document to link to',
|
||||
relationTo: ['pages', 'posts'],
|
||||
relationTo: ['posts'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ArrayField, Field } from 'payload'
|
||||
|
||||
import type { LinkAppearances } from './link'
|
||||
import type { LinkAppearances } from './link.ts'
|
||||
|
||||
import deepMerge from '@/utilities/deepMerge'
|
||||
import { link } from './link'
|
||||
import deepMerge from '../utilities/deepMerge.ts'
|
||||
import { link } from './link.ts'
|
||||
|
||||
type LinkGroupType = (options?: {
|
||||
appearances?: LinkAppearances[] | false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CheckboxField, TextField } from 'payload'
|
||||
|
||||
import { formatSlugHook } from './formatSlug'
|
||||
import { formatSlugHook } from './formatSlug.ts'
|
||||
|
||||
type Overrides = {
|
||||
slugOverrides?: Partial<TextField>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+154
-221
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,15 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES ('cms', 'cms', false)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DELETE FROM storage.objects WHERE bucket_id = 'cms';
|
||||
DELETE FROM storage.buckets WHERE id = 'cms';
|
||||
`)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,21 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
CREATE TABLE "users_sessions" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"created_at" timestamp(3) with time zone,
|
||||
"expires_at" timestamp(3) with time zone NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "users_sessions" ADD CONSTRAINT "users_sessions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX "users_sessions_order_idx" ON "users_sessions" USING btree ("_order");
|
||||
CREATE INDEX "users_sessions_parent_id_idx" ON "users_sessions" USING btree ("_parent_id");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP TABLE "users_sessions" CASCADE;`)
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
import * as migration_20250529_103319 from './20250529_103319';
|
||||
import * as migration_20250619_155345 from './20250619_155345.ts';
|
||||
import * as migration_20250619_160549 from './20250619_160549.ts';
|
||||
import * as migration_20250808_145722_update_payload_migrations from './20250808_145722_update_payload_migrations.ts';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20250529_103319.up,
|
||||
down: migration_20250529_103319.down,
|
||||
name: '20250529_103319'
|
||||
up: migration_20250619_155345.up,
|
||||
down: migration_20250619_155345.down,
|
||||
name: '20250619_155345',
|
||||
},
|
||||
{
|
||||
up: migration_20250619_160549.up,
|
||||
down: migration_20250619_160549.down,
|
||||
name: '20250619_160549',
|
||||
},
|
||||
{
|
||||
up: migration_20250808_145722_update_payload_migrations.up,
|
||||
down: migration_20250808_145722_update_payload_migrations.down,
|
||||
name: '20250808_145722_update_payload_migrations'
|
||||
},
|
||||
];
|
||||
@@ -987,6 +987,13 @@ export interface User {
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
sessions?:
|
||||
| {
|
||||
id: string;
|
||||
createdAt?: string | null;
|
||||
expiresAt: string;
|
||||
}[]
|
||||
| null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
@@ -1454,6 +1461,13 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
hash?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
sessions?:
|
||||
| T
|
||||
| {
|
||||
id?: T;
|
||||
createdAt?: T;
|
||||
expiresAt?: T;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
||||
@@ -1,23 +1,78 @@
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
import path from 'path'
|
||||
import { buildConfig } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import sharp from 'sharp'
|
||||
import { plugins } from './plugins'
|
||||
import { defaultLexical } from '@/fields/defaultLexical'
|
||||
import { getServerSideURL } from './utilities/getURL'
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
|
||||
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
|
||||
import { seoPlugin } from '@payloadcms/plugin-seo'
|
||||
import { s3Storage } from '@payloadcms/storage-s3'
|
||||
import { buildConfig, type Plugin } from 'payload'
|
||||
import { defaultLexical } from './fields/defaultLexical.ts'
|
||||
import { getServerSideURL } from './utilities/getURL.ts'
|
||||
|
||||
import { Authors } from './collections/Authors'
|
||||
import { Categories } from './collections/Categories'
|
||||
import { Customers } from './collections/Customers'
|
||||
import { Events } from './collections/Events'
|
||||
import { Media } from './collections/Media'
|
||||
import { Posts } from './collections/Posts'
|
||||
import { Tags } from './collections/Tags'
|
||||
import { Users } from './collections/Users'
|
||||
import type { GenerateTitle, GenerateURL } from '@payloadcms/plugin-seo/types'
|
||||
import type { Customer, Event, Post } from './payload-types'
|
||||
|
||||
import { Authors } from './collections/Authors.ts'
|
||||
import { Categories } from './collections/Categories.ts'
|
||||
import { Customers } from './collections/Customers/index.ts'
|
||||
import { Events } from './collections/Events/index.ts'
|
||||
import { Media } from './collections/Media.ts'
|
||||
import { Posts } from './collections/Posts/index.ts'
|
||||
import { Tags } from './collections/Tags.ts'
|
||||
import { Users } from './collections/Users.ts'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
const siteName = 'Supabase'
|
||||
|
||||
const generateTitle: GenerateTitle<Post & Customer & Event> = ({ doc, collectionSlug }: any) => {
|
||||
switch (collectionSlug) {
|
||||
case 'customers':
|
||||
return `${doc.name} | ${siteName} Customer Stories`
|
||||
case 'events':
|
||||
return `${doc.title} | ${siteName} Events`
|
||||
case 'posts':
|
||||
return doc.title
|
||||
default:
|
||||
return `${doc.title} | ${siteName}`
|
||||
}
|
||||
}
|
||||
|
||||
const generateURL: GenerateURL<Post> = ({ doc }: any) => {
|
||||
const url = getServerSideURL()
|
||||
|
||||
return doc?.slug ? `${url}/${doc.slug}` : url
|
||||
}
|
||||
|
||||
const plugins: Plugin[] = [
|
||||
nestedDocsPlugin({
|
||||
collections: ['categories'],
|
||||
generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''),
|
||||
}),
|
||||
seoPlugin({
|
||||
generateTitle,
|
||||
generateURL,
|
||||
}),
|
||||
payloadCloudPlugin(),
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: {
|
||||
prefix: 'media',
|
||||
},
|
||||
},
|
||||
bucket: process.env.S3_BUCKET || '',
|
||||
config: {
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID || '',
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || '',
|
||||
},
|
||||
region: process.env.S3_REGION,
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
@@ -59,7 +114,7 @@ export default buildConfig({
|
||||
pool: {
|
||||
connectionString: process.env.DATABASE_URI || '',
|
||||
},
|
||||
schemaName: 'cms-payload',
|
||||
// schemaName: 'cms-payload',
|
||||
}),
|
||||
sharp,
|
||||
plugins,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
db: postgresAdapter({
|
||||
pool: {
|
||||
connectionString: process.env.DATABASE_URI || '',
|
||||
},
|
||||
}),
|
||||
})
|
||||
@@ -1,87 +0,0 @@
|
||||
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
|
||||
// import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'
|
||||
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
|
||||
import { seoPlugin } from '@payloadcms/plugin-seo'
|
||||
import { s3Storage } from '@payloadcms/storage-s3'
|
||||
import { Plugin } from 'payload'
|
||||
import { GenerateTitle, GenerateURL } from '@payloadcms/plugin-seo/types'
|
||||
// import { FixedToolbarFeature, HeadingFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { Customer, Event, Post } from '@/payload-types'
|
||||
import { getServerSideURL } from '@/utilities/getURL'
|
||||
|
||||
const siteName = 'Supabase'
|
||||
|
||||
const generateTitle: GenerateTitle<Post & Customer & Event> = ({ doc, collectionSlug }) => {
|
||||
switch (collectionSlug) {
|
||||
case 'customers':
|
||||
return `${doc.name} | ${siteName} Customer Stories`
|
||||
case 'events':
|
||||
return `${doc.title} | ${siteName} Events`
|
||||
case 'posts':
|
||||
return doc.title
|
||||
default:
|
||||
return `${doc.title} | ${siteName}`
|
||||
}
|
||||
}
|
||||
|
||||
const generateURL: GenerateURL<Post> = ({ doc }) => {
|
||||
const url = getServerSideURL()
|
||||
|
||||
return doc?.slug ? `${url}/${doc.slug}` : url
|
||||
}
|
||||
|
||||
export const plugins: Plugin[] = [
|
||||
nestedDocsPlugin({
|
||||
collections: ['categories'],
|
||||
generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''),
|
||||
}),
|
||||
seoPlugin({
|
||||
generateTitle,
|
||||
generateURL,
|
||||
}),
|
||||
// formBuilderPlugin({
|
||||
// fields: {
|
||||
// payment: false,
|
||||
// },
|
||||
// formOverrides: {
|
||||
// fields: ({ defaultFields }) => {
|
||||
// return defaultFields.map((field) => {
|
||||
// if ('name' in field && field.name === 'confirmationMessage') {
|
||||
// return {
|
||||
// ...field,
|
||||
// editor: lexicalEditor({
|
||||
// features: ({ rootFeatures }) => {
|
||||
// return [
|
||||
// ...rootFeatures,
|
||||
// FixedToolbarFeature(),
|
||||
// HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
|
||||
// ]
|
||||
// },
|
||||
// }),
|
||||
// }
|
||||
// }
|
||||
// return field
|
||||
// })
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
payloadCloudPlugin(),
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: {
|
||||
prefix: 'media',
|
||||
},
|
||||
},
|
||||
bucket: process.env.S3_BUCKET || '',
|
||||
config: {
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID || '',
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || '',
|
||||
},
|
||||
region: process.env.S3_REGION,
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
},
|
||||
}),
|
||||
]
|
||||
@@ -27,7 +27,11 @@ export const HeaderThemeProvider = ({ children }: { children: React.ReactNode })
|
||||
setThemeState(themeToSet)
|
||||
}, [])
|
||||
|
||||
return <HeaderThemeContext value={{ headerTheme, setHeaderTheme }}>{children}</HeaderThemeContext>
|
||||
return (
|
||||
<HeaderThemeContext.Provider value={{ headerTheme, setHeaderTheme }}>
|
||||
{children}
|
||||
</HeaderThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useHeaderTheme = (): ContextType => use(HeaderThemeContext)
|
||||
@@ -51,7 +51,7 @@ export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
setThemeState(themeToSet)
|
||||
}, [])
|
||||
|
||||
return <ThemeContext value={{ setTheme, theme }}>{children}</ThemeContext>
|
||||
return <ThemeContext.Provider value={{ setTheme, theme }}>{children}</ThemeContext.Provider>
|
||||
}
|
||||
|
||||
export const useTheme = (): ThemeContextType => use(ThemeContext)
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Theme } from './types'
|
||||
import type { Theme } from './types.ts'
|
||||
|
||||
export const themeLocalStorageKey = 'payload-theme'
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
'use client'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useDebounce } from '@/utilities/useDebounce'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export const Search: React.FC = () => {
|
||||
const [value, setValue] = useState('')
|
||||
const router = useRouter()
|
||||
|
||||
const debouncedValue = useDebounce(value)
|
||||
|
||||
useEffect(() => {
|
||||
router.push(`/search${debouncedValue ? `?q=${debouncedValue}` : ''}`)
|
||||
}, [debouncedValue, router])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<Label htmlFor="search" className="sr-only">
|
||||
Search
|
||||
</Label>
|
||||
<Input
|
||||
id="search"
|
||||
onChange={(event) => {
|
||||
setValue(event.target.value)
|
||||
}}
|
||||
placeholder="Search"
|
||||
/>
|
||||
<button type="submit" className="sr-only">
|
||||
submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { BeforeSync, DocToSync } from '@payloadcms/plugin-search/types'
|
||||
|
||||
export const beforeSyncWithSearch: BeforeSync = async ({ originalDoc, searchDoc }) => {
|
||||
const {
|
||||
doc: { relationTo: collection },
|
||||
} = searchDoc
|
||||
|
||||
const { slug, id, categories, title, meta } = originalDoc
|
||||
|
||||
const modifiedDoc: DocToSync = {
|
||||
...searchDoc,
|
||||
slug,
|
||||
meta: {
|
||||
...meta,
|
||||
title: meta?.title || title,
|
||||
image: meta?.image?.id || meta?.image,
|
||||
description: meta?.description,
|
||||
},
|
||||
categories: [],
|
||||
}
|
||||
|
||||
if (categories && Array.isArray(categories) && categories.length > 0) {
|
||||
// get full categories and keep a flattened copy of their most important properties
|
||||
try {
|
||||
const mappedCategories = categories.map((category) => {
|
||||
const { id, title } = category
|
||||
|
||||
return {
|
||||
relationTo: 'categories',
|
||||
id,
|
||||
title,
|
||||
}
|
||||
})
|
||||
|
||||
modifiedDoc.categories = mappedCategories
|
||||
} catch (_err) {
|
||||
console.error(
|
||||
`Failed. Category not found when syncing collection '${collection}' with id: '${id}' to search.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedDoc
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Field } from 'payload'
|
||||
|
||||
export const searchFields: Field[] = [
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
index: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'meta',
|
||||
label: 'Meta',
|
||||
type: 'group',
|
||||
index: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
label: 'Image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Categories',
|
||||
name: 'categories',
|
||||
type: 'array',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'relationTo',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Post } from '@/payload-types'
|
||||
|
||||
/**
|
||||
* Formats an array of populatedAuthors from Posts into a prettified string.
|
||||
* @param authors - The populatedAuthors array from a Post.
|
||||
* @returns A prettified string of authors.
|
||||
* @example
|
||||
*
|
||||
* [Author1, Author2] becomes 'Author1 and Author2'
|
||||
* [Author1, Author2, Author3] becomes 'Author1, Author2, and Author3'
|
||||
*
|
||||
*/
|
||||
export const formatAuthors = (
|
||||
authors: NonNullable<NonNullable<Post['populatedAuthors']>[number]>[]
|
||||
) => {
|
||||
// Ensure we don't have any authors without a name
|
||||
const authorNames = authors.map((author) => author.name).filter(Boolean)
|
||||
|
||||
if (authorNames.length === 0) return ''
|
||||
if (authorNames.length === 1) return authorNames[0]
|
||||
if (authorNames.length === 2) return `${authorNames[0]} and ${authorNames[1]}`
|
||||
|
||||
return `${authorNames.slice(0, -1).join(', ')} and ${authorNames[authorNames.length - 1]}`
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import type { Metadata } from 'next'
|
||||
|
||||
import type { Media, Post, Config } from '../payload-types'
|
||||
|
||||
import { mergeOpenGraph } from './mergeOpenGraph'
|
||||
import { getServerSideURL } from './getURL'
|
||||
import { mergeOpenGraph } from './mergeOpenGraph.ts'
|
||||
import { getServerSideURL } from './getURL.ts'
|
||||
|
||||
const getImageURL = (image?: Media | Config['db']['defaultIDType'] | null) => {
|
||||
const serverUrl = getServerSideURL()
|
||||
|
||||
@@ -2,7 +2,6 @@ import { PayloadRequest, CollectionSlug } from 'payload'
|
||||
|
||||
const collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {
|
||||
posts: '/blog',
|
||||
pages: '',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Config } from 'src/payload-types'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
// Do not import next/cache at module scope to keep migrate step Node-compatible
|
||||
|
||||
type Collection = keyof Config['collections']
|
||||
|
||||
@@ -25,7 +25,16 @@ async function getDocument(collection: Collection, slug: string, depth = 0) {
|
||||
/**
|
||||
* Returns a unstable_cache function mapped with the cache tag for the slug
|
||||
*/
|
||||
export const getCachedDocument = (collection: Collection, slug: string) =>
|
||||
unstable_cache(async () => getDocument(collection, slug), [collection, slug], {
|
||||
tags: [`${collection}_${slug}`],
|
||||
})
|
||||
export const getCachedDocument = (collection: Collection, slug: string) => {
|
||||
return async () => {
|
||||
try {
|
||||
const { unstable_cache } = await import('next/cache')
|
||||
const cached = unstable_cache(async () => getDocument(collection, slug), [collection, slug], {
|
||||
tags: [`${collection}_${slug}`],
|
||||
})
|
||||
return cached()
|
||||
} catch {
|
||||
return getDocument(collection, slug)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { Config } from 'src/payload-types'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
// Do not import next/cache at module scope to keep migrate step Node-compatible
|
||||
|
||||
type Global = keyof Config['globals']
|
||||
|
||||
@@ -20,7 +20,16 @@ async function getGlobal(slug: Global, depth = 0) {
|
||||
/**
|
||||
* Returns a unstable_cache function mapped with the cache tag for the slug
|
||||
*/
|
||||
export const getCachedGlobal = (slug: Global, depth = 0) =>
|
||||
unstable_cache(async () => getGlobal(slug, depth), [slug], {
|
||||
tags: [`global_${slug}`],
|
||||
})
|
||||
export const getCachedGlobal = (slug: Global, depth = 0) => {
|
||||
return async () => {
|
||||
try {
|
||||
const { unstable_cache } = await import('next/cache')
|
||||
const cached = unstable_cache(async () => getGlobal(slug, depth), [slug], {
|
||||
tags: [`global_${slug}`],
|
||||
})
|
||||
return cached()
|
||||
} catch {
|
||||
return getGlobal(slug, depth)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { cookies } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
import type { User } from '../payload-types'
|
||||
import { getClientSideURL } from './getURL'
|
||||
import { getClientSideURL } from './getURL.ts'
|
||||
|
||||
export const getMeUser = async (args?: {
|
||||
nullUserRedirect?: string
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import configPromise from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
export async function getRedirects(depth = 1) {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
const { docs: redirects } = await payload.find({
|
||||
collection: 'redirects',
|
||||
depth,
|
||||
limit: 0,
|
||||
pagination: false,
|
||||
})
|
||||
|
||||
return redirects
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unstable_cache function mapped with the cache tag for 'redirects'.
|
||||
*
|
||||
* Cache all redirects together to avoid multiple fetches.
|
||||
*/
|
||||
export const getCachedRedirects = () =>
|
||||
unstable_cache(async () => getRedirects(), ['redirects'], {
|
||||
tags: ['redirects'],
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import canUseDOM from './canUseDOM'
|
||||
import canUseDOM from './canUseDOM.ts'
|
||||
|
||||
export const getServerSideURL = () => {
|
||||
let url = process.env.NEXT_PUBLIC_SERVER_URL
|
||||
@@ -8,7 +8,7 @@ export const getServerSideURL = () => {
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
url = 'http://localhost:3000'
|
||||
url = 'http://localhost:3030'
|
||||
}
|
||||
|
||||
return url
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { getServerSideURL } from './getURL'
|
||||
import { getServerSideURL } from './getURL.ts'
|
||||
|
||||
const defaultOpenGraph: Metadata['openGraph'] = {
|
||||
type: 'website',
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# https://supabase.com/docs/guides/local-development/cli/config
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the
|
||||
# working directory name when running `supabase init`.
|
||||
project_id = "cms-payload"
|
||||
project_id = "cms"
|
||||
|
||||
[api]
|
||||
enabled = true
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
port = 34321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. `public` and `graphql_public` schemas are included by default.
|
||||
schemas = ["public", "graphql_public"]
|
||||
@@ -23,17 +23,17 @@ enabled = false
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
port = 34322
|
||||
# Port used by db diff command to initialize the shadow database.
|
||||
shadow_port = 54320
|
||||
shadow_port = 34320
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 15
|
||||
major_version = 17
|
||||
|
||||
[db.pooler]
|
||||
enabled = false
|
||||
# Port to use for the local connection pooler.
|
||||
port = 54329
|
||||
port = 34329
|
||||
# Specifies when a server connection can be reused by other clients.
|
||||
# Configure one of the supported pooler modes: `transaction`, `session`.
|
||||
pool_mode = "transaction"
|
||||
@@ -46,6 +46,8 @@ max_client_conn = 100
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[db.migrations]
|
||||
# If disabled, migrations will be skipped during a db push or reset.
|
||||
enabled = true
|
||||
# Specifies an ordered list of schema files that describe your database.
|
||||
# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
|
||||
schema_paths = []
|
||||
@@ -57,6 +59,16 @@ enabled = true
|
||||
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
|
||||
sql_paths = ["./seed.sql"]
|
||||
|
||||
[db.network_restrictions]
|
||||
# Enable management of network restrictions.
|
||||
enabled = false
|
||||
# List of IPv4 CIDR blocks allowed to connect to the database.
|
||||
# Defaults to allow all IPv4 connections. Set empty array to block all IPs.
|
||||
allowed_cidrs = ["0.0.0.0/0"]
|
||||
# List of IPv6 CIDR blocks allowed to connect to the database.
|
||||
# Defaults to allow all IPv6 connections. Set empty array to block all IPs.
|
||||
allowed_cidrs_v6 = ["::/0"]
|
||||
|
||||
[realtime]
|
||||
enabled = true
|
||||
# Bind realtime via either IPv4 or IPv6. (default: IPv4)
|
||||
@@ -67,7 +79,7 @@ enabled = true
|
||||
[studio]
|
||||
enabled = true
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
port = 34323
|
||||
# External URL of the API server that frontend connects to.
|
||||
api_url = "http://127.0.0.1"
|
||||
# OpenAI API Key to use for Supabase AI in the Supabase Studio.
|
||||
@@ -78,10 +90,10 @@ openai_api_key = "env(OPENAI_API_KEY)"
|
||||
[inbucket]
|
||||
enabled = true
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
port = 34324
|
||||
# Uncomment to expose additional ports for testing user applications that send emails.
|
||||
# smtp_port = 54325
|
||||
# pop3_port = 54326
|
||||
# smtp_port = 34325
|
||||
# pop3_port = 34326
|
||||
# admin_email = "admin@email.com"
|
||||
# sender_name = "Admin"
|
||||
|
||||
@@ -110,6 +122,8 @@ site_url = "http://127.0.0.1:3000"
|
||||
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
||||
jwt_expiry = 3600
|
||||
# Path to JWT signing key. DO NOT commit your signing keys file to git.
|
||||
# signing_keys_path = "./signing_keys.json"
|
||||
# If disabled, the refresh token will never expire.
|
||||
enable_refresh_token_rotation = true
|
||||
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
||||
@@ -140,6 +154,8 @@ token_refresh = 150
|
||||
sign_in_sign_ups = 30
|
||||
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
|
||||
token_verifications = 30
|
||||
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
|
||||
web3 = 30
|
||||
|
||||
# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
|
||||
# [auth.captcha]
|
||||
@@ -200,6 +216,11 @@ max_frequency = "5s"
|
||||
# Force log out if the user has been inactive longer than the specified duration.
|
||||
# inactivity_timeout = "8h"
|
||||
|
||||
# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object.
|
||||
# [auth.hook.before_user_created]
|
||||
# enabled = true
|
||||
# uri = "pg-functions://postgres/auth/before-user-created-hook"
|
||||
|
||||
# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
|
||||
# [auth.hook.custom_access_token]
|
||||
# enabled = true
|
||||
@@ -252,6 +273,11 @@ url = ""
|
||||
# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
|
||||
skip_nonce_check = false
|
||||
|
||||
# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard.
|
||||
# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting.
|
||||
[auth.web3.solana]
|
||||
enabled = false
|
||||
|
||||
# Use Firebase Auth as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.firebase]
|
||||
enabled = false
|
||||
@@ -276,7 +302,7 @@ enabled = false
|
||||
# domain = "example.clerk.accounts.dev"
|
||||
|
||||
[edge_runtime]
|
||||
enabled = true
|
||||
enabled = false
|
||||
# Configure one of the supported request policies: `oneshot`, `per_worker`.
|
||||
# Use `oneshot` for hot reload, or `per_worker` for load testing.
|
||||
policy = "oneshot"
|
||||
@@ -289,8 +315,8 @@ deno_version = 1
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[analytics]
|
||||
enabled = true
|
||||
port = 54327
|
||||
enabled = false
|
||||
port = 34327
|
||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||
backend = "postgres"
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Strictness */
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
"baseUrl": ".",
|
||||
"esModuleInterop": true,
|
||||
"target": "ES2022",
|
||||
@@ -18,6 +16,7 @@
|
||||
"jsx": "preserve",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"buildCommand": "pnpm vercel-build",
|
||||
"installCommand": "pnpm install"
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
"ui": "workspace:*",
|
||||
"ui-patterns": "workspace:*",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"zod": "^3.22.4"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shikijs/compat": "^1.1.7",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import './../../../packages/ui/build/css/source/global.css';
|
||||
@import './../../../packages/ui/build/css/themes/dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/classic-dark.css';
|
||||
@import './../../../packages/ui/build/css/themes/light.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--chart-1: 12 76% 61%;
|
||||
|
||||
@@ -4,7 +4,9 @@ import { type OpenAIClientInterface } from '~/lib/openAi'
|
||||
import { ApiError } from '../../utils'
|
||||
import { POST } from '../route'
|
||||
|
||||
const contentEmbeddingMock = vi.fn().mockImplementation(async () => Result.ok([0.1, 0.2, 0.3]))
|
||||
const contentEmbeddingMock = vi
|
||||
.fn()
|
||||
.mockImplementation(async () => Result.ok({ embedding: [0.1, 0.2, 0.3], tokenCount: 10 }))
|
||||
const openAIMock: OpenAIClientInterface = {
|
||||
createContentEmbedding: contentEmbeddingMock,
|
||||
}
|
||||
|
||||
@@ -64,6 +64,16 @@ export class NoDataError<Details extends ObjectOrNever = never> extends ApiError
|
||||
}
|
||||
}
|
||||
|
||||
export class FileNotFoundError<Details extends ObjectOrNever = never> extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
error: Error,
|
||||
public details?: Details
|
||||
) {
|
||||
super(`FileNotFound: ${message}`, { cause: error })
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiError<ErrorType = unknown, Details extends ObjectOrNever = never> extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
@@ -79,7 +89,7 @@ export class MultiError<ErrorType = unknown, Details extends ObjectOrNever = nev
|
||||
|
||||
appendError(message: string, error: ErrorType): this {
|
||||
this.message = `${this.message}\n\t${message}`
|
||||
;((this.cause ?? (this.cause = [])) as Array<ErrorType>).push(error)
|
||||
;((this.cause ??= []) as Array<ErrorType>).push(error)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,8 @@ Line numbers are 1-indexed and inclusive.
|
||||
lines={[[1, 3], [5, -1]]}
|
||||
{/* Optional, displays as a file name on the code block */}
|
||||
meta="name=display/path.js"
|
||||
{/* Optional, strips TypeScript types to produce JavaScript, which you also include with another <CodeSample /> */}
|
||||
convertToJs={true}
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -195,11 +197,31 @@ commit="1623aa9b95ec90e21c5bae5a0d50dcf272abe92f"
|
||||
path="/relative/path/from/root.js"
|
||||
lines={[[1, 3], [5, -1]]}
|
||||
meta="name=display/path.js"
|
||||
convertToJs={true}
|
||||
/>
|
||||
```
|
||||
|
||||
The repo must be public, the org must be on the allow list, and the commit must be an immutable SHA (not a mutable tag or branch name).
|
||||
|
||||
#### Converting TypeScript to JavaScript
|
||||
|
||||
You can automatically strip TypeScript types from code samples to produce JavaScript using the `convertToJs` option:
|
||||
|
||||
```mdx
|
||||
<$CodeSample
|
||||
path="/path/to/typescript-file.ts"
|
||||
lines={[[1, -1]]}
|
||||
convertToJs={true}
|
||||
/>
|
||||
```
|
||||
|
||||
This is useful when you want to show JavaScript examples from TypeScript source files. The conversion:
|
||||
|
||||
- Removes all TypeScript type annotations, interfaces, and type definitions
|
||||
- Converts the language identifier from `typescript` to `javascript` (or `tsx` to `jsx`)
|
||||
- Happens before any line selection or elision processing
|
||||
- Defaults to `false` to preserve TypeScript code when not specified
|
||||
|
||||
#### Multi-file code samples
|
||||
|
||||
Multi-file code samples use the `<$CodeTabs>` annotation:
|
||||
|
||||
@@ -2477,11 +2477,12 @@ export const deployment: NavMenuConstant = {
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/deployment/branching' },
|
||||
{ name: 'GitHub integration', url: '/guides/deployment/branching/github-integration' },
|
||||
{ name: 'Branching via GitHub', url: '/guides/deployment/branching/github-integration' },
|
||||
{
|
||||
name: 'Branching 2.0 (Alpha)',
|
||||
url: '/guides/deployment/branching/branching-2',
|
||||
name: 'Branching via dashboard',
|
||||
url: '/guides/deployment/branching/dashboard',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Working with branches',
|
||||
url: '/guides/deployment/branching/working-with-branches',
|
||||
|
||||
@@ -22,15 +22,13 @@ Now set up the database schema. You can use the "User Management Starter" quicks
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [SQL Editor](https://supabase.com/dashboard/project/_/sql) page in the Dashboard.
|
||||
2. Click **User Management Starter**.
|
||||
2. Click **User Management Starter** under the **Community > Quickstarts** tab.
|
||||
3. Click **Run**.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
You can pull the database schema down to your local project by running the `db pull` command. Read the [local development docs](/docs/guides/cli/local-development#link-your-project) for detailed instructions.
|
||||
|
||||
{/* TODO: discuss */}
|
||||
|
||||
```bash
|
||||
supabase link --project-ref <project-id>
|
||||
# You can get <project-id> from your project's dashboard URL: https://supabase.com/dashboard/project/<project-id>
|
||||
|
||||
@@ -2,4 +2,4 @@ This tutorial demonstrates how to build a basic user management app. The app aut
|
||||
|
||||
- [Supabase Database](/docs/guides/database) - a Postgres database for storing your user data and [Row Level Security](/docs/guides/auth#row-level-security) so data is protected and users can only access their own information.
|
||||
- [Supabase Auth](/docs/guides/auth) - allow users to sign up and log in.
|
||||
- [Supabase Storage](/docs/guides/storage) - users can upload a profile photo.
|
||||
- [Supabase Storage](/docs/guides/storage) - allow users to upload a profile photo.
|
||||
@@ -264,7 +264,8 @@ export async function updateSession(request: NextRequest) {
|
||||
if (
|
||||
!user &&
|
||||
!request.nextUrl.pathname.startsWith('/login') &&
|
||||
!request.nextUrl.pathname.startsWith('/auth')
|
||||
!request.nextUrl.pathname.startsWith('/auth') &&
|
||||
!request.nextUrl.pathname.startsWith('/error')
|
||||
) {
|
||||
// no user, potentially respond by redirecting the user to the login page
|
||||
const url = request.nextUrl.clone()
|
||||
|
||||
@@ -95,7 +95,7 @@ To use Google's pre-built signin buttons:
|
||||
|
||||
1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for.
|
||||
- For Android, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app.
|
||||
- You will have a different set of SHA-1 certificate fingerprint for testing locally and going to production. Make sure to add both to the Google Cloud Console. and add all of the Client IDs to Supabase dashboard.
|
||||
- You will have a different set of SHA-1 certificate fingerprints for testing locally and going to production. Make sure to add both to the Google Cloud Console, and add all of the Client IDs to the Supabase dashboard.
|
||||
- For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple App Store.
|
||||
2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service.
|
||||
3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_.
|
||||
@@ -397,7 +397,7 @@ const OneTapComponent = () => {
|
||||
const supabase = createClient()
|
||||
const router = useRouter()
|
||||
|
||||
const initializeGoogleOneTap = () => {
|
||||
const initializeGoogleOneTap = async () => {
|
||||
console.log('Initializing Google One Tap')
|
||||
const [nonce, hashedNonce] = await generateNonce()
|
||||
console.log('Nonce: ', nonce, hashedNonce)
|
||||
@@ -801,4 +801,5 @@ chrome.identity.launchWebAuthFlow(
|
||||
|
||||
By default, the Google consent screen shows the root domain of the callback URL, where Google will send the authentication response. With Supabase Auth, it is your Supabase project's domain `(https://<your-project-ref>.supabase.co)`.
|
||||
|
||||
If that is not preferable, you can use a [Custom Domain](https://supabase.com/docs/guides/platform/custom-domains) with your Supabase project. You can use it as your project's domain when creating the Supabase client in your application and initiating the authentication flow. It will then show up in the Google consent screen. If you want your app name and the logo on the consent screen, [you must submit your app to Google for verification](https://support.google.com/cloud/answer/10311615).
|
||||
You can change this domain in the settings for your Google app. Go to Google App Platform, click on Branding, and then set your application home page, privacy policy, and terms of use. You can also add your logo.
|
||||
Next, go to Audience and click publish. Google will need to verify the app, which usually takes around 24 hours.
|
||||
@@ -188,4 +188,4 @@ Do reach out to support if you have any concerns around this change.
|
||||
|
||||
- [Supabase - Get started for free](https://supabase.com)
|
||||
- [Supabase JS Client](https://github.com/supabase/supabase-js)
|
||||
- [LinkedIn Developer Dashboard](https://api.LinkedIn.com/apps)
|
||||
- [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps)
|
||||
+1
-1
@@ -26,7 +26,7 @@ This is made possible if the providers are using JWTs signed with asymmetric key
|
||||
|
||||
There are some limitations you should be aware of when using third-party authentication providers with Supabase.
|
||||
|
||||
1. The third-party provider must use asymmetrically signed JWTs (exposed as an OIDC Issuer Discovery URL by the third-party authentication provider). Using symmetrically signed JWTs is not possible at this time.
|
||||
1. The third-party provider must use asymmetrically signed JWTs (exposed as an OIDC Issuer Discovery URL by the third-party authentication provider). The signed JWTs must have a `kid` header parameter to identify which key must be used. Using symmetrically signed JWTs is not possible at this time.
|
||||
2. The JWT signing keys from the third-party provider are stored in the configuration of your project, and are checked for changes periodically. If you are rotating your keys (when supported) allow up to 30 minutes for the change to be picked up.
|
||||
3. It is not possible to disable Supabase Auth at this time.
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ This approach has several benefits:
|
||||
1. **On-demand:** analytical data is immediately available within your application with no additional infrastructure.
|
||||
1. **Always in sync:** since the data is queried directly from the remote server, it's always up-to-date.
|
||||
1. **Integrated:** large datasets are available within your application, and can be joined with your operational/transactional data.
|
||||
1. **Save on bandwidth:** only extract/load what you need.
|
||||
1. **Save on egress:** only extract/load what you need.
|
||||
|
||||
### Batch ETL with Wrappers
|
||||
|
||||
|
||||
Loaded 100 of 636 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user