Merge pull request #2289 from payloadcms/feat/form-builder-example
Feat/form builder example
This commit is contained in:
4
examples/form-builder/cms/.env.example
Normal file
4
examples/form-builder/cms/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3000
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:8000
|
||||
MONGODB_URI=mongodb://localhost/form-builder-example
|
||||
PAYLOAD_SECRET=ENTER-STRING-HERE
|
||||
4
examples/form-builder/cms/.eslintrc.js
Normal file
4
examples/form-builder/cms/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@payloadcms'],
|
||||
}
|
||||
5
examples/form-builder/cms/.gitignore
vendored
Normal file
5
examples/form-builder/cms/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
build
|
||||
dist
|
||||
node_modules
|
||||
package-lock.json
|
||||
.env
|
||||
1
examples/form-builder/cms/.npmrc
Normal file
1
examples/form-builder/cms/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
legacy-peer-deps=true
|
||||
8
examples/form-builder/cms/.prettierrc.js
Normal file
8
examples/form-builder/cms/.prettierrc.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
parser: "typescript",
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: "all",
|
||||
arrowParens: "avoid",
|
||||
};
|
||||
21
examples/form-builder/cms/.vscode/launch.json
vendored
Normal file
21
examples/form-builder/cms/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Form Builder Example CMS",
|
||||
"program": "${workspaceFolder}/src/server.ts",
|
||||
"preLaunchTask": "npm: build:server",
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "${workspaceFolder}/src/payload.config.ts"
|
||||
},
|
||||
// "outFiles": [
|
||||
// "${workspaceFolder}/dist/**/*.js"
|
||||
// ]
|
||||
},
|
||||
]
|
||||
}
|
||||
26
examples/form-builder/cms/README.md
Normal file
26
examples/form-builder/cms/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Form Builder Example CMS
|
||||
|
||||
This is an example repo for a CMS built with [Payload](https://payloadcms.com). This repo showcases how to utilize Payload's [Form Builder Plugin](https://github.com/payloadcms/plugin-form-builder).
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Clone this repo
|
||||
2. `cd` into the directory and run `yarn` or `npm install`
|
||||
3. Copy (`cp`) the `.env.example` file to an `.env` file
|
||||
4. Run `yarn dev` or `npm run dev` to start the development server
|
||||
5. Visit `http://localhost:8000/admin` to access the admin panel
|
||||
6. Login with the following credentials:
|
||||
- Email: `dev@payloadcms.com`
|
||||
- Password: `test`
|
||||
|
||||
## Frontend Development
|
||||
|
||||
There is a fully working Next.js app tailored specifically for this example which can be found [here](../nextjs). Follow the instructions there to get started. You can use this repo as a backend for the frontend and see for yourself how it all works together.
|
||||
|
||||
## Usage
|
||||
|
||||
Once booted up, a `Basic Form` will be immediately available to view on the home page along with a few other forms on their corresponding pages.
|
||||
|
||||
- These forms are seeded into the `forms` collection.
|
||||
- A few pages have also been seeded in on start up and utilize a layout building block called `Form Block` that is wired up to use the different forms from the `forms` collection.
|
||||
- This is done by adding a `relationship` field in the form-block config and setting its `relationTo` field to the `forms` collection.
|
||||
4
examples/form-builder/cms/nodemon.json
Normal file
4
examples/form-builder/cms/nodemon.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
}
|
||||
33
examples/form-builder/cms/package.json
Normal file
33
examples/form-builder/cms/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "form-builder-example-cms",
|
||||
"description": "The CMS that utilizes Payload's form builder plugin.",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/server.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
||||
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||
"build:server": "tsc",
|
||||
"build": "yarn copyfiles && yarn build:payload && yarn build:server",
|
||||
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
|
||||
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
|
||||
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faceless-ui/modal": "^2.0.1",
|
||||
"@payloadcms/plugin-form-builder": "^1.0.12",
|
||||
"@payloadcms/plugin-seo": "^1.0.8",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "^1.6.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.9",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"nodemon": "^2.0.6",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
11
examples/form-builder/cms/src/access/publishedOnly.ts
Normal file
11
examples/form-builder/cms/src/access/publishedOnly.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Access } from 'payload/config';
|
||||
|
||||
export const publishedOnly: Access = ({ req: { user } }) => {
|
||||
if (user) return true;
|
||||
|
||||
return {
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
};
|
||||
};
|
||||
33
examples/form-builder/cms/src/blocks/Form/index.ts
Normal file
33
examples/form-builder/cms/src/blocks/Form/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Block } from 'payload/types';
|
||||
import richText from '../../fields/richText';
|
||||
|
||||
export const FormBlock: Block = {
|
||||
slug: 'formBlock',
|
||||
labels: {
|
||||
singular: 'Form Block',
|
||||
plural: 'Form Blocks',
|
||||
},
|
||||
graphQL: {
|
||||
singularName: 'FormBlock',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'form',
|
||||
type: 'relationship',
|
||||
relationTo: 'forms',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'enableIntro',
|
||||
label: 'Enable Intro Content',
|
||||
type: 'checkbox',
|
||||
},
|
||||
richText({
|
||||
name: 'introContent',
|
||||
label: 'Intro Content',
|
||||
admin: {
|
||||
condition: (_, { enableIntro }) => Boolean(enableIntro),
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
44
examples/form-builder/cms/src/collections/Pages.ts
Normal file
44
examples/form-builder/cms/src/collections/Pages.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { publishedOnly } from '../access/publishedOnly';
|
||||
import { FormBlock } from '../blocks/Form';
|
||||
import { slugField } from '../fields/slug';
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', 'updatedAt'],
|
||||
},
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
access: {
|
||||
read: publishedOnly,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Content',
|
||||
fields: [
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'blocks',
|
||||
required: true,
|
||||
blocks: [
|
||||
FormBlock,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
slugField(),
|
||||
],
|
||||
};
|
||||
12
examples/form-builder/cms/src/collections/Users.ts
Normal file
12
examples/form-builder/cms/src/collections/Users.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
auth: true,
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
fields: [
|
||||
// Don't need any user fields here
|
||||
],
|
||||
};
|
||||
151
examples/form-builder/cms/src/fields/link.ts
Normal file
151
examples/form-builder/cms/src/fields/link.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Field } from 'payload/types';
|
||||
import deepMerge from '../utilities/deepMerge';
|
||||
|
||||
export const appearanceOptions = {
|
||||
primary: {
|
||||
label: 'Primary Button',
|
||||
value: 'primary',
|
||||
},
|
||||
secondary: {
|
||||
label: 'Secondary Button',
|
||||
value: 'secondary',
|
||||
},
|
||||
default: {
|
||||
label: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
export type LinkAppearances = 'primary' | 'secondary' | 'default'
|
||||
|
||||
type LinkType = (
|
||||
options?: {
|
||||
appearances?: LinkAppearances[] | false
|
||||
disableLabel?: boolean
|
||||
overrides?: Record<string, unknown>
|
||||
}
|
||||
) => Field;
|
||||
|
||||
const link: LinkType = ({
|
||||
appearances,
|
||||
disableLabel = false,
|
||||
overrides = {},
|
||||
} = {}) => {
|
||||
const linkResult: Field = {
|
||||
name: 'link',
|
||||
type: 'group',
|
||||
admin: {
|
||||
hideGutter: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'radio',
|
||||
options: [
|
||||
{
|
||||
label: 'Internal link',
|
||||
value: 'reference',
|
||||
},
|
||||
{
|
||||
label: 'Custom URL',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
defaultValue: 'reference',
|
||||
admin: {
|
||||
layout: 'horizontal',
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'newTab',
|
||||
label: 'Open in new tab',
|
||||
type: 'checkbox',
|
||||
admin: {
|
||||
width: '50%',
|
||||
style: {
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const linkTypes: Field[] = [
|
||||
{
|
||||
name: 'reference',
|
||||
label: 'Document to link to',
|
||||
type: 'relationship',
|
||||
relationTo: ['pages'],
|
||||
required: true,
|
||||
maxDepth: 1,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'reference',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
label: 'Custom URL',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'custom',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (!disableLabel) {
|
||||
linkTypes[0].admin.width = '50%';
|
||||
linkTypes[1].admin.width = '50%';
|
||||
|
||||
linkResult.fields.push({
|
||||
type: 'row',
|
||||
fields: [
|
||||
...linkTypes,
|
||||
{
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
linkResult.fields = [...linkResult.fields, ...linkTypes];
|
||||
}
|
||||
|
||||
|
||||
if (appearances !== false) {
|
||||
let appearanceOptionsToUse = [
|
||||
appearanceOptions.default,
|
||||
appearanceOptions.primary,
|
||||
appearanceOptions.secondary,
|
||||
];
|
||||
|
||||
if (appearances) {
|
||||
appearanceOptionsToUse = appearances.map((appearance) => appearanceOptions[appearance]);
|
||||
}
|
||||
|
||||
linkResult.fields.push({
|
||||
name: 'appearance',
|
||||
type: 'select',
|
||||
defaultValue: 'default',
|
||||
options: appearanceOptionsToUse,
|
||||
admin: {
|
||||
description: 'Choose how the link should be rendered.',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return deepMerge(linkResult, overrides);
|
||||
};
|
||||
|
||||
export default link;
|
||||
13
examples/form-builder/cms/src/fields/richText/elements.ts
Normal file
13
examples/form-builder/cms/src/fields/richText/elements.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { RichTextElement } from 'payload/dist/fields/config/types';
|
||||
|
||||
const elements: RichTextElement[] = [
|
||||
'blockquote',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'link',
|
||||
];
|
||||
|
||||
export default elements;
|
||||
94
examples/form-builder/cms/src/fields/richText/index.ts
Normal file
94
examples/form-builder/cms/src/fields/richText/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { RichTextElement, RichTextField, RichTextLeaf } from 'payload/dist/fields/config/types';
|
||||
import deepMerge from '../../utilities/deepMerge';
|
||||
import elements from './elements';
|
||||
import leaves from './leaves';
|
||||
import link from '../link';
|
||||
|
||||
type RichText = (
|
||||
overrides?: Partial<RichTextField>,
|
||||
additions?: {
|
||||
elements?: RichTextElement[]
|
||||
leaves?: RichTextLeaf[]
|
||||
}
|
||||
) => RichTextField
|
||||
|
||||
const richText: RichText = (
|
||||
overrides,
|
||||
additions = {
|
||||
elements: [],
|
||||
leaves: [],
|
||||
},
|
||||
) => deepMerge<RichTextField, Partial<RichTextField>>(
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
admin: {
|
||||
upload: {
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
type: 'richText',
|
||||
name: 'caption',
|
||||
label: 'Caption',
|
||||
admin: {
|
||||
elements: [
|
||||
...elements,
|
||||
],
|
||||
leaves: [
|
||||
...leaves,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'alignment',
|
||||
label: 'Alignment',
|
||||
options: [
|
||||
{
|
||||
label: 'Left',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Center',
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
label: 'Right',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'enableLink',
|
||||
type: 'checkbox',
|
||||
label: 'Enable Link',
|
||||
},
|
||||
link({
|
||||
appearances: false,
|
||||
disableLabel: true,
|
||||
overrides: {
|
||||
admin: {
|
||||
condition: (_, data) => Boolean(data?.enableLink),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: [
|
||||
...elements,
|
||||
...additions.elements || [],
|
||||
],
|
||||
leaves: [
|
||||
...leaves,
|
||||
...additions.leaves || [],
|
||||
],
|
||||
},
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
|
||||
export default richText;
|
||||
9
examples/form-builder/cms/src/fields/richText/leaves.ts
Normal file
9
examples/form-builder/cms/src/fields/richText/leaves.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { RichTextLeaf } from 'payload/dist/fields/config/types';
|
||||
|
||||
const defaultLeaves: RichTextLeaf[] = [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
];
|
||||
|
||||
export default defaultLeaves;
|
||||
23
examples/form-builder/cms/src/fields/slug.ts
Normal file
23
examples/form-builder/cms/src/fields/slug.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Field } from 'payload/types';
|
||||
import formatSlug from '../utilities/formatSlug';
|
||||
import deepMerge from '../utilities/deepMerge';
|
||||
|
||||
type Slug = (fieldToUse?: string, overrides?: Partial<Field>) => Field
|
||||
|
||||
export const slugField: Slug = (fieldToUse = 'title', overrides) => deepMerge<Field, Partial<Field>>(
|
||||
{
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
type: 'text',
|
||||
index: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
formatSlug(fieldToUse),
|
||||
],
|
||||
},
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
21
examples/form-builder/cms/src/globals/MainMenu.ts
Normal file
21
examples/form-builder/cms/src/globals/MainMenu.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { GlobalConfig } from 'payload/types';
|
||||
import link from '../fields/link';
|
||||
|
||||
export const MainMenu: GlobalConfig = {
|
||||
slug: 'main-menu',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'navItems',
|
||||
type: 'array',
|
||||
maxRows: 6,
|
||||
fields: [
|
||||
link({
|
||||
appearances: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
202
examples/form-builder/cms/src/payload-types.ts
Normal file
202
examples/form-builder/cms/src/payload-types.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload CMS.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "main-menu".
|
||||
*/
|
||||
export interface MainMenu {
|
||||
id: string;
|
||||
navItems: {
|
||||
link: {
|
||||
type?: 'reference' | 'custom';
|
||||
newTab?: boolean;
|
||||
reference: {
|
||||
value: string | Page;
|
||||
relationTo: 'pages';
|
||||
};
|
||||
url: string;
|
||||
label: string;
|
||||
};
|
||||
id?: string;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "pages".
|
||||
*/
|
||||
export interface Page {
|
||||
id: string;
|
||||
title: string;
|
||||
layout: {
|
||||
form: string | Form;
|
||||
enableIntro?: boolean;
|
||||
introContent: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'formBlock';
|
||||
}[];
|
||||
slug?: string;
|
||||
_status?: 'draft' | 'published';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "forms".
|
||||
*/
|
||||
export interface Form {
|
||||
id: string;
|
||||
title: string;
|
||||
fields: (
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: string;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'text';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: string;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'textarea';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: string;
|
||||
options: {
|
||||
label: string;
|
||||
value: string;
|
||||
id?: string;
|
||||
}[];
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'select';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'state';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'country';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
defaultValue?: number;
|
||||
required?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
defaultValue?: boolean;
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'checkbox';
|
||||
}
|
||||
| {
|
||||
message?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
id?: string;
|
||||
blockName?: string;
|
||||
blockType: 'message';
|
||||
}
|
||||
)[];
|
||||
submitButtonLabel?: string;
|
||||
confirmationType?: 'message' | 'redirect';
|
||||
confirmationMessage: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
redirect: {
|
||||
url: string;
|
||||
};
|
||||
emails: {
|
||||
emailTo: string;
|
||||
bcc?: string;
|
||||
replyTo?: string;
|
||||
replyToName?: string;
|
||||
emailFrom?: string;
|
||||
emailFromName?: string;
|
||||
subject: string;
|
||||
message?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
email?: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "form-submissions".
|
||||
*/
|
||||
export interface FormSubmission {
|
||||
id: string;
|
||||
form: string | Form;
|
||||
submissionData: {
|
||||
field: string;
|
||||
value: string;
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
31
examples/form-builder/cms/src/payload.config.ts
Normal file
31
examples/form-builder/cms/src/payload.config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
import FormBuilder from '@payloadcms/plugin-form-builder';
|
||||
import { Users } from './collections/Users';
|
||||
import { Pages } from './collections/Pages';
|
||||
import { MainMenu } from './globals/MainMenu';
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
Pages,
|
||||
Users,
|
||||
],
|
||||
globals: [
|
||||
MainMenu,
|
||||
],
|
||||
cors: [
|
||||
'http://localhost:3000',
|
||||
process.env.PAYLOAD_PUBLIC_SITE_URL,
|
||||
],
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||
},
|
||||
plugins: [
|
||||
FormBuilder({
|
||||
fields: {
|
||||
payment: false,
|
||||
},
|
||||
|
||||
}),
|
||||
],
|
||||
});
|
||||
23
examples/form-builder/cms/src/seed/advanced.ts
Normal file
23
examples/form-builder/cms/src/seed/advanced.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const advanced = {
|
||||
title: 'Advanced',
|
||||
layout: [
|
||||
{
|
||||
form: '{{ADVANCED_FORM_ID}}',
|
||||
enableIntro: true,
|
||||
introContent: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Advanced form:',
|
||||
},
|
||||
],
|
||||
type: 'h4',
|
||||
},
|
||||
],
|
||||
id: '63adc92568224b995af9df14',
|
||||
blockType: 'formBlock',
|
||||
},
|
||||
],
|
||||
slug: 'advanced',
|
||||
_status: 'published',
|
||||
};
|
||||
119
examples/form-builder/cms/src/seed/advancedForm.ts
Normal file
119
examples/form-builder/cms/src/seed/advancedForm.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
export const advancedForm = {
|
||||
id: '63c0835134d40cef85cc11a2',
|
||||
title: 'Advanced Form',
|
||||
fields: [
|
||||
{
|
||||
name: 'first-name',
|
||||
label: 'First Name',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63c081b169853127a8895312',
|
||||
blockName: 'first-name',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'last-name',
|
||||
label: 'Last Name',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63c081c669853127a8895313',
|
||||
blockName: 'last-name',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c081e869853127a8895314',
|
||||
blockName: 'email',
|
||||
blockType: 'email',
|
||||
},
|
||||
{
|
||||
name: 'street-address',
|
||||
label: 'Street Address',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c081fe69853127a8895315',
|
||||
blockName: 'street-address',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'street-address-two',
|
||||
label: 'Street Address Line 2',
|
||||
width: 100,
|
||||
id: '63c0823169853127a8895316',
|
||||
blockName: 'street-address-two',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
label: 'City',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63c0825a69853127a8895317',
|
||||
blockName: 'city',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63c0826569853127a8895318',
|
||||
blockName: 'state',
|
||||
blockType: 'state',
|
||||
},
|
||||
{
|
||||
name: 'zip-code',
|
||||
label: 'Postal / Zip Code',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63c082bb69853127a889531a',
|
||||
blockName: 'zip-code',
|
||||
blockType: 'number',
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
label: 'Country',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63c0829269853127a8895319',
|
||||
blockName: 'country',
|
||||
blockType: 'country',
|
||||
},
|
||||
],
|
||||
submitButtonLabel: 'Submit',
|
||||
confirmationType: 'message',
|
||||
confirmationMessage: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Your shipping information submission was successful.',
|
||||
},
|
||||
],
|
||||
type: 'h2',
|
||||
},
|
||||
],
|
||||
emails: [
|
||||
{
|
||||
emailTo: '{{email}}',
|
||||
emailFrom: 'dev@payloadcms.com',
|
||||
emailFromName: 'Payload Team',
|
||||
subject: "You've received a new message.",
|
||||
message: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Your example shipping information form submission was received successfully.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: '63c0831869853127a889531b',
|
||||
},
|
||||
],
|
||||
createdAt: '2023-01-12T22:01:53.023Z',
|
||||
updatedAt: '2023-01-12T22:01:53.023Z',
|
||||
redirect: {},
|
||||
};
|
||||
103
examples/form-builder/cms/src/seed/basicForm.ts
Normal file
103
examples/form-builder/cms/src/seed/basicForm.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
export const basicForm = {
|
||||
id: '63c0651b132c8e2783f8dcae',
|
||||
updatedAt: '2023-01-12T21:25:41.113Z',
|
||||
createdAt: '2022-12-28T20:48:53.181Z',
|
||||
title: 'Basic Form',
|
||||
fields: [
|
||||
{
|
||||
name: 'first-name',
|
||||
label: 'First name',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63adaaba5236fe69ca8973f8',
|
||||
blockName: 'first-name',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'last-name',
|
||||
label: 'Last name',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63bf4b1fd69cef4f34272f9a',
|
||||
blockName: 'last-name',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c0953adc1cd2c2f6c2d30b',
|
||||
blockName: 'email',
|
||||
blockType: 'email',
|
||||
},
|
||||
{
|
||||
name: 'coolest-project',
|
||||
label: "What's the coolest project you've built with Payload so far?",
|
||||
width: 100,
|
||||
required: false,
|
||||
id: '63adab96b65c28c168442316',
|
||||
blockName: 'coolest-project',
|
||||
blockType: 'textarea',
|
||||
},
|
||||
{
|
||||
message: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Have a great rest of your day!',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Sincerely, \n\nPayload Team.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: '63adb90db65c28c168442322',
|
||||
blockName: 'farewell',
|
||||
blockType: 'message',
|
||||
},
|
||||
],
|
||||
submitButtonLabel: 'Submit',
|
||||
confirmationType: 'message',
|
||||
confirmationMessage: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'The basic form has been submitted successfully.',
|
||||
},
|
||||
],
|
||||
type: 'h2',
|
||||
},
|
||||
],
|
||||
emails: [
|
||||
{
|
||||
emailTo: '{{email}}',
|
||||
emailFrom: 'dev@payloadcms.com',
|
||||
emailFromName: 'Payload',
|
||||
subject: "You've received a new message.",
|
||||
message: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Your basic form submission was successfully received.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: '63acab72433ea1822764c538',
|
||||
},
|
||||
],
|
||||
redirect: {},
|
||||
};
|
||||
23
examples/form-builder/cms/src/seed/contact.ts
Normal file
23
examples/form-builder/cms/src/seed/contact.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const contact = {
|
||||
title: 'Contact',
|
||||
layout: [
|
||||
{
|
||||
form: '{{CONTACT_FORM_ID}}',
|
||||
enableIntro: true,
|
||||
introContent: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Example contact form:',
|
||||
},
|
||||
],
|
||||
type: 'h4',
|
||||
},
|
||||
],
|
||||
id: '63adc92568224b995af9df13',
|
||||
blockType: 'formBlock',
|
||||
},
|
||||
],
|
||||
slug: 'contact',
|
||||
_status: 'published',
|
||||
};
|
||||
75
examples/form-builder/cms/src/seed/contactForm.ts
Normal file
75
examples/form-builder/cms/src/seed/contactForm.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
export const contactForm = {
|
||||
id: '63c07ffd4cb6b574b4977573',
|
||||
title: 'Contact Form',
|
||||
fields: [
|
||||
{
|
||||
name: 'full-name',
|
||||
label: 'Full Name',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c07f4e69853127a889530c',
|
||||
blockName: 'full-name',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
width: 50,
|
||||
required: true,
|
||||
id: '63c07f7069853127a889530d',
|
||||
blockName: 'email',
|
||||
blockType: 'email',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
label: 'Phone',
|
||||
width: 50,
|
||||
required: false,
|
||||
id: '63c07f8169853127a889530e',
|
||||
blockName: 'phone',
|
||||
blockType: 'number',
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
label: 'Message',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c07f9d69853127a8895310',
|
||||
blockName: 'message',
|
||||
blockType: 'textarea',
|
||||
},
|
||||
],
|
||||
submitButtonLabel: 'Submit',
|
||||
confirmationType: 'message',
|
||||
confirmationMessage: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'The contact form has been submitted successfully.',
|
||||
},
|
||||
],
|
||||
type: 'h2',
|
||||
},
|
||||
],
|
||||
emails: [
|
||||
{
|
||||
emailTo: '{{email}}',
|
||||
emailFrom: 'dev@payloadcms.com',
|
||||
emailFromName: 'Payload Team',
|
||||
subject: "You've received a new message.",
|
||||
message: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Your contact form submission was successfully received.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: '63c07fcb69853127a8895311',
|
||||
},
|
||||
],
|
||||
createdAt: '2023-01-12T21:47:41.374Z',
|
||||
updatedAt: '2023-01-12T21:47:41.374Z',
|
||||
redirect: {},
|
||||
};
|
||||
23
examples/form-builder/cms/src/seed/home.ts
Normal file
23
examples/form-builder/cms/src/seed/home.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const home = {
|
||||
title: 'Home',
|
||||
layout: [
|
||||
{
|
||||
form: '{{BASIC_FORM_ID}}',
|
||||
enableIntro: true,
|
||||
introContent: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Example basic form:',
|
||||
},
|
||||
],
|
||||
type: 'h4',
|
||||
},
|
||||
],
|
||||
id: '63adc92568224b995af9df12',
|
||||
blockType: 'formBlock',
|
||||
},
|
||||
],
|
||||
slug: 'home',
|
||||
_status: 'published',
|
||||
};
|
||||
121
examples/form-builder/cms/src/seed/index.ts
Normal file
121
examples/form-builder/cms/src/seed/index.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Payload } from 'payload';
|
||||
import { home } from './home';
|
||||
import { contact } from './contact';
|
||||
import { advanced } from './advanced';
|
||||
import { signUp } from './signUp';
|
||||
import { basicForm } from './basicForm';
|
||||
import { contactForm } from './contactForm';
|
||||
import { advancedForm } from './advancedForm';
|
||||
import { signUpForm } from './signUpForm';
|
||||
|
||||
export const seed = async (payload: Payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'dev@payloadcms.com',
|
||||
password: 'test',
|
||||
},
|
||||
});
|
||||
|
||||
const basicFormJSON = JSON.parse(JSON.stringify(basicForm));
|
||||
|
||||
const { id: basicFormID } = await payload.create({
|
||||
collection: 'forms',
|
||||
data: basicFormJSON,
|
||||
});
|
||||
|
||||
const contactFormJSON = JSON.parse(JSON.stringify(contactForm));
|
||||
|
||||
const { id: contactFormID } = await payload.create({
|
||||
collection: 'forms',
|
||||
data: contactFormJSON,
|
||||
});
|
||||
|
||||
const advancedFormJSON = JSON.parse(JSON.stringify(advancedForm));
|
||||
|
||||
const { id: advancedFormID } = await payload.create({
|
||||
collection: 'forms',
|
||||
data: advancedFormJSON,
|
||||
});
|
||||
|
||||
const signUpFormJSON = JSON.parse(JSON.stringify(signUpForm));
|
||||
|
||||
const { id: signUpFormID } = await payload.create({
|
||||
collection: 'forms',
|
||||
data: signUpFormJSON,
|
||||
});
|
||||
|
||||
const homePageJSON = JSON.parse(
|
||||
JSON.stringify(home).replace(/{{BASIC_FORM_ID}}/g, basicFormID),
|
||||
);
|
||||
|
||||
const contactPageJSON = JSON.parse(
|
||||
JSON.stringify(contact).replace(/{{CONTACT_FORM_ID}}/g, contactFormID),
|
||||
);
|
||||
|
||||
const advancedPageJSON = JSON.parse(
|
||||
JSON.stringify(advanced).replace(/{{ADVANCED_FORM_ID}}/g, advancedFormID),
|
||||
);
|
||||
|
||||
const signupPageJSON = JSON.parse(
|
||||
JSON.stringify(signUp).replace(/{{SIGNUP_FORM_ID}}/g, signUpFormID),
|
||||
);
|
||||
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: homePageJSON,
|
||||
});
|
||||
|
||||
const { id: contactPageID } = await payload.create({
|
||||
collection: 'pages',
|
||||
data: contactPageJSON,
|
||||
});
|
||||
|
||||
const { id: advancedPageID } = await payload.create({
|
||||
collection: 'pages',
|
||||
data: advancedPageJSON,
|
||||
});
|
||||
|
||||
const { id: signupPageID } = await payload.create({
|
||||
collection: 'pages',
|
||||
data: signupPageJSON,
|
||||
});
|
||||
|
||||
await payload.updateGlobal({
|
||||
slug: 'main-menu',
|
||||
data: {
|
||||
navItems: [
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'pages',
|
||||
value: contactPageID,
|
||||
},
|
||||
label: 'Contact Form',
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'pages',
|
||||
value: advancedPageID,
|
||||
},
|
||||
label: 'Advanced Form',
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'pages',
|
||||
value: signupPageID,
|
||||
},
|
||||
label: 'Signup Form',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
23
examples/form-builder/cms/src/seed/signUp.ts
Normal file
23
examples/form-builder/cms/src/seed/signUp.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const signUp = {
|
||||
title: 'Sign Up',
|
||||
layout: [
|
||||
{
|
||||
form: '{{SIGNUP_FORM_ID}}',
|
||||
enableIntro: true,
|
||||
introContent: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Example sign-up form:',
|
||||
},
|
||||
],
|
||||
type: 'h4',
|
||||
},
|
||||
],
|
||||
id: '63adc92568224b995af9df15',
|
||||
blockType: 'formBlock',
|
||||
},
|
||||
],
|
||||
slug: 'sign-up',
|
||||
_status: 'published',
|
||||
};
|
||||
87
examples/form-builder/cms/src/seed/signUpForm.ts
Normal file
87
examples/form-builder/cms/src/seed/signUpForm.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
export const signUpForm = {
|
||||
id: '63c086c36955e39c4208aa8f',
|
||||
title: 'Sign Up Form',
|
||||
fields: [
|
||||
{
|
||||
name: 'full-name',
|
||||
label: 'Full Name',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c085ae69853127a889531e',
|
||||
blockName: 'full-name',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c085df69853127a889531f',
|
||||
blockName: 'email',
|
||||
blockType: 'email',
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
width: 100,
|
||||
required: true,
|
||||
id: '63c0861869853127a8895321',
|
||||
blockName: 'password',
|
||||
blockType: 'text',
|
||||
},
|
||||
{
|
||||
message: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: '63c0865769853127a8895324',
|
||||
blockType: 'message',
|
||||
},
|
||||
{
|
||||
name: 'terms-and-conditions',
|
||||
label: 'I agree to the terms and conditions',
|
||||
required: true,
|
||||
id: '63c086a469853127a8895325',
|
||||
blockName: 'terms-and-conditions',
|
||||
blockType: 'checkbox',
|
||||
},
|
||||
],
|
||||
submitButtonLabel: 'Create Account',
|
||||
confirmationType: 'message',
|
||||
confirmationMessage: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Your sign up submission was successful.',
|
||||
},
|
||||
],
|
||||
type: 'h2',
|
||||
},
|
||||
],
|
||||
emails: [
|
||||
{
|
||||
emailTo: '{{email}}',
|
||||
emailFrom: 'dev@payloadcms.com',
|
||||
emailFromName: 'Payload Team',
|
||||
subject: "You've received a new message.",
|
||||
message: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Your sign up submissioin was received successfully.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: '63c0858f69853127a889531d',
|
||||
},
|
||||
],
|
||||
createdAt: '2023-01-12T22:16:35.480Z',
|
||||
updatedAt: '2023-01-12T22:16:35.480Z',
|
||||
redirect: {},
|
||||
};
|
||||
40
examples/form-builder/cms/src/server.ts
Normal file
40
examples/form-builder/cms/src/server.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import path from 'path';
|
||||
import express from 'express';
|
||||
import payload from 'payload';
|
||||
import { seed } from './seed';
|
||||
|
||||
// eslint-disable-next-line
|
||||
require('dotenv').config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
app.get('/', (_, res) => {
|
||||
res.redirect('/admin');
|
||||
});
|
||||
|
||||
const start = async () => {
|
||||
await payload.init({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
express: app,
|
||||
email: {
|
||||
fromName: 'Admin',
|
||||
fromAddress: 'admin@example.com',
|
||||
logMockCredentials: true,
|
||||
},
|
||||
onInit: () => {
|
||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.PAYLOAD_SEED === 'true') {
|
||||
payload.logger.info('---- SEEDING DATABASE ----');
|
||||
await seed(payload);
|
||||
}
|
||||
|
||||
app.listen(8000);
|
||||
};
|
||||
|
||||
start();
|
||||
32
examples/form-builder/cms/src/utilities/deepMerge.ts
Normal file
32
examples/form-builder/cms/src/utilities/deepMerge.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Simple object check.
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isObject(item: unknown): boolean {
|
||||
return (item && typeof item === 'object' && !Array.isArray(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep merge two objects.
|
||||
* @param target
|
||||
* @param ...sources
|
||||
*/
|
||||
export default function deepMerge<T, R>(target: T, source: R): T {
|
||||
const output = { ...target };
|
||||
if (isObject(target) && isObject(source)) {
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (isObject(source[key])) {
|
||||
if (!(key in target)) {
|
||||
Object.assign(output, { [key]: source[key] });
|
||||
} else {
|
||||
output[key] = deepMerge(target[key], source[key]);
|
||||
}
|
||||
} else {
|
||||
Object.assign(output, { [key]: source[key] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
21
examples/form-builder/cms/src/utilities/formatSlug.ts
Normal file
21
examples/form-builder/cms/src/utilities/formatSlug.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { FieldHook } from 'payload/types';
|
||||
|
||||
const format = (val: string): string => val.replace(/ /g, '-').replace(/[^\w-]+/g, '').toLowerCase();
|
||||
|
||||
const formatSlug = (fallback: string): FieldHook => ({ operation, value, originalDoc, data }) => {
|
||||
if (typeof value === 'string') {
|
||||
return format(value);
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
const fallbackData = (data && data[fallback]) || (originalDoc && originalDoc[fallback]);
|
||||
|
||||
if (fallbackData && typeof fallbackData === 'string') {
|
||||
return format(fallbackData);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export default formatSlug;
|
||||
30
examples/form-builder/cms/tsconfig.json
Normal file
30
examples/form-builder/cms/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"jsx": "react",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"build",
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
6012
examples/form-builder/cms/yarn.lock
Normal file
6012
examples/form-builder/cms/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
10
examples/form-builder/nextjs/.editorconfig
Normal file
10
examples/form-builder/nextjs/.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
max_line_length = null
|
||||
1
examples/form-builder/nextjs/.env.example
Normal file
1
examples/form-builder/nextjs/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
NEXT_PUBLIC_CMS_URL=http://localhost:8000
|
||||
4
examples/form-builder/nextjs/.eslintrc.js
Normal file
4
examples/form-builder/nextjs/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['plugin:@next/next/recommended', '@payloadcms'],
|
||||
}
|
||||
38
examples/form-builder/nextjs/.gitignore
vendored
Normal file
38
examples/form-builder/nextjs/.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.env
|
||||
8
examples/form-builder/nextjs/.prettierrc.js
Normal file
8
examples/form-builder/nextjs/.prettierrc.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
parser: "typescript",
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: "all",
|
||||
arrowParens: "avoid",
|
||||
};
|
||||
42
examples/form-builder/nextjs/README.md
Normal file
42
examples/form-builder/nextjs/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Form Builder Example Website
|
||||
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) that fetches data from [Payload](https://payloadcms.com).
|
||||
|
||||
This example repo was made explicitly to demonstrate the power and convenience of the [Form-Builder plugin](https://github.com/payloadcms/plugin-form-builder). Along with the `Form-Builder plugin`, this repo takes advantage of the popular [React Hooks Form](https://react-hook-form.com/) library for easy validation, giving users an easy way to build and manage forms.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Payload CMS
|
||||
|
||||
First you'll need a running CMS. If you have not done so already, open up the `cms` folder and follow the setup instructions. Take note of your server URL, you'll need this in the next step.
|
||||
|
||||
### Next.js App
|
||||
|
||||
1. Clone this repo
|
||||
2. `cd` into this directory and run `yarn` or `npm install`
|
||||
3. `cp .env.example .env` to copy the example environment variables
|
||||
4. `yarn dev` or `npm run dev` to start the server
|
||||
5. `open http://localhost:3000` to see the result
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
Once running, you will find a few seeded example forms on your local environment. Give them a try!
|
||||
|
||||
You can also start editing the pages by modifying the documents within your CMS.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Payload and Next.js, take a look at the following resources:
|
||||
|
||||
- [Payload Documentation](https://payloadcms.com/docs) - learn about Payload features and API.
|
||||
- [Form Builder Plugin Documentation](https://github.com/payloadcms/plugin-form-builder) - learn about the plugin's features.
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Payload GitHub repository](https://github.com/payloadcms/payload/) as well as [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Payload deployment documentation](https://payloadcms.com/docs/production/deployment) or the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
@@ -0,0 +1,69 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.checkbox {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
|
||||
:global {
|
||||
button {
|
||||
border: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.input {
|
||||
@include shared.formInput;
|
||||
padding: 0;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
width: var(--base);
|
||||
height: var(--base);
|
||||
margin-right: calc(var(--base) * 0.5);
|
||||
margin-bottom: 0;
|
||||
|
||||
svg {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.checked {
|
||||
:global {
|
||||
svg {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React, { useState } from 'react';
|
||||
import { CheckboxField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { UseFormRegister, FieldErrorsImpl, FieldValues } from 'react-hook-form';
|
||||
import { Check } from '../../../icons/Check';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Checkbox: React.FC<CheckboxField & {
|
||||
register: UseFormRegister<FieldValues & any>,
|
||||
setValue: any,
|
||||
getValues: any,
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, register, setValue, getValues, required: requiredFromProps, errors }) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
const isCheckboxChecked = getValues(name);
|
||||
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div
|
||||
className={[
|
||||
classes.checkbox,
|
||||
checked && classes.checked
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
<div className={classes.container}>
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register(name, { required: requiredFromProps })}
|
||||
checked={isCheckboxChecked}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setValue(name, !checked)
|
||||
setChecked(!checked)
|
||||
}}
|
||||
>
|
||||
<span className={classes.input}>
|
||||
<Check />
|
||||
</span>
|
||||
</button>
|
||||
<span className={classes.label}>{label}</span>
|
||||
</div>
|
||||
{requiredFromProps && errors[name] && checked === false && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.select {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.reactSelect {
|
||||
display: flex;
|
||||
|
||||
:global {
|
||||
div.rs__control {
|
||||
@include shared.formInput;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.rs__input-container {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.rs__value-container {
|
||||
padding: 0;
|
||||
> * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rs__single-value {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.rs__indicators {
|
||||
position: absolute;
|
||||
top: calc(var(--base) * 0.9);
|
||||
right: calc(var(--base) * 0.9);
|
||||
.arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__indicator {
|
||||
padding: 0px 4px;
|
||||
cursor: pointer;
|
||||
|
||||
svg path {
|
||||
fill: var(--color-dark-gray);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg path {
|
||||
fill: var(--color-dark-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rs__indicator-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rs__menu {
|
||||
color: var(--color-black);
|
||||
background-color: var(--color-white);
|
||||
z-index: 2;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 4px 11px hsl(0deg 0% 0% / 10%);
|
||||
}
|
||||
|
||||
.rs__menu-list {
|
||||
padding: calc(var(--base) / 4) 0;
|
||||
}
|
||||
|
||||
.rs__group-heading {
|
||||
margin-bottom: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
.rs__option {
|
||||
font-size: 1rem;
|
||||
padding: calc(var(--base) / 2) var(--base);
|
||||
|
||||
&--is-focused {
|
||||
background-color: var(--color-light-gray);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
&--is-selected {
|
||||
background-color: var(--color-light-gray);
|
||||
color: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__multi-value {
|
||||
padding: 0;
|
||||
background: var(--color-light-gray);
|
||||
}
|
||||
|
||||
.rs__multi-value__label {
|
||||
max-width: 150px;
|
||||
color: var(--color-black);
|
||||
padding: calc(var(--base) / 8) calc(var(--base) / 4);
|
||||
}
|
||||
|
||||
.rs__multi-value__remove {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-black);
|
||||
background: var(--color-light-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__clear-indicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import ReactSelect from 'react-select';
|
||||
import { CountryField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { Controller, Control, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { countryOptions } from './options';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Country: React.FC<CountryField & {
|
||||
control: Control<FieldValues, any>
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, control, required, errors }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.select}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{label}
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
rules={{ required }}
|
||||
name={name}
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ReactSelect
|
||||
instanceId={name}
|
||||
options={countryOptions}
|
||||
value={countryOptions.find(c => c.value === value)}
|
||||
onChange={(val) => onChange(val.value)}
|
||||
className={classes.reactSelect}
|
||||
classNamePrefix="rs"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{required && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,982 @@
|
||||
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'
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
.input {
|
||||
@include shared.formInput;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { EmailField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { UseFormRegister, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Email: React.FC<EmailField & {
|
||||
register: UseFormRegister<FieldValues & any>;
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, width, label, register, required: requiredFromProps, errors }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.wrap}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Email"
|
||||
className={classes.input}
|
||||
{...register(name, { required: requiredFromProps, pattern: /^\S+@\S+$/i })}
|
||||
/>
|
||||
{requiredFromProps && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
.error {
|
||||
margin-top: 5px;
|
||||
color: var(--color-red);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Error: React.FC = () => {
|
||||
return (
|
||||
<div className={classes.error}>
|
||||
This field is required
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@use '../.../../../../../css/queries.scss' as *;
|
||||
|
||||
.message {
|
||||
margin: var(--base) 0 var(--base) 0;
|
||||
|
||||
@include mid-break {
|
||||
margin: calc(var(--base) * 0.5) 0 calc(var(--base) * 0.5) 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { MessageField } from "payload-plugin-form-builder/dist/types";
|
||||
import RichText from '../../../RichText';
|
||||
import { Width } from "../Width";
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Message: React.FC<MessageField> = ({ message }) => {
|
||||
return (
|
||||
<Width width="100">
|
||||
<RichText
|
||||
content={message}
|
||||
className={classes.message}
|
||||
/>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
.input {
|
||||
@include shared.formInput;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { TextField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { UseFormRegister, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Number: React.FC<TextField & {
|
||||
register: UseFormRegister<FieldValues & any>;
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, register, required: requiredFromProps, errors }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.wrap}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className={classes.input}
|
||||
{...register(name, { required: requiredFromProps })}
|
||||
/>
|
||||
{requiredFromProps && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.select {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.reactSelect {
|
||||
display: flex;
|
||||
|
||||
:global {
|
||||
div.rs__control {
|
||||
@include shared.formInput;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.rs__input-container {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.rs__value-container {
|
||||
padding: 0;
|
||||
> * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rs__single-value {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.rs__indicators {
|
||||
position: absolute;
|
||||
top: calc(var(--base) * 0.9);
|
||||
right: calc(var(--base) * 0.9);
|
||||
.arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__indicator {
|
||||
padding: 0px 4px;
|
||||
cursor: pointer;
|
||||
|
||||
svg path {
|
||||
fill: var(--color-dark-gray);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg path {
|
||||
fill: var(--color-dark-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rs__indicator-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rs__menu {
|
||||
color: var(--color-black);
|
||||
background-color: var(--color-white);
|
||||
z-index: 2;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 4px 11px hsl(0deg 0% 0% / 10%);
|
||||
}
|
||||
|
||||
.rs__menu-list {
|
||||
padding: calc(var(--base) / 4) 0;
|
||||
}
|
||||
|
||||
.rs__group-heading {
|
||||
margin-bottom: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
.rs__option {
|
||||
font-size: 1rem;
|
||||
padding: calc(var(--base) / 2) var(--base);
|
||||
|
||||
&--is-focused {
|
||||
background-color: var(--color-light-gray);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
&--is-selected {
|
||||
background-color: var(--color-light-gray);
|
||||
color: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__multi-value {
|
||||
padding: 0;
|
||||
background: var(--color-light-gray);
|
||||
}
|
||||
|
||||
.rs__multi-value__label {
|
||||
max-width: 150px;
|
||||
color: var(--color-black);
|
||||
padding: calc(var(--base) / 8) calc(var(--base) / 4);
|
||||
}
|
||||
|
||||
.rs__multi-value__remove {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-black);
|
||||
background: var(--color-light-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__clear-indicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import ReactSelect from 'react-select';
|
||||
import { SelectField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { Controller, Control, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Select: React.FC<SelectField & {
|
||||
control: Control<FieldValues, any>
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, options, control, required, errors }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.select}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{label}
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
rules={{ required }}
|
||||
name={name}
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ReactSelect
|
||||
instanceId={name}
|
||||
options={options}
|
||||
value={options.find(s => s.value === value)}
|
||||
onChange={(val) => onChange(val.value)}
|
||||
className={classes.reactSelect}
|
||||
classNamePrefix="rs"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{required && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.select {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.reactSelect {
|
||||
display: flex;
|
||||
|
||||
:global {
|
||||
div.rs__control {
|
||||
@include shared.formInput;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.rs__input-container {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.rs__value-container {
|
||||
padding: 0;
|
||||
> * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rs__single-value {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.rs__indicators {
|
||||
position: absolute;
|
||||
top: calc(var(--base) * 0.9);
|
||||
right: calc(var(--base) * 0.9);
|
||||
.arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__indicator {
|
||||
padding: 0px 4px;
|
||||
cursor: pointer;
|
||||
|
||||
svg path {
|
||||
fill: var(--color-dark-gray);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg path {
|
||||
fill: var(--color-dark-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rs__indicator-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rs__menu {
|
||||
color: var(--color-black);
|
||||
background-color: var(--color-white);
|
||||
z-index: 2;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 4px 11px hsl(0deg 0% 0% / 10%);
|
||||
}
|
||||
|
||||
.rs__menu-list {
|
||||
padding: calc(var(--base) / 4) 0;
|
||||
}
|
||||
|
||||
.rs__group-heading {
|
||||
margin-bottom: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
.rs__option {
|
||||
font-size: 1rem;
|
||||
padding: calc(var(--base) / 2) var(--base);
|
||||
|
||||
&--is-focused {
|
||||
background-color: var(--color-light-gray);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
&--is-selected {
|
||||
background-color: var(--color-light-gray);
|
||||
color: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__multi-value {
|
||||
padding: 0;
|
||||
background: var(--color-light-gray);
|
||||
}
|
||||
|
||||
.rs__multi-value__label {
|
||||
max-width: 150px;
|
||||
color: var(--color-black);
|
||||
padding: calc(var(--base) / 8) calc(var(--base) / 4);
|
||||
}
|
||||
|
||||
.rs__multi-value__remove {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-black);
|
||||
background: var(--color-light-gray);
|
||||
}
|
||||
}
|
||||
|
||||
.rs__clear-indicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import ReactSelect from 'react-select';
|
||||
import { StateField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { Controller, Control, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { stateOptions } from './options';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const State: React.FC<StateField & {
|
||||
control: Control<FieldValues, any>
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, control, required, errors }) => {
|
||||
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.select}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{label}
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
rules={{ required }}
|
||||
name={name}
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ReactSelect
|
||||
instanceId={name}
|
||||
options={stateOptions}
|
||||
value={stateOptions.find(t => t.value === value)}
|
||||
onChange={(val) => onChange(val.value)}
|
||||
className={classes.reactSelect}
|
||||
classNamePrefix="rs"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{required && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
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' },
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
.input {
|
||||
@include shared.formInput;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { TextField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { UseFormRegister, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
|
||||
export const Text: React.FC<TextField & {
|
||||
register: UseFormRegister<FieldValues & any>;
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, register, required: requiredFromProps, errors }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.wrap}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className={classes.input}
|
||||
{...register(name, { required: requiredFromProps })}
|
||||
/>
|
||||
{requiredFromProps && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
@use "../shared.scss";
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
.textarea {
|
||||
@include shared.formInput;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { TextField } from 'payload-plugin-form-builder/dist/types';
|
||||
import { UseFormRegister, FieldValues, FieldErrorsImpl } from 'react-hook-form';
|
||||
import { Error } from '../Error';
|
||||
import { Width } from '../Width';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Textarea: React.FC<TextField & {
|
||||
register: UseFormRegister<FieldValues & any>;
|
||||
rows?: number;
|
||||
errors: Partial<FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>>
|
||||
}> = ({ name, label, width, rows = 3, register, required: requiredFromProps, errors }) => {
|
||||
return (
|
||||
<Width width={width}>
|
||||
<div className={classes.wrap}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{label}
|
||||
</label>
|
||||
<textarea
|
||||
rows={rows}
|
||||
className={classes.textarea}
|
||||
{...register(name, { required: requiredFromProps })}
|
||||
/>
|
||||
{requiredFromProps && errors[name] && <Error />}
|
||||
</div>
|
||||
</Width>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
@use '../../../../css/queries.scss' as *;
|
||||
|
||||
.width {
|
||||
padding-left: calc(var(--base) * 0.5);
|
||||
padding-right: calc(var(--base) * 0.5);
|
||||
|
||||
@include mid-break {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export const Width: React.FC<{
|
||||
width?: string
|
||||
children: React.ReactNode
|
||||
}> = ({ width, children }) => {
|
||||
return (
|
||||
<div
|
||||
className={classes.width}
|
||||
style={{ width: width ? `${width}%` : undefined }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { FormFieldBlock } from "payload-plugin-form-builder/dist/types"
|
||||
|
||||
export const buildInitialFormState = (fields: FormFieldBlock[]) => {
|
||||
return fields.reduce((initialSchema, field) => {
|
||||
if (field.blockType === 'checkbox') {
|
||||
return {
|
||||
...initialSchema,
|
||||
[field.blockName]: false,
|
||||
}
|
||||
}
|
||||
if (field.blockType === 'country') {
|
||||
return {
|
||||
...initialSchema,
|
||||
[field.blockName]: '',
|
||||
}
|
||||
}
|
||||
if (field.blockType === 'email') {
|
||||
return {
|
||||
...initialSchema,
|
||||
[field.blockName]: '',
|
||||
}
|
||||
}
|
||||
if (field.blockType === 'text') {
|
||||
return {
|
||||
...initialSchema,
|
||||
[field.blockName]: '',
|
||||
}
|
||||
}
|
||||
if (field.blockType === 'select') {
|
||||
return {
|
||||
...initialSchema,
|
||||
[field.blockName]: '',
|
||||
}
|
||||
}
|
||||
if (field.blockType === 'state') {
|
||||
return {
|
||||
...initialSchema,
|
||||
[field.blockName]: '',
|
||||
}
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
@use '../.../../../../css/queries.scss' as *;
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hasSubmitted {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80vh;
|
||||
|
||||
@include small-break {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin-bottom: var(--base);
|
||||
|
||||
@include mid-break {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
}
|
||||
|
||||
.confirmationMessage {
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fieldWrap {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-left: calc(var(--base) * -0.5);
|
||||
margin-right: calc(var(--base) * -0.5);
|
||||
width: calc(100% + #{var(--base)});
|
||||
|
||||
>* {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.Select {
|
||||
& label {
|
||||
position: absolute;
|
||||
top: calc(var(--base) * -0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebarContent {
|
||||
@include mid-break {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mobileSidebar {
|
||||
display: none;
|
||||
|
||||
@include mid-break {
|
||||
display: block;
|
||||
margin-bottom: calc(var(--base) * 2);
|
||||
}
|
||||
}
|
||||
175
examples/form-builder/nextjs/components/Blocks/Form/index.tsx
Normal file
175
examples/form-builder/nextjs/components/Blocks/Form/index.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { buildInitialFormState } from './buildInitialFormState'
|
||||
import { fields } from './fields'
|
||||
import { Form as FormType } from 'payload-plugin-form-builder/dist/types'
|
||||
import RichText from '../../RichText'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Gutter } from '../../Gutter'
|
||||
import { Button } from '../../Button'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export type Value = unknown
|
||||
|
||||
export interface Property {
|
||||
[key: string]: Value
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
[key: string]: Value | Property | Property[]
|
||||
}
|
||||
|
||||
export type FormBlockType = {
|
||||
blockName?: string
|
||||
blockType?: 'formBlock'
|
||||
enableIntro: Boolean
|
||||
form: FormType
|
||||
introContent?: {
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
}
|
||||
|
||||
export const FormBlock: React.FC<
|
||||
FormBlockType & {
|
||||
id?: string
|
||||
}
|
||||
> = props => {
|
||||
const {
|
||||
enableIntro,
|
||||
introContent,
|
||||
form: formFromProps,
|
||||
form: { id: formID, submitButtonLabel, confirmationType, redirect, confirmationMessage } = {},
|
||||
} = props
|
||||
|
||||
const formMethods = useForm({
|
||||
defaultValues: buildInitialFormState(formFromProps.fields),
|
||||
})
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
control,
|
||||
setValue,
|
||||
getValues,
|
||||
} = formMethods
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [hasSubmitted, setHasSubmitted] = useState<boolean>()
|
||||
const [error, setError] = useState<{ status?: string; message: string } | undefined>()
|
||||
const router = useRouter()
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(data: Data) => {
|
||||
let loadingTimerID: NodeJS.Timer
|
||||
|
||||
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(`${process.env.NEXT_PUBLIC_CMS_URL}/api/form-submissions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
form: formID,
|
||||
submissionData: dataToSend,
|
||||
}),
|
||||
})
|
||||
|
||||
const res = await req.json()
|
||||
|
||||
clearTimeout(loadingTimerID)
|
||||
|
||||
if (req.status >= 400) {
|
||||
setIsLoading(false)
|
||||
setError({
|
||||
status: res.status,
|
||||
message: res.errors?.[0]?.message || 'Internal Server Error',
|
||||
})
|
||||
|
||||
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.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
submitForm()
|
||||
},
|
||||
[router, formID, redirect, confirmationType],
|
||||
)
|
||||
|
||||
return (
|
||||
<Gutter>
|
||||
<div
|
||||
className={[
|
||||
classes.form,
|
||||
hasSubmitted && classes.hasSubmitted,
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
{enableIntro && introContent && !hasSubmitted && (
|
||||
<RichText className={classes.intro} content={introContent} />
|
||||
)}
|
||||
{!isLoading && hasSubmitted && confirmationType === 'message' && (
|
||||
<RichText className={classes.confirmationMessage} content={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={classes.fieldWrap}>
|
||||
{formFromProps &&
|
||||
formFromProps.fields &&
|
||||
formFromProps.fields.map((field, index) => {
|
||||
const Field: React.FC<any> = fields?.[field.blockType]
|
||||
if (Field) {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<Field
|
||||
form={formFromProps}
|
||||
{...field}
|
||||
{...formMethods}
|
||||
register={register}
|
||||
errors={errors}
|
||||
control={control}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</div>
|
||||
<Button label={submitButtonLabel} appearance="primary" el="button" form={formID} />
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</Gutter>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
@use "../../../css/common.scss" as *;
|
||||
|
||||
@mixin formInput() {
|
||||
all: unset;
|
||||
-webkit-appearance: none;
|
||||
box-sizing: border-box;
|
||||
font-family: var(--font-body);
|
||||
width: 100%;
|
||||
border: 1px solid var(--color-black);
|
||||
background: var(--color-white);
|
||||
color: var(--color-black);
|
||||
font-size: 1rem;
|
||||
height: calc(var(--base) * 2.5);
|
||||
line-height: var(--base);
|
||||
padding: calc(var(--base) * 0.75);
|
||||
|
||||
&::-moz-placeholder,
|
||||
&::-webkit-input-placeholder {
|
||||
color: var(--color-mid-gray);
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-mid-gray);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
border-color: var(--color-gray);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: var(--color-light-gray);
|
||||
color: var(--color-gray);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-light-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
examples/form-builder/nextjs/components/Blocks/index.tsx
Normal file
61
examples/form-builder/nextjs/components/Blocks/index.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Page } from '../../payload-types';
|
||||
import { toKebabCase } from '../../utilities/toKebabCase';
|
||||
import { VerticalPadding } from '../VerticalPadding';
|
||||
import { FormBlock } from './Form'
|
||||
|
||||
const blockComponents = {
|
||||
formBlock: FormBlock,
|
||||
}
|
||||
|
||||
const Blocks: React.FC<{
|
||||
blocks: Page['layout']
|
||||
}> = (props) => {
|
||||
const {
|
||||
blocks,
|
||||
} = props;
|
||||
|
||||
const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0;
|
||||
|
||||
if (hasBlocks) {
|
||||
return (
|
||||
<Fragment>
|
||||
{blocks.map((block, index) => {
|
||||
const {
|
||||
blockName,
|
||||
blockType,
|
||||
form
|
||||
} = block;
|
||||
|
||||
const isFormBlock = blockType === 'formBlock'
|
||||
{/*@ts-ignore*/ }
|
||||
const formID: string = isFormBlock && form && form.id
|
||||
|
||||
if (blockType && blockType in blockComponents) {
|
||||
const Block = blockComponents[blockType];
|
||||
|
||||
return (
|
||||
<VerticalPadding
|
||||
key={isFormBlock ? formID : index}
|
||||
top='small'
|
||||
bottom='small'
|
||||
>
|
||||
{/*@ts-ignore*/}
|
||||
<Block
|
||||
id={toKebabCase(blockName)}
|
||||
{...block}
|
||||
/>
|
||||
</VerticalPadding>
|
||||
);
|
||||
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Blocks;
|
||||
@@ -0,0 +1,59 @@
|
||||
@import '../../css/type.scss';
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
margin-right: calc(var(--base) / 2);
|
||||
width: var(--base);
|
||||
height: var(--base);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
@extend %label;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
padding: 12px 18px;
|
||||
margin-bottom: var(--base);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.appearance--primary {
|
||||
background-color: var(--color-black);
|
||||
color: var(--color-white);
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-black);
|
||||
box-shadow: inset 0 0 0 1px var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.appearance--secondary {
|
||||
background-color: var(--color-white);
|
||||
box-shadow: inset 0 0 0 1px var(--color-black);
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: var(--color-black);
|
||||
color: var(--color-white);
|
||||
box-shadow: inset 0 0 0 1px var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.appearance--default {
|
||||
padding: 0;
|
||||
margin-left: -8px;
|
||||
}
|
||||
66
examples/form-builder/nextjs/components/Button/index.tsx
Normal file
66
examples/form-builder/nextjs/components/Button/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
label: string
|
||||
appearance?: 'default' | 'primary' | 'secondary'
|
||||
el?: 'button' | 'link' | 'a'
|
||||
onClick?: () => void
|
||||
href?: string
|
||||
form?: string
|
||||
newTab?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const elements = {
|
||||
a: 'a',
|
||||
link: Link,
|
||||
button: 'button',
|
||||
}
|
||||
|
||||
export const Button: React.FC<Props> = ({
|
||||
el = 'button',
|
||||
label,
|
||||
newTab,
|
||||
href,
|
||||
form,
|
||||
appearance,
|
||||
className: classNameFromProps
|
||||
}) => {
|
||||
const newTabProps = newTab ? { target: '_blank', rel: 'noopener noreferrer' } : {};
|
||||
const Element = elements[el];
|
||||
const className = [classNameFromProps, classes[`appearance--${appearance}`], classes.button].filter(Boolean).join(' ');
|
||||
|
||||
const elementProps = {
|
||||
...newTabProps,
|
||||
href,
|
||||
className,
|
||||
form,
|
||||
}
|
||||
|
||||
const content = (
|
||||
<div className={classes.content}>
|
||||
<span className={classes.label}>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Element {...elementProps}>
|
||||
<React.Fragment>
|
||||
{el === 'link' && (
|
||||
<a {...newTabProps} href={href} className={elementProps.className}>
|
||||
{content}
|
||||
</a>
|
||||
)}
|
||||
{el !== 'link' && (
|
||||
<React.Fragment>
|
||||
{content}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</Element>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useModal } from '@faceless-ui/modal';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
export const CloseModalOnRouteChange: React.FC = () => {
|
||||
const { closeAllModals } = useModal();
|
||||
const { asPath } = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
closeAllModals();
|
||||
}, [asPath, closeAllModals]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.gutterLeft {
|
||||
padding-left: var(--gutter-h);
|
||||
}
|
||||
|
||||
.gutterRight {
|
||||
padding-right: var(--gutter-h);
|
||||
}
|
||||
35
examples/form-builder/nextjs/components/Gutter/index.tsx
Normal file
35
examples/form-builder/nextjs/components/Gutter/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { forwardRef, Ref } from 'react';
|
||||
import classes from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
left?: boolean
|
||||
right?: boolean
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
ref?: Ref<HTMLDivElement>
|
||||
}
|
||||
|
||||
export const Gutter: React.FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
const {
|
||||
left = true,
|
||||
right = true,
|
||||
className,
|
||||
children
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={[
|
||||
left && classes.gutterLeft,
|
||||
right && classes.gutterRight,
|
||||
className
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
Gutter.displayName = 'Gutter';
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Modal } from "@faceless-ui/modal";
|
||||
import { HeaderBar } from ".";
|
||||
import { MainMenu } from "../../payload-types"
|
||||
import { Gutter } from "../Gutter";
|
||||
import { CMSLink } from "../Link";
|
||||
|
||||
import classes from './mobileMenuModal.module.scss';
|
||||
|
||||
type Props = {
|
||||
navItems: MainMenu['navItems'];
|
||||
}
|
||||
|
||||
export const slug = 'menu-modal';
|
||||
|
||||
export const MobileMenuModal: React.FC<Props> = ({ navItems }) => {
|
||||
return (
|
||||
<Modal slug={slug} className={classes.mobileMenuModal}>
|
||||
<HeaderBar />
|
||||
|
||||
<Gutter>
|
||||
<div className={classes.mobileMenuItems}>
|
||||
{navItems.map(({ link }, i) => {
|
||||
return (
|
||||
<CMSLink className={classes.menuItem} key={i} {...link} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Gutter>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@use '../../css/queries.scss' as *;
|
||||
|
||||
.header {
|
||||
padding: var(--base) 0;
|
||||
z-index: var(--header-z-index);
|
||||
}
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.nav {
|
||||
a {
|
||||
text-decoration: none;
|
||||
margin-left: var(--base);
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mobileMenuToggler {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
|
||||
&[aria-expanded="true"] {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
55
examples/form-builder/nextjs/components/Header/index.tsx
Normal file
55
examples/form-builder/nextjs/components/Header/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ModalToggler } from '@faceless-ui/modal';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { useGlobals } from '../../providers/Globals';
|
||||
import { Gutter } from '../Gutter';
|
||||
import { MenuIcon } from '../icons/Menu';
|
||||
import { CMSLink } from '../Link';
|
||||
import { Logo } from '../Logo';
|
||||
import { MobileMenuModal, slug as menuModalSlug } from './MobileMenuModal';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
type HeaderBarProps = {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const HeaderBar: React.FC<HeaderBarProps> = ({ children }) => {
|
||||
return (
|
||||
<header className={classes.header}>
|
||||
<Gutter className={classes.wrap}>
|
||||
<Link href="/">
|
||||
<a>
|
||||
<Logo />
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
{children}
|
||||
|
||||
<ModalToggler slug={menuModalSlug} className={classes.mobileMenuToggler}>
|
||||
<MenuIcon />
|
||||
</ModalToggler>
|
||||
</Gutter>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
const { mainMenu: { navItems } } = useGlobals();
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderBar>
|
||||
<nav className={classes.nav}>
|
||||
{navItems.map(({ link }, i) => {
|
||||
return (
|
||||
<CMSLink key={i} {...link} />
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</HeaderBar>
|
||||
|
||||
<MobileMenuModal navItems={navItems} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
@use '../../css/common.scss' as *;
|
||||
|
||||
.mobileMenuModal {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
padding: 0;
|
||||
opacity: 1;
|
||||
display: none;
|
||||
|
||||
@include mid-break {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.mobileMenuItems {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
@extend %h4;
|
||||
margin-top: 0;
|
||||
}
|
||||
66
examples/form-builder/nextjs/components/Link/index.tsx
Normal file
66
examples/form-builder/nextjs/components/Link/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { Page } from '../../payload-types';
|
||||
import { Button } from '../Button';
|
||||
|
||||
type CMSLinkType = {
|
||||
type?: 'custom' | 'reference'
|
||||
url?: string
|
||||
newTab?: boolean
|
||||
reference?: {
|
||||
value: string | Page
|
||||
relationTo: 'pages'
|
||||
}
|
||||
label?: string
|
||||
appearance?: 'default' | 'primary' | 'secondary'
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const CMSLink: React.FC<CMSLinkType> = ({
|
||||
type,
|
||||
url,
|
||||
newTab,
|
||||
reference,
|
||||
label,
|
||||
appearance,
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
const href = (type === 'reference' && typeof reference?.value === 'object' && reference.value.slug) ? `/${reference.value.slug}` : url;
|
||||
|
||||
if (!appearance) {
|
||||
const newTabProps = newTab ? { target: '_blank', rel: 'noopener noreferrer' } : {};
|
||||
|
||||
if (type === 'custom') {
|
||||
return (
|
||||
<a href={url} {...newTabProps} className={className}>
|
||||
{label && label}
|
||||
{children && children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a {...newTabProps} className={className}>
|
||||
{label && label}
|
||||
{children && children}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const buttonProps = {
|
||||
newTab,
|
||||
href,
|
||||
appearance,
|
||||
label,
|
||||
}
|
||||
|
||||
return (
|
||||
<Button className={className} {...buttonProps} el="link" />
|
||||
)
|
||||
}
|
||||
18
examples/form-builder/nextjs/components/Logo/index.tsx
Normal file
18
examples/form-builder/nextjs/components/Logo/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export const Logo: React.FC = () => {
|
||||
return (
|
||||
<svg width="123" height="29" viewBox="0 0 123 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M34.7441 22.9997H37.2741V16.3297H41.5981C44.7031 16.3297 46.9801 14.9037 46.9801 11.4537C46.9801 8.00369 44.7031 6.55469 41.5981 6.55469H34.7441V22.9997ZM37.2741 14.1447V8.73969H41.4831C43.3921 8.73969 44.3581 9.59069 44.3581 11.4537C44.3581 13.2937 43.3921 14.1447 41.4831 14.1447H37.2741Z" fill="black" />
|
||||
<path d="M51.3652 23.3217C53.2742 23.3217 54.6082 22.5627 55.3672 21.3437H55.4132C55.5512 22.6777 56.1492 23.1147 57.2762 23.1147C57.6442 23.1147 58.0352 23.0687 58.4262 22.9767V21.5967C58.2882 21.6197 58.2192 21.6197 58.1502 21.6197C57.7132 21.6197 57.5982 21.1827 57.5982 20.3317V14.9497C57.5982 11.9137 55.6662 10.9017 53.2512 10.9017C49.6632 10.9017 48.1912 12.6727 48.0762 14.9267H50.3762C50.4912 13.3627 51.1122 12.7187 53.1592 12.7187C54.8842 12.7187 55.3902 13.4317 55.3902 14.2827C55.3902 15.4327 54.2632 15.6627 52.4232 16.0077C49.5022 16.5597 47.5242 17.3417 47.5242 19.9637C47.5242 21.9647 49.0192 23.3217 51.3652 23.3217ZM49.8702 19.8027C49.8702 18.5837 50.7442 18.0087 52.8142 17.5947C54.0102 17.3417 55.0222 17.0887 55.3902 16.7437V18.4227C55.3902 20.4697 53.8952 21.5047 51.8712 21.5047C50.4682 21.5047 49.8702 20.9067 49.8702 19.8027Z" fill="black" />
|
||||
<path d="M61.4996 27.1167C63.3166 27.1167 64.4436 26.1737 65.5706 23.2757L70.2166 11.2697H67.8476L64.6276 20.2397H64.5816L61.1546 11.2697H58.6936L63.4316 22.8847C62.9716 24.7247 61.9136 25.1847 61.0166 25.1847C60.6486 25.1847 60.4416 25.1617 60.0506 25.1157V26.9557C60.6486 27.0707 60.9936 27.1167 61.4996 27.1167Z" fill="black" />
|
||||
<path d="M71.5939 22.9997H73.8479V6.55469H71.5939V22.9997Z" fill="black" />
|
||||
<path d="M81.6221 23.3447C85.2791 23.3447 87.4871 20.7917 87.4871 17.1117C87.4871 13.4547 85.2791 10.9017 81.6451 10.9017C77.9651 10.9017 75.7571 13.4777 75.7571 17.1347C75.7571 20.8147 77.9651 23.3447 81.6221 23.3447ZM78.1031 17.1347C78.1031 14.6737 79.2071 12.7877 81.6451 12.7877C84.0371 12.7877 85.1411 14.6737 85.1411 17.1347C85.1411 19.5727 84.0371 21.4817 81.6451 21.4817C79.2071 21.4817 78.1031 19.5727 78.1031 17.1347Z" fill="black" />
|
||||
<path d="M92.6484 23.3217C94.5574 23.3217 95.8914 22.5627 96.6504 21.3437H96.6964C96.8344 22.6777 97.4324 23.1147 98.5594 23.1147C98.9274 23.1147 99.3184 23.0687 99.7094 22.9767V21.5967C99.5714 21.6197 99.5024 21.6197 99.4334 21.6197C98.9964 21.6197 98.8814 21.1827 98.8814 20.3317V14.9497C98.8814 11.9137 96.9494 10.9017 94.5344 10.9017C90.9464 10.9017 89.4744 12.6727 89.3594 14.9267H91.6594C91.7744 13.3627 92.3954 12.7187 94.4424 12.7187C96.1674 12.7187 96.6734 13.4317 96.6734 14.2827C96.6734 15.4327 95.5464 15.6627 93.7064 16.0077C90.7854 16.5597 88.8074 17.3417 88.8074 19.9637C88.8074 21.9647 90.3024 23.3217 92.6484 23.3217ZM91.1534 19.8027C91.1534 18.5837 92.0274 18.0087 94.0974 17.5947C95.2934 17.3417 96.3054 17.0887 96.6734 16.7437V18.4227C96.6734 20.4697 95.1784 21.5047 93.1544 21.5047C91.7514 21.5047 91.1534 20.9067 91.1534 19.8027Z" fill="black" />
|
||||
<path d="M106.181 23.3217C108.021 23.3217 109.148 22.4477 109.792 21.6197H109.838V22.9997H112.092V6.55469H109.838V12.6957H109.792C109.148 11.7757 108.021 10.9247 106.181 10.9247C103.191 10.9247 100.914 13.2707 100.914 17.1347C100.914 20.9987 103.191 23.3217 106.181 23.3217ZM103.26 17.1347C103.26 14.8347 104.341 12.8107 106.549 12.8107C108.573 12.8107 109.815 14.4667 109.815 17.1347C109.815 19.7797 108.573 21.4587 106.549 21.4587C104.341 21.4587 103.26 19.4347 103.26 17.1347Z" fill="black" />
|
||||
<path d="M12.2464 2.33838L22.2871 8.83812V21.1752L14.7265 25.8854V13.5484L4.67383 7.05725L12.2464 2.33838Z" fill="black" />
|
||||
<path d="M11.477 25.2017V15.5747L3.90039 20.2936L11.477 25.2017Z" fill="black" />
|
||||
<path d="M120.442 6.30273C119.086 6.30273 117.998 7.29978 117.998 8.75952C117.998 10.2062 119.086 11.1968 120.442 11.1968C121.791 11.1968 122.879 10.2062 122.879 8.75952C122.879 7.29978 121.791 6.30273 120.442 6.30273ZM120.442 10.7601C119.34 10.7601 118.48 9.95207 118.48 8.75952C118.48 7.54742 119.34 6.73935 120.442 6.73935C121.563 6.73935 122.397 7.54742 122.397 8.75952C122.397 9.95207 121.563 10.7601 120.442 10.7601ZM120.52 8.97457L121.048 9.9651H121.641L121.041 8.86378C121.367 8.72042 121.511 8.45975 121.511 8.17302C121.511 7.49528 121.054 7.36495 120.285 7.36495H119.49V9.9651H120.025V8.97457H120.52ZM120.37 7.78853C120.729 7.78853 120.976 7.86673 120.976 8.17953C120.976 8.43368 120.807 8.56402 120.403 8.56402H120.025V7.78853H120.37Z" fill="black" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.richText {
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
18
examples/form-builder/nextjs/components/RichText/index.tsx
Normal file
18
examples/form-builder/nextjs/components/RichText/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import serialize from './serialize';
|
||||
|
||||
import classes from './index.module.scss';
|
||||
|
||||
const RichText: React.FC<{ className?: string, content: any }> = ({ className, content }) => {
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[classes.richText, className].filter(Boolean).join(' ')}>
|
||||
{serialize(content)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RichText;
|
||||
160
examples/form-builder/nextjs/components/RichText/serialize.tsx
Normal file
160
examples/form-builder/nextjs/components/RichText/serialize.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import escapeHTML from 'escape-html';
|
||||
import { Text } from 'slate';
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
type Children = Leaf[]
|
||||
|
||||
type Leaf = {
|
||||
type: string
|
||||
value?: {
|
||||
url: string
|
||||
alt: string
|
||||
}
|
||||
children?: Children
|
||||
url?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const serialize = (children: Children): React.ReactElement[] => children.map((node, i) => {
|
||||
if (Text.isText(node)) {
|
||||
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />;
|
||||
|
||||
if (node.bold) {
|
||||
text = (
|
||||
<strong key={i}>
|
||||
{text}
|
||||
</strong>
|
||||
);
|
||||
}
|
||||
|
||||
if (node.code) {
|
||||
text = (
|
||||
<code key={i}>
|
||||
{text}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
if (node.italic) {
|
||||
text = (
|
||||
<em key={i}>
|
||||
{text}
|
||||
</em>
|
||||
);
|
||||
}
|
||||
|
||||
if (node.underline) {
|
||||
text = (
|
||||
<span
|
||||
style={{ textDecoration: 'underline' }}
|
||||
key={i}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (node.strikethrough) {
|
||||
text = (
|
||||
<span
|
||||
style={{ textDecoration: 'line-through' }}
|
||||
key={i}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
{text}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'h1':
|
||||
return (
|
||||
<h1 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h1>
|
||||
);
|
||||
case 'h2':
|
||||
return (
|
||||
<h2 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h2>
|
||||
);
|
||||
case 'h3':
|
||||
return (
|
||||
<h3 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h3>
|
||||
);
|
||||
case 'h4':
|
||||
return (
|
||||
<h4 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h4>
|
||||
);
|
||||
case 'h5':
|
||||
return (
|
||||
<h5 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h5>
|
||||
);
|
||||
case 'h6':
|
||||
return (
|
||||
<h6 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h6>
|
||||
);
|
||||
case 'quote':
|
||||
return (
|
||||
<blockquote key={i}>
|
||||
{serialize(node.children)}
|
||||
</blockquote>
|
||||
);
|
||||
case 'ul':
|
||||
return (
|
||||
<ul key={i}>
|
||||
{serialize(node.children)}
|
||||
</ul>
|
||||
);
|
||||
case 'ol':
|
||||
return (
|
||||
<ol key={i}>
|
||||
{serialize(node.children)}
|
||||
</ol>
|
||||
);
|
||||
case 'li':
|
||||
return (
|
||||
<li key={i}>
|
||||
{serialize(node.children)}
|
||||
</li>
|
||||
);
|
||||
case 'link':
|
||||
return (
|
||||
<a
|
||||
href={escapeHTML(node.url)}
|
||||
key={i}
|
||||
>
|
||||
{serialize(node.children)}
|
||||
</a>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<p key={i}>
|
||||
{serialize(node.children)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default serialize;
|
||||
@@ -0,0 +1,23 @@
|
||||
.top-large {
|
||||
padding-top: var(--block-padding);
|
||||
}
|
||||
|
||||
.top-medium {
|
||||
padding-top: calc(var(--block-padding) / 2);
|
||||
}
|
||||
|
||||
.top-small {
|
||||
padding-top: calc(var(--block-padding) / 3);
|
||||
}
|
||||
|
||||
.bottom-large {
|
||||
padding-bottom: var(--block-padding);
|
||||
}
|
||||
|
||||
.bottom-medium {
|
||||
padding-bottom: calc(var(--block-padding) / 2);
|
||||
}
|
||||
|
||||
.bottom-small {
|
||||
padding-bottom: calc(var(--block-padding) / 3);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import classes from './index.module.scss';
|
||||
|
||||
export type VerticalPaddingOptions = 'large' | 'medium' | 'small' | 'none';
|
||||
|
||||
type Props = {
|
||||
top?: VerticalPaddingOptions
|
||||
bottom?: VerticalPaddingOptions
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const VerticalPadding: React.FC<Props> = ({
|
||||
top = 'medium',
|
||||
bottom = 'medium',
|
||||
className,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
className,
|
||||
classes[`top-${top}`],
|
||||
classes[`bottom-${bottom}`],
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.checkBox {
|
||||
height: var(--base);
|
||||
width: var(--base);
|
||||
|
||||
.stroke {
|
||||
stroke-width: 1px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
}
|
||||
|
||||
&:local() {
|
||||
.stroke {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const Check: React.FC = () => {
|
||||
return (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 25 25"
|
||||
className={classes.checkBox}
|
||||
>
|
||||
<path
|
||||
d="M10.6092 16.0192L17.6477 8.98076"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="bevel"
|
||||
className={classes.stroke}
|
||||
/>
|
||||
<path
|
||||
d="M7.35229 12.7623L10.6092 16.0192"
|
||||
className={classes.stroke}
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="bevel"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
11
examples/form-builder/nextjs/components/icons/Menu/index.tsx
Normal file
11
examples/form-builder/nextjs/components/icons/Menu/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
export const MenuIcon: React.FC = () => {
|
||||
return (
|
||||
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3.5" y="4.5" width="18" height="2" fill="currentColor" />
|
||||
<rect x="3.5" y="11.5" width="18" height="2" fill="currentColor" />
|
||||
<rect x="3.5" y="18.5" width="18" height="2" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
129
examples/form-builder/nextjs/css/app.scss
Normal file
129
examples/form-builder/nextjs/css/app.scss
Normal file
@@ -0,0 +1,129 @@
|
||||
@use './queries.scss' as *;
|
||||
@use './colors.scss' as *;
|
||||
@use './type.scss' as *;
|
||||
|
||||
:root {
|
||||
--breakpoint-xs-width : #{$breakpoint-xs-width};
|
||||
--breakpoint-s-width : #{$breakpoint-s-width};
|
||||
--breakpoint-m-width : #{$breakpoint-m-width};
|
||||
--breakpoint-l-width : #{$breakpoint-l-width};
|
||||
--scrollbar-width: 17px;
|
||||
|
||||
--base: 24px;
|
||||
--font-body: system-ui;
|
||||
--font-mono: 'Roboto Mono', monospace;
|
||||
|
||||
--gutter-h: 180px;
|
||||
--block-padding: 120px;
|
||||
|
||||
--header-z-index: 100;
|
||||
--modal-z-index: 90;
|
||||
|
||||
@include large-break {
|
||||
--gutter-h: 144px;
|
||||
--block-padding: 96px;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
--gutter-h: 24px;
|
||||
--block-padding: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// GLOBAL STYLES
|
||||
/////////////////////////////
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
@extend %body;
|
||||
background: var(--color-white);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
color: var(--color-black);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-green);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--color-green);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend %h1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@extend %h2;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@extend %h3;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@extend %h4;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@extend %h5;
|
||||
}
|
||||
|
||||
h6 {
|
||||
@extend %h6;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: var(--base) 0;
|
||||
|
||||
@include mid-break {
|
||||
margin: calc(var(--base) * .75) 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: var(--base);
|
||||
margin: 0 0 var(--base);
|
||||
}
|
||||
|
||||
a {
|
||||
color: currentColor;
|
||||
|
||||
&:focus {
|
||||
opacity: .8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: .7;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
10
examples/form-builder/nextjs/css/colors.scss
Normal file
10
examples/form-builder/nextjs/css/colors.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
:root {
|
||||
--color-red: rgb(255,0,0);
|
||||
--color-green: rgb(178, 255, 214);
|
||||
--color-white: rgb(255, 255, 255);
|
||||
--color-dark-gray: rgb(51,52,52);
|
||||
--color-mid-gray: rgb(196,196,196);
|
||||
--color-gray: rgb(212,212,212);
|
||||
--color-light-gray: rgb(244,244,244);
|
||||
--color-black: rgb(0, 0, 0);
|
||||
}
|
||||
2
examples/form-builder/nextjs/css/common.scss
Normal file
2
examples/form-builder/nextjs/css/common.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
@forward './queries.scss';
|
||||
@forward './type.scss';
|
||||
32
examples/form-builder/nextjs/css/queries.scss
Normal file
32
examples/form-builder/nextjs/css/queries.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
$breakpoint-xs-width: 400px;
|
||||
$breakpoint-s-width: 768px;
|
||||
$breakpoint-m-width: 1024px;
|
||||
$breakpoint-l-width: 1440px;
|
||||
|
||||
////////////////////////////
|
||||
// MEDIA QUERIES
|
||||
/////////////////////////////
|
||||
|
||||
@mixin extra-small-break {
|
||||
@media (max-width: #{$breakpoint-xs-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin small-break {
|
||||
@media (max-width: #{$breakpoint-s-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mid-break {
|
||||
@media (max-width: #{$breakpoint-m-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin large-break {
|
||||
@media (max-width: #{$breakpoint-l-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
168
examples/form-builder/nextjs/css/type.scss
Normal file
168
examples/form-builder/nextjs/css/type.scss
Normal file
@@ -0,0 +1,168 @@
|
||||
@use 'queries' as *;
|
||||
|
||||
/////////////////////////////
|
||||
// HEADINGS
|
||||
/////////////////////////////
|
||||
|
||||
%h1,
|
||||
%h2,
|
||||
%h3,
|
||||
%h4,
|
||||
%h5,
|
||||
%h6 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
%h1 {
|
||||
margin: 50px 0;
|
||||
font-size: 84px;
|
||||
line-height: 1;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 70px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
margin: 24px 0;
|
||||
font-size: 36px;
|
||||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
%h2 {
|
||||
margin: 32px 0;
|
||||
font-size: 56px;
|
||||
line-height: 1;
|
||||
|
||||
@include mid-break {
|
||||
margin: 36px 0;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
margin: 24px 0;
|
||||
font-size: 28px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
%h3 {
|
||||
margin: 28px 0;
|
||||
font-size: 48px;
|
||||
line-height: 56px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 40px;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
margin: 24px 0;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
%h4 {
|
||||
margin: 24px 0;
|
||||
font-size: 40px;
|
||||
line-height: 48px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 33px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
margin: 20px 0;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
%h5 {
|
||||
margin: 20px 0;
|
||||
font-size: 32px;
|
||||
line-height: 42px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 26px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
margin: 16px 0;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
%h6 {
|
||||
margin: 20px 0;
|
||||
font-size: 24px;
|
||||
line-height: 28px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
margin: 16px 0;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// TYPE STYLES
|
||||
/////////////////////////////
|
||||
|
||||
%body {
|
||||
font-size: 18px;
|
||||
line-height: 32px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
%large-body {
|
||||
font-size: 25px;
|
||||
line-height: 32px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
%label {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 3px;
|
||||
text-transform: uppercase;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 13px;
|
||||
letter-spacing: 2.75px;
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 2.625px;
|
||||
}
|
||||
}
|
||||
14
examples/form-builder/nextjs/next.config.js
Normal file
14
examples/form-builder/nextjs/next.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
images: {
|
||||
domains: [
|
||||
'localhost',
|
||||
process.env.NEXT_PUBLIC_CMS_URL
|
||||
],
|
||||
},
|
||||
allowJs: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
33
examples/form-builder/nextjs/package.json
Normal file
33
examples/form-builder/nextjs/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "form-builder-example-website",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.0",
|
||||
"@faceless-ui/css-grid": "^1.2.0",
|
||||
"@faceless-ui/modal": "^2.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"graphql": "^16.6.0",
|
||||
"next": "12.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.41.0",
|
||||
"react-select": "^5.7.0",
|
||||
"sass": "^1.55.0",
|
||||
"slate": "^0.84.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.3",
|
||||
"@types/react": "18.0.21",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"payload-plugin-form-builder": "^1.0.8",
|
||||
"typescript": "4.8.4"
|
||||
}
|
||||
}
|
||||
60
examples/form-builder/nextjs/pages/[slug].tsx
Normal file
60
examples/form-builder/nextjs/pages/[slug].tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
GetStaticProps,
|
||||
GetStaticPropsContext,
|
||||
GetStaticPaths
|
||||
} from 'next';
|
||||
import Blocks from '../components/Blocks';
|
||||
import type { Page, MainMenu } from '../payload-types';
|
||||
|
||||
const Page: React.FC<{
|
||||
mainMenu: MainMenu
|
||||
page: Page
|
||||
}> = (props) => {
|
||||
const {
|
||||
page: {
|
||||
layout,
|
||||
},
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Blocks blocks={layout} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (
|
||||
context: GetStaticPropsContext,
|
||||
) => {
|
||||
const { params } = context;
|
||||
|
||||
const slug = params?.slug || 'home';
|
||||
|
||||
const pageQuery = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/pages?where[slug][equals]=${slug}`).then(
|
||||
(res) => res.json(),
|
||||
);
|
||||
|
||||
return {
|
||||
props: {
|
||||
page: pageQuery.docs[0],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
|
||||
const pagesQuery: { docs: Page[] } = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/pages?limit=100`).then(
|
||||
(res) => res.json(),
|
||||
);
|
||||
|
||||
return {
|
||||
paths: pagesQuery.docs.map((page) => ({
|
||||
params: {
|
||||
slug: page.slug,
|
||||
},
|
||||
})),
|
||||
fallback: 'blocking',
|
||||
};
|
||||
};
|
||||
65
examples/form-builder/nextjs/pages/_app.tsx
Normal file
65
examples/form-builder/nextjs/pages/_app.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import App, { AppContext, AppProps } from 'next/app';
|
||||
import { ModalContainer, ModalProvider } from '@faceless-ui/modal';
|
||||
import React from 'react';
|
||||
import { Header } from '../components/Header';
|
||||
import { GlobalsProvider } from '../providers/Globals';
|
||||
import { CloseModalOnRouteChange } from '../components/CloseModalOnRouteChange';
|
||||
import { MainMenu } from "../payload-types";
|
||||
|
||||
import '../css/app.scss';
|
||||
|
||||
export interface IGlobals {
|
||||
mainMenu: MainMenu,
|
||||
}
|
||||
|
||||
export const getAllGlobals = async (): Promise<IGlobals> => {
|
||||
const [
|
||||
mainMenu,
|
||||
] = await Promise.all([
|
||||
fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/globals/main-menu?depth=1`).then((res) => res.json()),
|
||||
]);
|
||||
|
||||
return {
|
||||
mainMenu,
|
||||
}
|
||||
}
|
||||
|
||||
const PayloadApp = (appProps: AppProps & {
|
||||
globals: IGlobals,
|
||||
}): React.ReactElement => {
|
||||
const {
|
||||
Component,
|
||||
pageProps,
|
||||
globals,
|
||||
} = appProps;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<GlobalsProvider {...globals}>
|
||||
<ModalProvider
|
||||
classPrefix="form"
|
||||
transTime={0}
|
||||
zIndex="var(--modal-z-index)"
|
||||
>
|
||||
<CloseModalOnRouteChange />
|
||||
<Header />
|
||||
<Component {...pageProps} />
|
||||
<ModalContainer />
|
||||
</ModalProvider>
|
||||
</GlobalsProvider>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
PayloadApp.getInitialProps = async (appContext: AppContext) => {
|
||||
const appProps = await App.getInitialProps(appContext);
|
||||
|
||||
const globals = await getAllGlobals();
|
||||
|
||||
return {
|
||||
...appProps,
|
||||
globals
|
||||
};
|
||||
};
|
||||
|
||||
export default PayloadApp
|
||||
9
examples/form-builder/nextjs/pages/index.tsx
Normal file
9
examples/form-builder/nextjs/pages/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { GetStaticProps } from 'next';
|
||||
import Page, { getStaticProps as sharedGetStaticProps } from './[slug]';
|
||||
|
||||
export default Page;
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (ctx) => {
|
||||
const func = sharedGetStaticProps.bind(this);
|
||||
return func(ctx);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user