Compare commits

...

8 Commits

Author SHA1 Message Date
49abaaf2fd fix(web): increase build memory to 8GB and support smooth-scroll in IframeSection
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Successful in 16m45s
Build & Deploy / 🚀 Deploy (push) Successful in 19s
Build & Deploy / 🧪 QA (push) Successful in 1m9s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m9s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-10 11:37:09 +02:00
7e9005e338 fix(web): remove forced transparent background from IframeSection to prevent visibility issues
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Failing after 37m43s
Build & Deploy / 🧪 QA (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
Nightly QA / 🔗 Links & Deps (push) Successful in 4m13s
Nightly QA / 🎭 Lighthouse (push) Successful in 4m38s
Nightly QA / 🔍 Static Analysis (push) Successful in 4m55s
Nightly QA / 📝 E2E (push) Successful in 5m9s
Nightly QA / 🔔 Notify (push) Has been skipped
2026-04-09 23:09:38 +02:00
bbcc7d159c fix(deploy): resolve permission issues and stabilize asset build context
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🏗️ Build (push) Successful in 19m0s
Build & Deploy / 🚀 Deploy (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Successful in 1m34s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 4m19s
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-04-09 22:26:57 +02:00
39f5bd3986 fix(blog): force dynamic rendering to avoid empty build-time cache
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🏗️ Build (push) Successful in 13m5s
Build & Deploy / 🚀 Deploy (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Successful in 1m8s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m8s
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-04-09 21:37:35 +02:00
944d369d43 ci: automate S3 cache purge during deployment
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🏗️ Build (push) Successful in 10m13s
Build & Deploy / 🚀 Deploy (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Successful in 1m9s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 2m59s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-09 18:18:34 +02:00
6aaf8ac44f fix(types): manually align Inquiry type for contact form fields
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🏗️ Build (push) Successful in 17m53s
Build & Deploy / 🚀 Deploy (push) Successful in 15s
Build & Deploy / 🧪 QA (push) Successful in 1m9s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m8s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-09 12:34:56 +02:00
2097b571f3 feat(web): streamline contact form and integrate identity fields
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 1m9s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Failing after 30m21s
Build & Deploy / 🧪 QA (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-04-07 23:08:04 +02:00
aeb1814a34 fix: trigger deploy to purge cache and ensure chunk alignment 2026-04-01 19:25:07 +02:00
18 changed files with 407 additions and 162 deletions

View File

@@ -316,6 +316,7 @@ jobs:
S3_BUCKET=${{ secrets.S3_BUCKET || vars.S3_BUCKET || 'mintel' }}
S3_REGION=${{ secrets.S3_REGION || vars.S3_REGION || 'fsn1' }}
S3_PREFIX=${{ secrets.S3_PREFIX || vars.S3_PREFIX || 'mintel.me' }}
BUILD_ID=${{ github.sha }}
tags: registry.infra.mintel.me/mintel/mintel.me:${{ needs.prepare.outputs.image_tag }}
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
@@ -532,6 +533,29 @@ jobs:
docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file $ENV_FILE up -d --remove-orphans
"
- name: 🧹 Purge S3 Cache
shell: bash
run: |
echo "Installing rclone..."
curl -s -O https://downloads.rclone.org/rclone-current-linux-amd64.deb
sudo dpkg -i rclone-current-linux-amd64.deb > /dev/null 2>&1
echo "Configuring rclone..."
cat > rclone.conf <<EOF
[mintel-s3]
type = s3
provider = Other
access_key_id = ${{ secrets.S3_ACCESS_KEY || vars.S3_ACCESS_KEY }}
secret_access_key = ${{ secrets.S3_SECRET_KEY || vars.S3_SECRET_KEY }}
endpoint = ${{ secrets.S3_ENDPOINT || vars.S3_ENDPOINT || 'https://fsn1.your-objectstorage.com' }}
region = ${{ secrets.S3_REGION || vars.S3_REGION || 'fsn1' }}
EOF
echo "Purging S3 cache for ${{ env.S3_PREFIX }} ..."
rclone --config rclone.conf delete mintel-s3:${{ env.S3_BUCKET }}/${{ env.S3_PREFIX }}/cache/ --include "*" || true
rm rclone.conf rclone-current-linux-amd64.deb
- name: 🧹 Post-Deploy Cleanup (Runner)
if: always()
run: docker builder prune -f --filter "until=1h"

View File

@@ -13,6 +13,7 @@ ARG S3_SECRET_KEY
ARG S3_BUCKET
ARG S3_REGION
ARG S3_PREFIX
ARG BUILD_ID
# Environment variables for Next.js build
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
@@ -47,10 +48,14 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
rm .npmrc
# Copy source code
# We use BUILD_ID here to ensure that if the commit changes, we always COPY the latest files
# even if Docker's metadata-based fingerprinting for the public directory fails.
ARG BUILD_ID
RUN echo "Building with ID: ${BUILD_ID}"
COPY . .
# Build application (monorepo filter)
ENV NODE_OPTIONS="--max_old_space_size=4096"
ENV NODE_OPTIONS="--max_old_space_size=8192"
RUN pnpm --filter @mintel/web build
# Stage 2: Runner
@@ -65,6 +70,13 @@ COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
# 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
# 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
# Start from the app directory to ensure references solve correctly
WORKDIR /app/apps/web
CMD ["node", "server.js"]

View File

@@ -1,7 +1,7 @@
FROM node:20-alpine
# Install essential build tools if needed (e.g., for node-gyp)
RUN apk add --no-cache libc6-compat python3 make g++
RUN apk add --no-cache libc6-compat git python3 make g++
WORKDIR /app

View File

@@ -2,6 +2,8 @@ import { getAllPosts } from "@/src/lib/posts";
import { BlogClient } from "@/src/components/blog/BlogClient";
import type { Metadata } from "next";
export const dynamic = "force-dynamic";
export const metadata: Metadata = {
title: "Blog | Mintel.me",
description:

View File

@@ -287,6 +287,9 @@ export interface Inquiry {
email: string;
companyName?: string | null;
projectType?: string | null;
phone?: string | null;
role?: string | null;
deadline?: string | null;
message?: string | null;
isFreeText?: boolean | null;
/**
@@ -808,7 +811,10 @@ export interface InquiriesSelect<T extends boolean = true> {
name?: T;
email?: T;
companyName?: T;
phone?: T;
role?: T;
projectType?: T;
deadline?: T;
message?: T;
isFreeText?: T;
config?: T;

View File

@@ -11,8 +11,11 @@ import configPromise from "@payload-config";
export async function sendContactInquiry(data: {
name: string;
email: string;
phone?: string;
role?: string;
companyName: string;
projectType: string;
deadline?: string;
message: string;
isFreeText: boolean;
config?: any;
@@ -25,8 +28,11 @@ export async function sendContactInquiry(data: {
data: {
name: data.name,
email: data.email,
phone: data.phone,
role: data.role,
companyName: data.companyName,
projectType: data.projectType,
deadline: data.deadline,
message: data.message,
isFreeText: data.isFreeText,
config: data.config || null,

View File

@@ -62,7 +62,7 @@ export function ContactForm({
initialStepIndex = 0,
initialState: injectedState,
}: ContactFormProps) {
const [flow, setFlow] = useState<FlowState>("discovery");
const [flow, setFlow] = useState<FlowState>("direct-message");
const [stepIndex, setStepIndex] = useState(initialStepIndex);
const [state, setState] = useState<FormState>({
...initialState,
@@ -125,8 +125,11 @@ export function ContactForm({
const result = await sendContactInquiry({
name: state.name,
email: state.email,
phone: state.phone,
role: state.role,
companyName: state.companyName,
projectType: state.projectType,
deadline: state.deadline,
message: state.message,
isFreeText: flow === "direct-message",
config: flow === "configurator" ? state : undefined,
@@ -190,7 +193,7 @@ export function ContactForm({
<button
onClick={() => {
setIsSubmitted(false);
setFlow("discovery");
setFlow("direct-message"); // Reset back to dm
setStepIndex(0);
setState(initialState);
}}
@@ -204,7 +207,7 @@ export function ContactForm({
);
}
// Gateway Flow
// Gateway Flow (Disabled but kept logically if needed, can just not render)
if (flow === "discovery") {
return (
<ContactGateway
@@ -225,13 +228,23 @@ export function ContactForm({
return (
<DirectMessageFlow
name={state.name}
setName={(v) => updateState({ name: v })}
email={state.email}
setEmail={(v) => updateState({ email: v })}
phone={state.phone}
setPhone={(v) => updateState({ phone: v })}
role={state.role}
setRole={(v) => updateState({ role: v })}
company={state.companyName}
setCompany={(v) => updateState({ companyName: v })}
projectType={state.projectType}
setProjectType={(v) => updateState({ projectType: v })}
deadline={state.deadline}
setDeadline={(v) => updateState({ deadline: v })}
message={state.message}
setMessage={(v) => updateState({ message: v })}
onBack={() => setFlow("discovery")}
onSubmit={handleSubmit}
onBack={() => setFlow("discovery")} // Can keep, but won't be seen if we hide button
onSubmit={() => handleSubmit()}
isSubmitting={isSubmitting}
/>
);

View File

@@ -88,41 +88,40 @@ export const ContactGateway = ({
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full">
{/* Configurator Path */}
<Reveal width="100%" delay={0.3} direction="up">
<button
onClick={onChooseConfigurator}
disabled={!name}
className={cn(
"group relative flex flex-col items-start p-8 rounded-3xl border text-left transition-all duration-500 overflow-hidden",
name
? "bg-slate-900 border-slate-800 text-white shadow-2xl hover:-translate-y-2"
: "bg-slate-50 border-slate-100 text-slate-400 cursor-not-allowed opacity-60",
)}
>
<div className="absolute top-0 right-0 p-8 opacity-10 group-hover:opacity-20 transition-opacity">
<Settings2 size={120} />
</div>
<div className="relative group p-[1px] rounded-3xl overflow-hidden transition-all duration-500">
{/* Disabled Overlay Background */}
<div className="absolute inset-0 bg-gradient-to-br from-slate-200 to-slate-100 dark:from-slate-800 dark:to-slate-900 opacity-50" />
<Settings2 size={24} className="mb-6 text-green-400" />
<h3 className="text-2xl font-bold mb-2 tracking-tight">
System-Konfigurator
</h3>
<p className="text-sm text-slate-400 font-medium mb-8 max-w-[280px]">
Konfigurieren Sie Ihr Projekt modular für eine präzise
Aufwandsschätzung.
</p>
<button
disabled
className={cn(
"w-full h-full relative flex flex-col items-start p-8 rounded-[23px] border text-left bg-slate-50 border-slate-100 text-slate-400 cursor-not-allowed",
)}
>
<div className="absolute top-0 right-0 p-8 opacity-5">
<Settings2 size={120} />
</div>
<div className="mt-auto flex items-center gap-2 text-[10px] font-mono uppercase tracking-widest font-bold">
<span>Sitzung starten</span>
<ArrowRight
size={14}
className="group-hover:translate-x-1 transition-transform"
/>
</div>
<div className="flex items-center gap-3 mb-6">
<Settings2 size={24} className="text-slate-300" />
<span className="px-2 py-0.5 rounded-full bg-slate-200 text-[8px] font-bold uppercase tracking-wider text-slate-500">
Wartungsmodus
</span>
</div>
{!name && (
<div className="absolute inset-0 bg-slate-50/60 backdrop-blur-[6px] z-20" />
)}
</button>
<h3 className="text-2xl font-bold mb-2 tracking-tight opacity-50">
System-Konfigurator
</h3>
<p className="text-sm text-slate-400 font-medium mb-8 max-w-[280px] opacity-70">
Dieser Modus wird aktuell optimiert und steht in Kürze wieder
zur Verfügung.
</p>
<div className="mt-auto flex items-center gap-2 text-[10px] font-mono uppercase tracking-widest font-bold opacity-30">
<span>Konfigurator offline</span>
</div>
</button>
</div>
</Reveal>
{/* Direct Mail Path */}

View File

@@ -3,13 +3,33 @@
import { motion } from "framer-motion";
import { cn } from "../../utils/cn";
import { Reveal } from "../Reveal";
import { Mail, MessageSquare, ArrowLeft, Send } from "lucide-react";
import { ProjectType } from "./types";
import {
Mail,
MessageSquare,
ArrowLeft,
Send,
Phone,
User as UserIcon,
Calendar,
Layers,
} from "lucide-react";
interface DirectMessageFlowProps {
name: string;
setName: (val: string) => void;
email: string;
setEmail: (val: string) => void;
phone: string;
setPhone: (val: string) => void;
role: string;
setRole: (val: string) => void;
company: string;
setCompany: (val: string) => void;
projectType: ProjectType;
setProjectType: (val: ProjectType) => void;
deadline: string;
setDeadline: (val: string) => void;
message: string;
setMessage: (val: string) => void;
onBack: () => void;
@@ -19,9 +39,19 @@ interface DirectMessageFlowProps {
export const DirectMessageFlow = ({
name,
setName,
email,
setEmail,
phone,
setPhone,
role,
setRole,
company,
setCompany,
projectType,
setProjectType,
deadline,
setDeadline,
message,
setMessage,
onBack,
@@ -30,72 +60,167 @@ export const DirectMessageFlow = ({
}: DirectMessageFlowProps) => {
return (
<div className="w-full max-w-3xl mx-auto px-4 py-12">
<Reveal width="100%" delay={0.1}>
<button
onClick={onBack}
className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400 hover:text-slate-900 transition-colors mb-12"
>
<ArrowLeft size={14} /> Zurück zur Auswahl
</button>
</Reveal>
<div className="space-y-12">
<Reveal width="100%" delay={0.2}>
<div className="space-y-2">
<div className="space-y-4">
<span className="text-[10px] font-mono text-green-600 uppercase tracking-[0.3em] font-bold">
DIREKTNACHRICHT // MODUS_AKTIVIERT
DIREKTANFRAGE // SCHRITT_01
</span>
<h2 className="text-3xl font-bold tracking-tight text-slate-900">
Wie kann ich helfen, {name.split(" ")[0]}?
<h2 className="text-3xl md:text-5xl font-bold tracking-tight text-slate-900 line-clamp-2">
Lassen Sie uns sprechen.
</h2>
<p className="text-slate-500 font-medium">
Sende mir eine Nachricht zu {company || "deinem Projekt"} und ich
melde mich in Kürze.
<p className="text-slate-500 font-medium text-lg">
Senden Sie mir eine Nachricht und ich melde mich zeitnah zurück.
</p>
</div>
</Reveal>
<div className="space-y-8">
{/* Email Input */}
<div className="space-y-12">
{/* Section: Mission Focus */}
<Reveal width="100%" delay={0.3} direction="up">
<div className="space-y-4">
<div className="space-y-6">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<Mail size={12} /> Rückantwort an
<Layers size={12} /> Mission // Projekt-Typ
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="ihre@email.de"
className="w-full bg-slate-50 border border-slate-100 rounded-2xl px-6 py-4 text-lg font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all"
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{(["website", "web-app", "ecommerce"] as ProjectType[]).map(
(type) => {
const labels = {
website: "Website",
"web-app": "Web Application",
ecommerce: "E-Commerce",
};
return (
<button
key={type}
onClick={() => setProjectType(type)}
className={cn(
"px-6 py-4 rounded-xl border font-bold text-sm transition-all duration-200 text-left",
projectType === type
? "bg-slate-900 text-white border-slate-900 shadow-lg"
: "bg-white border-slate-100 text-slate-500 hover:border-slate-300",
)}
>
{labels[type]}
</button>
);
},
)}
</div>
</div>
</Reveal>
{/* Message Input */}
{/* Section: Identity Details */}
<Reveal width="100%" delay={0.4} direction="up">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<UserIcon size={12} /> Ihr Name
</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Max Mustermann"
className="w-full bg-slate-50 border border-slate-100 rounded-xl px-4 py-3 text-base font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all"
/>
</div>
<div className="space-y-4">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<Layers size={12} /> Unternehmen (Optional)
</label>
<input
type="text"
value={company}
onChange={(e) => setCompany(e.target.value)}
placeholder="Beispiel GmbH"
className="w-full bg-slate-50 border border-slate-100 rounded-xl px-4 py-3 text-base font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all"
/>
</div>
<div className="space-y-4">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<Mail size={12} /> E-Mail Adresse
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="name@firma.de"
className="w-full bg-slate-50 border border-slate-100 rounded-xl px-4 py-3 text-base font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all"
/>
</div>
<div className="space-y-4">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<Phone size={12} /> Rückruf-Nummer (Optional)
</label>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="+49 123 456789"
className="w-full bg-slate-50 border border-slate-100 rounded-xl px-4 py-3 text-base font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all"
/>
</div>
</div>
</Reveal>
{/* Section: Additional Context */}
<Reveal width="100%" delay={0.5} direction="up">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<UserIcon size={12} /> Ihre Position
</label>
<input
type="text"
value={role}
onChange={(e) => setRole(e.target.value)}
placeholder="z.B. Gründer, Marketing Lead..."
className="w-full bg-slate-50 border border-slate-100 rounded-xl px-4 py-3 text-base font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all"
/>
</div>
<div className="space-y-4">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<Calendar size={12} /> Zeitfenster
</label>
<select
value={deadline}
onChange={(e) => setDeadline(e.target.value)}
className="w-full bg-slate-50 border border-slate-100 rounded-xl px-4 py-3 text-base font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all appearance-none cursor-pointer"
>
<option value="asap">ASAP (Sofort)</option>
<option value="1month">&lt; 1 Monat (Priorität)</option>
<option value="3months">1-3 Monate (Standard)</option>
<option value="flexible">Flexibel</option>
</select>
</div>
</div>
</Reveal>
{/* Section: Payload */}
<Reveal width="100%" delay={0.6} direction="up">
<div className="space-y-4">
<label className="flex items-center gap-2 text-[10px] font-mono font-bold uppercase tracking-widest text-slate-400">
<MessageSquare size={12} /> Ihre Nachricht
<MessageSquare size={12} /> Nachricht // Briefing
</label>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Beschreiben Sie kurz Ihr Anliegen..."
rows={6}
className="w-full bg-slate-50 border border-slate-100 rounded-2xl px-6 py-4 text-lg font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all resize-none"
placeholder="Beschreiben Sie kurz Ihr Anliegen oder hinterlassen Sie einen Link zum Briefing..."
rows={5}
className="w-full bg-slate-50 border border-slate-100 rounded-2xl px-6 py-4 text-base font-medium focus:outline-none focus:ring-2 focus:ring-slate-900/5 focus:border-slate-900 transition-all resize-none"
/>
</div>
</Reveal>
{/* Submit Button */}
<Reveal width="100%" delay={0.5} direction="up">
<Reveal width="100%" delay={0.7} direction="up">
<button
onClick={onSubmit}
disabled={isSubmitting || !email || !message}
disabled={isSubmitting || !email || !message || !name}
className={cn(
"group relative w-full py-5 rounded-2xl font-bold text-lg transition-all duration-300 flex items-center justify-center gap-3 overflow-hidden",
isSubmitting || !email || !message
isSubmitting || !email || !message || !name
? "bg-slate-100 text-slate-400 cursor-not-allowed"
: "bg-slate-900 text-white shadow-xl hover:shadow-2xl hover:-translate-y-1 active:scale-[0.98]",
)}
@@ -109,7 +234,7 @@ export const DirectMessageFlow = ({
</>
) : (
<>
<span>Nachricht absenden</span>
<span>Anfrage senden</span>
<Send
size={20}
className="group-hover:translate-x-1 group-hover:-translate-y-1 transition-transform"

View File

@@ -21,15 +21,19 @@ export const getInquiryEmailHtml = (data: any) => `
<div class="title">NEUE_ANFRAGE_INPUT</div>
</div>
<div class="section">
<div class="label">ABSENDER</div>
<div class="value">${data.name} (${data.email})</div>
${data.phone ? `<div class="label">TELEFON</div><div class="value">${data.phone}</div>` : ""}
${data.role ? `<div class="label">POSITION</div><div class="value">${data.role}</div>` : ""}
<div class="label">UNTERNEHMEN</div>
<div class="value">${data.companyName || "N/A"}</div>
<div class="label">PROJEKT_TYP</div>
<div class="value">${data.projectType}</div>
${data.deadline ? `<div class="label">ZEITRAUM</div><div class="value">${data.deadline}</div>` : ""}
</div>
${

View File

@@ -35,6 +35,7 @@ export interface FormState {
storageExpansion: number;
name: string;
email: string;
phone: string;
role: string;
message: string;
sitemapFile: File | null;

View File

@@ -196,11 +196,13 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
const updateScrollState = React.useCallback(() => {
try {
const win = iframeRef.current?.contentWindow;
const doc = iframeRef.current?.contentDocument?.documentElement;
if (doc) {
const atTop = doc.scrollTop <= 5;
if (doc && win) {
const scrollTop = doc.scrollTop || win.scrollY || 0;
const atTop = scrollTop <= 5;
const atBottom =
doc.scrollTop + doc.clientHeight >= doc.scrollHeight - 5;
scrollTop + doc.clientHeight >= doc.scrollHeight - 5;
const isScrollable = doc.scrollHeight > doc.clientHeight + 10;
setScrollState({ atTop, atBottom, isScrollable });
}
@@ -493,7 +495,6 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
style.textContent = `
*::-webkit-scrollbar { display: none !important; }
* { -ms-overflow-style: none !important; scrollbar-width: none !important; }
body { background: transparent !important; }
`;
iframe.contentDocument.head.appendChild(style);
setTimeout(updateAmbilight, 600);

View File

@@ -47,6 +47,7 @@ export const initialState: FormState = {
storageExpansion: 0,
name: "",
email: "",
phone: "",
role: "",
message: "",
sitemapFile: null,

View File

@@ -1,89 +1,90 @@
export type ProjectType = 'website' | 'web-app';
export type ProjectType = "website" | "web-app";
export interface FormState {
projectType: ProjectType;
// Company
companyName: string;
employeeCount: string;
// Existing Presence
existingWebsite: string;
socialMedia: string[];
socialMediaUrls: Record<string, string>;
existingDomain: string;
wishedDomain: string;
// Project
websiteTopic: string;
selectedPages: string[];
otherPages: string[];
otherPagesCount: number;
features: string[];
otherFeatures: string[];
otherFeaturesCount: number;
functions: string[];
otherFunctions: string[];
otherFunctionsCount: number;
apiSystems: string[];
otherTech: string[];
otherTechCount: number;
assets: string[];
otherAssets: string[];
otherAssetsCount: number;
newDatasets: number;
cmsSetup: boolean;
storageExpansion: number;
name: string;
email: string;
role: string;
message: string;
sitemapFile: any; // Using any for File/null to be CLI-compatible
contactFiles: any[]; // Using any[] for File[]
// Design
designVibe: string;
colorScheme: string[];
references: string[];
designWishes: string;
// Maintenance
expectedAdjustments: string;
languagesList: string[];
// Timeline
deadline: string;
// Web App specific
targetAudience: string;
userRoles: string[];
dataSensitivity: string;
platformType: string;
// Meta
dontKnows: string[];
visualStaging: string;
complexInteractions: string;
gridDontKnows?: Record<string, string>;
briefingSummary?: string;
companyAddress?: string;
companyPhone?: string;
personName?: string;
taxId?: string;
designVision?: string;
positionDescriptions?: Record<string, string>;
sitemap?: {
category: string;
pages: { title: string; desc: string }[];
}[];
projectType: ProjectType;
// Company
companyName: string;
employeeCount: string;
// Existing Presence
existingWebsite: string;
socialMedia: string[];
socialMediaUrls: Record<string, string>;
existingDomain: string;
wishedDomain: string;
// Project
websiteTopic: string;
selectedPages: string[];
otherPages: string[];
otherPagesCount: number;
features: string[];
otherFeatures: string[];
otherFeaturesCount: number;
functions: string[];
otherFunctions: string[];
otherFunctionsCount: number;
apiSystems: string[];
otherTech: string[];
otherTechCount: number;
assets: string[];
otherAssets: string[];
otherAssetsCount: number;
newDatasets: number;
cmsSetup: boolean;
storageExpansion: number;
name: string;
email: string;
phone: string;
role: string;
message: string;
sitemapFile: any; // Using any for File/null to be CLI-compatible
contactFiles: any[]; // Using any[] for File[]
// Design
designVibe: string;
colorScheme: string[];
references: string[];
designWishes: string;
// Maintenance
expectedAdjustments: string;
languagesList: string[];
// Timeline
deadline: string;
// Web App specific
targetAudience: string;
userRoles: string[];
dataSensitivity: string;
platformType: string;
// Meta
dontKnows: string[];
visualStaging: string;
complexInteractions: string;
gridDontKnows?: Record<string, string>;
briefingSummary?: string;
companyAddress?: string;
companyPhone?: string;
personName?: string;
taxId?: string;
designVision?: string;
positionDescriptions?: Record<string, string>;
sitemap?: {
category: string;
pages: { title: string; desc: string }[];
}[];
}
export interface Position {
pos: number;
title: string;
desc: string;
qty: number;
price: number;
isRecurring?: boolean;
pos: number;
title: string;
desc: string;
qty: number;
price: number;
isRecurring?: boolean;
}
export interface Totals {
totalPrice: number;
monthlyPrice: number;
totalPagesCount: number;
totalFeatures: number;
totalFunctions: number;
totalApis: number;
languagesCount: number;
totalPrice: number;
monthlyPrice: number;
totalPagesCount: number;
totalFeatures: number;
totalFunctions: number;
totalApis: number;
languagesCount: number;
}

View File

@@ -62,10 +62,22 @@ export const Inquiries: CollectionConfig = {
name: "companyName",
type: "text",
},
{
name: "phone",
type: "text",
},
{
name: "role",
type: "text",
},
{
name: "projectType",
type: "text",
},
{
name: "deadline",
type: "text",
},
{
name: "message",
type: "textarea",

View File

@@ -31,6 +31,7 @@ services:
# - CI=true
- NPM_TOKEN=${NPM_TOKEN:-}
- DATABASE_URI=postgres://${postgres_DB_USER:-payload}:${postgres_DB_PASSWORD:-payload}@postgres-db:5432/${postgres_DB_NAME:-payload}
- QDRANT_URL=http://qdrant:6333
- PAYLOAD_SECRET=dev-secret
command: >
sh -c "pnpm install --no-frozen-lockfile && pnpm --filter @mintel/web dev"

36
scripts/visual_debug.mjs Normal file
View File

@@ -0,0 +1,36 @@
import puppeteer from 'puppeteer';
(async () => {
// Launch browser
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// Set viewport
await page.setViewport({ width: 1920, height: 1080 });
try {
console.log("Navigating to case study page...");
await page.goto('https://mintel.me/case-studies/klz-cables', { waitUntil: 'networkidle2' });
console.log("Waiting a bit for iframes to load...");
await new Promise(r => setTimeout(r, 5000));
// Let's get console logs from the page too
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
console.log("Taking screenshot...");
await page.screenshot({ path: '/Users/marcmintel/Projects/mintel.me/screenshot_case_study.png', fullPage: true });
// Also take a screenshot of the actual showcase HTML page directly to compare
console.log("Navigating directly to showcase HTML...");
await page.goto('https://mintel.me/showcase/klz-cables.com/power-cables-medium-voltage-cables.html', { waitUntil: 'networkidle2' });
await new Promise(r => setTimeout(r, 3000));
await page.screenshot({ path: '/Users/marcmintel/Projects/mintel.me/screenshot_showcase.png', fullPage: true });
console.log("Done!");
} catch (e) {
console.error("Error:", e);
} finally {
await browser.close();
}
})();

1
wget_out.txt Normal file
View File

@@ -0,0 +1 @@
zsh:1: command not found: wget