Compare commits

...

3 Commits

Author SHA1 Message Date
e3f7344daf chore: traefik labels
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 10s
Build & Deploy KLZ Cables / 🏗️ Build App (push) Successful in 26s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Failing after 1m34s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Has been skipped
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Has been skipped
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 2s
2026-02-06 18:36:34 +01:00
21a7b0ade2 feat: Implement replyTo for contact form emails and refine success/error message layout. 2026-02-06 18:24:58 +01:00
d027fbeac2 chore: Standardize error message extraction in contact form actions and mailer, and add Directus restart to the Directus sync script.
All checks were successful
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 14s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Has been skipped
Build & Deploy KLZ Cables / 🏗️ Build App (push) Has been skipped
Build & Deploy KLZ Cables / 🚀 Deploy (push) Has been skipped
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Has been skipped
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 2s
2026-02-06 17:55:43 +01:00
6 changed files with 61 additions and 34 deletions

View File

@@ -71,6 +71,7 @@ export async function sendContactFormAction(formData: FormData) {
); );
const notificationResult = await sendEmail({ const notificationResult = await sendEmail({
replyTo: email,
subject: notificationSubject, subject: notificationSubject,
html: notificationHtml, html: notificationHtml,
}); });
@@ -111,13 +112,20 @@ export async function sendContactFormAction(formData: FormData) {
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
logger.error('Failed to send branded emails', { error }); const errorMsg = error instanceof Error ? error.message : String(error);
logger.error('Failed to send branded emails', {
error: errorMsg,
stack: error instanceof Error ? error.stack : undefined,
});
services.errors.captureException(error, { action: 'sendContactFormAction', email }); services.errors.captureException(error, { action: 'sendContactFormAction', email });
await services.notifications.notify({ await services.notifications.notify({
title: '🚨 Contact Form Error', title: '🚨 Contact Form Error',
message: `Failed to send emails for ${name} (${email}). Error: ${JSON.stringify(error)}`, message: `Failed to send emails for ${name} (${email}). Error: ${errorMsg}`,
priority: 8, priority: 8,
}); });
return { success: false, error };
return { success: false, error: errorMsg };
} }
} }

View File

@@ -20,7 +20,7 @@ export default function ContactForm() {
try { try {
const result = await sendContactFormAction(formData); const result = await sendContactFormAction(formData);
if (result.success) { if (result?.success) {
trackEvent('contact_form_submission', { trackEvent('contact_form_submission', {
form_type: 'general', form_type: 'general',
email, email,

View File

@@ -49,21 +49,28 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
if (status === 'success') { if (status === 'success') {
return ( return (
<div className="bg-accent/5 border border-accent/20 text-primary-dark p-4 rounded-xl text-center flex flex-col items-center animate-fade-in !mt-0"> <div className="bg-accent/5 border border-accent/20 text-primary-dark p-4 rounded-xl text-center animate-fade-in !mt-0 w-full">
<div className="w-10 h-10 bg-accent rounded-full flex items-center justify-center mx-auto mb-3 shadow-lg shadow-accent/20"> <div className="flex justify-center mb-3">
<svg <div className="w-10 h-10 bg-accent rounded-full flex items-center justify-center shadow-lg shadow-accent/20">
className="w-5 h-5 text-primary-dark" <svg
fill="none" className="w-5 h-5 text-primary-dark"
stroke="currentColor" fill="none"
viewBox="0 0 24 24" stroke="currentColor"
> viewBox="0 0 24 24"
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" /> >
</svg> <path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M5 13l4 4L19 7"
/>
</svg>
</div>
</div> </div>
<h3 className="text-base font-extrabold mb-1 tracking-tight !mt-0 text-center"> <h3 className="text-base font-extrabold mb-1 tracking-tight !mt-0 text-center w-full">
{t('successTitle')} {t('successTitle')}
</h3> </h3>
<p className="text-text-secondary text-xs leading-tight mb-4 !mt-0 text-center"> <p className="text-text-secondary text-xs leading-tight mb-4 !mt-0 text-center w-full">
{t('successDesc', { productName })} {t('successDesc', { productName })}
</p> </p>
<button <button
@@ -80,24 +87,26 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
if (status === 'error') { if (status === 'error') {
return ( return (
<div className="bg-destructive/5 border border-destructive/20 text-destructive p-4 rounded-xl text-center flex flex-col items-center animate-fade-in !mt-0"> <div className="bg-destructive/5 border border-destructive/20 text-destructive p-4 rounded-xl text-center animate-fade-in !mt-0 w-full">
<div className="w-10 h-10 bg-destructive rounded-full flex items-center justify-center mx-auto mb-3 shadow-lg shadow-destructive/20"> <div className="flex justify-center mb-3">
<svg <div className="w-10 h-10 bg-destructive rounded-full flex items-center justify-center shadow-lg shadow-destructive/20">
className="w-5 h-5 text-destructive-foreground" <svg
fill="none" className="w-5 h-5 text-destructive-foreground"
viewBox="0 0 24 24" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
strokeWidth="3" stroke="currentColor"
> strokeWidth="3"
<circle cx="12" cy="12" r="10" /> >
<line x1="15" y1="9" x2="9" y2="15" /> <circle cx="12" cy="12" r="10" />
<line x1="9" y1="9" x2="15" y2="15" /> <line x1="15" y1="9" x2="9" y2="15" />
</svg> <line x1="9" y1="9" x2="15" y2="15" />
</svg>
</div>
</div> </div>
<h3 className="text-base font-extrabold mb-1 tracking-tight !mt-0 text-destructive text-center"> <h3 className="text-base font-extrabold mb-1 tracking-tight !mt-0 text-destructive text-center w-full">
{t('errorTitle') || 'Submission Failed'} {t('errorTitle') || 'Submission Failed'}
</h3> </h3>
<p className="text-destructive/80 text-xs leading-tight mb-4 !mt-0 text-center"> <p className="text-destructive/80 text-xs leading-tight mb-4 !mt-0 text-center w-full">
{t('errorDesc') || 'Something went wrong. Please try again.'} {t('errorDesc') || 'Something went wrong. Please try again.'}
</p> </p>
<Button <Button

View File

@@ -41,6 +41,7 @@ services:
- "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-forward.headers.customrequestheaders.X-Forwarded-Ssl=on" - "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-forward.headers.customrequestheaders.X-Forwarded-Ssl=on"
# Middlewares # Middlewares
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}.middlewares=${PROJECT_NAME:-klz-cables}-ratelimit,${PROJECT_NAME:-klz-cables}-forward,${AUTH_MIDDLEWARE:-compress}" - "traefik.http.routers.${PROJECT_NAME:-klz-cables}.middlewares=${PROJECT_NAME:-klz-cables}-ratelimit,${PROJECT_NAME:-klz-cables}-forward,${AUTH_MIDDLEWARE:-compress}"
- "traefik.docker.network=infra"
# Gatekeeper Router (to show the login page) # Gatekeeper Router (to show the login page)
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-gatekeeper.rule=Host(`gatekeeper.${TRAEFIK_HOST}`)" - "traefik.http.routers.${PROJECT_NAME:-klz-cables}-gatekeeper.rule=Host(`gatekeeper.${TRAEFIK_HOST}`)"
@@ -74,6 +75,7 @@ services:
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.services.${PROJECT_NAME:-klz-cables}-gatekeeper.loadbalancer.server.port=3000" - "traefik.http.services.${PROJECT_NAME:-klz-cables}-gatekeeper.loadbalancer.server.port=3000"
- "traefik.docker.network=infra"
directus: directus:
image: directus/directus:11 image: directus/directus:11
@@ -111,6 +113,7 @@ services:
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-directus.tls=true" - "traefik.http.routers.${PROJECT_NAME:-klz-cables}-directus.tls=true"
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-directus.middlewares=${PROJECT_NAME:-klz-cables}-forward,compress" - "traefik.http.routers.${PROJECT_NAME:-klz-cables}-directus.middlewares=${PROJECT_NAME:-klz-cables}-forward,compress"
- "traefik.http.services.${PROJECT_NAME:-klz-cables}-directus.loadbalancer.server.port=8055" - "traefik.http.services.${PROJECT_NAME:-klz-cables}-directus.loadbalancer.server.port=8055"
- "traefik.docker.network=infra"
directus-db: directus-db:
image: postgres:15-alpine image: postgres:15-alpine

View File

@@ -27,16 +27,18 @@ function getTransporter() {
interface SendEmailOptions { interface SendEmailOptions {
to?: string | string[]; to?: string | string[];
replyTo?: string;
subject: string; subject: string;
html: string; html: string;
} }
export async function sendEmail({ to, subject, html }: SendEmailOptions) { export async function sendEmail({ to, replyTo, subject, html }: SendEmailOptions) {
const recipients = to || config.mail.recipients; const recipients = to || config.mail.recipients;
const mailOptions = { const mailOptions = {
from: config.mail.from, from: config.mail.from,
to: recipients, to: recipients,
replyTo,
subject, subject,
html, html,
}; };
@@ -48,7 +50,8 @@ export async function sendEmail({ to, subject, html }: SendEmailOptions) {
logger.info('Email sent successfully', { messageId: info.messageId, subject, recipients }); logger.info('Email sent successfully', { messageId: info.messageId, subject, recipients });
return { success: true, messageId: info.messageId }; return { success: true, messageId: info.messageId };
} catch (error) { } catch (error) {
logger.error('Error sending email', { error, subject, recipients }); const errorMsg = error instanceof Error ? error.message : String(error);
return { success: false, error }; logger.error('Error sending email', { error: errorMsg, subject, recipients });
return { success: false, error: errorMsg };
} }
} }

View File

@@ -97,6 +97,10 @@ if [ "$ACTION" == "push" ]; then
rm dump.sql rm dump.sql
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql" ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
# 5. Restart Directus to trigger migrations and refresh schema cache
echo "🔄 Restarting remote Directus to apply migrations..."
ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME restart directus"
echo "✨ Push to $ENV complete!" echo "✨ Push to $ENV complete!"
elif [ "$ACTION" == "pull" ]; then elif [ "$ACTION" == "pull" ]; then