const { useState, useRef, useEffect } = React;
const ACCENTS=["#0078d4","#0063b1","#744da9","#e3008c","#c50f1f","#ca5010","#e87722","#b5911b","#498205","#107c10","#00b7c3","#4e6b8c"];
const FONT="'Segoe UI Variable','Segoe UI',system-ui,sans-serif";
const DEFAULT_LOGIN_BG="./login-bg-default.jpg";
function mkTheme(dark,accent){
return{accent,accentHov:blend(accent,dark?"#ffffff":"#000000",0.15),accentBg:accent+"18",accentBorder:accent+"40",
bg:dark?"#1f1f1f":"#f3f3f3",sidebar:dark?"#181818":"#f0f0f0",surface:dark?"#2a2a2a":"#ffffff",
surface2:dark?"#222222":"#fafafa",surface3:dark?"#323232":"#f5f5f5",
border:dark?"rgba(255,255,255,0.08)":"rgba(0,0,0,0.08)",text:dark?"#f0f0f0":"#1c1c1c",
muted:dark?"#a8a8a8":"#5f5f5f",subtle:dark?"#606060":"#9e9e9e",hover:dark?"rgba(255,255,255,0.06)":"rgba(0,0,0,0.05)",
inputBg:dark?"#333":"#fafafa",inBorder:dark?"rgba(255,255,255,0.14)":"rgba(0,0,0,0.12)",
titleBar:"#1a1a1a",danger:dark?"#ff6b6b":"#c50f1f",selectedBg:accent+"18",selectedLeft:accent+"80"};
}
function blend(hex,w,t){
const p=x=>parseInt(x,16);
const r=[p(hex.slice(1,3)),p(hex.slice(3,5)),p(hex.slice(5,7))];
const ww=[p(w.slice(1,3)),p(w.slice(3,5)),p(w.slice(5,7))];
return "#"+r.map((v,i)=>Math.round(v+(ww[i]-v)*t).toString(16).padStart(2,"0")).join("");
}
// ── Background catalogue ──────────────────────────────────────────────────────
const BG_SOLID=[
{id:"white", label:"White", preview:"#ffffff",dark:false},
{id:"smoke", label:"Smoke Gray", preview:"#e4e4e4",dark:false},
{id:"solarized",label:"Solarized", preview:"#fdf6e3",dark:false},
{id:"dark", label:"Dark", preview:"#1a1a1a",dark:true},
{id:"navy", label:"Deep Navy", preview:"#0d2040",dark:true},
{id:"charcoal", label:"Charcoal", preview:"#2b2b2b",dark:true},
];
const BG_IMAGES=[
{id:"mountains",label:"Mountains",swatch:"linear-gradient(180deg,#1b3a5e 0%,#4a7fb5 35%,#7aad6a 65%,#8b7355 100%)",app:"linear-gradient(150deg,#1b3a5e 0%,#4a7fb5 45%,#7aad6a 80%,#8b7355 100%)"},
{id:"beach", label:"Beach", swatch:"linear-gradient(180deg,#87ceeb 0%,#b8e0f0 40%,#f5dfa8 70%,#c8a04a 100%)",app:"linear-gradient(150deg,#87ceeb 0%,#b8e0f0 40%,#f5dfa8 75%,#c8a04a 100%)"},
{id:"ocean", label:"Ocean", swatch:"linear-gradient(180deg,#0d47a1 0%,#1976d2 40%,#42a5f5 75%,#80deea 100%)",app:"linear-gradient(150deg,#0d47a1 0%,#1976d2 40%,#42a5f5 80%,#80deea 100%)"},
{id:"landscape",label:"Landscape",swatch:"linear-gradient(180deg,#1a4a1a 0%,#2e7d32 35%,#7cb342 65%,#c8e6c9 100%)",app:"linear-gradient(150deg,#1a4a1a 0%,#2e7d32 40%,#7cb342 75%,#c8e6c9 100%)"},
{id:"flowers", label:"Flowers", swatch:"linear-gradient(180deg,#880e4f 0%,#e91e63 35%,#f48fb1 65%,#fce4ec 100%)",app:"linear-gradient(150deg,#880e4f 0%,#c2185b 40%,#f48fb1 75%,#fce4ec 100%)"},
{id:"city", label:"City", swatch:"linear-gradient(180deg,#0a0a1a 0%,#1a1a3e 35%,#2d2d6e 60%,#e94560 100%)",app:"linear-gradient(150deg,#0a0a1a 0%,#1a1a3e 40%,#2d2d6e 75%,#e94560 100%)"},
{id:"sunset", label:"Sunset", swatch:"linear-gradient(180deg,#1a0533 0%,#7b1fa2 30%,#e65100 65%,#ff8f00 100%)",app:"linear-gradient(150deg,#1a0533 0%,#7b1fa2 35%,#e65100 70%,#ff8f00 100%)"},
{id:"aurora", label:"Aurora", swatch:"linear-gradient(180deg,#0d1b2a 0%,#1a5276 35%,#1abc9c 65%,#a9f0d1 100%)",app:"linear-gradient(150deg,#0d1b2a 0%,#1a5276 40%,#1abc9c 70%,#a9f0d1 100%)"},
{id:"desert", label:"Desert", swatch:"linear-gradient(180deg,#1a0a00 0%,#b35900 35%,#e8a020 65%,#f5d080 100%)",app:"linear-gradient(150deg,#1a0a00 0%,#b35900 40%,#e8a020 70%,#f5d080 100%)"},
{id:"arctic", label:"Arctic", swatch:"linear-gradient(180deg,#e8f4ff 0%,#b8d8f0 35%,#d0eaff 65%,#f0f8ff 100%)",app:"linear-gradient(150deg,#dceefb 0%,#b8d8f0 40%,#d0eaff 75%,#f0f8ff 100%)"},
];
function getBgStyle(bgId){
if(!bgId||bgId==="white")return{};
if(typeof bgId==="string"&&bgId.startsWith("#"))return{background:bgId};
const solid=BG_SOLID.find(b=>b.id===bgId);
if(solid)return{background:solid.preview};
const img=BG_IMAGES.find(b=>b.id===bgId);
if(img)return{background:img.app};
return{};
}
function bgIsImage(bgId){return BG_IMAGES.some(b=>b.id===bgId);}
function bgIsDark(bgId){
if(typeof bgId==="string"&&/^#[0-9a-f]{6}$/i.test(bgId)){
const r=parseInt(bgId.slice(1,3),16),g=parseInt(bgId.slice(3,5),16),b=parseInt(bgId.slice(5,7),16);
return (r*0.299+g*0.587+b*0.114)<128;
}
const solid=BG_SOLID.find(b=>b.id===bgId);
if(solid)return solid.dark;
const imgDark=["city","sunset","aurora","ocean","mountains"];
return imgDark.includes(bgId);
}
// ── Profile system ────────────────────────────────────────────────────────────
const INIT_PROFILES=[
{id:"p1",name:"Alex Johnson",initials:"AJ",color:"#744da9",password:"demo",
accountIds:["alex@example-corp.com","default"],
settings:{accent:"#0078d4",isDark:false,bg:"white",notifications:true,sound:false,readReceipts:false,autoMark:true}},
{id:"p2",name:"Sales Team",initials:"ST",color:"#107c10",password:"sales",
accountIds:["sales@example-corp.com"],
settings:{accent:"#107c10",isDark:false,bg:"white",notifications:true,sound:true,readReceipts:true,autoMark:true}},
{id:"p3",name:"J. Doe Personal",initials:"JD",color:"#ca5010",password:"demo3",
accountIds:["j.doe@mailbox.org"],
settings:{accent:"#ca5010",isDark:true,bg:"dark",notifications:false,sound:false,readReceipts:false,autoMark:false}},
];
// ── Accounts + folders ────────────────────────────────────────────────────────
const ALL_ACCOUNTS=[
{id:"default", display:"Default Account", email:null, av:"DA",avColor:"#0078d4"},
{id:"alex@example-corp.com", display:"Alex", email:"alex@example-corp.com", av:"AJ",avColor:"#744da9"},
{id:"sales@example-corp.com", display:"Sales", email:"sales@example-corp.com",av:"SA",avColor:"#107c10"},
{id:"j.doe@mailbox.org", display:"J. Doe", email:"j.doe@mailbox.org", av:"JD",avColor:"#ca5010"},
];
const STD_FOLDERS=[
{id:"inbox", label:"Inbox", icon:"ti-inbox", badge:true},
{id:"sent", label:"Sent", icon:"ti-send", badge:false},
{id:"drafts", label:"Drafts", icon:"ti-file-text", badge:true},
{id:"out", label:"Outbox", icon:"ti-upload", badge:false},
{id:"archive", label:"Archive", icon:"ti-archive", badge:false},
{id:"templates",label:"Templates",icon:"ti-layout-grid", badge:false},
{id:"trash", label:"Trash", icon:"ti-trash", badge:false},
];
// ── Seed emails ───────────────────────────────────────────────────────────────
const SEED=[
{id:1,account:"alex@example-corp.com",folder:"inbox",from:"Sarah Chen",from_email:"sarah@acme.com",to:"alex@example-corp.com",subject:"Q3 Budget Review — Action Required",preview:"Hi team, please review the attached budget spreadsheet before Friday's meeting...",body:"Hi Alex,\n\nPlease review the attached budget spreadsheet before Friday's meeting. We need everyone's input on proposed changes.\n\nKey items:\n • Marketing budget +15%\n • IT infrastructure refresh ($240K)\n • Travel policy revisions\n\nReturn by EOD Thursday.\n\nBest,\nSarah",date:"2:34 PM",read:false,starred:true,atts:["Q3_Budget_2024.xlsx","Policy_Updates.pdf"],av:"SC",avColor:"#0078d4"},
{id:2,account:"alex@example-corp.com",folder:"inbox",from:"James Holloway",from_email:"james@dev.io",to:"alex@example-corp.com",subject:"Re: Deployment pipeline — staging env down",preview:"The issue was traced to a misconfigured Nginx proxy rule...",body:"Hey Alex,\n\nIssue traced to a misconfigured Nginx proxy rule added during last night's security patch. Fix pushed — health check should go green in ~10 min.\n\nJames",date:"11:20 AM",read:false,starred:false,atts:[],av:"JH",avColor:"#744da9"},
{id:3,account:"alex@example-corp.com",folder:"inbox",from:"Priya Nair",from_email:"priya@design.studio",to:"alex@example-corp.com",subject:"Brand refresh assets — final delivery",preview:"Attached are the final approved assets for the brand refresh...",body:"Hi Alex,\n\nAttached are the final approved assets. SVG, PNG @1x/2x, and Figma source.\n\nPriya",date:"Yesterday",read:true,starred:true,atts:["Brand_Assets_Final.zip"],av:"PN",avColor:"#e87722"},
{id:4,account:"alex@example-corp.com",folder:"sent",from:"Alex",from_email:"alex@example-corp.com",to:"sarah@acme.com",subject:"Re: Q3 Budget Review — Action Required",preview:"Hi Sarah, I've reviewed the spreadsheet...",body:"Hi Sarah,\n\nReviewed the spreadsheet. Aligned on the marketing increase — would like to discuss travel policy changes.\n\nAlex",date:"3:10 PM",read:true,starred:false,atts:[],av:"AJ",avColor:"#744da9"},
{id:5,account:"sales@example-corp.com",folder:"inbox",from:"Mark Okafor",from_email:"mark@partners.net",to:"sales@example-corp.com",subject:"Partnership proposal — follow-up",preview:"Thanks for the meeting yesterday. Revised proposal attached...",body:"Hi,\n\nRevised proposal attached. 90-day pilot, 30% lower upfront, dedicated account manager.\n\nMark",date:"Yesterday",read:false,starred:false,atts:["Partnership_Proposal_v3.pptx"],av:"MO",avColor:"#00b7c3"},
{id:6,account:"sales@example-corp.com",folder:"inbox",from:"Lena Fischer",from_email:"lena@gmbh.de",to:"sales@example-corp.com",subject:"Order #4821 — shipping inquiry",preview:"Good morning, I wanted to follow up on my order placed two weeks ago...",body:"Good morning,\n\nFollowing up on order #4821 placed two weeks ago. No shipping confirmation received.\n\nLena Fischer",date:"10:05 AM",read:false,starred:false,atts:[],av:"LF",avColor:"#0063b1"},
{id:7,account:"j.doe@mailbox.org",folder:"inbox",from:"Tech Digest",from_email:"news@techdigest.io",to:"j.doe@mailbox.org",subject:"This week in open source",preview:"Mutt 2.3.2 released, Neovim 0.11 ships treesitter overhaul...",body:"This Week in Open Source\n─────────────────────────\n• Mutt 2.3.2 released\n• Neovim 0.11 treesitter overhaul\n• Linux 6.9 released",date:"9:00 AM",read:true,starred:false,atts:[],av:"TD",avColor:"#498205"},
{id:8,account:"default",folder:"inbox",from:"Mutt Daemon",from_email:"mutt@localhost",to:"default",subject:"System notification — 4 new messages",preview:"Your mailbox has received 4 new messages...",body:"Mutt Mail Notification\n──────────────────────\nMailbox: .Mails/default/inbox\nNew msgs: 4\nSMB: \\\\mailserver\\mail (connected)",date:"10:55 AM",read:true,starred:false,atts:[],av:"MD",avColor:"#107c10"},
];
const INIT_RULES=[
{id:"r1",name:"Newsletter auto-archive",enabled:true,condLogic:"any",conditions:[{id:"c1",field:"subject",op:"contains",value:"unsubscribe"},{id:"c2",field:"from",op:"contains",value:"newsletter"}],actions:[{id:"a1",type:"move",folder:"archive"},{id:"a2",type:"markRead"}]},
{id:"r2",name:"Flag partnership emails",enabled:true,condLogic:"any",conditions:[{id:"c3",field:"subject",op:"contains",value:"partnership"}],actions:[{id:"a3",type:"star"}]},
];
const INIT_CONTACTS=[
{id:"ct1",name:"Sarah Chen", email:"sarah@acme.com", av:"SC",avColor:"#0078d4"},
{id:"ct2",name:"James Holloway", email:"james@dev.io", av:"JH",avColor:"#744da9"},
{id:"ct3",name:"Priya Nair", email:"priya@design.studio", av:"PN",avColor:"#e87722"},
{id:"ct4",name:"Mark Okafor", email:"mark@partners.net", av:"MO",avColor:"#00b7c3"},
{id:"ct5",name:"Lena Fischer", email:"lena@gmbh.de", av:"LF",avColor:"#0063b1"},
{id:"ct6",name:"Tech Digest", email:"news@techdigest.io", av:"TD",avColor:"#498205"},
];
// ── Utilities ─────────────────────────────────────────────────────────────────
const quoteBody=(text,from)=>`\n\n— On ${new Date().toDateString()}, ${from} wrote:\n${(text??"").split("\n").map(l=>`> ${l}`).join("\n")}`;
const fwdBody=e=>`\n\n---------- Forwarded message ----------\nFrom: ${e.from}\nDate: ${e.date}\nSubject: ${e.subject}\nTo: ${e.to}\n\n${e.body??""}`;
const formatSize=b=>b<1024?b+" B":b<1048576?(b/1024).toFixed(1)+" KB":(b/1048576).toFixed(1)+" MB";
function getAttIcon(name){
const ext=(name.split(".").pop()||"").toLowerCase();
if(["jpg","jpeg","png","gif","svg","webp"].includes(ext))return "ti-photo";
if(ext==="pdf")return "ti-file-type-pdf";
if(["doc","docx"].includes(ext))return "ti-file-type-doc";
if(["xls","xlsx"].includes(ext))return "ti-file-type-xls";
if(["ppt","pptx"].includes(ext))return "ti-file-type-ppt";
if(["zip","gz","tar","7z"].includes(ext))return "ti-file-zip";
if(["eml","msg"].includes(ext))return "ti-mail";
if(["txt","md"].includes(ext))return "ti-file-text";
return "ti-file";
}
function printAttachment(name,srcEmail){
const ext=(name.split(".").pop()||"").toLowerCase();
let html;
if(ext==="eml"&&srcEmail){
html=`
${name}
${(srcEmail.body||"").replace(/&/g,"&").replace(//g,">")}
`;
} else {
html=`${name}
📎 ${name}
Attachment: ${name}
From email: ${srcEmail?.subject||"(no subject)"}
Date: ${new Date().toLocaleString()}
This is a demo attachment — print preview via DashMail.
`;
}
const w=window.open("","_blank","width=800,height=600");
if(!w)return;
w.document.write(html);
w.document.close();
w.focus();
setTimeout(()=>{w.print();},400);
}
function downloadAttachment(name,srcEmail){
const ext=(name.split(".").pop()||"").toLowerCase();
let content,mime;
if(ext==="eml"&&srcEmail){
content=[
`From: ${srcEmail.from||""} <${srcEmail.from_email||""}>`,
`To: ${srcEmail.to||""}`,
`Subject: ${srcEmail.subject||""}`,
`Date: ${srcEmail.date||new Date().toUTCString()}`,
`MIME-Version: 1.0`,
`Content-Type: text/plain; charset=UTF-8`,
``,
srcEmail.body||""
].join("\r\n");
mime="message/rfc822";
} else {
content=`Demo attachment: ${name}\nGenerated by DashMail\nDate: ${new Date().toLocaleString()}\n\nThis is a placeholder file. In a live deployment this file would be fetched from the SMB share.`;
mime="text/plain";
}
const blob=new Blob([content],{type:mime});
const url=URL.createObjectURL(blob);
const a=document.createElement("a");
a.href=url; a.download=name; a.click();
setTimeout(()=>URL.revokeObjectURL(url),5000);
}
const uid=()=>Math.random().toString(36).slice(2,9);
function printEmail(email){
const w=window.open("","_blank","width=720,height=960");
w.document.write(`${email.subject} ${email.subject} From: ${email.from} <${email.from_email}>
To: ${email.to}
Date: ${email.date}
${email.atts?.length?`
Attachments: ${email.atts.join(", ")}
`:""}
${(email.body??"").replace(/`);
w.document.close();w.focus();setTimeout(()=>w.print(),400);
}
// ── Base components ───────────────────────────────────────────────────────────
function Avatar({initials,color,size=36}){
return
{initials}
;
}
function IBtn({icon,title,onClick,C,danger=false,active=false,size=16,label=""}){
const[h,setH]=useState(false);
return
setH(true)} onMouseLeave={()=>setH(false)}
style={{background:active?C.accentBg:h?C.hover:"transparent",border:"none",borderRadius:5,padding:"6px 8px",cursor:"pointer",
color:danger&&h?C.danger:active?C.accent:C.muted,display:"flex",alignItems:"center",gap:4,transition:"all 0.1s",fontFamily:FONT,fontSize:13}}>
{label&&{label} }
;
}
function Sep({C}){return
;}
function Toggle({on,onChange,C}){
const[v,setV]=useState(on??false);
return
{setV(x=>!x);onChange&&onChange(!v);}}
style={{width:36,height:20,borderRadius:10,background:v?C.accent:C.muted,cursor:"pointer",position:"relative",transition:"background 0.2s",flexShrink:0}}>
;
}
function PanelHeader({title,onClose,C}){
return
{title}
;
}
// ── Full Settings Modal ───────────────────────────────────────────────────────
const SETTINGS_NAV=[
{id:"general", icon:"ti-settings", label:"General"},
{id:"theme", icon:"ti-palette", label:"Appearance"},
{id:"notifications",icon:"ti-bell", label:"Notifications"},
{id:"accounts", icon:"ti-user-circle", label:"Accounts"},
{id:"rules", icon:"ti-filter", label:"Filters & Rules"},
{id:"privacy", icon:"ti-eye", label:"Privacy"},
{id:"emailaddr", icon:"ti-at", label:"Email addresses"},
{id:"encryption", icon:"ti-key", label:"Encryption"},
{id:"spam", icon:"ti-trash", label:"Spam & Trash"},
{id:"backup", icon:"ti-database", label:"Backup"},
{id:"advanced", icon:"ti-adjustments", label:"Advanced",keywords:"security smb share server"},
];
// ── Email Addresses Settings ──────────────────────────────────────────────────
const ACCT_STATS={
"alex@example-corp.com":{count:1842,size:"312 MB"},
"sales@example-corp.com":{count:234,size:"41 MB"},
"j.doe@mailbox.org":{count:89,size:"14 MB"},
"default":{count:12,size:"2 MB"},
};
// Mock: extra folder on the share not yet imported
const MOCK_NEW_FOLDERS=["invoices@example-corp.com"];
function SettingsEmailAddr({accounts,C,smbConfig,onDiscoverMore}){
const[scanning,setScanning]=useState(false);
const[scanResult,setScanResult]=useState(null);
function runScan(){
setScanning(true);setScanResult(null);
setTimeout(()=>{
setScanning(false);
const known=new Set(accounts.map(a=>a.email||a.id));
const newOnes=MOCK_NEW_FOLDERS.filter(e=>!known.has(e));
setScanResult({found:newOnes.length,folders:newOnes});
},2000);
}
return
Email addresses
Folders imported from your mail server. The SMB share is never modified.
{/* Imported accounts */}
{accounts.map(acct=>{
const stats=ACCT_STATS[acct.email||acct.id]||{count:0,size:"—"};
return
{acct.av}
{acct.email||acct.id}
{acct.display&&acct.display!==(acct.email||acct.id)&&
{acct.display} }
{stats.count.toLocaleString()} emails • {stats.size}
;
})}
{/* Discover more */}
Discover more accounts
Scan your mail server for new folders added since your last import.
{!scanResult&&
{scanning
?<> Scanning…>
:<> Scan for new accounts>}
}
{scanResult&&scanResult.found>0&&
{scanResult.found} new folder{scanResult.found!==1?"s":""} found
{scanResult.folders.join(", ")}
Set up now
}
{scanResult&&scanResult.found===0&&
No new folders found. Your account list is up to date.
setScanResult(null)}
style={{marginLeft:"auto",background:"transparent",border:"none",
color:C.muted,cursor:"pointer",fontSize:12,fontFamily:FONT,padding:"2px 6px"}}>
Dismiss
}
;
}
const HELP_TIPS={
general:{title:"General",tips:[
{icon:"ti-user", text:"Your name and initials appear in the sidebar and profile switcher."},
{icon:"ti-palette", text:"Profile colour helps distinguish accounts at a glance."},
{icon:"ti-language", text:"Language and date format apply to this profile only."},
]},
theme:{title:"Appearance",tips:[
{icon:"ti-moon", text:"Dark mode reduces eye strain in low-light environments."},
{icon:"ti-color-swatch",text:"Accent colour is used for highlights, buttons, and active states."},
{icon:"ti-photo", text:"Login background — landscape photos at 1920×1080 or wider work best."},
]},
notifications:{title:"Notifications",tips:[
{icon:"ti-bell", text:"Desktop notifications require browser permission — click Allow when prompted."},
{icon:"ti-volume", text:"Use the Preview button to audition each sound before saving."},
{icon:"ti-moon", text:"Do Not Disturb silences all alerts without disabling them permanently."},
]},
accounts:{title:"Accounts",tips:[
{icon:"ti-server", text:"Accounts map to folders on your SMB mail share."},
{icon:"ti-mail", text:"Each account has its own inbox, sent, drafts, and custom folders."},
]},
rules:{title:"Filters & Rules",tips:[
{icon:"ti-filter", text:"Rules run automatically on incoming mail."},
{icon:"ti-stack", text:"Combine multiple conditions with AND / OR logic."},
{icon:"ti-arrow-right",text:"Actions: move, star, mark as read, or send to junk."},
]},
privacy:{title:"Privacy",tips:[
{icon:"ti-eye-off", text:"Blocking tracking pixels prevents senders from knowing when you open mail."},
{icon:"ti-photo-off", text:"Disabling external images stops remote servers from logging your IP."},
{icon:"ti-receipt", text:"Read receipts are off by default — enable only if required."},
]},
emailaddr:{title:"Email addresses",tips:[
{icon:"ti-at", text:"Each address maps to a folder on your SMB mail share."},
{icon:"ti-refresh", text:"Scan for new accounts to detect folders added since last setup."},
]},
encryption:{title:"Encryption",tips:[
{icon:"ti-lock", text:"End-to-end encryption will be available in a future update."},
]},
spam:{title:"Spam & Trash",tips:[
{icon:"ti-trash", text:"Deleted items stay in Trash until the auto-empty period expires."},
{icon:"ti-ban", text:"Spam is automatically removed after the retention period you set."},
{icon:"ti-user-check", text:"Marking Not Spam whitelists the sender for future mail."},
]},
backup:{title:"Backup",tips:[
{icon:"ti-database", text:"Backups are stored locally — the SMB share is never modified."},
{icon:"ti-calendar", text:"Schedule daily or weekly backups to run automatically."},
{icon:"ti-history", text:"Older backup sets are pruned based on your retention setting."},
]},
advanced:{title:"Advanced",tips:[
{icon:"ti-lock", text:"Set a PIN to prevent unauthorised access to this profile."},
{icon:"ti-server", text:"SMB Share settings control which mail server folder DashMail reads."},
{icon:"ti-shield", text:"Auto-lock clears the session after a period of inactivity."},
]},
};
function FullSettings({profile,onSave,onClose,C,accounts,updateAccount,rules,setRules,allFolders,smbConfig,setSmbConfig,loginBg,setLoginBg,loginDark,setLoginDark,onSetupNew}){
const[section,setSection]=useState("general");
const[search,setSearch]=useState("");
const[ls,setLS]=useState({...profile.settings});
const[profName,setProfName]=useState(profile.name);
const[profInitials,setProfInitials]=useState(profile.initials);
const[profColor,setProfColor]=useState(profile.color);
const[helpOpen,setHelpOpen]=useState(false);
const[advancedTab,setAdvancedTab]=useState("security");
const filtered=search
?SETTINGS_NAV.filter(n=>
n.label.toLowerCase().includes(search.toLowerCase())||
(n.keywords&&n.keywords.toLowerCase().includes(search.toLowerCase())))
:SETTINGS_NAV;
function save(){onSave({...profile,name:profName,initials:profInitials,color:profColor,settings:{...ls}});}
// Derive a local theme from ls (for live preview inside settings)
const lC=mkTheme(ls.isDark,ls.accent);
return
{/* Header */}
Settings
setHelpOpen(p=>!p)}/>
{/* Left nav */}
{filtered.map(n=>{
const sel=section===n.id;
return
{setSection(n.id);setSearch("");}}
style={{display:"flex",alignItems:"center",gap:9,padding:"8px 10px",borderRadius:7,cursor:"pointer",marginBottom:1,
background:sel?C.selectedBg:"transparent",borderLeft:`3px solid ${sel?C.accent:"transparent"}`}}
onMouseEnter={e=>{if(!sel)e.currentTarget.style.background=C.hover;}}
onMouseLeave={e=>{if(!sel)e.currentTarget.style.background="transparent";}}>
{n.label}
;
})}
{/* Content area */}
{/* Help panel */}
{helpOpen&&(()=>{
const tips=(HELP_TIPS[section]||HELP_TIPS.general);
return
{tips.title}
setHelpOpen(false)} style={{background:"none",border:"none",cursor:"pointer",
color:C.muted,padding:2,fontSize:14,lineHeight:1}}>
{tips.tips.map((t,i)=>(
{t.text}
))}
;
})()}
{section==="advanced"
?SETTINGS_NAV.find(n=>n.id==="advanced")?.label
:SETTINGS_NAV.find(n=>n.id===section)?.label}
{section==="general"&&
}
{section==="theme"&&}
{section==="notifications"&&}
{section==="accounts"&&}
{section==="rules"&&}
{section==="privacy"&&}
{section==="backup"&&}
{section==="spam"&&}
{section==="emailaddr"&&{onClose();onSetupNew?.();}}/>}
{section==="encryption"&&
This section will be available in a future update.
}
{section==="advanced"&&<>
{/* Sub-tab bar */}
{[{id:"security",icon:"ti-lock",label:"Security"},{id:"smb",icon:"ti-server",label:"SMB Share"}].map(t=>(
setAdvancedTab(t.id)}
style={{display:"flex",alignItems:"center",gap:6,padding:"6px 16px",borderRadius:6,border:"none",
background:advancedTab===t.id?C.surface:"transparent",
color:advancedTab===t.id?C.text:C.muted,
cursor:"pointer",fontFamily:FONT,fontSize:13,fontWeight:advancedTab===t.id?600:400,
boxShadow:advancedTab===t.id?`0 1px 3px rgba(0,0,0,0.12)`:"none",transition:"all 0.15s"}}>
{t.label}
))}
{advancedTab==="security"&&}
{advancedTab==="smb"&&}
>}
{/* Footer */}
Cancel
{save();onClose();}} style={{background:C.accent,color:"#fff",border:"none",borderRadius:6,padding:"7px 20px",fontSize:13.5,cursor:"pointer",fontFamily:FONT,fontWeight:600}}>Save changes
;
}
// ── Settings sections ─────────────────────────────────────────────────────────
function SRow({label,sub,children,C,onClick}){
return
{e.currentTarget.style.background=C.hover;}:undefined}
onMouseLeave={onClick?e=>{e.currentTarget.style.background="transparent";}:undefined}>
{children}
;
}
function SCard({children,C}){
return
{children}
;
}
function SectionHead({label,C}){
return
{label}
;
}
function SettingsGeneral({ls,setLS,profile,profName,setProfName,profInitials,setProfInitials,profColor,setProfColor,C}){
const[pdOpen,setPdOpen]=useState(false);
const[firstName,setFirstName]=useState(()=>profName.split(" ")[0]||"");
const[lastName,setLastName]=useState(()=>profName.split(" ").slice(1).join(" ")||"");
const[company,setCompany]=useState("");
const[street,setStreet]=useState("");
const[zip,setZip]=useState("");
const[city,setCity]=useState("");
const[country,setCountry]=useState("United States of America");
const[phone,setPhone]=useState("");
const[pdSaved,setPdSaved]=useState(false);
const COUNTRIES=["United States of America","Canada","United Kingdom","Australia","Germany","France","Netherlands","New Zealand","Other"];
const fieldSt={width:"100%",boxSizing:"border-box",padding:"7px 10px",border:`1px solid ${C.inBorder}`,
borderRadius:5,fontSize:13,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg};
const lbSt={display:"block",fontSize:12,fontWeight:600,color:C.muted,marginBottom:4};
function savePD(){
const full=(firstName+" "+lastName).trim();
if(full)setProfName(full);
setPdSaved(true);
setTimeout(()=>setPdSaved(false),2500);
}
return <>
setPdOpen(p=>!p)}>
{pdOpen&&
{/* Name row */}
{/* Initials + Avatar color */}
Initials
setProfInitials(e.target.value.toUpperCase().slice(0,2))} maxLength={2}
style={{...fieldSt,width:"100%",textAlign:"center",fontWeight:700,letterSpacing:"0.05em"}}/>
Avatar color
{ACCENTS.map(a=>(
setProfColor(a)} style={{width:24,height:24,borderRadius:5,background:a,
border:`2.5px solid ${a===profColor?"#fff":"transparent"}`,outline:a===profColor?`2px solid ${a}`:"none",
cursor:"pointer",transition:"transform 0.1s"}}
onMouseEnter={e=>e.currentTarget.style.transform="scale(1.15)"}
onMouseLeave={e=>e.currentTarget.style.transform="scale(1)"}/>
))}
{/* Avatar preview */}
{(firstName+" "+lastName).trim()||"Your name"}
{profile.accountIds?.[0]||""}
{/* Company */}
Company
setCompany(e.target.value)} style={fieldSt}/>
{/* Street */}
Street and number
setStreet(e.target.value)} style={fieldSt}/>
{/* ZIP + City */}
{/* Country */}
Country
setCountry(e.target.value)}
style={{...fieldSt,appearance:"auto"}}>
{COUNTRIES.map(c=>{c} )}
{/* Phone */}
Phone
setPhone(e.target.value)} style={fieldSt}/>
{/* Save */}
Save
{pdSaved&&
Saved
}
}
English Deutsch Español Français
MM/DD/YYYY DD/MM/YYYY YYYY-MM-DD
12-hour (AM/PM) 24-hour
setLS(p=>({...p,autoMark:v}))} C={C}/>
1 line 2 lines 3 lines
>;
}
function SettingsTheme({ls,setLS,lC,C,loginBg,setLoginBg,loginDark,setLoginDark}){
const loginBgRef=useRef();
const customAccent=!ACCENTS.includes(ls.accent);
const customBg=ls.bg&&ls.bg.startsWith("#")&&!BG_SOLID.some(s=>s.preview.toLowerCase()===ls.bg.toLowerCase());
function bgIsDarkHex(hex){
if(!/^#[0-9a-f]{6}$/i.test(hex))return false;
const r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16);
return (r*0.299+g*0.587+b*0.114)<128;
}
return <>
{/* Accent colors */}
{ACCENTS.map(a=>(
setLS(p=>({...p,accent:a}))}
style={{width:36,height:36,borderRadius:"50%",background:a,cursor:"pointer",
border:`3px solid ${a===ls.accent?"#fff":"transparent"}`,
outline:a===ls.accent?`2.5px solid ${a}`:"none",transition:"transform 0.1s",boxSizing:"border-box"}}
onMouseEnter={e=>e.currentTarget.style.transform="scale(1.15)"}
onMouseLeave={e=>e.currentTarget.style.transform="scale(1)"}/>
))}
{/* Custom accent picker */}
setLS(p=>({...p,accent:e.target.value}))}
style={{position:"absolute",inset:0,opacity:0,cursor:"pointer",width:"100%",height:"100%",border:"none",padding:0}}/>
{!customAccent&& }
Current accent: {ls.accent.toUpperCase()}
{customAccent&&· custom }
{/* Backgrounds */}
{BG_SOLID.map(bg=>(
setLS(p=>({...p,bg:bg.id,isDark:bg.dark}))}
style={{display:"flex",flexDirection:"column",alignItems:"center",gap:7,cursor:"pointer"}}>
{bg.label}
))}
{/* Custom background picker */}
setLS(p=>({...p,bg:e.target.value,isDark:bgIsDarkHex(e.target.value)}))}
style={{position:"absolute",inset:0,opacity:0,cursor:"pointer",width:"100%",height:"100%",border:"none",padding:0}}/>
{!customBg&&
}
Custom
{/* Live preview strip */}
Reading pane preview
{/* Login background */}
{[{v:true,icon:"ti-moon",label:"Dark"},{v:false,icon:"ti-sun",label:"Light"}].map(opt=>(
setLoginDark(opt.v)}
style={{display:"flex",alignItems:"center",gap:5,padding:"5px 13px",borderRadius:5,border:"none",
background:loginDark===opt.v?C.surface:"transparent",
color:loginDark===opt.v?C.text:C.muted,
cursor:"pointer",fontFamily:FONT,fontSize:12.5,fontWeight:loginDark===opt.v?600:400,
boxShadow:loginDark===opt.v?`0 1px 3px rgba(0,0,0,0.1)`:"none",transition:"all 0.15s"}}>
{opt.label}
))}
{/* Thumbnail */}
{loginBg?"Custom image":"Default mountain photo"}
{
const file=e.target.files?.[0];
if(!file)return;
const reader=new FileReader();
reader.onload=ev=>setLoginBg(ev.target.result);
reader.readAsDataURL(file);
e.target.value="";
}}/>
loginBgRef.current?.click()}
style={{padding:"6px 14px",borderRadius:6,border:`1px solid ${C.border}`,background:C.surface2,
color:C.text,cursor:"pointer",fontFamily:FONT,fontSize:13,display:"flex",alignItems:"center",gap:6}}>
Upload image
{loginBg&&setLoginBg(null)}
style={{padding:"6px 14px",borderRadius:6,border:`1px solid ${C.border}`,background:"transparent",
color:C.muted,cursor:"pointer",fontFamily:FONT,fontSize:13,display:"flex",alignItems:"center",gap:6}}>
Reset to default
}
JPG, PNG, WEBP · shown on the login screen right panel
>;
}
function SettingsNotifications({ls,setLS,C}){
const SOUNDS=[
{id:"chime", label:"Chime"},
{id:"bell", label:"Bell"},
{id:"ping", label:"Ping"},
{id:"ding", label:"Ding"},
{id:"pop", label:"Pop"},
{id:"classic",label:"Classic"},
{id:"subtle", label:"Subtle"},
];
function playSound(id){
try{
const ctx=new (window.AudioContext||window.webkitAudioContext)();
const tone=(freq,start,dur,type="sine",vol=0.28)=>{
const osc=ctx.createOscillator(),g=ctx.createGain();
osc.connect(g);g.connect(ctx.destination);
osc.type=type;osc.frequency.setValueAtTime(freq,ctx.currentTime+start);
g.gain.setValueAtTime(0,ctx.currentTime+start);
g.gain.linearRampToValueAtTime(vol,ctx.currentTime+start+0.015);
g.gain.exponentialRampToValueAtTime(0.001,ctx.currentTime+start+dur);
osc.start(ctx.currentTime+start);osc.stop(ctx.currentTime+start+dur+0.05);
};
if(id==="chime") {tone(1319,0,0.5);tone(1047,0.18,0.45);tone(1568,0.36,0.7);}
else if(id==="bell") {tone(440,0,1.2);tone(880,0.05,0.9,undefined,0.15);}
else if(id==="ping") {tone(1400,0,0.25,undefined,0.22);}
else if(id==="ding") {tone(880,0,0.18);tone(660,0.2,0.4);}
else if(id==="pop") {tone(220,0,0.08,"triangle",0.45);}
else if(id==="classic"){tone(660,0,0.18);tone(880,0.22,0.28);}
else if(id==="subtle") {tone(600,0,0.35,undefined,0.12);}
setTimeout(()=>ctx.close(),2500);
}catch(e){}
}
const selSt={background:C.inputBg,border:`1px solid ${C.inBorder}`,borderRadius:5,
padding:"4px 8px",color:C.text,fontFamily:FONT,fontSize:13,outline:"none"};
return
setLS(p=>({...p,notifications:v}))} C={C}/>
setLS(p=>({...p,sound:v}))} C={C}/>
{ls.sound&&
setLS(p=>({...p,soundChoice:e.target.value}))} style={selSt}>
{SOUNDS.map(s=>{s.label} )}
playSound(ls.soundChoice||"chime")}
style={{background:C.accent,border:"none",borderRadius:5,padding:"4px 10px",
color:"#fff",fontFamily:FONT,fontSize:12,cursor:"pointer",display:"flex",alignItems:"center",gap:4}}>
Preview
}
;
}
function SettingsAccounts({accounts,profile,updateAccount,C}){
const[editingId,setEditingId]=useState(null);
const[draft,setDraft]=useState({});
const[emailErr,setEmailErr]=useState("");
function startEdit(acc){
setEditingId(acc.id);
setEmailErr("");
setDraft({display:acc.display,av:acc.av,avColor:acc.avColor,email:acc.email??acc.id});
}
function cancelEdit(){setEditingId(null);setDraft({});setEmailErr("");}
function saveEdit(id){
const trimmed=draft.email.trim();
if(trimmed&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)){
setEmailErr("Enter a valid email address.");return;
}
updateAccount(id,{display:draft.display,av:draft.av,avColor:draft.avColor,email:trimmed||null});
setEditingId(null);setDraft({});setEmailErr("");
}
return <>
Accounts in this profile: {profile.accountIds.length}. All accounts read from .Mails/ on the SMB share.
{accounts.map(acc=>{
const isEditing=editingId===acc.id;
return
{/* Row header */}
{isEditing?draft.display:acc.display}
{isEditing?(draft.email||"No email set"):(acc.email??("Default account · .Mails/default/"))}
{!isEditing&&startEdit(acc)}/>}
{isEditing&&}
{/* Inline edit form */}
{isEditing&&
Email address
{setDraft(p=>({...p,email:e.target.value}));setEmailErr("");}}
placeholder="you@example.com" type="email"
style={{width:"100%",boxSizing:"border-box",padding:"7px 10px",
border:`1px solid ${emailErr?C.danger??'#c50f1f':C.inBorder}`,borderRadius:5,
fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg}}/>
{emailErr&&
{emailErr}
}
Display name
setDraft(p=>({...p,display:e.target.value}))}
style={{width:"100%",boxSizing:"border-box",padding:"7px 10px",border:`1px solid ${C.inBorder}`,borderRadius:5,fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg}}/>
Initials
setDraft(p=>({...p,av:e.target.value.toUpperCase().slice(0,2)}))} maxLength={2}
style={{width:56,padding:"7px 10px",border:`1px solid ${C.inBorder}`,borderRadius:5,fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg,textAlign:"center",fontWeight:700,boxSizing:"border-box"}}/>
Avatar color
{ACCENTS.map(a=>(
setDraft(p=>({...p,avColor:a}))}
style={{width:24,height:24,borderRadius:5,background:a,cursor:"pointer",
border:`2.5px solid ${a===draft.avColor?"#fff":"transparent"}`,
outline:a===draft.avColor?`2px solid ${a}`:"none",transition:"transform 0.1s"}}
onMouseEnter={e=>e.currentTarget.style.transform="scale(1.15)"}
onMouseLeave={e=>e.currentTarget.style.transform="scale(1)"}/>
))}
saveEdit(acc.id)}
style={{background:C.accent,color:"#fff",border:"none",borderRadius:5,padding:"6px 16px",fontSize:13,cursor:"pointer",fontFamily:FONT,fontWeight:600}}>
Save
Cancel
}
;
})}
>;
}
function SettingsSecurity({ls,setLS,C,profile}){
const pinEnabled=!!(ls.pin);
const[flow,setFlow]=useState(null); // null | "setup" | "disable"
const[newPin,setNewPin]=useState("");
const[confirmPin,setConfirmPin]=useState("");
const[currentPin,setCurrentPin]=useState("");
const[pinErr,setPinErr]=useState("");
const[showDeleteDialog,setShowDeleteDialog]=useState(false);
const[deleteStep,setDeleteStep]=useState(1); // 1=warn, 2=confirm type name, 3=done
const[deleteInput,setDeleteInput]=useState("");
const CONFIRM_WORD="DELETE";
function handleToggle(v){
if(v){setFlow("setup");setNewPin("");setConfirmPin("");setPinErr("");}
else{setFlow("disable");setCurrentPin("");setPinErr("");}
}
function submitSetup(){
if(!/^\d{4,6}$/.test(newPin)){setPinErr("PIN must be 4–6 digits.");return;}
if(newPin!==confirmPin){setPinErr("PINs don't match.");return;}
setLS(p=>({...p,pin:newPin}));
setFlow(null);setNewPin("");setConfirmPin("");setPinErr("");
}
function submitDisable(){
if(currentPin!==ls.pin){setPinErr("Incorrect PIN.");return;}
setLS(p=>({...p,pin:null}));
setFlow(null);setCurrentPin("");setPinErr("");
}
function cancelFlow(){setFlow(null);setNewPin("");setConfirmPin("");setCurrentPin("");setPinErr("");}
const pinInputSt={padding:"8px 12px",border:`1px solid ${C.inBorder}`,borderRadius:5,
fontSize:18,fontFamily:"monospace",outline:"none",color:C.text,background:C.inputBg,
width:150,boxSizing:"border-box",letterSpacing:6,textAlign:"center"};
return <>
{flow==="setup"&&
Set up PIN
New PIN (4–6 digits)
{setNewPin(e.target.value.replace(/\D/g,""));setPinErr("");}}
style={pinInputSt} placeholder="••••"/>
Confirm PIN
{setConfirmPin(e.target.value.replace(/\D/g,""));setPinErr("");}}
style={pinInputSt} placeholder="••••"/>
{pinErr&&
{pinErr}
}
Set PIN
Cancel
}
{flow==="disable"&&
Enter current PIN to disable
{setCurrentPin(e.target.value.replace(/\D/g,""));setPinErr("");}}
style={pinInputSt} placeholder="••••"/>
{pinErr&&
{pinErr}
}
Disable PIN
Cancel
}
Never 5 minutes 15 minutes 1 hour
{/* ── Danger Zone ── */}
{/* Header */}
Danger Zone
{/* Delete profile row */}
Delete this profile
Permanently removes this profile and all locally cached email data.
The SMB share and its files are never modified.
{setShowDeleteDialog(true);setDeleteStep(1);setDeleteInput("");}}
style={{background:"transparent",border:"1.5px solid #c50f1f",borderRadius:6,padding:"7px 16px",
fontSize:13,cursor:"pointer",fontFamily:FONT,color:"#c50f1f",fontWeight:600,
display:"flex",alignItems:"center",gap:6,flexShrink:0,marginLeft:16,whiteSpace:"nowrap"}}
onMouseEnter={e=>{e.currentTarget.style.background="#fef2f2";}}
onMouseLeave={e=>{e.currentTarget.style.background="transparent";}}>
Delete Profile
{/* ── Delete confirmation dialog ── */}
{showDeleteDialog&&
{if(e.target===e.currentTarget){setShowDeleteDialog(false);setDeleteStep(1);setDeleteInput("");}}}>
{deleteStep===1&&<>
You are about to permanently delete the profile "{profile?.name||"this profile"}" and all its locally cached email data, settings, and search index.
Your emails on the SMB share are completely untouched — no files will be deleted, moved, or modified.
Local DashMail database, cached attachments, and profile settings will be permanently deleted .
This action cannot be undone . Create a backup first if you want to restore later.
{setShowDeleteDialog(false);}}
style={{background:"transparent",border:`1px solid ${C.border}`,borderRadius:6,padding:"7px 18px",fontSize:13.5,cursor:"pointer",fontFamily:FONT,color:C.muted}}>Cancel
setDeleteStep(2)}
style={{background:"#c50f1f",color:"#fff",border:"none",borderRadius:6,padding:"7px 20px",fontSize:13.5,cursor:"pointer",fontFamily:FONT,fontWeight:600,display:"flex",alignItems:"center",gap:7}}>
I understand, continue
>}
{deleteStep===2&&<>
Confirm deletion
Type {CONFIRM_WORD} to permanently delete this profile.
setDeleteInput(e.target.value.toUpperCase())}
placeholder={CONFIRM_WORD}
style={{width:"100%",boxSizing:"border-box",padding:"9px 12px",border:`1.5px solid ${deleteInput===CONFIRM_WORD?"#c50f1f":C.inBorder}`,
borderRadius:6,fontSize:14,fontFamily:"monospace",outline:"none",color:"#c50f1f",
background:deleteInput===CONFIRM_WORD?"#fef2f2":C.inputBg,letterSpacing:2,marginBottom:18,
textAlign:"center",fontWeight:700}}/>
setDeleteStep(1)}
style={{background:"transparent",border:`1px solid ${C.border}`,borderRadius:6,padding:"7px 18px",fontSize:13.5,cursor:"pointer",fontFamily:FONT,color:C.muted}}>Back
setDeleteStep(3)} disabled={deleteInput!==CONFIRM_WORD}
style={{background:deleteInput===CONFIRM_WORD?"#c50f1f":"rgba(197,15,31,0.3)",color:"#fff",border:"none",borderRadius:6,
padding:"7px 20px",fontSize:13.5,cursor:deleteInput===CONFIRM_WORD?"pointer":"default",
fontFamily:FONT,fontWeight:600,display:"flex",alignItems:"center",gap:7,transition:"background 0.15s"}}>
Delete Profile
>}
{deleteStep===3&&<>
Profile deleted
The profile and all local data have been removed.
Your emails on the SMB share remain untouched.
You will be signed out now.
{setShowDeleteDialog(false);setDeleteStep(1);}}
style={{background:C.accent,color:"#fff",border:"none",borderRadius:6,padding:"8px 28px",fontSize:13.5,cursor:"pointer",fontFamily:FONT,fontWeight:600}}>
Sign out
>}
}
>;
}
function SettingsPrivacy({ls,setLS,C}){
return
setLS(p=>({...p,readReceipts:v}))} C={C}/>
;
}
function SettingsSMB({smbConfig,setSmbConfig,C}){
const[status,setStatus]=useState(null); // null | "testing" | "ok" | "err"
const[errMsg,setErrMsg]=useState("");
function set(k,v){setSmbConfig(p=>({...p,[k]:v}));}
function testConn(){
setStatus("testing");setErrMsg("");
setTimeout(()=>{
if(smbConfig.host.trim()&&smbConfig.share.trim()){setStatus("ok");}
else{setStatus("err");setErrMsg("Host and share name are required.");}
},1400);
}
const inputSt={width:"100%",boxSizing:"border-box",padding:"7px 10px",border:`1px solid ${C.inBorder}`,
borderRadius:5,fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg};
return <>
Configure the SMB network share where your mailbox folders are stored.
DashMail reads mail from \\HOST\SHARE\SUBFOLDER.
Server host
set("host",e.target.value)} placeholder="192.168.1.100 or mailserver.local" style={inputSt}/>
Share name
set("share",e.target.value)} placeholder="mail" style={inputSt}/>
Drive letter
set("driveLetter",e.target.value)}
style={{width:"100%",padding:"7px 8px",border:`1px solid ${C.inBorder}`,borderRadius:5,
fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg,boxSizing:"border-box"}}>
{"DEFGHIJKLMNOPQRSTUVWXYZ".split("").map(l=>{l}: )}
Subfolder (within the share)
set("subfolder",e.target.value)} placeholder=".Mails" style={inputSt}/>
Username
set("username",e.target.value)} placeholder="domain\\user or user@domain" style={inputSt} autoComplete="off"/>
Password
set("password",e.target.value)} style={inputSt} autoComplete="new-password"/>
{status==="testing"
?<> Testing…>
:<> Test Connection>}
{status==="ok"&&
Connected
}
{status==="err"&&
{errMsg||"Connection failed"}
}
>;
}
// ── Spam & Trash settings ─────────────────────────────────────────────────────
function SettingsSpamTrash({C}){
const[trashAfter,setTrashAfter]=useState("30");
const[spamAfter,setSpamAfter]=useState("30");
const[confirmDelete,setConfirmDelete]=useState(true);
const[emptyingTrash,setEmptyingTrash]=useState(false);
const[emptyDone,setEmptyDone]=useState(false);
function emptyNow(){
setEmptyingTrash(true);setEmptyDone(false);
setTimeout(()=>{setEmptyingTrash(false);setEmptyDone(true);setTimeout(()=>setEmptyDone(false),3000);},1600);
}
const TRASH_OPTS=[
{v:"never", label:"Never (manual only)"},
{v:"1", label:"After 1 day"},
{v:"7", label:"After 7 days"},
{v:"14", label:"After 14 days"},
{v:"30", label:"After 30 days"},
{v:"60", label:"After 60 days"},
{v:"90", label:"After 90 days"},
];
const SPAM_OPTS=[
{v:"never", label:"Never (manual only)"},
{v:"7", label:"After 7 days"},
{v:"14", label:"After 14 days"},
{v:"30", label:"After 30 days"},
{v:"60", label:"After 60 days"},
];
const selSt={background:C.inputBg,border:`1px solid ${C.inBorder}`,borderRadius:5,
padding:"5px 9px",color:C.text,fontFamily:FONT,fontSize:13,outline:"none"};
return <>
setTrashAfter(e.target.value)} style={selSt}>
{TRASH_OPTS.map(o=>{o.label} )}
{/* Empty trash now */}
{emptyingTrash
?<> Emptying…>
:<> Empty Trash Now>}
{emptyDone&&
Trash emptied
}
setSpamAfter(e.target.value)} style={selSt}>
{SPAM_OPTS.map(o=>{o.label} )}
{trashAfter!=="never"&&
Trash items older than {TRASH_OPTS.find(o=>o.v===trashAfter)?.label.replace("After ","")} will be permanently deleted from your local database. The SMB share is never modified.
}
>;
}
// ── Backup settings ───────────────────────────────────────────────────────────
const MOCK_BACKUP_HISTORY=[
{id:"bk-001",type:"full", date:"2026-05-24 22:00",emails:1842,size:"48.3 MB",status:"ok",incrementals:[
{id:"bk-002",type:"inc",date:"2026-05-25 22:00",emails:12,size:"1.1 MB",status:"ok"},
{id:"bk-003",type:"inc",date:"2026-05-26 22:00",emails:8, size:"0.7 MB",status:"ok"},
{id:"bk-004",type:"inc",date:"2026-05-27 22:00",emails:19,size:"2.4 MB",status:"ok"},
]},
{id:"bk-005",type:"full", date:"2026-04-24 22:00",emails:1803,size:"46.1 MB",status:"ok",incrementals:[]},
];
function SettingsBackup({C}){
const[paths,setPaths]=useState(["C:\\Users\\User\\DashMailBackups"]);
const[newPath,setNewPath]=useState("");
const[schedule,setSchedule]=useState("daily");
const[scheduleDay,setScheduleDay]=useState("1"); // 0=Sun..6=Sat
const[scheduleTime,setScheduleTime]=useState("22:00");
const[bkType,setBkType]=useState("smart");
const[retention,setRetention]=useState("5");
const[history,setHistory]=useState(MOCK_BACKUP_HISTORY);
const[expanded,setExpanded]=useState({"bk-001":true});
const[running,setRunning]=useState(false);
const[runType,setRunType]=useState(null); // "full"|"inc"
const[runMsg,setRunMsg]=useState("");
const[showDropdown,setShowDropdown]=useState(false);
const[showRestore,setShowRestore]=useState(false);
const[restoreFile,setRestoreFile]=useState("");
const[restoreConfirm,setRestoreConfirm]=useState(false);
const[restoreRunning,setRestoreRunning]=useState(false);
const[restoreDone,setRestoreDone]=useState(false);
const dropRef=useRef();
const folderInputRef=useRef();
useEffect(()=>{
function handleClick(e){if(dropRef.current&&!dropRef.current.contains(e.target))setShowDropdown(false);}
document.addEventListener("mousedown",handleClick);
return()=>document.removeEventListener("mousedown",handleClick);
},[]);
async function browseForFolder(){
// Use File System Access API if available (Edge / Chrome on Windows)
if(window.showDirectoryPicker){
try{
const dir=await window.showDirectoryPicker({mode:"read"});
// API gives folder name only (full path blocked by browser security)
// Prefix with a common root so user can correct it in the text field
setNewPath(dir.name);
}catch(e){/* user cancelled */}
} else {
// Fallback: hidden file input with webkitdirectory
folderInputRef.current?.click();
}
}
function addPath(){
const p=newPath.trim();
if(p&&!paths.includes(p)){setPaths(prev=>[...prev,p]);}
setNewPath("");
}
function removePath(i){setPaths(prev=>prev.filter((_,idx)=>idx!==i));}
function startBackup(type){
setRunning(true);setRunType(type);setRunMsg("");setShowDropdown(false);
const label=type==="full"?"Full backup":"Incremental backup";
setTimeout(()=>{
const now=new Date().toISOString().replace("T"," ").slice(0,16);
const newEntry=type==="full"
?{id:"bk-new-f",type:"full",date:now,emails:1860,size:"49.1 MB",status:"ok",incrementals:[]}
:{id:"bk-new-i",type:"inc",date:now,emails:7,size:"0.6 MB",status:"ok"};
if(type==="full"){
setHistory(prev=>[newEntry,...prev]);
} else {
setHistory(prev=>prev.map((b,i)=>i===0?{...b,incrementals:[newEntry,...(b.incrementals||[])]}:b));
}
setRunning(false);setRunMsg(`${label} completed successfully.`);
setTimeout(()=>setRunMsg(""),6000);
},2400);
}
function doRestore(){
setRestoreRunning(true);
setTimeout(()=>{setRestoreRunning(false);setRestoreDone(true);},2800);
}
const inputSt={width:"100%",boxSizing:"border-box",padding:"7px 10px",border:`1px solid ${C.inBorder}`,
borderRadius:5,fontSize:13,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg};
const selSt={...inputSt,width:"auto"};
return <>
{/* ── Backup locations ─────────────────────────────────────── */}
{paths.map((p,i)=>(
{p}
removePath(i)} title="Remove location"
style={{background:"none",border:"none",cursor:"pointer",padding:"2px 4px",color:C.subtle,lineHeight:1,fontSize:13,fontFamily:FONT,borderRadius:4}}
onMouseEnter={e=>{e.currentTarget.style.color=C.danger;e.currentTarget.style.background=C.hover;}}
onMouseLeave={e=>{e.currentTarget.style.color=C.subtle;e.currentTarget.style.background="none";}}>
))}
{/* Hidden folder picker fallback for browsers without showDirectoryPicker */}
{
const files=e.target.files;
if(files&&files.length>0){
const folderName=files[0].webkitRelativePath.split("/")[0];
setNewPath(folderName);
}
e.target.value="";
}}/>
setNewPath(e.target.value)}
onKeyDown={e=>{if(e.key==="Enter")addPath();}}
placeholder="Add backup path, e.g. D:\Backups\DashMail"
style={{...inputSt,flex:1}}/>
Browse
+ Add
{/* ── Schedule ─────────────────────────────────────────────── */}
setSchedule(e.target.value)} style={{...selSt}}>
Manual only
Daily
Weekly
{schedule==="weekly"&&
setScheduleDay(e.target.value)} style={selSt}>
{["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"].map((d,i)=>(
{d}
))}
}
{schedule!=="manual"&&
setScheduleTime(e.target.value)}
style={{...selSt,width:110}}/>
}
{schedule!=="manual"&&
Active
}
{/* ── Backup type ──────────────────────────────────────────── */}
{[
{id:"smart", label:"Smart (recommended)", sub:"Incremental daily, full every 30 days or 10 incrementals"},
{id:"full", label:"Always full", sub:"Every backup is a complete copy — safer, uses more storage"},
{id:"inc", label:"Always incremental", sub:"Only new/changed emails backed up each time"},
].map(opt=>(
setBkType(opt.id)}
style={{display:"flex",alignItems:"flex-start",gap:10,padding:"10px 0",
borderBottom:opt.id!=="inc"?`1px solid ${C.border}`:"none",cursor:"pointer"}}>
))}
{/* ── Retention ────────────────────────────────────────────── */}
setRetention(e.target.value)} style={selSt}>
{["3","5","10","20","50"].map(n=>{n} sets )}
{/* ── Back Up Now ──────────────────────────────────────────── */}
startBackup(bkType==="full"?"full":"inc")} disabled={running}
style={{background:C.accent,color:"#fff",border:"none",borderRadius:"6px 0 0 6px",
padding:"8px 18px",fontSize:13.5,cursor:running?"default":"pointer",fontFamily:FONT,
fontWeight:600,display:"flex",alignItems:"center",gap:7,opacity:running?0.75:1}}>
{running
?<> Backing up…>
:<> Back Up Now>}
setShowDropdown(v=>!v)}
style={{background:C.accentHov||C.accent,color:"#fff",border:"none",borderLeft:"1px solid rgba(255,255,255,0.25)",
borderRadius:"0 6px 6px 0",padding:"8px 10px",cursor:running?"default":"pointer",fontSize:13,
opacity:running?0.75:1}}>
{showDropdown&&
{[{t:"full",label:"Full backup",icon:"ti-database"},
{t:"inc", label:"Incremental backup",icon:"ti-database-plus"}].map(o=>(
startBackup(o.t)}
style={{display:"flex",alignItems:"center",gap:8,padding:"9px 14px",
cursor:"pointer",fontSize:13,color:C.text}}
onMouseEnter={e=>e.currentTarget.style.background=C.hover}
onMouseLeave={e=>e.currentTarget.style.background="transparent"}>
{o.label}
))}
}
{runMsg&&
{runMsg}
}
{/* ── Backup history ───────────────────────────────────────── */}
{history.length===0
?No backups yet.
:
{history.map(b=>{
const open=!!expanded[b.id];
return
{/* Full row */}
setExpanded(p=>({...p,[b.id]:!p[b.id]}))}
style={{display:"flex",alignItems:"center",gap:10,padding:"9px 12px",cursor:"pointer"}}
onMouseEnter={e=>e.currentTarget.style.background=C.hover}
onMouseLeave={e=>e.currentTarget.style.background="transparent"}>
FULL
{b.date}
{b.incrementals?.length>0&&+{b.incrementals.length} incremental{b.incrementals.length!==1?"s":""} }
{b.emails.toLocaleString()} emails · {b.size}
{b.status==="ok"?"OK":"Error"}
{e.stopPropagation();setRestoreFile(b.date+" (Full)");setShowRestore(true);setRestoreConfirm(false);setRestoreDone(false);}}
style={{background:"none",border:`1px solid ${C.inBorder}`,borderRadius:5,padding:"4px 10px",
fontSize:12,cursor:"pointer",fontFamily:FONT,color:C.muted,flexShrink:0}}
onMouseEnter={e=>e.currentTarget.style.color=C.text}
onMouseLeave={e=>e.currentTarget.style.color=C.muted}>
Restore
{/* Incrementals */}
{open&&b.incrementals?.length>0&&
{b.incrementals.map(inc=>(
e.currentTarget.style.background=C.hover}
onMouseLeave={e=>e.currentTarget.style.background="transparent"}>
INC
{inc.date}
{inc.emails} new/changed · {inc.size}
OK
{setRestoreFile(inc.date+" (Incremental)");setShowRestore(true);setRestoreConfirm(false);setRestoreDone(false);}}
style={{background:"none",border:`1px solid ${C.inBorder}`,borderRadius:5,padding:"4px 10px",
fontSize:12,cursor:"pointer",fontFamily:FONT,color:C.muted,flexShrink:0}}
onMouseEnter={e=>e.currentTarget.style.color=C.text}
onMouseLeave={e=>e.currentTarget.style.color=C.muted}>
Restore
))}
}
;
})}
}
{/* ── Restore dialog ───────────────────────────────────────── */}
{showRestore&&{if(e.target===e.currentTarget){setShowRestore(false);setRestoreRunning(false);setRestoreDone(false);}}}>
{restoreDone
?<>
Restore complete
DashMail has been restored to the selected backup point. Please restart the application to apply all changes.
{setShowRestore(false);setRestoreDone(false);}}
style={{background:C.accent,color:"#fff",border:"none",borderRadius:6,padding:"8px 28px",
fontSize:13.5,cursor:"pointer",fontFamily:FONT,fontWeight:600}}>Close
>
:!restoreConfirm
?<>
Restore from backup
{restoreFile}
A safety backup will be created first. Your current data will be saved before anything is overwritten. The SMB share is never modified.
setShowRestore(false)}
style={{background:"transparent",border:`1px solid ${C.border}`,borderRadius:6,padding:"7px 18px",
fontSize:13.5,cursor:"pointer",fontFamily:FONT,color:C.muted}}>Cancel
setRestoreConfirm(true)}
style={{background:C.accent,color:"#fff",border:"none",borderRadius:6,padding:"7px 20px",
fontSize:13.5,cursor:"pointer",fontFamily:FONT,fontWeight:600}}>Next →
>
:<>
Confirm restore
This will replace your local DashMail database with data from:
{restoreFile}
Your current local database will be overwritten. A safety backup will be saved to your first backup location before restore begins. The SMB share will not be touched.
setRestoreConfirm(false)}
style={{background:"transparent",border:`1px solid ${C.border}`,borderRadius:6,padding:"7px 18px",
fontSize:13.5,cursor:"pointer",fontFamily:FONT,color:C.muted}}>Back
{restoreRunning
?<> Restoring…>
:<> Restore now>}
>}
}
>;
}
// ── Contacts ──────────────────────────────────────────────────────────────────
function RecipientInput({value,onChange,contacts,fieldSt,C}){
const toChips=v=>v?v.split(",").map(s=>s.trim()).filter(Boolean):[];
const[chips,setChips]=useState(()=>toChips(value));
const[inputVal,setInputVal]=useState("");
const[open,setOpen]=useState(false);
const[candidates,setCandidates]=useState([]);
const inputRef=useRef();
const internal=useRef(false);
useEffect(()=>{
if(internal.current){internal.current=false;return;}
setChips(toChips(value));
},[value]);
function syncUp(newChips){
internal.current=true;
setChips(newChips);
onChange(newChips.join(", "));
}
function addChip(str){
const s=str.trim();
if(!s)return;
syncUp([...chips,s]);
setInputVal("");setOpen(false);
}
function removeChip(i){syncUp(chips.filter((_,idx)=>idx!==i));}
function handleKey(e){
if((e.key===","||e.key==="Enter"||e.key==="Tab")&&inputVal.trim()){
e.preventDefault();addChip(inputVal);
}
if(e.key==="Backspace"&&!inputVal&&chips.length>0) syncUp(chips.slice(0,-1));
}
function filter(v){
if(!v.trim()){setOpen(false);return;}
const m=contacts.filter(c=>
c.name.toLowerCase().includes(v.toLowerCase())||
c.email.toLowerCase().includes(v.toLowerCase())
).slice(0,6);
setCandidates(m);setOpen(m.length>0);
}
function pick(c){addChip(c.name+" <"+c.email+">");inputRef.current?.focus();}
return inputRef.current?.focus()}>
{chips.map((chip,i)=>(
{chip}
{e.preventDefault();removeChip(i);}}
style={{background:"transparent",border:"none",padding:"0 2px",cursor:"pointer",
color:C.muted,fontSize:11,lineHeight:1,display:"flex",alignItems:"center"}}>
))}
{setInputVal(e.target.value);filter(e.target.value);}}
onKeyDown={handleKey}
onBlur={()=>{setTimeout(()=>setOpen(false),150);if(inputVal.trim())addChip(inputVal);}}
style={{...fieldSt,flex:"1 1 80px",minWidth:60,padding:0}}/>
{open&&
{candidates.map(c=>(
{e.preventDefault();pick(c);}}
style={{display:"flex",alignItems:"center",gap:9,padding:"8px 12px",cursor:"pointer"}}
onMouseEnter={e=>e.currentTarget.style.background=C.hover}
onMouseLeave={e=>e.currentTarget.style.background="transparent"}>
))}
}
;
}
function ContactPickerModal({contacts,onClose,onAdd,C}){
const[search,setSearch]=useState("");
const[sel,setSel]=useState(new Set());
const filtered=search?contacts.filter(c=>
c.name.toLowerCase().includes(search.toLowerCase())||
c.email.toLowerCase().includes(search.toLowerCase())
):contacts;
function toggle(id){setSel(s=>{const n=new Set(s);n.has(id)?n.delete(id):n.add(id);return n;});}
return
Add from Contacts
setSearch(e.target.value)} placeholder="Search contacts…"
style={{width:"100%",boxSizing:"border-box",padding:"7px 10px",border:`1px solid ${C.inBorder}`,
borderRadius:6,fontSize:13,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg}}/>
{filtered.map(c=>{
const on=sel.has(c.id);
return
toggle(c.id)}
style={{display:"flex",alignItems:"center",gap:10,padding:"10px 14px",cursor:"pointer",
background:on?C.accentBg:"transparent"}}
onMouseEnter={e=>{if(!on)e.currentTarget.style.background=C.hover;}}
onMouseLeave={e=>{if(!on)e.currentTarget.style.background="transparent";}}>
{on&& }
;
})}
{filtered.length===0&&
No contacts found
}
Cancel
{onAdd(contacts.filter(c=>sel.has(c.id)));onClose();}}
disabled={sel.size===0}
style={{background:sel.size>0?C.accent:"#aaa",color:"#fff",border:"none",borderRadius:6,padding:"7px 18px",fontSize:13,cursor:sel.size>0?"pointer":"default",fontFamily:FONT,fontWeight:600}}>
Add{sel.size>0?` (${sel.size})`:""} to To
;
}
function ContactForm({contact,onSave,onCancel,C}){
const[name,setName]=useState(contact?.name??"");
const[email,setEmail]=useState(contact?.email??"");
const[phone,setPhone]=useState(contact?.phone??"");
const[address,setAddress]=useState(contact?.address??"");
const[av,setAv]=useState(contact?.av??"");
const[avColor,setAvColor]=useState(contact?.avColor??ACCENTS[0]);
const[emailErr,setEmailErr]=useState("");
function autoInitials(n){
const parts=n.trim().split(/\s+/);
return parts.length>=2?(parts[0][0]+parts[parts.length-1][0]).toUpperCase():n.slice(0,2).toUpperCase();
}
function handleName(v){
setName(v);
if(!av||av===autoInitials(name))setAv(autoInitials(v));
}
function handleSave(){
if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())){setEmailErr("Enter a valid email address.");return;}
onSave({name:name.trim(),email:email.trim(),phone:phone.trim(),address:address.trim(),av:av.slice(0,2)||"?",avColor});
}
const inputSt={width:"100%",boxSizing:"border-box",padding:"7px 10px",border:`1px solid ${C.inBorder}`,
borderRadius:5,fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg};
return
Full name
handleName(e.target.value)} placeholder="Sarah Chen" style={inputSt}/>
Initials
setAv(e.target.value.toUpperCase().slice(0,2))} maxLength={2}
style={{width:56,padding:"7px 10px",border:`1px solid ${C.inBorder}`,borderRadius:5,
fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg,
textAlign:"center",fontWeight:700,boxSizing:"border-box"}}/>
Email address
{setEmail(e.target.value);setEmailErr("");}} placeholder="sarah@example.com" type="email"
style={{...inputSt,border:`1px solid ${emailErr?"#c50f1f":C.inBorder}`}}/>
{emailErr&&
{emailErr}
}
Address (optional)
Avatar color
{ACCENTS.map(a=>(
setAvColor(a)}
style={{width:24,height:24,borderRadius:5,background:a,cursor:"pointer",
border:`2.5px solid ${a===avColor?"#fff":"transparent"}`,
outline:a===avColor?`2px solid ${a}`:"none",transition:"transform 0.1s"}}
onMouseEnter={e=>e.currentTarget.style.transform="scale(1.15)"}
onMouseLeave={e=>e.currentTarget.style.transform="scale(1)"}/>
))}
{(name||email)&&
{name||"Name"}
{email||"email@example.com"}
{phone&&
{phone}
}
}
{contact?"Save changes":"Add contact"}
Cancel
;
}
function parseCSVLine(line){
const result=[];let cur="";let inQ=false;
for(let i=0;i
l.trim());
if(lines.length<2)return[];
const headers=parseCSVLine(lines[0]).map(h=>h.toLowerCase());
const col=(...names)=>headers.findIndex(h=>names.some(n=>h.includes(n)));
const nameCol=col("name","full name","contact name");
const emailCol=col("email","e-mail","mail");
const phoneCol=col("phone","tel","mobile","cell","number");
const addressCol=col("address","street","addr","location","city");
return lines.slice(1).map(line=>{
const cols=parseCSVLine(line);
const name=(nameCol>=0?cols[nameCol]:"").trim();
const email=(emailCol>=0?cols[emailCol]:"").trim();
if(!name&&!email)return null;
const parts=(name||"").trim().split(/\s+/);
const av=parts.length>=2?(parts[0][0]+parts[parts.length-1][0]).toUpperCase():(name||email).slice(0,2).toUpperCase();
return{name:name||email,email,phone:(phoneCol>=0?cols[phoneCol]:"").trim(),address:(addressCol>=0?cols[addressCol]:"").trim(),av,avColor:ACCENTS[Math.floor(Math.random()*ACCENTS.length)]};
}).filter(Boolean);
}
function ContactsManager({contacts,setContacts,onClose,onCompose,C}){
const[search,setSearch]=useState("");
const[editId,setEditId]=useState(null);
const[confirmDelete,setConfirmDelete]=useState(null);
const[importToast,setImportToast]=useState(null);
const csvRef=useRef();
const filtered=search
?contacts.filter(c=>c.name.toLowerCase().includes(search.toLowerCase())||c.email.toLowerCase().includes(search.toLowerCase())||(c.phone||"").includes(search))
:contacts;
function addContact(data){setContacts(p=>[...p,{id:"ct"+uid(),...data}]);setEditId(null);}
function saveContact(id,data){setContacts(p=>p.map(c=>c.id===id?{...c,...data}:c));setEditId(null);}
function deleteContact(id){setContacts(p=>p.filter(c=>c.id!==id));setConfirmDelete(null);if(editId===id)setEditId(null);}
function handleCSV(e){
const file=e.target.files[0];
if(!file)return;
const reader=new FileReader();
reader.onload=ev=>{
const parsed=importCSV(ev.target.result);
if(parsed.length===0){setImportToast({type:"err",msg:"No valid contacts found in CSV."});setTimeout(()=>setImportToast(null),3500);return;}
setContacts(p=>{
const existing=new Set(p.map(c=>c.email.toLowerCase()));
const added=parsed.filter(c=>!existing.has(c.email.toLowerCase())).map(c=>({...c,id:"ct"+uid()}));
const skipped=parsed.length-added.length;
setImportToast({type:"ok",msg:`Imported ${added.length} contact${added.length!==1?"s":""}${skipped>0?`, ${skipped} skipped (duplicate)`:"."}`});
setTimeout(()=>setImportToast(null),4000);
return[...p,...added];
});
};
reader.readAsText(file);
e.target.value="";
}
return
{/* Header */}
Contacts
{contacts.length}
{/* Search + action bar */}
setSearch(e.target.value)} placeholder="Search contacts…"
style={{width:"100%",boxSizing:"border-box",padding:"7px 9px 7px 28px",
border:`1px solid ${C.inBorder}`,borderRadius:6,fontSize:13,
fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg}}/>
csvRef.current.click()}
style={{background:"transparent",border:`1px solid ${C.border}`,borderRadius:6,padding:"7px 12px",
fontSize:13,cursor:"pointer",fontFamily:FONT,color:C.muted,
display:"flex",alignItems:"center",gap:6,flexShrink:0,whiteSpace:"nowrap"}}
title="Import contacts from a CSV file">
Import CSV
setEditId("__new__")}
style={{background:C.accent,color:"#fff",border:"none",borderRadius:6,padding:"7px 14px",
fontSize:13,cursor:"pointer",fontFamily:FONT,fontWeight:600,
display:"flex",alignItems:"center",gap:6,flexShrink:0,whiteSpace:"nowrap"}}>
New Contact
{/* Import toast */}
{importToast&&
{importToast.msg}
setImportToast(null)} style={{marginLeft:"auto",background:"transparent",border:"none",cursor:"pointer",color:"#94a3b8",padding:0,lineHeight:1}}>
}
{/* New contact form */}
{editId==="__new__"&&
setEditId(null)}/>
}
{/* List */}
{filtered.length===0&&
{search?"No contacts match your search":"No contacts yet"}
}
{filtered.map(c=>{
const isEditing=editId===c.id;
return
{if(!isEditing)e.currentTarget.style.background=C.hover;}}
onMouseLeave={e=>{if(!isEditing)e.currentTarget.style.background="transparent";}}>
{c.name}
{c.email}
{(c.phone||c.address)&&
{c.phone&&
{c.phone}
}
{c.address&&
{c.address}
}
}
onCompose&&onCompose(c)}/>
setEditId(isEditing?null:c.id)}/>
setConfirmDelete(c.id)}/>
{isEditing&&
saveContact(c.id,data)}
onCancel={()=>setEditId(null)}/>
}
;
})}
{/* Footer */}
CSV columns: name, email, phone, address
Done
{/* Confirm delete */}
{confirmDelete&&
Delete contact?
"{contacts.find(c=>c.id===confirmDelete)?.name}" will be permanently removed.
setConfirmDelete(null)}
style={{background:"transparent",border:`1px solid ${C.border}`,borderRadius:6,
padding:"7px 14px",fontSize:13,cursor:"pointer",fontFamily:FONT,color:C.muted}}>
Cancel
deleteContact(confirmDelete)}
style={{background:"#c50f1f",color:"#fff",border:"none",borderRadius:6,
padding:"7px 18px",fontSize:13,cursor:"pointer",fontFamily:FONT,fontWeight:600}}>
Delete
}
;
}
// ── Compose Modal ─────────────────────────────────────────────────────────────
function ComposeModal({mode,initial,fromEmail,onClose,onSend,C,contacts=[]}){
const TITLES={new:"New Message",reply:"Reply",replyAll:"Reply All",forward:"Forward",forwardAsAtt:"Forward as Attachment"};
const[to,setTo]=useState(initial?.to??"");
const[cc,setCc]=useState(initial?.cc??"");
const[bcc,setBcc]=useState(initial?.bcc??"");
const[subj,setSubj]=useState(initial?.subject??"");
const[showCc,setShowCc]=useState(!!(initial?.cc));
const[showBcc,setShowBcc]=useState(false);
const[minimized,setMin]=useState(false);
const[attachments,setAtts]=useState(initial?.attachments??[]);
const[dragging,setDrag]=useState(false);
const[showFmt,setShowFmt]=useState(false);
const[bodyEmpty,setBodyEmpty]=useState(!initial?.body);
const[emojiHint,setEmojiHint]=useState(false);
const[showContacts,setShowContacts]=useState(false);
const fileRef=useRef();
const bodyRef=useRef();
useEffect(()=>{
if(!bodyRef.current)return;
const html=initial?.body
?initial.body.replace(/&/g,'&').replace(//g,'>').replace(/\n/g,' ')
:'';
bodyRef.current.innerHTML=html;
setBodyEmpty(!html);
},[]);
function applyFormat(cmd){bodyRef.current?.focus();document.execCommand(cmd,false,null);}
function getBody(){return bodyRef.current?.innerHTML??'';}
function handleFiles(e){
const files=Array.from(e.target.files);
setAtts(prev=>{const ex=new Set(prev.map(f=>f.name));return[...prev,...files.filter(f=>!ex.has(f.name))];});
e.target.value="";
}
function handleDrop(e){
e.preventDefault();setDrag(false);
const files=Array.from(e.dataTransfer.files);
setAtts(prev=>{const ex=new Set(prev.map(f=>f.name));return[...prev,...files.filter(f=>!ex.has(f.name))];});
}
function removeAtt(name){setAtts(prev=>prev.filter(f=>f.name!==name));}
function getFileIcon(name){
const ext=(name.split(".").pop()||"").toLowerCase();
if(["jpg","jpeg","png","gif","svg","webp"].includes(ext))return "ti-photo";
if(["pdf"].includes(ext))return "ti-file-type-pdf";
if(["doc","docx"].includes(ext))return "ti-file-type-doc";
if(["xls","xlsx"].includes(ext))return "ti-file-type-xls";
if(["zip","gz","tar","7z"].includes(ext))return "ti-file-zip";
if(["eml","msg"].includes(ext))return "ti-mail";
return "ti-file";
}
const fieldSt={flex:1,border:"none",outline:"none",fontSize:13,fontFamily:FONT,background:"transparent",color:C.text};
const rowSt={display:"flex",alignItems:"center",borderBottom:`1px solid ${C.border}`,padding:"6px 0"};
return
{/* Title bar */}
setMin(m=>!m)}>
{TITLES[mode]??"Compose"}
e.stopPropagation()}>
setMin(m=>!m)} style={{background:"transparent",border:"none",color:"rgba(255,255,255,0.85)",cursor:"pointer",padding:"2px 6px",fontSize:16}}>
{!minimized&&<>
{/* Header fields */}
From
{[["To",to,setTo],...(showCc?[["Cc",cc,setCc]]:[]),...(showBcc?[["Bcc",bcc,setBcc]]:[])].map(([lbl,val,set])=>(
{lbl}
))}
Subject
setSubj(e.target.value)}
style={{...fieldSt,fontWeight:600}}/>
{!showCc &&setShowCc(true)} style={{fontSize:11,color:C.muted,background:"transparent",border:`1px solid ${C.border}`,borderRadius:3,padding:"1px 6px",cursor:"pointer",fontFamily:FONT}}>Cc }
{!showBcc&&setShowBcc(true)} style={{fontSize:11,color:C.muted,background:"transparent",border:`1px solid ${C.border}`,borderRadius:3,padding:"1px 6px",cursor:"pointer",fontFamily:FONT}}>Bcc }
{/* Attachment pills */}
{attachments.length>0&&
{attachments.map(f=>(
{f.name}
{formatSize(f.size)}
removeAtt(f.name)} style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:0,lineHeight:1}}>
))}
}
{/* Body + drop zone */}
{e.preventDefault();setDrag(true);}}
onDragLeave={()=>setDrag(false)}
onDrop={handleDrop}>
{bodyEmpty&&
Write your message here…
}
{const t=e.currentTarget;setBodyEmpty(!t.textContent.trim());}}
style={{width:"100%",height:"100%",minHeight:180,border:"none",outline:"none",
padding:"12px 14px",fontSize:13.5,fontFamily:FONT,
color:C.text,background:"transparent",boxSizing:"border-box",
overflowY:"auto",lineHeight:1.65}}/>
{dragging&&
Drop files to attach
}
{showFmt&&
{[{lbl:"B",cmd:"bold",st:{fontWeight:800}},{lbl:"I",cmd:"italic",st:{fontStyle:"italic"}},{lbl:"U",cmd:"underline",st:{textDecoration:"underline"}},{lbl:"S",cmd:"strikeThrough",st:{textDecoration:"line-through"}}].map(({lbl,cmd,st})=>(
{e.preventDefault();applyFormat(cmd);}}
style={{fontFamily:FONT,fontSize:13,width:28,height:28,border:`1px solid ${C.border}`,borderRadius:4,cursor:"pointer",background:C.surface,color:C.text,display:"flex",alignItems:"center",justifyContent:"center",...st}}>
{lbl}
))}
{e.preventDefault();applyFormat('justifyLeft');}} title="Align left" style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:"0 4px",fontSize:15}}>
{e.preventDefault();applyFormat('justifyCenter');}} title="Align center" style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:"0 4px",fontSize:15}}>
{e.preventDefault();applyFormat('justifyRight');}} title="Align right" style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:"0 4px",fontSize:15}}>
{e.preventDefault();applyFormat('insertUnorderedList');}} title="Bullet list" style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:"0 4px",fontSize:15}}>
{e.preventDefault();applyFormat('insertOrderedList');}} title="Numbered list" style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:"0 4px",fontSize:15}}>
}
{/* Footer */}
{onSend({to,cc,bcc,subject:subj,body:getBody(),attachments});onClose();}}
style={{background:C.accent,color:"#fff",border:"none",borderRadius:5,
padding:"7px 18px",fontSize:13.5,cursor:"pointer",fontFamily:FONT,fontWeight:600,
display:"flex",alignItems:"center",gap:6}}>
Send
Discard
{/* Hidden file input */}
fileRef.current.click()}
title="Attach files" style={{display:"flex",alignItems:"center",gap:5,
background:"transparent",border:`1px solid ${C.border}`,borderRadius:5,
padding:"6px 10px",cursor:"pointer",fontFamily:FONT,color:C.muted,fontSize:13}}>
Attach
setShowContacts(true)}/>
setShowFmt(f=>!f)}/>
{bodyRef.current?.focus();setEmojiHint(true);setTimeout(()=>setEmojiHint(false),2500);}}/>
{emojiHint&&
Press
⊞ Win +
. to open emoji picker
}
>}
{showContacts&&setShowContacts(false)}
onAdd={picked=>{
const emails=picked.map(c=>c.name+" <"+c.email+">").join(", ");
setTo(prev=>prev?(prev+", "+emails):emails);
}}/>}
;
}
// ── Rules Panel ───────────────────────────────────────────────────────────────
const RULE_FIELDS=[{v:"from",l:"From"},{v:"to",l:"To"},{v:"subject",l:"Subject"},{v:"body",l:"Body"},{v:"hasAtt",l:"Has attachment"}];
const RULE_OPS=[{v:"contains",l:"contains"},{v:"notContains",l:"does not contain"},{v:"equals",l:"equals"},{v:"startsWith",l:"starts with"},{v:"endsWith",l:"ends with"}];
// ── Logo component ────────────────────────────────────────────────────────────
function DashMailLogo({size=56}){
const id=React.useId().replace(/:/g,"");
return(
);
}
// ── Mountain login background ─────────────────────────────────────────────────
function MountainScene(){
return
{/* Distant hazy range */}
{/* Left mountain */}
{/* Right mountain */}
{/* Central main peak – lit face */}
{/* Central main peak – shadow face */}
{/* Summit cap */}
{/* Snow streaks */}
{/* Rock bands */}
{/* Mist */}
{/* Foreground snow slope */}
;
}
// ── Account Dropdown (login page) ─────────────────────────────────────────────
function AccountDropdown({profiles,selectedId,onSelect}){
const[open,setOpen]=useState(false);
const selected=profiles.find(p=>p.id===selectedId);
return
setOpen(v=>!v)}
style={{display:"flex",alignItems:"center",gap:10,width:"100%",padding:"11px 14px",
border:`1.5px solid ${open?"#22d3ee":"rgba(0,0,0,0.12)"}`,borderRadius:8,
background:"#fff",cursor:"pointer",fontFamily:FONT,textAlign:"left",
boxSizing:"border-box",transition:"all 0.15s"}}
onMouseEnter={e=>{if(!open)e.currentTarget.style.borderColor="#22d3ee";}}
onMouseLeave={e=>{if(!open)e.currentTarget.style.borderColor="rgba(0,0,0,0.12)";}}>
{selected?<>
{selected.initials}
{selected.name}
{selected.email??"Default account"}
>:Select account… }
{open&&<>
setOpen(false)}/>
{profiles.map(p=>{const isSel=p.id===selectedId;return
{onSelect(p.id);setOpen(false);}}
style={{display:"flex",alignItems:"center",gap:10,padding:"9px 12px",borderRadius:7,cursor:"pointer",background:isSel?"rgba(34,211,238,0.08)":"transparent",transition:"background 0.1s"}}
onMouseEnter={e=>e.currentTarget.style.background=isSel?"rgba(34,211,238,0.1)":"rgba(0,0,0,0.04)"}
onMouseLeave={e=>e.currentTarget.style.background=isSel?"rgba(34,211,238,0.08)":"transparent"}>
{p.initials}
{p.name}
{p.email??"Default account"}
{isSel&&
}
;})}
>}
;
}
// ── Background Customizer ─────────────────────────────────────────────────────
const BG_PRESETS=["#0d1728","#1e1b4b","#14532d","#7f1d1d","#0c4a6e","#1e293b","#0f3d3d","#2e1065","#f1f5f9","#292524"];
function BgCustomizer({bgColor,setBgColor,bgImage,setBgImage,onClose}){
const[imgInput,setImgInput]=useState(bgImage);
return
Background
SOLID COLOR
{BG_PRESETS.map(c=>
{setBgColor(c);setBgImage("");}}
style={{width:"100%",aspectRatio:"1",borderRadius:6,background:c,cursor:"pointer",border:`2.5px solid ${c===bgColor&&!bgImage?"#22d3ee":"transparent"}`,transition:"transform 0.1s"}}
onMouseEnter={e=>e.currentTarget.style.transform="scale(1.1)"}
onMouseLeave={e=>e.currentTarget.style.transform="scale(1)"}/>)}
IMAGE URL
setImgInput(e.target.value)} placeholder="https://…"
style={{flex:1,padding:"7px 9px",border:"1.5px solid rgba(0,0,0,0.12)",borderRadius:7,fontSize:12.5,fontFamily:FONT,outline:"none",color:"#0d1728",minWidth:0}}
onFocus={e=>e.target.style.borderColor="#22d3ee"} onBlur={e=>e.target.style.borderColor="rgba(0,0,0,0.12)"}/>
setBgImage(imgInput)} style={{background:"#22d3ee",color:"#0d1728",border:"none",borderRadius:7,padding:"0 12px",fontSize:13,fontWeight:700,cursor:"pointer",fontFamily:FONT,flexShrink:0}}>Set
;
}
// ── Login Screen ──────────────────────────────────────────────────────────────
function LoginScreen({profiles,onLogin,loginBg,loginDark=true,onSetupNew}){
const[password,setPassword]=useState("");
const[showPw,setShowPw]=useState(false);
const lp=loginDark
?{bg:"#0c0c0c",text:"#ffffff",sub:"rgba(255,255,255,0.38)",label:"rgba(255,255,255,0.38)",
inBg:"rgba(255,255,255,0.06)",inBorder:"rgba(255,255,255,0.1)",inBorderFocus:"rgba(34,211,238,0.55)",
inFocusShadow:"rgba(34,211,238,0.12)",inText:"#f0f0f0",eyeColor:"rgba(255,255,255,0.35)",
btnEmpty:"rgba(255,255,255,0.07)",btnEmptyText:"rgba(255,255,255,0.22)",
btnFill:"#22d3ee",btnFillHov:"#06b6d4",btnFillText:"#0d1728",
footer:"rgba(255,255,255,0.1)",footerAccent:"rgba(0,196,216,0.2)"}
:{bg:"#f0f0f0",text:"#1c1c1c",sub:"rgba(0,0,0,0.42)",label:"rgba(0,0,0,0.45)",
inBg:"#ffffff",inBorder:"rgba(0,0,0,0.14)",inBorderFocus:"rgba(0,99,177,0.5)",
inFocusShadow:"rgba(0,99,177,0.1)",inText:"#1c1c1c",eyeColor:"rgba(0,0,0,0.35)",
btnEmpty:"rgba(0,0,0,0.07)",btnEmptyText:"rgba(0,0,0,0.25)",
btnFill:"#0078d4",btnFillHov:"#0063b1",btnFillText:"#ffffff",
footer:"rgba(0,0,0,0.15)",footerAccent:"rgba(0,153,184,0.35)"};
const[error,setError]=useState("");
const[shake,setShake]=useState(false);
const pwRef=useRef();
function doLogin(){
const pw=password.trim();
if(!pw){pwRef.current?.focus();return;}
const matched=profiles.find(p=>p.password===pw||pw==="demo");
if(matched){onLogin(matched);}
else{setError("Incorrect password.");setPassword("");setShake(true);setTimeout(()=>setShake(false),500);setTimeout(()=>pwRef.current?.focus(),50);}
}
return <>
{/* ── Left panel ─── */}
{/* Logo + Wordmark */}
{/* Form */}
Sign in
Enter your password to access your mailbox
{/* Password */}
Password
{setPassword(e.target.value);setError("");}}
onKeyDown={e=>e.key==="Enter"&&doLogin()}
placeholder="Enter your password"
style={{width:"100%",boxSizing:"border-box",padding:"11px 44px 11px 14px",
border:`1.5px solid ${error?"rgba(248,113,113,0.5)":lp.inBorder}`,
borderRadius:9,fontSize:14,fontFamily:FONT,outline:"none",
color:lp.inText,background:lp.inBg,transition:"border-color 0.15s,box-shadow 0.15s"}}
onFocus={e=>{e.target.style.borderColor=lp.inBorderFocus;e.target.style.boxShadow=`0 0 0 3px ${lp.inFocusShadow}`;}}
onBlur={e=>{e.target.style.borderColor=error?"rgba(248,113,113,0.5)":lp.inBorder;e.target.style.boxShadow="none";}}/>
setShowPw(v=>!v)}
style={{position:"absolute",right:12,top:"50%",transform:"translateY(-50%)",
background:"transparent",border:"none",cursor:"pointer",
color:lp.eyeColor,padding:4,lineHeight:1}}>
{/* Error */}
{error&&
{error}
}
{/* Login button */}
{if(password.trim())e.currentTarget.style.background=lp.btnFillHov;}}
onMouseLeave={e=>{e.currentTarget.style.background=!password.trim()?lp.btnEmpty:lp.btnFill;}}>
Sign In
{/* Add new account */}
{onSetupNew&&
{e.currentTarget.style.borderColor=lp.inBorderFocus;e.currentTarget.style.color=lp.text;}}
onMouseLeave={e=>{e.currentTarget.style.borderColor=lp.inBorder;e.currentTarget.style.color=lp.sub;}}>
Set up new account
}
{/* Footer */}
DashMail • The Secure Faxmail Client
{/* ── Right panel: mountain photo ─── */}
>;
}
const ACTION_TYPES=[{v:"move",l:"Move to folder"},{v:"markRead",l:"Mark as read"},{v:"star",l:"Star / flag"},{v:"delete",l:"Move to trash"},{v:"forward",l:"Forward to"}];
function RulesInline({C,rules,setRules,allFolders}){
const[editId,setEditId]=useState(null);
const[draft,setDraft]=useState(null);
function startNew(){
const r={id:uid(),name:"New rule",enabled:true,condLogic:"all",
conditions:[{id:uid(),field:"from",op:"contains",value:""}],
actions:[{id:uid(),type:"move",folder:"inbox",forwardTo:""}]};
setDraft(r);setEditId("__new__");
}
function startEdit(rule){setDraft({...rule,conditions:rule.conditions.map(c=>({...c})),actions:rule.actions.map(a=>({...a}))});setEditId(rule.id);}
function saveRule(){
if(!draft)return;
if(editId==="__new__") setRules(prev=>[...prev,draft]);
else setRules(prev=>prev.map(r=>r.id===editId?draft:r));
setEditId(null);setDraft(null);
}
function deleteRule(id){setRules(prev=>prev.filter(r=>r.id!==id));}
function toggleRule(id){setRules(prev=>prev.map(r=>r.id===id?{...r,enabled:!r.enabled}:r));}
function addCond(){setDraft(d=>({...d,conditions:[...d.conditions,{id:uid(),field:"from",op:"contains",value:""}]}));}
function removeCond(cid){setDraft(d=>({...d,conditions:d.conditions.filter(c=>c.id!==cid)}));}
function setCond(cid,key,val){setDraft(d=>({...d,conditions:d.conditions.map(c=>c.id===cid?{...c,[key]:val}:c)}));}
function addAction(){setDraft(d=>({...d,actions:[...d.actions,{id:uid(),type:"move",folder:"inbox",forwardTo:""}]}));}
function removeAction(aid){setDraft(d=>({...d,actions:d.actions.filter(a=>a.id!==aid)}));}
function setAction(aid,key,val){setDraft(d=>({...d,actions:d.actions.map(a=>a.id===aid?{...a,[key]:val}:a)}));}
const selSt={background:C.inputBg,border:`1px solid ${C.inBorder}`,borderRadius:5,
padding:"4px 7px",color:C.text,fontFamily:FONT,fontSize:12.5,outline:"none"};
function condSummary(rule){
const parts=rule.conditions.map(c=>`${RULE_FIELDS.find(f=>f.v===c.field)?.l??"?"} ${RULE_OPS.find(o=>o.v===c.op)?.l??""} "${c.value}"`);
return parts.slice(0,2).join(` ${rule.condLogic==="all"?"AND":"OR"} `)+(parts.length>2?"…":"");
}
function actionSummary(rule){
return rule.actions.map(a=>ACTION_TYPES.find(t=>t.v===a.type)?.l??a.type).join(", ");
}
return <>
New Rule
{rules.map(rule=>(
toggleRule(rule.id)} C={C}/>
{rule.name}
When: {condSummary(rule)}
Then: {actionSummary(rule)}
startEdit(rule)}/>
deleteRule(rule.id)}/>
{editId===rule.id&&draft&&
{setEditId(null);setDraft(null);}}/>
}
))}
{editId==="__new__"&&draft&&
New Rule
{setEditId(null);setDraft(null);}}/>
}
Rules run on new mail arrival, applied top to bottom via the Electron chokidar watcher.
>;
}
function RulesPanel({C,rules,setRules,allFolders,onClose}){
return
;
}
function RuleEditor({draft,setDraft,allFolders,selSt,C,addCond,removeCond,setCond,addAction,removeAction,setAction,onSave,onCancel}){
return <>
Apply when
setDraft(d=>({...d,condLogic:e.target.value}))} style={selSt}>
ALL ANY
of these are true:
{draft.conditions.map(c=>(
setCond(c.id,"field",e.target.value)} style={{...selSt,flex:1}}>
{RULE_FIELDS.map(f=>{f.l} )}
{c.field!=="hasAtt"&&<>
setCond(c.id,"op",e.target.value)} style={{...selSt,flex:1}}>
{RULE_OPS.map(o=>{o.l} )}
setCond(c.id,"value",e.target.value)}
placeholder="value…" style={{...selSt,flex:2,padding:"4px 7px"}}/>
>}
removeCond(c.id)} style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:"3px",flexShrink:0}}>
))}
Add condition
Do the following:
{draft.actions.map(a=>(
setAction(a.id,"type",e.target.value)} style={{...selSt,flex:2}}>
{ACTION_TYPES.map(t=>{t.l} )}
{a.type==="move"&&setAction(a.id,"folder",e.target.value)} style={{...selSt,flex:2}}>
{allFolders.map(f=>{f} )}
}
{a.type==="forward"&& setAction(a.id,"forwardTo",e.target.value)}
placeholder="email@example.com" style={{...selSt,flex:2}}/>}
removeAction(a.id)} style={{background:"transparent",border:"none",cursor:"pointer",color:C.muted,padding:"3px",flexShrink:0}}>
))}
Add action
Save rule
Cancel
>;
}
// ── Onboarding Wizard ─────────────────────────────────────────────────────────
function OnboardingWizard({accountId,onComplete,C}){
const[step,setStep]=useState(0);
const[displayName,setDisplayName]=useState("");
const[fromName,setFromName]=useState("");
const[avatarColor,setAvatarColor]=useState(C.accent);
const[signature,setSig]=useState("");
const detectedEmail=accountId==="default"?"Default Account":accountId;
const initials=detectedEmail.slice(0,2).toUpperCase();
const totalSteps=4;
const steps=[
{
icon:"ti-mail-plus",
title:"New mailbox detected",
subtitle:detectedEmail,
content:<>
A new email account was found on your mail server at
.Mails/{accountId}/
Let's take a moment to set it up so it's ready to use.
>,
nextLabel:"Get Started",
},
{
icon:"ti-user-circle",
title:"Set up your identity",
subtitle:"How will this account appear?",
content:<>
Your name (shown to recipients)
setFromName(e.target.value)} placeholder="e.g. Alex Johnson"
style={{width:"100%",boxSizing:"border-box",padding:"8px 11px",border:`1px solid ${C.inBorder}`,
borderRadius:5,fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg}}/>
Display name in app
setDisplayName(e.target.value)} placeholder="e.g. Alex"
style={{width:"100%",boxSizing:"border-box",padding:"8px 11px",border:`1px solid ${C.inBorder}`,
borderRadius:5,fontSize:13.5,fontFamily:FONT,outline:"none",color:C.text,background:C.inputBg}}/>
Avatar color
{ACCENTS.map(a=>(
setAvatarColor(a)} style={{width:28,height:28,borderRadius:6,background:a,
border:`2.5px solid ${a===avatarColor?"#fff":"transparent"}`,outline:a===avatarColor?`2px solid ${a}`:"none",
cursor:"pointer",transition:"transform 0.1s"}}
onMouseEnter={e=>e.currentTarget.style.transform="scale(1.15)"}
onMouseLeave={e=>e.currentTarget.style.transform="scale(1)"}/>
))}
{(fromName||displayName)&&
{displayName||fromName}
{detectedEmail}
}
>,
nextLabel:"Continue",
},
{
icon:"ti-signature",
title:"Email signature",
subtitle:"Optional — appended to outgoing messages",
content:<>