refactor: abstract site URL across email templates

- Add NEXT_PUBLIC_SITE_URL to .env.local
- Update email templates to accept siteUrl prop
- Update webhook handler to pass siteUrl from env var
- Update create-webhooks.graphql with placeholder URL
This commit is contained in:
Unchained
2026-03-25 10:33:03 +02:00
parent b8b3a57e6f
commit 4fcd4b3ba8
7 changed files with 36 additions and 13 deletions

View File

@@ -6,6 +6,8 @@ import { OrderShipped } from "@/emails/OrderShipped";
import { OrderCancelled } from "@/emails/OrderCancelled"; import { OrderCancelled } from "@/emails/OrderCancelled";
import { OrderPaid } from "@/emails/OrderPaid"; import { OrderPaid } from "@/emails/OrderPaid";
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
interface SaleorWebhookHeaders { interface SaleorWebhookHeaders {
"saleor-event": string; "saleor-event": string;
"saleor-domain": string; "saleor-domain": string;
@@ -159,6 +161,7 @@ async function handleOrderConfirmed(order: SaleorOrder) {
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
total: formatPrice(order.total.gross.amount, currency), total: formatPrice(order.total.gross.amount, currency),
shippingAddress: formatAddress(order.shippingAddress), shippingAddress: formatAddress(order.shippingAddress),
siteUrl: SITE_URL,
}), }),
language, language,
idempotencyKey: `order-confirmed/${order.id}`, idempotencyKey: `order-confirmed/${order.id}`,
@@ -175,6 +178,7 @@ async function handleOrderConfirmed(order: SaleorOrder) {
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
total: formatPrice(order.total.gross.amount, currency), total: formatPrice(order.total.gross.amount, currency),
shippingAddress: formatAddress(order.shippingAddress), shippingAddress: formatAddress(order.shippingAddress),
siteUrl: SITE_URL,
}), }),
eventType: "ORDER_CONFIRMED", eventType: "ORDER_CONFIRMED",
orderId: order.id, orderId: order.id,
@@ -219,6 +223,7 @@ async function handleOrderFulfilled(order: SaleorOrder) {
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
trackingNumber, trackingNumber,
trackingUrl, trackingUrl,
siteUrl: SITE_URL,
}), }),
language, language,
idempotencyKey: `order-fulfilled/${order.id}`, idempotencyKey: `order-fulfilled/${order.id}`,
@@ -234,6 +239,7 @@ async function handleOrderFulfilled(order: SaleorOrder) {
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
trackingNumber, trackingNumber,
trackingUrl, trackingUrl,
siteUrl: SITE_URL,
}), }),
eventType: "ORDER_FULFILLED", eventType: "ORDER_FULFILLED",
orderId: order.id, orderId: order.id,
@@ -272,6 +278,7 @@ async function handleOrderCancelled(order: SaleorOrder) {
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
total: formatPrice(order.total.gross.amount, currency), total: formatPrice(order.total.gross.amount, currency),
reason, reason,
siteUrl: SITE_URL,
}), }),
language, language,
idempotencyKey: `order-cancelled/${order.id}`, idempotencyKey: `order-cancelled/${order.id}`,
@@ -287,6 +294,7 @@ async function handleOrderCancelled(order: SaleorOrder) {
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
total: formatPrice(order.total.gross.amount, currency), total: formatPrice(order.total.gross.amount, currency),
reason, reason,
siteUrl: SITE_URL,
}), }),
eventType: "ORDER_CANCELLED", eventType: "ORDER_CANCELLED",
orderId: order.id, orderId: order.id,
@@ -316,6 +324,7 @@ async function handleOrderFullyPaid(order: SaleorOrder) {
customerName, customerName,
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
total: formatPrice(order.total.gross.amount, currency), total: formatPrice(order.total.gross.amount, currency),
siteUrl: SITE_URL,
}), }),
language, language,
idempotencyKey: `order-paid/${order.id}`, idempotencyKey: `order-paid/${order.id}`,
@@ -330,6 +339,7 @@ async function handleOrderFullyPaid(order: SaleorOrder) {
customerName, customerName,
items: parseOrderItems(order.lines, currency), items: parseOrderItems(order.lines, currency),
total: formatPrice(order.total.gross.amount, currency), total: formatPrice(order.total.gross.amount, currency),
siteUrl: SITE_URL,
}), }),
eventType: "ORDER_FULLY_PAID", eventType: "ORDER_FULLY_PAID",
orderId: order.id, orderId: order.id,

View File

@@ -16,6 +16,7 @@ interface BaseLayoutProps {
children: React.ReactNode; children: React.ReactNode;
previewText: string; previewText: string;
language: string; language: string;
siteUrl: string;
} }
const translations: Record<string, { footer: string; company: string }> = { const translations: Record<string, { footer: string; company: string }> = {
@@ -37,7 +38,7 @@ const translations: Record<string, { footer: string; company: string }> = {
}, },
}; };
export function BaseLayout({ children, previewText, language }: BaseLayoutProps) { export function BaseLayout({ children, previewText, language, siteUrl }: BaseLayoutProps) {
const t = translations[language] || translations.en; const t = translations[language] || translations.en;
return ( return (
@@ -48,7 +49,7 @@ export function BaseLayout({ children, previewText, language }: BaseLayoutProps)
<Container style={styles.container}> <Container style={styles.container}>
<Section style={styles.logoSection}> <Section style={styles.logoSection}>
<Img <Img
src="https://manoonoils.com/logo.png" src={`${siteUrl}/logo.png`}
width="150" width="150"
height="auto" height="auto"
alt="ManoonOils" alt="ManoonOils"

View File

@@ -16,6 +16,7 @@ interface OrderCancelledProps {
items: OrderItem[]; items: OrderItem[];
total: string; total: string;
reason?: string; reason?: string;
siteUrl: string;
} }
const translations: Record< const translations: Record<
@@ -85,11 +86,12 @@ export function OrderCancelled({
items, items,
total, total,
reason, reason,
siteUrl,
}: OrderCancelledProps) { }: OrderCancelledProps) {
const t = translations[language] || translations.en; const t = translations[language] || translations.en;
return ( return (
<BaseLayout previewText={t.preview} language={language}> <BaseLayout previewText={t.preview} language={language} siteUrl={siteUrl}>
<Text style={styles.title}>{t.title}</Text> <Text style={styles.title}>{t.title}</Text>
<Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text> <Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text>
<Text style={styles.text}>{t.orderCancelled}</Text> <Text style={styles.text}>{t.orderCancelled}</Text>
@@ -124,7 +126,7 @@ export function OrderCancelled({
</Section> </Section>
<Section style={styles.buttonSection}> <Section style={styles.buttonSection}>
<Button href="https://manoonoils.com" style={styles.button}> <Button href={siteUrl} style={styles.button}>
{language === "sr" ? "Pogledajte proizvode" : "Browse Products"} {language === "sr" ? "Pogledajte proizvode" : "Browse Products"}
</Button> </Button>
</Section> </Section>

View File

@@ -17,6 +17,7 @@ interface OrderConfirmationProps {
items: OrderItem[]; items: OrderItem[];
total: string; total: string;
shippingAddress?: string; shippingAddress?: string;
siteUrl: string;
} }
const translations: Record< const translations: Record<
@@ -101,11 +102,12 @@ export function OrderConfirmation({
items, items,
total, total,
shippingAddress, shippingAddress,
siteUrl,
}: OrderConfirmationProps) { }: OrderConfirmationProps) {
const t = translations[language] || translations.en; const t = translations[language] || translations.en;
return ( return (
<BaseLayout previewText={t.preview} language={language}> <BaseLayout previewText={t.preview} language={language} siteUrl={siteUrl}>
<Text style={styles.title}>{t.title}</Text> <Text style={styles.title}>{t.title}</Text>
<Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text> <Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text>
<Text style={styles.text}>{t.orderReceived}</Text> <Text style={styles.text}>{t.orderReceived}</Text>
@@ -142,7 +144,7 @@ export function OrderConfirmation({
)} )}
<Section style={styles.buttonSection}> <Section style={styles.buttonSection}>
<Button href="https://manoonoils.com" style={styles.button}> <Button href={siteUrl} style={styles.button}>
{language === "sr" {language === "sr"
? "Pogledajte narudžbinu" ? "Pogledajte narudžbinu"
: language === "de" : language === "de"

View File

@@ -15,6 +15,7 @@ interface OrderPaidProps {
customerName: string; customerName: string;
items: OrderItem[]; items: OrderItem[];
total: string; total: string;
siteUrl: string;
} }
const translations: Record< const translations: Record<
@@ -92,11 +93,12 @@ export function OrderPaid({
customerName, customerName,
items, items,
total, total,
siteUrl,
}: OrderPaidProps) { }: OrderPaidProps) {
const t = translations[language] || translations.en; const t = translations[language] || translations.en;
return ( return (
<BaseLayout previewText={t.preview} language={language}> <BaseLayout previewText={t.preview} language={language} siteUrl={siteUrl}>
<Text style={styles.title}>{t.title}</Text> <Text style={styles.title}>{t.title}</Text>
<Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text> <Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text>
<Text style={styles.text}>{t.orderPaid}</Text> <Text style={styles.text}>{t.orderPaid}</Text>
@@ -131,7 +133,7 @@ export function OrderPaid({
</Section> </Section>
<Section style={styles.buttonSection}> <Section style={styles.buttonSection}>
<Button href="https://manoonoils.com" style={styles.button}> <Button href={siteUrl} style={styles.button}>
{language === "sr" ? "Nastavite kupovinu" : "Continue Shopping"} {language === "sr" ? "Nastavite kupovinu" : "Continue Shopping"}
</Button> </Button>
</Section> </Section>

View File

@@ -16,6 +16,7 @@ interface OrderShippedProps {
items: OrderItem[]; items: OrderItem[];
trackingNumber?: string; trackingNumber?: string;
trackingUrl?: string; trackingUrl?: string;
siteUrl: string;
} }
const translations: Record< const translations: Record<
@@ -80,11 +81,12 @@ export function OrderShipped({
items, items,
trackingNumber, trackingNumber,
trackingUrl, trackingUrl,
siteUrl,
}: OrderShippedProps) { }: OrderShippedProps) {
const t = translations[language] || translations.en; const t = translations[language] || translations.en;
return ( return (
<BaseLayout previewText={t.preview} language={language}> <BaseLayout previewText={t.preview} language={language} siteUrl={siteUrl}>
<Text style={styles.title}>{t.title}</Text> <Text style={styles.title}>{t.title}</Text>
<Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text> <Text style={styles.greeting}>{t.greeting.replace("{name}", customerName)}</Text>
<Text style={styles.text}>{t.orderShipped}</Text> <Text style={styles.text}>{t.orderShipped}</Text>

View File

@@ -1,7 +1,11 @@
# Replace YOUR_STOREFRONT_URL with your actual storefront URL
# Dev: https://dev.manoonoils.com
# Prod: https://manoonoils.com
mutation CreateSaleorWebhooks { mutation CreateSaleorWebhooks {
orderConfirmedWebhook: webhookCreate(input: { orderConfirmedWebhook: webhookCreate(input: {
name: "Resend - Order Confirmed" name: "Resend - Order Confirmed"
targetUrl: "https://manoonoils.com/api/webhooks/saleor" targetUrl: "YOUR_STOREFRONT_URL/api/webhooks/saleor"
events: [ORDER_CONFIRMED] events: [ORDER_CONFIRMED]
isActive: true isActive: true
}) { }) {
@@ -20,7 +24,7 @@ mutation CreateSaleorWebhooks {
orderPaidWebhook: webhookCreate(input: { orderPaidWebhook: webhookCreate(input: {
name: "Resend - Order Paid" name: "Resend - Order Paid"
targetUrl: "https://manoonoils.com/api/webhooks/saleor" targetUrl: "YOUR_STOREFRONT_URL/api/webhooks/saleor"
events: [ORDER_FULLY_PAID] events: [ORDER_FULLY_PAID]
isActive: true isActive: true
}) { }) {
@@ -39,7 +43,7 @@ mutation CreateSaleorWebhooks {
orderCancelledWebhook: webhookCreate(input: { orderCancelledWebhook: webhookCreate(input: {
name: "Resend - Order Cancelled" name: "Resend - Order Cancelled"
targetUrl: "https://manoonoils.com/api/webhooks/saleor" targetUrl: "YOUR_STOREFRONT_URL/api/webhooks/saleor"
events: [ORDER_CANCELLED] events: [ORDER_CANCELLED]
isActive: true isActive: true
}) { }) {
@@ -58,7 +62,7 @@ mutation CreateSaleorWebhooks {
orderFulfilledWebhook: webhookCreate(input: { orderFulfilledWebhook: webhookCreate(input: {
name: "Resend - Order Fulfilled" name: "Resend - Order Fulfilled"
targetUrl: "https://manoonoils.com/api/webhooks/saleor" targetUrl: "YOUR_STOREFRONT_URL/api/webhooks/saleor"
events: [ORDER_FULFILLED] events: [ORDER_FULFILLED]
isActive: true isActive: true
}) { }) {