Compare commits
No commits in common. "main" and "dev" have entirely different histories.
3
.gitignore
vendored
@ -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
|
||||
|
||||
3
.vscode/extensions.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["denoland.vscode-deno"]
|
||||
}
|
||||
24
.vscode/settings.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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"]
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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}
|
||||
# ports:
|
||||
# this exposes the database to the public ... do we want that??
|
||||
# - "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- my_network
|
||||
# db:
|
||||
# image: postgres:latest
|
||||
# environment:
|
||||
# POSTGRES_USER: ${POSTGRES_USER}
|
||||
# POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
# POSTGRES_DB: ${POSTGRES_DB}
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
# 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
@ -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;
|
||||
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 150 KiB |
@ -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,
|
||||
};
|
||||
|
||||
|
||||
57
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
190
payload-types.ts
@ -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;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
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
1
public/file.svg
Normal 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
@ -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 |
|
Before Width: | Height: | Size: 29 KiB |
1
public/next.svg
Normal 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
@ -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
@ -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 |
@ -1,7 +0,0 @@
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="container mx-auto pt-12">
|
||||
<h1>Brand</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import ClientHeader from "./Client";
|
||||
import ServerHeader from "./Server";
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<ClientHeader>
|
||||
<ServerHeader />
|
||||
</ClientHeader>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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'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>
|
||||
);
|
||||
}
|
||||
@ -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've worked with …
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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" });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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,
|
||||
}));
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import CTA from "./components/home/CTA";
|
||||
import Hero from "./components/home/Hero";
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<CTA />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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,
|
||||
}));
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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
|
After Width: | Height: | Size: 17 MiB |
61
src/app/(website)/brand/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
41
src/app/(website)/components/Button/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
28
src/app/(website)/components/Footer/index.tsx
Normal 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">© iNZight Analytics Ltd 2025</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
38
src/app/(website)/components/Header/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
141
src/app/(website)/components/Home/Collaboration/index.tsx
Normal 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 …
|
||||
</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 ></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>
|
||||
);
|
||||
};
|
||||
96
src/app/(website)/components/Home/Hero/01-intro.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
99
src/app/(website)/components/Home/Hero/02-data.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
144
src/app/(website)/components/Home/Projects/index.tsx
Normal 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 ></p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
43
src/app/(website)/components/Home/ScrollingNumbers.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
18
src/app/(website)/components/Home/letters.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export const letters = [
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
];
|
||||
28
src/app/(website)/components/PayloadImage.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
37
src/app/(website)/components/SmoothScroll.tsx
Normal 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}</>;
|
||||
}
|
||||
41
src/app/(website)/globals.css
Normal 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;
|
||||
}
|
||||
22
src/app/(website)/hooks/useWindow.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
24
src/app/(website)/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
56
src/app/(website)/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
6
src/app/(website)/utils/cn.ts
Normal 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));
|
||||
}
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -2,83 +2,86 @@ import { formatSlug } from "@/lib/slugs";
|
||||
import { CollectionConfig } from "payload";
|
||||
|
||||
export const Projects: CollectionConfig = {
|
||||
slug: 'projects',
|
||||
fields: [
|
||||
slug: "projects",
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
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.",
|
||||
// readOnly: true,
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [formatSlug("title")],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "content",
|
||||
label: "Content",
|
||||
type: "richText",
|
||||
required: true,
|
||||
},
|
||||
// list of files
|
||||
// gallery
|
||||
// keywords
|
||||
// is featured?
|
||||
{
|
||||
name: "featured",
|
||||
label: "Featured",
|
||||
type: "checkbox",
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
position: "sidebar",
|
||||
description: "Show this project on the homepage.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "banner",
|
||||
label: "Banner",
|
||||
type: "upload",
|
||||
relationTo: "images",
|
||||
},
|
||||
// links
|
||||
{
|
||||
name: "links",
|
||||
label: "Links",
|
||||
type: "array",
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
name: "link",
|
||||
label: "Link",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
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.',
|
||||
// readOnly: true,
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
formatSlug('title'),
|
||||
]
|
||||
}
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "text",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
label: 'Content',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
name: "group",
|
||||
label: "Group",
|
||||
type: "text",
|
||||
required: false,
|
||||
admin: {
|
||||
description: "Optional: organise link under this heading",
|
||||
},
|
||||
},
|
||||
// list of files
|
||||
// gallery
|
||||
// keywords
|
||||
// is featured?
|
||||
{
|
||||
name: 'featured',
|
||||
label: 'Featured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Show this project on the homepage.',
|
||||
}
|
||||
},
|
||||
// links
|
||||
{
|
||||
name: 'links',
|
||||
label: 'Links',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'link',
|
||||
label: 'Link',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
label: 'Group',
|
||||
type: 'text',
|
||||
required: false,
|
||||
admin: {
|
||||
description: 'Optional: organise link under this heading',
|
||||
},
|
||||
}
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
admin: {
|
||||
position: "sidebar",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,45 +1,49 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Images: CollectionConfig = {
|
||||
slug: 'images',
|
||||
upload: {
|
||||
staticDir: 'media/images',
|
||||
imageSizes: [
|
||||
{
|
||||
name: 'thumbnail',
|
||||
width: 400,
|
||||
height: 300,
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'card',
|
||||
width: 768,
|
||||
height: 1024,
|
||||
position: 'centre',
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
width: 1024,
|
||||
height: undefined,
|
||||
position: 'centre',
|
||||
}
|
||||
],
|
||||
adminThumbnail: 'thumbnail',
|
||||
mimeTypes: ['image/*'],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
label: 'Alt Text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
}
|
||||
slug: "images",
|
||||
access: {
|
||||
// TODO: fix this
|
||||
read: () => true,
|
||||
},
|
||||
upload: {
|
||||
staticDir: "media/images",
|
||||
imageSizes: [
|
||||
{
|
||||
name: "thumbnail",
|
||||
width: 400,
|
||||
height: 300,
|
||||
position: "centre",
|
||||
},
|
||||
{
|
||||
name: "card",
|
||||
width: 768,
|
||||
height: 1024,
|
||||
position: "centre",
|
||||
},
|
||||
{
|
||||
name: "tablet",
|
||||
width: 1024,
|
||||
height: undefined,
|
||||
position: "centre",
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
group: 'Media',
|
||||
}
|
||||
}
|
||||
adminThumbnail: "thumbnail",
|
||||
mimeTypes: ["image/*"],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "alt",
|
||||
label: "Alt Text",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "textarea",
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
group: "Media",
|
||||
},
|
||||
};
|
||||
|
||||
22
src/globals/General.ts
Normal 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"),
|
||||
],
|
||||
},
|
||||
};
|
||||
@ -1,83 +1,118 @@
|
||||
import { GlobalConfig } from "payload";
|
||||
import refreshHome from "./hooks/refreshHome";
|
||||
|
||||
export const HomeHero: GlobalConfig = {
|
||||
slug: 'homeHero',
|
||||
label: 'Hero',
|
||||
fields: [
|
||||
slug: "homeHero",
|
||||
label: "Hero",
|
||||
fields: [
|
||||
{
|
||||
name: "titleGroup",
|
||||
label: "Landing page",
|
||||
type: "group",
|
||||
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: "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: '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: "heroDescription",
|
||||
label: "Hero Description",
|
||||
type: "richText",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
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: "heroItems",
|
||||
label: "Items",
|
||||
type: "group",
|
||||
fields: [
|
||||
{
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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'
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [refreshHome],
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,22 +1,26 @@
|
||||
import { GlobalConfig } from "payload";
|
||||
import refreshHome from "./hooks/refreshHome";
|
||||
|
||||
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'
|
||||
}
|
||||
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",
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [refreshHome],
|
||||
},
|
||||
};
|
||||
|
||||
5
src/globals/Home/hooks/refreshHome.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export default function refreshHome() {
|
||||
revalidatePath("/");
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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"],
|
||||
|
||||