From b5b248aad7f611e32cb2dffebf00b5aede205db7 Mon Sep 17 00:00:00 2001 From: uai14 Date: Tue, 12 Aug 2025 16:03:54 -0700 Subject: [PATCH] Create App my --- App my | 537 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 App my diff --git a/App my b/App my new file mode 100644 index 00000000000000..13bff997378c29 --- /dev/null +++ b/App my @@ -0,0 +1,537 @@ +# Building an enhanced Vakil On-Demand starter with added mock features: +# - Matching algorithm (proximity + rating + fee) +# - Mock OTP authentication +# - Mock payment & invoice generation (text file) +# - KYC upload (saved in localStorage) +# - Mock map (simple canvas showing markers & compute ETA/distance) +# - Real-time simulation via polling/intervals +# - Urdu localization toggle +# - CSV export for bookings +# This will produce a zip ready to run (React single-page app). + +import os, zipfile, json, textwrap, shutil, base64 +base = "/mnt/data/vakil_on_demand_enhanced" +if os.path.exists(base): + shutil.rmtree(base) +os.makedirs(os.path.join(base, "public"), exist_ok=True) +os.makedirs(os.path.join(base, "src"), exist_ok=True) + +package_json = { + "name": "vakil-on-demand-enhanced", + "version": "1.0.0", + "private": True, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build" + } +} + +index_html = """ + + + + + Vakil On-Demand (Enhanced Starter) + + +
+ + +""" + +index_js = """import React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App'; +import './styles.css'; + +const root = createRoot(document.getElementById('root')); +root.render(); +""" + +app_js = r"""import React, { useState, useEffect, useRef } from 'react'; +import { SAMPLE_LAWYERS } from './data'; +import { haversine, exportCSV } from './utils'; + +// Simple Urdu translations +const URDU = { + title: "وکيل آن ڈیمانڈ", + role: "کردار", + client: "کلائنٹ", + lawyer: "وکيل", + admin: "ایڈمن", + searchPlaceholder: "شہر یا کی ورڈ تلاش کریں (مثلا فیملی لا)", + availableLawyers: "دستیاب وکلا", + viewProfile: "پروفائل دیکھیں", + book: "بک کریں", + bookings: "بکنگز", + accept: "قبول کریں", + complete: "مکمل کریں", + approve: "منظور کریں", + payment: "ادائیگی", + payNow: "اب ادائیگی کریں", + otp: "OTP", + sendOTP: "OTP بھیجیں", + verify: "تصدیق کریں", + uploadKYC: "KYC اپ لوڈ کریں" +}; + +function Header({role, setRole, urdu, setUrdu}){ + return ( +
+

{urdu? URDU.title : 'VakilOnDemand'}

+
+ + + +
+
+ ); +} + +function SearchBar({query, setQuery, specialty, setSpecialty, urdu}){ + return ( +
+ setQuery(e.target.value)} /> + +
+ ); +} + +function MapMock({center, lawyers, urdu}){ + // Simple SVG map mock: shows center and lawyer markers, computes distance & ETA (walking/car mock) + const width = 600, height = 300; + const cx = width/2, cy = height/2; + // lat/lng to simple xy transform (mock) + const latRange = 0.1, lngRange = 0.1; + function toXY(lat, lng){ + const x = cx + ((lng - center.lng)/lngRange) * (width/2); + const y = cy - ((lat - center.lat)/latRange) * (height/2); + return {x,y}; + } + + return ( +
+

{urdu? 'نقشہ (نمونہ)' : 'Map (mock)'}

+ + + + {urdu? 'آپکا مقام' : 'Your location'} + {lawyers.map(l=>{ + const p = toXY(l.coords.lat, l.coords.lng); + return ( + + + {l.name} ({Math.round(haversine(center.lat, center.lng, l.coords.lat, l.coords.lng))} km) + + ); + })} + +
{urdu? 'فاصلہ کیلومیٹر میں نمونہ' : 'Distances shown in km (mock ETA based on 40 km/h)'}.
+
+ ); +} + +function LawyerCard({lawyer, onView, onBook, urdu}){ + return ( +
+
+
{lawyer.name}
+
{lawyer.specialty.join(', ')} • {lawyer.city}
+
Rating: {lawyer.rating} • Fee: PKR {lawyer.fee}
+
+
+ + +
+
+ ); +} + +function AuthMock({onLogin, urdu}){ + const [phone, setPhone] = useState('03'); + const [sent, setSent] = useState(false); + const [code, setCode] = useState(''); + function send(){ + setSent(true); + alert(urdu? 'OTP بھیج دیا گیا (mock): 1234' : 'Mock OTP sent: 1234'); + } + function verify(){ + if(code.trim()==='1234'){ onLogin({phone}); } else { alert(urdu? 'غلط OTP' : 'Wrong OTP'); } + } + return ( +
+

{urdu? 'لاگ ان (نمونہ)' : 'Login (mock)'}

+ setPhone(e.target.value)} placeholder="03XXXXXXXXX" /> + {!sent? : +
+ setCode(e.target.value)} placeholder={urdu? URDU.otp : 'Enter OTP (1234)'} /> + +
+ } +
+ ); +} + +function PaymentMock({booking, onPaid, urdu}){ + const [card, setCard] = useState(''); + function pay(){ + // mock success + const updated = {...booking, paid:true, status:'paid'}; + onPaid(updated); + // create simple invoice file + const invoice = `INVOICE\nBooking ID: ${booking.id}\nLawyer: ${booking.lawyerName}\nAmount: PKR ${booking.fee}\nDate: ${new Date().toLocaleString()}\n`; + const blob = new Blob([invoice], {type:'text/plain'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); a.href = url; a.download = `invoice_${booking.id}.txt`; a.click(); URL.revokeObjectURL(url); + } + return ( +
+

{urdu? URDU.payment : 'Payment (mock)'}

+
Amount: PKR {booking.fee}
+ setCard(e.target.value)} placeholder={urdu? 'کارڈ نمبر' : 'Card number (mock)'} /> + +
+ ); +} + + +function ClientView({user, urdu}){ + const [query, setQuery] = useState(''); + const [specialty, setSpecialty] = useState(''); + const [lawyers, setLawyers] = useState(()=>JSON.parse(localStorage.getItem('lod_lawyers')) || SAMPLE_LAWYERS); + const [bookings, setBookings] = useState(()=>JSON.parse(localStorage.getItem('lod_bookings')||'[]')); + const [selected, setSelected] = useState(null); + const [center, setCenter] = useState({lat:33.6844, lng:73.0479}); // Islamabad mock + const [showPaymentFor, setShowPaymentFor] = useState(null); + + useEffect(()=>{ localStorage.setItem('lod_lawyers', JSON.stringify(lawyers)); }, [lawyers]); + useEffect(()=>{ localStorage.setItem('lod_bookings', JSON.stringify(bookings)); }, [bookings]); + + function filtered(){ + return lawyers.filter(l=> (specialty? l.specialty.includes(specialty): true) && + (query? (l.city.toLowerCase().includes(query.toLowerCase()) || l.name.toLowerCase().includes(query.toLowerCase())) : true) && + l.approved + ); + } + + function viewProfile(id){ const l = lawyers.find(x=>x.id===id); setSelected(l); } + + function bookLawyer(id, instant=true){ + const l = lawyers.find(x=>x.id===id); + const distance = Math.round(haversine(center.lat, center.lng, l.coords.lat, l.coords.lng)); + const etaMinutes = Math.max(5, Math.round((distance / 40) * 60)); // 40 km/h mock + const booking = { + id: Date.now(), + lawyerId: l.id, + lawyerName: l.name, + clientName: user? user.phone : 'Guest', + status: 'requested', + time: new Date().toISOString(), + fee: l.fee, + location: 'Client Address (demo)', + distance_km: distance, + eta_min: etaMinutes, + paid: false + }; + // matching: notify top 3 by score + const candidates = lawyers.filter(x=> x.approved && x.id!==l.id); + // compute scores + const scored = candidates.map(c=>{ + const dist = haversine(center.lat, center.lng, c.coords.lat, c.coords.lng); + const proximityScore = Math.max(0, 1 - (dist/30)); // within 30km + const ratingScore = c.rating/5; + const feeScore = 1 - Math.min(1, c.fee/10000); + const score = proximityScore*0.4 + ratingScore*0.4 + feeScore*0.2; + return {...c, score}; + }).sort((a,b)=>b.score-a.score); + // store booking and simulate notifying top 3 (for demo we'll just add booking) + setBookings([booking, ...bookings]); + alert(urdu? 'بکنگ کی درخواست بھیج دی گئی' : 'Booking requested. Switch to Lawyer role to accept.'); + // auto-simulate an acceptance by target lawyer after random delay (demo) + setTimeout(()=>{ + const updated = {...booking, status:'accepted'}; + setBookings(prev => prev.map(b=> b.id===booking.id? updated : b)); + alert((urdu? 'وکيل نے قبول کر لیا: ' : 'A lawyer accepted: ') + booking.lawyerName); + }, 3000 + Math.floor(Math.random()*4000)); + } + + function onPaid(updated){ + setBookings(prev => prev.map(b=> b.id===updated.id? updated: b)); + setShowPaymentFor(null); + } + + return ( +
+ +
+
+

{urdu? URDU.availableLawyers : 'Available Lawyers'}

+ {filtered().map(l=> bookLawyer(l.id)} urdu={urdu} />)} +
+

{urdu? 'فاسٹ میچنگ (نمونہ)' : 'Quick matching (demo)'}

+ +
+
+
+ l.approved)} urdu={urdu} /> + {selected? ( +
+

{selected.name}

+
{selected.specialty.join(', ')} • {selected.city}
+

{selected.bio}

+
Fee: PKR {selected.fee} • Rating: {selected.rating}
+
+ + +
+
+ ) : ( +
{urdu? 'پروفائل منتخب کریں' : 'Select a lawyer to see profile here.'}
+ )} +
+

{urdu? URDU.bookings : 'Your Bookings (demo)'}

+ + {bookings.length===0?
No bookings yet.
: bookings.map(b => ( +
+
{b.lawyerName} • {new Date(b.time).toLocaleString()}
+
Status: {b.status} • Dist: {b.distance_km} km • ETA: {b.eta_min} min
+
+ {!b.paid && } +
+
+ ))} +
+
+
+ {showPaymentFor && } +
+ ); +} + +function LawyerDashboard({urdu}){ + const [lawyers, setLawyers] = useState(()=>JSON.parse(localStorage.getItem('lod_lawyers')) || SAMPLE_LAWYERS); + const [bookings, setBookings] = useState(()=>JSON.parse(localStorage.getItem('lod_bookings')||'[]')); + + useEffect(()=>{ localStorage.setItem('lod_lawyers', JSON.stringify(lawyers)); }, [lawyers]); + useEffect(()=>{ localStorage.setItem('lod_bookings', JSON.stringify(bookings)); }, [bookings]); + + function accept(id){ setBookings(bookings.map(b=> b.id===id? {...b, status:'accepted'} : b)); alert(urdu? 'بکنگ قبول' : 'Booking accepted'); } + function complete(id){ setBookings(bookings.map(b=> b.id===id? {...b, status:'completed'} : b)); } + function uploadKYC(ev, id){ + const file = ev.target.files[0]; + if(!file) return; + const reader = new FileReader(); + reader.onload = ()=>{ + const dataUrl = reader.result; + setLawyers(prev => prev.map(l=> l.id===id? {...l, kyc:dataUrl}: l)); + alert(urdu? 'KYC اپ لوڈ ہوا (مقامی)' : 'KYC uploaded (local)'); + }; + reader.readAsDataURL(file); + } + + const myBookings = bookings.filter(b=> true); + + return ( +
+

{urdu? 'وکيل ڈیش بورڈ (نمونہ)' : 'Lawyer Dashboard (demo)'}

+
+

Incoming Requests

+ {myBookings.filter(b=>b.status==='requested').length===0?
No new requests.
: myBookings.filter(b=>b.status==='requested').map(b=> ( +
+
{b.clientName} • {b.location}
+
Fee: PKR {b.fee} • Dist: {b.distance_km} km • ETA: {b.eta_min} min
+
+ + +
+
+ ))} +
+ +
+

All Bookings

+ {myBookings.length===0?
No bookings.
: myBookings.map(b=> ( +
+
{b.lawyerName} • {b.clientName} • {new Date(b.time).toLocaleString()}
+
Status: {b.status} • Paid: {b.paid? 'Yes' : 'No'}
+
+ ))} +
+ +
+

{urdu? URDU.uploadKYC : 'Upload KYC (local)'}

+ { (JSON.parse(localStorage.getItem('lod_lawyers')) || []).map(l=> ( +
+
+
{l.name}
+
{l.specialty.join(', ')}
+
+
+ uploadKYC(e, l.id)} /> +
+
+ ))} +
+
+ ); +} + +function AdminView({urdu}){ + const [lawyers, setLawyers] = useState(()=>JSON.parse(localStorage.getItem('lod_lawyers')) || SAMPLE_LAWYERS); + const [bookings, setBookings] = useState(()=>JSON.parse(localStorage.getItem('lod_bookings')||'[]')); + + function approveLawyer(id){ setLawyers(lawyers.map(l=> l.id===id? {...l, approved:true}: l)); alert(urdu? 'منظور شد' : 'Approved'); } + + return ( +
+
+
+
+

Lawyer Applications

+ {lawyers.filter(l=>!l.approved).length===0?
No pending.
: lawyers.filter(l=>!l.approved).map(l=> ( +
+
+
{l.name}
+
{l.specialty.join(', ')}
+
+
+
+ ))} +
+
+
+
+

Bookings Overview

+
Total bookings: {bookings.length}
+
Approved lawyers: {lawyers.filter(l=>l.approved).length}
+
+
+
+
+ ); +} + +export default function App(){ + const [role, setRole] = useState('client'); + const [user, setUser] = useState(()=> JSON.parse(localStorage.getItem('lod_user')||'null')); + const [urdu, setUrdu] = useState(false); + + useEffect(()=>{ document.title = 'VakilOnDemand (Enhanced Demo)'; }, []); + + function onLogin(u){ setUser(u); localStorage.setItem('lod_user', JSON.stringify(u)); } + + return ( +
+
+
+ {!user? : ( + role==='client' ? : role==='lawyer' ? : + )} +
+ +
+ ); +} +""" + +data_js = """export const SAMPLE_LAWYERS = [ + { id: 1, name: 'Ayesha Khan', specialty: ['family','immigration'], city: 'Islamabad', rating: 4.7, fee: 5000, bio: 'Experienced family lawyer with 8 years practice.', approved: true, coords: {lat:33.6844, lng:73.0479} }, + { id: 2, name: 'Bilal Ahmed', specialty: ['criminal'], city: 'Rawalpindi', rating: 4.5, fee: 4000, bio: 'Criminal defense specialist.', approved: true, coords: {lat:33.5975, lng:73.0479} }, + { id: 3, name: 'Dr. Sana R.', specialty: ['corporate','contracts'], city: 'Lahore', rating: 4.9, fee: 8000, bio: 'Corporate lawyer with international experience.', approved: false, coords: {lat:31.5204, lng:74.3587} }, + { id: 4, name: 'Imran Qureshi', specialty: ['family','criminal'], city: 'Islamabad', rating: 4.2, fee: 3500, bio: 'Local counsel for civil cases.', approved: true, coords: {lat:33.7000, lng:73.0667} } +]; +""" + +utils_js = r"""export function haversine(lat1, lon1, lat2, lon2){ + const toRad = x => x * Math.PI / 180; + const R = 6371; // km + const dLat = toRad(lat2-lat1); + const dLon = toRad(lon2-lon1); + const a = Math.sin(dLat/2)*Math.sin(dLat/2) + Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.sin(dLon/2)*Math.sin(dLon/2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + return R * c; +} + +export function exportCSV(data, filename='export.csv'){ + const rows = [Object.keys(data[0]||{}) , ...data.map(d=> Object.values(d))]; + const csv = rows.map(r=> r.map(c=> `\"${String(c).replace(/\"/g,'\"\"')}\"`).join(',')).join('\\n'); + const blob = new Blob([csv], {type:'text/csv;charset=utf-8;'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); +} +""" + +styles_css = """body { margin:0; font-family: Inter, system-ui, Arial; background:#f3f4f6; color:#111827; } +.header { background:#111827; color:#fff; padding:12px 20px; display:flex; justify-content:space-between; align-items:center; } +.header h1 { margin:0; font-size:20px; } +.container { max-width:1000px; margin:18px auto; padding:0 12px; } +.searchbar { display:flex; gap:8px; margin:12px 0; } +.searchbar input, .searchbar select, .panel input { padding:8px; border:1px solid #e5e7eb; border-radius:6px; flex:1; } +.layout { display:grid; grid-template-columns:1fr 320px; gap:12px; } +.card { background:#fff; padding:12px; border-radius:8px; display:flex; justify-content:space-between; align-items:center; box-shadow:0 1px 2px rgba(0,0,0,0.04); margin-bottom:8px; } +.card .name { font-weight:600; } +.muted { color:#6b7280; font-size:13px; } +.panel { background:#fff; padding:12px; border-radius:8px; box-shadow:0 1px 2px rgba(0,0,0,0.04); margin-bottom:12px; } +.card-actions button, .row button, .card button { padding:8px 10px; border-radius:6px; border:1px solid #d1d5db; background:transparent; cursor:pointer; } +.booking { background:#fafafa; padding:8px; border-radius:6px; margin-bottom:8px; } +.footer { text-align:center; color:#6b7280; padding:12px 0; } +small { color:#6b7280; } +""" + +readme = """# Vakil On-Demand (Enhanced Starter Web App)\n\nThis enhanced starter includes mock implementations for many features of an on-demand app (maps mock, matching, OTP mock, payments mock, KYC local upload, Urdu toggle, CSV export). It's still a demo with localStorage persistence only — no backend or real payments.\n\n## How to run\n1. Ensure Node.js and npm are installed.\n2. Extract the zip, run `npm install` then `npm start`.\n3. Open http://localhost:3000\n\n## Notes\n- Map is a visual mock (SVG) showing relative positions using coordinates in `src/data.js`.\n- Payments are mocked and produce a text invoice file.\n- OTP is mocked (enter 1234 to pass).\n- KYC uploads are stored in localStorage as data URLs — not secure.\n- For production you must add backend APIs, authentication, real maps, routing, and payment gateway.\n""" + +# write files +with open(os.path.join(base, "package.json"), "w") as f: + json.dump(package_json, f, indent=2) +with open(os.path.join(base, "public", "index.html"), "w") as f: + f.write(index_html) +with open(os.path.join(base, "src", "index.js"), "w") as f: + f.write(index_js) +with open(os.path.join(base, "src", "App.jsx"), "w") as f: + f.write(app_js) +with open(os.path.join(base, "src", "data.js"), "w") as f: + f.write(data_js) +with open(os.path.join(base, "src", "utils.js"), "w") as f: + f.write(utils_js) +with open(os.path.join(base, "src", "styles.css"), "w") as f: + f.write(styles_css) +with open(os.path.join(base, "README.md"), "w") as f: + f.write(readme) + +# create zip +zip_path = "/mnt/data/vakil_on_demand_enhanced.zip" +with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z: + for folder, _, files in os.walk(base): + for file in files: + full = os.path.join(folder, file) + arcname = os.path.relpath(full, base) + z.write(full, arcname) + +print("Created enhanced starter zip at", zip_path)