// Login.jsx // Cloudflare's official test sitekey — always passes. Replace with your real // sitekey from https://dash.cloudflare.com/?to=/:account/turnstile (free) // before going to production. const TURNSTILE_SITEKEY = '1x00000000000000000000AA'; function CloudflareTurnstile({ onVerify, onExpire }) { const ref = React.useRef(null); const widgetIdRef = React.useRef(null); React.useEffect(() => { let cancelled = false; const ensureScript = () => new Promise((resolve) => { if (window.turnstile) return resolve(); const existing = document.querySelector('script[data-turnstile]'); if (existing) { existing.addEventListener('load', resolve, { once: true }); return; } const s = document.createElement('script'); s.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; s.async = true; s.defer = true; s.dataset.turnstile = '1'; s.addEventListener('load', resolve, { once: true }); document.head.appendChild(s); }); ensureScript().then(() => { if (cancelled || !ref.current || !window.turnstile) return; widgetIdRef.current = window.turnstile.render(ref.current, { sitekey: TURNSTILE_SITEKEY, theme: 'light', callback: (token) => onVerify && onVerify(token), 'expired-callback': () => onExpire && onExpire(), 'error-callback': () => onExpire && onExpire(), }); }); return () => { cancelled = true; if (widgetIdRef.current && window.turnstile) { try { window.turnstile.remove(widgetIdRef.current); } catch (e) {} } }; }, []); return
; } // Email → role mapping. In production this is a server lookup against the // directory of registered users; the role + landing page are returned with the // auth token. Here we mock that with a static map for the prototype. const EMAIL_ROLE_MAP = { 'admin@powleecrane.com': { role: 'Admin', landing: 'dashboard', name: 'Wei Ming Lim', initials: 'WM', password: 'PowLee-Admin#26' }, 'dispatch@powleecrane.com': { role: 'Operation', landing: 'bookings', name: 'Sarah Tan', initials: 'ST', password: 'PowLee-Dispatch#26' }, 'razak@powleecrane.com': { role: 'Operator', landing: 'mobile', name: 'Razak Hassan', initials: 'RH', password: 'PowLee-Razak#26' }, 'faizal@powleecrane.com': { role: 'Rigger', landing: 'mobile', name: 'Ahmad Faizal', initials: 'AF', password: 'PowLee-Faizal#26' }, 'workshop@powleecrane.com': { role: 'Mechanic', landing: 'rm', name: 'Awei', initials: 'AW', password: 'PowLee-Workshop#26' }, 'finance@powleecrane.com': { role: 'Finance', landing: 'export', name: 'Aminah Yusof', initials: 'AY', password: 'PowLee-Finance#26' }, 'mgmt@powleecrane.com': { role: 'Management', landing: 'analytics', name: 'David Ng', initials: 'DN', password: 'PowLee-Mgmt#26' }, }; function LoginPage({ onLogin }) { const [email, setEmail] = React.useState(''); const [pass, setPass] = React.useState(''); const [show, setShow] = React.useState(false); const [cfToken, setCfToken] = React.useState(null); const [error, setError] = React.useState(null); const cfChecked = !!cfToken; const handleSubmit = (e) => { e.preventDefault(); if (!cfChecked) return; const acct = EMAIL_ROLE_MAP[email.trim().toLowerCase()]; if (!acct) { setError('No account matches that email. Contact admin@powleecrane.com to be added.'); return; } if (pass !== acct.password) { setError('Incorrect password for that account. Check your credentials and try again.'); return; } setError(null); onLogin(acct); }; return (
Pow Lee
POW LEE CRANE & ENGINEERING SDN. BHD.
Our Vision

Dispatch a crane.
Track every lift.

“To be the most trusted mobile crane solutions provider in Southern Malaysia.”

100%
Certified Crews
20+
Years In JB
24/7
Dispatch

Sign in

Sign in with your Pow Lee email — you'll be taken to the right workspace automatically based on your role.
{ setEmail(e.target.value); setError(null); }} style={{paddingLeft: 38, width: '100%'}} placeholder="you@powleecrane.com"/>
{ setPass(e.target.value); setError(null); }} style={{paddingLeft: 38, paddingRight: 60, width: '100%'}} placeholder="Enter your password"/>
Forgot password?
setCfToken(t)} onExpire={() => setCfToken(null)}/> {error ? (
{error}
) : null}
v1.0 prototype · PWA-ready · Add to home screen for fastest access
); } Object.assign(window, { LoginPage });