Files
manoon-headless/src/emails/OrderConfirmation.tsx
T
Unchained 5576946829
Build and Deploy / build (push) Has been cancelled
feat(emails): implement transactional email system with Resend
- Add Resend email integration with @react-email/render
- Create email templates: OrderConfirmation, OrderShipped, OrderCancelled, OrderPaid
- Implement webhook handler for ORDER_CREATED and other events
- Add multi-language support for customer emails
- Admin emails in English with order details
- Update checkout page with auto-scroll on order completion
- Configure DASHBOARD_URL environment variable
2026-03-25 14:13:34 +02:00

395 lines
11 KiB
TypeScript

import { Button, Hr, Section, Text } from "@react-email/components";
import { BaseLayout } from "./BaseLayout";
interface OrderItem {
id: string;
name: string;
quantity: number;
price: string;
}
interface OrderConfirmationProps {
language: string;
orderId: string;
orderNumber: string;
customerEmail: string;
customerName: string;
items: OrderItem[];
total: string;
shippingAddress?: string;
billingAddress?: string;
phone?: string;
siteUrl: string;
dashboardUrl?: string;
isAdmin?: boolean;
}
const translations: Record<
string,
{
title: string;
preview: string;
greeting: string;
orderReceived: string;
orderNumber: string;
items: string;
quantity: string;
total: string;
shippingTo: string;
questions: string;
thankYou: string;
adminTitle: string;
adminPreview: string;
adminGreeting: string;
adminMessage: string;
customerLabel: string;
customerEmailLabel: string;
billingAddressLabel: string;
phoneLabel: string;
viewDashboard: string;
}
> = {
sr: {
title: "Potvrda narudžbine",
preview: "Vaša narudžbina je potvrđena",
greeting: "Poštovani {name},",
orderReceived: "Zahvaljujemo se na Vašoj narudžbini! Primili smo je i sada je u pripremi.",
orderNumber: "Broj narudžbine",
items: "Artikli",
quantity: "Količina",
total: "Ukupno",
shippingTo: "Adresa za dostavu",
questions: "Imate pitanja? Pišite nam na [email protected]",
thankYou: "Hvala Vam što kupujete kod nas!",
adminTitle: "Nova narudžbina!",
adminPreview: "Nova narudžbina je primljena",
adminGreeting: "Čestitamo na prodaji!",
adminMessage: "Nova narudžbina je upravo primljena. Detalji su ispod:",
customerLabel: "Kupac",
customerEmailLabel: "Email kupca",
billingAddressLabel: "Adresa za naplatu",
phoneLabel: "Telefon",
viewDashboard: "Pogledaj u Dashboardu",
},
en: {
title: "Order Confirmation",
preview: "Your order has been confirmed",
greeting: "Dear {name},",
orderReceived:
"Thank you for your order! We have received it and it is now being processed.",
orderNumber: "Order number",
items: "Items",
quantity: "Quantity",
total: "Total",
shippingTo: "Shipping address",
questions: "Questions? Email us at [email protected]",
thankYou: "Thank you for shopping with us!",
adminTitle: "New Order! 🎉",
adminPreview: "A new order has been received",
adminGreeting: "Congratulations on the sale!",
adminMessage: "A new order has just been placed. Details below:",
customerLabel: "Customer",
customerEmailLabel: "Customer Email",
billingAddressLabel: "Billing Address",
phoneLabel: "Phone",
viewDashboard: "View in Dashboard",
},
de: {
title: "Bestellungsbestätigung",
preview: "Ihre Bestellung wurde bestätigt",
greeting: "Sehr geehrte/r {name},",
orderReceived:
"Vielen Dank für Ihre Bestellung! Wir haben sie erhalten und sie wird nun bearbeitet.",
orderNumber: "Bestellnummer",
items: "Artikel",
quantity: "Menge",
total: "Gesamt",
shippingTo: "Lieferadresse",
questions: "Fragen? Schreiben Sie uns an [email protected]",
thankYou: "Vielen Dank für Ihren Einkauf!",
adminTitle: "Neue Bestellung! 🎉",
adminPreview: "Eine neue Bestellung wurde erhalten",
adminGreeting: "Glückwunsch zum Verkauf!",
adminMessage: "Eine neue Bestellung wurde soeben aufgegeben. Details unten:",
customerLabel: "Kunde",
customerEmailLabel: "Kunden-E-Mail",
billingAddressLabel: "Rechnungsadresse",
phoneLabel: "Telefon",
viewDashboard: "Im Dashboard anzeigen",
},
fr: {
title: "Confirmation de commande",
preview: "Votre commande a été confirmée",
greeting: "Cher(e) {name},",
orderReceived:
"Merci pour votre commande! Nous l'avons reçue et elle est en cours de traitement.",
orderNumber: "Numéro de commande",
items: "Articles",
quantity: "Quantité",
total: "Total",
shippingTo: "Adresse de livraison",
questions: "Questions? Écrivez-nous à [email protected]",
thankYou: "Merci d'avoir Magasiné avec nous!",
adminTitle: "Nouvelle commande! 🎉",
adminPreview: "Une nouvelle commande a été reçue",
adminGreeting: "Félicitations pour la vente!",
adminMessage: "Une nouvelle commande vient d'être passée. Détails ci-dessous:",
customerLabel: "Client",
customerEmailLabel: "Email du client",
billingAddressLabel: "Adresse de facturation",
phoneLabel: "Téléphone",
viewDashboard: "Voir dans le Dashboard",
},
};
export function OrderConfirmation({
language = "en",
orderId,
orderNumber,
customerEmail,
customerName,
items,
total,
shippingAddress,
billingAddress,
phone,
siteUrl,
dashboardUrl,
isAdmin = false,
}: OrderConfirmationProps) {
const t = translations[language] || translations.en;
// For admin emails, always use English
const adminT = translations["en"];
if (isAdmin) {
return (
<BaseLayout previewText={adminT.adminPreview} language="en" siteUrl={siteUrl}>
<Text style={styles.title}>{adminT.adminTitle}</Text>
<Text style={styles.greeting}>{adminT.adminGreeting}</Text>
<Text style={styles.text}>{adminT.adminMessage}</Text>
<Section style={styles.orderInfo}>
<Text style={styles.orderNumber}>
<strong>{adminT.orderNumber}:</strong> {orderNumber}
</Text>
<Text style={styles.customerInfo}>
<strong>{adminT.customerLabel}:</strong> {customerName}
</Text>
<Text style={styles.customerInfo}>
<strong>{adminT.customerEmailLabel}:</strong> {customerEmail}
</Text>
{phone && (
<Text style={styles.customerInfo}>
<strong>{adminT.phoneLabel}:</strong> {phone}
</Text>
)}
</Section>
<Section style={styles.itemsSection}>
<Text style={styles.sectionTitle}>{adminT.items}</Text>
<Hr style={styles.hr} />
{items.map((item) => (
<Section key={item.id} style={styles.itemRow}>
<Text style={styles.itemName}>
{item.quantity}x {item.name}
</Text>
<Text style={styles.itemPrice}>{item.price}</Text>
</Section>
))}
<Hr style={styles.hr} />
<Section style={styles.totalRow}>
<Text style={styles.totalLabel}>{adminT.total}:</Text>
<Text style={styles.totalValue}>{total}</Text>
</Section>
</Section>
{shippingAddress && (
<Section style={styles.shippingSection}>
<Text style={styles.sectionTitle}>{adminT.shippingTo}</Text>
<Text style={styles.shippingAddress}>{shippingAddress}</Text>
</Section>
)}
{billingAddress && (
<Section style={styles.shippingSection}>
<Text style={styles.sectionTitle}>{adminT.billingAddressLabel}</Text>
<Text style={styles.shippingAddress}>{billingAddress}</Text>
</Section>
)}
<Section style={styles.buttonSection}>
<Button href={`${dashboardUrl}/orders/${orderId}`} style={styles.button}>
{adminT.viewDashboard}
</Button>
</Section>
</BaseLayout>
);
}
return (
<BaseLayout previewText={t.preview} language={language} siteUrl={siteUrl}>
<Text style={styles.title}>{t.title}</Text>
<Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text>
<Text style={styles.text}>{t.orderReceived}</Text>
<Section style={styles.orderInfo}>
<Text style={styles.orderNumber}>
<strong>{t.orderNumber}:</strong> {orderNumber}
</Text>
</Section>
<Section style={styles.itemsSection}>
<Text style={styles.sectionTitle}>{t.items}</Text>
<Hr style={styles.hr} />
{items.map((item) => (
<Section key={item.id} style={styles.itemRow}>
<Text style={styles.itemName}>
{item.quantity}x {item.name}
</Text>
<Text style={styles.itemPrice}>{item.price}</Text>
</Section>
))}
<Hr style={styles.hr} />
<Section style={styles.totalRow}>
<Text style={styles.totalLabel}>{t.total}:</Text>
<Text style={styles.totalValue}>{total}</Text>
</Section>
</Section>
{shippingAddress && (
<Section style={styles.shippingSection}>
<Text style={styles.sectionTitle}>{t.shippingTo}</Text>
<Text style={styles.shippingAddress}>{shippingAddress}</Text>
</Section>
)}
<Section style={styles.buttonSection}>
<Button href={siteUrl} style={styles.button}>
{language === "sr"
? "Pogledajte narudžbinu"
: language === "de"
? "Bestellung ansehen"
: language === "fr"
? "Voir la commande"
: "View Order"}
</Button>
</Section>
<Text style={styles.questions}>{t.questions}</Text>
<Text style={styles.thankYou}>{t.thankYou}</Text>
</BaseLayout>
);
}
const styles = {
title: {
fontSize: "24px",
fontWeight: "bold" as const,
color: "#1a1a1a",
marginBottom: "20px",
},
greeting: {
fontSize: "16px",
color: "#333333",
marginBottom: "10px",
},
text: {
fontSize: "14px",
color: "#666666",
marginBottom: "20px",
},
orderInfo: {
backgroundColor: "#f9f9f9",
padding: "15px",
borderRadius: "8px",
marginBottom: "20px",
},
orderNumber: {
fontSize: "14px",
color: "#333333",
margin: "0 0 8px 0",
},
customerInfo: {
fontSize: "14px",
color: "#333333",
margin: "0 0 4px 0",
},
itemsSection: {
marginBottom: "20px",
},
sectionTitle: {
fontSize: "16px",
fontWeight: "bold" as const,
color: "#1a1a1a",
marginBottom: "10px",
},
hr: {
borderColor: "#e0e0e0",
margin: "10px 0",
},
itemRow: {
display: "flex" as const,
justifyContent: "space-between" as const,
padding: "8px 0",
},
itemName: {
fontSize: "14px",
color: "#333333",
margin: "0",
},
itemPrice: {
fontSize: "14px",
color: "#333333",
margin: "0",
},
totalRow: {
display: "flex" as const,
justifyContent: "space-between" as const,
padding: "8px 0",
},
totalLabel: {
fontSize: "16px",
fontWeight: "bold" as const,
color: "#1a1a1a",
margin: "0",
},
totalValue: {
fontSize: "16px",
fontWeight: "bold" as const,
color: "#1a1a1a",
margin: "0",
},
shippingSection: {
marginBottom: "20px",
},
shippingAddress: {
fontSize: "14px",
color: "#666666",
margin: "0",
},
buttonSection: {
textAlign: "center" as const,
marginBottom: "20px",
},
button: {
backgroundColor: "#000000",
color: "#ffffff",
padding: "12px 30px",
borderRadius: "4px",
fontSize: "14px",
fontWeight: "bold" as const,
textDecoration: "none",
},
questions: {
fontSize: "14px",
color: "#666666",
marginBottom: "10px",
},
thankYou: {
fontSize: "14px",
fontWeight: "bold" as const,
color: "#1a1a1a",
},
};