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.alt} +
+ )} + {!page?.heroImg?.url && ( +
+ +
+ )} + {/* */} + +
+
+
+

{page.title}

+
+
+
+
+ + + + ); +} 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;