feat: form submission components blocks and styling
This commit is contained in:
parent
d8d7532169
commit
a85fba4882
@ -15,6 +15,7 @@
|
|||||||
"@payloadcms/db-postgres": "^3.35.1",
|
"@payloadcms/db-postgres": "^3.35.1",
|
||||||
"@payloadcms/next": "^3.35.1",
|
"@payloadcms/next": "^3.35.1",
|
||||||
"@payloadcms/payload-cloud": "^3.35.1",
|
"@payloadcms/payload-cloud": "^3.35.1",
|
||||||
|
"@payloadcms/plugin-form-builder": "^3.35.1",
|
||||||
"@payloadcms/richtext-lexical": "^3.35.1",
|
"@payloadcms/richtext-lexical": "^3.35.1",
|
||||||
"@payloadcms/storage-s3": "^3.35.1",
|
"@payloadcms/storage-s3": "^3.35.1",
|
||||||
"country-state-city": "^3.2.1",
|
"country-state-city": "^3.2.1",
|
||||||
@ -25,6 +26,7 @@
|
|||||||
"qs-esm": "^7.0.2",
|
"qs-esm": "^7.0.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.56.1",
|
||||||
"react-select": "^5.10.1",
|
"react-select": "^5.10.1",
|
||||||
"swiper": "^11.2.6"
|
"swiper": "^11.2.6"
|
||||||
},
|
},
|
||||||
|
@ -1482,168 +1482,168 @@ $(function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Select2
|
// Select2
|
||||||
if (plugins.selectFilter.length) {
|
// if (plugins.selectFilter.length) {
|
||||||
var i;
|
// var i;
|
||||||
for (i = 0; i < plugins.selectFilter.length; i++) {
|
// for (i = 0; i < plugins.selectFilter.length; i++) {
|
||||||
var select = $(plugins.selectFilter[i]);
|
// var select = $(plugins.selectFilter[i]);
|
||||||
|
|
||||||
select.select2({
|
// select.select2({
|
||||||
placeholder: select.attr("data-placeholder") ? select.attr("data-placeholder") : false,
|
// placeholder: select.attr("data-placeholder") ? select.attr("data-placeholder") : false,
|
||||||
minimumResultsForSearch: select.attr("data-minimum-results-search") ? select.attr("data-minimum-results-search") : -1,
|
// minimumResultsForSearch: select.attr("data-minimum-results-search") ? select.attr("data-minimum-results-search") : -1,
|
||||||
maximumSelectionSize: 3,
|
// maximumSelectionSize: 3,
|
||||||
dropdownCssClass: select.attr("data-class") ? select.attr("data-class") : ''
|
// dropdownCssClass: select.attr("data-class") ? select.attr("data-class") : ''
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// RD Mailform
|
// RD Mailform
|
||||||
if (plugins.rdMailForm.length) {
|
// if (plugins.rdMailForm.length) {
|
||||||
var i, j, k,
|
// var i, j, k,
|
||||||
msg = {
|
// msg = {
|
||||||
'MF000': 'Successfully sent!',
|
// 'MF000': 'Successfully sent!',
|
||||||
'MF001': 'Recipients are not set!',
|
// 'MF001': 'Recipients are not set!',
|
||||||
'MF002': 'Form will not work locally!',
|
// 'MF002': 'Form will not work locally!',
|
||||||
'MF003': 'Please, define email field in your form!',
|
// 'MF003': 'Please, define email field in your form!',
|
||||||
'MF004': 'Please, define type of your form!',
|
// 'MF004': 'Please, define type of your form!',
|
||||||
'MF254': 'Something went wrong with PHPMailer!',
|
// 'MF254': 'Something went wrong with PHPMailer!',
|
||||||
'MF255': 'Aw, snap! Something went wrong.'
|
// 'MF255': 'Aw, snap! Something went wrong.'
|
||||||
};
|
// };
|
||||||
|
|
||||||
for (i = 0; i < plugins.rdMailForm.length; i++) {
|
// for (i = 0; i < plugins.rdMailForm.length; i++) {
|
||||||
var $form = $(plugins.rdMailForm[i]),
|
// var $form = $(plugins.rdMailForm[i]),
|
||||||
formHasCaptcha = false;
|
// formHasCaptcha = false;
|
||||||
|
|
||||||
$form.attr('novalidate', 'novalidate').ajaxForm({
|
// $form.attr('novalidate', 'novalidate').ajaxForm({
|
||||||
data: {
|
// data: {
|
||||||
"form-type": $form.attr("data-form-type") || "contact",
|
// "form-type": $form.attr("data-form-type") || "contact",
|
||||||
"counter": i
|
// "counter": i
|
||||||
},
|
// },
|
||||||
beforeSubmit: function (arr, $form, options) {
|
// beforeSubmit: function (arr, $form, options) {
|
||||||
if (isNoviBuilder)
|
// if (isNoviBuilder)
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
var form = $(plugins.rdMailForm[this.extraData.counter]),
|
// var form = $(plugins.rdMailForm[this.extraData.counter]),
|
||||||
inputs = form.find("[data-constraints]"),
|
// inputs = form.find("[data-constraints]"),
|
||||||
output = $("#" + form.attr("data-form-output")),
|
// output = $("#" + form.attr("data-form-output")),
|
||||||
captcha = form.find('.recaptcha'),
|
// captcha = form.find('.recaptcha'),
|
||||||
captchaFlag = true;
|
// captchaFlag = true;
|
||||||
|
|
||||||
output.removeClass("active error success");
|
// output.removeClass("active error success");
|
||||||
|
|
||||||
if (isValidated(inputs, captcha)) {
|
// if (isValidated(inputs, captcha)) {
|
||||||
|
|
||||||
// veify reCaptcha
|
// // veify reCaptcha
|
||||||
if (captcha.length) {
|
// if (captcha.length) {
|
||||||
var captchaToken = captcha.find('.g-recaptcha-response').val(),
|
// var captchaToken = captcha.find('.g-recaptcha-response').val(),
|
||||||
captchaMsg = {
|
// captchaMsg = {
|
||||||
'CPT001': 'Please, setup you "site key" and "secret key" of reCaptcha',
|
// 'CPT001': 'Please, setup you "site key" and "secret key" of reCaptcha',
|
||||||
'CPT002': 'Something wrong with google reCaptcha'
|
// 'CPT002': 'Something wrong with google reCaptcha'
|
||||||
};
|
// };
|
||||||
|
|
||||||
formHasCaptcha = true;
|
// formHasCaptcha = true;
|
||||||
|
|
||||||
$.ajax({
|
// $.ajax({
|
||||||
method: "POST",
|
// method: "POST",
|
||||||
url: "bat/reCaptcha.php",
|
// url: "bat/reCaptcha.php",
|
||||||
data: { 'g-recaptcha-response': captchaToken },
|
// data: { 'g-recaptcha-response': captchaToken },
|
||||||
async: false
|
// async: false
|
||||||
})
|
// })
|
||||||
.done(function (responceCode) {
|
// .done(function (responceCode) {
|
||||||
if (responceCode !== 'CPT000') {
|
// if (responceCode !== 'CPT000') {
|
||||||
if (output.hasClass("snackbars")) {
|
// if (output.hasClass("snackbars")) {
|
||||||
output.html('<p><span class="icon text-middle mdi mdi-check icon-xxs"></span><span>' + captchaMsg[responceCode] + '</span></p>')
|
// output.html('<p><span class="icon text-middle mdi mdi-check icon-xxs"></span><span>' + captchaMsg[responceCode] + '</span></p>')
|
||||||
|
|
||||||
setTimeout(function () {
|
// setTimeout(function () {
|
||||||
output.removeClass("active");
|
// output.removeClass("active");
|
||||||
}, 3500);
|
// }, 3500);
|
||||||
|
|
||||||
captchaFlag = false;
|
// captchaFlag = false;
|
||||||
} else {
|
// } else {
|
||||||
output.html(captchaMsg[responceCode]);
|
// output.html(captchaMsg[responceCode]);
|
||||||
}
|
// }
|
||||||
|
|
||||||
output.addClass("active");
|
// output.addClass("active");
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!captchaFlag) {
|
// if (!captchaFlag) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
form.addClass('form-in-process');
|
// form.addClass('form-in-process');
|
||||||
|
|
||||||
if (output.hasClass("snackbars")) {
|
// if (output.hasClass("snackbars")) {
|
||||||
output.html('<p><span class="icon text-middle fa fa-circle-o-notch fa-spin icon-xxs"></span><span>Sending</span></p>');
|
// output.html('<p><span class="icon text-middle fa fa-circle-o-notch fa-spin icon-xxs"></span><span>Sending</span></p>');
|
||||||
output.addClass("active");
|
// output.addClass("active");
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
error: function (result) {
|
// error: function (result) {
|
||||||
if (isNoviBuilder)
|
// if (isNoviBuilder)
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
var output = $("#" + $(plugins.rdMailForm[this.extraData.counter]).attr("data-form-output")),
|
// var output = $("#" + $(plugins.rdMailForm[this.extraData.counter]).attr("data-form-output")),
|
||||||
form = $(plugins.rdMailForm[this.extraData.counter]);
|
// form = $(plugins.rdMailForm[this.extraData.counter]);
|
||||||
|
|
||||||
output.text(msg[result]);
|
// output.text(msg[result]);
|
||||||
form.removeClass('form-in-process');
|
// form.removeClass('form-in-process');
|
||||||
|
|
||||||
if (formHasCaptcha) {
|
// if (formHasCaptcha) {
|
||||||
grecaptcha.reset();
|
// grecaptcha.reset();
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
success: function (result) {
|
// success: function (result) {
|
||||||
if (isNoviBuilder)
|
// if (isNoviBuilder)
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
var form = $(plugins.rdMailForm[this.extraData.counter]),
|
// var form = $(plugins.rdMailForm[this.extraData.counter]),
|
||||||
output = $("#" + form.attr("data-form-output")),
|
// output = $("#" + form.attr("data-form-output")),
|
||||||
select = form.find('select');
|
// select = form.find('select');
|
||||||
|
|
||||||
form
|
// form
|
||||||
.addClass('success')
|
// .addClass('success')
|
||||||
.removeClass('form-in-process');
|
// .removeClass('form-in-process');
|
||||||
|
|
||||||
if (formHasCaptcha) {
|
// if (formHasCaptcha) {
|
||||||
grecaptcha.reset();
|
// grecaptcha.reset();
|
||||||
}
|
// }
|
||||||
|
|
||||||
result = result.length === 5 ? result : 'MF255';
|
// result = result.length === 5 ? result : 'MF255';
|
||||||
output.text(msg[result]);
|
// output.text(msg[result]);
|
||||||
|
|
||||||
if (result === "MF000") {
|
// if (result === "MF000") {
|
||||||
if (output.hasClass("snackbars")) {
|
// if (output.hasClass("snackbars")) {
|
||||||
output.html('<p><span class="icon text-middle mdi mdi-check icon-xxs"></span><span>' + msg[result] + '</span></p>');
|
// output.html('<p><span class="icon text-middle mdi mdi-check icon-xxs"></span><span>' + msg[result] + '</span></p>');
|
||||||
} else {
|
// } else {
|
||||||
output.addClass("active success");
|
// output.addClass("active success");
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
if (output.hasClass("snackbars")) {
|
// if (output.hasClass("snackbars")) {
|
||||||
output.html(' <p class="snackbars-left"><span class="icon icon-xxs mdi mdi-alert-outline text-middle"></span><span>' + msg[result] + '</span></p>');
|
// output.html(' <p class="snackbars-left"><span class="icon icon-xxs mdi mdi-alert-outline text-middle"></span><span>' + msg[result] + '</span></p>');
|
||||||
} else {
|
// } else {
|
||||||
output.addClass("active error");
|
// output.addClass("active error");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
form.clearForm();
|
// form.clearForm();
|
||||||
|
|
||||||
if (select.length) {
|
// if (select.length) {
|
||||||
select.select2("val", "");
|
// select.select2("val", "");
|
||||||
}
|
// }
|
||||||
|
|
||||||
form.find('input, textarea').trigger('blur');
|
// form.find('input, textarea').trigger('blur');
|
||||||
|
|
||||||
setTimeout(function () {
|
// setTimeout(function () {
|
||||||
output.removeClass("active error success");
|
// output.removeClass("active error success");
|
||||||
form.removeClass('success');
|
// form.removeClass('success');
|
||||||
}, 3500);
|
// }, 3500);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// RD Filepicker
|
// RD Filepicker
|
||||||
if ((plugins.filePicker.length || plugins.fileDrop.length) && !isNoviBuilder) {
|
if ((plugins.filePicker.length || plugins.fileDrop.length) && !isNoviBuilder) {
|
||||||
|
15
src/blocks/Content.ts
Normal file
15
src/blocks/Content.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { lexicalEditor } from "@payloadcms/richtext-lexical";
|
||||||
|
import { Block } from "payload";
|
||||||
|
|
||||||
|
export const ContentBlock: Block = {
|
||||||
|
slug: "contentBlock",
|
||||||
|
labels: { plural: "Contents", singular: "Content" },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "content",
|
||||||
|
type: "richText",
|
||||||
|
editor: lexicalEditor({}),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
33
src/blocks/Form.ts
Normal file
33
src/blocks/Form.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { Block } from "payload";
|
||||||
|
|
||||||
|
export const FormBlock: Block = {
|
||||||
|
slug: "formBlock",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "form",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "forms",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enableIntro",
|
||||||
|
type: "checkbox",
|
||||||
|
label: "Enable Intro Content",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "introContent",
|
||||||
|
type: "richText",
|
||||||
|
admin: {
|
||||||
|
condition: (_, { enableIntro }) => Boolean(enableIntro),
|
||||||
|
},
|
||||||
|
label: "Intro Content",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
graphQL: {
|
||||||
|
singularName: "FormBlock",
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
plural: "Form Blocks",
|
||||||
|
singular: "Form Block",
|
||||||
|
},
|
||||||
|
};
|
92
src/collections/Pages.ts
Normal file
92
src/collections/Pages.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { ContentBlock } from "@/blocks/Content";
|
||||||
|
import { FormBlock } from "@/blocks/Form";
|
||||||
|
import formatSlug from "@/utils/payload/formatSlug";
|
||||||
|
import setAuthor from "@/utils/payload/setAuthor";
|
||||||
|
import { CollectionConfig } from "payload";
|
||||||
|
|
||||||
|
export const Pages: CollectionConfig = {
|
||||||
|
slug: "pages",
|
||||||
|
versions: {
|
||||||
|
drafts: {
|
||||||
|
validate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
label: "Page Title",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hero_img",
|
||||||
|
label: "Hero Image",
|
||||||
|
type: "upload",
|
||||||
|
relationTo: "media",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "slug",
|
||||||
|
label: "Page Slug",
|
||||||
|
type: "text",
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [formatSlug("title")],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "layout",
|
||||||
|
label: "Page Layout",
|
||||||
|
type: "blocks",
|
||||||
|
minRows: 1,
|
||||||
|
blocks: [ContentBlock, FormBlock],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "meta",
|
||||||
|
label: "Page Meta",
|
||||||
|
type: "group",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
label: "Title",
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
label: "Description",
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "canonical_url",
|
||||||
|
label: "Canonical Url",
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "createdBy",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
hooks: {
|
||||||
|
beforeChange: [setAuthor],
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "updatedBy",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: "users",
|
||||||
|
hooks: {
|
||||||
|
beforeChange: [setAuthor],
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
admin: {
|
||||||
|
hideAPIURL: true,
|
||||||
|
group: "General",
|
||||||
|
useAsTitle: "title",
|
||||||
|
},
|
||||||
|
};
|
@ -1,66 +1,23 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useFormQuery } from "@/services/hooks/form";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { FormBlock } from "@/components/blocks/Form";
|
||||||
|
|
||||||
export default function ContactFormBox() {
|
export default function ContactFormBox() {
|
||||||
|
const form = useFormQuery();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form._fetch(1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:w-1/3 box-1-cell height-fill hidden! lg:flex! context-dark bg-colorContactForm/80! z-20!">
|
<div className="lg:w-1/3 box-1-cell height-fill hidden! lg:flex! context-dark bg-colorContactForm/80! z-20!">
|
||||||
<div className="box-1-bg-shape">
|
<div className="box-1-bg-shape">
|
||||||
<img className="box-1-bg-image" src="images/bg-shape-1.png" alt="" role="presentation" />
|
<img className="box-1-bg-image" src="images/bg-shape-1.png" alt="" role="presentation" />
|
||||||
</div>
|
</div>
|
||||||
<div className="cell-inner box-1-outer">
|
<div className="cell-inner box-1-outer">
|
||||||
<div className="box-1">
|
<div className="box-1">{!form.isFetching && !!form.data && <FormBlock form={form.data} />}</div>
|
||||||
<h2>Get in Touch with Us Today!</h2>
|
|
||||||
<form className="rd-form">
|
|
||||||
<div className="row row-x-20 row-20">
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input className="form-input" placeholder="First Name" name="first_name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input className="form-input" placeholder="Last Name" name="last_name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input type="email" className="form-input" placeholder="E-Mail" name="email" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input className="form-input" placeholder="Phone Number" name="phone" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-12 col-lg-12 col-xl-12">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<select
|
|
||||||
className="form-input select-filter"
|
|
||||||
name="interest"
|
|
||||||
data-style="modern"
|
|
||||||
data-classname="select-dropdown-context-dark"
|
|
||||||
data-placeholder="I'm interested in"
|
|
||||||
data-minimum-results-for-search="Infinity"
|
|
||||||
data-constraints="@Required"
|
|
||||||
>
|
|
||||||
<option label="placeholder"></option>
|
|
||||||
<option value="2">Buying</option>
|
|
||||||
<option value="3">Selling</option>
|
|
||||||
<option value="4">Renting</option>
|
|
||||||
</select>
|
|
||||||
<span className="select-arrow"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-12 col-lg-12 col-xl-12">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<textarea className="form-input" placeholder="Questions/Comments" name="question"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<a className="button button-primary-outline min-w-[150px]" href="search-results.html">
|
|
||||||
Send
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,66 +1,23 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FormBlock } from "@/components/blocks/Form";
|
||||||
|
import { useFormQuery } from "@/services/hooks/form";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function ContactFormSection() {
|
export default function ContactFormSection() {
|
||||||
|
const form = useFormQuery();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form._fetch(1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="box-1-cell height-fill context-dark bg-colorContactForm! z-20!">
|
<div className="box-1-cell height-fill context-dark bg-colorContactForm! z-20!">
|
||||||
<div className="box-1-bg-shape">
|
<div className="box-1-bg-shape">
|
||||||
<img className="box-1-bg-image" src="images/bg-shape-1.png" alt="" role="presentation" />
|
<img className="box-1-bg-image" src="images/bg-shape-1.png" alt="" role="presentation" />
|
||||||
</div>
|
</div>
|
||||||
<div className="cell-inner box-1-outer">
|
<div className="cell-inner box-1-outer">
|
||||||
<div className="box-1">
|
<div className="box-1">{!form.isFetching && !!form.data && <FormBlock form={form.data} />}</div>
|
||||||
<h2>Get in Touch with Us Today!</h2>
|
|
||||||
<form className="rd-form">
|
|
||||||
<div className="row row-x-20 row-20">
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input className="form-input" placeholder="First Name" name="first_name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input className="form-input" placeholder="Last Name" name="last_name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input type="email" className="form-input" placeholder="E-Mail" name="email" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<input className="form-input" placeholder="Phone Number" name="phone" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-12 col-lg-12 col-xl-12">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<select
|
|
||||||
className="form-input select-filter"
|
|
||||||
name="interest"
|
|
||||||
data-style="modern"
|
|
||||||
data-classname="select-dropdown-context-dark"
|
|
||||||
data-placeholder="I'm interested in"
|
|
||||||
data-minimum-results-for-search="Infinity"
|
|
||||||
data-constraints="@Required"
|
|
||||||
>
|
|
||||||
<option label="placeholder"></option>
|
|
||||||
<option value="2">Buying</option>
|
|
||||||
<option value="3">Selling</option>
|
|
||||||
<option value="4">Renting</option>
|
|
||||||
</select>
|
|
||||||
<span className="select-arrow"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-12 col-lg-12 col-xl-12">
|
|
||||||
<div className="form-wrap form-wrap-validation">
|
|
||||||
<textarea className="form-input" placeholder="Questions/Comments" name="question"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6 col-lg-12 col-xl-6">
|
|
||||||
<a className="button button-primary-outline min-w-[150px]" href="search-results.html">
|
|
||||||
Send
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Autoplay } from "swiper/modules";
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import ContactFormBox from "./ContactFormBox";
|
import ContactFormBox from "./ContactFormBox";
|
||||||
import { Autoplay } from "swiper/modules";
|
|
||||||
import { useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
type HeroSliderProps = {
|
type HeroSliderProps = {
|
||||||
onClickBook?: () => void;
|
onClickBook?: () => void;
|
||||||
|
26
src/components/blocks/Content/index.tsx
Normal file
26
src/components/blocks/Content/index.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { RichText } from "@payloadcms/richtext-lexical/react";
|
||||||
|
|
||||||
|
// type Props = extract
|
||||||
|
|
||||||
|
export function ContentBlock(props: any) {
|
||||||
|
return (
|
||||||
|
<div className="container relative">
|
||||||
|
<div className="row">
|
||||||
|
{/* Content */}
|
||||||
|
<div className="col-md-10 offset-md-1 col-lg-10 offset-lg-1">
|
||||||
|
{/* Post */}
|
||||||
|
<div className="blog-item mb-10">
|
||||||
|
<div className="blog-item-body">
|
||||||
|
<div>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<RichText data={props.content} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* End Post */}
|
||||||
|
</div>
|
||||||
|
{/* End Content */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
54
src/components/blocks/Form/Button/index.tsx
Normal file
54
src/components/blocks/Form/Button/index.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import type { ElementType } from "react";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
appearance?: "default" | "primary" | "secondary";
|
||||||
|
className?: string;
|
||||||
|
el?: "a" | "button" | "link";
|
||||||
|
form?: string;
|
||||||
|
href?: string;
|
||||||
|
label?: string;
|
||||||
|
newTab?: boolean | null;
|
||||||
|
onClick?: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Button: React.FC<Props> = ({
|
||||||
|
className: classNameFromProps,
|
||||||
|
el = "button",
|
||||||
|
form,
|
||||||
|
href,
|
||||||
|
label,
|
||||||
|
newTab,
|
||||||
|
isLoading,
|
||||||
|
}) => {
|
||||||
|
const newTabProps = newTab ? { rel: "noopener noreferrer", target: "_blank" } : {};
|
||||||
|
const Element: ElementType = el;
|
||||||
|
const className = [classNameFromProps].filter(Boolean).join(" ");
|
||||||
|
|
||||||
|
const elementProps = {
|
||||||
|
...newTabProps,
|
||||||
|
className,
|
||||||
|
form,
|
||||||
|
href,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Element {...elementProps} className="button button-primary-outline" {...{ disabled: isLoading }}>
|
||||||
|
<React.Fragment>
|
||||||
|
{(el === "link" || el === "a") && (
|
||||||
|
<Link {...newTabProps} href={href || ""}>
|
||||||
|
{label}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{el === "button" && (
|
||||||
|
<>
|
||||||
|
{label} {isLoading && <span className="fa fa-spinner animate-spin ml-2" />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
</Element>
|
||||||
|
);
|
||||||
|
};
|
34
src/components/blocks/Form/Checkbox/index.tsx
Normal file
34
src/components/blocks/Form/Checkbox/index.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { CheckboxField } from "@payloadcms/plugin-form-builder/types";
|
||||||
|
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Error } from "../Error";
|
||||||
|
import { Width } from "../Width";
|
||||||
|
|
||||||
|
export const Checkbox: React.FC<
|
||||||
|
{
|
||||||
|
errors: Partial<
|
||||||
|
FieldErrorsImpl<{
|
||||||
|
[x: string]: any;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
getValues: any;
|
||||||
|
register: UseFormRegister<any & FieldValues>;
|
||||||
|
setValue: any;
|
||||||
|
} & CheckboxField
|
||||||
|
> = ({ name, errors, label, register, required: requiredFromProps, width }) => {
|
||||||
|
return (
|
||||||
|
<Width width={width}>
|
||||||
|
<div className="form-wrap pr-4">
|
||||||
|
<div className="flex">
|
||||||
|
<input className="form-input mr-2 p-2" type="checkbox" {...register(name, { required: requiredFromProps })} />
|
||||||
|
<label className="form-label" htmlFor={name}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{requiredFromProps && errors[name] && <Error />}
|
||||||
|
</div>
|
||||||
|
</Width>
|
||||||
|
);
|
||||||
|
};
|
35
src/components/blocks/Form/Email/index.tsx
Normal file
35
src/components/blocks/Form/Email/index.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { EmailField } from "@payloadcms/plugin-form-builder/types";
|
||||||
|
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Error } from "../Error";
|
||||||
|
import { Width } from "../Width";
|
||||||
|
|
||||||
|
export const Email: React.FC<
|
||||||
|
{
|
||||||
|
errors: Partial<
|
||||||
|
FieldErrorsImpl<{
|
||||||
|
[x: string]: any;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
register: UseFormRegister<any & FieldValues>;
|
||||||
|
} & EmailField
|
||||||
|
> = ({ name, errors, label, register, required: requiredFromProps, width }) => {
|
||||||
|
return (
|
||||||
|
<Width width={width}>
|
||||||
|
<div className="form-wrap">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
className="form-input"
|
||||||
|
id={name}
|
||||||
|
{...register(name, { pattern: /^\S[^\s@]*@\S+$/, required: requiredFromProps })}
|
||||||
|
/>
|
||||||
|
<label className="form-label" htmlFor={name}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{requiredFromProps && errors[name] && <Error />}
|
||||||
|
</div>
|
||||||
|
</Width>
|
||||||
|
);
|
||||||
|
};
|
4
src/components/blocks/Form/Error/index.scss
Normal file
4
src/components/blocks/Form/Error/index.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.error {
|
||||||
|
margin-top: 5px;
|
||||||
|
color: var(--color-red);
|
||||||
|
}
|
5
src/components/blocks/Form/Error/index.tsx
Normal file
5
src/components/blocks/Form/Error/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export const Error: React.FC = () => {
|
||||||
|
return <div className="text-red-500">This field is required</div>;
|
||||||
|
};
|
72
src/components/blocks/Form/Link/index.tsx
Normal file
72
src/components/blocks/Form/Link/index.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import type { Page } from "@/payload-types";
|
||||||
|
|
||||||
|
import { Button } from "../Button";
|
||||||
|
|
||||||
|
export type CMSLinkType = {
|
||||||
|
appearance?: "default" | "primary" | "secondary";
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
label?: string;
|
||||||
|
newTab?: boolean | null;
|
||||||
|
reference?: {
|
||||||
|
relationTo: "pages";
|
||||||
|
value: number | Page | string;
|
||||||
|
} | null;
|
||||||
|
type?: "custom" | "reference" | null;
|
||||||
|
url?: null | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CMSLink: React.FC<CMSLinkType> = ({
|
||||||
|
type,
|
||||||
|
appearance,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
newTab,
|
||||||
|
reference,
|
||||||
|
url,
|
||||||
|
}) => {
|
||||||
|
const href =
|
||||||
|
type === "reference" && typeof reference?.value === "object" && reference.value.slug
|
||||||
|
? `${reference?.relationTo !== "pages" ? `/${reference?.relationTo}` : ""}/${reference.value.slug}`
|
||||||
|
: url;
|
||||||
|
|
||||||
|
if (!href) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!appearance) {
|
||||||
|
const newTabProps = newTab ? { rel: "noopener noreferrer", target: "_blank" } : {};
|
||||||
|
|
||||||
|
if (type === "custom") {
|
||||||
|
return (
|
||||||
|
<a href={url || ""} {...newTabProps} className={className}>
|
||||||
|
{label && label}
|
||||||
|
{children ? <>{children}</> : null}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
return (
|
||||||
|
<Link href={href} {...newTabProps} className={className} prefetch={false}>
|
||||||
|
{label && label}
|
||||||
|
{children ? <>{children}</> : null}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonProps = {
|
||||||
|
appearance,
|
||||||
|
href,
|
||||||
|
label,
|
||||||
|
newTab,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Button className={className} {...buttonProps} el="link" />;
|
||||||
|
};
|
30
src/components/blocks/Form/Number/index.tsx
Normal file
30
src/components/blocks/Form/Number/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { TextField } from "@payloadcms/plugin-form-builder/types";
|
||||||
|
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Error } from "../Error";
|
||||||
|
import { Width } from "../Width";
|
||||||
|
|
||||||
|
export const Number: React.FC<
|
||||||
|
{
|
||||||
|
errors: Partial<
|
||||||
|
FieldErrorsImpl<{
|
||||||
|
[x: string]: any;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
register: UseFormRegister<any & FieldValues>;
|
||||||
|
} & TextField
|
||||||
|
> = ({ name, errors, label, register, required: requiredFromProps, width }) => {
|
||||||
|
return (
|
||||||
|
<Width width={width}>
|
||||||
|
<div className="form-wrap">
|
||||||
|
<input type="number" className="form-input" id={name} {...register(name, { required: requiredFromProps })} />
|
||||||
|
<label className="form-label" htmlFor={name}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{requiredFromProps && errors[name] && <Error />}
|
||||||
|
</div>
|
||||||
|
</Width>
|
||||||
|
);
|
||||||
|
};
|
24
src/components/blocks/Form/RichText/index.tsx
Normal file
24
src/components/blocks/Form/RichText/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { serializeLexical } from './serialize'
|
||||||
|
|
||||||
|
const RichText: React.FC<{ className?: string; content: any; enableGutter?: boolean }> = ({
|
||||||
|
className,
|
||||||
|
content,
|
||||||
|
}) => {
|
||||||
|
if (!content) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={[className].filter(Boolean).join(' ')}>
|
||||||
|
{content &&
|
||||||
|
!Array.isArray(content) &&
|
||||||
|
typeof content === 'object' &&
|
||||||
|
'root' in content &&
|
||||||
|
serializeLexical({ nodes: content?.root?.children })}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RichText
|
118
src/components/blocks/Form/RichText/nodeFormat.tsx
Normal file
118
src/components/blocks/Form/RichText/nodeFormat.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
//This copy-and-pasted from lexical here here: https://github.com/facebook/lexical/blob/c2ceee223f46543d12c574e62155e619f9a18a5d/packages/lexical/src/LexicalConstants.ts
|
||||||
|
|
||||||
|
import type { ElementFormatType, TextFormatType } from "@payloadcms/richtext-lexical/lexical";
|
||||||
|
import type { TextDetailType, TextModeType } from "lexical/nodes/LexicalTextNode";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// DOM
|
||||||
|
export const DOM_ELEMENT_TYPE = 1;
|
||||||
|
export const DOM_TEXT_TYPE = 3;
|
||||||
|
|
||||||
|
// Reconciling
|
||||||
|
export const NO_DIRTY_NODES = 0;
|
||||||
|
export const HAS_DIRTY_NODES = 1;
|
||||||
|
export const FULL_RECONCILE = 2;
|
||||||
|
|
||||||
|
// Text node modes
|
||||||
|
export const IS_NORMAL = 0;
|
||||||
|
export const IS_TOKEN = 1;
|
||||||
|
export const IS_SEGMENTED = 2;
|
||||||
|
// IS_INERT = 3
|
||||||
|
|
||||||
|
// Text node formatting
|
||||||
|
export const IS_BOLD = 1;
|
||||||
|
export const IS_ITALIC = 1 << 1;
|
||||||
|
export const IS_STRIKETHROUGH = 1 << 2;
|
||||||
|
export const IS_UNDERLINE = 1 << 3;
|
||||||
|
export const IS_CODE = 1 << 4;
|
||||||
|
export const IS_SUBSCRIPT = 1 << 5;
|
||||||
|
export const IS_SUPERSCRIPT = 1 << 6;
|
||||||
|
export const IS_HIGHLIGHT = 1 << 7;
|
||||||
|
|
||||||
|
export const IS_ALL_FORMATTING =
|
||||||
|
IS_BOLD | IS_ITALIC | IS_STRIKETHROUGH | IS_UNDERLINE | IS_CODE | IS_SUBSCRIPT | IS_SUPERSCRIPT | IS_HIGHLIGHT;
|
||||||
|
|
||||||
|
// Text node details
|
||||||
|
export const IS_DIRECTIONLESS = 1;
|
||||||
|
export const IS_UNMERGEABLE = 1 << 1;
|
||||||
|
|
||||||
|
// Element node formatting
|
||||||
|
export const IS_ALIGN_LEFT = 1;
|
||||||
|
export const IS_ALIGN_CENTER = 2;
|
||||||
|
export const IS_ALIGN_RIGHT = 3;
|
||||||
|
export const IS_ALIGN_JUSTIFY = 4;
|
||||||
|
export const IS_ALIGN_START = 5;
|
||||||
|
export const IS_ALIGN_END = 6;
|
||||||
|
|
||||||
|
// Reconciliation
|
||||||
|
export const NON_BREAKING_SPACE = "\u00A0";
|
||||||
|
// const ZERO_WIDTH_SPACE = '\u200b'
|
||||||
|
|
||||||
|
export const DOUBLE_LINE_BREAK = "\n\n";
|
||||||
|
|
||||||
|
// For FF, we need to use a non-breaking space, or it gets composition
|
||||||
|
// in a stuck state.
|
||||||
|
|
||||||
|
const RTL = "\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";
|
||||||
|
const LTR =
|
||||||
|
"A-Za-z\u00C0-\u00D6\u00D8-\u00F6" +
|
||||||
|
"\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C" +
|
||||||
|
"\uFE00-\uFE6F\uFEFD-\uFFFF";
|
||||||
|
|
||||||
|
export const RTL_REGEX = new RegExp("^[^" + LTR + "]*[" + RTL + "]");
|
||||||
|
|
||||||
|
export const LTR_REGEX = new RegExp("^[^" + RTL + "]*[" + LTR + "]");
|
||||||
|
|
||||||
|
export const TEXT_TYPE_TO_FORMAT: Record<string | TextFormatType, number> = {
|
||||||
|
bold: IS_BOLD,
|
||||||
|
code: IS_CODE,
|
||||||
|
highlight: IS_HIGHLIGHT,
|
||||||
|
italic: IS_ITALIC,
|
||||||
|
strikethrough: IS_STRIKETHROUGH,
|
||||||
|
subscript: IS_SUBSCRIPT,
|
||||||
|
superscript: IS_SUPERSCRIPT,
|
||||||
|
underline: IS_UNDERLINE,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DETAIL_TYPE_TO_DETAIL: Record<string | TextDetailType, number> = {
|
||||||
|
directionless: IS_DIRECTIONLESS,
|
||||||
|
unmergeable: IS_UNMERGEABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ELEMENT_TYPE_TO_FORMAT: Record<Exclude<ElementFormatType, "">, number> = {
|
||||||
|
center: IS_ALIGN_CENTER,
|
||||||
|
end: IS_ALIGN_END,
|
||||||
|
justify: IS_ALIGN_JUSTIFY,
|
||||||
|
left: IS_ALIGN_LEFT,
|
||||||
|
right: IS_ALIGN_RIGHT,
|
||||||
|
start: IS_ALIGN_START,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ELEMENT_FORMAT_TO_TYPE: Record<number, ElementFormatType> = {
|
||||||
|
[IS_ALIGN_CENTER]: "center",
|
||||||
|
[IS_ALIGN_END]: "end",
|
||||||
|
[IS_ALIGN_JUSTIFY]: "justify",
|
||||||
|
[IS_ALIGN_LEFT]: "left",
|
||||||
|
[IS_ALIGN_RIGHT]: "right",
|
||||||
|
[IS_ALIGN_START]: "start",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEXT_MODE_TO_TYPE: Record<TextModeType, 0 | 1 | 2> = {
|
||||||
|
normal: IS_NORMAL,
|
||||||
|
segmented: IS_SEGMENTED,
|
||||||
|
token: IS_TOKEN,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEXT_TYPE_TO_MODE: Record<number, TextModeType> = {
|
||||||
|
[IS_NORMAL]: "normal",
|
||||||
|
[IS_SEGMENTED]: "segmented",
|
||||||
|
[IS_TOKEN]: "token",
|
||||||
|
};
|
173
src/components/blocks/Form/RichText/serialize.tsx
Normal file
173
src/components/blocks/Form/RichText/serialize.tsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import type { LinkFields, SerializedLinkNode } from "@payloadcms/richtext-lexical";
|
||||||
|
import type {
|
||||||
|
SerializedElementNode,
|
||||||
|
SerializedLexicalNode,
|
||||||
|
SerializedTextNode,
|
||||||
|
} from "@payloadcms/richtext-lexical/lexical";
|
||||||
|
import type { SerializedListItemNode, SerializedListNode } from "@payloadcms/richtext-lexical/lexical/list";
|
||||||
|
import type { SerializedHeadingNode } from "@payloadcms/richtext-lexical/lexical/rich-text";
|
||||||
|
import type { JSX } from "react";
|
||||||
|
|
||||||
|
import React, { Fragment } from "react";
|
||||||
|
|
||||||
|
import { CMSLink } from "../Link";
|
||||||
|
import {
|
||||||
|
IS_BOLD,
|
||||||
|
IS_CODE,
|
||||||
|
IS_ITALIC,
|
||||||
|
IS_STRIKETHROUGH,
|
||||||
|
IS_SUBSCRIPT,
|
||||||
|
IS_SUPERSCRIPT,
|
||||||
|
IS_UNDERLINE,
|
||||||
|
} from "./nodeFormat";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
nodes: SerializedLexicalNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeLexical({ nodes }: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{nodes?.map((_node, index): JSX.Element | null => {
|
||||||
|
if (_node.type === "text") {
|
||||||
|
const node = _node as SerializedTextNode;
|
||||||
|
let text = <React.Fragment key={index}>{node.text}</React.Fragment>;
|
||||||
|
if (node.format & IS_BOLD) {
|
||||||
|
text = <strong key={index}>{text}</strong>;
|
||||||
|
}
|
||||||
|
if (node.format & IS_ITALIC) {
|
||||||
|
text = <em key={index}>{text}</em>;
|
||||||
|
}
|
||||||
|
if (node.format & IS_STRIKETHROUGH) {
|
||||||
|
text = (
|
||||||
|
<span key={index} style={{ textDecoration: "line-through" }}>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (node.format & IS_UNDERLINE) {
|
||||||
|
text = (
|
||||||
|
<span key={index} style={{ textDecoration: "underline" }}>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (node.format & IS_CODE) {
|
||||||
|
text = <code key={index}>{node.text}</code>;
|
||||||
|
}
|
||||||
|
if (node.format & IS_SUBSCRIPT) {
|
||||||
|
text = <sub key={index}>{text}</sub>;
|
||||||
|
}
|
||||||
|
if (node.format & IS_SUPERSCRIPT) {
|
||||||
|
text = <sup key={index}>{text}</sup>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Hacky fix for
|
||||||
|
// https://github.com/facebook/lexical/blob/d10c4e6e55261b2fdd7d1845aed46151d0f06a8c/packages/lexical-list/src/LexicalListItemNode.ts#L133
|
||||||
|
// which does not return checked: false (only true - i.e. there is no prop for false)
|
||||||
|
const serializedChildrenFn = (node: SerializedElementNode): JSX.Element | null => {
|
||||||
|
if (node.children == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (node?.type === "list" && (node as SerializedListNode)?.listType === "check") {
|
||||||
|
for (const item of node.children) {
|
||||||
|
if ("checked" in item) {
|
||||||
|
if (!item?.checked) {
|
||||||
|
item.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serializeLexical({ nodes: node.children });
|
||||||
|
} else {
|
||||||
|
return serializeLexical({ nodes: node.children });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const serializedChildren = "children" in _node ? serializedChildrenFn(_node as SerializedElementNode) : "";
|
||||||
|
|
||||||
|
switch (_node.type) {
|
||||||
|
case "heading": {
|
||||||
|
const node = _node as SerializedHeadingNode;
|
||||||
|
|
||||||
|
type Heading = Extract<keyof JSX.IntrinsicElements, "h1" | "h2" | "h3" | "h4" | "h5">;
|
||||||
|
const Tag = node?.tag as Heading;
|
||||||
|
return <Tag key={index}>{serializedChildren}</Tag>;
|
||||||
|
}
|
||||||
|
case "linebreak": {
|
||||||
|
return <br key={index} />;
|
||||||
|
}
|
||||||
|
case "link": {
|
||||||
|
const node = _node as SerializedLinkNode;
|
||||||
|
|
||||||
|
const fields: LinkFields = node.fields;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CMSLink
|
||||||
|
key={index}
|
||||||
|
newTab={Boolean(fields?.newTab)}
|
||||||
|
reference={fields.doc as any}
|
||||||
|
type={fields.linkType === "internal" ? "reference" : "custom"}
|
||||||
|
url={fields.url}
|
||||||
|
>
|
||||||
|
{serializedChildren}
|
||||||
|
</CMSLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "list": {
|
||||||
|
const node = _node as SerializedListNode;
|
||||||
|
|
||||||
|
type List = Extract<keyof JSX.IntrinsicElements, "ol" | "ul">;
|
||||||
|
const Tag = node?.tag as List;
|
||||||
|
return (
|
||||||
|
<Tag className="list" key={index}>
|
||||||
|
{serializedChildren}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "listitem": {
|
||||||
|
const node = _node as SerializedListItemNode;
|
||||||
|
|
||||||
|
if (node?.checked != null) {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
aria-checked={node.checked ? "true" : "false"}
|
||||||
|
className={` ${node.checked ? "" : ""}`}
|
||||||
|
key={index}
|
||||||
|
role="checkbox"
|
||||||
|
tabIndex={-1}
|
||||||
|
value={node?.value}
|
||||||
|
>
|
||||||
|
{serializedChildren}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<li key={index} value={node?.value}>
|
||||||
|
{serializedChildren}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "paragraph": {
|
||||||
|
return <p key={index}>{serializedChildren}</p>;
|
||||||
|
}
|
||||||
|
case "quote": {
|
||||||
|
return <blockquote key={index}>{serializedChildren}</blockquote>;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
54
src/components/blocks/Form/Select/index.tsx
Normal file
54
src/components/blocks/Form/Select/index.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import type { SelectField } from "@payloadcms/plugin-form-builder/types";
|
||||||
|
import type { Control, FieldErrorsImpl, FieldValues } from "react-hook-form";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
import { Error } from "../Error";
|
||||||
|
import { Width } from "../Width";
|
||||||
|
|
||||||
|
export const Select: React.FC<
|
||||||
|
{
|
||||||
|
control: Control<FieldValues, any>;
|
||||||
|
errors: Partial<
|
||||||
|
FieldErrorsImpl<{
|
||||||
|
[x: string]: any;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
} & SelectField
|
||||||
|
> = ({ name, control, errors, label, options, required, width }) => {
|
||||||
|
return (
|
||||||
|
<Width width={width}>
|
||||||
|
<div className="form-wrap relative">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name={name}
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<>
|
||||||
|
{/* <ReactSelect
|
||||||
|
classNamePrefix="rs"
|
||||||
|
inputId={name}
|
||||||
|
instanceId={name}
|
||||||
|
onChange={(val) => onChange(val ? val.value : "")}
|
||||||
|
options={options}
|
||||||
|
value={options.find((s) => s.value === value)}
|
||||||
|
/> */}
|
||||||
|
<select className="form-input select-filter" name="interest" onChange={onChange} value={value}>
|
||||||
|
<option value="">{label}</option>
|
||||||
|
{options.map((opt, idx) => (
|
||||||
|
<option key={idx} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<span className="absolute right-2 top-3 fa fa-caret-down text-black"></span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
rules={{ required }}
|
||||||
|
/>
|
||||||
|
{required && errors[name] && <Error />}
|
||||||
|
</div>
|
||||||
|
</Width>
|
||||||
|
);
|
||||||
|
};
|
30
src/components/blocks/Form/Text/index.tsx
Normal file
30
src/components/blocks/Form/Text/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { TextField } from "@payloadcms/plugin-form-builder/types";
|
||||||
|
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Error } from "../Error";
|
||||||
|
import { Width } from "../Width";
|
||||||
|
|
||||||
|
export const Text: React.FC<
|
||||||
|
{
|
||||||
|
errors: Partial<
|
||||||
|
FieldErrorsImpl<{
|
||||||
|
[x: string]: any;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
register: UseFormRegister<any & FieldValues>;
|
||||||
|
} & TextField
|
||||||
|
> = ({ name, errors, label, register, required: requiredFromProps, width }) => {
|
||||||
|
return (
|
||||||
|
<Width width={width}>
|
||||||
|
<div className="form-wrap">
|
||||||
|
<input type="text" id={name} className="form-input" {...register(name, { required: requiredFromProps })} />
|
||||||
|
<label className="form-label" htmlFor={name}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{requiredFromProps && errors[name] && <Error />}
|
||||||
|
</div>
|
||||||
|
</Width>
|
||||||
|
);
|
||||||
|
};
|
31
src/components/blocks/Form/Textarea/index.tsx
Normal file
31
src/components/blocks/Form/Textarea/index.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { TextField } from "@payloadcms/plugin-form-builder/types";
|
||||||
|
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Error } from "../Error";
|
||||||
|
import { Width } from "../Width";
|
||||||
|
|
||||||
|
export const Textarea: React.FC<
|
||||||
|
{
|
||||||
|
errors: Partial<
|
||||||
|
FieldErrorsImpl<{
|
||||||
|
[x: string]: any;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
register: UseFormRegister<any & FieldValues>;
|
||||||
|
rows?: number;
|
||||||
|
} & TextField
|
||||||
|
> = ({ name, errors, label, register, required: requiredFromProps, rows = 3, width }) => {
|
||||||
|
return (
|
||||||
|
<Width width={width}>
|
||||||
|
<div className="form-wrap">
|
||||||
|
<textarea className="form-input" id={name} rows={rows} {...register(name, { required: requiredFromProps })} />
|
||||||
|
<label className="form-label" htmlFor={name}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{requiredFromProps && errors[name] && <Error />}
|
||||||
|
</div>
|
||||||
|
</Width>
|
||||||
|
);
|
||||||
|
};
|
13
src/components/blocks/Form/Width/index.tsx
Normal file
13
src/components/blocks/Form/Width/index.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export const Width: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
width?: number;
|
||||||
|
}> = ({ children, width }) => {
|
||||||
|
let colXl = "col-xl-12";
|
||||||
|
if (!!width && width === 50) {
|
||||||
|
colXl = "col-xl-6";
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={`col-sm-6 col-lg-12 ${colXl}`}>{children}</div>;
|
||||||
|
};
|
43
src/components/blocks/Form/buildInitialFormState.tsx
Normal file
43
src/components/blocks/Form/buildInitialFormState.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import type { FormFieldBlock } from "@payloadcms/plugin-form-builder/types";
|
||||||
|
|
||||||
|
export const buildInitialFormState = (fields: FormFieldBlock[]) => {
|
||||||
|
return fields.reduce((initialSchema, field) => {
|
||||||
|
if (field.blockType === "checkbox") {
|
||||||
|
return {
|
||||||
|
...initialSchema,
|
||||||
|
[field.name]: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (field.blockType === "country") {
|
||||||
|
return {
|
||||||
|
...initialSchema,
|
||||||
|
[field.name]: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (field.blockType === "email") {
|
||||||
|
return {
|
||||||
|
...initialSchema,
|
||||||
|
[field.name]: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (field.blockType === "text") {
|
||||||
|
return {
|
||||||
|
...initialSchema,
|
||||||
|
[field.name]: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (field.blockType === "select") {
|
||||||
|
return {
|
||||||
|
...initialSchema,
|
||||||
|
[field.name]: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (field.blockType === "state") {
|
||||||
|
return {
|
||||||
|
...initialSchema,
|
||||||
|
[field.name]: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {});
|
||||||
|
};
|
15
src/components/blocks/Form/fields.tsx
Normal file
15
src/components/blocks/Form/fields.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Checkbox } from "./Checkbox";
|
||||||
|
import { Email } from "./Email";
|
||||||
|
import { Number } from "./Number";
|
||||||
|
import { Select } from "./Select";
|
||||||
|
import { Text } from "./Text";
|
||||||
|
import { Textarea } from "./Textarea";
|
||||||
|
|
||||||
|
export const fields = {
|
||||||
|
checkbox: Checkbox,
|
||||||
|
email: Email,
|
||||||
|
number: Number,
|
||||||
|
select: Select,
|
||||||
|
text: Text,
|
||||||
|
textarea: Textarea,
|
||||||
|
};
|
180
src/components/blocks/Form/index.tsx
Normal file
180
src/components/blocks/Form/index.tsx
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import React, { useCallback, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
import { Form } from "@/payload-types";
|
||||||
|
import { Button } from "./Button";
|
||||||
|
import RichText from "./RichText";
|
||||||
|
import { buildInitialFormState } from "./buildInitialFormState";
|
||||||
|
import { fields } from "./fields";
|
||||||
|
|
||||||
|
export type Value = unknown;
|
||||||
|
|
||||||
|
export interface Property {
|
||||||
|
[key: string]: Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Data {
|
||||||
|
[key: string]: Property | Property[] | Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormBlockType = {
|
||||||
|
blockName?: string;
|
||||||
|
blockType?: "formBlock";
|
||||||
|
enableIntro?: boolean | null;
|
||||||
|
form: number | Form;
|
||||||
|
introContent?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ("ltr" | "rtl") | null;
|
||||||
|
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormBlock: React.FC<
|
||||||
|
FormBlockType & {
|
||||||
|
id?: string | null;
|
||||||
|
}
|
||||||
|
> = (props) => {
|
||||||
|
const {
|
||||||
|
enableIntro,
|
||||||
|
form: formFromProps,
|
||||||
|
form: { id: formID, confirmationMessage, confirmationType, redirect, submitButtonLabel } = {},
|
||||||
|
introContent,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const formMethods = useForm({
|
||||||
|
defaultValues: buildInitialFormState(formFromProps.fields),
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
// getValues,
|
||||||
|
// setValue,
|
||||||
|
} = formMethods;
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [hasSubmitted, setHasSubmitted] = useState<boolean>();
|
||||||
|
const [error, setError] = useState<{ message: string; status?: string } | undefined>();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(data: Data) => {
|
||||||
|
let loadingTimerID: ReturnType<typeof setTimeout>;
|
||||||
|
const submitForm = async () => {
|
||||||
|
setError(undefined);
|
||||||
|
|
||||||
|
const dataToSend = Object.entries(data).map(([name, value]) => ({
|
||||||
|
field: name,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// delay loading indicator by 1s
|
||||||
|
loadingTimerID = setTimeout(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const req = await fetch(`/api/form-submissions`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
form: formID,
|
||||||
|
submissionData: dataToSend,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await req.json();
|
||||||
|
|
||||||
|
clearTimeout(loadingTimerID);
|
||||||
|
|
||||||
|
if (req.status >= 400) {
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
setError({
|
||||||
|
message: res.errors?.[0]?.message || "Internal Server Error",
|
||||||
|
status: res.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
setHasSubmitted(true);
|
||||||
|
|
||||||
|
if (confirmationType === "redirect" && redirect) {
|
||||||
|
const { url } = redirect;
|
||||||
|
|
||||||
|
const redirectUrl = url;
|
||||||
|
|
||||||
|
if (redirectUrl) router.push(redirectUrl);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
setIsLoading(false);
|
||||||
|
setError({
|
||||||
|
message: "Something went wrong.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void submitForm();
|
||||||
|
},
|
||||||
|
[router, formID, redirect, confirmationType]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{!!formFromProps?.title && <h2>{formFromProps.title}</h2>}
|
||||||
|
<div className="mt-4">
|
||||||
|
{!!enableIntro && introContent && !hasSubmitted && <RichText content={introContent} />}
|
||||||
|
{!isLoading && hasSubmitted && confirmationType === "message" && <RichText content={confirmationMessage} />}
|
||||||
|
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
||||||
|
{error && <div className="text-red-500">{`${error.status || "500"}: ${error.message || ""}`}</div>}
|
||||||
|
{!hasSubmitted && (
|
||||||
|
<form id={formID} onSubmit={handleSubmit(onSubmit)} className="rd-form">
|
||||||
|
<div className="row space-y-4">
|
||||||
|
{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}
|
||||||
|
control={control}
|
||||||
|
errors={errors}
|
||||||
|
register={register}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Button appearance="primary" el="button" form={formID} label={submitButtonLabel} isLoading={isLoading} />
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
42
src/components/blocks/Form/shared.scss
Normal file
42
src/components/blocks/Form/shared.scss
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/components/blocks/RenderBlocks.tsx
Normal file
56
src/components/blocks/RenderBlocks.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React, { Fragment } from "react";
|
||||||
|
|
||||||
|
import type { Page } from "@/payload-types";
|
||||||
|
import { ContentBlock } from "./Content";
|
||||||
|
import { FormBlock } from "./Form";
|
||||||
|
|
||||||
|
const blockComponents = {
|
||||||
|
contentBlock: ContentBlock,
|
||||||
|
formBlock: FormBlock,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RenderBlocks: 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 { blockType } = block;
|
||||||
|
|
||||||
|
if (blockType && blockType in blockComponents) {
|
||||||
|
const Block = blockComponents[blockType];
|
||||||
|
if (!Block) return null;
|
||||||
|
|
||||||
|
if (blockType === "formBlock") {
|
||||||
|
return (
|
||||||
|
<div className="my-10" key={index}>
|
||||||
|
<FormBlock
|
||||||
|
id={block.id}
|
||||||
|
enableIntro={block.enableIntro}
|
||||||
|
introContent={block.introContent}
|
||||||
|
form={block.form}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="my-10" key={index}>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Block {...block} disableInnerContainer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef } from "react";
|
|
||||||
import HeroSlider from "@/components/HeroSlider";
|
import HeroSlider from "@/components/HeroSlider";
|
||||||
import ContactFormSection from "@/components/ContactFormSection";
|
import { useRef } from "react";
|
||||||
|
import ContactFormSection from "../ContactFormSection";
|
||||||
|
|
||||||
export default function HomeTopSection() {
|
export default function HomeTopSection() {
|
||||||
const formRef = useRef<HTMLDivElement | null>(null);
|
const formRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
@ -74,6 +74,9 @@ export interface Config {
|
|||||||
blogs: Blog;
|
blogs: Blog;
|
||||||
propertyFeatures: PropertyFeature;
|
propertyFeatures: PropertyFeature;
|
||||||
properties: Property;
|
properties: Property;
|
||||||
|
pages: Page;
|
||||||
|
forms: Form;
|
||||||
|
'form-submissions': FormSubmission;
|
||||||
'payload-locked-documents': PayloadLockedDocument;
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
'payload-preferences': PayloadPreference;
|
'payload-preferences': PayloadPreference;
|
||||||
'payload-migrations': PayloadMigration;
|
'payload-migrations': PayloadMigration;
|
||||||
@ -87,6 +90,9 @@ export interface Config {
|
|||||||
blogs: BlogsSelect<false> | BlogsSelect<true>;
|
blogs: BlogsSelect<false> | BlogsSelect<true>;
|
||||||
propertyFeatures: PropertyFeaturesSelect<false> | PropertyFeaturesSelect<true>;
|
propertyFeatures: PropertyFeaturesSelect<false> | PropertyFeaturesSelect<true>;
|
||||||
properties: PropertiesSelect<false> | PropertiesSelect<true>;
|
properties: PropertiesSelect<false> | PropertiesSelect<true>;
|
||||||
|
pages: PagesSelect<false> | PagesSelect<true>;
|
||||||
|
forms: FormsSelect<false> | FormsSelect<true>;
|
||||||
|
'form-submissions': FormSubmissionsSelect<false> | FormSubmissionsSelect<true>;
|
||||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||||
@ -285,6 +291,245 @@ export interface Property {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
_status?: ('draft' | 'published') | null;
|
_status?: ('draft' | 'published') | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "pages".
|
||||||
|
*/
|
||||||
|
export interface Page {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
hero_img?: (number | null) | Media;
|
||||||
|
slug?: string | null;
|
||||||
|
layout?:
|
||||||
|
| (
|
||||||
|
| {
|
||||||
|
content: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
};
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'contentBlock';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
form: number | Form;
|
||||||
|
enableIntro?: boolean | null;
|
||||||
|
introContent?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'formBlock';
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| null;
|
||||||
|
meta?: {
|
||||||
|
title?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
canonical_url?: string | null;
|
||||||
|
};
|
||||||
|
createdBy?: (number | null) | User;
|
||||||
|
updatedBy?: (number | null) | User;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
_status?: ('draft' | 'published') | null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "forms".
|
||||||
|
*/
|
||||||
|
export interface Form {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
fields?:
|
||||||
|
| (
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
defaultValue?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'checkbox';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'email';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
message?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'message';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: number | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'number';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
placeholder?: string | null;
|
||||||
|
options?:
|
||||||
|
| {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'select';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'text';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
label?: string | null;
|
||||||
|
width?: number | null;
|
||||||
|
defaultValue?: string | null;
|
||||||
|
required?: boolean | null;
|
||||||
|
id?: string | null;
|
||||||
|
blockName?: string | null;
|
||||||
|
blockType: 'textarea';
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| null;
|
||||||
|
submitButtonLabel?: string | null;
|
||||||
|
/**
|
||||||
|
* Choose whether to display an on-page message or redirect to a different page after they submit the form.
|
||||||
|
*/
|
||||||
|
confirmationType?: ('message' | 'redirect') | null;
|
||||||
|
confirmationMessage?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
redirect?: {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Send custom emails when the form submits. Use comma separated lists to send the same email to multiple recipients. To reference a value from this form, wrap that field's name with double curly brackets, i.e. {{firstName}}. You can use a wildcard {{*}} to output all data and {{*:table}} to format it as an HTML table in the email.
|
||||||
|
*/
|
||||||
|
emails?:
|
||||||
|
| {
|
||||||
|
emailTo?: string | null;
|
||||||
|
cc?: string | null;
|
||||||
|
bcc?: string | null;
|
||||||
|
replyTo?: string | null;
|
||||||
|
emailFrom?: string | null;
|
||||||
|
subject: string;
|
||||||
|
/**
|
||||||
|
* Enter the message that should be sent in this email.
|
||||||
|
*/
|
||||||
|
message?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "form-submissions".
|
||||||
|
*/
|
||||||
|
export interface FormSubmission {
|
||||||
|
id: number;
|
||||||
|
form: number | Form;
|
||||||
|
submissionData?:
|
||||||
|
| {
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-locked-documents".
|
* via the `definition` "payload-locked-documents".
|
||||||
@ -319,6 +564,18 @@ export interface PayloadLockedDocument {
|
|||||||
| ({
|
| ({
|
||||||
relationTo: 'properties';
|
relationTo: 'properties';
|
||||||
value: number | Property;
|
value: number | Property;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'pages';
|
||||||
|
value: number | Page;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'forms';
|
||||||
|
value: number | Form;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'form-submissions';
|
||||||
|
value: number | FormSubmission;
|
||||||
} | null);
|
} | null);
|
||||||
globalSlug?: string | null;
|
globalSlug?: string | null;
|
||||||
user: {
|
user: {
|
||||||
@ -494,6 +751,176 @@ export interface PropertiesSelect<T extends boolean = true> {
|
|||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
_status?: T;
|
_status?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "pages_select".
|
||||||
|
*/
|
||||||
|
export interface PagesSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
hero_img?: T;
|
||||||
|
slug?: T;
|
||||||
|
layout?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
contentBlock?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
content?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
formBlock?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
form?: T;
|
||||||
|
enableIntro?: T;
|
||||||
|
introContent?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
meta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
title?: T;
|
||||||
|
description?: T;
|
||||||
|
canonical_url?: T;
|
||||||
|
};
|
||||||
|
createdBy?: T;
|
||||||
|
updatedBy?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
_status?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "forms_select".
|
||||||
|
*/
|
||||||
|
export interface FormsSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
fields?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
checkbox?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
required?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
email?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
message?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
message?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
number?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
select?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
placeholder?: T;
|
||||||
|
options?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
label?: T;
|
||||||
|
value?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
text?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
textarea?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
name?: T;
|
||||||
|
label?: T;
|
||||||
|
width?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
required?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
submitButtonLabel?: T;
|
||||||
|
confirmationType?: T;
|
||||||
|
confirmationMessage?: T;
|
||||||
|
redirect?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
url?: T;
|
||||||
|
};
|
||||||
|
emails?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
emailTo?: T;
|
||||||
|
cc?: T;
|
||||||
|
bcc?: T;
|
||||||
|
replyTo?: T;
|
||||||
|
emailFrom?: T;
|
||||||
|
subject?: T;
|
||||||
|
message?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "form-submissions_select".
|
||||||
|
*/
|
||||||
|
export interface FormSubmissionsSelect<T extends boolean = true> {
|
||||||
|
form?: T;
|
||||||
|
submissionData?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
field?: T;
|
||||||
|
value?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-locked-documents_select".
|
* via the `definition` "payload-locked-documents_select".
|
||||||
|
@ -15,6 +15,8 @@ import { BlogCategories } from "@/collections/BlogCategories";
|
|||||||
import { Blogs } from "@/collections/Blogs";
|
import { Blogs } from "@/collections/Blogs";
|
||||||
import { PropertyFeatures } from "@/collections/PropertyFeatures";
|
import { PropertyFeatures } from "@/collections/PropertyFeatures";
|
||||||
import { Properties } from "./collections/Properties";
|
import { Properties } from "./collections/Properties";
|
||||||
|
import { Pages } from "@/collections/Pages";
|
||||||
|
import { formBuilderPlugin } from "@payloadcms/plugin-form-builder";
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url);
|
const filename = fileURLToPath(import.meta.url);
|
||||||
const dirname = path.dirname(filename);
|
const dirname = path.dirname(filename);
|
||||||
@ -39,7 +41,7 @@ export default buildConfig({
|
|||||||
},
|
},
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
},
|
},
|
||||||
collections: [Users, Media, BlogTags, BlogCategories, Blogs, PropertyFeatures, Properties],
|
collections: [Users, Media, BlogTags, BlogCategories, Blogs, PropertyFeatures, Properties, Pages],
|
||||||
editor: lexicalEditor(),
|
editor: lexicalEditor(),
|
||||||
secret: process.env.PAYLOAD_SECRET || "",
|
secret: process.env.PAYLOAD_SECRET || "",
|
||||||
typescript: {
|
typescript: {
|
||||||
@ -71,5 +73,55 @@ export default buildConfig({
|
|||||||
endpoint: process.env.S3_ENDPOINT,
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
// form builder
|
||||||
|
formBuilderPlugin({
|
||||||
|
fields: {
|
||||||
|
checkbox: true,
|
||||||
|
email: true,
|
||||||
|
number: true,
|
||||||
|
select: true,
|
||||||
|
text: true,
|
||||||
|
textarea: true,
|
||||||
|
country: false,
|
||||||
|
state: false,
|
||||||
|
payment: false,
|
||||||
|
},
|
||||||
|
formOverrides: {
|
||||||
|
fields: undefined,
|
||||||
|
admin: {
|
||||||
|
group: "General",
|
||||||
|
useAsTitle: "title",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formSubmissionOverrides: {
|
||||||
|
fields: undefined,
|
||||||
|
admin: {
|
||||||
|
group: "General",
|
||||||
|
useAsTitle: "form",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// beforeEmail: (emailsToSend, beforeChangeParams) => {
|
||||||
|
// if (beforeChangeParams.data.form === 1) {
|
||||||
|
// const submissionData: Record<string, any> = {};
|
||||||
|
// for (const s of beforeChangeParams.data.submissionData) {
|
||||||
|
// submissionData[s.field] = s.value;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// submissionData["confirmation"] = submissionData["confirmation"]
|
||||||
|
// ? "I consent to receive text messages and emails, and that I am 18+ years old."
|
||||||
|
// : "";
|
||||||
|
|
||||||
|
// // modify the emails in any way before they are sent
|
||||||
|
// return emailsToSend.map((email) => {
|
||||||
|
// return {
|
||||||
|
// ...email,
|
||||||
|
// html: loadEmailTemplate("contact-form-email.html", submissionData),
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return emailsToSend;
|
||||||
|
// },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
26
src/services/hooks/form.ts
Normal file
26
src/services/hooks/form.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Form } from "@/payload-types";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { fetchFormREST } from "../rest/form";
|
||||||
|
|
||||||
|
export function useFormQuery() {
|
||||||
|
const [data, setData] = useState<Form | null>(null);
|
||||||
|
const [isFetching, setFetching] = useState(false);
|
||||||
|
|
||||||
|
async function _fetch(id: number) {
|
||||||
|
setFetching(true);
|
||||||
|
const res = await fetchFormREST(id);
|
||||||
|
setFetching(false);
|
||||||
|
|
||||||
|
if (!!res) {
|
||||||
|
setData(res);
|
||||||
|
} else {
|
||||||
|
setData(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_fetch,
|
||||||
|
data,
|
||||||
|
isFetching,
|
||||||
|
};
|
||||||
|
}
|
12
src/services/payload/form.ts
Normal file
12
src/services/payload/form.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import payloadConfig from "@/payload.config";
|
||||||
|
import { getPayload } from "payload";
|
||||||
|
|
||||||
|
export async function fetchForm(formID: number) {
|
||||||
|
const payload = await getPayload({ config: payloadConfig });
|
||||||
|
const formRes = await payload.findByID({
|
||||||
|
collection: "forms",
|
||||||
|
id: formID,
|
||||||
|
});
|
||||||
|
|
||||||
|
return formRes;
|
||||||
|
}
|
11
src/services/rest/form.ts
Normal file
11
src/services/rest/form.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Form } from "@/payload-types";
|
||||||
|
|
||||||
|
export async function fetchFormREST(id: number) {
|
||||||
|
const req = await fetch(`/api/forms/${id}`);
|
||||||
|
if (req.ok) {
|
||||||
|
const resData = (await req.json()) as Form;
|
||||||
|
return resData;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
27
yarn.lock
27
yarn.lock
@ -2673,6 +2673,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@payloadcms/plugin-form-builder@npm:^3.35.1":
|
||||||
|
version: 3.35.1
|
||||||
|
resolution: "@payloadcms/plugin-form-builder@npm:3.35.1"
|
||||||
|
dependencies:
|
||||||
|
"@payloadcms/ui": "npm:3.35.1"
|
||||||
|
escape-html: "npm:^1.0.3"
|
||||||
|
peerDependencies:
|
||||||
|
payload: 3.35.1
|
||||||
|
react: ^19.0.0 || ^19.0.0-rc-65a56d0e-20241020
|
||||||
|
react-dom: ^19.0.0 || ^19.0.0-rc-65a56d0e-20241020
|
||||||
|
checksum: 10c0/48a7f5ecbf0c7c3b46fcc0a92ca3c2ff3c759f16c568973c19ccd8c38cfc93ffdd8ddbee76266ac7063de3d6b1ba9916da07ea13fd6828ad12ae6be7ea2cca8f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@payloadcms/richtext-lexical@npm:^3.35.1":
|
"@payloadcms/richtext-lexical@npm:^3.35.1":
|
||||||
version: 3.35.1
|
version: 3.35.1
|
||||||
resolution: "@payloadcms/richtext-lexical@npm:3.35.1"
|
resolution: "@payloadcms/richtext-lexical@npm:3.35.1"
|
||||||
@ -5023,6 +5037,7 @@ __metadata:
|
|||||||
"@payloadcms/db-postgres": "npm:^3.35.1"
|
"@payloadcms/db-postgres": "npm:^3.35.1"
|
||||||
"@payloadcms/next": "npm:^3.35.1"
|
"@payloadcms/next": "npm:^3.35.1"
|
||||||
"@payloadcms/payload-cloud": "npm:^3.35.1"
|
"@payloadcms/payload-cloud": "npm:^3.35.1"
|
||||||
|
"@payloadcms/plugin-form-builder": "npm:^3.35.1"
|
||||||
"@payloadcms/richtext-lexical": "npm:^3.35.1"
|
"@payloadcms/richtext-lexical": "npm:^3.35.1"
|
||||||
"@payloadcms/storage-s3": "npm:^3.35.1"
|
"@payloadcms/storage-s3": "npm:^3.35.1"
|
||||||
"@tailwindcss/postcss": "npm:^4"
|
"@tailwindcss/postcss": "npm:^4"
|
||||||
@ -5042,6 +5057,7 @@ __metadata:
|
|||||||
qs-esm: "npm:^7.0.2"
|
qs-esm: "npm:^7.0.2"
|
||||||
react: "npm:^19.0.0"
|
react: "npm:^19.0.0"
|
||||||
react-dom: "npm:^19.0.0"
|
react-dom: "npm:^19.0.0"
|
||||||
|
react-hook-form: "npm:^7.56.1"
|
||||||
react-select: "npm:^5.10.1"
|
react-select: "npm:^5.10.1"
|
||||||
swiper: "npm:^11.2.6"
|
swiper: "npm:^11.2.6"
|
||||||
tailwindcss: "npm:^4"
|
tailwindcss: "npm:^4"
|
||||||
@ -5510,7 +5526,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"escape-html@npm:1.0.3":
|
"escape-html@npm:1.0.3, escape-html@npm:^1.0.3":
|
||||||
version: 1.0.3
|
version: 1.0.3
|
||||||
resolution: "escape-html@npm:1.0.3"
|
resolution: "escape-html@npm:1.0.3"
|
||||||
checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3
|
checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3
|
||||||
@ -8782,6 +8798,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-hook-form@npm:^7.56.1":
|
||||||
|
version: 7.56.1
|
||||||
|
resolution: "react-hook-form@npm:7.56.1"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
|
checksum: 10c0/26eafd54bf47167628e34c2f22ea27ea7bdd5b4231a56af5be31ccedfb2a721708b308026ea4a17f2fb15c7b5fff83c0feb4acc7aa07bd6faa8506e917810cab
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-image-crop@npm:10.1.8":
|
"react-image-crop@npm:10.1.8":
|
||||||
version: 10.1.8
|
version: 10.1.8
|
||||||
resolution: "react-image-crop@npm:10.1.8"
|
resolution: "react-image-crop@npm:10.1.8"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user