Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m53s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
- implemented BrochureDeliveryEmail template - created AutoBrochureModal wrapper with 5s delay - updated layout.tsx and BrochureCTA to use new success state - added tests/brochure-modal.test.ts e2e test
225 lines
7.9 KiB
TypeScript
225 lines
7.9 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { useTranslations, useLocale } from 'next-intl';
|
|
import { cn } from '@/components/ui/utils';
|
|
import { requestBrochureAction } from '@/app/actions/brochure';
|
|
import { useAnalytics } from './analytics/useAnalytics';
|
|
import { AnalyticsEvents } from './analytics/analytics-events';
|
|
|
|
interface BrochureModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function BrochureModal({ isOpen, onClose }: BrochureModalProps) {
|
|
const t = useTranslations('Brochure');
|
|
const locale = useLocale();
|
|
const { trackEvent } = useAnalytics();
|
|
const formRef = useRef<HTMLFormElement>(null);
|
|
const [state, setState] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
|
|
const [errorMsg, setErrorMsg] = useState('');
|
|
|
|
// Close on escape + lock scroll
|
|
useEffect(() => {
|
|
const handleEsc = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
if (isOpen) {
|
|
document.addEventListener('keydown', handleEsc);
|
|
document.body.style.overflow = 'hidden';
|
|
} else {
|
|
document.body.style.overflow = '';
|
|
}
|
|
return () => {
|
|
document.removeEventListener('keydown', handleEsc);
|
|
document.body.style.overflow = '';
|
|
};
|
|
}, [isOpen, onClose]);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!formRef.current) return;
|
|
|
|
setState('submitting');
|
|
setErrorMsg('');
|
|
|
|
try {
|
|
const formData = new FormData(formRef.current);
|
|
formData.set('locale', locale);
|
|
|
|
const result = await requestBrochureAction(formData);
|
|
|
|
if (result.success) {
|
|
setState('success');
|
|
trackEvent(AnalyticsEvents.DOWNLOAD, {
|
|
file_name: `klz-product-catalog-${locale}.pdf`,
|
|
file_type: 'brochure',
|
|
location: 'brochure_modal',
|
|
});
|
|
} else {
|
|
setState('error');
|
|
setErrorMsg(result.error || 'Something went wrong');
|
|
}
|
|
} catch {
|
|
setState('error');
|
|
setErrorMsg('Network error');
|
|
}
|
|
};
|
|
|
|
const handleClose = () => {
|
|
setState('idle');
|
|
setErrorMsg('');
|
|
onClose();
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const modal = (
|
|
<div
|
|
className="fixed inset-0 z-[9999] flex items-center justify-center p-4"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
>
|
|
{/* Backdrop */}
|
|
<div
|
|
className="absolute inset-0 bg-black/70 backdrop-blur-sm"
|
|
onClick={handleClose}
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
{/* Modal Panel */}
|
|
<div className="relative z-10 w-full max-w-md rounded-[28px] bg-[#000d26] border border-white/10 shadow-[0_40px_80px_rgba(0,0,0,0.6)] overflow-hidden">
|
|
{/* Accent bar at top */}
|
|
<div className="h-1 w-full bg-gradient-to-r from-[#82ed20] via-[#5cb516] to-[#82ed20]" />
|
|
|
|
{/* Close Button */}
|
|
<button
|
|
type="button"
|
|
onClick={handleClose}
|
|
className="absolute top-4 right-4 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/5 text-white/40 hover:bg-white/10 hover:text-white transition-colors"
|
|
aria-label={t('close')}
|
|
>
|
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
|
|
<div className="p-8 pt-7">
|
|
{/* Icon + Header */}
|
|
<div className="mb-7">
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[#82ed20]/10 border border-[#82ed20]/20 mb-4">
|
|
<svg
|
|
className="h-6 w-6 text-[#82ed20]"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={1.5}
|
|
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h2 className="text-2xl font-black text-white uppercase tracking-tight leading-none mb-2">
|
|
{t('title')}
|
|
</h2>
|
|
<p className="text-sm text-white/50 leading-relaxed">{t('subtitle')}</p>
|
|
</div>
|
|
|
|
{state === 'success' ? (
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-6 p-4 rounded-2xl bg-[#82ed20]/10 border border-[#82ed20]/20">
|
|
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-[#82ed20]/20">
|
|
<svg
|
|
className="h-5 w-5 text-[#82ed20]"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M5 13l4 4L19 7"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-[#82ed20]">
|
|
{locale === 'de' ? 'Erfolgreich gesendet' : 'Successfully sent'}
|
|
</p>
|
|
<p className="text-xs text-white/50 mt-0.5">
|
|
{locale === 'de'
|
|
? 'Bitte prüfen Sie Ihren Posteingang.'
|
|
: 'Please check your inbox.'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={handleClose}
|
|
className="flex items-center justify-center gap-3 w-full py-4 px-6 rounded-2xl bg-white/10 hover:bg-white/20 text-white font-black text-sm uppercase tracking-widest transition-colors"
|
|
>
|
|
{t('close')}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<form ref={formRef} onSubmit={handleSubmit}>
|
|
<div className="mb-5">
|
|
<label
|
|
htmlFor="brochure-email"
|
|
className="block text-[10px] font-black uppercase tracking-[0.2em] text-white/40 mb-2"
|
|
>
|
|
{t('emailLabel')}
|
|
</label>
|
|
<input
|
|
id="brochure-email"
|
|
name="email"
|
|
type="email"
|
|
required
|
|
autoComplete="email"
|
|
placeholder={t('emailPlaceholder')}
|
|
className="w-full rounded-xl bg-white/5 border border-white/10 px-4 py-3.5 text-white placeholder:text-white/20 text-sm font-medium focus:outline-none focus:border-[#82ed20]/40 transition-colors"
|
|
disabled={state === 'submitting'}
|
|
/>
|
|
</div>
|
|
|
|
{state === 'error' && errorMsg && (
|
|
<p className="text-red-400 text-xs mb-4 font-medium">{errorMsg}</p>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={state === 'submitting'}
|
|
className={cn(
|
|
'w-full py-4 px-6 rounded-2xl font-black text-sm uppercase tracking-widest transition-colors',
|
|
state === 'submitting'
|
|
? 'bg-white/10 text-white/40 cursor-wait'
|
|
: 'bg-[#82ed20] hover:bg-[#6dd318] text-[#000d26]',
|
|
)}
|
|
>
|
|
{state === 'submitting' ? t('submitting') : t('submit')}
|
|
</button>
|
|
|
|
<p className="mt-4 text-[10px] text-white/25 text-center leading-relaxed">
|
|
{t('privacyNote')}
|
|
</p>
|
|
</form>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return createPortal(modal, document.body);
|
|
}
|