`); popup.document.close(); }; // ─── Listing Actions ─────────────────────────────────────── const saveListing=async()=>{ if(!user||savingListing)return; if(!ensureMerchantAccess())return; const name=String(form.name||"").trim(); if(!name){toast("Item name is required.","err");return;} const price=Number(form.price||0); if(!Number.isFinite(price)||price<=0){toast("Enter a valid price.","err");return;} const stock=Number(form.stock||0); if(!Number.isFinite(stock)||stock<0){toast("Stock must be zero or more.","err");return;} if(listingImageFile){ if(listingImageFile.size>5*1024*1024){toast("Listing image must be under 5MB.","err");return;} if(!/^image\//.test(listingImageFile.type)){toast("Listing image must be a valid image file.","err");return;} } setSavingListing(true); try{ let img=form.img||""; if(listingImageFile){ const ref=storage.ref(`merchant_listing_images/${user.uid}_${Date.now()}_${listingImageFile.name}`); await ref.put(listingImageFile); img=await ref.getDownloadURL(); } const payload={name,cat:ensureValidCategory(form.cat),img,price,unit:form.unit||"pc",stock,basePrice:price,autoPriceEnabled:false,merchantId:user.uid,merchantLogo:merchant?.logoUrl||"",storeName:merchant?.storeName||"",active:true,approved:true,menuTime:form.menuTime||"all",description:(form.description||"").trim(),updatedAt:Date.now()}; if(editingId){ await db.ref("merchant_listings/"+user.uid+"/"+editingId).update(payload); toast("Listing updated.","ok"); }else{ await db.ref("merchant_listings/"+user.uid).push({...payload,createdAt:Date.now(),soldOut:false}); toast("Listing added to your menu.","ok"); } setForm({name:"",cat:DEFAULT_LISTING_CATEGORY,price:"",unit:"pc",stock:"",img:"",menuTime:"all",description:""}); setListingImageFile(null);setEditingId(null); }catch(e){toast("Save failed: "+(e?.message||"Unknown"),"err");} finally{setSavingListing(false);} }; const editListing=(l)=>{ setEditingId(l.id);setListingImageFile(null); setForm({name:l.name||"",cat:ensureValidCategory(l.cat||DEFAULT_LISTING_CATEGORY),price:String(l.price??""),unit:l.unit||"pc",stock:String(l.stock??""),img:l.img||"",menuTime:l.menuTime||"all",description:l.description||""}); setActiveSection("menu"); }; const deleteListing=async(id)=>{ if(!ensureMerchantAccess())return; if(!window.confirm("Delete this listing?"))return; try{ await db.ref("merchant_listings/"+user.uid+"/"+id).remove(); if(editingId===id){setEditingId(null);setForm({name:"",cat:DEFAULT_LISTING_CATEGORY,price:"",unit:"pc",stock:"",img:"",menuTime:"all",description:""});setListingImageFile(null);} toast("Listing removed.","ok"); }catch(e){toast("Could not delete listing: "+(e?.message||"Unknown"),"err");} }; const addCustomCategory=async()=>{ if(!user) return; if(!ensureMerchantAccess())return; const rawName=newCustomCategory.trim(); if(!rawName){toast("Enter a category name.","err");return;} const id=rawName.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,""); if(!id){toast("Invalid category name.","err");return;} if(CATEGORIES.some(c=>c.id===id)||merchantCustomCategories.some(c=>c.toLowerCase()===rawName.toLowerCase())){ toast("Category already exists.","warn"); return; } try{ const next=[...merchantCustomCategories,rawName]; await db.ref("partner_merchants/"+user.uid).update({customCategories:next,updatedAt:Date.now()}); setNewCustomCategory(""); toast("Custom category added.","ok"); }catch(e){toast("Could not add category: "+(e?.message||"Unknown"),"err");} }; const removeCustomCategory=async(name)=>{ if(!user||!name)return; if(!ensureMerchantAccess())return; const targetId=name.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,""); const inUse=listings.some(l=>String(l.cat||"")===targetId); if(inUse){ toast("This category is used by active listings. Reassign those listings first.","warn"); return; } try{ const next=merchantCustomCategories.filter(c=>c.toLowerCase()!==name.toLowerCase()); await db.ref("partner_merchants/"+user.uid).update({customCategories:next.length?next:null,updatedAt:Date.now()}); toast("Category removed.","ok"); }catch(e){toast("Could not remove category: "+(e?.message||"Unknown"),"err");} }; const toggleSoldOut=async(id,current)=>{ if(!ensureMerchantAccess())return; try{ await db.ref("merchant_listings/"+user.uid+"/"+id).update({soldOut:!current,updatedAt:Date.now()}); }catch(e){toast("Could not update stock: "+(e?.message||"Unknown"),"err");} }; // ─── Banner Actions ──────────────────────────────────────── const saveBanner=async()=>{ if(!user||savingBanner)return; if(!ensureMerchantAccess())return; if(!bannerForm.title.trim()){toast("Enter a banner title.","err");return;} if(bannerImageFile){ if(bannerImageFile.size>5*1024*1024){toast("Banner image must be under 5MB.","err");return;} if(!/^image\//.test(bannerImageFile.type)){toast("Banner must be an image file.","err");return;} } setSavingBanner(true); try{ let imageUrl=bannerForm.imageUrl.trim(); if(bannerImageFile){ const ref=storage.ref(`merchant_banners/${user.uid}_${Date.now()}_${bannerImageFile.name}`); await ref.put(bannerImageFile); imageUrl=await ref.getDownloadURL(); } if(!imageUrl){toast("Upload a banner image.","err");setSavingBanner(false);return;} const id="bnr_"+Date.now(); await db.ref(`partner_merchants/${user.uid}/promoBanners/${id}`).set({id,title:bannerForm.title.trim(),imageUrl,linkUrl:bannerForm.linkUrl.trim(),active:bannerForm.active!==false,createdAt:Date.now(),updatedAt:Date.now()}); setBannerForm({title:"",imageUrl:"",linkUrl:"",active:true});setBannerImageFile(null); toast("Banner saved.","ok"); }catch(e){toast("Could not save banner: "+(e?.message||"Unknown"),"err");} finally{setSavingBanner(false);} }; const toggleBanner=async(id,next)=>{ if(!ensureMerchantAccess())return; try{await db.ref(`partner_merchants/${user.uid}/promoBanners/${id}`).update({active:next,updatedAt:Date.now()});} catch(e){toast("Could not update banner: "+(e?.message||"Unknown"),"err");} }; const removeBanner=async(id)=>{ if(!ensureMerchantAccess())return; if(!window.confirm("Delete this banner?"))return; try{await db.ref(`partner_merchants/${user.uid}/promoBanners/${id}`).remove();toast("Banner deleted.","ok");} catch(e){toast("Could not delete banner: "+(e?.message||"Unknown"),"err");} }; // ─── Campaign Actions ────────────────────────────────────── const saveCampaign=async()=>{ if(!user||savingCampaign)return; if(!ensureMerchantAccess())return; if(!campaignForm.title.trim()){toast("Enter a campaign title.","err");return;} const start=campaignForm.startDate?new Date(campaignForm.startDate).getTime():Date.now(); const end=campaignForm.endDate?new Date(campaignForm.endDate).getTime():null; if(end!==null && end<=start){toast("Campaign end date must be after the start date.","err");return;} const value=Number(campaignForm.value||0); if(["percent_off","flat_off"].includes(campaignForm.type) && (!Number.isFinite(value)||value<=0)){ toast("Discount value must be greater than zero.","err"); return; } if(campaignForm.type==="percent_off" && value>100){ toast("Percent discount cannot exceed 100%.","err"); return; } setSavingCampaign(true); try{ const id="camp_"+Date.now(); await db.ref(`partner_merchants/${user.uid}/campaigns/${id}`).set({ id,type:campaignForm.type,title:campaignForm.title.trim(), value,minOrder:Number(campaignForm.minOrder||0), startDate:start, endDate:end, active:true,createdAt:Date.now() }); setCampaignForm({type:"percent_off",title:"",value:"",minOrder:"",startDate:"",endDate:""}); toast("Campaign created.","ok"); }catch(e){toast("Could not create campaign: "+(e?.message||"Unknown"),"err");} finally{setSavingCampaign(false);} }; const toggleCampaign=async(id,next)=>{ if(!ensureMerchantAccess())return; try{await db.ref(`partner_merchants/${user.uid}/campaigns/${id}`).update({active:next});} catch(e){toast("Could not update campaign: "+(e?.message||"Unknown"),"err");} }; const removeCampaign=async(id)=>{ if(!ensureMerchantAccess())return; if(!window.confirm("Delete this campaign?"))return; try{await db.ref(`partner_merchants/${user.uid}/campaigns/${id}`).remove();toast("Campaign deleted.","ok");} catch(e){toast("Could not delete campaign: "+(e?.message||"Unknown"),"err");} }; const deleteStorePermanently=async()=>{ if(!user?.uid||deletingStore)return; if(!ensureMerchantAccess())return; const warningText="This will permanently remove your store from customer listings and delete your merchant data."; if(!window.confirm(`${warningText}\n\nAdmin records will be kept for compliance.\n\nContinue?`))return; const typed=window.prompt("Type DELETE to confirm store deletion:"); if((typed||"").trim().toUpperCase()!=="DELETE"){ toast("Store deletion cancelled. Confirmation text did not match.","warn"); return; } setDeletingStore(true); try{ const now=Date.now(); const merchantId=user.uid; const merchantUpdate={ storeOpen:false, busyMode:true, active:false, approved:false, featuredInApp:false, status:"deleted", enforcementStatus:"deleted", deletedAt:now, deletedByUid:merchantId, deletedByRole:"merchant", selfDeleted:true, selfDeletedAt:now, selfDeleteReason:"Requested from merchant dashboard settings", deletedStoreName:merchant?.storeName||null, deletedStoreAddress:merchant?.address||merchant?.businessAddress||null, deletedOwnerEmail:user?.email||null, logoUrl:null, description:null, promoBanners:null, campaigns:null, customCategories:null, moderation:{ action:"deleted", reason:"Requested by merchant from dashboard settings", updatedAt:now, updatedByUid:merchantId, updatedByRole:"merchant", }, }; const rootUpdates={ [`merchant_listings/${merchantId}`]:null, [`partner_merchants/${merchantId}`]:{ ...(merchant||{}), ...merchantUpdate, }, }; await db.ref().update(rootUpdates); toast("Store deleted. Your merchant account will now be signed out.","ok"); setTimeout(()=>auth.signOut(),1200); }catch(e){ toast("Could not delete store: "+(e?.message||"Unknown"),"err"); setDeletingStore(false); } }; // ─── Charts ──────────────────────────────────────────────── React.useEffect(()=>{ if(activeSection!=="analytics"||!earningsChartRef.current)return; if(earningsChartInstance.current)earningsChartInstance.current.destroy(); const data=financials.dailyData; earningsChartInstance.current=new Chart(earningsChartRef.current,{ type:"line", data:{labels:data.map(d=>d.date.slice(5)),datasets:[{label:"Revenue",data:data.map(d=>d.revenue),borderColor:"#16a34a",backgroundColor:"rgba(22,163,74,.1)",fill:true,tension:.3,pointRadius:2}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{y:{beginAtZero:true,ticks:{callback:v=>fmt(v)}},x:{ticks:{maxRotation:45,font:{size:10}}}}} }); return ()=>{if(earningsChartInstance.current)earningsChartInstance.current.destroy();}; },[activeSection,financials.dailyData]); React.useEffect(()=>{ if(activeSection!=="analytics"||!peakChartRef.current)return; if(peakChartInstance.current)peakChartInstance.current.destroy(); const labels=Array.from({length:24},(_,i)=>`${i}:00`); peakChartInstance.current=new Chart(peakChartRef.current,{ type:"bar", data:{labels,datasets:[{label:"Orders",data:financials.hourMap,backgroundColor:"rgba(37,99,235,.6)",borderRadius:4}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{y:{beginAtZero:true,ticks:{stepSize:1}},x:{ticks:{font:{size:9}}}}} }); return ()=>{if(peakChartInstance.current)peakChartInstance.current.destroy();}; },[activeSection,financials.hourMap]); // ═══════════════════════════════════════════════════════════ // TOAST HOST // ═══════════════════════════════════════════════════════════ const Toaster=()=>(
{toasts.map(t=>{ const icon=t.type==="err"?"triangle-exclamation":t.type==="ok"?"circle-check":t.type==="warn"?"circle-exclamation":"circle-info"; return (
{t.message}
); })}
); // ═══════════════════════════════════════════════════════════ // LOGIN / LOADING / APPROVAL GATES // ═══════════════════════════════════════════════════════════ if(!authReady) return ( <>

Loading…

); if(!user) return ( <>

BayanGo Merchant

Sign in to manage your store, orders, and analytics.

); if(!merchantLoaded) return ( <>

Loading your store…

); if(!isMerchantApproved(merchant)) { const hasApplication=!!merchant; return ( <>

{hasApplication?"Waiting for Approval":"No Merchant Account"}

{hasApplication ? "Your merchant account is pending admin approval. You'll get access once it's approved." : <>You don't have a merchant account yet. Submit a partner application to get started.}

); } // Hard block for revoked / deleted stores — suspended still sees a banner. if(moderationAction==="deleted"||moderationAction==="revoked") return ( <>

Access {moderationAction==="deleted"?"Removed":"Revoked"}

Your merchant access has been {moderationAction==="deleted"?"removed":"revoked"} by BayanGo Admin. {moderationReason?<>
Reason: {moderationReason}:null}

Check your email for the full compliance notice, or contact support if you think this is a mistake.

); // ═══════════════════════════════════════════════════════════ // RENDER ORDER CARD // ═══════════════════════════════════════════════════════════ const renderOrderCard=(o)=>{ const s=getMerchantOrderStatus(o); const badge=getStatusBadge(s); const items=Array.isArray(o.items)?o.items:Object.values(o.items||{}); const expanded=expandedOrder===o.id; const riderLoc=o.riderId?riderLocs[o.riderId]:null; return (
{o.customer?.name||"Customer"}
{ago(o.createdAt)}
{badge.label}
{fmt(o.total)}
{expanded && (<>
    {items.map((it,i)=>(
  • {Number(it.qty||1)}x {it.name||"Item"} — {fmt(Number(it.price||0)*Number(it.qty||1))}{it.unit?` (${it.unit})`:""}
  • ))}
{o.specialInstructions &&
{o.specialInstructions}
} {o.notes && !o.specialInstructions &&
{o.notes}
} {o.customer?.phone &&
{o.customer.phone}
} {o.deliveryAddress &&
{typeof o.deliveryAddress==='string'?o.deliveryAddress:o.deliveryAddress?.label||"Delivery address"}
} {o.paymentMethod &&
{o.paymentMethod}
} )} {o.riderId && (s==='delivery'||s==='ready') && (
{o.riderName||"Rider assigned"} {riderLoc && ETA ~{Math.max(1,Math.round((riderLoc.eta||5)))} min} {!riderLoc && Tracking...}
)} {s!=='delivered'&&s!=='cancelled'&&s!=='delivery' && (
{s==='new' && } {(s==='new'||s==='accepted') && } {(s==='preparing'||s==='accepted') && } {s==='new' && }
)}
); }; // ═══════════════════════════════════════════════════════════ // MAIN RENDER // ═══════════════════════════════════════════════════════════ return (
{/* HEADER */}

{merchant.storeName||"My Store"}

{storeOpen?(busyMode?"Busy Mode — Receiving paused":"Open — Receiving orders"):"Store Closed"}
{merchant.logoUrl && logo}
{isAccessRestricted && (
Store enforcement: {moderationLabel}. {moderationReason||"May violation na na-detect sa account mo."} Check your email for the full notice from BayanGo Admin.
)}
{[ {id:"dashboard",label:"Dashboard",icon:"gauge-high"}, {id:"orders",label:"Orders",icon:"receipt",badge:orderGroups.new.length||null}, {id:"menu",label:"Menu",icon:"utensils"}, {id:"store_profile",label:"Store Profile",icon:"store"}, {id:"financials",label:"Financials",icon:"peso-sign"}, {id:"analytics",label:"Analytics",icon:"chart-line"}, {id:"marketing",label:"Marketing",icon:"bullhorn"}, {id:"settings",label:"Settings",icon:"gear"}, ].map(t=>( ))}
{/* ═══ DASHBOARD ═══ */} {activeSection==="dashboard" && (<>
{financials.today.count}
Today's Orders
{fmt(financials.today.net)}
Today's Revenue
{fmt(financials.avgOrderValue)}
Avg Order Value
{activeOrderCount}
Active Orders

Quick Controls

Store Status
{storeOpen?"Customers can see and order from your store.":"Your store is hidden from customers."}
Busy Mode
{busyMode?"New orders paused. Toggle off when ready.":"Enable to temporarily stop receiving new orders."}
Prep Time
Estimated minutes to prepare orders
{prepTime} min

New Orders

{orderGroups.new.length}
{orderGroups.new.length===0 &&

No new orders right now.

} {orderGroups.new.slice(0,5).map(renderOrderCard)} {orderGroups.new.length>5 && }

Store Branding

Store Logo
uploadLogo(e.target.files?.[0])} style={{marginBottom:8}}/> {merchant.logoUrl && Store logo}
)} {/* ═══ ORDERS ═══ */} {activeSection==="orders" && (<>
{[ {id:"active",label:"Active",count:activeOrderCount}, {id:"new",label:"New",count:orderGroups.new.length}, {id:"preparing",label:"Preparing",count:orderGroups.accepted.length+orderGroups.preparing.length}, {id:"ready",label:"Ready",count:orderGroups.ready.length}, {id:"delivery",label:"In Delivery",count:orderGroups.delivery.length}, {id:"archive",label:"Archive",count:orderGroups.delivered.length+orderGroups.cancelled.length}, ].map(t=>( ))}
{orderTab==="active" && (<> {activeOrderCount===0 &&

No active orders

} {[...orderGroups.new,...orderGroups.accepted,...orderGroups.preparing,...orderGroups.ready,...orderGroups.delivery].map(renderOrderCard)} )} {orderTab==="new" && (<> {orderGroups.new.length===0 &&

No new orders

} {orderGroups.new.map(renderOrderCard)} )} {orderTab==="preparing" && (<> {(orderGroups.accepted.length+orderGroups.preparing.length)===0 &&

Nothing being prepared

} {[...orderGroups.accepted,...orderGroups.preparing].map(renderOrderCard)} )} {orderTab==="ready" && (<> {orderGroups.ready.length===0 &&

No orders ready for pickup

} {orderGroups.ready.map(renderOrderCard)} )} {orderTab==="delivery" && (<> {orderGroups.delivery.length===0 &&

No orders in delivery

} {orderGroups.delivery.map(renderOrderCard)} )} {orderTab==="archive" && (<> {(orderGroups.delivered.length+orderGroups.cancelled.length)===0 &&

No archived orders

} {[...orderGroups.delivered,...orderGroups.cancelled].map(renderOrderCard)} )} )} {/* ═══ MENU & CATALOG ═══ */} {activeSection==="menu" && (<>

{editingId?"Update Listing":"Add Listing"}

setForm(f=>({...f,name:e.target.value}))}/>
setForm(f=>({...f,price:e.target.value}))}/> setForm(f=>({...f,unit:e.target.value}))}/>
setForm(f=>({...f,stock:e.target.value}))}/>