refactor: large refactor around monorepo

Just a commit point while I'm testing stuff. Already decided at this
point to simplify and revert away from PayloadCMS.
This commit is contained in:
2026-01-04 13:18:34 -06:00
parent 31b1804fc9
commit af81d8e048
110 changed files with 8392 additions and 12918 deletions
-36
View File
@@ -1,36 +0,0 @@
import React from "react";
// Fontsource imports
import "@fontsource-variable/inter";
import "@fontsource-variable/roboto";
import "@fontsource-variable/roboto-mono";
import "@fontsource/hanken-grotesk/900.css";
import "@fontsource/schibsted-grotesk/400.css";
import "@fontsource/schibsted-grotesk/500.css";
import "@fontsource/schibsted-grotesk/600.css";
import "@radix-ui/themes/styles.css";
import "@/styles/globals.css";
import type { Metadata } from "next";
import { Providers } from "./providers";
export const metadata: Metadata = {
title: "Xevion.dev",
description:
"The personal website of Xevion, a full-stack software developer.",
applicationName: "xevion.dev",
};
export default async function RootLayout(props: { children: React.ReactNode }) {
const { children } = props;
return (
<html lang="en">
<body>
<Providers>
<main>{children}</main>
</Providers>
</body>
</html>
);
}
-122
View File
@@ -1,122 +0,0 @@
import AppWrapper from "@/components/AppWrapper";
import { Flex, Button, Text, Container, Box } from "@radix-ui/themes";
import Link from "next/link";
import { SiGithub, IconType } from "@icons-pack/react-simple-icons";
import { SiLinkedin } from "react-icons/si";
import { Rss } from "lucide-react";
function NavLink({
href,
children,
}: {
href: string;
children: React.ReactNode;
}) {
return (
<Link href={href}>
<Text size="3" className="text-(--gray-11) hover:text-(--gray-12)">
{children}
</Text>
</Link>
);
}
function IconLink({
href,
icon: Icon,
}: {
href: string;
icon: React.ElementType;
}) {
return (
<Link href={href} className="text-(--gray-11) hover:text-(--gray-12)">
<Icon className="size-5" />
</Link>
);
}
function SocialLink({
href,
icon: IconComponent,
children,
}: {
href: string;
icon: React.ElementType;
children: React.ReactNode;
}) {
return (
<Link href={href}>
<Flex
align="center"
className="gap-x-1.5 px-1.5 py-1 rounded-xs bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
>
<IconComponent className="size-4 text-zinc-300" />
<Text size="2" className="text-zinc-100">
{children}
</Text>
</Flex>
</Link>
);
}
export default async function HomePage() {
return (
<AppWrapper
className="overflow-x-hidden font-schibsted"
dotsClassName="animate-bg"
>
{/* Top Navigation Bar */}
<Flex justify="end" align="center" width="100%" pt="5" px="6" pb="9">
<Flex gap="4" align="center">
<NavLink href="/projects">Projects</NavLink>
<NavLink href="/blog">Blog</NavLink>
<IconLink href="https://github.com/Xevion" icon={SiGithub} />
<IconLink href="/rss" icon={Rss} />
</Flex>
</Flex>
{/* Main Content */}
<Flex align="center" direction="column">
<Box className="max-w-2xl mx-6 border-b border-(--gray-7) divide-y divide-(--gray-7)">
{/* Name & Job Title */}
<Flex direction="column" pb="4">
<Text size="6" weight="bold" highContrast>
Ryan Walters,
</Text>
<Text
size="6"
weight="regular"
style={{
color: "var(--gray-11)",
}}
>
Software Engineer
</Text>
</Flex>
<Box py="4" className="text-(--gray-12)">
<Text style={{ fontSize: "0.95em" }}>
A fanatical software engineer with expertise and passion for
sound, scalable and high-performance applications. I'm always
working on something new. <br />
Sometimes innovative &mdash; sometimes crazy.
</Text>
</Box>
<Box py="3">
<Text>Find me on</Text>
<Flex gapX="2" pl="3" pt="3" pb="2">
<SocialLink href="https://github.com/Xevion" icon={SiGithub}>
GitHub
</SocialLink>
<SocialLink
href="https://linkedin.com/in/ryancwalters"
icon={SiLinkedin}
>
LinkedIn
</SocialLink>
</Flex>
</Box>
</Box>
</Flex>
</AppWrapper>
);
}
-96
View File
@@ -1,96 +0,0 @@
import AppWrapper from "@/components/AppWrapper";
import { cn } from "@/utils/helpers";
import Link from "next/link";
import Balancer from "react-wrap-balancer";
import { getPayload } from "payload";
import config from "../../../payload.config";
import type { Link as PayloadLink } from "@/payload-types";
export const dynamic = "force-dynamic"; // Don't prerender at build time
export default async function ProjectsPage() {
const payloadConfig = await config;
const payload = await getPayload({ config: payloadConfig });
// Fetch all projects
const { docs: projects } = await payload.find({
collection: "projects",
where: {
status: {
equals: "published",
},
},
sort: "-updatedAt",
});
// Fetch all links in one query (fixes N+1 problem)
const { docs: allLinks } = await payload.find({
collection: "links",
});
// Group links by project ID
const linksByProject = new Map<number, PayloadLink[]>();
for (const link of allLinks) {
const projectId =
typeof link.project === "number" ? link.project : link.project.id;
if (!linksByProject.has(projectId)) {
linksByProject.set(projectId, []);
}
linksByProject.get(projectId)!.push(link);
}
return (
<AppWrapper dotsClassName="animate-bg-fast">
<div className="relative z-10 mx-auto grid grid-cols-1 justify-center gap-y-4 px-4 py-20 align-middle sm:grid-cols-2 md:max-w-[50rem] lg:max-w-[75rem] lg:grid-cols-3 lg:gap-y-9">
<div className="mb-3 text-center sm:col-span-2 md:mb-5 lg:col-span-3 lg:mb-7">
<h1 className="pb-3 font-hanken text-4xl text-zinc-200 opacity-100 md:text-5xl">
Projects
</h1>
<Balancer className="text-lg text-zinc-400">
created, maintained, or contributed to by me...
</Balancer>
</div>
{projects.map(({ id, name, shortDescription: description, icon }) => {
const links = linksByProject.get(id) ?? [];
const useAnchor = links.length > 0;
const DynamicLink = useAnchor ? Link : "div";
const linkProps = useAnchor
? { href: links[0]!.url, target: "_blank", rel: "noreferrer" }
: {};
return (
<div className="max-w-fit" key={id}>
{/* @ts-expect-error because div can't accept href */}
<DynamicLink
key={name}
title={name}
className="flex items-center justify-start overflow-hidden rounded bg-black/10 pb-2.5 pl-3 pr-5 pt-1 text-zinc-400 transition-colors hover:bg-zinc-500/10 hover:text-zinc-50"
{...linkProps}
>
<div className="flex h-full w-14 items-center justify-center pr-5">
<i
className={cn(
icon ?? "fa-heart",
"fa-solid text-3xl text-opacity-80 saturate-0",
)}
></i>
</div>
<div className="overflow-hidden">
<span className="text-sm md:text-base lg:text-lg">
{name}
</span>
<p
className="truncate text-xs opacity-70 md:text-sm lg:text-base"
title={description}
>
{description}
</p>
</div>
</DynamicLink>
</div>
);
})}
</div>
</AppWrapper>
);
}
-17
View File
@@ -1,17 +0,0 @@
"use client";
import { Analytics } from "@vercel/analytics/react";
import { Provider as BalancerProvider } from "react-wrap-balancer";
import { Theme } from "@radix-ui/themes";
export function Providers({ children }: { children: React.ReactNode }) {
return (
// @ts-expect-error - Radix UI Themes has React 19 type compatibility issues
<Theme appearance="dark">
<BalancerProvider>
{children}
<Analytics />
</BalancerProvider>
</Theme>
);
}
-36
View File
@@ -1,36 +0,0 @@
import { getPayload } from "payload";
import config from "../../../payload.config";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic"; // Don't prerender at build time
type Metadata = {
tagline: string;
resume: {
id: string;
url: string;
filename: string;
};
resumeFilename?: string;
};
export default async function ResumePage() {
try {
const payloadConfig = await config;
const payload = await getPayload({ config: payloadConfig });
// @ts-ignore - Globals will be typed after first database connection
const metadata = (await payload.findGlobal({
slug: "metadata",
})) as Metadata;
if (!metadata.resume?.url) {
throw new Error("Resume URL not found");
}
redirect(metadata.resume.url);
} catch (error) {
console.error("Failed to acquire resume asset URL", error);
throw new Error(`Failed to acquire resume (${error})`);
}
}
-164
View File
@@ -1,164 +0,0 @@
:root {
--font-mono: "Roboto Mono", monospace;
}
* {
box-sizing: border-box;
}
html {
font-size: 18px;
line-height: 32px;
background: rgb(0, 0, 0);
-webkit-font-smoothing: antialiased;
}
html,
body,
#app {
height: 100%;
}
body {
font-family: system-ui;
font-size: 18px;
line-height: 32px;
margin: 0;
color: rgb(1000, 1000, 1000);
@media (max-width: 1024px) {
font-size: 15px;
line-height: 24px;
}
}
img {
max-width: 100%;
height: auto;
display: block;
}
h1 {
margin: 40px 0;
font-size: 64px;
line-height: 70px;
font-weight: bold;
@media (max-width: 1024px) {
margin: 24px 0;
font-size: 42px;
line-height: 42px;
}
@media (max-width: 768px) {
font-size: 38px;
line-height: 38px;
}
@media (max-width: 400px) {
font-size: 32px;
line-height: 32px;
}
}
p {
margin: 24px 0;
@media (max-width: 1024px) {
margin: calc(var(--base) * 0.75) 0;
}
}
a {
color: currentColor;
&:focus {
opacity: 0.8;
outline: none;
}
&:active {
opacity: 0.7;
outline: none;
}
}
svg {
vertical-align: middle;
}
.home {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: 100vh;
padding: 45px;
max-width: 1024px;
margin: 0 auto;
overflow: hidden;
@media (max-width: 400px) {
padding: 24px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-grow: 1;
h1 {
text-align: center;
}
}
.links {
display: flex;
align-items: center;
gap: 12px;
a {
text-decoration: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
.admin {
color: rgb(0, 0, 0);
background: rgb(1000, 1000, 1000);
border: 1px solid rgb(0, 0, 0);
}
.docs {
color: rgb(1000, 1000, 1000);
background: rgb(0, 0, 0);
border: 1px solid rgb(1000, 1000, 1000);
}
}
.footer {
display: flex;
align-items: center;
gap: 8px;
@media (max-width: 1024px) {
flex-direction: column;
gap: 6px;
}
p {
margin: 0;
}
.codeLink {
text-decoration: none;
padding: 0 0.5rem;
background: rgb(60, 60, 60);
border-radius: 4px;
}
}
}
@@ -1,27 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from "next";
import config from "../../../../payload.config";
import { NotFoundPage, generatePageMetadata } from "@payloadcms/next/views";
import { importMap } from "../importMap";
type Args = {
params: Promise<{
segments: string[];
}>;
searchParams: Promise<{
[key: string]: string | string[];
}>;
};
export const generateMetadata = ({
params,
searchParams,
}: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams });
const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, params, searchParams, importMap });
export default NotFound;
@@ -1,27 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from "next";
import config from "../../../../payload.config";
import { RootPage, generatePageMetadata } from "@payloadcms/next/views";
import { importMap } from "../importMap";
type Args = {
params: Promise<{
segments: string[];
}>;
searchParams: Promise<{
[key: string]: string | string[];
}>;
};
export const generateMetadata = ({
params,
searchParams,
}: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams });
const Page = ({ params, searchParams }: Args) =>
RootPage({ config, params, searchParams, importMap });
export default Page;
-5
View File
@@ -1,5 +0,0 @@
export const importMap = {
}
-19
View File
@@ -1,19 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from "../../../../payload.config";
import "@payloadcms/next/css";
import {
REST_DELETE,
REST_GET,
REST_OPTIONS,
REST_PATCH,
REST_POST,
REST_PUT,
} from "@payloadcms/next/routes";
export const GET = REST_GET(config);
export const POST = REST_POST(config);
export const DELETE = REST_DELETE(config);
export const PATCH = REST_PATCH(config);
export const PUT = REST_PUT(config);
export const OPTIONS = REST_OPTIONS(config);
@@ -1,7 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from "../../../../payload.config";
import "@payloadcms/next/css";
import { GRAPHQL_PLAYGROUND_GET } from "@payloadcms/next/routes";
export const GET = GRAPHQL_PLAYGROUND_GET(config);
-8
View File
@@ -1,8 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from "../../../../payload.config";
import { GRAPHQL_POST, REST_OPTIONS } from "@payloadcms/next/routes";
export const POST = GRAPHQL_POST(config);
export const OPTIONS = REST_OPTIONS(config);
-34
View File
@@ -1,34 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from "../../payload.config";
import "@payloadcms/next/css";
import type { ServerFunctionClient } from "payload";
import { handleServerFunctions, RootLayout } from "@payloadcms/next/layouts";
import React from "react";
import { importMap } from "./admin/importMap.js";
type Args = {
children: React.ReactNode;
};
const serverFunction: ServerFunctionClient = async function (args) {
"use server";
return handleServerFunctions({
...args,
config,
importMap,
});
};
const Layout = ({ children }: Args) => (
<RootLayout
config={config}
importMap={importMap}
serverFunction={serverFunction}
>
{children}
</RootLayout>
);
export default Layout;
-299
View File
@@ -1,299 +0,0 @@
import { NextResponse } from "next/server";
import { getPayload } from "payload";
import config from "../../../../payload.config";
import { Octokit } from "@octokit/core";
const octokit = new Octokit({
auth: process.env.GITHUB_API_TOKEN,
request: {
fetch: (url: string | URL, options: RequestInit) => {
console.log(`${options.method} ${url}`);
return fetch(url, options);
},
},
});
type ProjectResult = {
id: number;
previousUpdated: Date | null;
latestUpdated: Date | null;
};
function getRepository(url: string): [string, string] | null {
const pattern = /github\.com\/([^/]+)\/([^/]+)/;
const match = url.match(pattern);
if (match === null) return null;
return [match[1]!, match[2]!];
}
function isFulfilled<T>(
result: PromiseSettledResult<T>,
): result is PromiseFulfilledResult<T> {
return result.status === "fulfilled";
}
function isRejected<T>(
result: PromiseSettledResult<T>,
): result is PromiseRejectedResult {
return result.status === "rejected";
}
async function handleProject({
id: project_id,
urls,
date_updated: previousUpdated,
}: {
id: number;
urls: string[];
date_updated: Date | null;
}): Promise<ProjectResult> {
const allBranches = await Promise.all(
urls.map(async (url) => {
const details = getRepository(url);
if (!details) {
return [];
}
const [owner, repo] = details;
const branches = await octokit.request(
"GET /repos/{owner}/{repo}/branches",
{
owner,
repo,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
},
);
return branches.data.map((branch) => ({
branch: branch.name,
owner: owner,
repo: repo,
}));
}),
);
const latestCommits = allBranches
.flat()
.map(async ({ owner, repo, branch }) => {
const commits = await octokit.request(
"GET /repos/{owner}/{repo}/commits",
{
owner,
repo,
sha: branch,
per_page: 1,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
},
);
const latestCommit = commits.data[0];
if (latestCommit == null) {
console.warn({
target: `${owner}/${repo}@${branch}`,
message: "No commits available",
});
return null;
}
if (latestCommit.commit.author == null) {
console.warn({
target: `${owner}/${repo}@${branch}`,
sha: latestCommit.sha,
commit: latestCommit.commit.message,
url: latestCommit.html_url,
message: "No author available",
});
return null;
} else if (latestCommit.commit.author.date == null) {
console.warn({
target: `${owner}/${repo}@${branch}`,
sha: latestCommit.sha,
commit: latestCommit.commit.message,
url: latestCommit.html_url,
message: "No date available",
});
return null;
}
return new Date(latestCommit.commit.author.date);
});
const results = await Promise.allSettled(latestCommits);
results.filter(isRejected).forEach((result) => {
console.error("Failed to fetch latest commit date", result.reason);
});
const latestUpdated = results
.filter(isFulfilled)
.map((v) => v.value)
.filter((v) => v != null)
.reduce((previous: Date | null, current: Date) => {
if (previous == null) return current;
return current > previous ? current : previous;
}, null);
if (latestUpdated == null) {
console.error("Unable to acquire the latest commit date for project");
return {
id: project_id,
previousUpdated,
latestUpdated: null,
};
}
if (latestUpdated != null && latestUpdated < new Date("2015-01-01")) {
console.error("Invalid commit date acquired", latestUpdated);
return {
id: project_id,
previousUpdated,
latestUpdated: null,
};
}
const result = { id: project_id, previousUpdated, latestUpdated: null };
if (previousUpdated == null || latestUpdated > previousUpdated) {
const payloadConfig = await config;
const payload = await getPayload({ config: payloadConfig });
await payload.update({
collection: "projects",
id: project_id,
data: {
lastUpdated: latestUpdated.toISOString(),
},
});
return {
...result,
latestUpdated,
};
}
return result;
}
export async function GET(req: Request) {
const { CRON_SECRET, GITHUB_API_TOKEN } = process.env;
if (!GITHUB_API_TOKEN) {
return NextResponse.json(
{
error: "Service unavailable",
message: "GITHUB_API_TOKEN not configured",
},
{ status: 503 },
);
}
if (process.env.NODE_ENV === "production") {
if (!CRON_SECRET) {
return NextResponse.json(
{ error: "Server misconfiguration" },
{ status: 500 },
);
}
const authHeader = req.headers.get("authorization");
const url = new URL(req.url);
const secretQueryParam = url.searchParams.get("secret");
if (
authHeader !== `Bearer ${CRON_SECRET}` &&
secretQueryParam !== CRON_SECRET
) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
let request_count = 0;
octokit.hook.before("request", async () => {
request_count++;
});
try {
const payloadConfig = await config;
const payload = await getPayload({ config: payloadConfig });
const { docs: projects } = await payload.find({
collection: "projects",
});
const { docs: allLinks } = await payload.find({
collection: "links",
});
const eligibleProjects = projects
.map((project) => {
if (!project.autocheckUpdated) return null;
const urls = allLinks
.filter((link) => {
const projectId =
typeof link.project === "number" ? link.project : link.project.id;
return projectId === project.id;
})
.map((link) => link.url)
.filter((url) => url.includes("github.com"));
if (urls.length === 0) return null;
return {
id: project.id,
name: project.name,
date_updated:
project.lastUpdated != null ? new Date(project.lastUpdated) : null,
urls,
};
})
.filter((project) => project !== null);
const projectPromises = eligibleProjects.map((project) =>
handleProject({
id: project.id,
urls: project.urls,
date_updated: project.date_updated,
}),
);
const results = await Promise.allSettled(projectPromises);
const isFailed = results.filter(isRejected).length > results.length * 0.1;
type Response = {
request_count: number;
errors: { project_name: string; reason: string }[];
ignored: number[];
changed: { project_name: number; previous: Date | null; latest: Date }[];
};
const fulfilled = results.filter(isFulfilled);
const response: Response = {
request_count,
errors: results.filter(isRejected).map((r) => ({
project_name: "unknown",
reason: r.reason,
})),
ignored: fulfilled
.filter((r) => r.value.latestUpdated == null)
.map((r) => r.value.id),
changed: fulfilled
.filter((r) => r.value.latestUpdated != null)
.map((r) => ({
project_name: r.value.id,
previous: r.value.previousUpdated,
latest: r.value.latestUpdated!,
})),
};
return NextResponse.json(response, { status: !isFailed ? 200 : 500 });
} catch (error) {
return NextResponse.json({ error }, { status: 500 });
}
}
-40
View File
@@ -1,40 +0,0 @@
import { getPayload } from "payload";
import config from "../../../payload.config";
import { NextResponse } from "next/server";
export async function GET(req: Request) {
const healthcheckSecret = process.env.HEALTHCHECK_SECRET;
if (!healthcheckSecret) {
return NextResponse.json(
{
error: "Service unavailable",
message: "HEALTHCHECK_SECRET not configured",
},
{ status: 503 },
);
}
const secret = req.headers.get("authorization");
if (secret !== healthcheckSecret) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
try {
// Try a simple Payload API call (fetch one project)
const payloadConfig = await config;
const payload = await getPayload({ config: payloadConfig });
await payload.find({
collection: "projects",
limit: 1,
});
return NextResponse.json({ status: "ok" }, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: "Payload CMS unhealthy", details: String(error) },
{ status: 500 },
);
}
}
-84
View File
@@ -1,84 +0,0 @@
import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
const requestSchema = z.object({
collection: z.string(),
doc: z.object({
id: z.string().or(z.number()),
}),
});
function getPathsToRevalidate(collection: string): string[] {
switch (collection) {
case "projects":
return ["/projects"];
case "metadata":
return ["/"];
case "technologies":
return ["/projects"];
case "links":
return ["/projects"];
default:
return [];
}
}
export async function POST(req: Request) {
const revalidateKey = process.env.PAYLOAD_REVALIDATE_KEY;
const authHeader = req.headers.get("authorization");
if (!authHeader || authHeader !== `Bearer ${revalidateKey}`) {
return NextResponse.json({ message: "Invalid token" }, { status: 401 });
}
try {
const body = await req.json();
const { success, data, error } = requestSchema.safeParse(body);
if (!success) {
console.error({ message: "Invalid JSON body", error });
return NextResponse.json(
{ message: "Invalid JSON body", error },
{ status: 400 },
);
}
const paths = getPathsToRevalidate(data.collection);
if (paths.length === 0) {
return NextResponse.json(
{ revalidated: false, message: "No paths to revalidate" },
{ status: 404 },
);
}
// Revalidate all paths
try {
for (const path of paths) {
revalidatePath(path);
}
} catch (error) {
console.error({ message: "Error while revalidating", error });
return NextResponse.json(
{
revalidated: false,
message: "Error while revalidating",
paths,
},
{ status: 500 },
);
}
return NextResponse.json({ revalidated: true, paths }, { status: 200 });
} catch (error) {
console.error({
message: "Error while preparing to revalidate",
error,
});
return NextResponse.json(
{ message: "Error revalidating" },
{ status: 500 },
);
}
}
-31
View File
@@ -1,31 +0,0 @@
import type { CollectionConfig } from "payload";
export const Links: CollectionConfig = {
slug: "links",
admin: {
useAsTitle: "url",
},
fields: [
{
name: "url",
type: "text",
required: true,
label: "URL",
},
{
name: "icon",
type: "text",
label: "Icon (FontAwesome class)",
},
{
name: "description",
type: "text",
},
{
name: "project",
type: "relationship",
relationTo: "projects",
required: true,
},
],
};
-16
View File
@@ -1,16 +0,0 @@
import type { CollectionConfig } from "payload";
export const Media: CollectionConfig = {
slug: "media",
access: {
read: () => true,
},
fields: [
{
name: "alt",
type: "text",
required: true,
},
],
upload: true,
};
-92
View File
@@ -1,92 +0,0 @@
import type { CollectionConfig } from "payload";
export const Projects: CollectionConfig = {
slug: "projects",
admin: {
useAsTitle: "name",
defaultColumns: ["name", "featured", "status", "updatedAt"],
},
fields: [
{
name: "name",
type: "text",
required: true,
},
{
name: "description",
type: "textarea",
required: true,
},
{
name: "shortDescription",
type: "text",
label: "Short Description",
required: true,
},
{
name: "icon",
type: "text",
label: "Icon (FontAwesome class)",
},
{
name: "status",
type: "select",
options: [
{ label: "Draft", value: "draft" },
{ label: "Published", value: "published" },
{ label: "Archived", value: "archived" },
],
defaultValue: "draft",
required: true,
},
{
name: "featured",
type: "checkbox",
label: "Featured Project",
defaultValue: false,
},
{
name: "autocheckUpdated",
type: "checkbox",
label: "Auto-check for GitHub updates",
defaultValue: false,
admin: {
description:
"Automatically check GitHub for latest commits and update lastUpdated field",
},
},
{
name: "lastUpdated",
type: "date",
label: "Last Updated",
admin: {
description:
"Automatically updated by cron job based on GitHub commits",
date: {
displayFormat: "yyyy-MM-dd HH:mm:ss",
},
},
},
{
name: "wakatimeOffset",
type: "number",
label: "WakaTime Offset",
admin: {
description: "Offset for WakaTime fetched data (optional)",
},
},
{
name: "bannerImage",
type: "upload",
relationTo: "media",
label: "Banner Image",
},
{
name: "technologies",
type: "relationship",
relationTo: "technologies",
hasMany: true,
label: "Technologies Used",
},
],
};
-20
View File
@@ -1,20 +0,0 @@
import type { CollectionConfig } from "payload";
export const Technologies: CollectionConfig = {
slug: "technologies",
admin: {
useAsTitle: "name",
},
fields: [
{
name: "name",
type: "text",
required: true,
},
{
name: "url",
type: "text",
label: "URL",
},
],
};
-13
View File
@@ -1,13 +0,0 @@
import type { CollectionConfig } from "payload";
export const Users: CollectionConfig = {
slug: "users",
admin: {
useAsTitle: "email",
},
auth: true,
fields: [
// Email added by default
// Add more fields as needed
],
};
-30
View File
@@ -1,30 +0,0 @@
"use client";
import { cn } from "@/utils/helpers";
import dynamic from "next/dynamic";
import type { FunctionComponent, ReactNode } from "react";
type WrapperProps = {
className?: string;
dotsClassName?: string;
children?: ReactNode;
};
const DotsDynamic = dynamic(() => import("@/components/Dots"), { ssr: false });
const AppWrapper: FunctionComponent<WrapperProps> = ({
children,
className,
dotsClassName,
}: WrapperProps) => {
return (
<main
className={cn("relative min-h-screen bg-black text-zinc-50", className)}
>
<DotsDynamic className={dotsClassName} />
{children}
</main>
);
};
export default AppWrapper;
-31
View File
@@ -1,31 +0,0 @@
import Image, { ImageProps } from "next/image";
import { useMemo, useState } from "react";
type DependentProps = {
className?: string | ((loaded: boolean) => string);
};
type DependentImageProps = Omit<ImageProps, "className"> & DependentProps;
const DependentImage = (props: DependentImageProps) => {
const [loaded, setLoaded] = useState(false);
const { className } = props;
const renderedClassName = useMemo(() => {
if (className === undefined) return "";
if (typeof className === "function") return className(loaded);
return className;
}, [loaded, className]);
return (
<Image
{...props}
className={renderedClassName}
alt="no"
onLoad={() => {
setLoaded(true);
}}
/>
);
};
export default DependentImage;
-139
View File
@@ -1,139 +0,0 @@
import { cn } from "@/utils/helpers";
import { p5i, P5I } from "p5i";
import React, { useEffect, useRef } from "react";
interface DotsProps {
className?: string;
}
const Dots = ({ className }: DotsProps) => {
const canvasRef = useRef<HTMLDivElement | null>(null);
const w = useRef(window.innerWidth);
const h = useRef(window.innerHeight);
const {
mount,
unmount,
background,
stroke,
noise,
resizeCanvas,
cos,
sin,
TWO_PI,
} = p5i();
const offsetY = window.scrollY;
const SCALE = 400;
const LENGTH = 3;
const SPACING = 20;
const TARGET_FRAMERATE = 5;
const TIMESCALE = 2;
const OPACITY = 0.7;
function getForceOnPoint(x: number, y: number, z: number) {
return (noise(x / SCALE, y / SCALE, z) - 0.5) * 2 * TWO_PI;
}
const pointIds = new Set<string>();
const points: { x: number; y: number; opacity: number }[] = [];
function addPoints() {
for (let x = -SPACING / 2; x < w.current + SPACING; x += SPACING) {
for (
let y = -SPACING / 2;
y < h.current + offsetY + SPACING;
y += SPACING
) {
const id = `${x}-${y}`;
if (pointIds.has(id)) continue;
pointIds.add(id);
points.push({ x, y, opacity: Math.random() * 0.5 + 0.5 });
}
}
}
function setup({
createCanvas,
stroke,
frameRate,
background,
noFill,
noiseSeed,
}: P5I) {
createCanvas(w.current, h.current);
background("#000000");
stroke("rgba(170, 170, 170, 0.05)");
noFill();
frameRate(TARGET_FRAMERATE);
noiseSeed(Date.now());
addPoints();
}
function draw({ circle, frameCount }: P5I) {
background("#000000");
const t = (frameCount / 80) * TIMESCALE;
// if (frameCount % 10000) console.log(frameRate());
for (const p of points) {
const rad = getForceOnPoint(p.x, p.y, t);
const length = (noise(p.x / SCALE, p.y / SCALE, t * 2) + 0.5) * LENGTH;
const nx = p.x + cos(rad) * length;
const ny = p.y + sin(rad) * length;
// const center_distance = Math.sqrt((x - w / 2) ** 2 + (y - h / 2) ** 2);
// if (center_distance < 350)
// opacity = 0;
// opacity =
stroke(
200,
200,
200,
(Math.abs(cos(rad)) * 0.8 + 0.1) * p.opacity * 255 * OPACITY,
);
circle(nx, ny - offsetY, 1);
}
}
function restart() {
if (canvasRef.current) {
mount(canvasRef.current, { setup, draw });
}
}
useEffect(() => {
restart();
const handleResize = () => {
w.current = window.innerWidth;
h.current = window.innerHeight;
resizeCanvas(w.current, h.current);
addPoints();
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
unmount();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div
ref={canvasRef}
className={cn(
"pointer-events-none fixed bottom-0 left-0 right-0 top-0 z-0 opacity-0",
className,
)}
/>
);
};
export default Dots;
-19
View File
@@ -1,19 +0,0 @@
import { FunctionComponent } from "react";
type SteppedSpanProps = {
children: string;
};
const SteppedSpan: FunctionComponent<SteppedSpanProps> = ({
children,
}: SteppedSpanProps) => {
return (
<div className="stepped">
{children.split("").map((char: string, index) => {
return <span key={index}>{char}</span>;
})}
</div>
);
};
export default SteppedSpan;
-35
View File
@@ -1,35 +0,0 @@
// @ts-check
import { clientEnv, clientSchema } from "./schema.mjs";
const _clientEnv = clientSchema.safeParse(clientEnv);
export const formatErrors = (
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
errors,
) =>
Object.entries(errors)
.map(([name, value]) => {
if (value && "_errors" in value)
return `${name}: ${value._errors.join(", ")}\n`;
})
.filter(Boolean);
if (!_clientEnv.success) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(_clientEnv.error.format()),
);
throw new Error("Invalid environment variables");
}
for (let key of Object.keys(_clientEnv.data)) {
if (!key.startsWith("NEXT_PUBLIC_")) {
console.warn(
`❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'`,
);
throw new Error("Invalid public environment variable name");
}
}
export const env = _clientEnv.data;
-45
View File
@@ -1,45 +0,0 @@
// @ts-check
import { z } from "zod";
/**
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
export const serverSchema = z.object({
CRON_SECRET: z.string().optional(),
GITHUB_API_TOKEN: z.string().optional(),
PAYLOAD_SECRET: z
.string()
.default("dev-secret-change-in-production-immediately"),
DATABASE_URI: z
.string()
.default(
"postgresql://xevion:xevion_dev_password@localhost:5432/xevion_dev",
),
PAYLOAD_REVALIDATE_KEY: z.string().optional(),
HEALTHCHECK_SECRET: z.string().optional(),
NODE_ENV: z.enum(["development", "test", "production"]),
TITLE: z.preprocess((value) => {
if (value === undefined || value === "") return null;
return value;
}, z.string().nullable()),
});
/**
* Specify your client-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
* To expose them to the client, prefix them with `NEXT_PUBLIC_`.
*/
export const clientSchema = z.object({
// NEXT_PUBLIC_CLIENTVAR: z.string(),
});
/**
* You can't destruct `process.env` as a regular object, so you have to do
* it manually here. This is because Next.js evaluates this at build time,
* and only used environment variables are included in the build.
* @type {{ [k in keyof z.infer<typeof clientSchema>]: z.infer<typeof clientSchema>[k] | undefined }}
*/
export const clientEnv = {
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
};
-52
View File
@@ -1,52 +0,0 @@
// @ts-check
/**
* This file is included in `/next.config.mjs` which ensures the app isn't built with invalid env vars.
* It has to be a `.mjs`-file to be imported there.
*/
import { serverSchema } from "./schema.mjs";
import { env as clientEnv, formatErrors } from "./client.mjs";
const _serverEnv = serverSchema.safeParse(process.env);
if (!_serverEnv.success) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(_serverEnv.error.format()),
);
throw new Error("Invalid environment variables");
}
for (let key of Object.keys(_serverEnv.data)) {
if (key.startsWith("NEXT_PUBLIC_")) {
console.warn("❌ You are exposing a server-side env-variable:", key);
throw new Error("You are exposing a server-side env-variable");
}
}
// Production safety checks
if (process.env.NODE_ENV === "production") {
if (
_serverEnv.data.PAYLOAD_SECRET ===
"dev-secret-change-in-production-immediately"
) {
throw new Error("PAYLOAD_SECRET must be explicitly set in production");
}
if (_serverEnv.data.DATABASE_URI?.includes("xevion_dev_password")) {
throw new Error("DATABASE_URI must be explicitly set in production");
}
}
// Development warnings for missing optional secrets
if (process.env.NODE_ENV !== "production") {
const missing = [];
if (!_serverEnv.data.GITHUB_API_TOKEN) missing.push("GITHUB_API_TOKEN");
if (!_serverEnv.data.HEALTHCHECK_SECRET) missing.push("HEALTHCHECK_SECRET");
if (!_serverEnv.data.CRON_SECRET) missing.push("CRON_SECRET");
if (missing.length > 0) {
console.warn(`Environment variables missing: [${missing.join(", ")}]`);
}
}
export const env = { ..._serverEnv.data, ...clientEnv };
-32
View File
@@ -1,32 +0,0 @@
import type { GlobalConfig } from "payload";
export const Metadata: GlobalConfig = {
slug: "metadata",
access: {
read: () => true,
},
fields: [
{
name: "tagline",
type: "textarea",
required: true,
label: "Site Tagline",
},
{
name: "resume",
type: "upload",
relationTo: "media",
required: true,
label: "Resume File",
},
{
name: "resumeFilename",
type: "text",
label: "Resume Filename Override",
admin: {
description:
'Optional: Override the filename for the resume (e.g., "resume.pdf")',
},
},
],
};
-617
View File
@@ -1,617 +0,0 @@
/* tslint: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 type {} from "@payloadcms/db-postgres";
import {
pgTable,
index,
uniqueIndex,
foreignKey,
integer,
varchar,
timestamp,
serial,
numeric,
boolean,
jsonb,
pgEnum,
} from "@payloadcms/db-postgres/drizzle/pg-core";
import { sql, relations } from "@payloadcms/db-postgres/drizzle";
export const enum_projects_status = pgEnum("enum_projects_status", [
"draft",
"published",
"archived",
]);
export const users_sessions = pgTable(
"users_sessions",
{
_order: integer("_order").notNull(),
_parentID: integer("_parent_id").notNull(),
id: varchar("id").primaryKey(),
createdAt: timestamp("created_at", {
mode: "string",
withTimezone: true,
precision: 3,
}),
expiresAt: timestamp("expires_at", {
mode: "string",
withTimezone: true,
precision: 3,
}).notNull(),
},
(columns) => [
index("users_sessions_order_idx").on(columns._order),
index("users_sessions_parent_id_idx").on(columns._parentID),
foreignKey({
columns: [columns["_parentID"]],
foreignColumns: [users.id],
name: "users_sessions_parent_id_fk",
}).onDelete("cascade"),
],
);
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", { mode: "number" }).default(0),
lockUntil: timestamp("lock_until", {
mode: "string",
withTimezone: true,
precision: 3,
}),
},
(columns) => [
index("users_updated_at_idx").on(columns.updatedAt),
index("users_created_at_idx").on(columns.createdAt),
uniqueIndex("users_email_idx").on(columns.email),
],
);
export const media = pgTable(
"media",
{
id: serial("id").primaryKey(),
alt: varchar("alt").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(),
url: varchar("url"),
thumbnailURL: varchar("thumbnail_u_r_l"),
filename: varchar("filename"),
mimeType: varchar("mime_type"),
filesize: numeric("filesize", { mode: "number" }),
width: numeric("width", { mode: "number" }),
height: numeric("height", { mode: "number" }),
focalX: numeric("focal_x", { mode: "number" }),
focalY: numeric("focal_y", { mode: "number" }),
},
(columns) => [
index("media_updated_at_idx").on(columns.updatedAt),
index("media_created_at_idx").on(columns.createdAt),
uniqueIndex("media_filename_idx").on(columns.filename),
],
);
export const projects = pgTable(
"projects",
{
id: serial("id").primaryKey(),
name: varchar("name").notNull(),
description: varchar("description").notNull(),
shortDescription: varchar("short_description").notNull(),
icon: varchar("icon"),
status: enum_projects_status("status").notNull().default("draft"),
featured: boolean("featured").default(false),
autocheckUpdated: boolean("autocheck_updated").default(false),
lastUpdated: timestamp("last_updated", {
mode: "string",
withTimezone: true,
precision: 3,
}),
wakatimeOffset: numeric("wakatime_offset", { mode: "number" }),
bannerImage: integer("banner_image_id").references(() => media.id, {
onDelete: "set null",
}),
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) => [
index("projects_banner_image_idx").on(columns.bannerImage),
index("projects_updated_at_idx").on(columns.updatedAt),
index("projects_created_at_idx").on(columns.createdAt),
],
);
export const projects_rels = pgTable(
"projects_rels",
{
id: serial("id").primaryKey(),
order: integer("order"),
parent: integer("parent_id").notNull(),
path: varchar("path").notNull(),
technologiesID: integer("technologies_id"),
},
(columns) => [
index("projects_rels_order_idx").on(columns.order),
index("projects_rels_parent_idx").on(columns.parent),
index("projects_rels_path_idx").on(columns.path),
index("projects_rels_technologies_id_idx").on(columns.technologiesID),
foreignKey({
columns: [columns["parent"]],
foreignColumns: [projects.id],
name: "projects_rels_parent_fk",
}).onDelete("cascade"),
foreignKey({
columns: [columns["technologiesID"]],
foreignColumns: [technologies.id],
name: "projects_rels_technologies_fk",
}).onDelete("cascade"),
],
);
export const technologies = pgTable(
"technologies",
{
id: serial("id").primaryKey(),
name: varchar("name").notNull(),
url: varchar("url"),
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) => [
index("technologies_updated_at_idx").on(columns.updatedAt),
index("technologies_created_at_idx").on(columns.createdAt),
],
);
export const links = pgTable(
"links",
{
id: serial("id").primaryKey(),
url: varchar("url").notNull(),
icon: varchar("icon"),
description: varchar("description"),
project: integer("project_id")
.notNull()
.references(() => projects.id, {
onDelete: "set null",
}),
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) => [
index("links_project_idx").on(columns.project),
index("links_updated_at_idx").on(columns.updatedAt),
index("links_created_at_idx").on(columns.createdAt),
],
);
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) => [
index("payload_locked_documents_global_slug_idx").on(columns.globalSlug),
index("payload_locked_documents_updated_at_idx").on(columns.updatedAt),
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(),
usersID: integer("users_id"),
mediaID: integer("media_id"),
projectsID: integer("projects_id"),
technologiesID: integer("technologies_id"),
linksID: integer("links_id"),
},
(columns) => [
index("payload_locked_documents_rels_order_idx").on(columns.order),
index("payload_locked_documents_rels_parent_idx").on(columns.parent),
index("payload_locked_documents_rels_path_idx").on(columns.path),
index("payload_locked_documents_rels_users_id_idx").on(columns.usersID),
index("payload_locked_documents_rels_media_id_idx").on(columns.mediaID),
index("payload_locked_documents_rels_projects_id_idx").on(
columns.projectsID,
),
index("payload_locked_documents_rels_technologies_id_idx").on(
columns.technologiesID,
),
index("payload_locked_documents_rels_links_id_idx").on(columns.linksID),
foreignKey({
columns: [columns["parent"]],
foreignColumns: [payload_locked_documents.id],
name: "payload_locked_documents_rels_parent_fk",
}).onDelete("cascade"),
foreignKey({
columns: [columns["usersID"]],
foreignColumns: [users.id],
name: "payload_locked_documents_rels_users_fk",
}).onDelete("cascade"),
foreignKey({
columns: [columns["mediaID"]],
foreignColumns: [media.id],
name: "payload_locked_documents_rels_media_fk",
}).onDelete("cascade"),
foreignKey({
columns: [columns["projectsID"]],
foreignColumns: [projects.id],
name: "payload_locked_documents_rels_projects_fk",
}).onDelete("cascade"),
foreignKey({
columns: [columns["technologiesID"]],
foreignColumns: [technologies.id],
name: "payload_locked_documents_rels_technologies_fk",
}).onDelete("cascade"),
foreignKey({
columns: [columns["linksID"]],
foreignColumns: [links.id],
name: "payload_locked_documents_rels_links_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) => [
index("payload_preferences_key_idx").on(columns.key),
index("payload_preferences_updated_at_idx").on(columns.updatedAt),
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) => [
index("payload_preferences_rels_order_idx").on(columns.order),
index("payload_preferences_rels_parent_idx").on(columns.parent),
index("payload_preferences_rels_path_idx").on(columns.path),
index("payload_preferences_rels_users_id_idx").on(columns.usersID),
foreignKey({
columns: [columns["parent"]],
foreignColumns: [payload_preferences.id],
name: "payload_preferences_rels_parent_fk",
}).onDelete("cascade"),
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", { mode: "number" }),
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) => [
index("payload_migrations_updated_at_idx").on(columns.updatedAt),
index("payload_migrations_created_at_idx").on(columns.createdAt),
],
);
export const metadata = pgTable(
"metadata",
{
id: serial("id").primaryKey(),
tagline: varchar("tagline").notNull(),
resume: integer("resume_id")
.notNull()
.references(() => media.id, {
onDelete: "set null",
}),
resumeFilename: varchar("resume_filename"),
updatedAt: timestamp("updated_at", {
mode: "string",
withTimezone: true,
precision: 3,
}),
createdAt: timestamp("created_at", {
mode: "string",
withTimezone: true,
precision: 3,
}),
},
(columns) => [index("metadata_resume_idx").on(columns.resume)],
);
export const relations_users_sessions = relations(
users_sessions,
({ one }) => ({
_parentID: one(users, {
fields: [users_sessions._parentID],
references: [users.id],
relationName: "sessions",
}),
}),
);
export const relations_users = relations(users, ({ many }) => ({
sessions: many(users_sessions, {
relationName: "sessions",
}),
}));
export const relations_media = relations(media, () => ({}));
export const relations_projects_rels = relations(projects_rels, ({ one }) => ({
parent: one(projects, {
fields: [projects_rels.parent],
references: [projects.id],
relationName: "_rels",
}),
technologiesID: one(technologies, {
fields: [projects_rels.technologiesID],
references: [technologies.id],
relationName: "technologies",
}),
}));
export const relations_projects = relations(projects, ({ one, many }) => ({
bannerImage: one(media, {
fields: [projects.bannerImage],
references: [media.id],
relationName: "bannerImage",
}),
_rels: many(projects_rels, {
relationName: "_rels",
}),
}));
export const relations_technologies = relations(technologies, () => ({}));
export const relations_links = relations(links, ({ one }) => ({
project: one(projects, {
fields: [links.project],
references: [projects.id],
relationName: "project",
}),
}));
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",
}),
usersID: one(users, {
fields: [payload_locked_documents_rels.usersID],
references: [users.id],
relationName: "users",
}),
mediaID: one(media, {
fields: [payload_locked_documents_rels.mediaID],
references: [media.id],
relationName: "media",
}),
projectsID: one(projects, {
fields: [payload_locked_documents_rels.projectsID],
references: [projects.id],
relationName: "projects",
}),
technologiesID: one(technologies, {
fields: [payload_locked_documents_rels.technologiesID],
references: [technologies.id],
relationName: "technologies",
}),
linksID: one(links, {
fields: [payload_locked_documents_rels.linksID],
references: [links.id],
relationName: "links",
}),
}),
);
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,
() => ({}),
);
export const relations_metadata = relations(metadata, ({ one }) => ({
resume: one(media, {
fields: [metadata.resume],
references: [media.id],
relationName: "resume",
}),
}));
type DatabaseSchema = {
enum_projects_status: typeof enum_projects_status;
users_sessions: typeof users_sessions;
users: typeof users;
media: typeof media;
projects: typeof projects;
projects_rels: typeof projects_rels;
technologies: typeof technologies;
links: typeof links;
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;
metadata: typeof metadata;
relations_users_sessions: typeof relations_users_sessions;
relations_users: typeof relations_users;
relations_media: typeof relations_media;
relations_projects_rels: typeof relations_projects_rels;
relations_projects: typeof relations_projects;
relations_technologies: typeof relations_technologies;
relations_links: typeof relations_links;
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;
relations_metadata: typeof relations_metadata;
};
declare module "@payloadcms/db-postgres" {
export interface GeneratedDatabaseSchema {
schema: DatabaseSchema;
}
}
-442
View File
@@ -1,442 +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:types` to regenerate this file.
*/
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config {
auth: {
users: UserAuthOperations;
};
blocks: {};
collections: {
users: User;
media: Media;
projects: Project;
technologies: Technology;
links: Link;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
media: MediaSelect<false> | MediaSelect<true>;
projects: ProjectsSelect<false> | ProjectsSelect<true>;
technologies: TechnologiesSelect<false> | TechnologiesSelect<true>;
links: LinksSelect<false> | LinksSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: number;
};
globals: {
metadata: Metadatum;
};
globalsSelect: {
metadata: MetadataSelect<false> | MetadataSelect<true>;
};
locale: null;
user: User & {
collection: 'users';
};
jobs: {
tasks: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: number;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media".
*/
export interface Media {
id: number;
alt: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "projects".
*/
export interface Project {
id: number;
name: string;
description: string;
shortDescription: string;
icon?: string | null;
status: 'draft' | 'published' | 'archived';
featured?: boolean | null;
/**
* Automatically check GitHub for latest commits and update lastUpdated field
*/
autocheckUpdated?: boolean | null;
/**
* Automatically updated by cron job based on GitHub commits
*/
lastUpdated?: string | null;
/**
* Offset for WakaTime fetched data (optional)
*/
wakatimeOffset?: number | null;
bannerImage?: (number | null) | Media;
technologies?: (number | Technology)[] | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "technologies".
*/
export interface Technology {
id: number;
name: string;
url?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "links".
*/
export interface Link {
id: number;
url: string;
icon?: string | null;
description?: string | null;
project: number | Project;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: number;
document?:
| ({
relationTo: 'users';
value: number | User;
} | null)
| ({
relationTo: 'media';
value: number | Media;
} | null)
| ({
relationTo: 'projects';
value: number | Project;
} | null)
| ({
relationTo: 'technologies';
value: number | Technology;
} | null)
| ({
relationTo: 'links';
value: number | Link;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: number | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: number;
user: {
relationTo: 'users';
value: number | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: number;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "media_select".
*/
export interface MediaSelect<T extends boolean = true> {
alt?: T;
updatedAt?: T;
createdAt?: T;
url?: T;
thumbnailURL?: T;
filename?: T;
mimeType?: T;
filesize?: T;
width?: T;
height?: T;
focalX?: T;
focalY?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "projects_select".
*/
export interface ProjectsSelect<T extends boolean = true> {
name?: T;
description?: T;
shortDescription?: T;
icon?: T;
status?: T;
featured?: T;
autocheckUpdated?: T;
lastUpdated?: T;
wakatimeOffset?: T;
bannerImage?: T;
technologies?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "technologies_select".
*/
export interface TechnologiesSelect<T extends boolean = true> {
name?: T;
url?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "links_select".
*/
export interface LinksSelect<T extends boolean = true> {
url?: T;
icon?: T;
description?: T;
project?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "metadata".
*/
export interface Metadatum {
id: number;
tagline: string;
resume: number | Media;
/**
* Optional: Override the filename for the resume (e.g., "resume.pdf")
*/
resumeFilename?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "metadata_select".
*/
export interface MetadataSelect<T extends boolean = true> {
tagline?: T;
resume?: T;
resumeFilename?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
export interface GeneratedTypes extends Config {}
}
-42
View File
@@ -1,42 +0,0 @@
// storage-adapter-import-placeholder
import { postgresAdapter } from "@payloadcms/db-postgres";
import { payloadCloudPlugin } from "@payloadcms/payload-cloud";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
import path from "path";
import { buildConfig } from "payload";
import { fileURLToPath } from "url";
import sharp from "sharp";
import { Users } from "./collections/Users";
import { Media } from "./collections/Media";
import { Projects } from "./collections/Projects";
import { Technologies } from "./collections/Technologies";
import { Links } from "./collections/Links";
import { Metadata } from "./globals/Metadata";
import { env } from "./env/server.mjs";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
export default buildConfig({
admin: {
user: Users.slug,
},
collections: [Users, Media, Projects, Technologies, Links],
globals: [Metadata],
editor: lexicalEditor(),
secret: env.PAYLOAD_SECRET,
typescript: {
outputFile: path.resolve(dirname, "payload-types.ts"),
},
db: postgresAdapter({
pool: {
connectionString: env.DATABASE_URI,
},
}),
sharp,
plugins: [
payloadCloudPlugin(),
// storage-adapter-placeholder
],
});
-120
View File
@@ -1,120 +0,0 @@
@import "tailwindcss";
@theme {
/* Custom colors */
--color-zinc-850: #1d1d20;
/* Custom font sizes */
--font-size-10xl: 10rem;
/* Drop shadows */
--drop-shadow-extreme: 0 0 50px black;
/* Font families */
--font-inter: "Inter", sans-serif;
--font-roboto: "Roboto", sans-serif;
--font-mono: "Roboto Mono", monospace;
--font-hanken: "Hanken Grotesk", sans-serif;
--font-schibsted: "Schibsted Grotesk", sans-serif;
/* Background images */
--background-image-gradient-radial: radial-gradient(
50% 50% at 50% 50%,
var(--tw-gradient-stops)
);
/* Animations */
--animate-bg-fast: fade 0.5s ease-in-out 0.5s forwards;
--animate-bg: fade 1.2s ease-in-out 1.1s forwards;
--animate-fade-in: fade-in 2.5s ease-in-out forwards;
--animate-title: title 3s ease-out forwards;
--animate-fade-left: fade-left 3s ease-in-out forwards;
--animate-fade-right: fade-right 3s ease-in-out forwards;
}
@keyframes fade {
0% {
opacity: 0%;
}
100% {
opacity: 100%;
}
}
@keyframes fade-in {
0% {
opacity: 0%;
}
75% {
opacity: 0%;
}
100% {
opacity: 100%;
}
}
@keyframes fade-left {
0% {
transform: translateX(100%);
opacity: 0%;
}
30% {
transform: translateX(0%);
opacity: 100%;
}
100% {
opacity: 0%;
}
}
@keyframes fade-right {
0% {
transform: translateX(-100%);
opacity: 0%;
}
30% {
transform: translateX(0%);
opacity: 100%;
}
100% {
opacity: 0%;
}
}
@keyframes title {
0% {
line-height: 0%;
letter-spacing: 0.25em;
opacity: 0;
}
25% {
line-height: 0%;
opacity: 0%;
}
80% {
opacity: 100%;
}
100% {
line-height: 100%;
opacity: 100%;
}
}
html,
body {
@apply font-inter overflow-x-hidden text-white;
}
.description {
hyphens: auto;
}
@media (min-width: 768px) {
.description {
hyphens: none;
}
}
body {
@apply h-full;
}
-22
View File
@@ -1,22 +0,0 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
const isClient = (): boolean => {
return typeof window !== "undefined";
};
const isServer = (): boolean => {
return !isClient();
};
const hoverableQuery: MediaQueryList | null = isClient()
? window.matchMedia("(hover: hover) and (pointer: fine)")
: null;
export function isHoverable() {
return hoverableQuery?.matches;
}
-21
View File
@@ -1,21 +0,0 @@
import { Github, ExternalLink, Link } from "lucide-react";
import type { LucideIcon } from "lucide-react";
// Promise.allSettled type guards
export const isFulfilled = <T>(
p: PromiseSettledResult<T>,
): p is PromiseFulfilledResult<T> => p.status === "fulfilled";
export const isRejected = <T>(
p: PromiseSettledResult<T>,
): p is PromiseRejectedResult => p.status === "rejected";
export const LinkIcons: Record<string, LucideIcon> = {
github: Github,
external: ExternalLink,
link: Link,
};
export type LinkIcon = {
icon: keyof typeof LinkIcons;
location: string;
newTab?: boolean;
};