import React,{useEffect,useState} from "react";import{createRoot}from"react-dom/client";import Dexie from "dexie";import"./style.css"; const db=new Dexie("PWAContactsDB");db.version(1).stores({contacts:"++localId,clientId,email,synced,createdAt"}); function App(){const[f,setF]=useState({name:"",email:"",phone:""}),[cs,setCs]=useState([]),[online,setOnline]=useState(navigator.onLine),[syncing,setSyncing]=useState(false),[msg,setMsg]=useState(""); async function load(){setCs(await db.contacts.orderBy("createdAt").reverse().toArray())} async function sync(){if(!navigator.onLine||syncing)return;setSyncing(true);try{for(const c of await db.contacts.where("synced").equals(0).toArray()){const r=await fetch("/api/contacts/sync",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientId:c.clientId,name:c.name,email:c.email,phone:c.phone,createdAt:c.createdAt})});if(!r.ok)throw Error("sync");const x=await r.json();await db.contacts.update(c.localId,{synced:1,serverId:x.id})}setMsg("Sync complete")}catch(e){setMsg("Sync paused — will retry automatically.")}finally{setSyncing(false);load()}} useEffect(()=>{load();const on=()=>{setOnline(true);sync()},off=()=>{setOnline(false);setMsg("Offline — saved locally.")};addEventListener("online",on);addEventListener("offline",off);if("serviceWorker"in navigator)navigator.serviceWorker.register("/sw.js");sync();return()=>{removeEventListener("online",on);removeEventListener("offline",off)}},[]); async function save(e){e.preventDefault();await db.contacts.add({clientId:crypto.randomUUID(),...f,createdAt:new Date().toISOString(),synced:0});setF({name:"",email:"",phone:""});setMsg(online?"Saved locally — syncing...":"Saved locally — waiting for internet.");load();if(navigator.onLine)sync()} return

Offline Contacts

Works offline and syncs automatically.

{online?"● Online":"● Offline"}
setF({...f,name:e.target.value})}/>setF({...f,email:e.target.value})}/>setF({...f,phone:e.target.value})}/>

{syncing?"Syncing...":msg}

Contacts

{cs.map(c=>
{c.name}{c.email} · {c.phone}
{c.synced?"✓ Synced":"⏳ Pending"}
)}
}createRoot(document.getElementById("root")).render();