format fix
This commit is contained in:
1 parent
f061457d7f
commit
d34a8474fc
35 files changed
+466
-294
No files matched your search
+15
-15
@@ -1,35 +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>
|
||||
);
|
||||
)
|
||||
}
|
||||
+52
-25
@@ -1,51 +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-5 justify-center items-center h-screen">
|
||||
<div className="glow-ring animate-fade-in" style={{ animationDelay: "0ms" }}>
|
||||
<Image className="rounded-full max-w-[200px]" src={ProfileImage} alt="Liam Pietralla" />
|
||||
<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>
|
||||
<h1 className="text-5xl font-bold animate-fade-in-up" style={{ animationDelay: "120ms" }}>
|
||||
<h1
|
||||
className="animate-fade-in-up text-5xl font-bold"
|
||||
style={{ animationDelay: '120ms' }}
|
||||
>
|
||||
Liam Pietralla
|
||||
</h1>
|
||||
<div className="flex flex-col md:flex-row gap-0 md:gap-[7px] animate-fade-in-up" style={{ animationDelay: "240ms" }}>
|
||||
<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="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="w-[300px] animate-fade-in" style={{ animationDelay: "320ms" }} />
|
||||
<div className="flex flex-row gap-4 animate-fade-in-up" style={{ animationDelay: "400ms" }}>
|
||||
{home.mainLinks.map(link => (
|
||||
<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 animate-fade-in-up relative z-20" style={{ animationDelay: "480ms" }}>
|
||||
{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] animate-fade-in" style={{ animationDelay: "560ms" }} />
|
||||
<Rule
|
||||
className="animate-fade-in w-[300px]"
|
||||
style={{ animationDelay: '560ms' }}
|
||||
/>
|
||||
<a
|
||||
href="mailto:me@liampietralla.com"
|
||||
className="group leading-relaxed animate-fade-in-up"
|
||||
style={{ animationDelay: "640ms" }}
|
||||
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-accent"></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,28 +1,45 @@
|
||||
import ProjectCard from "@/components/project-card";
|
||||
import { getProjects } from "@/services/projects-service";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
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 animate-fade-in-up">
|
||||
<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-accent"></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 animate-fade-in-up" style={{ animationDelay: "120ms" }}>Projects</h1>
|
||||
<h2 className="animate-fade-in-up text-center max-w-2xl" style={{ animationDelay: "240ms" }}>
|
||||
A collection of interesting projects that I am working on currently or have worked on in the past.
|
||||
<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 flex-col gap-4 w-full">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{projects.docs.map((project, index) => (
|
||||
<div key={index} className="animate-fade-in-up" style={{ animationDelay: `${320 + index * 120}ms` }}>
|
||||
<div
|
||||
key={index}
|
||||
className="animate-fade-in-up"
|
||||
style={{ animationDelay: `${320 + index * 120}ms` }}
|
||||
>
|
||||
<ProjectCard {...project} />
|
||||
</div>
|
||||
))}
|
||||
@@ -31,4 +48,4 @@ const ProjectsPage = async () => {
|
||||
)
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
})
|
||||
}
|
||||
+40
-14
@@ -1,4 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
@@ -31,7 +31,7 @@ body {
|
||||
|
||||
.aurora::before,
|
||||
.aurora::after {
|
||||
content: "";
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 60vw;
|
||||
height: 60vw;
|
||||
@@ -56,18 +56,32 @@ body {
|
||||
}
|
||||
|
||||
@keyframes drift-1 {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
100% { transform: translate(15vw, 10vh) scale(1.15); }
|
||||
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); }
|
||||
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); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
@@ -76,8 +90,12 @@ body {
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
@@ -90,7 +108,7 @@ body {
|
||||
}
|
||||
|
||||
.glow-ring::before {
|
||||
content: "";
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -6px;
|
||||
border-radius: 9999px;
|
||||
@@ -102,8 +120,12 @@ body {
|
||||
}
|
||||
|
||||
@keyframes spin-slow {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.glass {
|
||||
@@ -111,7 +133,11 @@ body {
|
||||
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;
|
||||
transition:
|
||||
background 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
transform 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.glass-hover:hover {
|
||||
|
||||
@@ -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,42 +1,42 @@
|
||||
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="flex items-center space-x-2 glass glass-hover rounded-lg px-3 py-2 transition-all duration-300">
|
||||
<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 left-1/2 -translate-x-1/2 top-full mt-2 z-10 hidden p-2 px-4 text-sm text-black bg-white rounded-md shadow-lg group-hover:block text-center whitespace-nowrap">
|
||||
<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">
|
||||
<div className="group relative">
|
||||
<Link href={url}>{LinkContent}</Link>
|
||||
{Popover}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="relative group">
|
||||
<div className="group relative">
|
||||
<a href={url}>{LinkContent}</a>
|
||||
{Popover}
|
||||
</div>
|
||||
@@ -45,12 +45,12 @@ const HomePageLink = ({ title, icon, url, isPopover }: HomePageLinkProps) => {
|
||||
} else {
|
||||
const Content = (
|
||||
<>
|
||||
<span className="flex flex-row gap-2 group-hover:text-accent transition-colors duration-300">
|
||||
<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 max-w-0 group-hover:max-w-full transition-all duration-500 h-0.5 bg-accent"></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 (
|
||||
@@ -68,5 +68,4 @@ const HomePageLink = ({ title, icon, url, isPopover }: HomePageLinkProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default HomePageLink;
|
||||
export default HomePageLink
|
||||
@@ -1,15 +1,15 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CSSProperties } from "react";
|
||||
import { cn } from '@/lib/utils'
|
||||
import { CSSProperties } from 'react'
|
||||
|
||||
interface RuleProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
const HorizontalRule = ({ className, style }: RuleProps) => {
|
||||
return (
|
||||
<div style={style} 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="glass glass-hover rounded-lg p-5 flex flex-col gap-3 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-white/10" />
|
||||
{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 flex-wrap 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/10 text-foreground rounded-md px-2 py-0.5 text-sm border border-white/10 transition-colors hover:bg-white/20">
|
||||
<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 group-hover:text-accent transition-colors duration-300">
|
||||
<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-accent"></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 group-hover:text-accent transition-colors duration-300">
|
||||
<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-accent"></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',
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user