Files
sap-erp/dist-1/public/register.html
T
John 69b4e68baf
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s
first commit
2026-09-23 17:31:02 +05:30

965 lines
55 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/company-config.js"></script>
<script src="/company-list.js"></script>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Customer Registration — MITRA ERP Portal</title>
<meta name="description" content="Register as a new customer. Fill in your business details, addresses and upload required documents."/>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500&family=Sora:wght@400;600;700;800&display=swap" rel="stylesheet"/>
<style>
:root{
--blue:#0070f2;--dark:#0f1923;--light:#f4f6fb;--border:#d1d7e0;
--green:#00b96b;--orange:#f59e0b;--red:#ef4444;--white:#fff;
--radius:10px;--shadow:0 4px 24px rgba(0,0,0,.10);
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'IBM Plex Sans',sans-serif;background:#edf1f7;color:#1a2535;min-height:100vh}
.topbar{background:var(--dark);color:#fff;display:flex;align-items:center;justify-content:space-between;padding:0 28px;height:56px;position:sticky;top:0;z-index:100;box-shadow:0 2px 20px rgba(0,0,0,.25)}
.topbar-brand{display:flex;align-items:center;gap:12px}
.sap-logo{background:var(--blue);color:#fff;font-family:'Sora',sans-serif;font-weight:800;font-size:13px;padding:5px 11px;border-radius:6px;letter-spacing:1.5px}
.topbar-title{font-size:14px;font-weight:500;color:#94a3b8}
.topbar-right{display:flex;align-items:center;gap:10px}
.topbar-badge{background:rgba(0,112,242,.15);color:#60a5fa;border:1px solid rgba(0,112,242,.25);padding:3px 12px;border-radius:20px;font-size:11px;font-weight:700;letter-spacing:.4px}
.page{max-width:820px;margin:0 auto;padding:32px 20px 60px}
.steps{display:flex;align-items:center;margin-bottom:32px}
.step{display:flex;align-items:center;gap:9px}
.step-line{flex:1;height:2px;background:var(--border);margin:0 8px;transition:background .3s}
.step-line.done{background:var(--green)}
.step-circle{width:32px;height:32px;border-radius:50%;border:2px solid var(--border);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;color:#8fa3bc;background:#fff;flex-shrink:0;transition:all .3s}
.step.active .step-circle{background:var(--blue);border-color:var(--blue);color:#fff;box-shadow:0 0 0 4px #d0e5fc}
.step.done .step-circle{background:var(--green);border-color:var(--green);color:#fff}
.step-label{font-size:12px;font-weight:600;color:#8fa3bc;white-space:nowrap}
.step.active .step-label{color:var(--blue)}
.step.done .step-label{color:var(--green)}
.card{background:#fff;border-radius:var(--radius);box-shadow:var(--shadow);border:1px solid var(--border);padding:28px 32px;margin-bottom:20px;overflow:hidden}
.card-title{font-family:'Sora',sans-serif;font-size:17px;font-weight:700;margin-bottom:4px}
.card-sub{font-size:13px;color:#6b7a8d;margin-bottom:22px;padding-bottom:16px;border-bottom:1px solid var(--border)}
.sec{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--blue);margin:24px 0 12px;padding-bottom:6px;border-bottom:1px solid #e8eef6}
.grid{display:grid;gap:16px;width:100%}
.g2{grid-template-columns:1fr 1fr}
.g3{grid-template-columns:1fr 1fr 1fr}
.span2{grid-column:span 2}
.span3{grid-column:span 3}
.field{display:flex;flex-direction:column;gap:5px;min-width:0}
.label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:#3d4f61}
.label .req{color:var(--red);margin-left:2px}
.label .auto-tag{color:var(--green);margin-left:4px;font-size:10px;font-weight:600;background:#e6f4eb;padding:1px 5px;border-radius:3px}
input,select,textarea{
padding:10px 12px;border:1.5px solid var(--border);border-radius:var(--radius);
font-size:13.5px;font-family:inherit;background:#fff;color:#1a2535;
transition:border .2s,box-shadow .2s;outline:none;
text-transform:uppercase;width:100%
}
select,input[type=email],input[type=password],.no-upper{text-transform:none}
input:focus,select:focus,textarea:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(0,112,242,.1)}
input[readonly],select[disabled]{background:#f4f6fb;color:#8fa3bc;cursor:not-allowed}
textarea{resize:vertical;min-height:80px;text-transform:uppercase}
.ferr{font-size:11px;color:var(--red);margin-top:2px}
.btn-row{display:flex;gap:12px;margin-top:28px;justify-content:flex-end}
.btn{padding:11px 26px;border-radius:var(--radius);border:none;font-family:inherit;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s;display:inline-flex;align-items:center;gap:8px}
.btn-primary{background:var(--blue);color:#fff}
.btn-primary:hover{background:#0057c2;box-shadow:0 4px 16px rgba(0,112,242,.35);transform:translateY(-1px)}
.btn-secondary{background:#fff;color:#1a2535;border:1.5px solid var(--border)}
.btn-secondary:hover{border-color:var(--blue);color:var(--blue)}
.btn:disabled{opacity:.5;cursor:not-allowed;transform:none!important}
/* ── ADDRESS BLOCK ── */
.addr-block{padding:18px 20px;background:#f8f9fc;border-radius:10px;border:1px solid var(--border);margin-bottom:14px}
.addr-block-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px}
.addr-block-label{font-size:12px;font-weight:700;color:var(--blue);display:flex;align-items:center;gap:6px}
.addr-block-num{background:var(--blue);color:#fff;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}
.addr-remove-btn{padding:4px 12px;font-size:12px;border:1.5px solid var(--red);color:var(--red);border-radius:6px;cursor:pointer;background:none;font-weight:600;transition:all .2s}
.addr-remove-btn:hover{background:var(--red);color:#fff}
.add-addr-btn{display:inline-flex;align-items:center;gap:7px;padding:8px 16px;font-size:12px;font-weight:600;border:1.5px solid var(--blue);color:var(--blue);border-radius:var(--radius);cursor:pointer;background:#fff;transition:all .2s;margin-bottom:16px}
.add-addr-btn:hover{background:#e8f1fd}
/* ── FROZEN ADDRESS NAME (matches vendor) ── */
.addr-name-wrap{position:relative}
.addr-name-frozen{background:#f4f6fb!important;color:#8fa3bc!important;cursor:not-allowed!important;border-color:rgba(0,112,242,.25)!important;padding-right:120px!important}
.addr-name-frozen:focus{box-shadow:none!important;border-color:rgba(0,112,242,.25)!important}
.addr-name-badges{position:absolute;right:10px;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:5px;pointer-events:none}
.badge-autofill{background:#e6f4eb;color:var(--green);font-size:9.5px;font-weight:700;padding:2px 7px;border-radius:4px;letter-spacing:.4px;white-space:nowrap}
.badge-frozen{background:#e8eef6;color:#8fa3bc;font-size:9.5px;font-weight:700;padding:2px 7px;border-radius:4px;display:flex;align-items:center;gap:3px;white-space:nowrap}
.badge-frozen svg{width:10px;height:10px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.badge{display:inline-flex;align-items:center;padding:3px 11px;border-radius:20px;font-size:11px;font-weight:700}
.b-b2b{background:#e8f1fd;color:var(--blue)}
.b-b2c{background:#e6f4eb;color:var(--green)}
.alert{padding:12px 16px;border-radius:var(--radius);font-size:13px;margin-bottom:18px;display:flex;align-items:flex-start;gap:10px;line-height:1.5}
.a-info{background:#e8f1fd;border:1px solid #b3d0fa;color:#003f8a}
.a-warn{background:#fff9ed;border:1px solid #fcd98a;color:#7a4f00}
.a-ok{background:#e6f4eb;border:1px solid #b7dfc5;color:#0e5027}
.a-err{background:#fce8e8;border:1px solid #f5b8b8;color:#700000}
/* ── ATTACHMENTS ── */
.attach-zone{
border:2px dashed var(--border);border-radius:var(--radius);
padding:20px 16px;background:#f8f9fc;text-align:center;
cursor:pointer;transition:all .2s;position:relative;
min-height:110px;display:flex;flex-direction:column;
justify-content:center;align-items:center
}
.attach-zone:hover,.attach-zone.drag{border-color:var(--blue);background:#e8f1fd}
.attach-zone input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%;z-index:2}
.attach-zone .az-icon{font-size:28px;margin-bottom:8px;line-height:1}
.attach-zone .az-label{font-size:12px;font-weight:600;color:#3d4f61}
.attach-zone .az-sub{font-size:11px;color:#8fa3bc;margin-top:4px}
.attach-preview{display:flex;align-items:center;gap:10px;padding:10px 12px;background:#fff;border:1px solid var(--border);border-radius:var(--radius);margin-top:8px;word-break:break-all}
.attach-preview .ap-thumb{width:38px;height:38px;border-radius:6px;background:#e8f1fd;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.attach-preview .ap-info{flex:1;min-width:0}
.attach-preview .ap-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.attach-preview .ap-size{font-size:11px;color:#8fa3bc;margin-top:2px}
.attach-preview .ap-rm{padding:3px 9px;font-size:11px;border:1.5px solid var(--red);color:var(--red);border-radius:4px;cursor:pointer;background:none;flex-shrink:0;font-weight:600;transition:all .15s}
.attach-preview .ap-rm:hover{background:var(--red);color:#fff}
/* ── MSME ── */
.msme-toggle{padding:16px 20px;border:2px solid var(--border);border-radius:10px;margin-bottom:14px;background:#fff;width:100%;transition:all .25s}
.msme-toggle.active{border-color:var(--green);background:#f0fdf6}
/* ── REVIEW TABLE ── */
.dtable{width:100%;border-collapse:collapse;font-size:13.5px}
.dtable tr{border-bottom:1px solid #f0f2f5}
.dtable tr:last-child{border:none}
.dtable td{padding:10px 6px;vertical-align:top}
.dtable td:first-child{color:#6b7a8d;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;width:38%;padding-top:12px}
.dtable td.val{font-weight:500}
/* ── SUCCESS ── */
.success-wrap{text-align:center;padding:52px 24px}
.success-icon{font-size:60px;margin-bottom:20px}
.success-wrap h2{font-family:'Sora',sans-serif;font-size:24px;font-weight:700;color:var(--green);margin-bottom:10px}
.success-wrap p{font-size:14px;color:#6b7a8d;max-width:440px;margin:0 auto 18px;line-height:1.6}
.ref-box{display:inline-block;background:#f4f6fb;border:1px solid var(--border);border-radius:8px;padding:10px 24px;font-family:'IBM Plex Mono',monospace;font-size:15px;font-weight:600;color:#0070f2;margin:16px 0}
.sp{width:16px;height:16px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite;display:inline-block;flex-shrink:0}
@keyframes spin{to{transform:rotate(360deg)}}
.toast-c{position:fixed;top:66px;right:20px;z-index:9999;display:flex;flex-direction:column;gap:8px}
.toast{padding:12px 18px;border-radius:var(--radius);font-size:13.5px;font-weight:500;box-shadow:var(--shadow);animation:slin .3s ease;display:flex;align-items:center;gap:10px;max-width:380px;line-height:1.4}
.t-ok{background:var(--green);color:#fff}
.t-err{background:var(--red);color:#fff}
@keyframes slin{from{transform:translateX(110%);opacity:0}to{transform:translateX(0);opacity:1}}
.type-cards{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:8px}
.type-card{display:flex;align-items:center;gap:12px;padding:16px 20px;border:2px solid var(--border);border-radius:10px;cursor:pointer;transition:all .2s;background:#fff;user-select:none}
.type-card.selected{border-color:var(--blue);background:#e8f1fd}
.type-card input[type=radio]{width:18px;height:18px;accent-color:var(--blue);flex-shrink:0}
.type-card-title{font-weight:700;font-size:14px;color:#1a2535;margin-bottom:3px}
.type-card.selected .type-card-title{color:var(--blue)}
.type-card-desc{font-size:12px;color:#8fa3bc}
.progress-bar-wrap{background:#e8eef6;border-radius:4px;height:4px;margin-bottom:32px;overflow:hidden}
.progress-bar{height:100%;border-radius:4px;background:linear-gradient(90deg,var(--blue),var(--green));transition:width .4s ease}
@media(max-width:640px){
.g2,.g3{grid-template-columns:1fr}
.span2,.span3{grid-column:span 1}
.type-cards{grid-template-columns:1fr}
.card{padding:20px 16px}
.topbar{padding:0 16px}
.topbar-title{display:none}
}
</style>
<link rel="stylesheet" href="/theme.css?v=3"/>
</head>
<body>
<div id="root"></div>
<script>
'use strict';
const BACKEND = (() => {
return localStorage.getItem('BACKEND_URL') || '/api';
})();
let S = { step: 1, fd: {}, toasts: [] };
let _companies=[{value:window.__DEFAULT_COMPANY__,label:window.__DEFAULT_COMPANY__}]; // live list
const STATES_LIST = ['','Andhra Pradesh','Arunachal Pradesh','Assam','Bihar','Chandigarh','Chhattisgarh','Delhi','Goa','Gujarat','Haryana','Himachal Pradesh','Jammu & Kashmir','Jharkhand','Karnataka','Kerala','Madhya Pradesh','Maharashtra','Manipur','Meghalaya','Mizoram','Nagaland','Odisha','Punjab','Rajasthan','Sikkim','Tamil Nadu','Telangana','Tripura','Uttar Pradesh','Uttarakhand','West Bengal'];
const COUNTRIES_LIST = ['India','United States','United Kingdom','UAE','Singapore','Germany','Japan','Australia'];
const STATE_SHORT = {'Andhra Pradesh':'AP','Arunachal Pradesh':'AR','Assam':'AS','Bihar':'BH','Chandigarh':'CH','Chhattisgarh':'CG','Delhi':'DL','Goa':'GA','Gujarat':'GJ','Haryana':'HR','Himachal Pradesh':'HP','Jammu & Kashmir':'JK','Jharkhand':'JH','Karnataka':'KA','Kerala':'KL','Madhya Pradesh':'MP','Maharashtra':'MH','Manipur':'MN','Meghalaya':'ML','Mizoram':'MZ','Nagaland':'NL','Odisha':'OD','Punjab':'PB','Rajasthan':'RJ','Sikkim':'SK','Tamil Nadu':'TN','Telangana':'TG','Tripura':'TR','Uttar Pradesh':'UP','Uttarakhand':'UK','West Bengal':'WB'};
// ── Helpers ─────────────────────────────────────────────
function esc(v){return(v||'').toString().replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
function coOptions(sel){return _companies.map(c=>`<option value="${esc(c.value)}" ${(sel||window.__DEFAULT_COMPANY__)===c.value?'selected':''}>${esc(c.label)}</option>`).join('');}
function mkEl(tag,cls=''){const e=document.createElement(tag);if(cls)e.className=cls;return e;}
function toast(msg,type='ok'){
const id=Date.now();
S.toasts.push({id,msg,type});
renderToasts();
setTimeout(()=>{S.toasts=S.toasts.filter(t=>t.id!==id);renderToasts();},4500);
}
function renderToasts(){
let tc=document.querySelector('.toast-c');
if(!tc){tc=document.createElement('div');tc.className='toast-c';document.body.appendChild(tc);}
tc.innerHTML=S.toasts.map(t=>`<div class="toast t-${t.type==='ok'?'ok':'err'}">${t.type==='ok'?'✓':'✗'} ${esc(t.msg)}</div>`).join('');
}
async function api(method,path,body=null){
const h={'Content-Type':'application/json'};
const opt={method:method.toUpperCase(),headers:h};
if(body) opt.body=JSON.stringify(body);
let r;
try{ r=await fetch(BACKEND+path,opt); }
catch(e){ throw new Error('Cannot reach server. Please check your connection or contact support.'); }
let d;
try{ d=await r.json(); }catch{ throw new Error('Server error ('+r.status+')'); }
if(!r.ok){ throw new Error(d?.message||d?.errors?.[0]?.msg||'Error '+r.status); }
return d;
}
// ── Address auto-fill (FROZEN — read-only, auto-updates) ──
// Mirrors vendor form: addrName is always computed, never user-editable
function wireAddrAutoFill(container, prefix, idx, cardNameInputId){
const addrNameEl=container.querySelector(`#${prefix}-addrName-${idx}`);
const stateEl=container.querySelector(`#${prefix}-state-${idx}`);
if(!addrNameEl||!stateEl) return;
function autoFill(){
const cardNameEl=cardNameInputId?document.getElementById(cardNameInputId):null;
const bizName=((cardNameEl?cardNameEl.value:'')||S?.fd?.cardName||'').toUpperCase().trim();
const stateFull=stateEl.value||'';
const stateShort=STATE_SHORT[stateFull]||stateFull.toUpperCase().substring(0,2);
addrNameEl.value=bizName?(stateShort?`${bizName} - ${stateShort}`:bizName).substring(0,50).toUpperCase():'';
}
stateEl.addEventListener('change',autoFill);
if(cardNameInputId){
const cn=document.getElementById(cardNameInputId);
if(cn) cn.addEventListener('input',autoFill);
}
if(stateEl.value) autoFill();
}
function wireAddrBlock(container,prefix,idx,cardNameInputId){
const block=container.querySelector(`#${prefix}-block-wrap-${idx}`);
if(!block) return;
// Only uppercase non-readonly inputs
block.querySelectorAll('input:not([readonly])').forEach(inp=>makeUpperCase(inp));
wireAddrAutoFill(container,prefix,idx,cardNameInputId);
}
function makeUpperCase(el){
if(!el||el.readOnly) return;
el.addEventListener('input',function(){
const p=this.selectionStart;
this.value=this.value.toUpperCase();
try{this.setSelectionRange(p,p);}catch{}
});
if(el.value) el.value=el.value.toUpperCase();
}
// ── Address block HTML — FROZEN address name (matches vendor style) ──
function buildAddrBlockHTML(prefix,label,idx,saved,opts={}){
const{removable=false}=opts;
const savedState=saved?.state||'';
const savedCountry=saved?.country||'India';
const savedAddr=(saved?.addrName||'').toUpperCase();
const STATES_OPTS=STATES_LIST.map(v=>`<option value="${esc(v)}" ${savedState===v?'selected':''}>${v||'-- Select State --'}</option>`).join('');
const COUNTRY_OPTS=COUNTRIES_LIST.map(v=>`<option value="${esc(v)}" ${savedCountry===v?'selected':''}>${v}</option>`).join('');
return `
<div class="addr-block" id="${prefix}-block-wrap-${idx}">
<div class="addr-block-header">
<span class="addr-block-label">
<span class="addr-block-num">${idx}</span>
${label}${idx>1?' Address #'+idx:''}
</span>
${removable?`<button type="button" class="addr-remove-btn" onclick="document.getElementById('${prefix}-block-wrap-${idx}').remove()">✗ Remove</button>`:''}
</div>
<div class="grid g2" style="gap:14px">
<div class="field span2">
<label class="label">Address Name <span class="req">*</span>
<span style="font-size:9px;color:#8fa3bc;text-transform:none;font-weight:400;margin-left:4px">(auto-filled · read-only)</span>
</label>
<div class="addr-name-wrap">
<input id="${prefix}-addrName-${idx}"
class="addr-name-frozen"
value="${esc(savedAddr)}"
placeholder="AUTO-FILLED: BUSINESS NAME - STATE CODE"
readonly tabindex="-1" style="user-select:none"/>
<div class="addr-name-badges">
<span class="badge-autofill">AUTO-FILL</span>
<span class="badge-frozen">
<svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>FROZEN
</span>
</div>
</div>
</div>
<div class="field span2">
<label class="label">Street / PO Box <span class="req">*</span></label>
<input id="${prefix}-street-${idx}" value="${esc((saved?.street||'').toUpperCase())}" placeholder="HOUSE/PLOT NO, STREET NAME"/>
</div>
<div class="field span2">
<label class="label">Block / Area</label>
<input id="${prefix}-block-${idx}" value="${esc((saved?.block||'').toUpperCase())}" placeholder="BLOCK / AREA / LOCALITY"/>
</div>
<div class="field">
<label class="label">City <span class="req">*</span></label>
<input id="${prefix}-city-${idx}" value="${esc((saved?.city||'').toUpperCase())}" placeholder="CITY NAME"/>
</div>
<div class="field">
<label class="label">Pin Code</label>
<input id="${prefix}-zip-${idx}" value="${esc((saved?.zip||'').toUpperCase())}" placeholder="PIN CODE"/>
</div>
<div class="field">
<label class="label">State <span class="req">*</span></label>
<select id="${prefix}-state-${idx}" style="text-transform:none">${STATES_OPTS}</select>
</div>
<div class="field">
<label class="label">Country</label>
<select id="${prefix}-country-${idx}" style="text-transform:none">${COUNTRY_OPTS}</select>
</div>
<div class="field span2">
<label class="label">GSTIN <span style="font-size:10px;color:#6b7a8d;font-weight:400;text-transform:none">(State-wise GSTIN for this address — optional)</span></label>
<input id="${prefix}-gstin-${idx}" value="${esc((saved?.gstin||'').toUpperCase())}" placeholder="E.G. 06ABCDE1234F1Z5" maxlength="15"/>
</div>
</div>
</div>`;
}
function collectAddrBlocks(container,prefix){
const addrs=[];
container.querySelectorAll(`[id^="${prefix}-block-wrap-"]`).forEach(div=>{
const n=div.id.replace(`${prefix}-block-wrap-`,'');
if(isNaN(n)) return;
const addrName=(div.querySelector(`#${prefix}-addrName-${n}`)?.value||'').trim().toUpperCase();
const street=(div.querySelector(`#${prefix}-street-${n}`)?.value||'').trim().toUpperCase();
if(!addrName&&!street) return;
addrs.push({
addrName, street,
block:(div.querySelector(`#${prefix}-block-${n}`)?.value||'').trim().toUpperCase(),
city:(div.querySelector(`#${prefix}-city-${n}`)?.value||'').trim().toUpperCase(),
zip:(div.querySelector(`#${prefix}-zip-${n}`)?.value||'').trim().toUpperCase(),
state:div.querySelector(`#${prefix}-state-${n}`)?.value||'',
country:div.querySelector(`#${prefix}-country-${n}`)?.value||'India',
gstin:(div.querySelector(`#${prefix}-gstin-${n}`)?.value||'').trim().toUpperCase(),
});
});
return addrs;
}
// ── ATTACHMENT HELPERS (matches vendor pattern exactly) ──
function renderAttachPreviewDirect(el, arr){
if(!el) return;
el.innerHTML = arr.map((a,i) => `
<div class="attach-preview">
<div class="ap-thumb">${a.type?.startsWith('image/') ? `<img src="${a.data}" style="width:100%;height:100%;object-fit:cover;border-radius:4px">` : a.type==='application/pdf'?'':''}</div>
<div class="ap-info">
<div class="ap-name">${esc(a.name)}</div>
<div class="ap-size">${(a.size/1024).toFixed(1)} KB · ${a.type||'file'}</div>
</div>
<button class="ap-rm" onclick="removeAttach('${el.id.replace('ap-','')}',${i},this.closest('.attach-preview'))">✗ Remove</button>
</div>`).join('');
}
window.removeAttach = function(key, idx, el){
if(!S.fd.attachments) return;
const arr = Array.isArray(S.fd.attachments[key]) ? S.fd.attachments[key] : [S.fd.attachments[key]];
arr.splice(idx,1);
S.fd.attachments[key] = arr.length ? (key==='other' ? arr : arr[0]) : null;
if(el) el.remove();
};
// Mirrors vendor: handleAttach is global, uses document.querySelector directly
window.handleAttach = function(key, input){
Array.from(input.files||[]).forEach(file=>{
if(file.size > 5*1024*1024){ toast(file.name+' is too large (max 5MB)','err'); return; }
const r = new FileReader();
r.onload = e => {
const obj = {name:file.name, size:file.size, type:file.type, data:e.target.result};
if(!S.fd.attachments) S.fd.attachments = {};
const previewEl = document.querySelector('#ap-'+key);
if(key==='other'){
if(!Array.isArray(S.fd.attachments.other)) S.fd.attachments.other = [];
S.fd.attachments.other.push(obj);
renderAttachPreviewDirect(previewEl, S.fd.attachments.other);
} else {
S.fd.attachments[key] = obj;
renderAttachPreviewDirect(previewEl, [obj]);
}
};
r.readAsDataURL(file);
});
input.value = '';
};
// ── MAIN RENDER ──────────────────────────────────────────
function render(){
const root=document.getElementById('root');
root.innerHTML='';
root.appendChild(renderTopbar());
const pg=mkEl('div','page');
const pct=S.step===1?33:S.step===2?66:100;
pg.innerHTML=`<div class="progress-bar-wrap"><div class="progress-bar" style="width:${pct}%"></div></div>`;
pg.appendChild(buildSteps());
if(S.step===1) pg.appendChild(renderStep1());
else if(S.step===2) pg.appendChild(renderStep2());
else pg.appendChild(renderStep3());
root.appendChild(pg);
}
function renderTopbar(){
const tb=mkEl('div','topbar');
tb.innerHTML=`
<div class="topbar-brand">
<span class="sap-logo">SAP</span>
<span class="topbar-title">Business One — Customer Registration</span>
</div>
<div class="topbar-right">
<span class="topbar-badge">CUSTOMER FORM</span>
</div>`;
return tb;
}
function buildSteps(){
const labels=['Your Details','Review','Confirmation'];
const wrap=mkEl('div','steps');
labels.forEach((lb,i)=>{
const n=i+1;
const s=mkEl('div','step'+(n<S.step?' done':n===S.step?' active':''));
s.innerHTML=`<div class="step-circle">${n<S.step?'✓':n}</div><span class="step-label">${lb}</span>`;
wrap.appendChild(s);
if(i<labels.length-1){
const line=document.createElement('div');
line.className='step-line'+(n<S.step?' done':'');
wrap.appendChild(line);
}
});
return wrap;
}
// ── STEP 1: THE FORM ────────────────────────────────────
function renderStep1(){
const card=mkEl('div','card');
const isB2B=(S.fd.customerType||'B2B')==='B2B';
const savedBill=S.fd.allBillAddresses?.length?S.fd.allBillAddresses:[{addrName:S.fd.billAddressName||'',street:S.fd.billStreet||'',block:S.fd.billBlock||'',city:S.fd.billCity||'',zip:S.fd.billZip||'',state:S.fd.billState||'',country:S.fd.billCountry||'India',gstin:S.fd.gstin||''}];
const savedShip=S.fd.allShipAddresses?.length?S.fd.allShipAddresses:[{addrName:S.fd.shipAddressName||'',street:S.fd.shipStreet||'',block:S.fd.shipBlock||'',city:S.fd.shipCity||'',zip:S.fd.shipZip||'',state:S.fd.shipState||'',country:S.fd.shipCountry||'India'}];
card.innerHTML=`
<div class="card-title">Customer Registration Form</div>
<div class="card-sub">Fill in your business details below. All fields marked <span style="color:var(--red)">*</span> are required.</div>
<div class="sec">Company / Database</div>
<div class="grid g2" style="margin-bottom:8px">
<div class="field">
<label class="label">Submit to Company <span class="req">*</span></label>
<select id="f-company">${coOptions(S.fd.company)}</select>
</div>
</div>
<div class="sec">Customer Type</div>
<div class="type-cards">
<label class="type-card ${isB2B?'selected':''}">
<input type="radio" name="custType" value="B2B" ${isB2B?'checked':''}/>
<div><div class="type-card-title">B2B — Business</div><div class="type-card-desc">Company, Firm, LLP, Partnership</div></div>
</label>
<label class="type-card ${!isB2B?'selected':''}">
<input type="radio" name="custType" value="B2C" ${!isB2B?'checked':''}/>
<div><div class="type-card-title">B2C — Individual</div><div class="type-card-desc">Individual / Retail Customer</div></div>
</label>
</div>
<div class="sec">Business Information</div>
<div class="grid g2">
<div class="field">
<label class="label">Company / Customer Name <span class="req">*</span></label>
<input id="f-cardName" placeholder="${isB2B?'E.G. MAHAVIR TRANSPORT PVT LTD':'E.G. RAJESH KUMAR'}" value="${esc((S.fd.cardName||'').toUpperCase())}"/>
<span class="ferr" id="e-cardName"></span>
</div>
<div class="field">
<label class="label">Foreign / Alternate Name <span style="font-size:10px;color:#8fa3bc;font-weight:400;text-transform:none">(optional)</span></label>
<input id="f-foreignName" placeholder="FOREIGN / ALTERNATE NAME" value="${esc((S.fd.foreignName||'').toUpperCase())}"/>
</div>
<div class="field">
<label class="label">Type of Business</label>
<select id="f-typeOfBusiness" style="text-transform:none">
${['Company','Individual','Partnership','LLP','Proprietorship'].map(v=>`<option value="${v}" ${(S.fd.typeOfBusiness||(isB2B?'Company':'Individual'))===v?'selected':''}>${v}</option>`).join('')}
</select>
</div>
<div class="field">
<label class="label">Industry <span class="req">*</span></label>
<select id="f-industry" style="text-transform:none">
${['','Transport','Manufacturing','Trading','Retail','Distribution','FMCG','Healthcare','Construction','IT Services','Others'].map(v=>`<option value="${v}" ${(S.fd.industry||'')===v?'selected':''}>${v||'-- Select Industry --'}</option>`).join('')}
</select>
<span class="ferr" id="e-industry"></span>
</div>
</div>
<div class="sec">Primary Contact Person</div>
<div class="grid g3">
<div class="field">
<label class="label">First Name <span class="req">*</span></label>
<input id="f-contactFirst" placeholder="FIRST NAME" value="${esc((S.fd.contactFirst||'').toUpperCase())}"/>
<span class="ferr" id="e-contactFirst"></span>
</div>
<div class="field">
<label class="label">Last Name <span class="req">*</span></label>
<input id="f-contactLast" placeholder="LAST NAME" value="${esc((S.fd.contactLast||'').toUpperCase())}"/>
<span class="ferr" id="e-contactLast"></span>
</div>
<div class="field">
<label class="label">Title / Position</label>
<input id="f-contactTitle" placeholder="E.G. OWNER / MANAGER" value="${esc((S.fd.contactTitle||'').toUpperCase())}"/>
</div>
<div class="field">
<label class="label">Mobile Number <span class="req">*</span></label>
<input id="f-mobile" placeholder="+91 99999 99999" value="${esc(S.fd.mobile||'')}" style="text-transform:none" class="no-upper"/>
<span class="ferr" id="e-mobile"></span>
</div>
<div class="field">
<label class="label">Email Address <span class="req">*</span></label>
<input id="f-email" type="email" placeholder="contact@company.com" value="${esc(S.fd.email||'')}" style="text-transform:none"/>
<span class="ferr" id="e-email"></span>
</div>
<div class="field">
<label class="label">Alternate Email <span style="font-size:10px;color:#8fa3bc;font-weight:400;text-transform:none">(optional)</span></label>
<input id="f-contactEmail" type="email" placeholder="alt@company.com" value="${esc(S.fd.contactEmail||'')}" style="text-transform:none"/>
</div>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-top:24px">
<div class="sec" style="margin:0;flex:1">Billing Address(es)</div>
</div>
<div class="alert a-info" style="margin-top:12px;margin-bottom:10px">
ℹ <strong>Address Name</strong> is auto-filled as <strong>BUSINESS NAME - STATE CODE</strong> and is read-only.
</div>
<div id="bill-addr-container" style="margin-top:4px">
${savedBill.map((a,i)=>buildAddrBlockHTML('fb','Billing',i+1,a,{removable:i>0})).join('')}
</div>
<button type="button" class="add-addr-btn" id="add-bill-addr">+ Add Another Billing Address</button>
<div class="sec" style="margin-top:8px">Shipping Address(es)</div>
<div style="margin-bottom:14px">
<label style="display:flex;align-items:center;gap:10px;cursor:pointer;padding:12px 16px;background:#f8f9fc;border:1.5px solid var(--border);border-radius:8px;width:fit-content">
<input type="checkbox" id="sameAsBill" ${S.fd.sameAsBill!==false?'checked':''} style="width:18px;height:18px;padding:0;accent-color:var(--blue)"/>
<span style="font-size:13px;font-weight:600">Same as first Billing Address</span>
</label>
</div>
<div id="ship-addr-section" style="${S.fd.sameAsBill!==false?'display:none':''}">
<div id="ship-addr-container">
${savedShip.map((a,i)=>buildAddrBlockHTML('fs','Shipping',i+1,a,{removable:i>0})).join('')}
</div>
<button type="button" class="add-addr-btn" id="add-ship-addr">+ Add Another Shipping Address</button>
</div>
<div class="sec">Financial & Tax Information</div>
<div style="display:grid;grid-template-columns:160px 1fr 200px;gap:16px;width:100%">
<div class="field">
<label class="label">Currency</label>
<select id="f-currency" style="text-transform:none">
${['Indian Rupee','US Dollar','Euro','British Pound','UAE Dirham'].map(v=>`<option ${(S.fd.currency||'Indian Rupee')===v?'selected':''}>${v}</option>`).join('')}
</select>
</div>
<div class="field">
<label class="label">GSTIN ${isB2B?'<span class="req">*</span>':''} ${!isB2B?'<span style="font-size:10px;color:#8fa3bc;font-weight:400;text-transform:none">(optional for B2C)</span>':''}</label>
<div style="display:flex;gap:8px;align-items:stretch">
<input id="f-gstin" placeholder="E.G. 06ABCDE1234F1Z5" value="${esc((S.fd.gstin||'').toUpperCase())}" style="flex:1;min-width:0" maxlength="15"/>
<button type="button" id="gstin-fetch" style="padding:0 14px;height:41px;background:var(--blue);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12px;font-weight:700;white-space:nowrap;flex-shrink:0;display:inline-flex;align-items:center;gap:5px">
<span id="gstin-sp" style="display:none" class="sp"></span>Fetch PAN
</button>
</div>
<span style="font-size:11px;color:#8fa3bc;margin-top:3px">Auto-extracts PAN from GSTIN</span>
<span class="ferr" id="e-gstin"></span>
</div>
<div class="field">
<label class="label">PAN Number <span class="req">*</span></label>
<div style="position:relative">
<input id="f-pan" placeholder="E.G. ABCDE1234F" value="${esc((S.fd.pan||'').toUpperCase())}" maxlength="10" style="padding-right:58px"/>
<span id="pan-badge" style="display:${S.fd.panAutoFetched?'flex':'none'};position:absolute;right:9px;top:50%;transform:translateY(-50%);background:#e6f4eb;color:var(--green);font-size:10px;font-weight:700;padding:2px 7px;border-radius:4px;align-items:center;pointer-events:none">AUTO</span>
</div>
<span class="ferr" id="e-pan"></span>
</div>
</div>
<div class="sec">MSME / Udyam Registration</div>
<div class="msme-toggle ${S.fd.hasMsme?'active':''}" id="msme-wrap">
<label style="display:flex;align-items:center;gap:12px;cursor:pointer">
<input type="checkbox" id="f-hasMsme" ${S.fd.hasMsme?'checked':''} style="width:20px;height:20px;accent-color:var(--green)"/>
<div>
<div style="font-weight:700;font-size:14px">Registered under MSME / Udyam Scheme</div>
<div style="font-size:12px;color:#6b7a8d;margin-top:3px">Check this if your business has an MSME / Udyam registration certificate</div>
</div>
</label>
<div id="msme-fields" style="${S.fd.hasMsme?'':'display:none'}">
<div class="grid g3" style="margin-top:16px">
<div class="field span2">
<label class="label">Udyam Registration Number <span class="req">*</span></label>
<input id="f-msmeNo" placeholder="E.G. UDYAM-HR-18-0040140" value="${esc((S.fd.msmeNo||'').toUpperCase())}" maxlength="19"/>
<span class="ferr" id="e-msmeNo"></span>
</div>
<div class="field">
<label class="label">MSME Type <span class="req">*</span></label>
<select id="f-msmeType" style="text-transform:none">${['','MICRO','SMALL','MEDIUM','LARGE'].map(v=>`<option value="${v}" ${(S.fd.msmeType||'')===v?'selected':''}>${v||'-- Select --'}</option>`).join('')}</select>
</div>
<div class="field">
<label class="label">Enterprise Type</label>
<select id="f-msmeBType" style="text-transform:none">${['','MANUFACTURING','SERVICES','TRADING'].map(v=>`<option value="${v}" ${(S.fd.msmeBType||'')===v?'selected':''}>${v||'-- Select --'}</option>`).join('')}</select>
</div>
</div>
</div>
</div>
<div class="sec">Required Documents</div>
<div class="alert a-warn" style="margin-bottom:16px">
Please upload <strong>clear scanned copies</strong> or photos. Max <strong>5MB</strong> per file. Accepted formats: <strong>PDF, JPG, PNG</strong>.
</div>
<div class="grid g2" style="align-items:start">
<div class="field">
<label class="label">PAN Card <span class="req">*</span></label>
<div class="attach-zone" id="az-pan">
<input type="file" id="f-att-pan" accept=".pdf,.jpg,.jpeg,.png" onchange="handleAttach('pan',this)"/>
<div class="az-icon">🪪</div>
<div class="az-label">Click or drag to upload PAN Card</div>
<div class="az-sub">PDF, JPG, PNG — max 5MB</div>
</div>
<div id="ap-pan"></div>
<span class="ferr" id="e-att-pan"></span>
</div>
<div class="field">
<label class="label">Aadhaar Card <span class="req">*</span></label>
<div class="attach-zone" id="az-aadhaar">
<input type="file" id="f-att-aadhaar" accept=".pdf,.jpg,.jpeg,.png" onchange="handleAttach('aadhaar',this)"/>
<div class="az-icon">🪪</div>
<div class="az-label">Click or drag to upload Aadhaar Card</div>
<div class="az-sub">PDF, JPG, PNG — max 5MB</div>
</div>
<div id="ap-aadhaar"></div>
<span class="ferr" id="e-att-aadhaar"></span>
</div>
<div class="field">
<label class="label">Cancelled Cheque / Bank Proof <span class="req">*</span></label>
<div class="attach-zone" id="az-cheque">
<input type="file" id="f-att-cheque" accept=".pdf,.jpg,.jpeg,.png" onchange="handleAttach('cheque',this)"/>
<div class="az-icon"></div>
<div class="az-label">Click or drag to upload Cancelled Cheque</div>
<div class="az-sub">PDF, JPG, PNG — max 5MB</div>
</div>
<div id="ap-cheque"></div>
<span class="ferr" id="e-att-cheque"></span>
</div>
<div class="field" id="az-msme-wrap" style="${S.fd.hasMsme?'':'display:none'}">
<label class="label">MSME / Udyam Certificate <span class="req">*</span></label>
<div class="attach-zone" id="az-msme">
<input type="file" id="f-att-msme" accept=".pdf,.jpg,.jpeg,.png" onchange="handleAttach('msme',this)"/>
<div class="az-icon"></div>
<div class="az-label">Click or drag MSME Certificate</div>
<div class="az-sub">PDF, JPG, PNG — max 5MB</div>
</div>
<div id="ap-msme"></div>
<span class="ferr" id="e-att-msme"></span>
</div>
<div class="field">
<label class="label">Other Documents <span style="font-size:10px;color:#8fa3bc;font-weight:400;text-transform:none">(optional)</span></label>
<div class="attach-zone" id="az-other">
<input type="file" id="f-att-other" accept=".pdf,.jpg,.jpeg,.png" multiple onchange="handleAttach('other',this)"/>
<div class="az-icon"></div>
<div class="az-label">Click or drag Other Documents</div>
<div class="az-sub">Multiple files allowed — PDF, JPG, PNG</div>
</div>
<div id="ap-other"></div>
</div>
</div>
<div class="sec">Additional Remarks</div>
<div class="field">
<label class="label">Remarks <span class="req">*</span></label>
<textarea id="f-remarks" placeholder="ANY ADDITIONAL INFORMATION ABOUT YOUR BUSINESS...">${esc((S.fd.remarks||'').toUpperCase())}</textarea>
<span class="ferr" id="e-remarks"></span>
</div>
<div class="btn-row">
<button class="btn btn-primary" id="next1">Review Details →</button>
</div>`;
let billCount=savedBill.length, shipCount=savedShip.length;
// Wire address blocks
savedBill.forEach((_,i)=>wireAddrBlock(card.querySelector('#bill-addr-container'),'fb',i+1,'f-cardName'));
savedShip.forEach((_,i)=>wireAddrBlock(card.querySelector('#ship-addr-container'),'fs',i+1,'f-cardName'));
// Customer type toggle
card.querySelectorAll('input[name="custType"]').forEach(r=>{
r.onchange=function(){
S.fd.customerType=this.value;
document.querySelectorAll('.type-card').forEach(c=>c.classList.remove('selected'));
this.closest('.type-card').classList.add('selected');
render();
};
});
// Same as billing
card.querySelector('#sameAsBill').onchange=function(){
card.querySelector('#ship-addr-section').style.display=this.checked?'none':'';
S.fd.sameAsBill=!this.checked;
};
// Add address buttons
card.querySelector('#add-bill-addr').onclick=()=>{
billCount++;
const c=card.querySelector('#bill-addr-container');
const d=document.createElement('div');
d.innerHTML=buildAddrBlockHTML('fb','Billing',billCount,{},{removable:true});
c.appendChild(d.firstElementChild);
wireAddrBlock(c,'fb',billCount,'f-cardName');
};
card.querySelector('#add-ship-addr').onclick=()=>{
shipCount++;
const c=card.querySelector('#ship-addr-container');
const d=document.createElement('div');
d.innerHTML=buildAddrBlockHTML('fs','Shipping',shipCount,{},{removable:true});
c.appendChild(d.firstElementChild);
wireAddrBlock(c,'fs',shipCount,'f-cardName');
};
// GSTIN → PAN
const gEl=card.querySelector('#f-gstin'),pEl=card.querySelector('#f-pan');
const pBadge=card.querySelector('#pan-badge'),fBtn=card.querySelector('#gstin-fetch');
function extractPan(g){return(g||'').length===15?g.substring(2,12):null;}
gEl.oninput=function(){
this.value=this.value.toUpperCase();
const p=extractPan(this.value);
if(p&&this.value.length===15){pEl.value=p;pBadge.style.display='flex';S.fd.pan=p;S.fd.panAutoFetched=true;}
else if(S.fd.panAutoFetched){pEl.value='';pBadge.style.display='none';S.fd.panAutoFetched=false;}
};
pEl.oninput=function(){this.value=this.value.toUpperCase();if(S.fd.panAutoFetched){pBadge.style.display='none';S.fd.panAutoFetched=false;}};
fBtn.onclick=()=>{
const g=(gEl.value||'').trim().toUpperCase();
if(g.length!==15){card.querySelector('#e-gstin').textContent='Enter a valid 15-digit GSTIN first';return;}
card.querySelector('#e-gstin').textContent='';
const p=extractPan(g);
pEl.value=p;pBadge.style.display='flex';S.fd.pan=p;S.fd.panAutoFetched=true;
toast('PAN extracted: '+p);
};
// MSME toggle
const mCk=card.querySelector('#f-hasMsme'),mF=card.querySelector('#msme-fields');
const mW=card.querySelector('#msme-wrap'),mA=card.querySelector('#az-msme-wrap');
mCk.onchange=function(){
mF.style.display=this.checked?'':'none';
mA&&(mA.style.display=this.checked?'':'none');
mW.classList.toggle('active',this.checked);
S.fd.hasMsme=this.checked;
};
// Restore existing attachment previews on re-render
if(!S.fd.attachments) S.fd.attachments={};
['pan','aadhaar','cheque','msme','other'].forEach(key=>{
const att=S.fd.attachments[key];
if(!att) return;
const el=document.querySelector('#ap-'+key);
if(el) renderAttachPreviewDirect(el, Array.isArray(att)?att:[att]);
});
// Drag-drop on attach zones
card.querySelectorAll('.attach-zone').forEach(z=>{
z.addEventListener('dragover',e=>{e.preventDefault();z.classList.add('drag');});
z.addEventListener('dragleave',()=>z.classList.remove('drag'));
z.addEventListener('drop',e=>{
e.preventDefault();z.classList.remove('drag');
const k=z.id.replace('az-','');
const inp=z.querySelector('input[type=file]');
if(inp&&e.dataTransfer.files.length){inp.files=e.dataTransfer.files;window.handleAttach(k,inp);}
});
});
// ── FIX: Validation — removed userId/userType checks (fields not in form)
card.querySelector('#next1').onclick=()=>{
const v=id=>(card.querySelector('#'+id)?.value||'').trim().toUpperCase();
const err=(id,msg)=>{const el=card.querySelector('#e-'+id);if(el)el.textContent=msg;};
const clr=id=>{const el=card.querySelector('#e-'+id);if(el)el.textContent='';};
const custType=card.querySelector('input[name="custType"]:checked')?.value||'B2B';
const sameAsBill=card.querySelector('#sameAsBill').checked;
let ok=true;
// Force uppercase on all text inputs
card.querySelectorAll('input:not([type=email]):not([type=file]):not([type=checkbox]):not([type=radio]):not([readonly])').forEach(inp=>{
if(inp.value) inp.value=inp.value.toUpperCase();
});
const remarksEl=card.querySelector('#f-remarks');
if(remarksEl) remarksEl.value=remarksEl.value.toUpperCase();
// Required business fields
if(!v('f-cardName')){err('cardName','Company / Customer Name is required');ok=false;}else clr('cardName');
if(!card.querySelector('#f-industry')?.value){err('industry','Industry is required');ok=false;}else clr('industry');
if(!v('f-contactFirst')){err('contactFirst','First Name is required');ok=false;}else clr('contactFirst');
if(!v('f-contactLast')){err('contactLast','Last Name is required');ok=false;}else clr('contactLast');
if(!v('f-remarks')){err('remarks','Remarks are required');ok=false;}else clr('remarks');
const mob=(card.querySelector('#f-mobile')?.value||'').trim();
if(!mob){err('mobile','Mobile Number is required');ok=false;}
else if(!/^[0-9+\s\-]{8,15}$/.test(mob)){err('mobile','Please enter a valid mobile number');ok=false;}
else clr('mobile');
const emailVal=(card.querySelector('#f-email')?.value||'').trim();
if(!emailVal){err('email','Email address is required');ok=false;}
else if(!/\S+@\S+\.\S+/.test(emailVal)){err('email','Please enter a valid email address');ok=false;}
else clr('email');
const panVal=v('f-pan');
if(!panVal){err('pan','PAN Number is required');ok=false;}
else if(!/^[A-Z]{5}[0-9]{4}[A-Z]$/.test(panVal)){err('pan','Invalid PAN format (e.g. ABCDE1234F)');ok=false;}
else clr('pan');
if(custType==='B2B'){
const g=v('f-gstin');
if(!g){err('gstin','GSTIN is required for B2B customers');ok=false;}
else if(!/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/.test(g)){err('gstin','Please enter a valid GSTIN (e.g. 06ABCDE1234F1Z5)');ok=false;}
else clr('gstin');
} else {
clr('gstin');
}
const hasMsme=card.querySelector('#f-hasMsme')?.checked;
if(hasMsme){
if(!v('f-msmeNo')){err('msmeNo','Udyam Registration Number is required');ok=false;}
else if(!/^UDYAM-[A-Z]{2}-\d{2}-\d{7}$/.test(v('f-msmeNo'))){err('msmeNo','Format: UDYAM-HR-18-0040140');ok=false;}
else clr('msmeNo');
if(!S.fd.attachments?.msme){err('att-msme','Please upload your MSME certificate');ok=false;}
}
if(!S.fd.attachments?.pan){err('att-pan','PAN card document is required');ok=false;}
if(!S.fd.attachments?.aadhaar){err('att-aadhaar','Aadhaar card document is required');ok=false;}
if(!S.fd.attachments?.cheque){err('att-cheque','Cancelled cheque is required');ok=false;}
const firstBill=card.querySelector('#fb-block-wrap-1');
if(firstBill){
if(!firstBill.querySelector('#fb-street-1')?.value?.trim()){toast('Please fill Street in the billing address','err');ok=false;}
if(!firstBill.querySelector('#fb-city-1')?.value?.trim()){toast('Please fill City in the billing address','err');ok=false;}
if(!firstBill.querySelector('#fb-state-1')?.value){toast('Please select State in the billing address','err');ok=false;}
}
if(!ok){
const fe=card.querySelector('.ferr:not(:empty)');
if(fe) fe.scrollIntoView({behavior:'smooth',block:'center'});
return;
}
const allBill=collectAddrBlocks(card.querySelector('#bill-addr-container'),'fb');
const allShip=sameAsBill?[]:collectAddrBlocks(card.querySelector('#ship-addr-container'),'fs');
const pb=allBill[0]||{},ps=sameAsBill?pb:(allShip[0]||pb);
S.fd={
company:card.querySelector('#f-company')?.value||window.__DEFAULT_COMPANY__,
customerType:custType,
cardName:v('f-cardName'),foreignName:v('f-foreignName'),
typeOfBusiness:card.querySelector('#f-typeOfBusiness')?.value||'',
industry:card.querySelector('#f-industry')?.value||'',
contactFirst:v('f-contactFirst'),contactLast:v('f-contactLast'),contactTitle:v('f-contactTitle'),
contactEmail:(card.querySelector('#f-contactEmail')?.value||'').trim(),
mobile:mob,email:emailVal,
billAddressName:pb.addrName||'',billStreet:pb.street||'',billBlock:pb.block||'',
billCity:pb.city||'',billZip:pb.zip||'',billState:pb.state||'',billCountry:pb.country||'India',
sameAsBill,
shipAddressName:ps.addrName||'',shipStreet:ps.street||'',shipBlock:ps.block||'',
shipCity:ps.city||'',shipZip:ps.zip||'',shipState:ps.state||'',shipCountry:ps.country||'India',
allBillAddresses:allBill,allShipAddresses:sameAsBill?allBill:allShip,
currency:card.querySelector('#f-currency')?.value||'Indian Rupee',
gstin:v('f-gstin'),pan:v('f-pan'),panAutoFetched:S.fd.panAutoFetched||false,
remarks:v('f-remarks'),
hasMsme:hasMsme||false,
msmeNo:hasMsme?v('f-msmeNo'):'',
msmeType:hasMsme?card.querySelector('#f-msmeType')?.value||'':'',
msmeBType:hasMsme?card.querySelector('#f-msmeBType')?.value||'':'',
attachments:S.fd.attachments||{},
};
S.step=2;window.scrollTo(0,0);render();
};
return card;
}
// ── STEP 2: REVIEW ──────────────────────────────────────
function renderStep2(){
const fd=S.fd;
const card=mkEl('div','card');
const billHtml=(fd.allBillAddresses||[]).map((a,i)=>`
<tr><td>Billing${i>0?' #'+(i+1):''}</td>
<td class="val">${esc(a.addrName||'—')}<br/>
<span style="font-size:12px;color:#6b7a8d">${esc(a.street||'')}${a.block?', '+esc(a.block):''}, ${esc(a.city||'')} ${esc(a.state||'')} ${esc(a.zip||'')}, ${esc(a.country||'')}</span>
${a.gstin?`<br/><span style="font-size:11px;color:#6b7a8d">GSTIN: ${esc(a.gstin)}</span>`:''}
</td></tr>`).join('');
const shipHtml=fd.sameAsBill
?'<tr><td>Shipping</td><td class="val" style="color:#6b7a8d">Same as Billing Address</td></tr>'
:(fd.allShipAddresses||[]).map((a,i)=>`
<tr><td>Shipping${i>0?' #'+(i+1):''}</td>
<td class="val">${esc(a.addrName||'—')}<br/>
<span style="font-size:12px;color:#6b7a8d">${esc(a.street||'')}${a.block?', '+esc(a.block):''}, ${esc(a.city||'')} ${esc(a.state||'')} ${esc(a.zip||'')}</span></td></tr>`).join('');
card.innerHTML=`
<div class="card-title">Review Your Details</div>
<div class="card-sub">Please review carefully before submitting. Once submitted, our team will contact you.</div>
<div class="alert a-info">ℹ After submission, our staff will verify your details and create your account in SAP B1. You will be contacted once approved.</div>
<table class="dtable">
<tr><td>Customer Type</td><td class="val"><span class="badge ${fd.customerType==='B2B'?'b-b2b':'b-b2c'}">${esc(fd.customerType)}</span></td></tr>
<tr><td>Company Name</td><td class="val"><strong>${esc(fd.cardName)}</strong></td></tr>
${fd.foreignName?`<tr><td>Foreign Name</td><td class="val">${esc(fd.foreignName)}</td></tr>`:''}
<tr><td>Industry</td><td class="val">${esc(fd.industry||'—')}</td></tr>
<tr><td>Type of Business</td><td class="val">${esc(fd.typeOfBusiness)}</td></tr>
<tr><td>Contact Person</td><td class="val">${esc(fd.contactFirst+' '+fd.contactLast)}${fd.contactTitle?` <span style="color:#6b7a8d;font-size:12px">(${esc(fd.contactTitle)})</span>`:''}</td></tr>
<tr><td>Mobile</td><td class="val">${esc(fd.mobile)}</td></tr>
<tr><td>Email</td><td class="val">${esc(fd.email)}</td></tr>
${fd.contactEmail?`<tr><td>Alt Email</td><td class="val">${esc(fd.contactEmail)}</td></tr>`:''}
${billHtml}${shipHtml}
<tr><td>Currency</td><td class="val">${esc(fd.currency)}</td></tr>
<tr><td>GSTIN</td><td class="val">${fd.gstin?`<code style="background:#f4f6fb;padding:2px 10px;border-radius:5px;font-family:'IBM Plex Mono'">${esc(fd.gstin)}</code>`:'— (B2C Customer)'}</td></tr>
<tr><td>PAN Number</td><td class="val"><code style="background:#f4f6fb;padding:2px 10px;border-radius:5px;font-family:'IBM Plex Mono'">${esc(fd.pan)}</code>${fd.panAutoFetched?' <span style="background:#e6f4eb;color:var(--green);font-size:10px;font-weight:700;padding:1px 6px;border-radius:3px">AUTO</span>':''}</td></tr>
<tr><td>MSME</td><td class="val">${fd.hasMsme?`<span style="color:var(--green);font-weight:600">✓ ${esc(fd.msmeNo)}</span> <span style="font-size:12px;color:#6b7a8d">${fd.msmeType}${fd.msmeBType?' / '+fd.msmeBType:''}</span>`:'Not registered'}</td></tr>
<tr><td>Documents</td><td class="val">${Object.entries(fd.attachments||{}).filter(([,v])=>v&&(Array.isArray(v)?v.length:true)).map(([k,v])=>{const arr=Array.isArray(v)?v:[v];return arr.map(a=>`<span style="display:inline-flex;background:#e8f1fd;color:var(--blue);padding:2px 9px;border-radius:4px;font-size:11px;font-weight:600;margin:2px 3px 2px 0">${k.toUpperCase()}: ${esc(a.name)}</span>`).join('');}).join('')||'<span style="color:#8fa3bc">None uploaded</span>'}</td></tr>
<tr><td>Remarks</td><td class="val" style="font-size:13px;color:#475569">${esc(fd.remarks||'—')}</td></tr>
</table>
<div class="btn-row">
<button class="btn btn-secondary" id="s2back">← Edit Details</button>
<button class="btn btn-primary" id="s2submit">
<span id="s2sp" style="display:none" class="sp"></span>
✓ Confirm & Submit Application
</button>
</div>`;
card.querySelector('#s2back').onclick=()=>{S.step=1;window.scrollTo(0,0);render();};
card.querySelector('#s2submit').onclick=async()=>{
const btn=card.querySelector('#s2submit'),sp=card.querySelector('#s2sp');
btn.disabled=true;sp.style.display='inline-block';
try{
const r=await api('POST','/customers/submit',S.fd);
S.submissionId=r.id;
toast('Application submitted successfully!');
S.step=3;window.scrollTo(0,0);render();
}catch(e){
toast(e.message,'err');
btn.disabled=false;sp.style.display='none';
}
};
return card;
}
// ── STEP 3: SUCCESS ─────────────────────────────────────
function renderStep3(){
const card=mkEl('div','card');
const refNo=S.submissionId?`REG-${String(S.submissionId).padStart(5,'0')}`:'Submitted';
card.innerHTML=`
<div class="success-wrap">
<div class="success-icon"></div>
<h2>Application Submitted!</h2>
<p>Thank you, <strong>${esc((S.fd.contactFirst||'')+' '+(S.fd.contactLast||''))}</strong>!
Your customer registration for <strong>${esc(S.fd.cardName)}</strong> has been received.</p>
<div class="ref-box">${esc(refNo)}</div>
<p>Our team will review your details and contact you at <strong>${esc(S.fd.mobile)}</strong> or <strong>${esc(S.fd.email)}</strong> once your account is created in SAP B1.</p>
<p style="font-size:12px;color:#94a3b8;margin-top:12px">Please keep your reference number for follow-up.</p>
<div style="margin-top:32px;display:flex;gap:12px;justify-content:center;flex-wrap:wrap">
<button class="btn btn-secondary" id="newapp">+ Register Another Customer</button>
<button class="btn btn-primary" onclick="window.print()">Print Confirmation</button>
</div>
</div>`;
card.querySelector('#newapp').onclick=()=>{S.fd={};S.step=1;S.submissionId=null;window.scrollTo(0,0);render();};
return card;
}
(async function(){try{const list=await window.fetchCompanyList();if(list.length)_companies=list;}catch(_e){}render();})();
</script>
<script src="/sidebar.js?v=25"></script>
</body>
</html>