Feat/ui update #5
No files matched your search
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Build App
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -96,6 +96,6 @@ jobs:
|
||||
run: |
|
||||
export DEPLOYMENT_VERSION="${GITHUB_SHA:0:7}-$(date +%s)"
|
||||
nomad job run -var="deployment_version=$DEPLOYMENT_VERSION" infra/nomad/portfolio.nomad.hcl
|
||||
env:
|
||||
env:
|
||||
NOMAD_ADDR: ${{ vars.NOMAD_ADDR }}
|
||||
NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}
|
||||
NOMAD_TOKEN: ${{ secrets.NOMAD_TOKEN }}
|
||||
+19
-19
@@ -10,25 +10,25 @@ jobs:
|
||||
name: SBOM Generation and Upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PNPM and Node
|
||||
uses: pnpm/setup@v3
|
||||
with:
|
||||
runtime: node@24
|
||||
- name: Setup PNPM and Node
|
||||
uses: pnpm/setup@v3
|
||||
with:
|
||||
runtime: node@24
|
||||
|
||||
- name: Generate SBOM
|
||||
run: pnpm sbom --sbom-format cyclonedx --sbom-type application --out sbom.cyclonedx.json
|
||||
- name: Generate SBOM
|
||||
run: pnpm sbom --sbom-format cyclonedx --sbom-type application --out sbom.cyclonedx.json
|
||||
|
||||
- name: Upload SBOM
|
||||
uses: DependencyTrack/gh-upload-sbom@v4
|
||||
with:
|
||||
protocol: ${{ vars.DT_API_PROTOCOL }}
|
||||
server-hostname: ${{ vars.DT_API_HOSTNAME }}
|
||||
port: ${{ vars.DT_API_PORT }}
|
||||
api-key: ${{ secrets.DT_API_KEY }}
|
||||
project-name: 'Liam Portfolio'
|
||||
project-version: 'main'
|
||||
auto-create: true
|
||||
bom-filename: 'sbom.cyclonedx.json'
|
||||
- name: Upload SBOM
|
||||
uses: DependencyTrack/gh-upload-sbom@v4
|
||||
with:
|
||||
protocol: ${{ vars.DT_API_PROTOCOL }}
|
||||
server-hostname: ${{ vars.DT_API_HOSTNAME }}
|
||||
port: ${{ vars.DT_API_PORT }}
|
||||
api-key: ${{ secrets.DT_API_KEY }}
|
||||
project-name: 'Liam Portfolio'
|
||||
project-version: 'main'
|
||||
auto-create: true
|
||||
bom-filename: 'sbom.cyclonedx.json'
|
||||
@@ -0,0 +1,7 @@
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
src/app/(payload)/admin/importMap.js
|
||||
src/payload-types.ts
|
||||
src/migrations
|
||||
infra/**
|
||||
@@ -1,6 +1,6 @@
|
||||
# Liam Pietralla Porfolio
|
||||
|
||||
The portfolio is built using Next.JS and Payload CMS. Payload is running directly in the Next app, and can be accessed by appending /admin to the route.
|
||||
The portfolio is built using Next.JS and Payload CMS. Payload is running directly in the Next app, and can be accessed by appending /admin to the route.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -18,7 +18,7 @@ pnpm dev
|
||||
|
||||
### Payload CMS
|
||||
|
||||
Payload will require rebuilding on the types file once any changes are made:
|
||||
Payload will require rebuilding on the types file once any changes are made:
|
||||
|
||||
```bash
|
||||
pnpm run payload:generate:types
|
||||
@@ -46,4 +46,4 @@ Once the container is built, you can run it with the following command:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 -v liam-portfolio-data:/app/portfolio-data -e PAYLOAD_SECRET=your_secret liam-portfolio
|
||||
```
|
||||
```
|
||||
+5
-6
@@ -1,5 +1,4 @@
|
||||
services:
|
||||
|
||||
db:
|
||||
image: postgres:18
|
||||
restart: unless-stopped
|
||||
@@ -8,7 +7,7 @@ services:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=portfolio-dev
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- '5432:5432'
|
||||
volumes:
|
||||
- portfolio_db_data:/var/lib/postgresql
|
||||
|
||||
@@ -16,7 +15,7 @@ services:
|
||||
image: ghcr.io/achtungsoftware/alarik:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9000:8080"
|
||||
- '9000:8080'
|
||||
environment:
|
||||
- API_BASE_URL=http://localhost:9000
|
||||
- CONSOLE_BASE_URL=http://localhost:9001
|
||||
@@ -33,7 +32,7 @@ services:
|
||||
image: ghcr.io/achtungsoftware/alarik-console:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9001:3000"
|
||||
- '9001:3000'
|
||||
environment:
|
||||
- NUXT_PUBLIC_API_BASE_URL=http://localhost:9000
|
||||
- NUXT_PUBLIC_CONSOLE_BASE_URL=http://localhost:9001
|
||||
@@ -55,8 +54,8 @@ services:
|
||||
mc mb alarik/payload-media &&
|
||||
echo 'S3 bucket created';
|
||||
fi
|
||||
"
|
||||
"
|
||||
|
||||
volumes:
|
||||
portfolio_s3_data:
|
||||
portfolio_db_data:
|
||||
portfolio_db_data:
|
||||
+14
-14
@@ -1,25 +1,25 @@
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
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"),
|
||||
...compat.extends('next/core-web-vitals', 'next/typescript'),
|
||||
{
|
||||
ignores: [
|
||||
"node_modules/**",
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
'node_modules/**',
|
||||
'.next/**',
|
||||
'out/**',
|
||||
'build/**',
|
||||
'next-env.d.ts',
|
||||
],
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
export default eslintConfig;
|
||||
export default eslintConfig
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
import { withPayload } from "@payloadcms/next/withPayload";
|
||||
import type { NextConfig } from "next";
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
};
|
||||
output: 'standalone',
|
||||
}
|
||||
|
||||
export default withPayload(nextConfig);
|
||||
export default withPayload(nextConfig)
|
||||
@@ -8,6 +8,7 @@
|
||||
"build": "next build",
|
||||
"start": "payload migrate && next start",
|
||||
"lint": "eslint",
|
||||
"format": "prettier --write .",
|
||||
"payload:generate-types": "payload generate:types",
|
||||
"payload:migrate:create": "payload migrate:create"
|
||||
},
|
||||
@@ -35,6 +36,8 @@
|
||||
"@types/react-dom": "19.3.0",
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-config-next": "16.1.2",
|
||||
"prettier": "^3.9.10",
|
||||
"prettier-plugin-tailwindcss": "^0.8.1",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
|
||||
Generated
+72
@@ -230,6 +230,12 @@ importers:
|
||||
eslint-config-next:
|
||||
specifier: 16.1.2
|
||||
version: 16.1.2(@typescript-eslint/parser@8.53.0(eslint@9.39.5(jiti@2.5.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@2.5.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
|
||||
prettier:
|
||||
specifier: ^3.9.10
|
||||
version: 3.9.10
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.8.1
|
||||
version: 0.8.1(prettier@3.9.10)
|
||||
tailwindcss:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3
|
||||
@@ -3905,11 +3911,71 @@ packages:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
prettier-plugin-tailwindcss@0.8.1:
|
||||
resolution: {integrity: sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==}
|
||||
engines: {node: '>=20.19'}
|
||||
peerDependencies:
|
||||
'@ianvs/prettier-plugin-sort-imports': '*'
|
||||
'@prettier/plugin-hermes': '*'
|
||||
'@prettier/plugin-oxc': '*'
|
||||
'@prettier/plugin-pug': '*'
|
||||
'@shopify/prettier-plugin-liquid': '*'
|
||||
'@trivago/prettier-plugin-sort-imports': '*'
|
||||
'@zackad/prettier-plugin-twig': '*'
|
||||
prettier: ^3.0
|
||||
prettier-plugin-astro: '*'
|
||||
prettier-plugin-css-order: '*'
|
||||
prettier-plugin-jsdoc: '*'
|
||||
prettier-plugin-marko: '*'
|
||||
prettier-plugin-multiline-arrays: '*'
|
||||
prettier-plugin-organize-attributes: '*'
|
||||
prettier-plugin-organize-imports: '*'
|
||||
prettier-plugin-sort-imports: '*'
|
||||
prettier-plugin-svelte: '*'
|
||||
peerDependenciesMeta:
|
||||
'@ianvs/prettier-plugin-sort-imports':
|
||||
optional: true
|
||||
'@prettier/plugin-hermes':
|
||||
optional: true
|
||||
'@prettier/plugin-oxc':
|
||||
optional: true
|
||||
'@prettier/plugin-pug':
|
||||
optional: true
|
||||
'@shopify/prettier-plugin-liquid':
|
||||
optional: true
|
||||
'@trivago/prettier-plugin-sort-imports':
|
||||
optional: true
|
||||
'@zackad/prettier-plugin-twig':
|
||||
optional: true
|
||||
prettier-plugin-astro:
|
||||
optional: true
|
||||
prettier-plugin-css-order:
|
||||
optional: true
|
||||
prettier-plugin-jsdoc:
|
||||
optional: true
|
||||
prettier-plugin-marko:
|
||||
optional: true
|
||||
prettier-plugin-multiline-arrays:
|
||||
optional: true
|
||||
prettier-plugin-organize-attributes:
|
||||
optional: true
|
||||
prettier-plugin-organize-imports:
|
||||
optional: true
|
||||
prettier-plugin-sort-imports:
|
||||
optional: true
|
||||
prettier-plugin-svelte:
|
||||
optional: true
|
||||
|
||||
prettier@3.8.0:
|
||||
resolution: {integrity: sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
prettier@3.9.10:
|
||||
resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
process-warning@5.0.0:
|
||||
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
||||
|
||||
@@ -8915,8 +8981,14 @@ snapshots:
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier-plugin-tailwindcss@0.8.1(prettier@3.9.10):
|
||||
dependencies:
|
||||
prettier: 3.9.10
|
||||
|
||||
prettier@3.8.0: {}
|
||||
|
||||
prettier@3.9.10: {}
|
||||
|
||||
process-warning@5.0.0: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
const config = {
|
||||
plugins: ["@tailwindcss/postcss"],
|
||||
};
|
||||
plugins: ['@tailwindcss/postcss'],
|
||||
}
|
||||
|
||||
export default config;
|
||||
export default config
|
||||
@@ -0,0 +1,13 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('prettier').Config} */
|
||||
const config = {
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
tailwindStylesheet: 'src/app/globals.css',
|
||||
tailwindFunctions: ['cn', 'cva'],
|
||||
}
|
||||
|
||||
export default config
|
||||
+16
-15
@@ -1,34 +1,35 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "../globals.css";
|
||||
import type { Metadata } from 'next'
|
||||
import { Geist, Geist_Mono } from 'next/font/google'
|
||||
import '../globals.css'
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
variable: '--font-geist-sans',
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
variable: '--font-geist-mono',
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Liam Pietralla",
|
||||
description: "Enthusiastic Software Developer & DevOps Engineer",
|
||||
};
|
||||
title: 'Liam Pietralla',
|
||||
description: 'Enthusiastic Software Developer & DevOps Engineer',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased px-4`}
|
||||
className={`${geistSans.variable} ${geistMono.variable} px-4 antialiased`}
|
||||
>
|
||||
<div className="aurora" />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
||||
+58
-23
@@ -1,43 +1,78 @@
|
||||
import IndexLink from "@/components/home-page-link";
|
||||
import Rule from "@/components/horizontal-rule";
|
||||
import { getHome } from "@/services/home-service";
|
||||
import { Mail } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import IndexLink from '@/components/home-page-link'
|
||||
import Rule from '@/components/horizontal-rule'
|
||||
import { getHome } from '@/services/home-service'
|
||||
import { Mail } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
|
||||
import ProfileImage from "../../../public/images/liam_pietralla.jpg";
|
||||
import ProfileImage from '../../../public/images/liam_pietralla.jpg'
|
||||
|
||||
const IndexPage = async () => {
|
||||
const home = await getHome();
|
||||
const home = await getHome()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 justify-center items-center h-screen">
|
||||
<Image className="rounded-full max-w-[200px]" src={ProfileImage} alt="Liam Pietralla" />
|
||||
<h1 className="text-5xl font-bold">Liam Pietralla</h1>
|
||||
<div className="flex flex-col md:flex-row gap-0 md:gap-[7px]">
|
||||
<h2 className="text-xl text-center">Enthusiastic Software Developer</h2>
|
||||
<h2 className="text-xl text-center mt-[-5px] md:mt-0">& DevOps Engineer</h2>
|
||||
<div className="flex h-screen flex-col items-center justify-center gap-5">
|
||||
<div
|
||||
className="glow-ring animate-fade-in"
|
||||
style={{ animationDelay: '0ms' }}
|
||||
>
|
||||
<Image
|
||||
className="max-w-[200px] rounded-full"
|
||||
src={ProfileImage}
|
||||
alt="Liam Pietralla"
|
||||
/>
|
||||
</div>
|
||||
<Rule className="w-[300px]" />
|
||||
<div className="flex flex-row gap-4">
|
||||
{home.mainLinks.map(link => (
|
||||
<h1
|
||||
className="animate-fade-in-up text-5xl font-bold"
|
||||
style={{ animationDelay: '120ms' }}
|
||||
>
|
||||
Liam Pietralla
|
||||
</h1>
|
||||
<div
|
||||
className="animate-fade-in-up flex flex-col gap-0 md:flex-row md:gap-[7px]"
|
||||
style={{ animationDelay: '240ms' }}
|
||||
>
|
||||
<h2 className="text-center text-xl">Enthusiastic Software Developer</h2>
|
||||
<h2 className="mt-[-5px] text-center text-xl md:mt-0">
|
||||
& DevOps Engineer
|
||||
</h2>
|
||||
</div>
|
||||
<Rule
|
||||
className="animate-fade-in w-[300px]"
|
||||
style={{ animationDelay: '320ms' }}
|
||||
/>
|
||||
<div
|
||||
className="animate-fade-in-up flex flex-row gap-4"
|
||||
style={{ animationDelay: '400ms' }}
|
||||
>
|
||||
{home.mainLinks.map((link) => (
|
||||
<IndexLink key={link.id} {...link} />
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-row gap-4">
|
||||
{home.popoverLinks.map(link => (
|
||||
<div
|
||||
className="animate-fade-in-up relative z-20 flex flex-row gap-4"
|
||||
style={{ animationDelay: '480ms' }}
|
||||
>
|
||||
{home.popoverLinks.map((link) => (
|
||||
<IndexLink key={link.id} {...link} isPopover={true} />
|
||||
))}
|
||||
</div>
|
||||
<Rule className="w-[300px]" />
|
||||
<a href="mailto:me@liampietralla.com" className="group leading-relaxed">
|
||||
<Rule
|
||||
className="animate-fade-in w-[300px]"
|
||||
style={{ animationDelay: '560ms' }}
|
||||
/>
|
||||
<a
|
||||
href="mailto:me@liampietralla.com"
|
||||
className="group animate-fade-in-up leading-relaxed"
|
||||
style={{ animationDelay: '640ms' }}
|
||||
>
|
||||
<span className="flex flex-row gap-2">
|
||||
<Mail />
|
||||
me@liampietralla.com
|
||||
</span>
|
||||
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||
<span className="block h-0.5 max-w-0 bg-accent transition-all duration-500 group-hover:max-w-full"></span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default IndexPage;
|
||||
export default IndexPage
|
||||
@@ -1,35 +1,51 @@
|
||||
import ProjectCard from "@/components/project-card";
|
||||
import Rule from "@/components/horizontal-rule";
|
||||
import { getProjects } from "@/services/projects-service";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Fragment } from "react";
|
||||
import ProjectCard from '@/components/project-card'
|
||||
import { getProjects } from '@/services/projects-service'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
import ProfileImage from "../../../../public/images/liam_pietralla.jpg";
|
||||
import ProfileImage from '../../../../public/images/liam_pietralla.jpg'
|
||||
|
||||
const ProjectsPage = async () => {
|
||||
const projects = await getProjects();
|
||||
const projects = await getProjects()
|
||||
return (
|
||||
<div className="flex flex-col gap-4 justify-center items-center my-15">
|
||||
<div className="flex flex-row items-center gap-2 my-2">
|
||||
<Image src={ProfileImage} alt="Liam Pietralla" className="rounded-full max-w-[50px]" />
|
||||
<Link href="/" className="group leading-relaxed font-semi-bold">
|
||||
<div className="my-15 flex flex-col items-center justify-center gap-4">
|
||||
<div className="animate-fade-in-up my-2 flex flex-row items-center gap-2">
|
||||
<Image
|
||||
src={ProfileImage}
|
||||
alt="Liam Pietralla"
|
||||
className="max-w-[50px] rounded-full"
|
||||
/>
|
||||
<Link href="/" className="group font-semi-bold leading-relaxed">
|
||||
Liam Pietralla
|
||||
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||
<span className="block h-0.5 max-w-0 bg-accent transition-all duration-500 group-hover:max-w-full"></span>
|
||||
</Link>
|
||||
</div>
|
||||
<h1 className="text-5xl font-bold">Projects</h1>
|
||||
<h2>A collection of interesting projects that I am working on currently or have worked on in the past.</h2>
|
||||
<div className="flex flex-col gap-4">
|
||||
<h1
|
||||
className="animate-fade-in-up text-5xl font-bold"
|
||||
style={{ animationDelay: '120ms' }}
|
||||
>
|
||||
Projects
|
||||
</h1>
|
||||
<h2
|
||||
className="animate-fade-in-up max-w-2xl text-center"
|
||||
style={{ animationDelay: '240ms' }}
|
||||
>
|
||||
A collection of interesting projects that I am working on currently or
|
||||
have worked on in the past.
|
||||
</h2>
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{projects.docs.map((project, index) => (
|
||||
<Fragment key={index}>
|
||||
<div
|
||||
key={index}
|
||||
className="animate-fade-in-up"
|
||||
style={{ animationDelay: `${320 + index * 120}ms` }}
|
||||
>
|
||||
<ProjectCard {...project} />
|
||||
{index < projects.docs.length - 1 && <Rule />}
|
||||
</Fragment>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectsPage;
|
||||
export default ProjectsPage
|
||||
@@ -15,7 +15,10 @@ type Args = {
|
||||
}>
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
export const generateMetadata = ({
|
||||
params,
|
||||
searchParams,
|
||||
}: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) =>
|
||||
|
||||
@@ -15,7 +15,10 @@ type Args = {
|
||||
}>
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
export const generateMetadata = ({
|
||||
params,
|
||||
searchParams,
|
||||
}: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const Page = ({ params, searchParams }: Args) =>
|
||||
|
||||
@@ -15,7 +15,7 @@ type NextCtx = { params: Promise<{ slug?: string[] }> }
|
||||
type PayloadCtx = { params: Promise<{ slug: string[] }> }
|
||||
|
||||
const coerceCtx = (ctx: NextCtx): PayloadCtx => ({
|
||||
params: ctx.params.then(p => ({ slug: p?.slug ?? [] })),
|
||||
params: ctx.params.then((p) => ({ slug: p?.slug ?? [] })),
|
||||
})
|
||||
|
||||
export function GET(req: Request, ctx: NextCtx) {
|
||||
@@ -40,4 +40,4 @@ export function PUT(req: Request, ctx: NextCtx) {
|
||||
|
||||
export function OPTIONS(req: Request, ctx: NextCtx) {
|
||||
return REST_OPTIONS(config)(req, coerceCtx(ctx))
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
||||
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
return GRAPHQL_PLAYGROUND_GET(config)(req)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ type NextCtx = { params: Promise<{ slug?: string[] }> }
|
||||
type PayloadCtx = { params: Promise<{ slug: string[] }> }
|
||||
|
||||
const coerceCtx = (ctx: NextCtx): PayloadCtx => ({
|
||||
params: ctx.params.then(p => ({ slug: p?.slug ?? [] })),
|
||||
params: ctx.params.then((p) => ({ slug: p?.slug ?? [] })),
|
||||
})
|
||||
|
||||
export function POST(req: Request) {
|
||||
@@ -16,4 +16,4 @@ export function POST(req: Request) {
|
||||
|
||||
export function OPTIONS(req: Request, ctx: NextCtx) {
|
||||
return REST_OPTIONS(config)(req, coerceCtx(ctx))
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,11 @@ const serverFunction: ServerFunctionClient = async function (args) {
|
||||
}
|
||||
|
||||
const Layout = ({ children }: Args) => (
|
||||
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
|
||||
<RootLayout
|
||||
config={config}
|
||||
importMap={importMap}
|
||||
serverFunction={serverFunction}
|
||||
>
|
||||
{children}
|
||||
</RootLayout>
|
||||
)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import pkg from "../../../../package.json";
|
||||
import pkg from '../../../../package.json'
|
||||
|
||||
export async function GET() {
|
||||
const response = {
|
||||
"status": "Healthy",
|
||||
"version": pkg.version
|
||||
};
|
||||
status: 'Healthy',
|
||||
version: pkg.version,
|
||||
}
|
||||
return new Response(JSON.stringify(response), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
}
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
+146
-3
@@ -1,13 +1,15 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--accent: #7c9cff;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-accent: var(--accent);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
@@ -15,5 +17,146 @@
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.aurora {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.aurora::before,
|
||||
.aurora::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 60vw;
|
||||
height: 60vw;
|
||||
border-radius: 9999px;
|
||||
filter: blur(120px);
|
||||
opacity: 0.22;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.aurora::before {
|
||||
top: -15%;
|
||||
left: -10%;
|
||||
background: radial-gradient(circle at center, var(--accent), transparent 70%);
|
||||
animation: drift-1 18s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.aurora::after {
|
||||
bottom: -20%;
|
||||
right: -10%;
|
||||
background: radial-gradient(circle at center, #b07cff, transparent 70%);
|
||||
animation: drift-2 22s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes drift-1 {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: translate(15vw, 10vh) scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes drift-2 {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-12vw, -8vh) scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
opacity: 0;
|
||||
animation: fade-in-up 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
opacity: 0;
|
||||
animation: fade-in 1.2s ease forwards;
|
||||
}
|
||||
|
||||
.glow-ring {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.glow-ring::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -6px;
|
||||
border-radius: 9999px;
|
||||
background: conic-gradient(from 0deg, var(--accent), #b07cff, var(--accent));
|
||||
filter: blur(8px);
|
||||
opacity: 0.55;
|
||||
z-index: -1;
|
||||
animation: spin-slow 8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin-slow {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
transition:
|
||||
background 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
transform 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.glass-hover:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(124, 156, 255, 0.4);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 30px -10px rgba(124, 156, 255, 0.35);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
.aurora::before,
|
||||
.aurora::after,
|
||||
.glow-ring::before {
|
||||
animation: none !important;
|
||||
}
|
||||
.animate-fade-in-up,
|
||||
.animate-fade-in {
|
||||
opacity: 1;
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
import type { CollectionConfig } from "payload"
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: "media",
|
||||
slug: 'media',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "alt",
|
||||
type: "text",
|
||||
name: 'alt',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
upload: {
|
||||
imageSizes: [
|
||||
{
|
||||
name: "thumbnail",
|
||||
name: 'thumbnail',
|
||||
width: 150,
|
||||
height: 150,
|
||||
position: "centre",
|
||||
}
|
||||
position: 'centre',
|
||||
},
|
||||
],
|
||||
adminThumbnail: "thumbnail",
|
||||
}
|
||||
adminThumbnail: 'thumbnail',
|
||||
},
|
||||
}
|
||||
+26
-26
@@ -1,61 +1,61 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { CollectionConfig } from "payload";
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Projects: CollectionConfig = {
|
||||
slug: "project",
|
||||
slug: 'project',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
type: "textarea",
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "featuredImage",
|
||||
type: "relationship",
|
||||
relationTo: "media",
|
||||
name: 'featuredImage',
|
||||
type: 'relationship',
|
||||
relationTo: 'media',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "tags",
|
||||
type: "array",
|
||||
name: 'tags',
|
||||
type: 'array',
|
||||
required: false,
|
||||
fields: [
|
||||
{
|
||||
name: "tag",
|
||||
type: "text",
|
||||
name: 'tag',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "viewLink",
|
||||
type: "text",
|
||||
name: 'viewLink',
|
||||
type: 'text',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "repositoryLink",
|
||||
type: "text",
|
||||
name: 'repositoryLink',
|
||||
type: 'text',
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
afterChange: [
|
||||
async () => {
|
||||
revalidatePath("/projects");
|
||||
}
|
||||
revalidatePath('/projects')
|
||||
},
|
||||
],
|
||||
afterDelete: [
|
||||
async () => {
|
||||
revalidatePath("/projects");
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
revalidatePath('/projects')
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CollectionConfig } from "payload"
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: "users",
|
||||
slug: 'users',
|
||||
admin: {
|
||||
useAsTitle: "email",
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
auth: true,
|
||||
fields: [
|
||||
|
||||
@@ -1,61 +1,71 @@
|
||||
import Link from "next/link";
|
||||
import { DynamicIcon, IconName } from "lucide-react/dynamic";
|
||||
import { lucidIconMap } from "@/lib/lucid-options";
|
||||
import Link from 'next/link'
|
||||
import { DynamicIcon, IconName } from 'lucide-react/dynamic'
|
||||
import { lucidIconMap } from '@/lib/lucid-options'
|
||||
|
||||
interface HomePageLinkProps {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
id?: string | null;
|
||||
isPopover?: boolean;
|
||||
title: string
|
||||
url: string
|
||||
icon: string
|
||||
id?: string | null
|
||||
isPopover?: boolean
|
||||
}
|
||||
|
||||
const HomePageLink = ({ title, icon, url, isPopover }: HomePageLinkProps) => {
|
||||
const isRelative = !url.startsWith("http");
|
||||
const IconComponent = lucidIconMap[icon as IconName];
|
||||
const isRelative = !url.startsWith('http')
|
||||
const IconComponent = lucidIconMap[icon as IconName]
|
||||
|
||||
if (isPopover) {
|
||||
const LinkContent = (
|
||||
<span className="glass glass-hover flex items-center space-x-2 rounded-lg px-3 py-2 transition-all duration-300">
|
||||
{icon && <IconComponent className="size-4" />}
|
||||
</span>
|
||||
)
|
||||
|
||||
const Popover = (
|
||||
<div className="absolute top-full left-1/2 z-10 mt-2 hidden -translate-x-1/2 rounded-md bg-white p-2 px-4 text-center text-sm whitespace-nowrap text-black shadow-lg group-hover:block">
|
||||
{title}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (isRelative) {
|
||||
return (
|
||||
<div className="relative group">
|
||||
<Link href={url} className="flex items-center space-x-2">
|
||||
{icon && <IconComponent />}
|
||||
</Link>
|
||||
<div className="absolute left-1/2 -translate-x-1/2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md group-hover:block text-center">
|
||||
{title}
|
||||
</div>
|
||||
<div className="group relative">
|
||||
<Link href={url}>{LinkContent}</Link>
|
||||
{Popover}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="relative group">
|
||||
<a href={url} className="flex items-center space-x-2">
|
||||
{icon && <IconComponent />}
|
||||
</a>
|
||||
<div className="absolute left-1/2 -translate-x-1/2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md group-hover:block text-center">
|
||||
{title}
|
||||
</div>
|
||||
<div className="group relative">
|
||||
<a href={url}>{LinkContent}</a>
|
||||
{Popover}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const Content = (
|
||||
<>
|
||||
<span className="flex flex-row gap-2 transition-colors duration-300 group-hover:text-accent">
|
||||
{icon && <IconComponent className="size-4" />} {title}
|
||||
</span>
|
||||
<span className="block h-0.5 max-w-0 bg-accent transition-all duration-500 group-hover:max-w-full"></span>
|
||||
</>
|
||||
)
|
||||
|
||||
if (isRelative) {
|
||||
return (
|
||||
<Link href={url} className="group leading-relaxed">
|
||||
<span className="flex flex-row gap-2">{icon && <IconComponent />} {title}</span>
|
||||
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||
{Content}
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<a href={url} className="group leading-relaxed">
|
||||
<span className="flex flex-row gap-2">{icon && <IconComponent />} {title}</span>
|
||||
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||
{Content}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default HomePageLink;
|
||||
export default HomePageLink
|
||||
@@ -1,13 +1,15 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils'
|
||||
import { CSSProperties } from 'react'
|
||||
|
||||
interface RuleProps {
|
||||
className?: string;
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
const HorizontalRule = ({ className }: RuleProps) => {
|
||||
const HorizontalRule = ({ className, style }: RuleProps) => {
|
||||
return (
|
||||
<div className={cn("border-t-1 border-accent", className)} />
|
||||
<div style={style} className={cn('border-t-1 border-accent', className)} />
|
||||
)
|
||||
}
|
||||
|
||||
export default HorizontalRule;
|
||||
export default HorizontalRule
|
||||
@@ -1,20 +1,29 @@
|
||||
import Image from "next/image";
|
||||
import HorizontalRule from "./horizontal-rule";
|
||||
import { Code, ExternalLink } from "lucide-react";
|
||||
import { Project } from "@/payload-types";
|
||||
import Image from 'next/image'
|
||||
import HorizontalRule from './horizontal-rule'
|
||||
import { Code, ExternalLink } from 'lucide-react'
|
||||
import { Project } from '@/payload-types'
|
||||
|
||||
const ProjectCard = (project: Project) => {
|
||||
return (
|
||||
<div className="border border-white rounded-md p-4 flex flex-col gap-2 max-w-[700px] mx-auto">
|
||||
<div className="glass glass-hover mx-auto flex max-w-[700px] flex-col gap-3 rounded-lg p-5">
|
||||
<h2 className="text-2xl font-bold">{project.title}</h2>
|
||||
<div className="flex flex-col gap-2">
|
||||
{project.featuredImage && typeof project.featuredImage === "object" && (
|
||||
<Image src={project.featuredImage.url!} alt={project.title} width={project.featuredImage.width!} height={project.featuredImage.height!} className="rounded-md border border-gray-600" />
|
||||
{project.featuredImage && typeof project.featuredImage === 'object' && (
|
||||
<Image
|
||||
src={project.featuredImage.url!}
|
||||
alt={project.title}
|
||||
width={project.featuredImage.width!}
|
||||
height={project.featuredImage.height!}
|
||||
className="rounded-md border border-white/10"
|
||||
/>
|
||||
)}
|
||||
<p className="whitespace-pre-wrap">{project.description}</p>
|
||||
<div className="flex flex-row gap-2 my-2">
|
||||
<div className="my-2 flex flex-row flex-wrap gap-2">
|
||||
{(project?.tags || []).map((tag) => (
|
||||
<span key={tag.id} className="bg-white text-black rounded-md px-1 py-0.5 text-sm hover:bg-gray-200">
|
||||
<span
|
||||
key={tag.id}
|
||||
className="rounded-md border border-white/10 bg-white/10 px-2 py-0.5 text-sm text-foreground transition-colors hover:bg-white/20"
|
||||
>
|
||||
{tag.tag}
|
||||
</span>
|
||||
))}
|
||||
@@ -24,20 +33,20 @@ const ProjectCard = (project: Project) => {
|
||||
<div className="flex flex-row gap-4">
|
||||
{project.viewLink && (
|
||||
<a href={project.viewLink} target="_blank" className="group">
|
||||
<span className="flex flex-row gap-2">
|
||||
<ExternalLink />
|
||||
<span className="flex flex-row gap-2 transition-colors duration-300 group-hover:text-accent">
|
||||
<ExternalLink className="size-4" />
|
||||
View Project
|
||||
</span>
|
||||
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||
<span className="block h-0.5 max-w-0 bg-accent transition-all duration-500 group-hover:max-w-full"></span>
|
||||
</a>
|
||||
)}
|
||||
{project.repositoryLink && (
|
||||
<a href={project.repositoryLink} target="_blank" className="group">
|
||||
<span className="flex flex-row gap-2">
|
||||
<Code />
|
||||
<span className="flex flex-row gap-2 transition-colors duration-300 group-hover:text-accent">
|
||||
<Code className="size-4" />
|
||||
View Source
|
||||
</span>
|
||||
<span className="block max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-white"></span>
|
||||
<span className="block h-0.5 max-w-0 bg-accent transition-all duration-500 group-hover:max-w-full"></span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
@@ -45,4 +54,4 @@ const ProjectCard = (project: Project) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectCard;
|
||||
export default ProjectCard
|
||||
+24
-24
@@ -1,54 +1,54 @@
|
||||
import { lucideOptions } from "@/lib/lucid-options";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { GlobalConfig } from "payload";
|
||||
import { lucideOptions } from '@/lib/lucid-options'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { GlobalConfig } from 'payload'
|
||||
|
||||
export const Home: GlobalConfig = {
|
||||
slug: "home",
|
||||
slug: 'home',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "mainLinks",
|
||||
type: "array",
|
||||
name: 'mainLinks',
|
||||
type: 'array',
|
||||
required: true,
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
type: "text",
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
type: "select",
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
options: lucideOptions,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "popoverLinks",
|
||||
type: "array",
|
||||
name: 'popoverLinks',
|
||||
type: 'array',
|
||||
required: true,
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
type: "text",
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
type: "select",
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
options: lucideOptions,
|
||||
required: true,
|
||||
},
|
||||
@@ -58,8 +58,8 @@ export const Home: GlobalConfig = {
|
||||
hooks: {
|
||||
afterChange: [
|
||||
async () => {
|
||||
revalidatePath("/");
|
||||
}
|
||||
revalidatePath('/')
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
+12
-12
@@ -1,15 +1,15 @@
|
||||
import { Code2, Github, Linkedin, LucideIcon, Notebook } from "lucide-react"
|
||||
import { Code2, Github, Linkedin, LucideIcon, Notebook } from 'lucide-react'
|
||||
|
||||
export const lucideOptions: { label: string, value: string }[] = [
|
||||
{ label: "Code 2", value: "code-2" },
|
||||
{ label: "Notebook", value: "notebook" },
|
||||
{ label: "Github", value: "github" },
|
||||
{ label: "Linkedin", value: "linkedin" },
|
||||
];
|
||||
export const lucideOptions: { label: string; value: string }[] = [
|
||||
{ label: 'Code 2', value: 'code-2' },
|
||||
{ label: 'Notebook', value: 'notebook' },
|
||||
{ label: 'Github', value: 'github' },
|
||||
{ label: 'Linkedin', value: 'linkedin' },
|
||||
]
|
||||
|
||||
export const lucidIconMap: Record<string, LucideIcon> = {
|
||||
"code-2": Code2,
|
||||
"notebook": Notebook,
|
||||
"github": Github,
|
||||
"linkedin": Linkedin,
|
||||
};
|
||||
'code-2': Code2,
|
||||
notebook: Notebook,
|
||||
github: Github,
|
||||
linkedin: Linkedin,
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
|
||||
+19
-19
@@ -1,15 +1,15 @@
|
||||
import { postgresAdapter } from "@payloadcms/db-postgres"
|
||||
import { lexicalEditor } from "@payloadcms/richtext-lexical"
|
||||
import path from "path"
|
||||
import { buildConfig } from "payload"
|
||||
import { s3Storage } from "@payloadcms/storage-s3"
|
||||
import sharp from "sharp"
|
||||
import { fileURLToPath } from "url"
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import path from 'path'
|
||||
import { buildConfig } from 'payload'
|
||||
import { s3Storage } from '@payloadcms/storage-s3'
|
||||
import sharp from 'sharp'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { Media } from "./collections/Media"
|
||||
import { Projects } from "./collections/Projects"
|
||||
import { Users } from "./collections/Users"
|
||||
import { Home } from "./globals/home"
|
||||
import { Media } from './collections/Media'
|
||||
import { Projects } from './collections/Projects'
|
||||
import { Users } from './collections/Users'
|
||||
import { Home } from './globals/home'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -24,13 +24,13 @@ export default buildConfig({
|
||||
collections: [Users, Media, Projects],
|
||||
globals: [Home],
|
||||
editor: lexicalEditor(),
|
||||
secret: process.env.PAYLOAD_SECRET || "",
|
||||
secret: process.env.PAYLOAD_SECRET || '',
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, "payload-types.ts"),
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
},
|
||||
db: postgresAdapter({
|
||||
pool: {
|
||||
connectionString: process.env.DATABASE_URL || "",
|
||||
connectionString: process.env.DATABASE_URL || '',
|
||||
},
|
||||
}),
|
||||
sharp,
|
||||
@@ -39,16 +39,16 @@ export default buildConfig({
|
||||
collections: {
|
||||
media: true,
|
||||
},
|
||||
bucket: process.env.S3_BUCKET || "",
|
||||
bucket: process.env.S3_BUCKET || '',
|
||||
config: {
|
||||
forcePathStyle: true,
|
||||
region: process.env.S3_REGION || "",
|
||||
region: process.env.S3_REGION || '',
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID || '',
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || '',
|
||||
},
|
||||
endpoint: process.env.S3_ENDPOINT || undefined,
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
@@ -4,6 +4,6 @@ import config from '@payload-config'
|
||||
export const getHome = async () => {
|
||||
const payload = await getPayload({ config })
|
||||
return await payload.findGlobal({
|
||||
slug: "home"
|
||||
slug: 'home',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,6 @@ import config from '@payload-config'
|
||||
export const getProjects = async () => {
|
||||
const payload = await getPayload({ config })
|
||||
return await payload.find({
|
||||
collection: "project"
|
||||
collection: 'project',
|
||||
})
|
||||
}
|
||||
}
|
||||
+4
-14
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -23,12 +19,8 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@payload-config": [
|
||||
"./src/payload.config.ts"
|
||||
]
|
||||
"@/*": ["./src/*"],
|
||||
"@payload-config": ["./src/payload.config.ts"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
@@ -38,7 +30,5 @@
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in new issue
Block a user