diff --git a/docker-compose.yml b/docker-compose.yml index 27fd94c..4f08007 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,6 @@ services: environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=admin - - POSTGRES_DB=prisma + - POSTGRES_DB=website volumes: - ./pgdata:/var/lib/postgresql/data diff --git a/package.json b/package.json index 10189e6..14c13c0 100644 --- a/package.json +++ b/package.json @@ -11,25 +11,29 @@ }, "type": "module", "dependencies": { + "@heroicons/react": "^2.2.0", "@payloadcms/db-postgres": "^3.31.0", "@payloadcms/next": "^3.31.0", "@payloadcms/richtext-lexical": "^3.31.0", "cross-env": "^7.0.3", "graphql": "^16.10.0", + "motion": "^12.6.5", "next": "15.2.4", "payload": "^3.31.0", + "postcss": "^8.5.3", "react": "^19.0.0", "react-dom": "^19.0.0", "sharp": "^0.33.5" }, "devDependencies": { - "@tailwindcss/postcss": "^4", + "@tailwindcss/postcss": "^4.0.17", "@tailwindcss/typography": "^0.5.16", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "clsx": "^2.1.1", "prettier": "^3.5.3", - "tailwindcss": "^4", + "tailwindcss": "^4.0.17", "typescript": "^5" }, "pnpm": { diff --git a/payload-types.ts b/payload-types.ts index 857f4a8..9983911 100644 --- a/payload-types.ts +++ b/payload-types.ts @@ -93,10 +93,12 @@ export interface Config { defaultIDType: number; }; globals: { - home: Home; + homeHero: HomeHero; + homeProjects: HomeProject; }; globalsSelect: { - home: HomeSelect | HomeSelect; + homeHero: HomeHeroSelect | HomeHeroSelect; + homeProjects: HomeProjectsSelect | HomeProjectsSelect; }; locale: null; user: User & { @@ -549,33 +551,54 @@ export interface PayloadMigrationsSelect { } /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "home". + * via the `definition` "homeHero". */ -export interface Home { +export interface HomeHero { id: number; - title?: string | null; - heroTitle?: string | null; - heroDescription?: { - root: { - type: string; - children: { + titleGroup: { + title: string; + }; + heroGroup: { + heroTitle: string; + heroDescription: { + 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; - }[]; - direction: ('ltr' | 'rtl') | null; - format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; - indent: number; - version: number; + }; + [k: string]: unknown; }; - [k: string]: unknown; - } | null; - heroItems?: - | { - name: string; - id?: string | null; - }[] - | null; + heroItems?: + | { + name: string; + id?: string | null; + }[] + | null; + }; + /** + * This title will be used for SEO purposes, and displayed in the browser tab. + */ + metaTitle: string; + /** + * This description will be used for SEO purposes (e.g., shown in search results and on social media cards). + */ + metaDescription: string; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "homeProjects". + */ +export interface HomeProject { + id: number; projectsTitle?: string | null; projectsDescription?: { root: { @@ -597,18 +620,37 @@ export interface Home { } /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "home_select". + * via the `definition` "homeHero_select". */ -export interface HomeSelect { - title?: T; - heroTitle?: T; - heroDescription?: T; - heroItems?: +export interface HomeHeroSelect { + titleGroup?: | T | { - name?: T; - id?: T; + title?: T; }; + heroGroup?: + | T + | { + heroTitle?: T; + heroDescription?: T; + heroItems?: + | T + | { + name?: T; + id?: T; + }; + }; + metaTitle?: T; + metaDescription?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "homeProjects_select". + */ +export interface HomeProjectsSelect { projectsTitle?: T; projectsDescription?: T; updatedAt?: T; diff --git a/payload.config.ts b/payload.config.ts index 65c2383..01847d6 100644 --- a/payload.config.ts +++ b/payload.config.ts @@ -3,13 +3,14 @@ import { FixedToolbarFeature, lexicalEditor } from '@payloadcms/richtext-lexical import { postgresAdapter } from '@payloadcms/db-postgres' import { buildConfig } from 'payload' -import { Home } from '@/globals/Home' +import { HomeHero } from '@/globals/Home/Hero' import { News } from './src/collections/News' import { Projects } from '@/collections/Projects' import { Images } from '@/collections/media/Images' import { Documents } from '@/collections/media/Documents' import { Data } from '@/collections/media/Data' +import { HomeProjects } from '@/globals/Home/Projects' export default buildConfig({ // If you'd like to use Rich Text, pass your editor here @@ -22,7 +23,7 @@ export default buildConfig({ serverURL: process.env.SERVER_URL || 'http://localhost:3000', - globals: [Home], + globals: [HomeHero, HomeProjects], // Define and configure your collections in this array collections: [Projects, News, Images, Documents, Data], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f75bcd..75a7c66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@heroicons/react': + specifier: ^2.2.0 + version: 2.2.0(react@19.0.0) '@payloadcms/db-postgres': specifier: ^3.31.0 version: 3.31.0(@types/react@19.0.12)(payload@3.31.0(graphql@16.10.0)(typescript@5.8.2))(react@19.0.0) @@ -23,12 +26,18 @@ importers: graphql: specifier: ^16.10.0 version: 16.10.0 + motion: + specifier: ^12.6.5 + version: 12.6.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next: specifier: 15.2.4 version: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) payload: specifier: ^3.31.0 version: 3.31.0(graphql@16.10.0)(typescript@5.8.2) + postcss: + specifier: ^8.5.3 + version: 8.5.3 react: specifier: ^19.0.0 version: 19.0.0 @@ -40,7 +49,7 @@ importers: version: 0.33.5 devDependencies: '@tailwindcss/postcss': - specifier: ^4 + specifier: ^4.0.17 version: 4.0.17 '@tailwindcss/typography': specifier: ^0.5.16 @@ -54,11 +63,14 @@ importers: '@types/react-dom': specifier: ^19 version: 19.0.4(@types/react@19.0.12) + clsx: + specifier: ^2.1.1 + version: 2.1.1 prettier: specifier: ^3.5.3 version: 3.5.3 tailwindcss: - specifier: ^4 + specifier: ^4.0.17 version: 4.0.17 typescript: specifier: ^5 @@ -651,6 +663,11 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@heroicons/react@2.2.0': + resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} + peerDependencies: + react: '>= 16 || ^19.0.0-rc' + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1482,6 +1499,20 @@ packages: focus-trap@7.5.4: resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} + framer-motion@12.6.5: + resolution: {integrity: sha512-MKvnWov0paNjvRJuIy6x418w23tFqRfS6CXHhZrCiSEpXVlo/F+usr8v4/3G6O0u7CpsaO1qop+v4Ip7PRCBqQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1837,6 +1868,26 @@ packages: monaco-editor@0.38.0: resolution: {integrity: sha512-11Fkh6yzEmwx7O0YoLxeae0qEGFwmyPRlVxpg7oF9czOOCB/iCjdJrG5I67da5WiXK3YJCxoz9TJFE8Tfq/v9A==} + motion-dom@12.6.5: + resolution: {integrity: sha512-jpM9TQLXzYMWMJ7Ec7sAj0iis8oIuu6WvjI3yNKJLdrZyrsI/b2cRInDVL8dCl683zQQq19DpL9cSMP+k8T1NA==} + + motion-utils@12.6.5: + resolution: {integrity: sha512-IsOeKsOF+FWBhxQEDFBO6ZYC8/jlidmVbbLpe9/lXSA9j9kzGIMUuIBx2SZY+0reAS0DjZZ1i7dJp4NHrjocPw==} + + motion@12.6.5: + resolution: {integrity: sha512-X3IIy76nxyk4I87xQEm5Ah8ojQ4qisd+/H592eXF14ha+xqpbDJcWOSf9PEKCOCC0K4PN/0UBaz+MvSQUkIeXQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2855,6 +2906,10 @@ snapshots: '@floating-ui/utils@0.2.9': {} + '@heroicons/react@2.2.0(react@19.0.0)': + dependencies: + react: 19.0.0 + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -3796,6 +3851,15 @@ snapshots: dependencies: tabbable: 6.2.0 + framer-motion@12.6.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + motion-dom: 12.6.5 + motion-utils: 12.6.5 + tslib: 2.8.1 + optionalDependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + fsevents@2.3.3: optional: true @@ -4229,6 +4293,20 @@ snapshots: monaco-editor@0.38.0: {} + motion-dom@12.6.5: + dependencies: + motion-utils: 12.6.5 + + motion-utils@12.6.5: {} + + motion@12.6.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + framer-motion: 12.6.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + tslib: 2.8.1 + optionalDependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + ms@2.1.3: {} nanoid@3.3.11: {} diff --git a/postcss.config.mjs b/postcss.config.mjs index c7bcb4b..4de2724 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -1,5 +1,5 @@ const config = { - plugins: ["@tailwindcss/postcss"], + plugins: { "@tailwindcss/postcss": {} }, }; export default config; diff --git a/public/file.svg b/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/globe.svg b/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/ial-white-transp.png b/public/ial-white-transp.png new file mode 100644 index 0000000..17bcfb6 Binary files /dev/null and b/public/ial-white-transp.png differ diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/window.svg b/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/(frontend)/components/Header/Client.tsx b/src/app/(frontend)/components/Header/Client.tsx new file mode 100644 index 0000000..35af8da --- /dev/null +++ b/src/app/(frontend)/components/Header/Client.tsx @@ -0,0 +1,32 @@ +"use client"; + +import clsx from "clsx"; +import { motion } from "motion/react"; +import React from "react"; + +export default function ClientHeader({ + children, +}: { + children: React.ReactNode; +}) { + // const { scrollY } = useScroll(); + // const [scrolled, setScrolled] = React.useState(0); + + // const scrollPoint = window.innerHeight / 2; + + // useMotionValueEvent(scrollY, "change", (latest) => { + // setScrolled( + // latest == 0 ? 0 : latest > scrollPoint ? 1 : latest / scrollPoint + // ); + // }); + + return ( + + {children} + + ); +} diff --git a/src/app/(frontend)/components/Header/Header.tsx b/src/app/(frontend)/components/Header/Header.tsx new file mode 100644 index 0000000..c76d1e7 --- /dev/null +++ b/src/app/(frontend)/components/Header/Header.tsx @@ -0,0 +1,10 @@ +import ClientHeader from "./Client"; +import ServerHeader from "./Server"; + +export default function Header() { + return ( + + + + ); +} diff --git a/src/app/(frontend)/components/Header/Server.tsx b/src/app/(frontend)/components/Header/Server.tsx new file mode 100644 index 0000000..04dd0b6 --- /dev/null +++ b/src/app/(frontend)/components/Header/Server.tsx @@ -0,0 +1,44 @@ +// import SignIn from "@/components/sign-in"; +// import SignOut from "@/components/sign-out"; +import Image from "next/image"; + +import ialLogo from "../../../../../public/ial-white-transp.png"; +import Link from "next/link"; +import Button from "../ui/Button"; + +// import { auth } from "@/auth"; + +export default async function ServerHeader() { + // const session = await auth(); + + return ( + + ); +} diff --git a/src/app/(frontend)/components/home/CTA.tsx b/src/app/(frontend)/components/home/CTA.tsx new file mode 100644 index 0000000..32ea95b --- /dev/null +++ b/src/app/(frontend)/components/home/CTA.tsx @@ -0,0 +1,22 @@ +import { ChevronRightIcon } from "@heroicons/react/20/solid"; +import Button from "../ui/Button"; + +export default function CTA() { + return ( +
+
+

+ Want to collaborate? +

+ +
+
+ ); +} diff --git a/src/app/(frontend)/components/home/Hero.tsx b/src/app/(frontend)/components/home/Hero.tsx new file mode 100644 index 0000000..23a46ac --- /dev/null +++ b/src/app/(frontend)/components/home/Hero.tsx @@ -0,0 +1,81 @@ +import React from "react"; + +import configPromise from "@payload-config"; +import { getPayload } from "payload"; + +import ScrollToSection from "./ScrollToSection"; +import { RichText } from "@payloadcms/richtext-lexical/react"; + +export default async function Hero() { + const payload = await getPayload({ config: configPromise }); + + const hero = await payload.findGlobal({ + slug: "homeHero", + }); + + return ( + <> +
+ hi +
+

+ {hero.titleGroup.title} +

+
+ +
+
+
+ +
+
+
+

+ {hero.heroGroup?.heroTitle} +

+
+ +
+
+
+
+
+ +
+
+
+ + ); +} + +const Oribal = () => { + return ( +
+
+
+
+ + + + + + + +
+ ); +}; + +const Planet = ({ text, className }: { text: string; className?: string }) => { + return ( +
+ + {text} + +
+ ); +}; diff --git a/src/app/(frontend)/components/home/News.tsx b/src/app/(frontend)/components/home/News.tsx new file mode 100644 index 0000000..e8cca76 --- /dev/null +++ b/src/app/(frontend)/components/home/News.tsx @@ -0,0 +1,59 @@ +import Button from "@/components/ui/button"; +import prisma from "@/lib/prisma"; +import { ChevronRightIcon } from "@heroicons/react/20/solid"; +import dayjs from "dayjs"; + +export default function News() { + return ( +
+
+
+

+ We've got some news +

+ +
+ +
+ +
+
+
+ ); +} + +async function Gallery() { + const news = await prisma.news.findMany({ + where: { + draft: false, + }, + orderBy: { + date: "desc", + }, + take: 3, + }); + + // TODO: parse markdown + return ( +
+ {news.map((item) => ( +
+
+ {dayjs(item.date).format("DD/MM/YYYY")} +
+

{item.title}

+
+

+ {item.body} +

+ +
+ ))} +
+ ); +} diff --git a/src/app/(frontend)/components/home/Partners.tsx b/src/app/(frontend)/components/home/Partners.tsx new file mode 100644 index 0000000..99c3304 --- /dev/null +++ b/src/app/(frontend)/components/home/Partners.tsx @@ -0,0 +1,54 @@ +"use client"; + +import clsx from "clsx"; +import { useState } from "react"; + +const PARTNERS = [ + { label: "Aotearoa", partners: [] }, + { label: "International", partners: [] }, +]; + +export default function Partners() { + const [partner, setPartner] = useState(PARTNERS[0].label); + + return ( +
+
+

+ We've worked with … +

+ +
+
    + {PARTNERS.map((group) => ( +
  • setPartner(group.label)} + > + {group.label} + +
  • + ))} +
+
+ {Array.from({ length: 12 }).map((_, index) => ( +
+ ))} +
+
+
+
+ ); +} diff --git a/src/app/(frontend)/components/home/Projects.tsx b/src/app/(frontend)/components/home/Projects.tsx new file mode 100644 index 0000000..f90f894 --- /dev/null +++ b/src/app/(frontend)/components/home/Projects.tsx @@ -0,0 +1,79 @@ +import Button from "@/components/ui/button"; +import prisma from "@/lib/prisma"; +import { ChevronRightIcon } from "@heroicons/react/20/solid"; +import Image from "next/image"; + +export default function Projects() { + return ( +
+
+
+

+ We do data differently +

+ +

+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. Commodi + modi repudiandae deleniti ut at, voluptatibus alias asperiores + temporibus sed id numquam nemo error ipsa adipisci perferendis. + Consequuntur vitae tenetur dolore. +

+

+ Eveniet doloremque ducimus deserunt labore, distinctio odit sunt + mollitia ab repellat excepturi aliquam fuga impedit! Maiores porro + qui consequatur nisi voluptates, odit facere nobis corrupti labore + ducimus cumque! Voluptates, beatae? +

+ +
+ +
+ +
+
+
+ ); +} + +async function Gallery() { + const projects = await prisma.project.findMany({ + where: { + feature: true, + }, + }); + + return ( +
+ {projects.map((project) => ( +
+
+ {project.image && ( + {project.title} + )} +
+
+

+ {project.title} +

+ +
+

{project.summary}

+
+ ))} +
+ ); +} diff --git a/src/app/(frontend)/components/home/ScrollToSection.tsx b/src/app/(frontend)/components/home/ScrollToSection.tsx new file mode 100644 index 0000000..583a34b --- /dev/null +++ b/src/app/(frontend)/components/home/ScrollToSection.tsx @@ -0,0 +1,13 @@ +"use client"; +import { ArrowDownCircleIcon } from "@heroicons/react/20/solid"; + +export default function ScrollToSection({ anchor }: { anchor: string }) { + return ( + { + document.querySelector(anchor)?.scrollIntoView({ behavior: "smooth" }); + }} + /> + ); +} diff --git a/src/app/(frontend)/components/ui/Button.tsx b/src/app/(frontend)/components/ui/Button.tsx new file mode 100644 index 0000000..3229ed5 --- /dev/null +++ b/src/app/(frontend)/components/ui/Button.tsx @@ -0,0 +1,23 @@ +export default function Button({ + children, + type, + variant, + className, + ...props +}: { + children: React.ReactNode; + type: "primary" | "secondary" | "alternate"; + variant?: "filled" | "outlined"; + className?: string; + // [x: string]: +}) { + const btnVariant = variant ?? "filled"; + return ( + + ); +} diff --git a/src/app/(frontend)/globals.css b/src/app/(frontend)/globals.css deleted file mode 100644 index 37ff499..0000000 --- a/src/app/(frontend)/globals.css +++ /dev/null @@ -1,27 +0,0 @@ -@import "tailwindcss"; -@plugin "@tailwindcss/typography"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index f7fa87e..13c6ede 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -1,21 +1,30 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import configPromise from "@payload-config"; +import { getPayload } from "payload"; -const geistSans = Geist({ - variable: "--font-geist-sans", +import "../globals.css"; +import Header from "./components/Header/Header"; + +import { Inter } from "next/font/google"; + +const inter = Inter({ subsets: ["latin"], + display: "swap", + variable: "--font-inter", }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +export async function generateMetadata(): Promise { + const payload = await getPayload({ config: configPromise }); -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; + const homeHero = await payload.findGlobal({ + slug: "homeHero", + }); + + return { + title: homeHero.metaTitle, + description: homeHero.metaDescription, + }; +} export default function RootLayout({ children, @@ -24,10 +33,10 @@ export default function RootLayout({ }>) { return ( - - {children} + +
+
{children}
+ {/*