feat(admin): add email management page with preview and test functionality

- Add email management page with template listing

- Add email preview with iframe

- Add test email sending functionality

- Add Email Management to sidebar navigation
This commit is contained in:
Ashwin Kumar 2026-04-10 04:50:00 +02:00
parent 31b3a04a81
commit 2409d85b3c
2 changed files with 606 additions and 79 deletions

View file

@ -1,14 +1,43 @@
import { A, useLocation } from '@solidjs/router';
import { For, Show, createMemo } from 'solid-js';
import { A, useLocation } from "@solidjs/router";
import { For, Show, createMemo } from "solid-js";
import {
LayoutGrid, Building2, Briefcase, Users, ShieldCheck, FileText,
LayoutDashboard, ClipboardList, UserRoundSearch, UserCircle,
Camera, Palette, BookOpen, Code2, BriefcaseBusiness, HandHelping,
WalletCards, CreditCard, Tag, Percent, Receipt, ShoppingCart,
FileCheck, Star, HeadphonesIcon, BarChart3,
ChevronLeft, BadgeCheck, Activity, Film, Utensils, PenTool, Mail,
Megaphone, Bell, Video,
} from 'lucide-solid';
LayoutGrid,
Building2,
Briefcase,
Users,
ShieldCheck,
FileText,
LayoutDashboard,
ClipboardList,
UserRoundSearch,
UserCircle,
Camera,
Palette,
BookOpen,
Code2,
BriefcaseBusiness,
HandHelping,
WalletCards,
CreditCard,
Tag,
Percent,
Receipt,
ShoppingCart,
FileCheck,
Star,
HeadphonesIcon,
BarChart3,
ChevronLeft,
BadgeCheck,
Activity,
Film,
Utensils,
PenTool,
Mail,
Megaphone,
Bell,
Video,
} from "lucide-solid";
type NavItem = {
href: string;
@ -20,63 +49,289 @@ type NavItem = {
const GROUPS: NavItem[][] = [
[
{ href: '/admin', label: 'Dashboard', icon: LayoutGrid, moduleKeys: ['ADMIN_DASHBOARD', 'DASHBOARD'] },
{
href: "/admin",
label: "Dashboard",
icon: LayoutGrid,
moduleKeys: ["ADMIN_DASHBOARD", "DASHBOARD"],
},
],
[
{ href: '/admin/department', label: 'Department Management', icon: Building2, moduleKeys: ['DEPARTMENT_MANAGEMENT', 'DEPARTMENTS'] },
{ href: '/admin/designation', label: 'Designation Management', icon: Briefcase, moduleKeys: ['DESIGNATION_MANAGEMENT', 'DESIGNATIONS'] },
{ href: '/admin/roles', label: 'Internal Role Management', icon: ShieldCheck, moduleKeys: ['INTERNAL_ROLE_MANAGEMENT', 'ROLES'] },
{ href: '/admin/employees', label: 'Employee Management', icon: Users, moduleKeys: ['EMPLOYEE_MANAGEMENT', 'EMPLOYEES'] },
{
href: "/admin/department",
label: "Department Management",
icon: Building2,
moduleKeys: ["DEPARTMENT_MANAGEMENT", "DEPARTMENTS"],
},
{
href: "/admin/designation",
label: "Designation Management",
icon: Briefcase,
moduleKeys: ["DESIGNATION_MANAGEMENT", "DESIGNATIONS"],
},
{
href: "/admin/roles",
label: "Internal Role Management",
icon: ShieldCheck,
moduleKeys: ["INTERNAL_ROLE_MANAGEMENT", "ROLES"],
},
{
href: "/admin/employees",
label: "Employee Management",
icon: Users,
moduleKeys: ["EMPLOYEE_MANAGEMENT", "EMPLOYEES"],
},
],
[
{ href: '/admin/external-roles', label: 'External Role Management', icon: ShieldCheck, moduleKeys: ['EXTERNAL_ROLE_MANAGEMENT', 'EXTERNAL_ROLES'] },
{ href: '/admin/internal-dashboard-management', label: 'Internal Dashboard Management', icon: LayoutDashboard, moduleKeys: ['INTERNAL_DASHBOARD_MANAGEMENT', 'INTERNAL_DASHBOARDS', 'INTERNAL_DASHBOARD_CONFIG'] },
{ href: '/admin/external-dashboard-management', label: 'External Dashboard Management', icon: LayoutDashboard, aliasPrefix: '/admin/role-ui-configs', moduleKeys: ['DASHBOARD_CONFIG_MANAGEMENT', 'EXTERNAL_DASHBOARD_MANAGEMENT', 'EXTERNAL_DASHBOARDS', 'EXTERNAL_DASHBOARD_CONFIG', 'RUNTIME_ROLES'] },
{
href: "/admin/external-roles",
label: "External Role Management",
icon: ShieldCheck,
moduleKeys: ["EXTERNAL_ROLE_MANAGEMENT", "EXTERNAL_ROLES"],
},
{
href: "/admin/internal-dashboard-management",
label: "Internal Dashboard Management",
icon: LayoutDashboard,
moduleKeys: [
"INTERNAL_DASHBOARD_MANAGEMENT",
"INTERNAL_DASHBOARDS",
"INTERNAL_DASHBOARD_CONFIG",
],
},
{
href: "/admin/external-dashboard-management",
label: "External Dashboard Management",
icon: LayoutDashboard,
aliasPrefix: "/admin/role-ui-configs",
moduleKeys: [
"DASHBOARD_CONFIG_MANAGEMENT",
"EXTERNAL_DASHBOARD_MANAGEMENT",
"EXTERNAL_DASHBOARDS",
"EXTERNAL_DASHBOARD_CONFIG",
"RUNTIME_ROLES",
],
},
],
[
{ href: '/admin/verification', label: 'Verification Management', icon: BadgeCheck, moduleKeys: ['VERIFICATION_MANAGEMENT', 'VERIFICATIONS'] },
{ href: '/admin/approval', label: 'Approval Management', icon: ClipboardList, moduleKeys: ['APPROVAL_MANAGEMENT', 'APPROVALS'] },
{
href: "/admin/verification",
label: "Verification Management",
icon: BadgeCheck,
moduleKeys: ["VERIFICATION_MANAGEMENT", "VERIFICATIONS"],
},
{
href: "/admin/approval",
label: "Approval Management",
icon: ClipboardList,
moduleKeys: ["APPROVAL_MANAGEMENT", "APPROVALS"],
},
],
[
{ href: '/admin/users', label: 'Users Management', icon: UserRoundSearch, moduleKeys: ['USER_MANAGEMENT', 'USERS'] },
{ href: '/admin/company', label: 'Company Management', icon: Building2, moduleKeys: ['COMPANY_MANAGEMENT', 'COMPANIES'] },
{ href: '/admin/candidate', label: 'Candidate Management', icon: UserCircle, moduleKeys: ['CANDIDATE_MANAGEMENT', 'CANDIDATES'] },
{ href: '/admin/customer', label: 'Customer Management', icon: UserCircle, moduleKeys: ['CUSTOMER_MANAGEMENT', 'CUSTOMERS'] },
{
href: "/admin/users",
label: "Users Management",
icon: UserRoundSearch,
moduleKeys: ["USER_MANAGEMENT", "USERS"],
},
{
href: "/admin/company",
label: "Company Management",
icon: Building2,
moduleKeys: ["COMPANY_MANAGEMENT", "COMPANIES"],
},
{
href: "/admin/candidate",
label: "Candidate Management",
icon: UserCircle,
moduleKeys: ["CANDIDATE_MANAGEMENT", "CANDIDATES"],
},
{
href: "/admin/customer",
label: "Customer Management",
icon: UserCircle,
moduleKeys: ["CUSTOMER_MANAGEMENT", "CUSTOMERS"],
},
],
[
{ href: '/admin/photographer', label: 'Photographer Management', icon: Camera, moduleKeys: ['PHOTOGRAPHER_MANAGEMENT', 'PHOTOGRAPHERS'] },
{ href: '/admin/makeup-artist', label: 'Makeup Artist Management', icon: Palette, moduleKeys: ['MAKEUP_ARTIST_MANAGEMENT', 'MAKEUP_ARTISTS'] },
{ href: '/admin/tutors', label: 'Tutors Management', icon: BookOpen, moduleKeys: ['TUTOR_MANAGEMENT', 'TUTORS'] },
{ href: '/admin/developers', label: 'Developers Management', icon: Code2, moduleKeys: ['DEVELOPER_MANAGEMENT', 'DEVELOPERS'] },
{ href: '/admin/video-editors', label: 'Video Editor Management', icon: Film, moduleKeys: ['VIDEO_EDITOR_MANAGEMENT', 'VIDEO_EDITORS'] },
{ href: '/admin/fitness-trainers', label: 'Fitness Trainer Management', icon: Activity, moduleKeys: ['FITNESS_TRAINER_MANAGEMENT', 'FITNESS_TRAINERS'] },
{ href: '/admin/catering-services', label: 'Catering Services Management', icon: Utensils, moduleKeys: ['CATERING_SERVICES_MANAGEMENT', 'CATERING_SERVICES'] },
{ href: '/admin/graphic-designers', label: 'Graphics Designer Management', icon: PenTool, moduleKeys: ['GRAPHIC_DESIGNER_MANAGEMENT', 'GRAPHIC_DESIGNERS'] },
{ href: '/admin/social-media-managers', label: 'Social Media Manager Management', icon: Megaphone, moduleKeys: ['SOCIAL_MEDIA_MANAGEMENT', 'SOCIAL_MEDIA_MANAGER_MANAGEMENT', 'SOCIAL_MEDIA_MANAGERS'] },
{ href: '/admin/ugc-content-creator', label: 'UGC Content Creator Management', icon: Video, moduleKeys: ['UGC_CONTENT_CREATOR_MANAGEMENT', 'UGC_CONTENT_CREATOR'] },
{
href: "/admin/photographer",
label: "Photographer Management",
icon: Camera,
moduleKeys: ["PHOTOGRAPHER_MANAGEMENT", "PHOTOGRAPHERS"],
},
{
href: "/admin/makeup-artist",
label: "Makeup Artist Management",
icon: Palette,
moduleKeys: ["MAKEUP_ARTIST_MANAGEMENT", "MAKEUP_ARTISTS"],
},
{
href: "/admin/tutors",
label: "Tutors Management",
icon: BookOpen,
moduleKeys: ["TUTOR_MANAGEMENT", "TUTORS"],
},
{
href: "/admin/developers",
label: "Developers Management",
icon: Code2,
moduleKeys: ["DEVELOPER_MANAGEMENT", "DEVELOPERS"],
},
{
href: "/admin/video-editors",
label: "Video Editor Management",
icon: Film,
moduleKeys: ["VIDEO_EDITOR_MANAGEMENT", "VIDEO_EDITORS"],
},
{
href: "/admin/fitness-trainers",
label: "Fitness Trainer Management",
icon: Activity,
moduleKeys: ["FITNESS_TRAINER_MANAGEMENT", "FITNESS_TRAINERS"],
},
{
href: "/admin/catering-services",
label: "Catering Services Management",
icon: Utensils,
moduleKeys: ["CATERING_SERVICES_MANAGEMENT", "CATERING_SERVICES"],
},
{
href: "/admin/graphic-designers",
label: "Graphics Designer Management",
icon: PenTool,
moduleKeys: ["GRAPHIC_DESIGNER_MANAGEMENT", "GRAPHIC_DESIGNERS"],
},
{
href: "/admin/social-media-managers",
label: "Social Media Manager Management",
icon: Megaphone,
moduleKeys: [
"SOCIAL_MEDIA_MANAGEMENT",
"SOCIAL_MEDIA_MANAGER_MANAGEMENT",
"SOCIAL_MEDIA_MANAGERS",
],
},
{
href: "/admin/ugc-content-creator",
label: "UGC Content Creator Management",
icon: Video,
moduleKeys: ["UGC_CONTENT_CREATOR_MANAGEMENT", "UGC_CONTENT_CREATOR"],
},
],
[
{ href: '/admin/jobs', label: 'Jobs Management', icon: BriefcaseBusiness, moduleKeys: ['JOBS_MANAGEMENT', 'JOBS'] },
{ href: '/admin/leads', label: 'Leads Management', icon: HandHelping, moduleKeys: ['LEADS_MANAGEMENT', 'LEADS', 'REQUIREMENTS_MANAGEMENT', 'REQUIREMENTS'] },
{
href: "/admin/jobs",
label: "Jobs Management",
icon: BriefcaseBusiness,
moduleKeys: ["JOBS_MANAGEMENT", "JOBS"],
},
{
href: "/admin/leads",
label: "Leads Management",
icon: HandHelping,
moduleKeys: ["LEADS_MANAGEMENT", "LEADS", "REQUIREMENTS_MANAGEMENT", "REQUIREMENTS"],
},
],
[
{ href: '/admin/pricing', label: 'Pricing Management', icon: WalletCards, moduleKeys: ['PRICING_MANAGEMENT', 'PRICING'] },
{ href: '/admin/credit', label: 'Credit Management', icon: CreditCard, moduleKeys: ['CREDIT_MANAGEMENT', 'CREDITS'] },
{ href: '/admin/coupon', label: 'Coupon Management', icon: Tag, moduleKeys: ['COUPON_MANAGEMENT', 'COUPONS'] },
{ href: '/admin/discount', label: 'Discount Management', icon: Percent, moduleKeys: ['DISCOUNT_MANAGEMENT', 'DISCOUNTS'] },
{ href: '/admin/tax', label: 'Tax Management', icon: Receipt, moduleKeys: ['TAX_MANAGEMENT', 'TAXES'] },
{ href: '/admin/order', label: 'Order Management', icon: ShoppingCart, moduleKeys: ['ORDER_MANAGEMENT', 'ORDERS'] },
{ href: '/admin/invoice', label: 'Invoice Management', icon: FileCheck, moduleKeys: ['INVOICE_MANAGEMENT', 'INVOICES'] },
{ href: '/admin/payment-gateway', label: 'Payment Gateway Management', icon: CreditCard, moduleKeys: ['PAYMENT_GATEWAY_MANAGEMENT', 'PAYMENT_GATEWAY'] },
{ href: '/admin/smtp', label: 'SMTP Management', icon: Mail, moduleKeys: ['SMTP_MANAGEMENT', 'SMTP'] },
{
href: "/admin/pricing",
label: "Pricing Management",
icon: WalletCards,
moduleKeys: ["PRICING_MANAGEMENT", "PRICING"],
},
{
href: "/admin/credit",
label: "Credit Management",
icon: CreditCard,
moduleKeys: ["CREDIT_MANAGEMENT", "CREDITS"],
},
{
href: "/admin/coupon",
label: "Coupon Management",
icon: Tag,
moduleKeys: ["COUPON_MANAGEMENT", "COUPONS"],
},
{
href: "/admin/discount",
label: "Discount Management",
icon: Percent,
moduleKeys: ["DISCOUNT_MANAGEMENT", "DISCOUNTS"],
},
{
href: "/admin/tax",
label: "Tax Management",
icon: Receipt,
moduleKeys: ["TAX_MANAGEMENT", "TAXES"],
},
{
href: "/admin/order",
label: "Order Management",
icon: ShoppingCart,
moduleKeys: ["ORDER_MANAGEMENT", "ORDERS"],
},
{
href: "/admin/invoice",
label: "Invoice Management",
icon: FileCheck,
moduleKeys: ["INVOICE_MANAGEMENT", "INVOICES"],
},
{
href: "/admin/payment-gateway",
label: "Payment Gateway Management",
icon: CreditCard,
moduleKeys: ["PAYMENT_GATEWAY_MANAGEMENT", "PAYMENT_GATEWAY"],
},
{
href: "/admin/smtp",
label: "SMTP Management",
icon: Mail,
moduleKeys: ["SMTP_MANAGEMENT", "SMTP"],
},
{
href: "/admin/email-management",
label: "Email Management",
icon: Mail,
moduleKeys: ["EMAIL_MANAGEMENT", "EMAILS", "EMAIL_TEMPLATES"],
},
],
[
{ href: '/admin/kb', label: 'Knowledge Base Management', icon: BookOpen, moduleKeys: ['KNOWLEDGE_BASE_MANAGEMENT', 'KNOWLEDGE_BASE', 'KB'] },
{ href: '/admin/notifications', label: 'Notifications', icon: Bell, moduleKeys: ['NOTIFICATIONS_MANAGEMENT', 'NOTIFICATIONS'] },
{ href: '/admin/review', label: 'Review Management', icon: Star, moduleKeys: ['REVIEW_MANAGEMENT', 'REVIEWS'] },
{ href: '/admin/support', label: 'Support Management', icon: HeadphonesIcon, moduleKeys: ['SUPPORT_MANAGEMENT', 'SUPPORT'] },
{ href: '/admin/report', label: 'Report Management', icon: BarChart3, moduleKeys: ['REPORT_MANAGEMENT', 'REPORTS'] },
{ href: '/admin/ledger', label: 'Ledger Management', icon: Receipt, moduleKeys: ['LEDGER', 'LEDGER_MANAGEMENT'] },
{
href: "/admin/kb",
label: "Knowledge Base Management",
icon: BookOpen,
moduleKeys: ["KNOWLEDGE_BASE_MANAGEMENT", "KNOWLEDGE_BASE", "KB"],
},
{
href: "/admin/notifications",
label: "Notifications",
icon: Bell,
moduleKeys: ["NOTIFICATIONS_MANAGEMENT", "NOTIFICATIONS"],
},
{
href: "/admin/review",
label: "Review Management",
icon: Star,
moduleKeys: ["REVIEW_MANAGEMENT", "REVIEWS"],
},
{
href: "/admin/support",
label: "Support Management",
icon: HeadphonesIcon,
moduleKeys: ["SUPPORT_MANAGEMENT", "SUPPORT"],
},
{
href: "/admin/report",
label: "Report Management",
icon: BarChart3,
moduleKeys: ["REPORT_MANAGEMENT", "REPORTS"],
},
{
href: "/admin/ledger",
label: "Ledger Management",
icon: Receipt,
moduleKeys: ["LEDGER", "LEDGER_MANAGEMENT"],
},
],
];
@ -86,66 +341,98 @@ export default function AdminSidebar(props: {
onNavigate?: () => void;
adminName: string;
adminInitials: string;
theme?: 'light' | 'dark';
theme?: "light" | "dark";
allowedModules?: string[] | null;
isSuperAdmin?: boolean;
}) {
const location = useLocation();
const allowed = createMemo(() => new Set((props.allowedModules || []).map((m) => String(m || '').trim().toUpperCase()).filter(Boolean)));
const allowed = createMemo(
() =>
new Set(
(props.allowedModules || [])
.map((m) =>
String(m || "")
.trim()
.toUpperCase()
)
.filter(Boolean)
)
);
const canShowItem = (item: NavItem) => {
if (props.isSuperAdmin) return true;
if (!props.allowedModules || props.allowedModules.length === 0) return true;
if (item.href === '/admin') return true;
if (item.href === "/admin") return true;
const keys = item.moduleKeys || [];
for (const k of keys) if (allowed().has(String(k).toUpperCase())) return true;
return false;
};
const visibleGroups = createMemo(() => GROUPS.map((group) => group.filter((item) => canShowItem(item))).filter((group) => group.length > 0));
const visibleGroups = createMemo(() =>
GROUPS.map((group) => group.filter((item) => canShowItem(item))).filter(
(group) => group.length > 0
)
);
const isActive = (item: NavItem) => {
if (location.pathname === '/admin') return item.href === '/admin';
if (item.href === '/admin') return false;
if (location.pathname === "/admin") return item.href === "/admin";
if (item.href === "/admin") return false;
if (item.aliasPrefix && location.pathname.startsWith(item.aliasPrefix)) return true;
return location.pathname === item.href || location.pathname.startsWith(`${item.href}/`);
};
const isDark = () => props.theme === 'dark';
const isDark = () => props.theme === "dark";
return (
<aside
style={{
overflow: 'hidden',
display: 'flex',
'flex-direction': 'column',
height: '100%',
background: isDark() ? '#0F172A' : 'white',
'border-right': `1px solid ${isDark() ? '#1F2937' : '#E5E7EB'}`,
transition: 'width 0.3s',
'flex-shrink': 0,
width: props.collapsed ? '64px' : '220px'
overflow: "hidden",
display: "flex",
"flex-direction": "column",
height: "100%",
background: isDark() ? "#0F172A" : "white",
"border-right": `1px solid ${isDark() ? "#1F2937" : "#E5E7EB"}`,
transition: "width 0.3s",
"flex-shrink": 0,
width: props.collapsed ? "64px" : "220px",
}}
>
{/* Logo area */}
<div style={`position:relative;height:64px;display:flex;align-items:center;border-bottom:1px solid ${isDark() ? '#1F2937' : '#E5E7EB'};flex-shrink:0;padding:0 14px`}>
<A href="/admin" onClick={props.onNavigate} style="display:flex;align-items:center;gap:10px;text-decoration:none;overflow:hidden">
<div
style={`position:relative;height:64px;display:flex;align-items:center;border-bottom:1px solid ${isDark() ? "#1F2937" : "#E5E7EB"};flex-shrink:0;padding:0 14px`}
>
<A
href="/admin"
onClick={props.onNavigate}
style="display:flex;align-items:center;gap:10px;text-decoration:none;overflow:hidden"
>
<Show
when={!props.collapsed}
fallback={
<img src="/nxtgauge-icon.png" alt="Nxtgauge" style="width:32px;height:32px;object-fit:contain;flex-shrink:0" />
<img
src="/nxtgauge-icon.png"
alt="Nxtgauge"
style="width:32px;height:32px;object-fit:contain;flex-shrink:0"
/>
}
>
<img src="/nxtgauge-logo.png" alt="Nxtgauge" style="height:44px;object-fit:contain;flex-shrink:0;max-width:180px" />
<img
src="/nxtgauge-logo.png"
alt="Nxtgauge"
style="height:44px;object-fit:contain;flex-shrink:0;max-width:180px"
/>
</Show>
</A>
<button
type="button"
onClick={props.onToggle}
style={`position:absolute;right:-10px;top:50%;transform:translateY(-50%);width:20px;height:20px;border-radius:50%;border:1px solid ${isDark() ? '#1F2937' : '#E5E7EB'};background:${isDark() ? '#111827' : 'white'};box-shadow:0 1px 4px rgba(0,0,0,0.1);display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;color:${isDark() ? '#CBD5E1' : '#6B7280'}`}
aria-label={props.collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
style={`position:absolute;right:-10px;top:50%;transform:translateY(-50%);width:20px;height:20px;border-radius:50%;border:1px solid ${isDark() ? "#1F2937" : "#E5E7EB"};background:${isDark() ? "#111827" : "white"};box-shadow:0 1px 4px rgba(0,0,0,0.1);display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;color:${isDark() ? "#CBD5E1" : "#6B7280"}`}
aria-label={props.collapsed ? "Expand sidebar" : "Collapse sidebar"}
>
<ChevronLeft size={11} style={`transition:transform 0.3s;${props.collapsed ? 'transform:rotate(180deg)' : ''}`} />
<ChevronLeft
size={11}
style={`transition:transform 0.3s;${props.collapsed ? "transform:rotate(180deg)" : ""}`}
/>
</button>
</div>
@ -155,7 +442,9 @@ export default function AdminSidebar(props: {
{(group, gi) => (
<>
<Show when={gi() > 0}>
<div style={`height:1px;background:${isDark() ? '#1F2937' : '#F3F4F6'};margin:6px 4px`} />
<div
style={`height:1px;background:${isDark() ? "#1F2937" : "#F3F4F6"};margin:6px 4px`}
/>
</Show>
<div style="display:flex;flex-direction:column;gap:1px">
<For each={group}>
@ -167,16 +456,18 @@ export default function AdminSidebar(props: {
href={item.href}
onClick={props.onNavigate}
title={props.collapsed ? item.label : undefined}
style={`display:flex;align-items:center;height:36px;border-radius:8px;text-decoration:none;padding:0 ${props.collapsed ? '0' : '10px'};transition:background 140ms ease,color 140ms ease;${props.collapsed ? 'justify-content:center;' : ''}${active() ? 'background:#FFF3EE;color:#FF5E13;' : `color:${isDark() ? '#CBD5E1' : '#6B7280'};`}`}
aria-current={active() ? 'page' : undefined}
style={`display:flex;align-items:center;height:36px;border-radius:8px;text-decoration:none;padding:0 ${props.collapsed ? "0" : "10px"};transition:background 140ms ease,color 140ms ease;${props.collapsed ? "justify-content:center;" : ""}${active() ? "background:#FFF3EE;color:#FF5E13;" : `color:${isDark() ? "#CBD5E1" : "#6B7280"};`}`}
aria-current={active() ? "page" : undefined}
>
<Icon
size={16}
style={`flex-shrink:0;${active() ? 'color:#FF5E13' : `color:${isDark() ? '#94A3B8' : '#9CA3AF'}`}`}
style={`flex-shrink:0;${active() ? "color:#FF5E13" : `color:${isDark() ? "#94A3B8" : "#9CA3AF"}`}`}
strokeWidth={active() ? 2.5 : 2}
/>
<Show when={!props.collapsed}>
<span style="margin-left:9px;font-size:12.5px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{item.label}</span>
<span style="margin-left:9px;font-size:12.5px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
{item.label}
</span>
</Show>
</A>
);

View file

@ -0,0 +1,236 @@
import { createResource, createSignal, Show, For } from "solid-js";
import { useNavigate } from "@solidjs/router";
import AdminLayout from "~/components/AdminLayout";
import { api } from "~/lib/api";
interface EmailTemplate {
name: string;
subject: string;
category: string;
description: string;
}
export default function EmailManagementPage() {
const [selectedTemplate, setSelectedTemplate] = createSignal<EmailTemplate | null>(null);
const [previewHtml, setPreviewHtml] = createSignal("");
const [testEmail, setTestEmail] = createSignal("");
const [sending, setSending] = createSignal(false);
const [message, setMessage] = createSignal("");
const [templates] = createResource(async () => {
const res = await api.get("/admin/email/templates");
return res.data?.templates || [];
});
const groupedTemplates = () => {
const groups: Record<string, EmailTemplate[]> = {};
const all = templates() || [];
all.forEach((t: EmailTemplate) => {
if (!groups[t.category]) groups[t.category] = [];
groups[t.category].push(t);
});
return groups;
};
const loadPreview = async (template: EmailTemplate) => {
setSelectedTemplate(template);
try {
const res = await api.get(`/admin/email/templates/${template.name}/preview`);
setPreviewHtml(res.data?.html || "");
} catch (e) {
setPreviewHtml("<p>Preview not available</p>");
}
};
const sendTestEmail = async () => {
if (!testEmail() || !selectedTemplate()) return;
setSending(true);
setMessage("");
try {
await api.post(`/admin/email/templates/${selectedTemplate()?.name}/test`, {
to_email: testEmail(),
template_name: selectedTemplate()?.name,
variables: {
first_name: "Test User",
otp_code: "123456",
package_name: "Test Package",
amount_paid: "₹999",
tracecoins_amount: "100",
},
});
setMessage("✅ Test email sent successfully!");
setTestEmail("");
} catch (e: any) {
setMessage("❌ Failed to send: " + (e.message || "Unknown error"));
} finally {
setSending(false);
}
};
return (
<AdminLayout>
<div class="p-6">
<h1 class="text-2xl font-bold mb-6">📧 Email Management</h1>
<Show when={message()}>
<div
class={`p-4 rounded-lg mb-4 ${message().includes("✅") ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}`}
>
{message()}
</div>
</Show>
<div class="grid lg:grid-cols-2 gap-6">
{/* Template List */}
<div class="bg-white rounded-xl border overflow-hidden">
<div class="p-4 border-b bg-gray-50">
<h2 class="font-semibold">Email Templates ({templates()?.length || 0})</h2>
</div>
<Show when={templates.loading}>
<div class="p-8 text-center">
<div class="animate-spin w-8 h-8 border-2 border-orange-500 border-t-transparent rounded-full mx-auto mb-4" />
<p class="text-gray-500">Loading templates...</p>
</div>
</Show>
<div class="max-h-[600px] overflow-y-auto">
<For each={Object.entries(groupedTemplates())}>
{([category, items]: [string, EmailTemplate[]]) => (
<div class="border-b last:border-b-0">
<div class="px-4 py-2 bg-gray-100 font-medium text-sm text-gray-700">
{category}
</div>
<For each={items}>
{(template: EmailTemplate) => (
<div
onClick={() => loadPreview(template)}
class={`p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
selectedTemplate()?.name === template.name
? "bg-orange-50 border-l-4 border-orange-500"
: "border-l-4 border-transparent"
}`}
>
<div class="flex justify-between items-start">
<div>
<h3 class="font-medium text-gray-900">{template.name}</h3>
<p class="text-sm text-gray-500">{template.subject}</p>
</div>
<button class="text-orange-600 text-sm hover:underline">Preview</button>
</div>
<p class="text-xs text-gray-400 mt-1">{template.description}</p>
</div>
)}
</For>
</div>
)}
</For>
</div>
</div>
{/* Preview Panel */}
<div class="space-y-6">
<Show
when={selectedTemplate()}
fallback={
<div class="bg-gray-50 rounded-xl p-12 text-center">
<div class="text-6xl mb-4">📧</div>
<p class="text-gray-500">Select a template to preview</p>
</div>
}
>
<div class="bg-white rounded-xl border overflow-hidden">
<div class="p-4 border-b bg-gray-50 flex justify-between items-center">
<div>
<h2 class="font-semibold">{selectedTemplate()?.name}</h2>
<p class="text-sm text-gray-500">{selectedTemplate()?.subject}</p>
</div>
<span class="px-3 py-1 bg-orange-100 text-orange-800 text-xs rounded-full">
{selectedTemplate()?.category}
</span>
</div>
{/* Email Preview */}
<div class="p-4">
<div class="border rounded-lg overflow-hidden">
<div class="bg-gray-100 p-2 text-xs text-gray-500 border-b">Preview</div>
<iframe
srcdoc={previewHtml()}
class="w-full h-[400px]"
sandbox="allow-same-origin"
/>
</div>
</div>
{/* Test Email Section */}
<div class="p-4 border-t bg-gray-50">
<h3 class="font-medium mb-3">Send Test Email</h3>
<div class="flex gap-3">
<input
type="email"
placeholder="Enter test email address"
value={testEmail()}
onInput={(e) => setTestEmail(e.currentTarget.value)}
class="flex-1 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
/>
<button
onClick={sendTestEmail}
disabled={!testEmail() || sending()}
class="px-6 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<Show when={sending()} fallback="Send Test">
<span class="flex items-center gap-2">
<span class="animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full" />
Sending...
</span>
</Show>
</button>
</div>
<p class="text-xs text-gray-500 mt-2">
Test emails will include sample data for preview purposes.
</p>
</div>
{/* Template Variables */}
<div class="p-4 border-t">
<h3 class="font-medium mb-2">Template Variables</h3>
<div class="flex flex-wrap gap-2">
<span class="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">
first_name
</span>
<span class="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">
dashboard_url
</span>
<span class="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">...</span>
</div>
</div>
</div>
</Show>
</div>
</div>
{/* Email Stats Summary */}
<div class="mt-8 grid grid-cols-4 gap-4">
<div class="bg-white p-6 rounded-xl border">
<div class="text-3xl font-bold text-orange-600">35</div>
<div class="text-sm text-gray-500">Total Templates</div>
</div>
<div class="bg-white p-6 rounded-xl border">
<div class="text-3xl font-bold text-green-600">6</div>
<div class="text-sm text-gray-500">Categories</div>
</div>
<div class="bg-white p-6 rounded-xl border">
<div class="text-3xl font-bold text-blue-600">100%</div>
<div class="text-sm text-gray-500">Branded</div>
</div>
<div class="bg-white p-6 rounded-xl border">
<div class="text-3xl font-bold text-purple-600">Mobile</div>
<div class="text-sm text-gray-500">Responsive</div>
</div>
</div>
</div>
</AdminLayout>
);
}