fix(web): correct relative imports in opengraph-image routes
Some checks failed
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Failing after 8m32s
Build & Deploy / 🔍 Prepare (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Failing after 1m33s
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s

This commit is contained in:
2026-02-23 01:14:16 +01:00
parent 43564d1bba
commit 95a8b702fe
35 changed files with 387 additions and 366 deletions

View File

@@ -0,0 +1,4 @@
"use server";
import { handleServerFunctions as payloadHandleServerFunctions } from "@payloadcms/next/layouts";
export const handleServerFunctions = payloadHandleServerFunctions;

View File

@@ -1,75 +1,6 @@
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from "@payloadcms/richtext-lexical/rsc";
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from "@payloadcms/richtext-lexical/rsc";
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from "@payloadcms/richtext-lexical/rsc";
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from "@payloadcms/richtext-lexical/client";
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from "@payloadcms/next/rsc";
export const importMap = {
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell":
RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField":
RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent":
LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient":
InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient":
HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UploadFeatureClient":
UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient":
BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient":
RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#LinkFeatureClient":
LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient":
ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient":
OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient":
UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#IndentFeatureClient":
IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#AlignFeatureClient":
AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HeadingFeatureClient":
HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient":
ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient":
InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient":
SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient":
SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient":
StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient":
UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BoldFeatureClient":
BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient":
ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/next/rsc#CollectionCards":
CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1,
};

View File

@@ -3,8 +3,7 @@ import "@payloadcms/next/css";
import { RootLayout } from "@payloadcms/next/layouts";
import React from "react";
// @ts-expect-error - Export exists in JS but TS types are missing in this version
import { handleServerFunctions } from "@payloadcms/next/utilities";
import { handleServerFunctions } from "./actions";
import { importMap } from "./admin/importMap";
export default function Layout({ children }: { children: React.ReactNode }) {

View File

@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
import { OGImageTemplate } from "../../src/components/OGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../../src/lib/og-helper";
import { OGImageTemplate } from "../../../src/components/OGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../../../src/lib/og-helper";
export const size = OG_IMAGE_SIZE;
export const contentType = "image/png";

View File

@@ -1,8 +1,8 @@
"use client";
import Image from "next/image";
import { Section } from "../../src/components/Section";
import { Reveal } from "../../src/components/Reveal";
import { Section } from "@/src/components/Section";
import { Reveal } from "@/src/components/Reveal";
import {
ExperienceIllustration,
ResponsibilityIllustration,
@@ -11,8 +11,8 @@ import {
HeroLines,
ParticleNetwork,
GridLines,
} from "../../src/components/Landing";
import { Signature } from "../../src/components/Signature";
} from "@/src/components/Landing";
import { Signature } from "@/src/components/Signature";
import { Check } from "lucide-react";
import {
H1,
@@ -22,17 +22,17 @@ import {
BodyText,
Label,
MonoLabel,
} from "../../src/components/Typography";
import { Card, Container } from "../../src/components/Layout";
import { Button } from "../../src/components/Button";
import { IconList, IconListItem } from "../../src/components/IconList";
} from "@/src/components/Typography";
import { Card, Container } from "@/src/components/Layout";
import { Button } from "@/src/components/Button";
import { IconList, IconListItem } from "@/src/components/IconList";
import {
GradientMesh,
CodeSnippet,
AbstractCircuit,
} from "../../src/components/Effects";
import { getImgproxyUrl } from "../../src/utils/imgproxy";
import { Marker } from "../../src/components/Marker";
} from "@/src/components/Effects";
import { getImgproxyUrl } from "@/src/utils/imgproxy";
import { Marker } from "@/src/components/Marker";
export default function AboutPage() {
return (

View File

@@ -1,8 +1,8 @@
import { ImageResponse } from "next/og";
import { getAllPosts } from "../../../src/lib/posts";
import { blogThumbnails } from "../../../src/components/blog/blogThumbnails";
import { BlogOGImageTemplate } from "../../../src/components/BlogOGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../../../src/lib/og-helper";
import { getAllPosts } from "../../../../src/lib/posts";
import { blogThumbnails } from "../../../../src/components/blog/blogThumbnails";
import { BlogOGImageTemplate } from "../../../../src/components/BlogOGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../../../../src/lib/og-helper";
import * as fs from "node:fs/promises";
import * as path from "node:path";

View File

@@ -1,14 +1,14 @@
import * as React from "react";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getAllPosts } from "../../../src/lib/posts";
import { BlogPostHeader } from "../../../src/components/blog/BlogPostHeader";
import { Section } from "../../../src/components/Section";
import { Reveal } from "../../../src/components/Reveal";
import { BlogPostClient } from "../../../src/components/BlogPostClient";
import { TextSelectionShare } from "../../../src/components/TextSelectionShare";
import { BlogPostStickyBar } from "../../../src/components/blog/BlogPostStickyBar";
import { MDXContent } from "../../../src/components/MDXContent";
import { getAllPosts } from "@/src/lib/posts";
import { BlogPostHeader } from "@/src/components/blog/BlogPostHeader";
import { Section } from "@/src/components/Section";
import { Reveal } from "@/src/components/Reveal";
import { BlogPostClient } from "@/src/components/BlogPostClient";
import { TextSelectionShare } from "@/src/components/TextSelectionShare";
import { BlogPostStickyBar } from "@/src/components/blog/BlogPostStickyBar";
import { MDXContent } from "@/src/components/MDXContent";
export async function generateStaticParams() {
const allPosts = await getAllPosts();

View File

@@ -1,5 +1,5 @@
import { getAllPosts } from "../../src/lib/posts";
import { BlogClient } from "../../src/components/blog/BlogClient";
import { getAllPosts } from "@/src/lib/posts";
import { BlogClient } from "@/src/components/blog/BlogClient";
import type { Metadata } from "next";
export const metadata: Metadata = {

View File

@@ -2,8 +2,8 @@
import React from "react";
import { motion, useScroll, useTransform } from "framer-motion";
import { Section } from "../../../src/components/Section";
import { Reveal } from "../../../src/components/Reveal";
import { Section } from "@/src/components/Section";
import { Reveal } from "@/src/components/Reveal";
import {
H1,
H2,
@@ -12,11 +12,11 @@ import {
Label,
MonoLabel,
BodyText,
} from "../../../src/components/Typography";
import { BackgroundGrid, Container } from "../../../src/components/Layout";
} from "@/src/components/Typography";
import { BackgroundGrid, Container } from "@/src/components/Layout";
import Link from "next/link";
import { Button } from "../../../src/components/Button";
import { IframeSection } from "../../../src/components/IframeSection";
import { Button } from "@/src/components/Button";
import { IframeSection } from "@/src/components/IframeSection";
import {
Activity,
ArrowRight,
@@ -27,8 +27,8 @@ import {
Layers,
} from "lucide-react";
import { Marker } from "../../../src/components/Marker";
import { GlitchText } from "../../../src/components/GlitchText";
import { Marker } from "@/src/components/Marker";
import { GlitchText } from "@/src/components/GlitchText";
export default function KLZCablesCaseStudy() {
const { scrollYProgress } = useScroll();

View File

@@ -1,12 +1,12 @@
"use client";
import Image from "next/image";
import { Section } from "../../src/components/Section";
import { Reveal } from "../../src/components/Reveal";
import { H3, LeadText, Label, BodyText } from "../../src/components/Typography";
import { Card } from "../../src/components/Layout";
import { Button } from "../../src/components/Button";
import { AbstractCircuit } from "../../src/components/Effects";
import { Section } from "@/src/components/Section";
import { Reveal } from "@/src/components/Reveal";
import { H3, LeadText, Label, BodyText } from "@/src/components/Typography";
import { Card } from "@/src/components/Layout";
import { Button } from "@/src/components/Button";
import { AbstractCircuit } from "@/src/components/Effects";
import { ArrowRight } from "lucide-react";
import { motion } from "framer-motion";

View File

@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
import { OGImageTemplate } from "../../src/components/OGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../../src/lib/og-helper";
import { OGImageTemplate } from "../../../src/components/OGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../../../src/lib/og-helper";
export const size = OG_IMAGE_SIZE;
export const contentType = "image/png";

View File

@@ -1,6 +1,6 @@
import { Section } from "../../src/components/Section";
import { ContactForm } from "../../src/components/ContactForm";
import { AbstractCircuit } from "../../src/components/Effects";
import { Section } from "@/src/components/Section";
import { ContactForm } from "@/src/components/ContactForm";
import { AbstractCircuit } from "@/src/components/Effects";
export default function ContactPage() {
return (

View File

@@ -2,7 +2,7 @@
import { useEffect } from "react";
import * as Sentry from "@sentry/nextjs";
import { Button } from "../src/components/Button";
import { Button } from "@/src/components/Button";
export default function Error({
error,

View File

@@ -86,7 +86,6 @@
/* Components - Tailwind utility classes */
@layer components {
/* Legacy hooks required by tests */
.file-example {
@apply w-full;
@@ -291,7 +290,6 @@
/* Print styles */
@media print {
.floating-back-to-top,
.reading-progress-bar {
display: none !important;
@@ -350,30 +348,38 @@
}
.highlighter-yellow {
background: linear-gradient(135deg,
background: linear-gradient(
135deg,
rgba(255, 235, 59, 0.95) 0%,
rgba(255, 213, 79, 0.95) 100%);
rgba(255, 213, 79, 0.95) 100%
);
color: #3f2f00;
}
.highlighter-pink {
background: linear-gradient(135deg,
background: linear-gradient(
135deg,
rgba(255, 167, 209, 0.95) 0%,
rgba(255, 122, 175, 0.95) 100%);
rgba(255, 122, 175, 0.95) 100%
);
color: #3f0018;
}
.highlighter-green {
background: linear-gradient(135deg,
background: linear-gradient(
135deg,
rgba(129, 199, 132, 0.95) 0%,
rgba(102, 187, 106, 0.95) 100%);
rgba(102, 187, 106, 0.95) 100%
);
color: #002f0a;
}
.highlighter-blue {
background: linear-gradient(135deg,
background: linear-gradient(
135deg,
rgba(226, 232, 240, 0.95) 0%,
rgba(203, 213, 225, 0.95) 100%);
rgba(203, 213, 225, 0.95) 100%
);
color: #0f172a;
}
@@ -409,14 +415,17 @@
border-radius: 0.18em;
z-index: -1;
background: linear-gradient(180deg,
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0) 20%,
rgba(253, 230, 138, 0.7) 20%,
rgba(253, 230, 138, 0.7) 100%);
rgba(253, 230, 138, 0.7) 100%
);
transform-origin: left center;
transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg)) skewX(calc((var(--marker-seed, 0) - 3) * -0.25deg));
transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg))
skewX(calc((var(--marker-seed, 0) - 3) * -0.25deg));
filter: saturate(1.05);
}
@@ -431,24 +440,28 @@
border-radius: 0.18em;
z-index: -1;
background: linear-gradient(90deg,
background: linear-gradient(
90deg,
rgba(253, 230, 138, 0) 0%,
rgba(253, 230, 138, 0.6) 8%,
rgba(253, 230, 138, 0.55) 60%,
rgba(253, 230, 138, 0.35) 100%);
rgba(253, 230, 138, 0.35) 100%
);
opacity: 0.75;
mix-blend-mode: multiply;
transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg)) translateY(0.02em);
transform: rotate(calc((var(--marker-seed, 0) - 3) * 0.45deg))
translateY(0.02em);
mask-image: linear-gradient(180deg,
mask-image: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 100%);
rgba(0, 0, 0, 1) 100%
);
}
@media (prefers-reduced-motion: no-preference) {
.post-link:hover .marker-title::before,
.post-link:hover .marker-title::after {
filter: saturate(1.08) contrast(1.02);
@@ -711,7 +724,6 @@ pre:has(code[class*="language-"]) {
/* Gradient Mesh Blob Animations */
@keyframes gradient-blob-1 {
0%,
100% {
transform: translate(0, 0) scale(1);
@@ -731,7 +743,6 @@ pre:has(code[class*="language-"]) {
}
@keyframes gradient-blob-2 {
0%,
100% {
transform: translate(0, 0) scale(1);
@@ -747,7 +758,6 @@ pre:has(code[class*="language-"]) {
}
@keyframes gradient-blob-3 {
0%,
100% {
transform: translate(0, 0) scale(1);
@@ -783,7 +793,6 @@ pre:has(code[class*="language-"]) {
/* Circuit Pulse (used for node glow effects) */
@keyframes circuit-pulse {
0%,
100% {
opacity: 0.2;
@@ -887,12 +896,14 @@ pre:has(code[class*="language-"]) {
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(90deg,
background: linear-gradient(
90deg,
transparent 0%,
rgba(148, 163, 184, 0.3) 25%,
rgba(191, 206, 228, 0.2) 50%,
rgba(148, 163, 184, 0.3) 75%,
transparent 100%);
transparent 100%
);
background-size: 200% 100%;
animation: border-trace 4s linear infinite;
-webkit-mask:
@@ -962,7 +973,6 @@ pre:has(code[class*="language-"]) {
}
@keyframes junctionGlow {
0%,
100% {
opacity: 0.1;

View File

@@ -0,0 +1,46 @@
import type { Metadata } from "next";
import { Inter, Newsreader } from "next/font/google";
import { Analytics } from "@/src/components/Analytics";
import { Footer } from "@/src/components/Footer";
import { Header } from "@/src/components/Header";
import { InteractiveElements } from "@/src/components/InteractiveElements";
import "./globals.css";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const newsreader = Newsreader({
subsets: ["latin"],
variable: "--font-newsreader",
style: "italic",
display: "swap",
});
export const metadata: Metadata = {
title: {
default: "Marc Mintel",
template: "%s | Marc Mintel",
},
description:
"Technical problem solver's blog - practical insights and learning notes",
metadataBase: new URL("https://mintel.me"),
icons: {
icon: "/favicon.svg",
},
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.variable} ${newsreader.variable}`}>
<body className="min-h-screen bg-white">
<Header />
<main>{children}</main>
<Footer />
<InteractiveElements />
<Analytics />
</body>
</html>
);
}

View File

@@ -4,7 +4,7 @@ export const metadata = {
};
import Link from "next/link";
import { Button } from "../src/components/Button";
import { Button } from "@/src/components/Button";
export default function NotFound() {
return (

View File

@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
import { OGImageTemplate } from "../src/components/OGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../src/lib/og-helper";
import { OGImageTemplate } from "../../src/components/OGImageTemplate";
import { getOgFonts, OG_IMAGE_SIZE } from "../../src/lib/og-helper";
export const size = OG_IMAGE_SIZE;
export const contentType = "image/png";

View File

@@ -7,9 +7,9 @@ import {
ConceptPrice,
ConceptPrototyping,
ConceptWebsite,
} from "../src/components/Landing";
import { Reveal } from "../src/components/Reveal";
import { Section } from "../src/components/Section";
} from "@/src/components/Landing";
import { Reveal } from "@/src/components/Reveal";
import { Section } from "@/src/components/Section";
import {
H1,
H3,
@@ -17,15 +17,15 @@ import {
BodyText,
MonoLabel,
Label,
} from "../src/components/Typography";
import { Card, Container } from "../src/components/Layout";
import { Button } from "../src/components/Button";
import { GradientMesh, CodeSnippet } from "../src/components/Effects";
import { IconList, IconListItem } from "../src/components/IconList";
import { HeroSection } from "../src/components/HeroSection";
import { GlitchText } from "../src/components/GlitchText";
import { Marker } from "../src/components/Marker";
import { PenCircle } from "../src/components/PenCircle";
} from "@/src/components/Typography";
import { Card, Container } from "@/src/components/Layout";
import { Button } from "@/src/components/Button";
import { GradientMesh, CodeSnippet } from "@/src/components/Effects";
import { IconList, IconListItem } from "@/src/components/IconList";
import { HeroSection } from "@/src/components/HeroSection";
import { GlitchText } from "@/src/components/GlitchText";
import { Marker } from "@/src/components/Marker";
import { PenCircle } from "@/src/components/PenCircle";
export default function LandingPage() {
return (

View File

@@ -1,5 +1,5 @@
import { MetadataRoute } from "next";
import { getAllPosts } from "../src/lib/posts";
import { getAllPosts } from "@/src/lib/posts";
import { technologies } from "./technologies/[slug]/data";
/**

View File

@@ -1,7 +1,7 @@
import Link from "next/link";
import { getAllPosts } from "../../../src/lib/posts";
import { MediumCard } from "../../../src/components/MediumCard";
import { Reveal } from "../../../src/components/Reveal";
import { getAllPosts } from "@/src/lib/posts";
import { MediumCard } from "@/src/components/MediumCard";
import { Reveal } from "@/src/components/Reveal";
export async function generateStaticParams() {
const allPosts = await getAllPosts();

View File

@@ -2,11 +2,11 @@
import React from "react";
import Link from "next/link";
import { Container } from "../../../src/components/Layout";
import { Label } from "../../../src/components/Typography";
import { Container } from "@/src/components/Layout";
import { Label } from "@/src/components/Typography";
import { Check, ArrowLeft, Zap, ExternalLink } from "lucide-react";
import { technologies } from "./data";
import { Reveal } from "../../../src/components/Reveal";
import { Reveal } from "@/src/components/Reveal";
export default function TechnologyContent({ slug }: { slug: string }) {
const tech = technologies[slug];

View File

@@ -0,0 +1,115 @@
import { Layers, Code, Database, Palette, Terminal } from "lucide-react";
export interface TechInfo {
title: string;
subtitle: string;
description: string;
icon: any; // React.ElementType
benefits: string[];
customerValue: string;
color: string;
related: { name: string; slug: string }[];
}
export const technologies: Record<string, TechInfo> = {
"next-js-14": {
title: "Next.js 14",
subtitle: "The React Framework for the Web",
description:
"Next.js 14 is the latest version of the industry-leading framework for building high-performance web applications. It allows me to create fast, scalable, and search-engine-friendly websites by rendering content on the server before sending it to your users.",
icon: Layers,
benefits: [
"Lightning-fast page loads with Server Components",
"Automatic image optimization",
"Instant navigation between pages",
"Top-tier SEO (Search Engine Optimization) out of the box",
],
customerValue:
"For my clients, Next.js means a website that ranks higher on Google, keeps visitors engaged with instant interactions, and scales effortlessly as your traffic grows.",
color: "bg-black text-white",
related: [
{ name: "TypeScript", slug: "typescript" },
{ name: "React", slug: "react" },
],
},
typescript: {
title: "TypeScript",
subtitle: "JavaScript with Syntax for Types",
description:
"TypeScript adds a powerful type system to JavaScript, catching errors before they ever reach production. It acts as a safety net for your code, ensuring that data flows exactly as expected through your entire application.",
icon: Code,
benefits: [
"Eliminates whole categories of common bugs",
"Makes large codebases easier to maintain",
"Improves developer productivity and code confidence",
"Ensures critical data integrity",
],
customerValue:
'Using TypeScript means your application is robust and reliable from day one. It dramatically reduces the risk of "runtime errors" that could crash your site, saving time and money on bug fixes down the line.',
color: "bg-blue-600 text-white",
related: [
{ name: "Directus CMS", slug: "directus-cms" },
{ name: "Next.js 14", slug: "next-js-14" },
],
},
"directus-cms": {
title: "Directus CMS",
subtitle: "The Open Data Platform",
description:
"Directus is a modern, headless Content Management System (CMS) that instantly turns any database into a beautiful, easy-to-use application for managing your content. Unlike traditional CMSs, it doesn't dictate how your website looks.",
icon: Database,
benefits: [
"Intuitive interface for non-technical editors",
"Complete freedom regarding front-end design",
"Real-time updates and live previews",
"Highly secure and role-based access control",
],
customerValue:
"Directus gives you full control over your content without needing a developer for every text change. It separates your data from the design, ensuring your website can evolve visually without rebuilding your entire content library.",
color: "bg-purple-600 text-white",
related: [
{ name: "Next.js 14", slug: "next-js-14" },
{ name: "Tailwind CSS", slug: "tailwind-css" },
],
},
"tailwind-css": {
title: "Tailwind CSS",
subtitle: "Utility-First CSS Framework",
description:
"Tailwind CSS is a utility-first framework that allows me to build custom designs directly in markup. It eliminates the need for bulky, overriding stylesheets and ensures a consistent design system across every page.",
icon: Palette,
benefits: [
"Rapid UI development and prototyping",
"Consistent spacing, colors, and typography",
"Highly optimized final bundle size (only includes used styles)",
"Responsive design made simple",
],
customerValue:
"Tailwind ensures your brand looks pixel-perfect on every device. It also results in incredibly small CSS files, meaning your site loads faster for users on mobile networks.",
color: "bg-cyan-500 text-white",
related: [
{ name: "React", slug: "react" },
{ name: "Next.js 14", slug: "next-js-14" },
],
},
react: {
title: "React",
subtitle: "The Library for Web and Native User Interfaces",
description:
"React is the core library powering Next.js. It lets me build encapsulated components that manage their own state, then compose them to make complex UIs.",
icon: Terminal,
benefits: [
"Component-based architecture for reuse",
"Efficient updates and rendering",
"Rich ecosystem of libraries and tools",
"Strong community support",
],
customerValue:
"React enables rich, app-like interactions on your website. Whether it's a complex dashboard or a smooth animation, React makes it possible to build dynamic experiences that feel instantaneous.",
color: "bg-blue-400 text-white",
related: [
{ name: "Next.js 14", slug: "next-js-14" },
{ name: "Tailwind CSS", slug: "tailwind-css" },
],
},
};

View File

@@ -0,0 +1,19 @@
import React from "react";
import { technologies } from "./data";
import TechnologyContent from "./content";
export default async function TechnologyPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <TechnologyContent slug={slug} />;
}
// Generate static params for these dynamic routes
export async function generateStaticParams() {
return Object.keys(technologies).map((slug) => ({
slug,
}));
}

View File

@@ -1,23 +1,23 @@
"use client";
import { Reveal } from "../../src/components/Reveal";
import { Section } from "../../src/components/Section";
import { Reveal } from "@/src/components/Reveal";
import { Section } from "@/src/components/Section";
import {
SpeedPerformance,
SolidFoundation,
LayerSeparation,
TaskDone,
} from "../../src/components/Landing";
} from "@/src/components/Landing";
import {
H3,
LeadText,
BodyText,
Label,
MonoLabel,
} from "../../src/components/Typography";
import { Card } from "../../src/components/Layout";
import { Button } from "../../src/components/Button";
import { IconList, IconListItem } from "../../src/components/IconList";
} from "@/src/components/Typography";
import { Card } from "@/src/components/Layout";
import { Button } from "@/src/components/Button";
import { IconList, IconListItem } from "@/src/components/IconList";
import {
GradientMesh,
CodeSnippet,
@@ -25,8 +25,8 @@ import {
CMSVisualizer,
ArchitectureVisualizer,
ResultVisualizer,
} from "../../src/components/Effects";
import { Marker } from "../../src/components/Marker";
} from "@/src/components/Effects";
import { Marker } from "@/src/components/Marker";
export default function WebsitesPage() {
return (

View File

@@ -1,47 +0,0 @@
import type { Metadata } from 'next';
import { Inter, Newsreader } from 'next/font/google';
import { Analytics } from '../src/components/Analytics';
import { Footer } from '../src/components/Footer';
import { Header } from '../src/components/Header';
import { InteractiveElements } from '../src/components/InteractiveElements';
import './globals.css';
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });
const newsreader = Newsreader({
subsets: ['latin'],
variable: '--font-newsreader',
style: 'italic',
display: 'swap',
});
export const metadata: Metadata = {
title: {
default: 'Marc Mintel',
template: '%s | Marc Mintel',
},
description: "Technical problem solver's blog - practical insights and learning notes",
metadataBase: new URL('https://mintel.me'),
icons: {
icon: '/favicon.svg',
},
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.variable} ${newsreader.variable}`}>
<body className="min-h-screen bg-white">
<Header />
<main>
{children}
</main>
<Footer />
<InteractiveElements />
<Analytics />
</body>
</html>
);
}

View File

@@ -1,105 +0,0 @@
import { Layers, Code, Database, Palette, Terminal } from 'lucide-react';
export interface TechInfo {
title: string;
subtitle: string;
description: string;
icon: any; // React.ElementType
benefits: string[];
customerValue: string;
color: string;
related: { name: string; slug: string }[];
}
export const technologies: Record<string, TechInfo> = {
'next-js-14': {
title: 'Next.js 14',
subtitle: 'The React Framework for the Web',
description: 'Next.js 14 is the latest version of the industry-leading framework for building high-performance web applications. It allows me to create fast, scalable, and search-engine-friendly websites by rendering content on the server before sending it to your users.',
icon: Layers,
benefits: [
'Lightning-fast page loads with Server Components',
'Automatic image optimization',
'Instant navigation between pages',
'Top-tier SEO (Search Engine Optimization) out of the box'
],
customerValue: 'For my clients, Next.js means a website that ranks higher on Google, keeps visitors engaged with instant interactions, and scales effortlessly as your traffic grows.',
color: 'bg-black text-white',
related: [
{ name: 'TypeScript', slug: 'typescript' },
{ name: 'React', slug: 'react' }
]
},
'typescript': {
title: 'TypeScript',
subtitle: 'JavaScript with Syntax for Types',
description: 'TypeScript adds a powerful type system to JavaScript, catching errors before they ever reach production. It acts as a safety net for your code, ensuring that data flows exactly as expected through your entire application.',
icon: Code,
benefits: [
'Eliminates whole categories of common bugs',
'Makes large codebases easier to maintain',
'Improves developer productivity and code confidence',
'Ensures critical data integrity'
],
customerValue: 'Using TypeScript means your application is robust and reliable from day one. It dramatically reduces the risk of "runtime errors" that could crash your site, saving time and money on bug fixes down the line.',
color: 'bg-blue-600 text-white',
related: [
{ name: 'Directus CMS', slug: 'directus-cms' },
{ name: 'Next.js 14', slug: 'next-js-14' }
]
},
'directus-cms': {
title: 'Directus CMS',
subtitle: 'The Open Data Platform',
description: 'Directus is a modern, headless Content Management System (CMS) that instantly turns any database into a beautiful, easy-to-use application for managing your content. Unlike traditional CMSs, it doesn\'t dictate how your website looks.',
icon: Database,
benefits: [
'Intuitive interface for non-technical editors',
'Complete freedom regarding front-end design',
'Real-time updates and live previews',
'Highly secure and role-based access control'
],
customerValue: 'Directus gives you full control over your content without needing a developer for every text change. It separates your data from the design, ensuring your website can evolve visually without rebuilding your entire content library.',
color: 'bg-purple-600 text-white',
related: [
{ name: 'Next.js 14', slug: 'next-js-14' },
{ name: 'Tailwind CSS', slug: 'tailwind-css' }
]
},
'tailwind-css': {
title: 'Tailwind CSS',
subtitle: 'Utility-First CSS Framework',
description: 'Tailwind CSS is a utility-first framework that allows me to build custom designs directly in markup. It eliminates the need for bulky, overriding stylesheets and ensures a consistent design system across every page.',
icon: Palette,
benefits: [
'Rapid UI development and prototyping',
'Consistent spacing, colors, and typography',
'Highly optimized final bundle size (only includes used styles)',
'Responsive design made simple'
],
customerValue: 'Tailwind ensures your brand looks pixel-perfect on every device. It also results in incredibly small CSS files, meaning your site loads faster for users on mobile networks.',
color: 'bg-cyan-500 text-white',
related: [
{ name: 'React', slug: 'react' },
{ name: 'Next.js 14', slug: 'next-js-14' }
]
},
'react': {
title: 'React',
subtitle: 'The Library for Web and Native User Interfaces',
description: 'React is the core library powering Next.js. It lets me build encapsulated components that manage their own state, then compose them to make complex UIs.',
icon: Terminal,
benefits: [
'Component-based architecture for reuse',
'Efficient updates and rendering',
'Rich ecosystem of libraries and tools',
'Strong community support'
],
customerValue: 'React enables rich, app-like interactions on your website. Whether it\'s a complex dashboard or a smooth animation, React makes it possible to build dynamic experiences that feel instantaneous.',
color: 'bg-blue-400 text-white',
related: [
{ name: 'Next.js 14', slug: 'next-js-14' },
{ name: 'Tailwind CSS', slug: 'tailwind-css' }
]
}
};

View File

@@ -1,15 +0,0 @@
import React from 'react';
import { technologies } from './data';
import TechnologyContent from './content';
export default async function TechnologyPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return <TechnologyContent slug={slug} />;
}
// Generate static params for these dynamic routes
export async function generateStaticParams() {
return Object.keys(technologies).map((slug) => ({
slug,
}));
}

View File

@@ -4,7 +4,7 @@
"version": "0.1.0",
"description": "Technical problem solver's blog - practical insights and learning notes",
"scripts": {
"dev": "rm -rf .next .contentlayer/.cache && concurrently -k -p \"[{name}]\" -n \"content,next\" -c \"magenta,cyan\" \"contentlayer2 dev\" \"next dev\"",
"dev": "rm -rf .next && next dev",
"build": "next build --webpack",
"start": "next start",
"lint": "eslint app src scripts video",
@@ -37,6 +37,7 @@
"@opentelemetry/core": "^2.1.0",
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@payloadcms/db-postgres": "^3.77.0",
"@payloadcms/email-nodemailer": "^3.77.0",
"@payloadcms/next": "^3.77.0",
"@payloadcms/richtext-lexical": "^3.77.0",
"@react-pdf/renderer": "^4.3.2",

View File

@@ -1,6 +1,7 @@
import { buildConfig } from "payload";
import { postgresAdapter } from "@payloadcms/db-postgres";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
import { nodemailerAdapter } from "@payloadcms/email-nodemailer";
import path from "path";
import { fileURLToPath } from "url";
import sharp from "sharp";
@@ -20,6 +21,22 @@ export default buildConfig({
},
},
collections: [Users, Media, Posts],
...(process.env.MAIL_HOST
? {
email: nodemailerAdapter({
defaultFromAddress: process.env.MAIL_FROM || "info@mintel.me",
defaultFromName: "Mintel.me",
transportOptions: {
host: process.env.MAIL_HOST,
port: parseInt(process.env.MAIL_PORT || "587"),
auth: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD,
},
},
}),
}
: {}),
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || "fallback-secret-for-dev",
typescript: {

View File

@@ -51,7 +51,7 @@ services:
- directus-db-data:/var/lib/postgresql/data
imgproxy:
image: darthsim/imgproxy:latest
image: registry.infra.mintel.me/mintel/image-processor:latest
restart: always
networks:
- default
@@ -59,13 +59,12 @@ services:
extra_hosts:
- "mintel.localhost:host-gateway"
- "host.docker.internal:host-gateway"
env_file:
- .env
environment:
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
IMGPROXY_URL_MAPPING: "http://mintel.localhost/:http://app:3000/"
IMGPROXY_USE_ETAG: "true"
IMGPROXY_MAX_SRC_RESOLUTION: 20
IMGPROXY_ALLOWED_NETWORKS: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
IMGPROXY_IGNORE_SSL_ERRORS: "true"
IMGPROXY_DEBUG: "true"
IMGPROXY_LOG_LEVEL: debug
labels:
- "traefik.http.services.${PROJECT_NAME:-mintel-me}-imgproxy.loadbalancer.server.port=8080"
- "traefik.docker.network=infra"

View File

@@ -79,6 +79,27 @@ services:
- "caddy=gatekeeper.${TRAEFIK_HOST:-mintel.localhost}"
- "caddy.reverse_proxy={{upstreams 3000}}"
mintel-me-imgproxy:
image: registry.infra.mintel.me/mintel/image-processor:latest
restart: always
networks:
infra:
aliases:
- mintel-me-imgproxy
env_file:
- ${ENV_FILE:-.env}
environment:
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
labels:
- "traefik.enable=true"
- "traefik.http.routers.mintel-me-imgproxy.rule=Host(`img.${TRAEFIK_HOST:-mintel.localhost}`)"
- "traefik.http.routers.mintel-me-imgproxy.entrypoints=${TRAEFIK_ENTRYPOINT:-web}"
- "traefik.http.routers.mintel-me-imgproxy.service=mintel-me-imgproxy-svc"
- "traefik.http.services.mintel-me-imgproxy-svc.loadbalancer.server.port=8080"
- "traefik.docker.network=infra"
- "caddy=img.${TRAEFIK_HOST:-mintel.localhost}"
- "caddy.reverse_proxy={{upstreams 8080}}"
postgres-db:
image: postgres:15-alpine
restart: always

26
pnpm-lock.yaml generated
View File

@@ -136,6 +136,9 @@ importers:
"@payloadcms/db-postgres":
specifier: ^3.77.0
version: 3.77.0(@opentelemetry/api@1.9.0)(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))
"@payloadcms/email-nodemailer":
specifier: ^3.77.0
version: 3.77.0(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))
"@payloadcms/next":
specifier: ^3.77.0
version: 3.77.0(@types/react@19.2.13)(graphql@16.12.0)(monaco-editor@0.55.1)(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
@@ -3580,6 +3583,15 @@ packages:
peerDependencies:
payload: 3.77.0
"@payloadcms/email-nodemailer@3.77.0":
resolution:
{
integrity: sha512-1i4JSYAzQ+t2htdZAbfek7cslWXYV0YeLN9MoDI6LSbR8Q6Run6idPBLdEkBNLPc0NJFVWepj2IgXt72ifdbtw==,
}
engines: { node: ^18.20.2 || >=20.9.0 }
peerDependencies:
payload: 3.77.0
"@payloadcms/graphql@3.77.0":
resolution:
{
@@ -11205,6 +11217,13 @@ packages:
integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==,
}
nodemailer@7.0.12:
resolution:
{
integrity: sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==,
}
engines: { node: ">=6.0.0" }
nodemailer@8.0.1:
resolution:
{
@@ -17406,6 +17425,11 @@ snapshots:
- sql.js
- sqlite3
"@payloadcms/email-nodemailer@3.77.0(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))":
dependencies:
nodemailer: 7.0.12
payload: 3.77.0(graphql@16.12.0)(typescript@5.9.3)
"@payloadcms/graphql@3.77.0(graphql@16.12.0)(payload@3.77.0(graphql@16.12.0)(typescript@5.9.3))(typescript@5.9.3)":
dependencies:
graphql: 16.12.0
@@ -22830,6 +22854,8 @@ snapshots:
node-releases@2.0.27: {}
nodemailer@7.0.12: {}
nodemailer@8.0.1: {}
normalize-path@3.0.0: {}