diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b66a51..4a63c4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,9 @@ jobs: cd /srv/portfolio pnpm install + - name: Generate updated types + run: pnpm generate:types + - name: Restore permissions in production directory run: | sudo chown -R www-data:www-data /srv/portfolio/ diff --git a/package.json b/package.json index ebf0112..f0d5929 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@payloadcms/plugin-nested-docs": "^1.0.12", "@payloadcms/plugin-redirects": "^1.0.2", "@payloadcms/plugin-seo": "^1.0.15", + "@payloadcms/richtext-lexical": "^0.11.3", "@payloadcms/richtext-slate": "^1.5.2", "cross-env": "^7.0.3", "dotenv": "^8.6.0", diff --git a/src/app/(pages)/styleguide/icon-row-block/page.tsx b/src/app/(pages)/styleguide/icon-row-block/page.tsx new file mode 100644 index 0000000..4e380f4 --- /dev/null +++ b/src/app/(pages)/styleguide/icon-row-block/page.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import { Metadata } from 'next' +import Link from 'next/link' + +import { IconRow } from '../../../../app/_blocks/IconRow' +import { Gutter } from '../../../_components/Gutter' +import { VerticalPadding } from '../../../_components/VerticalPadding' +import { mergeOpenGraph } from '../../../_utilities/mergeOpenGraph' + +// Example data for IconRow +const exampleIconRow = { + blockType: 'iconRow' as 'iconRow', + blockName: 'Icon Row', + introContent: [], + subheading: 'Example Subheading', + icons: [ + { iconTitle: 'Icon 1', iconImage: { url: '/path/to/icon1.jpg' } }, + { iconTitle: 'Icon 2', iconImage: { url: '/path/to/icon2.jpg' } }, + ], +} + +export default function IconRowStyleguide() { + return ( + + + Icon Row Example + + Back to Styleguide + + + ) +} + +export const metadata: Metadata = { + title: 'Icon Row Styleguide', + description: 'Showcasing the Icon Row component.', + openGraph: mergeOpenGraph({ + title: 'Icon Row Styleguide', + url: '/styleguide/icon-row', + }), +} diff --git a/src/app/(pages)/styleguide/icon-row-container-block/page.tsx b/src/app/(pages)/styleguide/icon-row-container-block/page.tsx new file mode 100644 index 0000000..3279bc1 --- /dev/null +++ b/src/app/(pages)/styleguide/icon-row-container-block/page.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import { Metadata } from 'next' +import Link from 'next/link' + +import { IconRowContainer } from '../../../_blocks/IconRowContainer' +import { Gutter } from '../../../_components/Gutter' +import { VerticalPadding } from '../../../_components/VerticalPadding' +import { mergeOpenGraph } from '../../../_utilities/mergeOpenGraph' + +// Example data for IconRowContainer, possibly including multiple IconRows +const iconRowContainerData = { + blockType: 'iconRowContainer' as 'iconRowContainer', + blockName: 'Icon Row Container', + introContent: [], + mainHeading: 'Main Heading Example', + rows: [ + { + subheading: 'Subheading 1', + icons: [ + { iconTitle: 'Icon 1', iconImage: { url: '/path/to/icon1.jpg' } }, + { iconTitle: 'Icon 2', iconImage: { url: '/path/to/icon2.jpg' } }, + ], + }, + ], +} + +export default function IconRowContainerStyleguide() { + return ( + + + Icon Row Container Example + + Back to Styleguide + + + ) +} + +export const metadata: Metadata = { + title: 'Icon Row Container Styleguide', + description: 'Showcasing the Icon Row Container component.', + openGraph: mergeOpenGraph({ + title: 'Icon Row Container Styleguide', + url: '/styleguide/icon-row-container', + }), +} diff --git a/src/app/_blocks/IconRow/index.module.scss b/src/app/_blocks/IconRow/index.module.scss new file mode 100644 index 0000000..bde4bf7 --- /dev/null +++ b/src/app/_blocks/IconRow/index.module.scss @@ -0,0 +1,20 @@ +.iconRow { + padding: 20px; + background-color: #f0f0f0; // example background color + + h3 { + margin-bottom: 10px; + } + + .iconsContainer { + display: flex; + justify-content: space-around; + } + + .icon { + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; + } +} diff --git a/src/app/_blocks/IconRow/index.tsx b/src/app/_blocks/IconRow/index.tsx new file mode 100644 index 0000000..b255ee7 --- /dev/null +++ b/src/app/_blocks/IconRow/index.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import Image from 'next/image' + +import { Media as MediaType } from '../../../payload/payload-types' +import { Gutter } from '../../_components/Gutter' +import { Media } from '../../_components/Media' +import RichText from '../../_components/RichText' + +import classes from './index.module.scss' + +export type Icon = { + iconTitle: string + iconImage: { + url?: string + media?: MediaType + } +} + +export type IconRowProps = { + blockType?: 'iconRow' + blockName?: string + introContent?: any + subheading: string + icons?: Icon[] +} + +export const IconRow: React.FC = ({ introContent, subheading, icons }) => { + return ( + + + {introContent && ( + + + + )} + {subheading} + + {icons.map((icon, index) => ( + + + {icon.iconTitle} + + ))} + + + + ) +} diff --git a/src/app/_blocks/IconRowContainer/index.module.scss b/src/app/_blocks/IconRowContainer/index.module.scss new file mode 100644 index 0000000..fa7f0a5 --- /dev/null +++ b/src/app/_blocks/IconRowContainer/index.module.scss @@ -0,0 +1,8 @@ +.iconRowContainer { + background-color: #e0e0e0; // example background color + + h2 { + padding: 20px; + text-align: center; + } +} diff --git a/src/app/_blocks/IconRowContainer/index.tsx b/src/app/_blocks/IconRowContainer/index.tsx new file mode 100644 index 0000000..268e3b1 --- /dev/null +++ b/src/app/_blocks/IconRowContainer/index.tsx @@ -0,0 +1,34 @@ +import React from 'react' + +import { Gutter } from '../../_components/Gutter' +import RichText from '../../_components/RichText' +import { IconRow, IconRowProps } from '../IconRow' + +import classes from './index.module.scss' + +export type IconRowContainerProps = { + blockType?: 'iconRowContainer' + blockName?: string + introContent?: any + mainHeading?: string + rows?: IconRowProps[] +} + +export const IconRowContainer: React.FC = (props: IconRowContainerProps) => { + const { introContent, mainHeading, rows } = props + return ( + + + {introContent && ( + + + + )} + {mainHeading} + {rows.map((row, index) => ( + + ))} + + + ) +} diff --git a/src/app/_components/Blocks/index.tsx b/src/app/_components/Blocks/index.tsx index c54140a..5a84e6d 100644 --- a/src/app/_components/Blocks/index.tsx +++ b/src/app/_components/Blocks/index.tsx @@ -5,6 +5,8 @@ import { ArchiveBlock } from '../../_blocks/ArchiveBlock' import { CallToActionBlock } from '../../_blocks/CallToAction' import { CommentsBlock, type CommentsBlockProps } from '../../_blocks/Comments/index' import { ContentBlock } from '../../_blocks/Content' +import { IconRow, IconRowProps } from '../../_blocks/IconRow' +import { IconRowContainer, IconRowContainerProps } from '../../_blocks/IconRowContainer' import { MediaBlock } from '../../_blocks/MediaBlock' import { RelatedPosts, type RelatedPostsProps } from '../../_blocks/RelatedPosts' import { toKebabCase } from '../../_utilities/toKebabCase' @@ -15,13 +17,21 @@ const blockComponents = { cta: CallToActionBlock, content: ContentBlock, mediaBlock: MediaBlock, + iconRow: IconRow, + iconRowContainer: IconRowContainer, archive: ArchiveBlock, relatedPosts: RelatedPosts, comments: CommentsBlock, } export const Blocks: React.FC<{ - blocks: (Page['layout'][0] | RelatedPostsProps | CommentsBlockProps)[] + blocks: ( + | Page['layout'][0] + | RelatedPostsProps + | CommentsBlockProps + | IconRowProps + | IconRowContainerProps + )[] disableTopPadding?: boolean }> = props => { const { disableTopPadding, blocks } = props diff --git a/src/payload/blocks/IconRow/index.ts b/src/payload/blocks/IconRow/index.ts new file mode 100644 index 0000000..3e0b42f --- /dev/null +++ b/src/payload/blocks/IconRow/index.ts @@ -0,0 +1,35 @@ +import type { Block } from 'payload/types' + +import richText from '../../fields/richText' + +export const IconRow: Block = { + slug: 'iconRow', + fields: [ + richText({ + name: 'introContent', + label: 'Intro Content', + }), + { + name: 'subheading', + type: 'text', + required: true, + }, + { + name: 'icons', + type: 'array', + fields: [ + { + name: 'iconTitle', + type: 'text', + required: true, + }, + { + name: 'iconImage', + type: 'upload', + relationTo: 'media', + required: true, + }, + ], + }, + ], +} diff --git a/src/payload/blocks/IconRowContainer/index.ts b/src/payload/blocks/IconRowContainer/index.ts new file mode 100644 index 0000000..f21eac2 --- /dev/null +++ b/src/payload/blocks/IconRowContainer/index.ts @@ -0,0 +1,24 @@ +import type { Block } from 'payload/types' + +import richText from '../../fields/richText' +import { IconRow } from '../IconRow' + +export const IconRowContainer: Block = { + slug: 'iconRowContainer', + fields: [ + richText({ + name: 'introContent', + label: 'Intro Content', + }), + { + name: 'mainHeading', + type: 'text', + required: true, + }, + { + name: 'rows', + type: 'blocks', + blocks: [IconRow], + }, + ], +} diff --git a/src/payload/collections/Pages/index.ts b/src/payload/collections/Pages/index.ts index 5c40a2e..d3fb8a1 100644 --- a/src/payload/collections/Pages/index.ts +++ b/src/payload/collections/Pages/index.ts @@ -5,6 +5,8 @@ import { adminsOrPublished } from '../../access/adminsOrPublished' import { Archive } from '../../blocks/ArchiveBlock' import { CallToAction } from '../../blocks/CallToAction' import { Content } from '../../blocks/Content' +import { IconRow } from '../../blocks/IconRow' +import { IconRowContainer } from '../../blocks/IconRowContainer' import { MediaBlock } from '../../blocks/MediaBlock' import { hero } from '../../fields/hero' import { slugField } from '../../fields/slug' @@ -64,7 +66,7 @@ export const Pages: CollectionConfig = { name: 'layout', type: 'blocks', required: true, - blocks: [CallToAction, Content, MediaBlock, Archive], + blocks: [CallToAction, Content, IconRow, IconRowContainer, MediaBlock, Archive], }, ], }, diff --git a/src/payload/collections/Posts/index.ts b/src/payload/collections/Posts/index.ts index 35d6698..0763786 100644 --- a/src/payload/collections/Posts/index.ts +++ b/src/payload/collections/Posts/index.ts @@ -5,6 +5,8 @@ import { adminsOrPublished } from '../../access/adminsOrPublished' import { Archive } from '../../blocks/ArchiveBlock' import { CallToAction } from '../../blocks/CallToAction' import { Content } from '../../blocks/Content' +import { IconRow } from '../../blocks/IconRow' +import { IconRowContainer } from '../../blocks/IconRowContainer' import { MediaBlock } from '../../blocks/MediaBlock' import { hero } from '../../fields/hero' import { slugField } from '../../fields/slug' @@ -120,7 +122,7 @@ export const Posts: CollectionConfig = { name: 'layout', type: 'blocks', required: true, - blocks: [CallToAction, Content, MediaBlock, Archive], + blocks: [CallToAction, Content, IconRow, IconRowContainer, MediaBlock, Archive], }, { name: 'enablePremiumContent', @@ -133,7 +135,7 @@ export const Posts: CollectionConfig = { access: { read: ({ req }) => req.user, }, - blocks: [CallToAction, Content, MediaBlock, Archive], + blocks: [CallToAction, Content, IconRow, IconRowContainer, MediaBlock, Archive], }, ], }, diff --git a/src/payload/collections/Projects/index.ts b/src/payload/collections/Projects/index.ts index 748b151..fa8d98c 100644 --- a/src/payload/collections/Projects/index.ts +++ b/src/payload/collections/Projects/index.ts @@ -5,6 +5,8 @@ import { adminsOrPublished } from '../../access/adminsOrPublished' import { Archive } from '../../blocks/ArchiveBlock' import { CallToAction } from '../../blocks/CallToAction' import { Content } from '../../blocks/Content' +import { IconRow } from '../../blocks/IconRow' +import { IconRowContainer } from '../../blocks/IconRowContainer' import { MediaBlock } from '../../blocks/MediaBlock' import { hero } from '../../fields/hero' import { slugField } from '../../fields/slug' @@ -73,7 +75,7 @@ export const Projects: CollectionConfig = { name: 'layout', type: 'blocks', required: true, - blocks: [CallToAction, Content, MediaBlock, Archive], + blocks: [CallToAction, Content, IconRow, IconRowContainer, MediaBlock, Archive], }, ], }, diff --git a/src/payload/payload-types.ts b/src/payload/payload-types.ts index 41f401c..96b6100 100644 --- a/src/payload/payload-types.ts +++ b/src/payload/payload-types.ts @@ -109,6 +109,49 @@ export interface Page { blockName?: string | null; blockType: 'content'; } + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + } + | { + introContent: { + [k: string]: unknown; + }[]; + mainHeading: string; + rows?: + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRowContainer'; + } | { invertBackground?: boolean | null; position?: ('default' | 'fullscreen') | null; @@ -299,6 +342,49 @@ export interface Post { blockName?: string | null; blockType: 'content'; } + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + } + | { + introContent: { + [k: string]: unknown; + }[]; + mainHeading: string; + rows?: + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRowContainer'; + } | { invertBackground?: boolean | null; position?: ('default' | 'fullscreen') | null; @@ -400,6 +486,49 @@ export interface Post { blockName?: string | null; blockType: 'content'; } + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + } + | { + introContent: { + [k: string]: unknown; + }[]; + mainHeading: string; + rows?: + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRowContainer'; + } | { invertBackground?: boolean | null; position?: ('default' | 'fullscreen') | null; @@ -562,6 +691,49 @@ export interface Project { blockName?: string | null; blockType: 'content'; } + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + } + | { + introContent: { + [k: string]: unknown; + }[]; + mainHeading: string; + rows?: + | { + introContent: { + [k: string]: unknown; + }[]; + subheading: string; + icons?: + | { + iconTitle: string; + iconImage: number | Media; + id?: string | null; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRow'; + }[] + | null; + id?: string | null; + blockName?: string | null; + blockType: 'iconRowContainer'; + } | { invertBackground?: boolean | null; position?: ('default' | 'fullscreen') | null;
{icon.iconTitle}