diff --git a/src/app/(main)/[pageslug]/page.tsx b/src/app/(main)/[pageslug]/page.tsx
new file mode 100644
index 0000000..62d3d78
--- /dev/null
+++ b/src/app/(main)/[pageslug]/page.tsx
@@ -0,0 +1,39 @@
+import { BlogDetailContentSkeleton } from "@/components/Blogs/BlogDetail";
+import Page from "@/components/Pages/Page";
+import Image from "next/image";
+import { Suspense } from "react";
+
+export const metadata = {
+ title: "Page | Cochise Oncology",
+ description: "Page | Cochise Oncology",
+};
+
+export default async function CustomPage({ params }: { params?: Promise<{ pageslug?: string }> }) {
+ const slug = (await params)?.pageslug;
+
+ return (
+ <>
+ }>
+
+
+ >
+ );
+}
+
+function Loading() {
+ return (
+ <>
+
+ {/* */}
+
+
+
+ {/* */}
+
+
+ {/* Section */}
+
+ {/* End Section */}
+ >
+ );
+}
diff --git a/src/app/(main)/blog/[slug]/page.tsx b/src/app/(main)/blog/[slug]/page.tsx
index e2c7bdf..9b29800 100644
--- a/src/app/(main)/blog/[slug]/page.tsx
+++ b/src/app/(main)/blog/[slug]/page.tsx
@@ -53,22 +53,6 @@ function Loading() {
{/* */}
-
-
-
-
-
...
- {/* Author, Categories, Comments */}
-
- {/* End Author, Categories, Comments */}
-
-
-
{/* Section */}
diff --git a/src/blocks/BeforeFooter.ts b/src/blocks/BeforeFooter.ts
new file mode 100644
index 0000000..5f25cdf
--- /dev/null
+++ b/src/blocks/BeforeFooter.ts
@@ -0,0 +1,27 @@
+import { Block } from "payload";
+
+export const BeforeFooterBlock: Block = {
+ slug: "beforeFooterBlock",
+ fields: [
+ {
+ name: "title",
+ type: "text",
+ required: true,
+ defaultValue: "Begin your path to healing with Cochise Oncology",
+ },
+ {
+ name: "description",
+ type: "textarea",
+ required: true,
+ defaultValue:
+ "Our dedicated team in Sierra Vista, AZ is here to support you with hope, strength, and courage. We offer personalized cancer care using innovative treatments in our state-of-the-art facility. Take the first step towards comprehensive, patient-focused treatment by scheduling a consultation. Let us listen to your needs, answer your questions, and create a tailored plan for your journey. Fill out our form to connect with our compassionate experts and discover how Cochise Oncology can stand with you in your fight against cancer.",
+ },
+ {
+ type: "text",
+ name: "buttonText",
+ label: "CTA Button Text",
+ required: true,
+ defaultValue: "Get Started",
+ },
+ ],
+};
diff --git a/src/blocks/Content.ts b/src/blocks/Content.ts
new file mode 100644
index 0000000..0314635
--- /dev/null
+++ b/src/blocks/Content.ts
@@ -0,0 +1,14 @@
+import { lexicalEditor } from "@payloadcms/richtext-lexical";
+import { Block } from "payload";
+
+export const ContentBlock: Block = {
+ slug: "contentBlock",
+ fields: [
+ {
+ name: "content",
+ type: "richText",
+ editor: lexicalEditor({}),
+ required: true,
+ },
+ ],
+};
diff --git a/src/collections/Pages.ts b/src/collections/Pages.ts
new file mode 100644
index 0000000..e318d14
--- /dev/null
+++ b/src/collections/Pages.ts
@@ -0,0 +1,62 @@
+import { BeforeFooterBlock } from "@/blocks/BeforeFooter";
+import { ContentBlock } from "@/blocks/Content";
+import formatSlug from "@/utils/formatSlug";
+import { CollectionConfig } from "payload";
+
+export const Pages: CollectionConfig = {
+ slug: "pages",
+ 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",
+ admin: {
+ position: "sidebar",
+ },
+ hooks: {
+ beforeValidate: [formatSlug("title")],
+ },
+ },
+ {
+ name: "layout",
+ label: "Page Layout",
+ type: "blocks",
+ minRows: 1,
+ blocks: [ContentBlock, BeforeFooterBlock],
+ },
+ {
+ name: "meta",
+ label: "Page Meta",
+ type: "group",
+ fields: [
+ {
+ name: "title",
+ label: "Title",
+ type: "text",
+ },
+ {
+ name: "description",
+ label: "Description",
+ type: "textarea",
+ },
+ {
+ name: "keywords",
+ label: "Keywords",
+ type: "text",
+ },
+ ],
+ },
+ ],
+};
diff --git a/src/components/Blocks/BeforeFooter/index.tsx b/src/components/Blocks/BeforeFooter/index.tsx
new file mode 100644
index 0000000..143133a
--- /dev/null
+++ b/src/components/Blocks/BeforeFooter/index.tsx
@@ -0,0 +1,29 @@
+import Link from "next/link";
+
+export interface BeforeFooterBlockProps {
+ id: string;
+ title: string;
+ description: string;
+ buttonText: string;
+}
+
+export function BeforeFooterBlock({ title, description, buttonText }: BeforeFooterBlockProps) {
+ return (
+
+
+
{title}
+
{description}
+ {!!buttonText && (
+
+
+ {buttonText}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/Blocks/Content/index.tsx b/src/components/Blocks/Content/index.tsx
new file mode 100644
index 0000000..fbabe54
--- /dev/null
+++ b/src/components/Blocks/Content/index.tsx
@@ -0,0 +1,26 @@
+import { RichText } from "@payloadcms/richtext-lexical/react";
+
+// type Props = extract
+
+export function ContentBlock(props: any) {
+ return (
+
+
+ {/* Content */}
+
+ {/* Post */}
+
+
+
+ {/* @ts-ignore */}
+
+
+
+
+ {/* End Post */}
+
+ {/* End Content */}
+
+
+ );
+}
diff --git a/src/components/Blocks/RenderBlocks.tsx b/src/components/Blocks/RenderBlocks.tsx
new file mode 100644
index 0000000..a4bc6c7
--- /dev/null
+++ b/src/components/Blocks/RenderBlocks.tsx
@@ -0,0 +1,44 @@
+import React, { Fragment } from "react";
+
+import type { Page } from "@/payload-types";
+import { ContentBlock } from "./Content";
+import { BeforeFooterBlock } from "./BeforeFooter";
+
+const blockComponents = {
+ contentBlock: ContentBlock,
+ beforeFooterBlock: BeforeFooterBlock,
+};
+
+export const RenderBlocks: React.FC<{
+ blocks: Page["layout"];
+}> = (props) => {
+ const { blocks } = props;
+
+ const hasBlocks = blocks && Array.isArray(blocks) && blocks.length > 0;
+
+ if (hasBlocks) {
+ return (
+
+ {blocks.map((block, index) => {
+ const { blockType } = block;
+
+ if (blockType && blockType in blockComponents) {
+ const Block = blockComponents[blockType];
+
+ if (Block) {
+ return (
+
+ {/* @ts-ignore */}
+
+
+ );
+ }
+ }
+ return null;
+ })}
+
+ );
+ }
+
+ return null;
+};
diff --git a/src/components/Pages/Page.tsx b/src/components/Pages/Page.tsx
new file mode 100644
index 0000000..b9fb2c6
--- /dev/null
+++ b/src/components/Pages/Page.tsx
@@ -0,0 +1,58 @@
+import { RenderBlocks } from "@/components/Blocks/RenderBlocks";
+import { fetchPageBySlug } from "@/services/payload/page";
+import Image from "next/image";
+import { notFound } from "next/navigation";
+
+export interface PageProps {
+ slug: string | undefined;
+}
+
+export default async function Page({ slug }: PageProps) {
+ const page = await fetchPageBySlug({ slug });
+ if (!page) {
+ return notFound();
+ }
+
+ return (
+ <>
+
+ {/* */}
+ {!!page.heroImg?.url && (
+
+
+
+ )}
+ {!page?.heroImg?.url && (
+
+
+
+ )}
+ {/* */}
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/data/menu.ts b/src/data/menu.ts
index 6e371bb..4bde8e4 100644
--- a/src/data/menu.ts
+++ b/src/data/menu.ts
@@ -4,7 +4,7 @@ export const navMenuData = [
href: "#",
text: "About",
child: [
- { href: "/slick-about-dark", text: "Our Oncology Center" },
+ { href: "/our-oncology-center", text: "Our Oncology Center" },
{ href: "/our-staff", text: "Our Staff" },
{ href: "/announcements", text: "Announcements" },
],
diff --git a/src/payload-types.ts b/src/payload-types.ts
index 0d8ed6b..3692e8d 100644
--- a/src/payload-types.ts
+++ b/src/payload-types.ts
@@ -14,6 +14,7 @@ export interface Config {
users: User;
media: Media;
blogs: Blog;
+ pages: Page;
forms: Form;
'form-submissions': FormSubmission;
'payload-locked-documents': PayloadLockedDocument;
@@ -25,6 +26,7 @@ export interface Config {
users: UsersSelect | UsersSelect;
media: MediaSelect | MediaSelect;
blogs: BlogsSelect | BlogsSelect;
+ pages: PagesSelect | PagesSelect;
forms: FormsSelect | FormsSelect;
'form-submissions': FormSubmissionsSelect | FormSubmissionsSelect;
'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect;
@@ -127,6 +129,55 @@ export interface Blog {
updatedAt: string;
createdAt: string;
}
+/**
+ * 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';
+ }
+ | {
+ title: string;
+ description: string;
+ buttonText: string;
+ id?: string | null;
+ blockName?: string | null;
+ blockType: 'beforeFooterBlock';
+ }
+ )[]
+ | null;
+ meta?: {
+ title?: string | null;
+ description?: string | null;
+ keywords?: string | null;
+ };
+ updatedAt: string;
+ createdAt: string;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "forms".
@@ -316,6 +367,10 @@ export interface PayloadLockedDocument {
relationTo: 'blogs';
value: number | Blog;
} | null)
+ | ({
+ relationTo: 'pages';
+ value: number | Page;
+ } | null)
| ({
relationTo: 'forms';
value: number | Form;
@@ -412,6 +467,44 @@ export interface BlogsSelect {
updatedAt?: T;
createdAt?: T;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "pages_select".
+ */
+export interface PagesSelect {
+ title?: T;
+ hero_img?: T;
+ slug?: T;
+ layout?:
+ | T
+ | {
+ contentBlock?:
+ | T
+ | {
+ content?: T;
+ id?: T;
+ blockName?: T;
+ };
+ beforeFooterBlock?:
+ | T
+ | {
+ title?: T;
+ description?: T;
+ buttonText?: T;
+ id?: T;
+ blockName?: T;
+ };
+ };
+ meta?:
+ | T
+ | {
+ title?: T;
+ description?: T;
+ keywords?: T;
+ };
+ updatedAt?: T;
+ createdAt?: T;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "forms_select".
diff --git a/src/payload.config.ts b/src/payload.config.ts
index 34ddb91..a68c5ae 100644
--- a/src/payload.config.ts
+++ b/src/payload.config.ts
@@ -10,6 +10,7 @@ import { fileURLToPath } from "url";
import { Blogs } from "@/collections/Blogs";
import { Media } from "@/collections/Media";
+import { Pages } from "@/collections/Pages";
import { Users } from "@/collections/Users";
import {
BoldFeature,
@@ -45,7 +46,7 @@ export default buildConfig({
},
theme: "dark",
},
- collections: [Users, Media, Blogs],
+ collections: [Users, Media, Blogs, Pages],
secret: process.env.PAYLOAD_SECRET || "",
typescript: {
outputFile: path.resolve(dirname, "payload-types.ts"),
diff --git a/src/services/payload/page.ts b/src/services/payload/page.ts
new file mode 100644
index 0000000..b46ec12
--- /dev/null
+++ b/src/services/payload/page.ts
@@ -0,0 +1,39 @@
+import payloadConfig from "@/payload.config";
+import { draftMode } from "next/headers";
+import { getPayload } from "payload";
+
+export const fetchPageBySlug = async ({ slug }: { slug: string | undefined }) => {
+ const { isEnabled: draft } = await draftMode();
+
+ const payload = await getPayload({ config: payloadConfig });
+
+ const result = await payload.find({
+ collection: "pages",
+ // draft,
+ limit: 1,
+ pagination: false,
+ // overrideAccess: draft,
+ where: {
+ slug: {
+ equals: slug,
+ },
+ },
+ });
+
+ if (!result.docs?.[0]) {
+ return null;
+ }
+
+ const data = result.docs[0];
+
+ const heroImgUrl = typeof data.hero_img !== "number" ? (data?.hero_img?.url ?? "") : "";
+ const heroImgAlt = typeof data.hero_img !== "number" ? (data?.hero_img?.alt ?? "") : "";
+
+ return {
+ ...data,
+ heroImg: {
+ url: heroImgUrl,
+ alt: heroImgAlt,
+ },
+ };
+};
diff --git a/src/utils/formatSlug.ts b/src/utils/formatSlug.ts
new file mode 100644
index 0000000..77d338a
--- /dev/null
+++ b/src/utils/formatSlug.ts
@@ -0,0 +1,24 @@
+import { FieldHook } from "payload";
+
+const format = (val: string): string =>
+ val
+ .replace(/ /g, "-")
+ .replace(/[^\w-/]+/g, "")
+ .toLowerCase();
+
+const formatSlug =
+ (fallback: string): FieldHook =>
+ ({ value, originalDoc, data }) => {
+ if (typeof value === "string") {
+ return format(value);
+ }
+ const fallbackData = (data && data[fallback]) || (originalDoc && originalDoc[fallback]);
+
+ if (fallbackData && typeof fallbackData === "string") {
+ return format(fallbackData);
+ }
+
+ return value;
+ };
+
+export default formatSlug;