Compare commits

...

8 Commits

Author SHA1 Message Date
c505050cb4 chore: add diagnostic rewrite and automated asset smoke tests
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🏗️ Build (push) Successful in 9m38s
Build & Deploy / 🚀 Deploy (push) Successful in 24s
Build & Deploy / 🩺 Smoke Test (push) Failing after 4s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-04-12 18:49:11 +02:00
d5d0d77180 fix: comprehensive asset rewrites to resolve 404s in klz showcase
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🏗️ Build (push) Successful in 10m9s
Build & Deploy / 🚀 Deploy (push) Successful in 25s
Build & Deploy / 🩺 Smoke Test (push) Successful in 3s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-04-12 12:53:41 +02:00
9399be3ea9 chore: implement nuclear runner cleanup to solve no space left on device
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Successful in 13m17s
Build & Deploy / 🚀 Deploy (push) Successful in 32s
Build & Deploy / 🩺 Smoke Test (push) Successful in 5s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-04-12 11:04:46 +02:00
7ec82027a8 fix: add build dependencies to Dockerfile for native modules
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 15s
Build & Deploy / 🏗️ Build (push) Failing after 24m54s
Nightly QA / 🔍 Static Analysis (push) Successful in 5m22s
Nightly QA / 🔗 Links & Deps (push) Successful in 2m18s
Nightly QA / 📝 E2E (push) Successful in 5m17s
Nightly QA / 🎭 Lighthouse (push) Successful in 4m46s
Nightly QA / 🔔 Notify (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🩺 Smoke Test (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-04-12 01:53:14 +02:00
631d051c2e chore: move rewrites to beforeFiles and fix smoke test dependencies
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🩺 Smoke Test (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-04-12 01:51:43 +02:00
1a8758a88c chore: extreme runner cleanup and disable registry cache to unblock deployment
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Successful in 8m6s
Build & Deploy / 🚀 Deploy (push) Successful in 23s
Build & Deploy / 🩺 Smoke Test (push) Failing after 2m2s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-12 00:14:26 +02:00
7d6d34dd7b fix(deploy): optimize Dockerfile for disk efficiency and add runner diagnostics
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🏗️ Build (push) Failing after 13m21s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Smoke Test (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-04-11 23:54:23 +02:00
1f0de18755 fix(form): resolve silent failures and cleanup deployment pipeline
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Failing after 22m37s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Smoke Test (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-11 23:21:13 +02:00
6 changed files with 139 additions and 31 deletions

View File

@@ -154,6 +154,30 @@ jobs:
run: | run: |
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: 🧹 Nuclear Runner Cleanup
run: |
echo "Disk space before nuclear prune:"
df -h
# Massive cleanup of pre-installed runner software to free up ~5GB+
# These are standard on VM-based runners and often unnecessary
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /usr/local/lib/android || true
sudo rm -rf /opt/ghc || true
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
# Comprehensive Docker purge
docker system prune -af --volumes
docker builder prune -af
docker buildx prune -af
# Clean temp build artifacts
rm -rf /tmp/docker-actions-toolkit-* || true
echo "Disk space after nuclear prune:"
df -h
continue-on-error: true
- name: 🏗️ Build and Push - name: 🏗️ Build and Push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
@@ -176,8 +200,9 @@ jobs:
PAYLOAD_SECRET=${{ secrets.PAYLOAD_SECRET || 'secret' }} PAYLOAD_SECRET=${{ secrets.PAYLOAD_SECRET || 'secret' }}
BUILD_ID=${{ github.sha }} BUILD_ID=${{ github.sha }}
tags: registry.infra.mintel.me/mintel/mintel.me:${{ needs.prepare.outputs.image_tag }} tags: registry.infra.mintel.me/mintel/mintel.me:${{ needs.prepare.outputs.image_tag }}
# Temporarily disable registry cache export to save runner disk/bandwidth
cache-from: type=registry,ref=registry.infra.mintel.me/mintel/mintel.me:buildcache-${{ needs.prepare.outputs.target }} cache-from: type=registry,ref=registry.infra.mintel.me/mintel/mintel.me:buildcache-${{ needs.prepare.outputs.target }}
cache-to: type=registry,ref=registry.infra.mintel.me/mintel/mintel.me:buildcache-${{ needs.prepare.outputs.target }},mode=max # cache-to: type=registry,ref=registry.infra.mintel.me/mintel/mintel.me:buildcache-${{ needs.prepare.outputs.target }},mode=max
secrets: | secrets: |
NPM_TOKEN=${{ secrets.NPM_TOKEN }} NPM_TOKEN=${{ secrets.NPM_TOKEN }}
@@ -432,6 +457,7 @@ jobs:
- name: 🌐 Check Production URL - name: 🌐 Check Production URL
shell: sh shell: sh
run: | run: |
apk add --no-cache curl
# Wait longer (up to 2 minutes) for Next.js to fully prime # Wait longer (up to 2 minutes) for Next.js to fully prime
COUNT=0 COUNT=0
MAX=24 MAX=24
@@ -454,17 +480,44 @@ jobs:
exit 1 exit 1
fi fi
- name: 🌐 Check Case Study Assets - name: 🌐 Check Case Study Assets and Rewrites
shell: sh shell: sh
run: | run: |
URL="${{ needs.prepare.outputs.next_public_url }}/case-studies/klz-cables" BASE_URL="${{ needs.prepare.outputs.next_public_url }}"
echo "Verifying case study $URL..."
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL") echo "Verifying diagnostic rewrite..."
STATUS=$(curl -s -L -o /dev/null -w "%{http_code}" "$BASE_URL/robots-test")
if [ "$STATUS" != "200" ]; then
echo "❌ Diagnostic rewrite /robots-test failed! (Status: $STATUS)"
exit 1
fi
echo "✅ Diagnostic rewrite OK"
echo "Verifying case study page..."
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/case-studies/klz-cables")
if [ "$STATUS" != "200" ]; then if [ "$STATUS" != "200" ]; then
echo "❌ Case study page failed! (Status: $STATUS)" echo "❌ Case study page failed! (Status: $STATUS)"
exit 1 exit 1
fi fi
echo "✅ Case study page is UP" echo "✅ Case study page UP"
echo "Verifying breeze CSS (root path)..."
CSS_PATH="/assets/klz-cables.com/wp-content/cache/breeze-minification/css/breeze_klz-cables-com-1-10895.css"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$CSS_PATH")
if [ "$STATUS" != "200" ]; then
echo "❌ Root asset path failed! (Status: $STATUS)"
exit 1
fi
echo "✅ Root asset path OK"
echo "Verifying breeze CSS (relative path from case-study)..."
REL_CSS_PATH="/case-studies/assets/klz-cables.com/wp-content/cache/breeze-minification/css/breeze_klz-cables-com-1-10895.css"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$REL_CSS_PATH")
if [ "$STATUS" != "200" ]; then
echo "❌ Relative asset path failed! (Status: $STATUS)"
exit 1
fi
echo "✅ Relative asset path OK"
# ────────────────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────────────────
# JOB 6: Notifications # JOB 6: Notifications

View File

@@ -39,6 +39,9 @@ ENV CI=true
# Copy manifest files specifically for better layer caching # Copy manifest files specifically for better layer caching
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .npmrc* ./ COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .npmrc* ./
COPY apps/web/package.json ./apps/web/package.json COPY apps/web/package.json ./apps/web/package.json
# Install build dependencies for native modules (like canvas)
RUN apk add --no-cache python3 make g++ pkgconfig pixman-dev cairo-dev pango-dev libjpeg-turbo-dev giflib-dev librsvg-dev
# Copy sibling monorepo for linked dependencies (cloned during CI) # Copy sibling monorepo for linked dependencies (cloned during CI)
# Placing it inside /app so relative links like ../../_at-mintel resolve correctly! # Placing it inside /app so relative links like ../../_at-mintel resolve correctly!
COPY _at-mintel* /app/_at-mintel/ COPY _at-mintel* /app/_at-mintel/
@@ -72,17 +75,14 @@ WORKDIR /app
# Copy standalone output and static files (Monorepo paths) # Copy standalone output and static files (Monorepo paths)
# Note: Base image already handles the non-root user and basic env # Note: Base image already handles the non-root user and basic env
COPY --from=builder /app/apps/web/public ./apps/web/public COPY --from=builder --chown=1001:65533 /app/apps/web/public ./apps/web/public
COPY --from=builder /app/apps/web/.next/standalone ./ COPY --from=builder --chown=1001:65533 /app/apps/web/.next/standalone ./
COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static COPY --from=builder --chown=1001:65533 /app/apps/web/.next/static ./apps/web/.next/static
# Explicitly copy Payload dynamically generated importMap.js excluded by Standalone tracing # Explicitly copy Payload dynamically generated importMap.js excluded by Standalone tracing
COPY --from=builder /app/apps/web/app/(payload)/admin/importMap.js ./apps/web/app/(payload)/admin/importMap.js COPY --from=builder --chown=1001:65533 /app/apps/web/app/(payload)/admin/importMap.js ./apps/web/app/(payload)/admin/importMap.js
# Fix permissions for the non-root user (Standard uid/gid from base image) # Fix permissions for the non-root user (Standard uid/gid from base image)
# We do this as root before switching users
USER root
RUN chown -R 1001:65533 /app
USER nextjs USER nextjs
# Start from the app directory to ensure references solve correctly # Start from the app directory to ensure references solve correctly

View File

@@ -38,22 +38,41 @@ const nextConfig = {
], ],
}, },
async rewrites() { async rewrites() {
return [ return {
// Umami proxy rewrite handled in app/stats/api/send/route.ts beforeFiles: [
// Sentry relay handled in app/errors/api/relay/route.ts // Assets requested from root
{ {
source: '/assets/:path*', source: '/assets/:path*',
destination: '/showcase/klz-cables.com/assets/:path*', destination: '/showcase/klz-cables.com/assets/:path*',
}, },
{ {
source: '/wp-content/:path*', source: '/wp-content/:path*',
destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-content/:path*', destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-content/:path*',
}, },
{ {
source: '/wp-includes/:path*', source: '/wp-includes/:path*',
destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-includes/:path*', destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-includes/:path*',
}, },
]; // Assets requested relatively from case-studies/work/blog subpages
{
source: '/:folder(case-studies|work|blog)/assets/:path*',
destination: '/showcase/klz-cables.com/assets/:path*',
},
{
source: '/:folder(case-studies|work|blog)/wp-content/:path*',
destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-content/:path*',
},
{
source: '/:folder(case-studies|work|blog)/wp-includes/:path*',
destination: '/showcase/klz-cables.com/assets/klz-cables.com/wp-includes/:path*',
},
// Diagnostic rule
{
source: '/robots-test',
destination: '/robots.txt',
},
],
};
}, },
async redirects() { async redirects() {
return [ return [

View File

@@ -246,6 +246,7 @@ export function ContactForm({
onBack={() => setFlow("discovery")} // Can keep, but won't be seen if we hide button onBack={() => setFlow("discovery")} // Can keep, but won't be seen if we hide button
onSubmit={() => handleSubmit()} onSubmit={() => handleSubmit()}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
error={error}
/> />
); );
} }

View File

@@ -35,6 +35,7 @@ interface DirectMessageFlowProps {
onBack: () => void; onBack: () => void;
onSubmit: () => void; onSubmit: () => void;
isSubmitting: boolean; isSubmitting: boolean;
error?: string | null;
} }
export const DirectMessageFlow = ({ export const DirectMessageFlow = ({
@@ -57,10 +58,32 @@ export const DirectMessageFlow = ({
onBack, onBack,
onSubmit, onSubmit,
isSubmitting, isSubmitting,
error,
}: DirectMessageFlowProps) => { }: DirectMessageFlowProps) => {
return ( return (
<div className="w-full max-w-3xl mx-auto px-4 py-12"> <div className="w-full max-w-3xl mx-auto px-4 py-12">
<div className="space-y-12"> <div className="space-y-12">
{error && (
<Reveal width="100%" delay={0.1}>
<div className="bg-red-50 border border-red-100 rounded-2xl p-6 flex items-start gap-4 mb-8">
<div className="p-2 bg-red-100 rounded-lg text-red-600">
<Mail size={20} className="animate-pulse" />
</div>
<div className="space-y-1">
<h3 className="text-red-900 font-bold text-sm uppercase tracking-wider">
Übertragungsfehler
</h3>
<p className="text-red-700/80 font-medium text-base">
{error}
</p>
<div className="mt-2 text-[10px] font-mono text-red-400 uppercase tracking-widest">
Status: FEHLER_BEI_SEQUENZ_INIT
</div>
</div>
</div>
</Reveal>
)}
<Reveal width="100%" delay={0.2}> <Reveal width="100%" delay={0.2}>
<div className="space-y-4"> <div className="space-y-4">
<span className="text-[10px] font-mono text-green-600 uppercase tracking-[0.3em] font-bold"> <span className="text-[10px] font-mono text-green-600 uppercase tracking-[0.3em] font-bold">

View File

@@ -45,11 +45,23 @@ export async function sendEmail({
subject, subject,
html, html,
}: SendEmailOptions) { }: SendEmailOptions) {
const recipients = to || env.MAIL_RECIPIENTS; let recipients = to || env.MAIL_RECIPIENTS;
let from = env.MAIL_FROM;
if (!from) {
from = "info@mintel.me";
console.warn("MAIL_FROM is empty. Using fallback: info@mintel.me");
}
if (!recipients) {
recipients = "marc@mintel.me";
console.warn("MAIL_RECIPIENTS is empty. Using fallback: marc@mintel.me");
}
const transporter = getTransporter(); const transporter = getTransporter();
const mailOptions = { const mailOptions = {
from: env.MAIL_FROM, from,
to: recipients, to: recipients,
replyTo, replyTo,
subject, subject,