diff --git a/src/blocks/BentoRow/Component.tsx b/src/blocks/BentoRow/Component.tsx
new file mode 100644
index 0000000..3ce1a3e
--- /dev/null
+++ b/src/blocks/BentoRow/Component.tsx
@@ -0,0 +1,97 @@
+import React from 'react'
+
+type Skill = {
+ icon: string
+ title: string
+ tags: string
+}
+
+type BentoRowBlockProps = {
+ aboutHeading?: string
+ aboutText?: string
+ aboutCta?: { label?: string; url?: string; newTab?: boolean }
+ skillsHeading?: string
+ skills?: Skill[]
+}
+
+export function BentoRowBlock({
+ aboutHeading,
+ aboutText,
+ aboutCta,
+ skillsHeading,
+ skills,
+}: BentoRowBlockProps) {
+ return (
+
+
+
+
+
+ {aboutHeading && (
+
+ {aboutHeading}
+
+ )}
+ {aboutText && (
+
+ {aboutText}
+
+ )}
+
+ {aboutCta?.label && aboutCta?.url && (
+
+ href={aboutCta.url}
+ target={aboutCta.newTab ? '_blank' : '_self'}
+ rel="noopener noreferrer"
+ className="self-start flex items-center gap-2 text-xs text-foreground/50 border border-foreground/10 rounded-lg px-4 py-2 hover:text-foreground/80 hover:bg-muted transition-colors"
+ >
+
+ {aboutCta.label}
+
+ )}
+
+
+
+ {skillsHeading && (
+
+ {skillsHeading}
+
+ )}
+ {Array.isArray(skills) && skills.length > 0 && (
+
+ {skills.map((skill, i) => {
+ const tags = skill.tags
+ ? skill.tags.split(',').map((t) => t.trim()).filter(Boolean)
+ : []
+ return (
+
+
+
{skill.title}
+
+ {tags.map((tag, j) => (
+
+ {tag}
+
+ ))}
+
+
+ )
+ })}
+
+ )}
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/blocks/BentoRow/config.ts b/src/blocks/BentoRow/config.ts
new file mode 100644
index 0000000..f2d6903
--- /dev/null
+++ b/src/blocks/BentoRow/config.ts
@@ -0,0 +1,50 @@
+import type { Block } from 'payload'
+
+export const BentoRowBlock: Block = {
+ slug: 'bentoRow',
+ labels: { singular: 'Bento Row (About + Skills)', plural: 'Bento Rows' },
+ fields: [
+ {
+ name: 'aboutHeading',
+ type: 'text',
+ label: 'About heading',
+ },
+ {
+ name: 'aboutText',
+ type: 'textarea',
+ label: 'About text',
+ },
+ {
+ name: 'aboutCta',
+ type: 'group',
+ label: 'About CTA button',
+ fields: [
+ { name: 'label', type: 'text', label: 'Label', defaultValue: 'Download CV' },
+ { name: 'url', type: 'text', label: 'URL' },
+ { name: 'newTab', type: 'checkbox', label: 'Open in new tab', defaultValue: true },
+ ],
+ },
+ {
+ name: 'skillsHeading',
+ type: 'text',
+ label: 'Skills heading',
+ defaultValue: 'Expertise',
+ },
+ {
+ name: 'skills',
+ type: 'array',
+ label: 'Skill cards',
+ maxRows: 6,
+ fields: [
+ {
+ name: 'icon',
+ type: 'text',
+ label: 'Tabler icon name (e.g. ti-components)',
+ required: true,
+ },
+ { name: 'title', type: 'text', label: 'Title', required: true },
+ { name: 'tags', type: 'text', label: 'Tags (comma separated)', required: true },
+ ],
+ },
+ ],
+}
diff --git a/src/blocks/Contact/Component.tsx b/src/blocks/Contact/Component.tsx
new file mode 100644
index 0000000..2640950
--- /dev/null
+++ b/src/blocks/Contact/Component.tsx
@@ -0,0 +1,84 @@
+import React from 'react'
+
+type ContactLink = {
+ label: string
+ sublabel?: string
+ url: string
+ icon?: string
+ newTab?: boolean
+}
+
+type ContactBlockProps = {
+ heading?: string
+ subtext?: string
+ email?: string
+ links?: ContactLink[]
+}
+
+export function ContactBlock({
+ heading,
+ subtext,
+ email,
+ links,
+}: ContactBlockProps) {
+ return (
+
+
+
+
+
+ {heading && (
+
{heading}
+ )}
+ {subtext && (
+
{subtext}
+ )}
+
+ {email && (
+
+ href={`mailto:${email}`}
+ className="self-start flex items-center gap-2 text-sm font-medium text-foreground/70 bg-background/50 border border-foreground/10 rounded-lg px-5 py-2.5 hover:text-foreground hover:bg-muted transition-colors"
+ >
+
+ Get in touch
+
+ )}
+
+
+
+ {Array.isArray(links) && links.map((link, i) => (
+
+ key={i}
+ href={link.url}
+ target={link.newTab ? '_blank' : '_self'}
+ rel="noopener noreferrer"
+ className="flex items-center gap-3 bg-muted/50 border border-foreground/8 rounded-xl px-5 py-4 hover:bg-muted/70 transition-colors group"
+ >
+ {link.icon && (
+
+ )}
+
+
+ {link.label}
+
+ {link.sublabel && (
+ {link.sublabel}
+ )}
+
+
+
+ ))}
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/blocks/Contact/config.ts b/src/blocks/Contact/config.ts
new file mode 100644
index 0000000..2de64ae
--- /dev/null
+++ b/src/blocks/Contact/config.ts
@@ -0,0 +1,37 @@
+import type { Block } from 'payload'
+
+export const ContactBlock: Block = {
+ slug: 'contact',
+ labels: { singular: 'Contact', plural: 'Contacts' },
+ fields: [
+ {
+ name: 'heading',
+ type: 'text',
+ label: 'Heading',
+ defaultValue: "Let's work together",
+ },
+ {
+ name: 'subtext',
+ type: 'textarea',
+ label: 'Subtext',
+ },
+ {
+ name: 'email',
+ type: 'text',
+ label: 'Email address',
+ },
+ {
+ name: 'links',
+ type: 'array',
+ label: 'Links',
+ maxRows: 4,
+ fields: [
+ { name: 'label', type: 'text', label: 'Label', required: true },
+ { name: 'sublabel', type: 'text', label: 'Sub label' },
+ { name: 'url', type: 'text', label: 'URL', required: true },
+ { name: 'icon', type: 'text', label: 'Tabler icon (e.g. ti-brand-github)' },
+ { name: 'newTab', type: 'checkbox', label: 'Open in new tab', defaultValue: true },
+ ],
+ },
+ ],
+}
diff --git a/src/blocks/RenderBlocks.tsx b/src/blocks/RenderBlocks.tsx
index d0649c8..a872772 100644
--- a/src/blocks/RenderBlocks.tsx
+++ b/src/blocks/RenderBlocks.tsx
@@ -13,6 +13,10 @@ import { KanbanColorBlock } from '@/blocks/KanbanColor/Component'
import { KanbanHoriBlock } from '@/blocks/KanbanHori/Component'
import { ShowcaseBlock } from '@/blocks/Showcase/Component'
import { AboutProfileBlock } from '@/blocks/AboutProfile/Component'
+import { StatsStripBlock } from '@/blocks/StatsStrip/Component'
+import { BentoRowBlock } from '@/blocks/BentoRow/Component'
+import { ContactBlock } from '@/blocks/Contact/Component'
+import { ToolStackBlock } from '@/blocks/ToolStack/Component'
const blockComponents = {
archive: ArchiveBlock,
@@ -26,6 +30,10 @@ const blockComponents = {
kanbanHori: KanbanHoriBlock,
showcase: ShowcaseBlock,
aboutProfile: AboutProfileBlock,
+ statsStrip: StatsStripBlock,
+ bentoRow: BentoRowBlock,
+ contact: ContactBlock,
+ toolStack: ToolStackBlock,
}
export const RenderBlocks: React.FC<{
diff --git a/src/blocks/Showcase/Component.tsx b/src/blocks/Showcase/Component.tsx
index d8b1dce..8459115 100644
--- a/src/blocks/Showcase/Component.tsx
+++ b/src/blocks/Showcase/Component.tsx
@@ -1,6 +1,5 @@
import React from 'react'
import Image from 'next/image'
-// Import the generated Media type from your Payload config
import type { Media as MediaType } from '@/payload-types'
type ShowcaseItem = {
@@ -18,47 +17,40 @@ type ShowcaseBlockProps = {
items?: ShowcaseItem[]
}
-function ShowcaseImage({ item }: { item: ShowcaseItem }) {
- // Payload 3.0 returns the object directly if depth is configured
+function ShowcaseImage(props: { item: ShowcaseItem }): React.ReactElement {
+ const item = props.item
const image = item.image
+ const imageUrl = image != null && typeof image === 'object' && 'url' in image ? image.url : null
+ const imageAlt = image != null && typeof image === 'object' && 'alt' in image ? image.alt : item.title
- // Use the image object if available, otherwise fallback
- if (image && typeof image === 'object' && 'url' in image) {
- const content = (
-
-
- {/* Hover Overlay */}
+ if (imageUrl != null && item.imageUrl != null) {
+ return (
+
+ href={item.imageUrl}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="block relative w-full aspect-video overflow-hidden group"
+ >
+
- {item.imageUrl && (
-
-
-
- )}
-
+
+
+
+
+ )
+ }
+
+ if (imageUrl != null) {
+ return (
+
+
+
)
-
- if (item.imageUrl) {
- return (
-
- {content}
-
- )
- }
- return content
}
- // Placeholder if no image
return (
-
-
+
+
Coming soon
)
@@ -72,67 +64,77 @@ export function ShowcaseBlock(props: ShowcaseBlockProps): React.ReactElement | n
return (
{(heading || subheading) && (
-
+
{heading && (
-
- {heading}
-
+
{heading}
)}
{subheading && (
-
{subheading}
+
{subheading}
)}
)}
-
- {items.map((item, i) => (
-
-
+
+ {items.map(function (item, i) {
+ const isFeatured = i === 0 && items.length > 1
+ return (
+
+
-
-
{item.title}
+
+ {item.description && (
+
+ {item.description.length > 40 ? item.description.slice(0, 40) + '...' : item.description}
+
+ )}
- {item.description && (
-
{item.description}
- )}
+
{item.title}
- {item.tags && item.tags.length > 0 && (
-
- {item.tags.map((t, j) => (
-
- {t.tag}
-
- ))}
-
- )}
+ {item.description && item.description.length > 40 && (
+
{item.description}
+ )}
- {item.links && item.links.length > 0 && (
-
- )}
+ {Array.isArray(item.tags) && item.tags.length > 0 && (
+
+ {item.tags.map(function (t, j) {
+ return (
+
+ {t.tag}
+
+ )
+ })}
+
+ )}
+
+ {Array.isArray(item.links) && item.links.length > 0 && (
+
+ {item.links.map(function (link, k) {
+ return (
+
+ key={k}
+ href={link.url}
+ target={link.newTab ? '_blank' : '_self'}
+ rel="noopener noreferrer"
+ className="flex items-center gap-1.5 text-xs text-foreground/40 hover:text-foreground/70 transition-colors"
+ >
+
+ {link.label}
+
+ )
+ })}
+
+ )}
+
-
- ))}
+ )
+ })}
)
-}
+}
\ No newline at end of file
diff --git a/src/blocks/StatsStrip/Component.tsx b/src/blocks/StatsStrip/Component.tsx
new file mode 100644
index 0000000..d4be37a
--- /dev/null
+++ b/src/blocks/StatsStrip/Component.tsx
@@ -0,0 +1,31 @@
+import React from 'react'
+
+type Stat = {
+ value: string
+ label: string
+}
+
+type StatsStripProps = {
+ stats?: Stat[]
+}
+
+export function StatsStripBlock({ stats }: StatsStripProps) {
+ if (!Array.isArray(stats) || stats.length === 0) return null
+
+ return (
+
+ {stats.map((stat, i) => (
+
+
{stat.value}
+
{stat.label}
+
+ ))}
+
+ )
+}
diff --git a/src/blocks/StatsStrip/config.ts b/src/blocks/StatsStrip/config.ts
new file mode 100644
index 0000000..3f0e630
--- /dev/null
+++ b/src/blocks/StatsStrip/config.ts
@@ -0,0 +1,19 @@
+import type { Block } from 'payload'
+
+export const StatsStripBlock: Block = {
+ slug: 'statsStrip',
+ labels: { singular: 'Stats Strip', plural: 'Stats Strips' },
+ fields: [
+ {
+ name: 'stats',
+ type: 'array',
+ label: 'Stats',
+ minRows: 1,
+ maxRows: 6,
+ fields: [
+ { name: 'value', type: 'text', label: 'Value', required: true },
+ { name: 'label', type: 'text', label: 'Label', required: true },
+ ],
+ },
+ ],
+}
diff --git a/src/blocks/ToolStack/Component.tsx b/src/blocks/ToolStack/Component.tsx
new file mode 100644
index 0000000..58a807d
--- /dev/null
+++ b/src/blocks/ToolStack/Component.tsx
@@ -0,0 +1,30 @@
+import React from 'react'
+
+type ToolStackBlockProps = {
+ heading?: string
+ tools?: { name: string }[]
+}
+
+export function ToolStackBlock({ heading, tools }: ToolStackBlockProps) {
+ if (!Array.isArray(tools) || tools.length === 0) return null
+
+ return (
+
+
+ {heading && (
+
{heading}
+ )}
+
+ {tools.map((tool, i) => (
+
+ {tool.name}
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/blocks/ToolStack/config.ts b/src/blocks/ToolStack/config.ts
new file mode 100644
index 0000000..e44ae42
--- /dev/null
+++ b/src/blocks/ToolStack/config.ts
@@ -0,0 +1,21 @@
+import type { Block } from 'payload'
+
+export const ToolStackBlock: Block = {
+ slug: 'toolStack',
+ labels: { singular: 'Tool Stack', plural: 'Tool Stacks' },
+ fields: [
+ {
+ name: 'heading',
+ type: 'text',
+ label: 'Heading',
+ defaultValue: 'Tool stack',
+ },
+ {
+ name: 'tools',
+ type: 'array',
+ label: 'Tools',
+ minRows: 1,
+ fields: [{ name: 'name', type: 'text', label: 'Tool name', required: true }],
+ },
+ ],
+}
diff --git a/src/collections/Pages/index.ts b/src/collections/Pages/index.ts
index b21a4df..907fcc5 100644
--- a/src/collections/Pages/index.ts
+++ b/src/collections/Pages/index.ts
@@ -13,6 +13,10 @@ import { KanbanColorBlock } from '../../blocks/KanbanColor/config'
import { KanbanHoriBlock } from '../../blocks/KanbanHori/config'
import { ShowcaseBlock } from '../../blocks/Showcase/config'
import { AboutProfileBlock } from '../../blocks/AboutProfile/config'
+import { StatsStripBlock } from '../../blocks/StatsStrip/config'
+import { BentoRowBlock } from '../../blocks/BentoRow/config'
+import { ContactBlock } from '../../blocks/Contact/config'
+import { ToolStackBlock } from '../../blocks/ToolStack/config'
import { hero } from '@/heros/config'
import { slugField } from 'payload'
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
@@ -87,6 +91,10 @@ export const Pages: CollectionConfig<'pages'> = {
KanbanHoriBlock,
ShowcaseBlock,
AboutProfileBlock,
+ StatsStripBlock,
+ BentoRowBlock,
+ ContactBlock,
+ ToolStackBlock,
],
required: true,
admin: {
diff --git a/src/heros/RenderHero.tsx b/src/heros/RenderHero.tsx
index 00ca702..d4285d8 100644
--- a/src/heros/RenderHero.tsx
+++ b/src/heros/RenderHero.tsx
@@ -1,15 +1,16 @@
import React from 'react'
-
import type { Page } from '@/payload-types'
import { HighImpactHero } from '@/heros/HighImpact'
import { LowImpactHero } from '@/heros/LowImpact'
import { MediumImpactHero } from '@/heros/MediumImpact'
+import { SplitHero } from '@/heros/SplitHero'
const heroes = {
highImpact: HighImpactHero,
lowImpact: LowImpactHero,
mediumImpact: MediumImpactHero,
+ splitHero: SplitHero,
}
export const RenderHero: React.FC
= (props) => {
diff --git a/src/heros/SplitHero/index.tsx b/src/heros/SplitHero/index.tsx
new file mode 100644
index 0000000..e5672de
--- /dev/null
+++ b/src/heros/SplitHero/index.tsx
@@ -0,0 +1,99 @@
+import React from 'react'
+import Image from 'next/image'
+import type { Page } from '@/payload-types'
+import type { Media as MediaType } from '@/payload-types'
+
+type SplitHeroProps = Page['hero'] & {
+ eyebrow?: string
+ heading?: string
+ subtext?: string
+ tags?: { tag: string }[]
+ primaryCta?: { label?: string; url?: string }
+ secondaryCta?: { label?: string; url?: string }
+ splitImage?: MediaType | null
+}
+
+export const SplitHero: React.FC = ({
+ eyebrow,
+ heading,
+ subtext,
+ tags,
+ primaryCta,
+ secondaryCta,
+ splitImage,
+}) => {
+ const hasImage = splitImage && typeof splitImage === 'object' && splitImage.url
+
+ return (
+
+
+
+ {eyebrow && (
+
+ {eyebrow}
+
+ )}
+ {heading && (
+
+ {heading}
+
+ )}
+ {subtext && (
+
+ {subtext}
+
+ )}
+ {Array.isArray(tags) && tags.length > 0 && (
+
+ {tags.map(({ tag }, i) => (
+
+ {tag}
+
+ ))}
+
+ )}
+
+
+ {primaryCta?.label && primaryCta?.url && (
+
+ href={primaryCta.url}
+ className="flex items-center gap-2 text-sm font-medium text-foreground bg-muted/50 border border-foreground/10 rounded-lg px-5 py-2.5 hover:bg-muted transition-colors"
+ >
+
+ {primaryCta.label}
+
+ )}
+ {secondaryCta?.label && secondaryCta?.url && (
+
+ href={secondaryCta.url}
+ className="flex items-center gap-2 text-sm text-foreground/50 border border-foreground/10 rounded-lg px-5 py-2.5 hover:text-foreground/80 hover:bg-muted/30 transition-colors"
+ >
+
+ {secondaryCta.label}
+
+ )}
+
+
+
+
+ {hasImage ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/heros/config.ts b/src/heros/config.ts
index 448d54c..535e829 100644
--- a/src/heros/config.ts
+++ b/src/heros/config.ts
@@ -23,6 +23,7 @@ export const hero: Field = {
{ label: 'High Impact', value: 'highImpact' },
{ label: 'Medium Impact', value: 'mediumImpact' },
{ label: 'Low Impact', value: 'lowImpact' },
+ { label: 'Split (Portfolio)', value: 'splitHero' },
],
required: true,
},
@@ -92,6 +93,73 @@ export const hero: Field = {
relationTo: 'media',
required: true,
},
+ {
+ name: 'eyebrow',
+ type: 'text',
+ label: 'Eyebrow text',
+ admin: {
+ condition: (_, { type } = {}) => type === 'splitHero',
+ description: 'Small text above the heading e.g. "Creative technologist · Singapore"',
+ },
+ },
+ {
+ name: 'heading',
+ type: 'text',
+ label: 'Heading',
+ admin: {
+ condition: (_, { type } = {}) => type === 'splitHero',
+ },
+ },
+ {
+ name: 'subtext',
+ type: 'textarea',
+ label: 'Subtext',
+ admin: {
+ condition: (_, { type } = {}) => type === 'splitHero',
+ },
+ },
+ {
+ name: 'tags',
+ type: 'array',
+ label: 'Tags',
+ admin: {
+ condition: (_, { type } = {}) => type === 'splitHero',
+ },
+ fields: [{ name: 'tag', type: 'text', label: 'Tag', required: true }],
+ },
+ {
+ name: 'primaryCta',
+ type: 'group',
+ label: 'Primary CTA',
+ admin: {
+ condition: (_, { type } = {}) => type === 'splitHero',
+ },
+ fields: [
+ { name: 'label', type: 'text', label: 'Label', defaultValue: 'View my work' },
+ { name: 'url', type: 'text', label: 'URL', defaultValue: '#work' },
+ ],
+ },
+ {
+ name: 'secondaryCta',
+ type: 'group',
+ label: 'Secondary CTA',
+ admin: {
+ condition: (_, { type } = {}) => type === 'splitHero',
+ },
+ fields: [
+ { name: 'label', type: 'text', label: 'Label', defaultValue: 'Get in touch' },
+ { name: 'url', type: 'text', label: 'URL', defaultValue: '#contact' },
+ ],
+ },
+ {
+ name: 'splitImage',
+ type: 'upload',
+ relationTo: 'media',
+ label: 'Photo (right side)',
+ admin: {
+ condition: (_, { type } = {}) => type === 'splitHero',
+ },
+ },
],
label: false,
}
diff --git a/src/payload-types.ts b/src/payload-types.ts
index 2935720..74e08da 100644
--- a/src/payload-types.ts
+++ b/src/payload-types.ts
@@ -159,7 +159,7 @@ export interface Page {
id: string;
title: string;
hero: {
- type: 'none' | 'highImpact' | 'mediumImpact' | 'lowImpact';
+ type: 'none' | 'highImpact' | 'mediumImpact' | 'lowImpact' | 'splitHero';
theme?: ('default' | 'muted' | 'card' | 'secondary' | 'image') | null;
/**
* Background image for the hero section
@@ -209,6 +209,27 @@ export interface Page {
}[]
| null;
media?: (string | null) | Media;
+ /**
+ * Small text above the heading e.g. "Creative technologist · Singapore"
+ */
+ eyebrow?: string | null;
+ heading?: string | null;
+ subtext?: string | null;
+ tags?:
+ | {
+ tag: string;
+ id?: string | null;
+ }[]
+ | null;
+ primaryCta?: {
+ label?: string | null;
+ url?: string | null;
+ };
+ secondaryCta?: {
+ label?: string | null;
+ url?: string | null;
+ };
+ splitImage?: (string | null) | Media;
};
layout: (
| CallToActionBlock
@@ -348,6 +369,69 @@ export interface Page {
blockName?: string | null;
blockType: 'aboutProfile';
}
+ | {
+ stats?:
+ | {
+ value: string;
+ label: string;
+ id?: string | null;
+ }[]
+ | null;
+ id?: string | null;
+ blockName?: string | null;
+ blockType: 'statsStrip';
+ }
+ | {
+ aboutHeading?: string | null;
+ aboutText?: string | null;
+ aboutCta?: {
+ label?: string | null;
+ url?: string | null;
+ newTab?: boolean | null;
+ };
+ skillsHeading?: string | null;
+ skills?:
+ | {
+ icon: string;
+ title: string;
+ tags: string;
+ id?: string | null;
+ }[]
+ | null;
+ id?: string | null;
+ blockName?: string | null;
+ blockType: 'bentoRow';
+ }
+ | {
+ heading?: string | null;
+ subtext?: string | null;
+ email?: string | null;
+ links?:
+ | {
+ label: string;
+ sublabel?: string | null;
+ url: string;
+ icon?: string | null;
+ newTab?: boolean | null;
+ id?: string | null;
+ }[]
+ | null;
+ id?: string | null;
+ blockName?: string | null;
+ blockType: 'contact';
+ }
+ | {
+ heading?: string | null;
+ tools?:
+ | {
+ name: string;
+ id?: string | null;
+ }[]
+ | null;
+ id?: string | null;
+ blockName?: string | null;
+ blockType: 'toolStack';
+ }
)[];
meta?: {
title?: string | null;
@@ -1230,6 +1314,28 @@ export interface PagesSelect {
id?: T;
};
media?: T;
+ eyebrow?: T;
+ heading?: T;
+ subtext?: T;
+ tags?:
+ | T
+ | {
+ tag?: T;
+ id?: T;
+ };
+ primaryCta?:
+ | T
+ | {
+ label?: T;
+ url?: T;
+ };
+ secondaryCta?:
+ | T
+ | {
+ label?: T;
+ url?: T;
+ };
+ splitImage?: T;
};
layout?:
| T
@@ -1366,6 +1472,75 @@ export interface PagesSelect {
id?: T;
blockName?: T;
};
+ statsStrip?:
+ | T
+ | {
+ stats?:
+ | T
+ | {
+ value?: T;
+ label?: T;
+ id?: T;
+ };
+ id?: T;
+ blockName?: T;
+ };
+ bentoRow?:
+ | T
+ | {
+ aboutHeading?: T;
+ aboutText?: T;
+ aboutCta?:
+ | T
+ | {
+ label?: T;
+ url?: T;
+ newTab?: T;
+ };
+ skillsHeading?: T;
+ skills?:
+ | T
+ | {
+ icon?: T;
+ title?: T;
+ tags?: T;
+ id?: T;
+ };
+ id?: T;
+ blockName?: T;
+ };
+ contact?:
+ | T
+ | {
+ heading?: T;
+ subtext?: T;
+ email?: T;
+ links?:
+ | T
+ | {
+ label?: T;
+ sublabel?: T;
+ url?: T;
+ icon?: T;
+ newTab?: T;
+ id?: T;
+ };
+ id?: T;
+ blockName?: T;
+ };
+ toolStack?:
+ | T
+ | {
+ heading?: T;
+ tools?:
+ | T
+ | {
+ name?: T;
+ id?: T;
+ };
+ id?: T;
+ blockName?: T;
+ };
};
meta?:
| T
diff --git a/src/utilities/mergeOpenGraph.ts b/src/utilities/mergeOpenGraph.ts
index a331a2a..8a92acb 100644
--- a/src/utilities/mergeOpenGraph.ts
+++ b/src/utilities/mergeOpenGraph.ts
@@ -3,14 +3,14 @@ import { getServerSideURL } from './getURL'
const defaultOpenGraph: Metadata['openGraph'] = {
type: 'website',
- description: 'An open-source website built with Payload and Next.js.',
+ description: "Full stack developer with a designer's eye.",
images: [
{
- url: `${getServerSideURL()}/website-template-OG.webp`,
+ url: `${getServerSideURL()}/og-image.webp`,
},
],
- siteName: 'Payload Website Template',
- title: 'Payload Website Template',
+ siteName: 'ByMackie',
+ title: 'ByMackie',
}
export const mergeOpenGraph = (og?: Metadata['openGraph']): Metadata['openGraph'] => {