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:
parent
31b3a04a81
commit
2409d85b3c
2 changed files with 606 additions and 79 deletions
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
236
src/routes/admin/email-management.tsx
Normal file
236
src/routes/admin/email-management.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue