Compare commits

..

No commits in common. "main" and "dev" have entirely different histories.
main ... dev

67 changed files with 4699 additions and 1791 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
media
pgdata
# dependencies
@ -41,5 +42,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
media

View File

@ -1,3 +0,0 @@
{
"recommendations": ["denoland.vscode-deno"]
}

24
.vscode/settings.json vendored
View File

@ -1,24 +0,0 @@
{
"deno.enablePaths": [
"supabase/functions"
],
"deno.lint": true,
"deno.unstable": [
"bare-node-builtins",
"byonm",
"sloppy-imports",
"unsafe-proto",
"webgpu",
"broadcast-channel",
"worker-options",
"cron",
"kv",
"ffi",
"fs",
"http",
"net"
],
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
}

View File

@ -25,4 +25,4 @@ COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["pnpm", "run", "server.js"]
CMD ["node", "server.js"]

View File

@ -1,11 +1,5 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Dev to-do list
- [x] set up PostgreSQL on Catalyst
- [x] install PayloadCMS
- [ ] get basic config working
## Getting Started
First, run the development server:

View File

@ -5,41 +5,42 @@ services:
- "3000:3000"
environment:
- NODE_ENV=production
depends_on:
- db
networks:
- my_network
# depends_on:
# - db
# networks:
# - my_network
# volumes:
# - payload_media:/data/media
db:
image: postgres:latest
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
# db:
# image: postgres:latest
# environment:
# POSTGRES_USER: ${POSTGRES_USER}
# POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# POSTGRES_DB: ${POSTGRES_DB}
# ports:
# this exposes the database to the public ... do we want that??
# - "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- my_network
# volumes:
# - postgres_data:/var/lib/postgresql/data
# networks:
# - my_network
cron:
image: alpine/curl
command: >
sh -c "
echo '*/10 * * * * curl -X POST http://web:3000/db/clear' > /etc/crontabs/root && \
crond -f -l 2
"
depends_on:
- web
networks:
- my_network
# cron:
# image: alpine/curl
# command: >
# sh -c "
# echo '*/10 * * * * curl -X POST http://web:3000/db/clear' > /etc/crontabs/root && \
# crond -f -l 2
# "
# depends_on:
# - web
# networks:
# - my_network
volumes:
postgres_data:
networks:
my_network:
name: my_network
driver: bridge
# payload_media:
# networks:
# my_network:
# name: my_network
# driver: bridge

16
eslint.config.mjs Normal file
View File

@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

View File

@ -1,9 +1,15 @@
import type { NextConfig } from "next";
import {withPayload} from "@payloadcms/next/withPayload";
import withPayload from "@payloadcms/next/withPayload";
const nextConfig: NextConfig = {
/* config options here */
output: "standalone",
images: {
remotePatterns: [
new URL("http://localhost:3000/**"),
new URL("https://inzight.co.nz/**"),
],
},
compress: false,
};

View File

@ -1,50 +1,47 @@
{
"name": "ial-website",
"name": "ial-website-2",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"prestart": "cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/",
"start": "node .next/standalone/server.js",
"lint": "next lint",
"payload": "cross-env PAYLOAD_CONFIG_PATH=./payload.config.ts payload"
"lint": "next lint"
},
"type": "module",
"dependencies": {
"@heroicons/react": "^2.2.0",
"@payloadcms/db-postgres": "^3.35.1",
"@payloadcms/next": "^3.35.1",
"@payloadcms/richtext-lexical": "^3.35.1",
"cross-env": "^7.0.3",
"dayjs": "^1.11.13",
"@tailwindcss/postcss": "^4.1.4",
"@types/d3": "^7.4.3",
"clsx": "^2.1.1",
"d3": "^7.9.0",
"graphql": "^16.10.0",
"motion": "^12.7.4",
"next": "15.2.4",
"lenis": "^1.3.4",
"motion": "^12.15.0",
"next": "15.3.1",
"payload": "^3.35.1",
"pg": "^8.14.1",
"pg": "8.11.3",
"postcss": "^8.5.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"sharp": "^0.33.5",
"tailwind-merge": "^3.2.0"
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sharp": "^0.34.1",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.4"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.4",
"@tailwindcss/typography": "^0.5.16",
"@types/node": "^20.17.30",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"clsx": "^2.1.1",
"@eslint/eslintrc": "^3",
"@next/eslint-plugin-next": "^15.4.6",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.3.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"prettier": "^3.5.3",
"tailwindcss": "^4.1.4",
"typescript": "^5.8.3"
},
"pnpm": {
"onlyBuiltDependencies": [
"@swc/core",
"core-js",
"es5-ext",
"sharp"
]
"typescript": "^5"
}
}

View File

@ -93,10 +93,12 @@ export interface Config {
defaultIDType: number;
};
globals: {
general: General;
homeHero: HomeHero;
homeProjects: HomeProject;
};
globalsSelect: {
general: GeneralSelect<false> | GeneralSelect<true>;
homeHero: HomeHeroSelect<false> | HomeHeroSelect<true>;
homeProjects: HomeProjectsSelect<false> | HomeProjectsSelect<true>;
};
@ -157,6 +159,7 @@ export interface Project {
* Show this project on the homepage.
*/
featured?: boolean | null;
banner?: (number | null) | Image;
links?:
| {
link: string;
@ -171,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".
@ -247,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".
@ -387,6 +390,7 @@ export interface ProjectsSelect<T extends boolean = true> {
slug?: T;
content?: T;
featured?: T;
banner?: T;
links?:
| T
| {
@ -549,6 +553,16 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "general".
*/
export interface General {
id: number;
logo: number | Image;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "homeHero".
@ -575,12 +589,98 @@ export interface HomeHero {
};
[k: string]: unknown;
};
heroItems?:
| {
name: string;
id?: string | null;
}[]
| null;
heroItems: {
heroDataDesign: {
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;
};
heroDataCollection: {
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;
};
heroDataAnalysis: {
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;
};
heroDataVisualisation: {
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;
};
heroTraining: {
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;
};
heroDataSovereignty: {
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;
};
};
};
/**
* This title will be used for SEO purposes, and displayed in the browser tab.
@ -618,6 +718,16 @@ export interface HomeProject {
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "general_select".
*/
export interface GeneralSelect<T extends boolean = true> {
logo?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "homeHero_select".
@ -636,8 +746,12 @@ export interface HomeHeroSelect<T extends boolean = true> {
heroItems?:
| T
| {
name?: T;
id?: T;
heroDataDesign?: T;
heroDataCollection?: T;
heroDataAnalysis?: T;
heroDataVisualisation?: T;
heroTraining?: T;
heroDataSovereignty?: T;
};
};
metaTitle?: T;

View File

@ -1,16 +1,20 @@
import sharp from 'sharp'
import { FixedToolbarFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { buildConfig } from 'payload'
import sharp from "sharp";
import {
FixedToolbarFeature,
lexicalEditor,
} from "@payloadcms/richtext-lexical";
import { postgresAdapter } from "@payloadcms/db-postgres";
import { buildConfig } from "payload";
import { HomeHero } from '@/globals/Home/Hero'
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'
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";
import { General } from "@/globals/General";
export default buildConfig({
// If you'd like to use Rich Text, pass your editor here
@ -18,28 +22,28 @@ export default buildConfig({
features: ({ defaultFeatures }) => [
...defaultFeatures,
FixedToolbarFeature(),
]
],
}),
serverURL: process.env.SERVER_URL || 'http://localhost:3000',
serverURL: process.env.SERVER_URL || "http://localhost:3000",
globals: [HomeHero, HomeProjects],
globals: [General, HomeHero, HomeProjects],
// Define and configure your collections in this array
collections: [Projects, News, Images, Documents, Data],
// Your Payload secret - should be a complex and secure string, unguessable
secret: process.env.PAYLOAD_SECRET || '',
secret: process.env.PAYLOAD_SECRET || "",
// Whichever Database Adapter you're using should go here
// Mongoose is shown as an example, but you can also use Postgres
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI,
}
},
}),
// If you want to resize images, crop, set focal point, etc.
// make sure to install it and pass it to the config.
// This is optional - if you don't need to do these things,
// you don't need it!
sharp,
})
});

3525
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

1
public/file.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 391 B

1
public/globe.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

1
public/next.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/vercel.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 128 B

1
public/window.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 385 B

View File

@ -1,7 +0,0 @@
export default function Page() {
return (
<div className="container mx-auto pt-12">
<h1>Brand</h1>
</div>
);
}

View File

@ -1,42 +0,0 @@
"use client";
import clsx from "clsx";
import { motion } from "motion/react";
import { usePathname } from "next/navigation";
import React from "react";
export default function ClientHeader({
children,
}: {
children: React.ReactNode;
}) {
const path = usePathname();
const isHome = path === "/";
// 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
animate={
{
// height: isHome ? "200px" : "",
}
}
className={clsx(
"text-white w-full top-0 z-50 bg-black"
// isHome ? "lg:h-[var(--header-height)]" : ""
)}
>
{children}
</motion.div>
);
}

View File

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

View File

@ -1,46 +0,0 @@
// 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">
<Link href="/">
<Image
src={ialLogo}
alt="iNZight Analytics Ltd"
className="h-full object-contain object-left"
fill
/>
</Link>
</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

@ -1,22 +0,0 @@
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

@ -1,81 +0,0 @@
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

@ -1,59 +0,0 @@
import Button from "~/components/ui/Button";
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

@ -1,54 +0,0 @@
"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

@ -1,79 +0,0 @@
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

@ -1,13 +0,0 @@
"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

@ -1,23 +0,0 @@
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,119 +0,0 @@
@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,43 +0,0 @@
import type { Metadata } from "next";
import configPromise from "@payload-config";
import { getPayload } from "payload";
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",
});
export async function generateMetadata(): Promise<Metadata> {
const payload = await getPayload({ config: configPromise });
const homeHero = await payload.findGlobal({
slug: "homeHero",
});
return {
title: homeHero.metaTitle,
description: homeHero.metaDescription,
};
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${inter.variable} antialiased`}>
<Header />
<div className="">{children}</div>
{/* <Footer /> */}
</body>
</html>
);
}

View File

@ -1,53 +0,0 @@
import configPromise from "@payload-config";
import { getPayload } from "payload";
import { notFound } from "next/navigation";
import { RichText } from "@payloadcms/richtext-lexical/react";
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const payload = await getPayload({ config: configPromise });
const { slug } = await params;
const result = await payload.find({
collection: "news",
where: {
slug: {
equals: slug,
},
},
depth: 1,
});
const item = result.docs[0];
if (!item) {
notFound();
}
return (
<div>
<h1>{item.title}</h1>
<RichText data={item.content} className="prose bg-gray-50 p-2" />
</div>
);
}
export async function generateStaticParams() {
const payload = await getPayload({ config: configPromise });
const newsItems = await payload.find({
collection: "news",
depth: 1,
limit: 5,
select: {
slug: true,
},
});
return newsItems.docs.map((item) => ({
slug: item.slug,
}));
}

View File

@ -1,39 +0,0 @@
import configPromise from "@payload-config";
import Link from "next/link";
import { getPayload } from "payload";
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,
slug: true,
},
sort: "-created_at",
});
return (
<div>
<h1>News</h1>
<ul>
{newsItems.docs.map((newsItem) => (
<li key={newsItem.id} className="p-4">
<h3 className="text-lg">
<Link
href={`
/news/${newsItem.slug}`}
>
{newsItem.title}
</Link>
</h3>
</li>
))}
</ul>
</div>
);
}

View File

@ -1,11 +0,0 @@
import CTA from "./components/home/CTA";
import Hero from "./components/home/Hero";
export default async function Page() {
return (
<>
<Hero />
<CTA />
</>
);
}

View File

@ -1,53 +0,0 @@
import configPromise from "@payload-config";
import { getPayload } from "payload";
import { notFound } from "next/navigation";
import { RichText } from "@payloadcms/richtext-lexical/react";
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const payload = await getPayload({ config: configPromise });
const { slug } = await params;
const result = await payload.find({
collection: "projects",
where: {
slug: {
equals: slug,
},
},
depth: 1,
});
const item = result.docs[0];
if (!item) {
notFound();
}
return (
<div>
<h1>{item.title}</h1>
<RichText data={item.content} className="prose bg-gray-50 p-2" />
</div>
);
}
export async function generateStaticParams() {
const payload = await getPayload({ config: configPromise });
const projectItems = await payload.find({
collection: "projects",
depth: 1,
limit: 5,
select: {
slug: true,
},
});
return projectItems.docs.map((item) => ({
slug: item.slug,
}));
}

View File

@ -1,39 +0,0 @@
import configPromise from "@payload-config";
import Link from "next/link";
import { getPayload } from "payload";
export default async function Page() {
const payload = await getPayload({ config: configPromise });
const projectItems = await payload.find({
collection: "projects",
depth: 1,
limit: 5,
select: {
title: true,
slug: true,
},
sort: "-created_at",
});
return (
<div>
<h1>Projects</h1>
<ul>
{projectItems.docs.map((projectItem) => (
<li key={projectItem.id} className="p-4">
<h3 className="text-lg">
<Link
href={`
/projects/${projectItem.slug}`}
>
{projectItem.title}
</Link>
</h3>
</li>
))}
</ul>
</div>
);
}

View File

@ -1,5 +1,6 @@
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { FixedToolbarFeatureClient as FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
@ -25,6 +26,7 @@ import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0
export const importMap = {
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,

BIN
src/app/(website)/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 MiB

View File

@ -0,0 +1,61 @@
import Button from "../components/Button";
const BUTTON_TYPES: Parameters<typeof Button>[0]["type"][] = [
"primary",
"secondary",
"alternate",
] as const;
const BUTTON_VARIANTS: Parameters<typeof Button>[0]["variant"][] = [
"filled",
"outlined",
];
export default function Page() {
return (
<div className="flex justify-center bg-gray-50 min-h-screen dark:bg-black dark:text-gray-50">
<div className="container px-4 py-8 flex flex-col gap-8">
<div className="border-b">
<h1 className="text-4xl mb-4">iNZight Analytics Brand</h1>
</div>
<div className="">
<h2 className="text-2xl mb-2">Colours</h2>
<div className="h-32 flex gap-1 bg-gray-500 p-4">
<div className="w-6/10 bg-white dark:bg-black"></div>
<div className="w-3/10 bg-black dark:bg-white"></div>
<div className="w-1/10 bg-accent-500"></div>
</div>
<div className="p-4 space-y-2">
{BUTTON_VARIANTS.map((variant) => (
<div className="grid grid-cols-5 gap-4" key={variant}>
{BUTTON_TYPES.map((type) => (
<Button
key={type}
type={type}
variant={variant}
className="capitalize"
>
{type} button
</Button>
))}
</div>
))}
</div>
</div>
<div>
<h2 className="text-2xl mb-2">Typography</h2>
<div className="bg-gray-300 p-4">
<h1 className="text-4xl mb-4">Heading level 1</h1>
<h2 className="text-2xl mb-2">Heading level 2</h2>
<h3 className="text-xl mb-1">Heading level 3</h3>
<h4 className="font-bold mb-1">Heading level 4</h4>
<p>Normal paragraph text.</p>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,41 @@
import cn from "../../utils/cn";
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={cn(
className,
"font-bold py-2 px-4 rounded-sm cursor-pointer shadow transition whitespace-nowrap flex justify-center items-center",
variant === "filled" ? "" : "border",
type === "primary" &&
(variant === "filled"
? "bg-accent-600 text-accent-50 hover:bg-accent-700"
: "border-accent-600 text-accent-600 hover:bg-accent-600 hover:text-accent-50"),
type === "secondary" &&
(variant === "filled"
? "bg-secondary-600 text-secondary-50 hover:bg-secondary-700"
: "border-secondary-600 text-secondary-600 hover:bg-secondary-600 hover:text-secondary-50"),
type === "alternate" &&
(variant === "filled"
? "bg-black text-white dark:bg-white dark:text-black"
: "border-black text-black hover:bg-black hover:text-white dark:border-white dark:text-white dark:hover:bg-white dark:hover:text-black")
)}
{...props}
>
{children}
</button>
);
}

View File

@ -0,0 +1,28 @@
export default function Footer() {
return (
<footer className="p-12 lg:p-24 flex flex-col gap-12">
<div className="flex justify-between flex-col lg:flex-row">
<div className="flex text-lg gap-24">
<ol>
<li>About</li>
<li>News</li>
<li>Projects</li>
<li>Apps</li>
</ol>
<ol>
<li>Cool stuff</li>
<li>Horizon Europe</li>
</ol>
</div>
<div className="flex flex-col items-center lg:items-end max-w-xs mt-12 lg:mt-0 mx-auto lg:mx-0">
<p className="text-lg pb-12 lg:pb-48 text-center lg:text-right">
Analytics, research, and data visualisation that make a difference.
</p>
<div>IMAGE</div>
</div>
</div>
<div className="text-sm">&copy; iNZight Analytics Ltd 2025</div>
</footer>
);
}

View File

@ -0,0 +1,38 @@
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";
// const isValidLogo = (image: number | PImage): image is PImage => {
// return typeof image !== "number";
// };
export default async function Header() {
const payload = await getPayload({ config });
const { logo } = await payload.findGlobal({
slug: "general",
});
if (typeof logo === "number") throw "Bad image";
return (
<div className="text-white bg-black px-8 py-4 flex justify-between items-center absolute z-10 w-full h-[var(--header-height)]">
{/* Left hand side - logo */}
<Link href="https://inzight.co.nz">
LOGO
{/* {logo.url && logo.alt && logo.width && logo.height && (
<Image
src={logo.url}
alt={logo.alt}
width={logo.width}
height={100}
/>
)} */}
</Link>
{/* Right hand size - nav */}
<nav>NAV</nav>
</div>
);
}

View File

@ -0,0 +1,141 @@
"use client";
import { motion, MotionValue, useScroll, useTransform } from "motion/react";
import Link from "next/link";
import { useRef } from "react";
const collaborators = {
aotearoa: Array.from({ length: 16 }).map((x, i) => `Organisation ${i}`),
international: Array.from({ length: 8 }).map((x, i) => `Organisation ${i}`),
};
export default function Collboaration() {
const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "end end"],
});
const titleOpacity = useTransform(scrollYProgress, [0.15, 0.25], [0, 1]);
const titleY = useTransform(scrollYProgress, [0.15, 0.25], [-40, 0]);
const aotearoaOpacity = useTransform(
scrollYProgress,
[0.25, 0.3, 0.6, 0.7],
[0, 1, 1, 0]
);
const aotearoaY = useTransform(
scrollYProgress,
[0.25, 0.3, 0.6, 0.7],
[20, 0, 0, -20]
);
const internationalOpacity = useTransform(
scrollYProgress,
[0.6, 0.7],
[0, 1]
);
const internationalY = useTransform(scrollYProgress, [0.6, 0.7], [20, 0]);
const n = [collaborators.aotearoa.length, collaborators.international.length];
return (
<section ref={containerRef} className="">
<div className="flex flex-col h-[200vh]">
<div className="flex flex-col items-center h-screen w-full sticky top-0 py-12 lg:py-24 gap-6 lg:gap-12">
<div className="max-w-6xl w-full px-12">
<motion.h2
className="w-full text-4xl font-display"
style={{ opacity: titleOpacity, y: titleY }}
>
We have worked with &hellip;
</motion.h2>
</div>
<motion.div
style={{}}
className="flex flex-col lg:grid lg:grid-cols-3 p-12 gap-16 flex-1 w-full max-w-6xl"
>
<div className="relative">
<motion.h3
className="text-3xl absolute font-display text-accent-700"
style={{
y: aotearoaY,
opacity: aotearoaOpacity,
}}
>
Aotearoa
</motion.h3>
<motion.h3
className="text-3xl absolute font-display text-accent-700"
style={{
y: internationalY,
opacity: internationalOpacity,
}}
>
International
</motion.h3>
</div>
<div className="w-full relative col-span-2 h-full">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 w-full h-full absolute">
{collaborators.aotearoa.map((org, i) => (
<Collaborator
name={org}
progress={scrollYProgress}
range={[
0.3 + (i / n[0]) * 0.1,
0.4 + (i / n[0]) * 0.1,
0.6,
0.7,
]}
key={i}
/>
))}
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 w-full h-full absolute">
{collaborators.international.map((org, i) => (
<Collaborator
name={org}
progress={scrollYProgress}
range={[0.65, 0.75, 1, 2]}
key={i}
/>
))}
</div>
</div>
</motion.div>
</div>
</div>
<div className="w-full flex flex-col items-center justify-center">
<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">Want to collaborate?</h4>
<Link href="">
<p className="text-xl font-bold">Get in touch &gt;</p>
</Link>
</div>
</div>
</section>
);
}
const Collaborator = ({
name,
progress,
range,
}: {
name: string;
progress: MotionValue<number>;
range: [number, number, number, number];
}) => {
const opacity = useTransform(progress, range, [0, 1, 1, 0]);
return (
<motion.div
className="w-full h-full bg-white text-black flex justify-center items-center"
style={{
opacity,
}}
>
{name}
</motion.div>
);
};

View File

@ -0,0 +1,96 @@
"use client";
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: HomeHero["heroGroup"]["heroDescription"];
}) {
const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "start start"],
});
const titleY = useTransform(scrollYProgress, [0, 1], [-100, 0]);
const paraOpacity = useTransform(scrollYProgress, [0.8, 1], [0, 1]);
const paraY = useTransform(scrollYProgress, [0.8, 1], [20, 0]);
const [windowSize, setWindowSize] = useState<[number, number]>();
useEffect(() => {
window.addEventListener("resize", () => {
setWindowSize([window.innerWidth, window.innerHeight]);
});
setWindowSize([window.innerWidth, window.innerHeight]);
}, []);
const [beamSize, setBeamSize] = useState(0);
scrollYProgress.on("change", (e) => setBeamSize(e.valueOf()));
const drawBeam = useCallback(() => {
if (!windowSize) return;
const isSmall = windowSize[0] < 1124;
const beam: [number, number][] = [
[0, 1 - beamSize / 2], // modify over scroll
[0.9, 0.1],
[isSmall ? 0.7 : beamSize / 2, 1], // modify over scroll
[0, 1],
];
const xScale = d3.scaleLinear().domain([0, 1]).range([0, windowSize[0]]);
const yScale = d3.scaleLinear().domain([0, 1]).range([0, windowSize[1]]);
const drawArea = d3
.area()
.x((p) => xScale(p[0]))
.y0(() => 0)
.y1((p) => yScale(p[1]));
return drawArea(beam);
}, [windowSize, beamSize]);
return (
<section
className="h-screen bg-black flex justify-center relative overflow-clip"
ref={containerRef}
>
<div className="absolute z-0 w-full h-full">
<svg id="beam" className="h-full w-full">
{drawBeam && (
<motion.path
className=" fill-accent-700"
d={drawBeam() ?? ""}
></motion.path>
)}
</svg>
</div>
<div className="w-full h-full max-w-6xl p-12 grid grid-cols-2 gap-12 z-10">
<div className="absolute top-1/2 left-0 h-1/2 w-2/3 lg:w-1/2 flex-1 flex flex-col justify-center px-[5vw] gap-8 ">
<motion.h2
style={{ opacity: scrollYProgress, y: titleY }}
className="text-3xl lg:text-5xl tracking-tight leading-tight font-display"
>
{title}
</motion.h2>
<motion.div
style={{
opacity: paraOpacity,
y: paraY,
}}
>
<RichText
data={desc}
className="text-xl lg:text-2xl leading-tight"
/>
</motion.div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,99 @@
"use client";
import { motion, useScroll, useTransform } from "motion/react";
import { useRef } from "react";
import type { HomeHero } from "@payload-types";
import { RichText } from "@payloadcms/richtext-lexical/react";
import useWindow from "@/app/(website)/hooks/useWindow";
const heroMap = {
heroDataDesign: "Data Design",
heroDataCollection: "Data Collection",
heroDataAnalysis: "Data Analysis",
heroDataVisualisation: "Data Visualisation",
heroTraining: "Training",
heroDataSovereignty: "Data Sovereignty",
} as const;
type HeroItems = HomeHero["heroGroup"]["heroItems"];
type HeroItem = HeroItems[keyof HeroItems];
export default function HeroData({
items,
}: {
items: HomeHero["heroGroup"]["heroItems"];
}) {
const itemKeys = Object.keys(items) as (keyof typeof items)[];
const itemArray = itemKeys.map((k) => ({ key: k, ...items[k] }));
return (
<section className="bg-black flex flex-col items-center relative">
{itemArray.map((item, i) => (
<Item
key={item.key}
title={heroMap[item.key]}
item={item}
last={i === 5}
/>
))}
</section>
);
}
const Item = ({
title,
item,
last,
}: {
title: string;
item: HeroItem;
last: boolean;
}) => {
const { height } = useWindow();
const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "end start"],
});
const yp = 0.7;
const yoffset = useTransform(
scrollYProgress,
[0.2, last ? 0.5 : 0.8],
[-height * yp, last ? 0 : height * yp]
);
const opacity = useTransform(
scrollYProgress,
[0.3, 0.4, 0.6, 0.7],
[0, 1, 1, last ? 1 : 0]
);
return (
<div
ref={containerRef}
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 }}
className="flex items-center flex-col bg-accent-600 rounded aspect-video"
>
IMAGE
</motion.div>
<div className="relative">
<motion.div
style={{
y: yoffset,
opacity,
}}
className="flex flex-col gap-4 absolute top-1/2 -translate-y-1/2"
>
<h5 className="text-4xl font-display">{title}</h5>
<div>
<RichText className="text-xl" data={item} />
</div>
</motion.div>
</div>
</div>
);
};

View File

@ -0,0 +1,144 @@
"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 Link from "next/link";
import { useRef, useState } from "react";
import PayloadImage from "../../PayloadImage";
export default function Projects({
text,
projects,
}: {
text: HomeProject;
projects: Project[];
}) {
const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "start start"],
});
const bannerHeight = useTransform(scrollYProgress, [0.6, 1], [0, 1]);
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 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">
<motion.div
style={{
scaleY: bannerHeight,
skewY: -6,
}}
className="absolute h-full w-screen left-1/2 -translate-x-1/2 bg-accent-800 z-0 top-1/2 -translate-y-1/2"
></motion.div>
<div className="flex flex-col items-center gap-8 z-10 py-24 px-12 ">
<motion.h2
style={{ opacity: headerOpacity }}
className="text-4xl font-display"
>
{text.projectsTitle}
</motion.h2>
<motion.div
style={{ opacity: textOpacity }}
className="max-w-xl text-xl"
>
{text.projectsDescription && (
<RichText data={text.projectsDescription} className="-mb-6" />
)}
</motion.div>
</div>
</div>
</div>
<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) => (
<motion.div
key={i}
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)}
>
{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>
<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>
</motion.div>
)}
</motion.div>
))}
</div>
<div className="w-full flex flex-col items-center justify-center">
<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,43 @@
"use client";
import { motion } from "motion/react";
import { letters } from "./letters";
export default function ScrollingNumbers({ numbers }: { numbers: string[][] }) {
return (
<div className="absolute top-0 h-full w-full z-0 opacity-50 pointer-events-none">
<div className="h-full w-full flex gap-4 skew-24 scale-150 overflow-hidden">
{numbers.map((col, i) => (
<NumberCol col={col} key={i} />
))}
<div className="absolute w-full h-full mask-b-from-0 bg-black"></div>
</div>
</div>
);
}
const NumberCol = ({ col }: { col: string[] }) => {
const range = [0, -100];
const speed = Math.log(letters.indexOf(col[0]) + 10) * 100;
return (
<motion.div
initial={{
translateY: range[0] + "vh",
}}
whileInView={{ translateY: range[1] + "vh" }}
transition={{
type: "tween",
duration: speed + 10,
repeat: Infinity,
repeatType: "reverse",
}}
className="flex-1 flex flex-col items-center"
>
{col.map((num, j) => (
<div key={j} className="text-accent-800 text-8xl">
{num}
</div>
))}
</motion.div>
);
};

View File

@ -0,0 +1,18 @@
export const letters = [
"a",
"b",
"c",
"d",
"e",
"f",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
];

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

@ -0,0 +1,37 @@
"use client";
import { ReactNode, useEffect } from "react";
import Lenis from "lenis";
// import Snap from "lenis/snap";
export default function SmoothScroll({
children,
// snapAt,
}: {
children: ReactNode;
// snapAt?: string[];
}) {
useEffect(() => {
const lenis = new Lenis();
// Use requestAnimationFrame to continuously update the scroll
function raf(time: number) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
// const snap = new Snap(lenis, {
// type: "proximity",
// velocityThreshold: 1,
// debounce: 0,
// });
// snapAt?.forEach((id) => {
// const el = document.querySelector<HTMLDivElement>(id);
// if (!el) return;
// snap.addElement(el, { align: ["center"] });
// });
}, []);
return <>{children}</>;
}

View File

@ -0,0 +1,41 @@
@import url("https://fonts.googleapis.com/css2?family=Inknut+Antiqua:wght@400;700&family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap");
@import "tailwindcss";
@theme {
/* FONT FAMILIES */
--font-sans: "Work Sans", sans;
--font-display: "Inknut Antiqua";
/* COLOURS */
--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;
/* Other vars */
--header-height: 100px;
}
.payload-richtext p {
@apply mb-2;
}

View File

@ -0,0 +1,22 @@
import { useEffect, useState } from "react";
export default function useWindow() {
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
useEffect(() => {
if (!window) return;
setWidth(window.innerWidth);
setHeight(window.innerHeight);
window.addEventListener("resize", () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
});
}, []);
return {
width,
height,
};
}

View File

@ -0,0 +1,24 @@
import type { Metadata } from "next";
import "./globals.css";
import Header from "./components/Header";
export const metadata: Metadata = {
title: "iNZight Analytics Ltd",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`bg-black`}>
<Header />
{children}
</body>
</html>
);
}

View File

@ -0,0 +1,56 @@
import { getPayload } from "payload";
import config from "@payload-config";
import HeroIntro from "./components/Home/Hero/01-intro";
import SmoothScroll from "./components/SmoothScroll";
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 Collboaration from "./components/Home/Collaboration";
import Footer from "./components/Footer";
export default async function Home() {
const payload = await getPayload({ config });
const { titleGroup, heroGroup } = await payload.findGlobal({
slug: "homeHero",
});
const projectsText = await payload.findGlobal({
slug: "homeProjects",
});
const projects = await payload.find({
collection: "projects",
where: {
featured: {
equals: true,
},
},
});
return (
<SmoothScroll>
<div className="text-white">
<div className="absolute h-full mt-[var(--header-height)] w-full opacity-50 ">
<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">
<h1 className="text-4xl p-8 lg:text-7xl leading-tight max-w-6xl z-10 font-display">
{titleGroup.title}
</h1>
</div>
<HeroIntro
title={heroGroup.heroTitle}
desc={heroGroup.heroDescription}
/>
<HeroData items={heroGroup.heroItems} />
<Projects text={projectsText} projects={projects.docs} />
<Collboaration />
<Footer />
</div>
</SmoothScroll>
);
}

View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export default function cn(...args: ClassValue[]) {
return twMerge(clsx(args));
}

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

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",
},
};

22
src/globals/General.ts Normal file
View File

@ -0,0 +1,22 @@
import { revalidatePath } from "next/cache";
import { GlobalConfig } from "payload";
export const General: GlobalConfig = {
slug: "general",
label: "General",
fields: [
{
name: "logo",
required: true,
label: "Logo",
type: "upload",
relationTo: "images",
},
],
hooks: {
afterChange: [
// revalidate ALL pages ...
() => revalidatePath("/", "layout"),
],
},
};

View File

@ -1,83 +1,118 @@
import { GlobalConfig } from "payload";
import refreshHome from "./hooks/refreshHome";
export const HomeHero: GlobalConfig = {
slug: 'homeHero',
label: 'Hero',
slug: "homeHero",
label: "Hero",
fields: [
{
name: 'titleGroup',
label: 'Landing page',
type: 'group',
name: "titleGroup",
label: "Landing page",
type: "group",
fields: [
{
name: 'title',
label: 'Title',
type: 'text',
name: "title",
label: "Title",
type: "text",
required: true,
defaultValue: 'Analytics, research, and data visualisation that make a difference'
defaultValue:
"Analytics, research, and data visualisation that make a difference",
},
],
},
{
name: 'heroGroup',
label: 'Hero',
type: 'group',
name: "heroGroup",
label: "Hero",
type: "group",
fields: [
{
name: 'heroTitle',
label: 'Hero Title',
type: 'text',
name: "heroTitle",
label: "Hero Title",
type: "text",
required: true,
defaultValue: 'We help people tell stories with data'
defaultValue: "We help people tell stories with data",
},
{
name: 'heroDescription',
label: 'Hero Description',
type: 'richText',
name: "heroDescription",
label: "Hero Description",
type: "richText",
required: true,
},
{
name: 'heroItems',
label: 'Items',
type: 'array',
name: "heroItems",
label: "Items",
type: "group",
fields: [
{
name: 'name',
label: 'Name',
type: 'text',
name: "heroDataDesign",
label: "Data Design",
type: "richText",
required: true,
}
},
{
name: "heroDataCollection",
label: "Data Collection",
type: "richText",
required: true,
},
{
name: "heroDataAnalysis",
label: "Data Analysis",
type: "richText",
required: true,
},
{
name: "heroDataVisualisation",
label: "Data Visualisation",
type: "richText",
required: true,
},
{
name: "heroTraining",
label: "Training",
type: "richText",
required: true,
},
{
name: "heroDataSovereignty",
label: "Data Sovereignty",
type: "richText",
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',
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.',
}
position: "sidebar",
description:
"This title will be used for SEO purposes, and displayed in the browser tab.",
},
},
{
name: 'metaDescription',
label: 'Meta Description',
type: 'textarea',
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.',
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).',
}
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'
}
group: "Home page",
},
hooks: {
afterChange: [refreshHome],
},
};

View File

@ -1,22 +1,26 @@
import { GlobalConfig } from "payload";
import refreshHome from "./hooks/refreshHome";
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",
},
hooks: {
afterChange: [refreshHome],
},
};

View File

@ -0,0 +1,5 @@
import { revalidatePath } from "next/cache";
export default function refreshHome() {
revalidatePath("/");
}

View File

@ -1,337 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:db-schema` to regenerate this file.
*/
import {
pgTable,
index,
uniqueIndex,
foreignKey,
serial,
varchar,
jsonb,
timestamp,
numeric,
integer,
} from "@payloadcms/db-postgres/drizzle/pg-core";
import { sql, relations } from "@payloadcms/db-postgres/drizzle";
export const news = pgTable(
"news",
{
id: serial("id").primaryKey(),
title: varchar("title").notNull(),
content: jsonb("content").notNull(),
updatedAt: timestamp("updated_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
createdAt: timestamp("created_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
},
(columns) => ({
news_updated_at_idx: index("news_updated_at_idx").on(columns.updatedAt),
news_created_at_idx: index("news_created_at_idx").on(columns.createdAt),
}),
);
export const users = pgTable(
"users",
{
id: serial("id").primaryKey(),
updatedAt: timestamp("updated_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
createdAt: timestamp("created_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
email: varchar("email").notNull(),
resetPasswordToken: varchar("reset_password_token"),
resetPasswordExpiration: timestamp("reset_password_expiration", {
mode: "string",
withTimezone: true,
precision: 3,
}),
salt: varchar("salt"),
hash: varchar("hash"),
loginAttempts: numeric("login_attempts").default("0"),
lockUntil: timestamp("lock_until", {
mode: "string",
withTimezone: true,
precision: 3,
}),
},
(columns) => ({
users_updated_at_idx: index("users_updated_at_idx").on(columns.updatedAt),
users_created_at_idx: index("users_created_at_idx").on(columns.createdAt),
users_email_idx: uniqueIndex("users_email_idx").on(columns.email),
}),
);
export const payload_locked_documents = pgTable(
"payload_locked_documents",
{
id: serial("id").primaryKey(),
globalSlug: varchar("global_slug"),
updatedAt: timestamp("updated_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
createdAt: timestamp("created_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
},
(columns) => ({
payload_locked_documents_global_slug_idx: index(
"payload_locked_documents_global_slug_idx",
).on(columns.globalSlug),
payload_locked_documents_updated_at_idx: index(
"payload_locked_documents_updated_at_idx",
).on(columns.updatedAt),
payload_locked_documents_created_at_idx: index(
"payload_locked_documents_created_at_idx",
).on(columns.createdAt),
}),
);
export const payload_locked_documents_rels = pgTable(
"payload_locked_documents_rels",
{
id: serial("id").primaryKey(),
order: integer("order"),
parent: integer("parent_id").notNull(),
path: varchar("path").notNull(),
newsID: integer("news_id"),
usersID: integer("users_id"),
},
(columns) => ({
order: index("payload_locked_documents_rels_order_idx").on(columns.order),
parentIdx: index("payload_locked_documents_rels_parent_idx").on(
columns.parent,
),
pathIdx: index("payload_locked_documents_rels_path_idx").on(columns.path),
payload_locked_documents_rels_news_id_idx: index(
"payload_locked_documents_rels_news_id_idx",
).on(columns.newsID),
payload_locked_documents_rels_users_id_idx: index(
"payload_locked_documents_rels_users_id_idx",
).on(columns.usersID),
parentFk: foreignKey({
columns: [columns["parent"]],
foreignColumns: [payload_locked_documents.id],
name: "payload_locked_documents_rels_parent_fk",
}).onDelete("cascade"),
newsIdFk: foreignKey({
columns: [columns["newsID"]],
foreignColumns: [news.id],
name: "payload_locked_documents_rels_news_fk",
}).onDelete("cascade"),
usersIdFk: foreignKey({
columns: [columns["usersID"]],
foreignColumns: [users.id],
name: "payload_locked_documents_rels_users_fk",
}).onDelete("cascade"),
}),
);
export const payload_preferences = pgTable(
"payload_preferences",
{
id: serial("id").primaryKey(),
key: varchar("key"),
value: jsonb("value"),
updatedAt: timestamp("updated_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
createdAt: timestamp("created_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
},
(columns) => ({
payload_preferences_key_idx: index("payload_preferences_key_idx").on(
columns.key,
),
payload_preferences_updated_at_idx: index(
"payload_preferences_updated_at_idx",
).on(columns.updatedAt),
payload_preferences_created_at_idx: index(
"payload_preferences_created_at_idx",
).on(columns.createdAt),
}),
);
export const payload_preferences_rels = pgTable(
"payload_preferences_rels",
{
id: serial("id").primaryKey(),
order: integer("order"),
parent: integer("parent_id").notNull(),
path: varchar("path").notNull(),
usersID: integer("users_id"),
},
(columns) => ({
order: index("payload_preferences_rels_order_idx").on(columns.order),
parentIdx: index("payload_preferences_rels_parent_idx").on(columns.parent),
pathIdx: index("payload_preferences_rels_path_idx").on(columns.path),
payload_preferences_rels_users_id_idx: index(
"payload_preferences_rels_users_id_idx",
).on(columns.usersID),
parentFk: foreignKey({
columns: [columns["parent"]],
foreignColumns: [payload_preferences.id],
name: "payload_preferences_rels_parent_fk",
}).onDelete("cascade"),
usersIdFk: foreignKey({
columns: [columns["usersID"]],
foreignColumns: [users.id],
name: "payload_preferences_rels_users_fk",
}).onDelete("cascade"),
}),
);
export const payload_migrations = pgTable(
"payload_migrations",
{
id: serial("id").primaryKey(),
name: varchar("name"),
batch: numeric("batch"),
updatedAt: timestamp("updated_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
createdAt: timestamp("created_at", {
mode: "string",
withTimezone: true,
precision: 3,
})
.defaultNow()
.notNull(),
},
(columns) => ({
payload_migrations_updated_at_idx: index(
"payload_migrations_updated_at_idx",
).on(columns.updatedAt),
payload_migrations_created_at_idx: index(
"payload_migrations_created_at_idx",
).on(columns.createdAt),
}),
);
export const relations_news = relations(news, () => ({}));
export const relations_users = relations(users, () => ({}));
export const relations_payload_locked_documents_rels = relations(
payload_locked_documents_rels,
({ one }) => ({
parent: one(payload_locked_documents, {
fields: [payload_locked_documents_rels.parent],
references: [payload_locked_documents.id],
relationName: "_rels",
}),
newsID: one(news, {
fields: [payload_locked_documents_rels.newsID],
references: [news.id],
relationName: "news",
}),
usersID: one(users, {
fields: [payload_locked_documents_rels.usersID],
references: [users.id],
relationName: "users",
}),
}),
);
export const relations_payload_locked_documents = relations(
payload_locked_documents,
({ many }) => ({
_rels: many(payload_locked_documents_rels, {
relationName: "_rels",
}),
}),
);
export const relations_payload_preferences_rels = relations(
payload_preferences_rels,
({ one }) => ({
parent: one(payload_preferences, {
fields: [payload_preferences_rels.parent],
references: [payload_preferences.id],
relationName: "_rels",
}),
usersID: one(users, {
fields: [payload_preferences_rels.usersID],
references: [users.id],
relationName: "users",
}),
}),
);
export const relations_payload_preferences = relations(
payload_preferences,
({ many }) => ({
_rels: many(payload_preferences_rels, {
relationName: "_rels",
}),
}),
);
export const relations_payload_migrations = relations(
payload_migrations,
() => ({}),
);
type DatabaseSchema = {
news: typeof news;
users: typeof users;
payload_locked_documents: typeof payload_locked_documents;
payload_locked_documents_rels: typeof payload_locked_documents_rels;
payload_preferences: typeof payload_preferences;
payload_preferences_rels: typeof payload_preferences_rels;
payload_migrations: typeof payload_migrations;
relations_news: typeof relations_news;
relations_users: typeof relations_users;
relations_payload_locked_documents_rels: typeof relations_payload_locked_documents_rels;
relations_payload_locked_documents: typeof relations_payload_locked_documents;
relations_payload_preferences_rels: typeof relations_payload_preferences_rels;
relations_payload_preferences: typeof relations_payload_preferences;
relations_payload_migrations: typeof relations_payload_migrations;
};
declare module "@payloadcms/db-postgres/types" {
export interface GeneratedDatabaseSchema {
schema: DatabaseSchema;
}
}

View File

@ -20,8 +20,8 @@
],
"paths": {
"@/*": ["./src/*"],
"~/*": ["./src/app/(frontend)/*"],
"@payload-config": ["./payload.config.ts"]
"@payload-config": ["./payload.config.ts"],
"@payload-types": ["./payload-types.ts"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],