some progress

This commit is contained in:
Tom Elliott 2025-04-13 20:31:34 +12:00
parent b1c2720d85
commit 4192a15779
29 changed files with 837 additions and 175 deletions

View File

@ -8,6 +8,6 @@ services:
environment: environment:
- POSTGRES_USER=postgres - POSTGRES_USER=postgres
- POSTGRES_PASSWORD=admin - POSTGRES_PASSWORD=admin
- POSTGRES_DB=prisma - POSTGRES_DB=website
volumes: volumes:
- ./pgdata:/var/lib/postgresql/data - ./pgdata:/var/lib/postgresql/data

View File

@ -11,25 +11,29 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@heroicons/react": "^2.2.0",
"@payloadcms/db-postgres": "^3.31.0", "@payloadcms/db-postgres": "^3.31.0",
"@payloadcms/next": "^3.31.0", "@payloadcms/next": "^3.31.0",
"@payloadcms/richtext-lexical": "^3.31.0", "@payloadcms/richtext-lexical": "^3.31.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"graphql": "^16.10.0", "graphql": "^16.10.0",
"motion": "^12.6.5",
"next": "15.2.4", "next": "15.2.4",
"payload": "^3.31.0", "payload": "^3.31.0",
"postcss": "^8.5.3",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"sharp": "^0.33.5" "sharp": "^0.33.5"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4.0.17",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"clsx": "^2.1.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"tailwindcss": "^4", "tailwindcss": "^4.0.17",
"typescript": "^5" "typescript": "^5"
}, },
"pnpm": { "pnpm": {

View File

@ -93,10 +93,12 @@ export interface Config {
defaultIDType: number; defaultIDType: number;
}; };
globals: { globals: {
home: Home; homeHero: HomeHero;
homeProjects: HomeProject;
}; };
globalsSelect: { globalsSelect: {
home: HomeSelect<false> | HomeSelect<true>; homeHero: HomeHeroSelect<false> | HomeHeroSelect<true>;
homeProjects: HomeProjectsSelect<false> | HomeProjectsSelect<true>;
}; };
locale: null; locale: null;
user: User & { user: User & {
@ -549,33 +551,54 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * 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; id: number;
title?: string | null; titleGroup: {
heroTitle?: string | null; title: string;
heroDescription?: { };
root: { heroGroup: {
type: string; heroTitle: string;
children: { heroDescription: {
root: {
type: string; 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; version: number;
[k: string]: unknown; };
}[]; [k: string]: unknown;
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
}; };
[k: string]: unknown; heroItems?:
} | null; | {
heroItems?: name: string;
| { id?: string | null;
name: string; }[]
id?: string | null; | 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; projectsTitle?: string | null;
projectsDescription?: { projectsDescription?: {
root: { root: {
@ -597,18 +620,37 @@ export interface Home {
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "home_select". * via the `definition` "homeHero_select".
*/ */
export interface HomeSelect<T extends boolean = true> { export interface HomeHeroSelect<T extends boolean = true> {
title?: T; titleGroup?:
heroTitle?: T;
heroDescription?: T;
heroItems?:
| T | T
| { | {
name?: T; title?: T;
id?: 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<T extends boolean = true> {
projectsTitle?: T; projectsTitle?: T;
projectsDescription?: T; projectsDescription?: T;
updatedAt?: T; updatedAt?: T;

View File

@ -3,13 +3,14 @@ import { FixedToolbarFeature, lexicalEditor } from '@payloadcms/richtext-lexical
import { postgresAdapter } from '@payloadcms/db-postgres' import { postgresAdapter } from '@payloadcms/db-postgres'
import { buildConfig } from 'payload' import { buildConfig } from 'payload'
import { Home } from '@/globals/Home' import { HomeHero } from '@/globals/Home/Hero'
import { News } from './src/collections/News' import { News } from './src/collections/News'
import { Projects } from '@/collections/Projects' import { Projects } from '@/collections/Projects'
import { Images } from '@/collections/media/Images' import { Images } from '@/collections/media/Images'
import { Documents } from '@/collections/media/Documents' import { Documents } from '@/collections/media/Documents'
import { Data } from '@/collections/media/Data' import { Data } from '@/collections/media/Data'
import { HomeProjects } from '@/globals/Home/Projects'
export default buildConfig({ export default buildConfig({
// If you'd like to use Rich Text, pass your editor here // 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', serverURL: process.env.SERVER_URL || 'http://localhost:3000',
globals: [Home], globals: [HomeHero, HomeProjects],
// Define and configure your collections in this array // Define and configure your collections in this array
collections: [Projects, News, Images, Documents, Data], collections: [Projects, News, Images, Documents, Data],

82
pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@heroicons/react':
specifier: ^2.2.0
version: 2.2.0(react@19.0.0)
'@payloadcms/db-postgres': '@payloadcms/db-postgres':
specifier: ^3.31.0 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) 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: graphql:
specifier: ^16.10.0 specifier: ^16.10.0
version: 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: next:
specifier: 15.2.4 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) version: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4)
payload: payload:
specifier: ^3.31.0 specifier: ^3.31.0
version: 3.31.0(graphql@16.10.0)(typescript@5.8.2) version: 3.31.0(graphql@16.10.0)(typescript@5.8.2)
postcss:
specifier: ^8.5.3
version: 8.5.3
react: react:
specifier: ^19.0.0 specifier: ^19.0.0
version: 19.0.0 version: 19.0.0
@ -40,7 +49,7 @@ importers:
version: 0.33.5 version: 0.33.5
devDependencies: devDependencies:
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4 specifier: ^4.0.17
version: 4.0.17 version: 4.0.17
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.16 specifier: ^0.5.16
@ -54,11 +63,14 @@ importers:
'@types/react-dom': '@types/react-dom':
specifier: ^19 specifier: ^19
version: 19.0.4(@types/react@19.0.12) version: 19.0.4(@types/react@19.0.12)
clsx:
specifier: ^2.1.1
version: 2.1.1
prettier: prettier:
specifier: ^3.5.3 specifier: ^3.5.3
version: 3.5.3 version: 3.5.3
tailwindcss: tailwindcss:
specifier: ^4 specifier: ^4.0.17
version: 4.0.17 version: 4.0.17
typescript: typescript:
specifier: ^5 specifier: ^5
@ -651,6 +663,11 @@ packages:
'@floating-ui/utils@0.2.9': '@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} 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': '@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@ -1482,6 +1499,20 @@ packages:
focus-trap@7.5.4: focus-trap@7.5.4:
resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} 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: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -1837,6 +1868,26 @@ packages:
monaco-editor@0.38.0: monaco-editor@0.38.0:
resolution: {integrity: sha512-11Fkh6yzEmwx7O0YoLxeae0qEGFwmyPRlVxpg7oF9czOOCB/iCjdJrG5I67da5WiXK3YJCxoz9TJFE8Tfq/v9A==} 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: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@ -2855,6 +2906,10 @@ snapshots:
'@floating-ui/utils@0.2.9': {} '@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': '@img/sharp-darwin-arm64@0.33.5':
optionalDependencies: optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4 '@img/sharp-libvips-darwin-arm64': 1.0.4
@ -3796,6 +3851,15 @@ snapshots:
dependencies: dependencies:
tabbable: 6.2.0 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: fsevents@2.3.3:
optional: true optional: true
@ -4229,6 +4293,20 @@ snapshots:
monaco-editor@0.38.0: {} 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: {} ms@2.1.3: {}
nanoid@3.3.11: {} nanoid@3.3.11: {}

View File

@ -1,5 +1,5 @@
const config = { const config = {
plugins: ["@tailwindcss/postcss"], plugins: { "@tailwindcss/postcss": {} },
}; };
export default config; export default config;

View File

@ -1 +0,0 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 391 B

View File

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/ial-white-transp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 128 B

View File

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

Before

Width:  |  Height:  |  Size: 385 B

View File

@ -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 (
<motion.div
className={clsx(
"text-white absolute w-full top-0 z-50 bg-transparent lg:h-[var(--header-height)]"
)}
>
{children}
</motion.div>
);
}

View File

@ -0,0 +1,10 @@
import ClientHeader from "./Client";
import ServerHeader from "./Server";
export default function Header() {
return (
<ClientHeader>
<ServerHeader />
</ClientHeader>
);
}

View File

@ -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 (
<nav className="flex h-full p-4 justify-between items-center container mx-auto">
{/* left */}
<div className="h-10 lg:h-20 relative w-20 lg:w-80">
<Image
src={ialLogo}
alt="iNZight Analytics Ltd"
className="h-full object-contain object-left"
fill
/>
</div>
{/* right */}
<div className="items-center gap-8 font-bold hidden lg:flex">
<Link href="/about">About</Link>
<Link href="/projects">Projects</Link>
<Link href="/news">News</Link>
<Link href="/apps">
<Button
type="primary"
variant="outlined"
className="border-white text-white hover:bg-white hover:text-black"
>
Apps
</Button>
</Link>
{/* {session ? <SignOut /> : <SignIn />} */}
</div>
</nav>
);
}

View File

@ -0,0 +1,22 @@
import { ChevronRightIcon } from "@heroicons/react/20/solid";
import Button from "../ui/Button";
export default function CTA() {
return (
<section className="bg-linear-to-tr from-accent-950 to-accent-700">
<div className="container px-4 lg:px-24 py-8 lg:py-48 mx-auto flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
<h2 className="text-3xl lg:text-5xl leading-tight text-white font-medium">
Want to collaborate?
</h2>
<Button
className="lg:button-large border-white text-white hover:bg-white hover:text-accent-950 group"
type="alternate"
variant="outlined"
>
Get in touch
<ChevronRightIcon className="h-5 lg:h-10 w-5 lg:w-10 ml-2 group-hover:translate-x-2 transition" />
</Button>
</div>
</section>
);
}

View File

@ -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 (
<>
<section className="relative h-screen bg-black lg:pt-[var(--header-height)]">
hi
<div className="relative z-10 p-4 container mx-auto flex flex-col justify-end h-full pb-12 gap-8">
<h1 className="text-4xl lg:text-8xl lg:px-20 font-medium leading-tight text-white">
{hero.titleGroup.title}
</h1>
<div className="flex justify-center">
<ScrollToSection anchor="#about" />
</div>
</div>
</section>
<section
id="about"
className="h-screen overflow-clip bg-linear-to-b from:black to-accent-950 lg:bg-black"
>
<div className="px-4 py-8 lg:py-0 lg:px-24 h-full flex flex-col lg:flex-row container gap-16 lg:gap-32 lg:items-center justify-center mx-auto text-white">
<div className="flex flex-col lg:flex-1 gap-4 lg:gap-8 z-10">
<h2 className="text-3xl lg:text-5xl leading-tight">
{hero.heroGroup?.heroTitle}
</h2>
<div className="text-lg lg:text-2xl leading-tight">
<RichText data={hero.heroGroup.heroDescription} />
</div>
</div>
<div className="flex-1 lg:h-full flex lg:items-center lg:justify-center relative">
<div className="hidden lg:block h-[200%] bg-radial aspect-square absolute from-accent-900 to-black to-80%"></div>
<div className="hidden lg:block h-[100%] bg-radial aspect-square absolute from-white/10 to-transparent to-20%"></div>
<Oribal />
</div>
</div>
</section>
</>
);
}
const Oribal = () => {
return (
<div className="lg:flex-1 lg:h-1/2 relative space-y-8">
<div className="hidden lg:block absolute h-full w-1/2 rounded-[100%] border-dashed border-white border rotate-10 left-1/2 -translate-x-1/2 translate-y-5"></div>
<div className="hidden lg:block absolute h-full w-1/2 rounded-[100%] border-dashed border-white border rotate-20 left-1/2 -translate-x-1/2"></div>
<div className="hidden lg:block absolute h-full w-1/2 rounded-[100%] border-dashed border-white border rotate-30 left-1/2 -translate-x-1/2 translate-y-10"></div>
<Planet text="Data design" className="right-2/5" />
<Planet text="Data collection" className="left-3/4 top-1/4" />
<Planet text="Data analysis" className="left-3/5 top-3/5" />
<Planet text="Data visualisation" className="left-2/7 top-full" />
<Planet text="Training" className="right-4/5 top-5/7" />
<Planet text="Data sovereignty" className="right-3/5 top-2/5" />
</div>
);
};
const Planet = ({ text, className }: { text: string; className?: string }) => {
return (
<div className={className}>
<span
className={`lg:absolute bg-accent-800/0 border border-white/10 backdrop-blur-md text-accent-100 font-bold px-6 shadow-lg py-2 rounded-full whitespace-nowrap ${className} hover:bg-accent-800`}
>
{text}
</span>
</div>
);
};

View File

@ -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 (
<section className="bg-secondary-50">
<div className="container px-4 lg:px-24 py-8 lg:py-48 mx-auto">
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
<h2 className="text-3xl lg:text-5xl leading-tight text-black">
We&apos;ve got some news
</h2>
<Button type="primary" variant="outlined" className="group">
View all news
<ChevronRightIcon className="h-5 w-5 ml-2 group-hover:translate-x-1" />
</Button>
</div>
<div className="py-12">
<Gallery />
</div>
</div>
</section>
);
}
async function Gallery() {
const news = await prisma.news.findMany({
where: {
draft: false,
},
orderBy: {
date: "desc",
},
take: 3,
});
// TODO: parse markdown
return (
<div className="flex flex-col lg:grid lg:grid-cols-3 place-items-start gap-10 lg:gap-20">
{news.map((item) => (
<div key={item.id} className="space-y-2 lg:space-y-4">
<div className="text-sm text-accent-700">
{dayjs(item.date).format("DD/MM/YYYY")}
</div>
<h3 className="text-md lg:text-2xl text-black">{item.title}</h3>
<hr className="border-accent-800/20 hidden lg:block" />
<p className="text-gray-600 text-base leading-normal line-clamp-[10] hidden lg:block">
{item.body}
</p>
<Button type="alternate" variant="outlined">
Read more <ChevronRightIcon className="h-5 w-5 ml-2" />
</Button>
</div>
))}
</div>
);
}

View File

@ -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 (
<section className="bg-gray-100">
<div className="container px-4 lg:px-24 py-8 lg:py-48 mx-auto lg:space-y-20">
<h2 className="text-3xl lg:text-5xl leading-tight text-black">
We&apos;ve worked with &hellip;
</h2>
<div className="flex gap-6 lg:gap-20 flex-col lg:flex-row text-black">
<ul className="lg:border-t border-gray-500 lg:pr-8 pt-8 lg:space-y-4 flex lg:flex-col items-center lg:items-start gap-x-4">
{PARTNERS.map((group) => (
<li
key={group.label}
className={clsx(
"lg:space-y-4 text-xl lg:text-3xl flex items-center lg:gap-4 font-medium cursor-pointer",
group.label === partner && "text-accent-500"
)}
onClick={() => setPartner(group.label)}
>
{group.label}
<div
className={clsx(
"size-3 bg-accent-500 rounded-full hidden lg:block",
group.label !== partner && "opacity-0"
)}
></div>
</li>
))}
</ul>
<div className="pt-8 border-t border-gray-500 grid grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-12 flex-1 justify-items-stretch">
{Array.from({ length: 12 }).map((_, index) => (
<div
key={index}
className="bg-gray-400/50 h-auto aspect-video rounded shadow"
></div>
))}
</div>
</div>
</div>
</section>
);
}

View File

@ -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 (
<section className="bg-white text-black">
<div className="min-h-screen container px-4 lg:px-24 py-8 lg:py-48 mx-auto">
<div className="lg:w-3/5 space-y-8">
<h2 className="text-3xl lg:text-5xl leading-tight">
We do data differently
</h2>
<p className="text-lg lg:text-2xl leading-tight">
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.
</p>
<p className="text-lg lg:text-2xl leading-tight">
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?
</p>
<Button type="primary" variant="outlined" className="group">
View all projects
<ChevronRightIcon className="h-5 w-5 ml-2 group-hover:translate-x-1" />
</Button>
</div>
<div className="py-12">
<Gallery />
</div>
</div>
</section>
);
}
async function Gallery() {
const projects = await prisma.project.findMany({
where: {
feature: true,
},
});
return (
<div className="flex flex-col gap-20">
{projects.map((project) => (
<div
key={project.id}
className="lg:w-3/5 py-8 lg:not-even:self-end space-y-8 border-b"
>
<div className="bg-gray-100 aspect-video relative shadow rounded overflow-clip">
{project.image && (
<Image
src={project.image}
alt={project.title}
fill={true}
className="object-cover"
/>
)}
</div>
<div className="flex flex-col lg:flex-row justify-between lg:items-center gap-4 items-start">
<h3 className="text-2xl lg:text-4xl text-accent-700">
{project.title}
</h3>
<Button type="alternate" variant="outlined" className="group">
View project{" "}
<ChevronRightIcon className="h-5 w-5 ml-2 transition group-hover:translate-x-1" />
</Button>
</div>
<p className="text-gray-500 text-lg lg:text-2xl">{project.summary}</p>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,13 @@
"use client";
import { ArrowDownCircleIcon } from "@heroicons/react/20/solid";
export default function ScrollToSection({ anchor }: { anchor: string }) {
return (
<ArrowDownCircleIcon
className="h-10 text-white cursor-pointer opacity-20 hover:opacity-100"
onClick={() => {
document.querySelector(anchor)?.scrollIntoView({ behavior: "smooth" });
}}
/>
);
}

View File

@ -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 (
<button
className={`button-${type} button-${btnVariant} transition whitespace-nowrap flex items-center ${className}`}
{...props}
>
{children}
</button>
);
}

View File

@ -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;
}

View File

@ -1,21 +1,30 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import configPromise from "@payload-config";
import "./globals.css"; import { getPayload } from "payload";
const geistSans = Geist({ import "../globals.css";
variable: "--font-geist-sans", import Header from "./components/Header/Header";
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"], subsets: ["latin"],
display: "swap",
variable: "--font-inter",
}); });
const geistMono = Geist_Mono({ export async function generateMetadata(): Promise<Metadata> {
variable: "--font-geist-mono", const payload = await getPayload({ config: configPromise });
subsets: ["latin"],
});
export const metadata: Metadata = { const homeHero = await payload.findGlobal({
title: "Create Next App", slug: "homeHero",
description: "Generated by create next app", });
};
return {
title: homeHero.metaTitle,
description: homeHero.metaDescription,
};
}
export default function RootLayout({ export default function RootLayout({
children, children,
@ -24,10 +33,10 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body <body className={`${inter.variable} antialiased`}>
className={`${geistSans.variable} ${geistMono.variable} antialiased`} <Header />
> <div className="">{children}</div>
{children} {/* <Footer /> */}
</body> </body>
</html> </html>
); );

View File

@ -1,41 +1,11 @@
import configPromise from "@payload-config"; import CTA from "./components/home/CTA";
import { RichText } from "@payloadcms/richtext-lexical/react"; import Hero from "./components/home/Hero";
import Link from "next/link";
import { getPayload } from "payload";
export default async function Page() { export default async function Page() {
const payload = await getPayload({ config: configPromise });
const newsItems = await payload.find({
collection: "news",
depth: 1,
limit: 5,
select: {
title: true,
content: true,
},
sort: "-created_at",
});
return ( return (
<div> <>
<h1>My Homepage</h1> <Hero />
<p>My Homepage content.</p> <CTA />
</>
<h2>
<Link href="/news">Latest News</Link>
</h2>
<ul>
{newsItems.docs.map((newsItem) => (
<li key={newsItem.id} className="p-4">
<h3 className="text-2xl">{newsItem.title}</h3>
<RichText
data={newsItem.content}
className="prose bg-gray-50 p-2"
/>
</li>
))}
</ul>
</div>
); );
} }

119
src/app/globals.css Normal file
View File

@ -0,0 +1,119 @@
@import "tailwindcss";
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--header-height: 175px;
--color-primary: var(--rojo);
--color-accent-50: #fff1f2;
--color-accent-100: #ffe1e3;
--color-accent-200: #ffc8cc;
--color-accent-300: #ffa1a8;
--color-accent-400: #fe6b76;
--color-accent-500: #f73c49;
--color-accent-600: #e52331;
--color-accent-700: #c01521;
--color-accent-800: #9f151f;
--color-accent-900: #841820;
--color-accent-950: #48070c;
--color-secondary-50: #f5f8f5;
--color-secondary-100: #e8f0e9;
--color-secondary-200: #d2e0d3;
--color-secondary-300: #aec7af;
--color-secondary-400: #82a684;
--color-secondary-500: #5f8862;
--color-secondary-600: #4b6e4d;
--color-secondary-700: #415d43;
--color-secondary-800: #344735;
--color-secondary-900: #2c3b2d;
--color-secondary-950: #141f16;
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
:root {
--background: #ffffff;
--foreground: #171717;
/* https://coolors.co/000000-ffffff-e52331-709775-415d43 */
--black: #000000;
--white: #ffffff;
--rojo: #e52331;
--cambridge-blue: #709775;
--hunter-green: #415d43;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: var(--font-inter);
}
@utility button-default {
@apply font-bold py-2 px-4 rounded-sm cursor-pointer;
}
@layer components {
.button-primary {
@apply button-default;
}
.button-primary.button-filled {
@apply bg-accent-800 text-accent-100;
}
.button-primary.button-outlined {
@apply border border-accent-800 text-accent-800 hover:bg-accent-800 hover:text-accent-100;
}
.button-secondary {
@apply button-default;
/* bg-secondary-800 text-secondary-100 hover:bg-linear-to-tr hover:from-secondary-700 hover:to-secondary-800 hover:text-secondary-50; */
}
.button-secondary.button-filled {
@apply bg-secondary-800 text-secondary-100;
}
.button-secondary.button-outlined {
@apply border border-secondary-800 text-secondary-800 hover:bg-secondary-800 hover:text-secondary-100;
}
.button-alternate {
@apply button-default;
}
.button-alternate.button-filled {
@apply bg-black text-white;
}
.button-alternate.button-outlined {
@apply border border-black text-black hover:bg-black hover:text-white;
}
.button-large {
@apply button-default;
@apply text-3xl px-8 py-4;
}
}

View File

@ -1,51 +0,0 @@
import { GlobalConfig } from "payload";
export const Home: GlobalConfig = {
slug: 'home',
fields: [
{
name: 'title',
label: 'Title',
type: 'text',
defaultValue: 'Analytics, research, and data visualisation that make a difference'
},
{
name: 'heroTitle',
label: 'Hero Title',
type: 'text',
defaultValue: 'We help people tell stories with data'
},
{
name: 'heroDescription',
label: 'Hero Description',
type: 'richText',
},
{
name: 'heroItems',
label: 'Hero Items',
type: 'array',
fields: [
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
}
],
defaultValue: ["Data design", "Data collection", "Data analysis", "Data visualisation", "Training", "Data sovereignty"].map((item) => ({
name: item,
})),
},
{
name: 'projectsTitle',
label: 'Projects Title',
type: 'text',
defaultValue: 'We do data differently'
},
{
name: 'projectsDescription',
label: 'Projects Description',
type: 'richText'
}
],
};

83
src/globals/Home/Hero.ts Normal file
View File

@ -0,0 +1,83 @@
import { GlobalConfig } from "payload";
export const HomeHero: GlobalConfig = {
slug: 'homeHero',
label: 'Hero',
fields: [
{
name: 'titleGroup',
label: 'Landing page',
type: 'group',
fields: [
{
name: 'title',
label: 'Title',
type: 'text',
required: true,
defaultValue: 'Analytics, research, and data visualisation that make a difference'
},
],
},
{
name: 'heroGroup',
label: 'Hero',
type: 'group',
fields: [
{
name: 'heroTitle',
label: 'Hero Title',
type: 'text',
required: true,
defaultValue: 'We help people tell stories with data'
},
{
name: 'heroDescription',
label: 'Hero Description',
type: 'richText',
required: true,
},
{
name: 'heroItems',
label: 'Items',
type: 'array',
fields: [
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
}
],
defaultValue: ["Data design", "Data collection", "Data analysis", "Data visualisation", "Training", "Data sovereignty"].map((item) => ({
name: item,
})),
},
]
},
{
name: 'metaTitle',
label: 'Meta Title',
type: 'text',
defaultValue: 'iNZight Analytics Ltd',
required: true,
admin: {
position: 'sidebar',
description: 'This title will be used for SEO purposes, and displayed in the browser tab.',
}
},
{
name: 'metaDescription',
label: 'Meta Description',
type: 'textarea',
required: true,
defaultValue: 'iNZight Analytics Ltd is a New Zealand-based company that provides data analysis and visualisation services.',
admin: {
position: 'sidebar',
description: 'This description will be used for SEO purposes (e.g., shown in search results and on social media cards).',
}
},
],
admin: {
group: 'Home page'
}
};

View File

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