Compare commits

..

2 Commits

Author SHA1 Message Date
Tom Elliott
60d67bb989 images on projects 2025-06-02 10:56:06 +12:00
Tom Elliott
6f0a5cc2fa build fixes 2025-06-01 11:38:06 +12:00
16 changed files with 316 additions and 204 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -159,6 +159,7 @@ export interface Project {
* Show this project on the homepage.
*/
featured?: boolean | null;
banner?: (number | null) | Image;
links?:
| {
link: string;
@ -173,36 +174,6 @@ export interface Project {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "news".
*/
export interface News {
id: number;
title: string;
/**
* The slug is used to identify the news item in the URL.
*/
slug: string;
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;
};
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "images".
@ -249,6 +220,36 @@ export interface Image {
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "news".
*/
export interface News {
id: number;
title: string;
/**
* The slug is used to identify the news item in the URL.
*/
slug: string;
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;
};
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "documents".
@ -389,6 +390,7 @@ export interface ProjectsSelect<T extends boolean = true> {
slug?: T;
content?: T;
featured?: T;
banner?: T;
links?:
| T
| {

View File

@ -27,9 +27,14 @@ export default function Page() {
</div>
<div className="p-4 space-y-2">
{BUTTON_VARIANTS.map((variant) => (
<div className="grid grid-cols-5 gap-4">
<div className="grid grid-cols-5 gap-4" key={variant}>
{BUTTON_TYPES.map((type) => (
<Button type={type} variant={variant} className="capitalize">
<Button
key={type}
type={type}
variant={variant}
className="capitalize"
>
{type} button
</Button>
))}

View File

@ -13,7 +13,7 @@ export default function Button({
className?: string;
// [x: string]:
}) {
const btnVariant = variant ?? "filled";
// const btnVariant = variant ?? "filled";
return (
<button
className={cn(

View File

@ -1,12 +1,12 @@
import { getPayload } from "payload";
import config from "@payload-config";
import Link from "next/link";
import Image from "next/image";
import { Image as PImage } from "@payload-types";
// import Image from "next/image";
// import { Image as PImage } from "@payload-types";
const isValidLogo = (image: number | PImage): image is PImage => {
return typeof image !== "number";
};
// const isValidLogo = (image: number | PImage): image is PImage => {
// return typeof image !== "number";
// };
export default async function Header() {
const payload = await getPayload({ config });

View File

@ -4,13 +4,14 @@ import { RichText } from "@payloadcms/richtext-lexical/react";
import { motion, useScroll, useTransform } from "motion/react";
import { useRef, useEffect, useState, useCallback } from "react";
import * as d3 from "d3";
import { HomeHero } from "@payload-types";
export default function HeroIntro({
title,
desc,
}: {
title: string;
desc: any;
desc: HomeHero["heroGroup"]["heroDescription"];
}) {
const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
@ -23,7 +24,7 @@ export default function HeroIntro({
const [windowSize, setWindowSize] = useState<[number, number]>();
useEffect(() => {
window.addEventListener("resize", (e) => {
window.addEventListener("resize", () => {
setWindowSize([window.innerWidth, window.innerHeight]);
});
setWindowSize([window.innerWidth, window.innerHeight]);
@ -48,7 +49,7 @@ export default function HeroIntro({
const drawArea = d3
.area()
.x((p) => xScale(p[0]))
.y0((p) => 0)
.y0(() => 0)
.y1((p) => yScale(p[1]));
return drawArea(beam);
}, [windowSize, beamSize]);

View File

@ -23,7 +23,7 @@ export default function HeroData({
}: {
items: HomeHero["heroGroup"]["heroItems"];
}) {
const itemKeys: (keyof typeof items)[] = Object.keys(items) as any;
const itemKeys = Object.keys(items) as (keyof typeof items)[];
const itemArray = itemKeys.map((k) => ({ key: k, ...items[k] }));
return (
@ -72,7 +72,7 @@ const Item = ({
return (
<div
ref={containerRef}
className="w-full h-screen max-w-6xl p-12 grid grid-cols-2 gap-12 z-10 items-center text-white"
className="w-full h-screen max-w-6xl p-12 grid grid-rows-2 lg:grid-cols-2 lg:grid-rows-1 gap-12 z-10 items-center text-white"
>
<motion.div
style={{ opacity }}

View File

@ -1,9 +1,13 @@
"use client";
import useWindow from "@/app/(website)/hooks/useWindow";
import { HomeProject, Project } from "@payload-types";
import { RichText } from "@payloadcms/richtext-lexical/react";
import { motion, useScroll, useTransform } from "motion/react";
import { useRef } from "react";
import Image from "next/image";
import Link from "next/link";
import { useRef, useState } from "react";
import PayloadImage from "../../PayloadImage";
export default function Projects({
text,
@ -22,10 +26,14 @@ export default function Projects({
const headerOpacity = useTransform(scrollYProgress, [0.8, 1], [0, 1]);
const textOpacity = useTransform(scrollYProgress, [0.9, 1], [0, 1]);
const [active, setActive] = useState(1);
const { width } = useWindow();
const largeScreen = width >= 1024;
return (
<section
ref={containerRef}
className="bg-black flex flex-col items-center justify-center pb-48"
className="bg-black flex flex-col items-center justify-center lg:pb-48"
>
<div className="flex flex-col items-center justify-center h-screen relative">
<div className="max-w-6xl w-full flex flex-col justify-center relative z-10">
@ -54,20 +62,84 @@ export default function Projects({
</div>
</div>
</div>
<div className="w-full max-w-4xl flex flex-col items-center gap-48 relative">
<div className="w-full px-12 flex flex-col lg:items-stretch gap-12 relative pb-12 lg:flex-row py-12 lg:h-screen">
{[...projects, ...projects].map((project, i) => (
<div
<motion.div
key={i}
className=" bg-accent-700 w-full px-8 py-12 gap-12 flex flex-col"
className="w-full lg:w-auto bg-white text-black gap-8 lg:gap-8 flex flex-col lg:cursor-pointer relative"
animate={{
flex: largeScreen && active === i ? 5 : 1,
}}
onClick={() => setActive(i)}
>
<h1 className="text-4xl font-display">{project.title}</h1>
{project.banner && typeof project.banner !== "number" && (
<motion.div
className="absolute h-full w-full"
animate={
{
// opacity: largeScreen ? (active === i ? 0.5 : 1) : 0.3,
}
}
>
<PayloadImage
img={project.banner}
className="h-full w-full object-cover z-0"
/>
</motion.div>
)}
{(!largeScreen || active === i) && (
<motion.div
style={{
opacity: 0,
y: -10,
}}
animate={{
opacity: 1,
y: 0,
}}
transition={{
delay: 0.3,
duration: 1,
}}
className="p-8 lg:p-12 z-10"
>
<div className="p-2 lg:p-4 flex flex-col gap-6 lg:gap-12 bg-white/50 backdrop-blur-lg shadow-lg rounded">
<h1 className="text-2xl lg:text-4xl font-display">
{project.title}
</h1>
<div className="line-clamp-6 overflow-ellipsis text-xl">
<motion.div
style={{
opacity: 0,
y: 30,
}}
animate={{
opacity: 1,
y: 0,
}}
transition={{
delay: 0.6,
duration: 0.5,
}}
className="line-clamp-6 overflow-ellipsis text-lg lg:text-2xl"
>
<RichText data={project.content} />
</motion.div>
</div>
</div>
</motion.div>
)}
</motion.div>
))}
</div>
<div className="w-full flex flex-col items-center justify-center pb-48">
<div className=" h-1/2 bg-white text-accent-600 w-full flex flex-col items-center justify-center gap-8 py-24">
<h4 className="text-4xl font-display">Have a project idea?</h4>
<Link href="">
<p className="text-xl font-bold">Get in touch &gt;</p>
</Link>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,28 @@
import type { Image as PImage } from "@payload-types";
import Image from "next/image";
export default function PayloadImage({
img,
className,
}: {
img: PImage;
className: string;
}) {
const url = img.url;
const width = img.width;
const height = img.height;
if (!url || !width || !height) return <></>;
console.log("URL: ", url);
return (
<Image
src={img.url ?? ""}
alt={img.alt ?? ""}
className={className}
width={width}
height={height}
/>
);
}

View File

@ -2,14 +2,14 @@
import { ReactNode, useEffect } from "react";
import Lenis from "lenis";
import Snap from "lenis/snap";
// import Snap from "lenis/snap";
export default function ({
export default function SmoothScroll({
children,
snapAt,
// snapAt,
}: {
children: ReactNode;
snapAt?: string[];
// snapAt?: string[];
}) {
useEffect(() => {
const lenis = new Lenis();

View File

@ -35,3 +35,7 @@
/* Other vars */
--header-height: 100px;
}
.payload-richtext p {
@apply mb-2;
}

View File

@ -7,7 +7,6 @@ import HeroData from "./components/Home/Hero/02-data";
import bgImage from "./bg.jpg";
import Image from "next/image";
import Projects from "./components/Home/Projects";
import Link from "next/link";
export default async function Home() {
const payload = await getPayload({ config });
@ -27,13 +26,16 @@ export default async function Home() {
});
return (
<SmoothScroll snapAt={["section"]}>
<SmoothScroll>
<div className="text-white">
<div className="absolute h-full mt-[var(--header-height)] w-full opacity-50 ">
<Image src={bgImage} alt="Bg image" objectFit="cover" />
<Image
src={bgImage}
alt="Bg image"
className="h-full w-full object-cover"
/>
</div>
<div className="h-screen pt-[var(--header-height)] flex flex-col items-center justify-end text-white pb-[10vh] relative">
{/* <ScrollingNumbers numbers={randomNumbers} /> */}
<h1 className="text-4xl p-8 lg:text-7xl leading-tight max-w-6xl z-10 font-display">
{titleGroup.title}
</h1>
@ -44,15 +46,6 @@ export default async function Home() {
/>
<HeroData items={heroGroup.heroItems} />
<Projects text={projectsText} projects={projects.docs} />
<div className="flex flex-col items-center justify-center pb-48">
<div className="max-w-4xl h-1/2 bg-white text-accent-600 w-full flex flex-col items-center justify-center gap-8 py-24">
<h4 className="text-4xl font-display">Have a project idea?</h4>
<Link href="">
<p className="text-xl font-bold">Get in touch &gt;</p>
</Link>
</div>
</div>
</div>
</SmoothScroll>
);

View File

@ -2,35 +2,33 @@ import { formatSlug } from "@/lib/slugs";
import { CollectionConfig } from "payload";
export const Projects: CollectionConfig = {
slug: 'projects',
slug: "projects",
fields: [
{
name: 'title',
label: 'Title',
type: 'text',
name: "title",
label: "Title",
type: "text",
required: true,
},
{
name: 'slug',
label: 'Slug',
type: 'text',
name: "slug",
label: "Slug",
type: "text",
required: true,
unique: true,
admin: {
position: 'sidebar',
description: 'The slug is used to identify the news item in the URL.',
position: "sidebar",
description: "The slug is used to identify the news item in the URL.",
// readOnly: true,
},
hooks: {
beforeValidate: [
formatSlug('title'),
]
}
beforeValidate: [formatSlug("title")],
},
},
{
name: 'content',
label: 'Content',
type: 'richText',
name: "content",
label: "Content",
type: "richText",
required: true,
},
// list of files
@ -38,47 +36,52 @@ export const Projects: CollectionConfig = {
// keywords
// is featured?
{
name: 'featured',
label: 'Featured',
type: 'checkbox',
name: "featured",
label: "Featured",
type: "checkbox",
defaultValue: false,
admin: {
position: 'sidebar',
description: 'Show this project on the homepage.',
}
position: "sidebar",
description: "Show this project on the homepage.",
},
},
{
name: "banner",
label: "Banner",
type: "upload",
relationTo: "images",
},
// links
{
name: 'links',
label: 'Links',
type: 'array',
name: "links",
label: "Links",
type: "array",
fields: [
{
name: 'link',
label: 'Link',
type: 'text',
name: "link",
label: "Link",
type: "text",
required: true,
},
{
name: 'description',
label: 'Description',
type: 'text',
name: "description",
label: "Description",
type: "text",
required: false,
},
{
name: 'group',
label: 'Group',
type: 'text',
name: "group",
label: "Group",
type: "text",
required: false,
admin: {
description: 'Optional: organise link under this heading',
description: "Optional: organise link under this heading",
},
},
}
],
admin: {
position: 'sidebar',
}
position: "sidebar",
},
]
}
},
],
};

View File

@ -1,45 +1,49 @@
import type { CollectionConfig } from "payload";
export const Images: CollectionConfig = {
slug: 'images',
slug: "images",
access: {
// TODO: fix this
read: () => true,
},
upload: {
staticDir: 'media/images',
staticDir: "media/images",
imageSizes: [
{
name: 'thumbnail',
name: "thumbnail",
width: 400,
height: 300,
position: 'centre',
position: "centre",
},
{
name: 'card',
name: "card",
width: 768,
height: 1024,
position: 'centre',
position: "centre",
},
{
name: 'tablet',
name: "tablet",
width: 1024,
height: undefined,
position: 'centre',
}
position: "centre",
},
],
adminThumbnail: 'thumbnail',
mimeTypes: ['image/*'],
adminThumbnail: "thumbnail",
mimeTypes: ["image/*"],
},
fields: [
{
name: 'alt',
label: 'Alt Text',
type: 'text',
name: "alt",
label: "Alt Text",
type: "text",
},
{
name: 'description',
label: 'Description',
type: 'textarea',
}
name: "description",
label: "Description",
type: "textarea",
},
],
admin: {
group: 'Media',
}
}
group: "Media",
},
};

View File

@ -1,22 +1,22 @@
import { GlobalConfig } from "payload";
export const HomeProjects: GlobalConfig = {
slug: 'homeProjects',
label: 'Projects',
slug: "homeProjects",
label: "Projects",
fields: [
{
name: 'projectsTitle',
label: 'Projects Title',
type: 'text',
defaultValue: 'We do data differently'
name: "projectsTitle",
label: "Projects Title",
type: "text",
defaultValue: "We do data differently",
},
{
name: 'projectsDescription',
label: 'Projects Description',
type: 'richText'
}
name: "projectsDescription",
label: "Projects Description",
type: "richText",
},
],
admin: {
group: 'Home page'
}
group: "Home page",
},
};