feat: add settings UI for webhook toggles
- Add settings store (src/lib/settings.ts) for persistent configuration - Add settings API route for CRUD operations - Add settings page with webhook toggles for order events - Update webhook handlers to check settings before sending emails - Settings include: webhook enable/disable, admin/customer notification toggles - Email configuration: from name, from email, admin emails, store/dashboard URLs
This commit is contained in:
Generated
+699
-27
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
export interface WebhookSettings {
|
||||
orderCreated: {
|
||||
enabled: boolean;
|
||||
sendAdminNotification: boolean;
|
||||
sendCustomerNotification: boolean;
|
||||
};
|
||||
orderFulfilled: {
|
||||
enabled: boolean;
|
||||
sendAdminNotification: boolean;
|
||||
sendCustomerNotification: boolean;
|
||||
};
|
||||
orderCancelled: {
|
||||
enabled: boolean;
|
||||
sendAdminNotification: boolean;
|
||||
sendCustomerNotification: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EmailSettings {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
adminEmails: string[];
|
||||
siteUrl: string;
|
||||
dashboardUrl: string;
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
webhooks: WebhookSettings;
|
||||
email: EmailSettings;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: AppSettings = {
|
||||
webhooks: {
|
||||
orderCreated: {
|
||||
enabled: true,
|
||||
sendAdminNotification: true,
|
||||
sendCustomerNotification: true,
|
||||
},
|
||||
orderFulfilled: {
|
||||
enabled: true,
|
||||
sendAdminNotification: true,
|
||||
sendCustomerNotification: true,
|
||||
},
|
||||
orderCancelled: {
|
||||
enabled: true,
|
||||
sendAdminNotification: true,
|
||||
sendCustomerNotification: true,
|
||||
},
|
||||
},
|
||||
email: {
|
||||
fromEmail: process.env.FROM_EMAIL || "support@mail.manoonoils.com",
|
||||
fromName: process.env.FROM_NAME || "ManoonOils",
|
||||
adminEmails: process.env.ADMIN_EMAILS?.split(",").map((e) => e.trim()).filter(Boolean) || [],
|
||||
siteUrl: process.env.SITE_URL || "https://manoonoils.com",
|
||||
dashboardUrl: process.env.DASHBOARD_URL || "https://dashboard.manoonoils.com",
|
||||
},
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
class SettingsStore {
|
||||
private filePath: string;
|
||||
private settings: AppSettings | null = null;
|
||||
|
||||
constructor() {
|
||||
this.filePath = process.env.SETTINGS_FILE_PATH || "/tmp/.app-settings.json";
|
||||
}
|
||||
|
||||
private async ensureFileExists(): Promise<void> {
|
||||
try {
|
||||
await fs.access(this.filePath);
|
||||
} catch {
|
||||
const dir = path.dirname(this.filePath);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
await fs.writeFile(this.filePath, JSON.stringify(DEFAULT_SETTINGS, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
async get(): Promise<AppSettings> {
|
||||
if (this.settings) {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.ensureFileExists();
|
||||
const data = await fs.readFile(this.filePath, "utf-8");
|
||||
this.settings = JSON.parse(data) as AppSettings;
|
||||
return this.settings!;
|
||||
} catch (error) {
|
||||
console.error("Error reading settings:", error);
|
||||
this.settings = DEFAULT_SETTINGS;
|
||||
return this.settings;
|
||||
}
|
||||
}
|
||||
|
||||
async set(newSettings: Partial<AppSettings>): Promise<AppSettings> {
|
||||
const current = await this.get();
|
||||
const updated: AppSettings = {
|
||||
...current,
|
||||
...newSettings,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
await fs.writeFile(this.filePath, JSON.stringify(updated, null, 2));
|
||||
this.settings = updated;
|
||||
return updated;
|
||||
} catch (error) {
|
||||
console.error("Error writing settings:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async isWebhookEnabled(webhook: keyof WebhookSettings): Promise<boolean> {
|
||||
const settings = await this.get();
|
||||
return settings.webhooks[webhook].enabled;
|
||||
}
|
||||
|
||||
async shouldSendAdminNotification(webhook: keyof WebhookSettings): Promise<boolean> {
|
||||
const settings = await this.get();
|
||||
return settings.webhooks[webhook].sendAdminNotification;
|
||||
}
|
||||
|
||||
async shouldSendCustomerNotification(webhook: keyof WebhookSettings): Promise<boolean> {
|
||||
const settings = await this.get();
|
||||
return settings.webhooks[webhook].sendCustomerNotification;
|
||||
}
|
||||
}
|
||||
|
||||
export const settingsStore = new SettingsStore();
|
||||
@@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { settingsStore, AppSettings } from "@/lib/settings";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const settings = await settingsStore.get();
|
||||
return NextResponse.json(settings);
|
||||
} catch (error) {
|
||||
console.error("Error getting settings:", error);
|
||||
return NextResponse.json({ error: "Failed to get settings" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json() as Partial<AppSettings>;
|
||||
const settings = await settingsStore.set(body);
|
||||
return NextResponse.json(settings);
|
||||
} catch (error) {
|
||||
console.error("Error updating settings:", error);
|
||||
return NextResponse.json({ error: "Failed to update settings" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json() as Partial<AppSettings>;
|
||||
const settings = await settingsStore.set(body);
|
||||
return NextResponse.json(settings);
|
||||
} catch (error) {
|
||||
console.error("Error patching settings:", error);
|
||||
return NextResponse.json({ error: "Failed to patch settings" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "@/generated/graphql";
|
||||
import { saleorApp } from "@/saleor-app";
|
||||
import { sendOrderCancelledEmail, formatPrice } from "@/lib/resend";
|
||||
import { settingsStore } from "@/lib/settings";
|
||||
|
||||
export const orderCancelledWebhook = new SaleorAsyncWebhook<OrderCancelledWebhookPayloadFragment>({
|
||||
name: "Order Cancelled in Saleor",
|
||||
@@ -23,7 +24,17 @@ export default orderCancelledWebhook.createHandler(async (req, res, ctx) => {
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
console.log(`Order ${order.number} cancelled for customer: ${order.userEmail}`);
|
||||
const webhookEnabled = await settingsStore.isWebhookEnabled("orderCancelled");
|
||||
const sendAdmin = await settingsStore.shouldSendAdminNotification("orderCancelled");
|
||||
const sendCustomer = await settingsStore.shouldSendCustomerNotification("orderCancelled");
|
||||
|
||||
console.log(`❌ Order ${order.number} cancelled for customer: ${order.userEmail}`);
|
||||
console.log(`📋 Webhook settings - enabled: ${webhookEnabled}, admin: ${sendAdmin}, customer: ${sendCustomer}`);
|
||||
|
||||
if (!webhookEnabled) {
|
||||
console.log("⏭️ Webhook disabled, skipping notifications");
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
const items = ((order as any).lines || []).map((line: any) => ({
|
||||
id: line.id,
|
||||
@@ -42,16 +53,36 @@ export default orderCancelledWebhook.createHandler(async (req, res, ctx) => {
|
||||
total: formatPrice((order as any).total?.gross?.amount || 0, (order as any).total?.gross?.currency || "USD"),
|
||||
};
|
||||
|
||||
if (sendAdmin) {
|
||||
try {
|
||||
const adminEmails = process.env.ADMIN_EMAILS?.split(",").map(e => e.trim()).filter(e => e) || [];
|
||||
|
||||
if (adminEmails.length > 0) {
|
||||
await sendOrderCancelledEmail({
|
||||
to: adminEmails,
|
||||
orderData,
|
||||
});
|
||||
console.log(`✅ Admin notification sent for cancelled order ${order.number}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to send admin email:", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (sendCustomer) {
|
||||
try {
|
||||
if (order.userEmail) {
|
||||
await sendOrderCancelledEmail({
|
||||
to: order.userEmail,
|
||||
orderData,
|
||||
});
|
||||
console.log(`Customer notification sent for cancelled order ${order.number}`);
|
||||
console.log(`✅ Customer notification sent for cancelled order ${order.number}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to send email:", error);
|
||||
console.error("❌ Failed to send customer email:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("⏭️ Customer notification disabled, skipping");
|
||||
}
|
||||
|
||||
return res.status(200).end();
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "@/generated/graphql";
|
||||
import { saleorApp } from "@/saleor-app";
|
||||
import { sendOrderConfirmationEmail, formatPrice } from "@/lib/resend";
|
||||
import { settingsStore } from "@/lib/settings";
|
||||
|
||||
export const orderCreatedWebhook = new SaleorAsyncWebhook<OrderCreatedWebhookPayloadFragment>({
|
||||
name: "Order Created in Saleor",
|
||||
@@ -23,7 +24,17 @@ export default orderCreatedWebhook.createHandler(async (req, res, ctx) => {
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
const webhookEnabled = await settingsStore.isWebhookEnabled("orderCreated");
|
||||
const sendAdmin = await settingsStore.shouldSendAdminNotification("orderCreated");
|
||||
const sendCustomer = await settingsStore.shouldSendCustomerNotification("orderCreated");
|
||||
|
||||
console.log(`🎉 Order #${order.number} created for customer: ${order.userEmail} (${order.languageCode || "EN"})`);
|
||||
console.log(`📋 Webhook settings - enabled: ${webhookEnabled}, admin: ${sendAdmin}, customer: ${sendCustomer}`);
|
||||
|
||||
if (!webhookEnabled) {
|
||||
console.log("⏭️ Webhook disabled, skipping notifications");
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
const items = ((order as any).lines || []).map((line: any) => ({
|
||||
id: line.id,
|
||||
@@ -50,7 +61,7 @@ export default orderCreatedWebhook.createHandler(async (req, res, ctx) => {
|
||||
phone: (order as any).shippingAddress?.phone,
|
||||
};
|
||||
|
||||
// Send admin notification
|
||||
if (sendAdmin) {
|
||||
try {
|
||||
const adminEmails = process.env.ADMIN_EMAILS?.split(",").map(e => e.trim()).filter(e => e) || [];
|
||||
|
||||
@@ -67,8 +78,11 @@ export default orderCreatedWebhook.createHandler(async (req, res, ctx) => {
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to send admin email:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("⏭️ Admin notification disabled, skipping");
|
||||
}
|
||||
|
||||
// Send customer confirmation
|
||||
if (sendCustomer) {
|
||||
try {
|
||||
if (order.userEmail) {
|
||||
await sendOrderConfirmationEmail({
|
||||
@@ -83,6 +97,9 @@ export default orderCreatedWebhook.createHandler(async (req, res, ctx) => {
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to send customer email:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("⏭️ Customer notification disabled, skipping");
|
||||
}
|
||||
|
||||
return res.status(200).end();
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "@/generated/graphql";
|
||||
import { saleorApp } from "@/saleor-app";
|
||||
import { sendOrderShippedEmail, formatPrice } from "@/lib/resend";
|
||||
import { settingsStore } from "@/lib/settings";
|
||||
|
||||
export const orderFulfilledWebhook = new SaleorAsyncWebhook<OrderFulfilledWebhookPayloadFragment>({
|
||||
name: "Order Fulfilled in Saleor",
|
||||
@@ -23,7 +24,17 @@ export default orderFulfilledWebhook.createHandler(async (req, res, ctx) => {
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
console.log(`Order ${order.number} fulfilled for customer: ${order.userEmail}`);
|
||||
const webhookEnabled = await settingsStore.isWebhookEnabled("orderFulfilled");
|
||||
const sendAdmin = await settingsStore.shouldSendAdminNotification("orderFulfilled");
|
||||
const sendCustomer = await settingsStore.shouldSendCustomerNotification("orderFulfilled");
|
||||
|
||||
console.log(`📦 Order ${order.number} fulfilled for customer: ${order.userEmail}`);
|
||||
console.log(`📋 Webhook settings - enabled: ${webhookEnabled}, admin: ${sendAdmin}, customer: ${sendCustomer}`);
|
||||
|
||||
if (!webhookEnabled) {
|
||||
console.log("⏭️ Webhook disabled, skipping notifications");
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
const items = ((order as any).lines || []).map((line: any) => ({
|
||||
id: line.id,
|
||||
@@ -41,16 +52,36 @@ export default orderFulfilledWebhook.createHandler(async (req, res, ctx) => {
|
||||
items,
|
||||
};
|
||||
|
||||
if (sendAdmin) {
|
||||
try {
|
||||
const adminEmails = process.env.ADMIN_EMAILS?.split(",").map(e => e.trim()).filter(e => e) || [];
|
||||
|
||||
if (adminEmails.length > 0) {
|
||||
await sendOrderShippedEmail({
|
||||
to: adminEmails,
|
||||
orderData,
|
||||
});
|
||||
console.log(`✅ Admin notification sent for fulfilled order ${order.number}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to send admin email:", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (sendCustomer) {
|
||||
try {
|
||||
if (order.userEmail) {
|
||||
await sendOrderShippedEmail({
|
||||
to: order.userEmail,
|
||||
orderData,
|
||||
});
|
||||
console.log(`Customer notification sent for fulfilled order ${order.number}`);
|
||||
console.log(`✅ Customer notification sent for fulfilled order ${order.number}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to send email:", error);
|
||||
console.error("❌ Failed to send customer email:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("⏭️ Customer notification disabled, skipping");
|
||||
}
|
||||
|
||||
return res.status(200).end();
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
import { Box, Button, Input, Text } from "@saleor/macaw-ui";
|
||||
import { NextPage } from "next";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
interface WebhookToggle {
|
||||
enabled: boolean;
|
||||
sendAdminNotification: boolean;
|
||||
sendCustomerNotification: boolean;
|
||||
}
|
||||
|
||||
interface WebhookSettings {
|
||||
orderCreated: WebhookToggle;
|
||||
orderFulfilled: WebhookToggle;
|
||||
orderCancelled: WebhookToggle;
|
||||
}
|
||||
|
||||
interface EmailSettings {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
adminEmails: string;
|
||||
siteUrl: string;
|
||||
dashboardUrl: string;
|
||||
}
|
||||
|
||||
interface Settings {
|
||||
webhooks: WebhookSettings;
|
||||
email: EmailSettings;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
const defaultSettings: Settings = {
|
||||
webhooks: {
|
||||
orderCreated: { enabled: true, sendAdminNotification: true, sendCustomerNotification: true },
|
||||
orderFulfilled: { enabled: true, sendAdminNotification: true, sendCustomerNotification: true },
|
||||
orderCancelled: { enabled: true, sendAdminNotification: true, sendCustomerNotification: true },
|
||||
},
|
||||
email: {
|
||||
fromEmail: "",
|
||||
fromName: "",
|
||||
adminEmails: "",
|
||||
siteUrl: "",
|
||||
dashboardUrl: "",
|
||||
},
|
||||
updatedAt: "",
|
||||
};
|
||||
|
||||
const webhookLabels: Record<keyof WebhookSettings, { title: string; description: string }> = {
|
||||
orderCreated: {
|
||||
title: "Order Created",
|
||||
description: "Send email notifications when a new order is placed",
|
||||
},
|
||||
orderFulfilled: {
|
||||
title: "Order Fulfilled",
|
||||
description: "Send email notifications when an order is shipped",
|
||||
},
|
||||
orderCancelled: {
|
||||
title: "Order Cancelled",
|
||||
description: "Send email notifications when an order is cancelled",
|
||||
},
|
||||
};
|
||||
|
||||
const Toggle: React.FC<{
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}> = ({ checked, onChange, disabled }) => (
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
disabled={disabled}
|
||||
onClick={() => onChange(!checked)}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "44px",
|
||||
height: "24px",
|
||||
borderRadius: "12px",
|
||||
backgroundColor: checked ? "#22c55e" : "#d1d5db",
|
||||
border: "none",
|
||||
cursor: disabled ? "not-allowed" : "pointer",
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
transition: "background-color 0.2s",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "2px",
|
||||
left: checked ? "22px" : "2px",
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "white",
|
||||
transition: "left 0.2s",
|
||||
boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
|
||||
const Card: React.FC<{
|
||||
children: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}> = ({ children, style }) => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#ffffff",
|
||||
borderRadius: "12px",
|
||||
border: "1px solid #e5e7eb",
|
||||
padding: "24px",
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
const SettingsPage: NextPage = () => {
|
||||
const [settings, setSettings] = useState<Settings>(defaultSettings);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const bgColor = "#f9fafb";
|
||||
const textColor = "#111827";
|
||||
const subtextColor = "#6b7280";
|
||||
const borderColor = "#e5e7eb";
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/settings")
|
||||
.then((res) => res.json())
|
||||
.then((data: Settings) => {
|
||||
setSettings({
|
||||
...data,
|
||||
email: {
|
||||
...data.email,
|
||||
adminEmails: Array.isArray(data.email.adminEmails)
|
||||
? data.email.adminEmails.join(", ")
|
||||
: data.email.adminEmails || "",
|
||||
},
|
||||
});
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to load settings:", err);
|
||||
setError("Failed to load settings");
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleWebhookChange = useCallback(
|
||||
(webhook: keyof WebhookSettings, field: keyof WebhookToggle) => {
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
webhooks: {
|
||||
...prev.webhooks,
|
||||
[webhook]: {
|
||||
...prev.webhooks[webhook],
|
||||
[field]: !prev.webhooks[webhook][field],
|
||||
},
|
||||
},
|
||||
}));
|
||||
setSaved(false);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleEmailChange = useCallback((field: keyof EmailSettings, value: string) => {
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
email: {
|
||||
...prev.email,
|
||||
[field]: value,
|
||||
},
|
||||
}));
|
||||
setSaved(false);
|
||||
}, []);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
setSaved(false);
|
||||
|
||||
try {
|
||||
const toSave = {
|
||||
...settings,
|
||||
email: {
|
||||
...settings.email,
|
||||
adminEmails: settings.email.adminEmails
|
||||
.split(",")
|
||||
.map((e) => e.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
};
|
||||
|
||||
const res = await fetch("/api/settings", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(toSave),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to save settings");
|
||||
}
|
||||
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 3000);
|
||||
} catch (err) {
|
||||
console.error("Failed to save settings:", err);
|
||||
setError("Failed to save settings. Please try again.");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box padding={8} style={{ backgroundColor: bgColor, minHeight: "100vh" }}>
|
||||
<Text size={5} style={{ color: textColor, fontWeight: "bold" }}>
|
||||
Loading...
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box padding={8} style={{ backgroundColor: bgColor, minHeight: "100vh" }}>
|
||||
<Box marginBottom={8}>
|
||||
<Text size={7} style={{ color: textColor, fontWeight: "bold" }}>
|
||||
Email Notifications
|
||||
</Text>
|
||||
<Text style={{ color: subtextColor, marginTop: "8px" }}>
|
||||
Configure when to send email notifications for order events
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Box
|
||||
padding={4}
|
||||
marginBottom={6}
|
||||
style={{
|
||||
backgroundColor: "#fee2e2",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #ef4444",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#991b1b" }}>{error}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box marginBottom={6}>
|
||||
<Text size={5} style={{ color: textColor, fontWeight: "bold", marginBottom: "16px" }}>
|
||||
Webhooks
|
||||
</Text>
|
||||
|
||||
{(Object.keys(webhookLabels) as Array<keyof WebhookSettings>).map((webhook) => {
|
||||
const { title, description } = webhookLabels[webhook];
|
||||
const webhookSettings = settings.webhooks[webhook];
|
||||
|
||||
return (
|
||||
<Card key={webhook} style={{ marginBottom: "16px" }}>
|
||||
<Box display="flex" alignItems="flex-start" justifyContent="space-between">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text style={{ color: textColor, fontWeight: "bold" }}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text size={3} style={{ color: subtextColor, marginTop: "4px" }}>
|
||||
{description}
|
||||
</Text>
|
||||
</Box>
|
||||
<Toggle
|
||||
checked={webhookSettings.enabled}
|
||||
onChange={() => handleWebhookChange(webhook, "enabled")}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{webhookSettings.enabled && (
|
||||
<Box
|
||||
marginTop={4}
|
||||
paddingTop={4}
|
||||
style={{ borderTop: `1px solid ${borderColor}` }}
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
marginBottom={3}
|
||||
>
|
||||
<Text size={3} style={{ color: subtextColor }}>
|
||||
Send to admin emails
|
||||
</Text>
|
||||
<Toggle
|
||||
checked={webhookSettings.sendAdminNotification}
|
||||
onChange={() => handleWebhookChange(webhook, "sendAdminNotification")}
|
||||
/>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<Text size={3} style={{ color: subtextColor }}>
|
||||
Send to customer
|
||||
</Text>
|
||||
<Toggle
|
||||
checked={webhookSettings.sendCustomerNotification}
|
||||
onChange={() => handleWebhookChange(webhook, "sendCustomerNotification")}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
<Box marginBottom={6}>
|
||||
<Text size={5} style={{ color: textColor, fontWeight: "bold", marginBottom: "16px" }}>
|
||||
Email Configuration
|
||||
</Text>
|
||||
|
||||
<Card>
|
||||
<Box marginBottom={4}>
|
||||
<Text size={3} style={{ color: textColor, fontWeight: 500, marginBottom: "8px" }}>
|
||||
From Name
|
||||
</Text>
|
||||
<Input
|
||||
value={settings.email.fromName}
|
||||
onChange={(e) => handleEmailChange("fromName", e.target.value)}
|
||||
placeholder="ManoonOils"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box marginBottom={4}>
|
||||
<Text size={3} style={{ color: textColor, fontWeight: 500, marginBottom: "8px" }}>
|
||||
From Email
|
||||
</Text>
|
||||
<Input
|
||||
type="email"
|
||||
value={settings.email.fromEmail}
|
||||
onChange={(e) => handleEmailChange("fromEmail", e.target.value)}
|
||||
placeholder="support@mail.manoonoils.com"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box marginBottom={4}>
|
||||
<Text size={3} style={{ color: textColor, fontWeight: 500, marginBottom: "8px" }}>
|
||||
Admin Emails (comma separated)
|
||||
</Text>
|
||||
<Input
|
||||
value={settings.email.adminEmails}
|
||||
onChange={(e) => handleEmailChange("adminEmails", e.target.value)}
|
||||
placeholder="admin@example.com, manager@example.com"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box marginBottom={4}>
|
||||
<Text size={3} style={{ color: textColor, fontWeight: 500, marginBottom: "8px" }}>
|
||||
Store URL
|
||||
</Text>
|
||||
<Input
|
||||
type="url"
|
||||
value={settings.email.siteUrl}
|
||||
onChange={(e) => handleEmailChange("siteUrl", e.target.value)}
|
||||
placeholder="https://manoonoils.com"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size={3} style={{ color: textColor, fontWeight: 500, marginBottom: "8px" }}>
|
||||
Dashboard URL
|
||||
</Text>
|
||||
<Input
|
||||
type="url"
|
||||
value={settings.email.dashboardUrl}
|
||||
onChange={(e) => handleEmailChange("dashboardUrl", e.target.value)}
|
||||
placeholder="https://dashboard.manoonoils.com"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? "Saving..." : "Save Settings"}
|
||||
</Button>
|
||||
{saved && (
|
||||
<Text size={3} style={{ color: "#22c55e" }}>
|
||||
Settings saved successfully!
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{settings.updatedAt && (
|
||||
<Text size={3} style={{ color: subtextColor, marginTop: "16px" }}>
|
||||
Last updated: {new Date(settings.updatedAt).toLocaleString()}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPage;
|
||||
Reference in New Issue
Block a user