diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 05e0307..330ca82 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,8 +19,8 @@ jobs: - name: Source environment variables run: | - sudo cp /srv/portfolio/.env ./ - sed -i 's/^PORT=.*/PORT=3300/' .env + sudo cp /srv/unforeseen-travels/.env ./ + sed -i 's/^PORT=.*/PORT=3400/' .env source .env - name: Install pnpm diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fba88b4..7364109 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,21 +27,21 @@ jobs: - name: Source environment variables run: | - sudo cp /srv/portfolio/.env ./ - sed -i 's/^PORT=.*/PORT=3300/' .env + sudo cp /srv/unforeseen-travels/.env ./ + sed -i 's/^PORT=.*/PORT=3400/' .env source .env - name: Install pnpm run: sudo npm install -g pnpm - name: Stop the production server - run: sudo systemctl stop portfolio || echo "Failed to stop the server, continuing..." + run: sudo systemctl stop unforeseen-travels || echo "Failed to stop the server, continuing..." - name: Steal ownership of production server directory - run: sudo chown -R gh-runner:gh-runner /srv/portfolio + run: sudo chown -R gh-runner:gh-runner /srv/unforeseen-travels - name: Link existing node modules - run: sudo ln -s /srv/portfolio/node_modules /home/gh-runner/actions-runner/_work/portfolio/portfolio/ + run: sudo ln -s /srv/unforeseen-travels/node_modules /home/gh-runner/actions-runner/_work/unforeseen-travels/unforeseen-travels/ - name: Install dependencies run: pnpm install @@ -52,21 +52,21 @@ jobs: - name: Publish Executable if: success() run: | - sudo find /srv/portfolio/ -mindepth 1 \ - ! -path '/srv/portfolio/media*' \ - ! -path '/srv/portfolio/node_modules*' \ - ! -name '/srv/portfolio/.git*' \ + sudo find /srv/unforeseen-travels/ -mindepth 1 \ + ! -path '/srv/unforeseen-travels/media*' \ + ! -path '/srv/unforeseen-travels/node_modules*' \ + ! -name '/srv/unforeseen-travels/.git*' \ ! -name '.env' \ ! -name 'log' \ ! -name 'tmp' -exec rm -rf {} + sudo rsync -av --exclude 'node_modules' --exclude 'media' --exclude '.env' --exclude '.git' \ - --exclude 'log' --exclude 'tmp' /home/gh-runner/actions-runner/_work/portfolio/portfolio/ /srv/portfolio/ + --exclude 'log' --exclude 'tmp' /home/gh-runner/actions-runner/_work/unforeseen-travels/unforeseen-travels/ /srv/unforeseen-travels/ - name: Install deps in prod after copy to be safe if: success() run: | - cd /srv/portfolio + cd /srv/unforeseen-travels pnpm install - name: Handle failure @@ -76,10 +76,10 @@ jobs: - name: Restore permissions in production directory if: always() run: | - sudo chown -R www-data:www-data /srv/portfolio/ - sudo chmod -R 755 /srv/portfolio/ + sudo chown -R www-data:www-data /srv/unforeseen-travels/ + sudo chmod -R 755 /srv/unforeseen-travels/ sudo chown -R gh-runner:gh-runner /home/gh-runner/ - name: Start production server if: always() - run: sudo systemctl start portfolio + run: sudo systemctl start unforeseen-travels diff --git a/README.md b/README.md index 2326233..0224c90 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,7 @@ -# Portfolio Site +# Unforeseen Travels -This is my personal portfolio site built with Payload CMS (version 2 via 'npx-create-payload-app@latest', updating to 3 beta soon). The site showcases my work and skills, featuring custom blocks for enhanced interactivity and visual appeal. +More info coming soon... ## Key Features - -- **Custom Babylon.js 3D Model Viewer Block**: A fully customizable 3D model viewer built using Babylon.js, allowing dynamic interaction with 3D models directly on the portfolio pages. - **IconRow Block**: A versatile block for displaying a row of icons, ideal for highlighting key technologies, tools, or skillsets in a visually appealing manner. - -## Technologies Used - -- **Payload CMS**: A headless CMS used to manage content efficiently, currently running on version 2 with plans to upgrade to version 3 beta. -- **Babylon.js**: A powerful 3D engine for creating the interactive 3D model viewer. -- **Custom Blocks**: Tailored blocks like the Babylon 3D Model Viewer and IconRow, designed to enhance the functionality and aesthetics of the site. diff --git a/csp.js b/csp.js index 49dcb5d..2a118e1 100644 --- a/csp.js +++ b/csp.js @@ -12,7 +12,7 @@ const policies = { ], 'font-src': ["'self'"], 'frame-src': ["'self'"], - 'connect-src': ["'self'", 'https://maps.googleapis.com', 'http://localhost:33000'], + 'connect-src': ["'self'", 'https://maps.googleapis.com', 'http://localhost:34000'], } module.exports = Object.entries(policies) diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03..40c3d68 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/package.json b/package.json index 5a55b05..2c57b4a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "portfolio", + "name": "unforeseentravels", "description": "Website template for Payload", "version": "1.0.0", "main": "dist/server.js", diff --git a/src/admin/css/admin.css b/src/admin/css/admin.css index 3330f8e..6e3ae6b 100644 --- a/src/admin/css/admin.css +++ b/src/admin/css/admin.css @@ -5,3 +5,11 @@ body { .nav { background: rgba(0, 0, 0, 0.8); } + +.nav nav a { + color: white; +} + +.nav-group__toggle { + color: #c0c0c0 +} diff --git a/src/app/(pages)/styleguide/content-block/page.tsx b/src/app/(pages)/styleguide/content-block/page.tsx index 105dd80..1336ded 100644 --- a/src/app/(pages)/styleguide/content-block/page.tsx +++ b/src/app/(pages)/styleguide/content-block/page.tsx @@ -29,6 +29,18 @@ export default async function ContentBlockPage() { text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', }, ], + useImage: false, + inlineMedia: { + imageScale: 100, + borderRadius: 50, + media: { + url: '/path/to/icon1.jpg', + alt: 'Icon 1', + id: 0, + createdAt: new Date().toDateString(), + updatedAt: new Date().toDateString(), + }, + }, }, ]} /> diff --git a/src/app/_blocks/Content/index.module.scss b/src/app/_blocks/Content/index.module.scss index 63a93d9..61d2efe 100644 --- a/src/app/_blocks/Content/index.module.scss +++ b/src/app/_blocks/Content/index.module.scss @@ -37,6 +37,12 @@ } } +.columnImageContainer { + overflow: hidden; + margin: 0 auto; + image-rendering: optimizeQuality; +} + .link { margin-top: var(--base); } diff --git a/src/app/_blocks/Content/index.tsx b/src/app/_blocks/Content/index.tsx index da228b9..8ed8c85 100644 --- a/src/app/_blocks/Content/index.tsx +++ b/src/app/_blocks/Content/index.tsx @@ -3,6 +3,7 @@ import React from 'react' import { Page } from '../../../payload/payload-types' import { Gutter } from '../../_components/Gutter' import { CMSLink } from '../../_components/Link' +import { Media } from '../../_components/Media' import RichText from '../../_components/RichText' import classes from './index.module.scss' @@ -22,11 +23,28 @@ export const ContentBlock: React.FC< {columns && columns.length > 0 && columns.map((col, index) => { - const { enableLink, richText, link, size } = col + const { enableLink, richText, link, size, useImage, inlineMedia } = col + + if (useImage && !inlineMedia) return
Improper image config...
+ + const { imageScale, borderRadius, media } = inlineMedia return (
+ {useImage && ( + <> +
+ +
+ + )} {enableLink && }
) diff --git a/src/app/_graphql/blocks.ts b/src/app/_graphql/blocks.ts index 0d673a9..451d212 100644 --- a/src/app/_graphql/blocks.ts +++ b/src/app/_graphql/blocks.ts @@ -1,6 +1,6 @@ import { CATEGORIES } from './categories' import { LINK_FIELDS } from './link' -import { MEDIA } from './media' +import { INLINE_MEDIA, MEDIA } from './media' import { META } from './meta' export const CALL_TO_ACTION = ` @@ -20,6 +20,8 @@ export const CONTENT = ` invertBackground columns { size + useImage + ${INLINE_MEDIA} richText enableLink link ${LINK_FIELDS()} diff --git a/src/app/_graphql/media.ts b/src/app/_graphql/media.ts index fbf8e52..ed7225b 100644 --- a/src/app/_graphql/media.ts +++ b/src/app/_graphql/media.ts @@ -10,3 +10,9 @@ caption export const MEDIA = `media { ${MEDIA_FIELDS} }` + +export const INLINE_MEDIA = `inlineMedia { +imageScale +borderRadius +${MEDIA} +}` diff --git a/src/payload/blocks/Content/index.ts b/src/payload/blocks/Content/index.ts index 8568aa4..b229fe0 100644 --- a/src/payload/blocks/Content/index.ts +++ b/src/payload/blocks/Content/index.ts @@ -1,5 +1,6 @@ import type { Block, Field } from 'payload/types' +import { inlineMedia } from '../../fields/inlineMedia' import { invertBackground } from '../../fields/invertBackground' import link from '../../fields/link' import richText from '../../fields/richText' @@ -28,7 +29,24 @@ const columnFields: Field[] = [ }, ], }, - richText(), + { + name: 'useImage', + type: 'checkbox', + defaultValue: false, + }, + inlineMedia({ + overrides: { + admin: { + condition: (_, siblingData) => siblingData?.useImage === true, + }, + }, + }), + richText({ + admin: { + condition: (_, siblingData) => siblingData?.useImage === false, + }, + required: false, + }), { name: 'enableLink', type: 'checkbox', diff --git a/src/payload/fields/inlineMedia.ts b/src/payload/fields/inlineMedia.ts new file mode 100644 index 0000000..b610481 --- /dev/null +++ b/src/payload/fields/inlineMedia.ts @@ -0,0 +1,36 @@ +import type { Field } from 'payload/types' + +import deepMerge from '../utilities/deepMerge' + +type InlineMediaType = (options?: { overrides?: Record }) => Field + +export const inlineMedia: InlineMediaType = ({ overrides = {} } = {}) => { + const defaultField: Field = { + name: 'inlineMedia', + type: 'group', + admin: { + condition: (_, siblingData) => siblingData?.useImage === true, + }, + fields: [ + { + name: 'imageScale', + type: 'number', + required: false, + }, + { + name: 'borderRadius', + type: 'number', + required: false, + }, + { + name: 'media', + type: 'upload', + relationTo: 'media', + required: false, + }, + ], + } + + // Merge the default field with any overrides + return deepMerge(defaultField, overrides) +} diff --git a/src/payload/generated-schema.graphql b/src/payload/generated-schema.graphql index 97f102c..72b1b14 100644 --- a/src/payload/generated-schema.graphql +++ b/src/payload/generated-schema.graphql @@ -387,6 +387,8 @@ type Content { type Content_Columns { size: Content_Columns_size + useImage: Boolean + inlineMedia: Content_Columns_InlineMedia richText(depth: Int): JSON enableLink: Boolean link: Content_Columns_Link @@ -400,6 +402,200 @@ enum Content_Columns_size { full } +type Content_Columns_InlineMedia { + imageScale: Float + borderRadius: Float + media(where: Content_Columns_InlineMedia_Media_where): Media +} + +input Content_Columns_InlineMedia_Media_where { + alt: Content_Columns_InlineMedia_Media_alt_operator + caption: Content_Columns_InlineMedia_Media_caption_operator + updatedAt: Content_Columns_InlineMedia_Media_updatedAt_operator + createdAt: Content_Columns_InlineMedia_Media_createdAt_operator + url: Content_Columns_InlineMedia_Media_url_operator + filename: Content_Columns_InlineMedia_Media_filename_operator + mimeType: Content_Columns_InlineMedia_Media_mimeType_operator + filesize: Content_Columns_InlineMedia_Media_filesize_operator + width: Content_Columns_InlineMedia_Media_width_operator + height: Content_Columns_InlineMedia_Media_height_operator + focalX: Content_Columns_InlineMedia_Media_focalX_operator + focalY: Content_Columns_InlineMedia_Media_focalY_operator + id: Content_Columns_InlineMedia_Media_id_operator + AND: [Content_Columns_InlineMedia_Media_where_and] + OR: [Content_Columns_InlineMedia_Media_where_or] +} + +input Content_Columns_InlineMedia_Media_alt_operator { + equals: String + not_equals: String + like: String + contains: String + in: [String] + not_in: [String] + all: [String] +} + +input Content_Columns_InlineMedia_Media_caption_operator { + equals: JSON + not_equals: JSON + like: JSON + contains: JSON + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_updatedAt_operator { + equals: DateTime + not_equals: DateTime + greater_than_equal: DateTime + greater_than: DateTime + less_than_equal: DateTime + less_than: DateTime + like: DateTime + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_createdAt_operator { + equals: DateTime + not_equals: DateTime + greater_than_equal: DateTime + greater_than: DateTime + less_than_equal: DateTime + less_than: DateTime + like: DateTime + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_url_operator { + equals: String + not_equals: String + like: String + contains: String + in: [String] + not_in: [String] + all: [String] + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_filename_operator { + equals: String + not_equals: String + like: String + contains: String + in: [String] + not_in: [String] + all: [String] + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_mimeType_operator { + equals: String + not_equals: String + like: String + contains: String + in: [String] + not_in: [String] + all: [String] + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_filesize_operator { + equals: Float + not_equals: Float + greater_than_equal: Float + greater_than: Float + less_than_equal: Float + less_than: Float + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_width_operator { + equals: Float + not_equals: Float + greater_than_equal: Float + greater_than: Float + less_than_equal: Float + less_than: Float + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_height_operator { + equals: Float + not_equals: Float + greater_than_equal: Float + greater_than: Float + less_than_equal: Float + less_than: Float + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_focalX_operator { + equals: Float + not_equals: Float + greater_than_equal: Float + greater_than: Float + less_than_equal: Float + less_than: Float + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_focalY_operator { + equals: Float + not_equals: Float + greater_than_equal: Float + greater_than: Float + less_than_equal: Float + less_than: Float + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_id_operator { + equals: String + not_equals: String + like: String + contains: String + in: [String] + not_in: [String] + all: [String] + exists: Boolean +} + +input Content_Columns_InlineMedia_Media_where_and { + alt: Content_Columns_InlineMedia_Media_alt_operator + caption: Content_Columns_InlineMedia_Media_caption_operator + updatedAt: Content_Columns_InlineMedia_Media_updatedAt_operator + createdAt: Content_Columns_InlineMedia_Media_createdAt_operator + url: Content_Columns_InlineMedia_Media_url_operator + filename: Content_Columns_InlineMedia_Media_filename_operator + mimeType: Content_Columns_InlineMedia_Media_mimeType_operator + filesize: Content_Columns_InlineMedia_Media_filesize_operator + width: Content_Columns_InlineMedia_Media_width_operator + height: Content_Columns_InlineMedia_Media_height_operator + focalX: Content_Columns_InlineMedia_Media_focalX_operator + focalY: Content_Columns_InlineMedia_Media_focalY_operator + id: Content_Columns_InlineMedia_Media_id_operator + AND: [Content_Columns_InlineMedia_Media_where_and] + OR: [Content_Columns_InlineMedia_Media_where_or] +} + +input Content_Columns_InlineMedia_Media_where_or { + alt: Content_Columns_InlineMedia_Media_alt_operator + caption: Content_Columns_InlineMedia_Media_caption_operator + updatedAt: Content_Columns_InlineMedia_Media_updatedAt_operator + createdAt: Content_Columns_InlineMedia_Media_createdAt_operator + url: Content_Columns_InlineMedia_Media_url_operator + filename: Content_Columns_InlineMedia_Media_filename_operator + mimeType: Content_Columns_InlineMedia_Media_mimeType_operator + filesize: Content_Columns_InlineMedia_Media_filesize_operator + width: Content_Columns_InlineMedia_Media_width_operator + height: Content_Columns_InlineMedia_Media_height_operator + focalX: Content_Columns_InlineMedia_Media_focalX_operator + focalY: Content_Columns_InlineMedia_Media_focalY_operator + id: Content_Columns_InlineMedia_Media_id_operator + AND: [Content_Columns_InlineMedia_Media_where_and] + OR: [Content_Columns_InlineMedia_Media_where_or] +} + type Content_Columns_Link { type: Content_Columns_Link_type newTab: Boolean diff --git a/src/payload/payload-types.ts b/src/payload/payload-types.ts index afcd0f1..348cf5c 100644 --- a/src/payload/payload-types.ts +++ b/src/payload/payload-types.ts @@ -87,9 +87,17 @@ export interface Page { columns?: | { size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null; - richText: { - [k: string]: unknown; - }[]; + useImage?: boolean | null; + inlineMedia?: { + imageScale?: number | null; + borderRadius?: number | null; + media?: number | Media | null; + }; + richText?: + | { + [k: string]: unknown; + }[] + | null; enableLink?: boolean | null; link?: { type?: ('reference' | 'custom') | null; @@ -340,9 +348,17 @@ export interface Post { columns?: | { size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null; - richText: { - [k: string]: unknown; - }[]; + useImage?: boolean | null; + inlineMedia?: { + imageScale?: number | null; + borderRadius?: number | null; + media?: number | Media | null; + }; + richText?: + | { + [k: string]: unknown; + }[] + | null; enableLink?: boolean | null; link?: { type?: ('reference' | 'custom') | null; @@ -504,9 +520,17 @@ export interface Post { columns?: | { size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null; - richText: { - [k: string]: unknown; - }[]; + useImage?: boolean | null; + inlineMedia?: { + imageScale?: number | null; + borderRadius?: number | null; + media?: number | Media | null; + }; + richText?: + | { + [k: string]: unknown; + }[] + | null; enableLink?: boolean | null; link?: { type?: ('reference' | 'custom') | null; @@ -715,9 +739,17 @@ export interface Project { columns?: | { size?: ('oneThird' | 'half' | 'twoThirds' | 'full') | null; - richText: { - [k: string]: unknown; - }[]; + useImage?: boolean | null; + inlineMedia?: { + imageScale?: number | null; + borderRadius?: number | null; + media?: number | Media | null; + }; + richText?: + | { + [k: string]: unknown; + }[] + | null; enableLink?: boolean | null; link?: { type?: ('reference' | 'custom') | null; diff --git a/src/payload/payload.config.ts b/src/payload/payload.config.ts index 584bd40..9183405 100644 --- a/src/payload/payload.config.ts +++ b/src/payload/payload.config.ts @@ -24,7 +24,7 @@ import { Header } from './globals/Header' import { Settings } from './globals/Settings' const generateTitle: GenerateTitle = () => { - return 'My Website' + return 'Unforeseen Travels' } dotenv.config({