PageSourceSearch

https://crewops.lovable.app/assets/Integrations-CB8bUfkF.js

js crewops.lovable.app collected 2026-10-03 03:21:32 UTC 35,664 bytes, 11 lines download raw bytes

1import{c as ke,ab as we,al as Ue,r as h,ad as Fe,K as w,an as v,aJ as _e,j as e,Q as q,V as z,D as V,Y as U,_ as W,m as G,$ as T,B as u,bA as R,a6 as Z,U as $,C as J,i as ue,a as X,a$ as he,aL as ee,aO as se,aP as te,aQ as ne,bb as ae,b3 as ie,a2 as We,t as de,ao as Ce,b6 as $e,aY as Re,aZ as Ze,a7 as He,a_ as Me,q as pe,x as Ke,bW as Ve,s as Se,h as Ee,M as Ye,v as Je,aq as Xe,aM as es,aN as ge,a0 as Y,a1 as je,as as ss,at as ts,au as ns,av as as,aw as is,Z as re,aV as rs}from"./index-B01jGDSO.js";import{S as Le}from"./switch-DMI64B-N.js";import{u as ls,Z as cs}from"./useZapierWebhooks-Gx3Apo8T.js";import{A as os}from"./activity-BtD7b2b7.js";import{R as le}from"./refresh-cw-DwcaIqPh.js";import{E as Pe}from"./external-link-Clh8aBfZ.js";import{T as fe,a as ye,b as M,c as A,d as be,e as D}from"./table-B_s0nExu.js";import{A as ds}from"./arrow-up-down-CydMclkM.js";import{H as ms}from"./hard-drive-DuThUvn8.js";import{S as xs}from"./star-DGOT8Vbw.js";import{i as us,I as hs}from"./integrations-config-DRBO6KnS.js";import{P as ps}from"./play-Rd75rCly.js";import"./save-DXWhUztY.js";import"./SecretInput-_n6rtxt2.js";import"./key-CB0aWigJ.js";/**
2 * @license lucide-react v0.462.0 - ISC
3 *
4 * This source code is licensed under the ISC license.
5 * See the LICENSE file in the root directory of this source tree.
6 */const gs=ke("LogIn",[["path",{d:"M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4",key:"u53s6r"}],["polyline",{points:"10 17 15 12 10 7",key:"1ail0h"}],["line",{x1:"15",x2:"3",y1:"12",y2:"12",key:"v6grx8"}]]);/**
7 * @license lucide-react v0.462.0 - ISC
8 *
9 * This source code is licensed under the ISC license.
10 * See the LICENSE file in the root directory of this source tree.
11 */const js=ke("Unlink",[["path",{d:"m18.84 12.25 1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71",key:"yqzxt4"}],["path",{d:"m5.17 11.75-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71",key:"4qinb0"}],["line",{x1:"8",x2:"8",y1:"2",y2:"5",key:"1041cp"}],["line",{x1:"2",x2:"5",y1:"8",y2:"8",key:"14m1p5"}],["line",{x1:"16",x2:"16",y1:"19",y2:"22",key:"rzdirn"}],["line",{x1:"19",x2:"22",y1:"16",y2:"16",key:"ox905f"}]]);function Ae(){const{organization:a}=we(),r=a==null?void 0:a.id,f=Ue(),[_,x]=h.useState(!1),{data:C=[],isLoading:L}=Fe({queryKey:["qb-sync-log",r],queryFn:async()=>{const{data:g,error:l}=await w.from("qb_sync_log").select("*").eq("tenant_id",r).order("synced_at",{ascending:!1}).limit(50);if(l)throw l;return g},enabled:!!r}),S=h.useCallback(async(g,l,t)=>{var j,N,y;if(r){x(!0);try{const{data:s,error:c}=await w.from("qb_sync_log").insert({tenant_id:r,entity_type:g,entity_id:l,direction:t,status:"PENDING"}).select().single();if(c)throw c;const k=await w.functions.invoke("quickbooks-sync",{body:{syncType:g,direction:t.toLowerCase(),entityId:l}}),I=k.error?"FAILED":"SUCCESS",B=((N=(j=k.data)==null?void 0:j.result)==null?void 0:N.qbId)||null;return await w.from("qb_sync_log").update({status:I,qb_entity_id:B,error_message:((y=k.error)==null?void 0:y.message)||null}).eq("id",s.id),I==="SUCCESS"&&B&&await w.from("qb_entity_mappings").upsert({tenant_id:r,local_entity_type:g,local_entity_id:l,qb_entity_type:g,qb_entity_id:B},{onConflict:"tenant_id,local_entity_type,local_entity_id"}),f.invalidateQueries({queryKey:["qb-sync-log"]}),v.success(`${g} ${t.toLowerCase()} completed`),k.data}catch(s){throw v.error(`Sync failed: ${s.message}`),s}finally{x(!1)}}},[r,f]),p=h.useCallback(async(g,l)=>{var t,j,N,y;if(r){x(!0);try{const s=await w.functions.invoke("quickbooks-sync",{body:{syncType:g,direction:l.toLowerCase(),bulk:!0}});if(s.error)throw s.error;return await w.from("qb_sync_log").insert({tenant_id:r,entity_type:g,entity_id:"00000000-0000-0000-0000-000000000000",direction:l,status:"SUCCESS",qb_entity_id:`bulk:${((j=(t=s.data)==null?void 0:t.result)==null?void 0:j.count)||0}`}),f.invalidateQueries({queryKey:["qb-sync-log"]}),v.success(`Synced ${((y=(N=s.data)==null?void 0:N.result)==null?void 0:y.count)||0} ${g}s`),s.data}catch(s){throw v.error(`Bulk sync failed: ${s.message}`),s}finally{x(!1)}}},[r,f]),E=C[0]||null;return{syncLogs:C,syncEntity:S,syncAll:p,syncing:_,lastSync:E,isLoading:L}}function fs({showSyncActions:a=!0}){const{connection:r,syncLogs:f,isLoading:_,isSyncing:x,connect:C,disconnect:L,syncCustomers:S,syncTimeTracking:p,syncEstimates:E,syncItems:g,syncVendors:l}=_e(),{syncLogs:t,syncing:j,syncAll:N}=Ae(),[y,s]=h.useState(!1),[c,k]=h.useState(!1),[I,B]=h.useState(null),[n,P]=h.useState(!1),i=t.slice(0,20).filter(o=>o.status==="FAILED").length,m=t[0],d=t.filter(o=>o.status==="PENDING").length,H=i===0?"text-green-600":i<=3?"text-amber-500":"text-destructive",F=i===0?"Healthy":i<=3?"Degraded":"Errors",De=i===0?"bg-green-500":i<=3?"bg-amber-500":"bg-destructive",Ie=async()=>{P(!0);try{const o=[...new Set(t.filter(O=>O.status==="PENDING").map(O=>O.entity_type))];for(const O of o)await N(O,"PUSH");o.length===0&&v.info("No pending items in sync queue")}catch{}finally{P(!1)}},qe=async()=>{await L(),s(!1)},Te=async()=>{try{const{data:o,error:O}=await w.from("time_entries").select("*, profiles!time_entries_employee_id_fkey(full_name), jobs(project_name, client_id)").eq("synced_to_qb",!1).not("clock_out","is",null);if(O)throw O;if(!(o!=null&&o.length)){v.info("No unsynced time entries to push");return}const Ge=o.map(Q=>{var me,xe;return{date:new Date(Q.clock_in).toISOString().split("T")[0],hours:Q.hours_w
11orked,hourlyRate:Q.hourly_rate,description:Q.description||`${((me=Q.profiles)==null?void 0:me.full_name)||"Employee"} - ${((xe=Q.jobs)==null?void 0:xe.project_name)||"General"}`,billable:Q.is_billable}});await p(Ge);const ze=o.map(Q=>Q.id);await w.from("time_entries").update({synced_to_qb:!0}).in("id",ze),v.success(`Synced ${o.length} time entries to QuickBooks`)}catch(o){console.error("Time sync error:",o),v.error("Failed to sync time entries")}},Be=async o=>{k(!1),B(null),await S(o)},Oe=o=>{switch(o){case"success":return e.jsx(Ce,{className:"h-4 w-4 text-emerald-600"});case"error":return e.jsx(de,{className:"h-4 w-4 text-destructive"});default:return e.jsx(We,{className:"h-4 w-4 text-amber-600"})}},Qe=o=>({customer:"Customers",invoice:"Invoices",time_tracking:"Time Tracking",labor_cost:"Labor Costs"})[o]||o;return _?e.jsx(q,{children:e.jsxs(z,{className:"flex flex-row items-center gap-4",children:[e.jsx("div",{className:"flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10",children:e.jsx(V,{className:"h-6 w-6 text-primary"})}),e.jsxs("div",{children:[e.jsx(U,{className:"text-lg",children:"QuickBooks"}),e.jsx(W,{children:"Loading..."})]})]})}):e.jsxs(e.Fragment,{children:[e.jsxs(q,{children:[e.jsxs(z,{className:"flex flex-row items-center gap-4",children:[e.jsx("div",{className:"flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10",children:e.jsx(V,{className:"h-6 w-6 text-primary"})}),e.jsxs("div",{className:"flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(U,{className:"text-lg",children:"QuickBooks"}),r&&e.jsx(G,{variant:"default",children:"Connected"})]}),e.jsx(W,{children:r?`Connected to ${r.company_name||"QuickBooks"}`:"Sync customers, invoices, time tracking & labor costs"})]})]}),e.jsx(T,{className:"space-y-4",children:r?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"rounded-lg border p-3 space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(os,{className:"h-4 w-4 text-muted-foreground"}),e.jsx("span",{className:"text-sm font-medium",children:"Sync Health"})]}),e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:`h-2 w-2 rounded-full ${De}`}),e.jsx("span",{className:`text-xs font-medium ${H}`,children:F})]})]}),e.jsxs("div",{className:"grid grid-cols-3 gap-2 text-center",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-lg font-bold",children:d}),e.jsx("p",{className:"text-[10px] text-muted-foreground",children:"Pending"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-lg font-bold",children:i}),e.jsx("p",{className:"text-[10px] text-muted-foreground",children:"Errors (20)"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] text-muted-foreground mt-1",children:"Last Sync"}),e.jsx("p",{className:"text-[10px] text-muted-foreground font-medium",children:m?R(new Date(m.synced_at),{addSuffix:!0}):"Never"})]})]}),d>0&&e.jsxs(u,{variant:"outline",size:"sm",className:"w-full",onClick:Ie,disabled:n||j,children:[n?e.jsx(Z,{className:"mr-2 h-3 w-3 animate-spin"}):e.jsx(le,{className:"mr-2 h-3 w-3"}),"Process Queue (",d,")"]})]}),a&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[e.jsxs(u,{variant:"outline",size:"sm",onClick:()=>{B("push"),k(!0)},disabled:x,children:[e.jsx($,{className:"mr-2 h-4 w-4"}),"Push Customers"]}),e.jsxs(u,{variant:"outline",size:"sm",onClick:()=>{B("pull"),k(!0)},disabled:x,children:[e.jsx($,{className:"mr-2 h-4 w-4"}),"Pull Customers"]}),e.jsxs(u,{variant:"outline",size:"sm",onClick:Te,disabled:x,children:[e.jsx(J,{className:"mr-2 h-4 w-4"}),"Sync Time Entries"]}),e.jsxs(u,{variant:"outline",size:"sm",onClick:()=>E("pull"),disabled:x,children:[e.jsx(ue,{className:"mr-2 h-4 w-4"}),"Pull Estimates"]}),e.jsxs(u,{variant:"outline",size:"sm",onClick:()=>g("pull"),disabled:x,children:[e.jsx(X,{className:"mr-2 h-4 w-4"}),"Pull Items"]}),e.jsxs(u,{variant:"outline",size:"sm",onClick:()=>l(),disabled:x,children:[e.jsx($,{className:"mr-2 h-4 w-4"}),"Pull Vendors"]})]}),e.jsxs("div",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:[e.jsx(ue,{className:"h-4 w-4"}),e.jsx("span",{children:"Estimates & Invoices sync both ways"})]}),e.jsxs("div",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:[e.jsx(J,{className:"h-4 w-4"}),e.jsx("span",{children:"Time tracking syncs to Payroll"})]}),e.jsxs("div",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:[e.jsx(X,{className:"h-4 w-4"}),e.jsx("span",{children:"Items, Vendors & Labor costs sync"})]}),f.length>0&&e.jsxs(e.Fragment,{children:[e.jsx(he,{}),e.jsxs("div",{className:"space-y-2",children:[e.jsx("h4",{className:"text-sm font-medium",children:"Recent Syncs"}),f.slice(0,3).map(o=>e.jsxs("div",{className:"flex items-center justify-between text-sm",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[Oe
11(o.status),e.jsx("span",{children:Qe(o.sync_type)}),e.jsx(G,{variant:"outline",className:"text-xs",children:o.direction})]}),e.jsx("span",{className:"text-muted-foreground text-xs",children:R(new Date(o.started_at),{addSuffix:!0})})]},o.id))]})]})]}),e.jsx(he,{}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs(u,{variant:"ghost",size:"sm",onClick:()=>window.open("https://quickbooks.intuit.com","_blank"),children:[e.jsx(Pe,{className:"mr-2 h-4 w-4"}),"Open QuickBooks"]}),e.jsxs(u,{variant:"ghost",size:"sm",onClick:()=>s(!0),className:"text-destructive hover:text-destructive",children:[e.jsx(js,{className:"mr-2 h-4 w-4"}),"Disconnect"]})]})]}):e.jsxs(u,{onClick:C,className:"w-full",children:[e.jsx(V,{className:"mr-2 h-4 w-4"}),"Connect QuickBooks"]})})]}),e.jsx(ee,{open:y,onOpenChange:s,children:e.jsxs(se,{children:[e.jsxs(te,{children:[e.jsx(ne,{children:"Disconnect QuickBooks?"}),e.jsx(ae,{children:"This will disconnect your QuickBooks account. You can reconnect anytime, but synced data mappings will be preserved."})]}),e.jsxs(ie,{children:[e.jsx(u,{variant:"outline",onClick:()=>s(!1),children:"Cancel"}),e.jsx(u,{variant:"destructive",onClick:qe,children:"Disconnect"})]})]})}),e.jsx(ee,{open:c,onOpenChange:k,children:e.jsxs(se,{children:[e.jsxs(te,{children:[e.jsx(ne,{children:I==="push"?"Push Customers to QuickBooks":"Pull Customers from QuickBooks"}),e.jsx(ae,{children:I==="push"?"This will sync all your CREWOPS customers to QuickBooks. Existing customers will be updated.":"This will import customers from QuickBooks into CREWOPS. Existing customers will be updated."})]}),e.jsxs(ie,{children:[e.jsx(u,{variant:"outline",onClick:()=>k(!1),children:"Cancel"}),e.jsx(u,{onClick:()=>I&&Be(I),disabled:x,children:x?e.jsxs(e.Fragment,{children:[e.jsx(Z,{className:"mr-2 h-4 w-4 animate-spin"}),"Syncing..."]}):e.jsxs(e.Fragment,{children:[e.jsx(le,{className:"mr-2 h-4 w-4"}),"Start Sync"]})})]})]})})]})}const Ne=[{type:"customer",label:"Customers",qbLabel:"QB Customers",direction:"bidirectional",directions:["PUSH","PULL"]},{type:"invoice",label:"Invoices",qbLabel:"QB Invoices",direction:"bidirectional",directions:["PUSH","PULL"]},{type:"estimate",label:"Estimates",qbLabel:"QB Estimates",direction:"bidirectional",directions:["PUSH","PULL"]},{type:"payment",label:"Payments",qbLabel:"QB Payments",direction:"bidirectional",directions:["PUSH","PULL"]},{type:"time_activity",label:"Time Entries",qbLabel:"QB Time Activities",direction:"push",directions:["PUSH"]},{type:"vendor",label:"Vendors",qbLabel:"QB Vendors",direction:"pull",directions:["PULL"]},{type:"expense",label:"Expenses / Bills",qbLabel:"QB Purchases",direction:"pull",directions:["PULL"]},{type:"account",label:"Chart of Accounts",qbLabel:"QB Accounts",direction:"pull",directions:["PULL"]}];function ys(){const{connection:a}=_e(),{syncLogs:r,syncAll:f,syncing:_,isLoading:x}=Ae(),[C,L]=h.useState(!1),[S,p]=h.useState(()=>Object.fromEntries(Ne.map(t=>[t.type,!0])));if(!a)return null;const E=t=>t==="SUCCESS"?e.jsx(Ce,{className:"h-4 w-4 text-green-600"}):t==="FAILED"?e.jsx(de,{className:"h-4 w-4 text-destructive"}):e.jsx(J,{className:"h-4 w-4 text-amber-600"}),g=t=>{const j=t==="bidirectional"?"default":"outline",N=t==="bidirectional"?"↔ Bidirectional":t==="push"?"→ Push":"← Pull";return e.jsx(G,{variant:j,className:"text-[10px]",children:N})},l=t=>{const j=r.filter(c=>c.entity_type===t),N=j[0],y=j.filter(c=>c.status==="FAILED").length,s=j.filter(c=>c.status==="SUCCESS").length;return{lastSync:N,errorCount:y,successCount:s}};return e.jsxs("div",{className:"space-y-6",children:[e.jsx(q,{className:"border-blue-200 bg-blue-50/50 dark:border-blue-900 dark:bg-blue-950/30",children:e.jsxs(T,{className:"flex items-start gap-3 py-4",children:[e.jsx($e,{className:"h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5 shrink-0"}),e.jsxs("div",{children:[e.jsx("p",{className:"text-sm font-medium text-blue-900 dark:text-blue-200",children:"QuickBooks is the system of record for accounting"}),e.jsx("p",{className:"text-xs text-blue-700/80 dark:text-blue-300/70 mt-0.5",children:"CrewOps pushes operational data TO QuickBooks. QuickBooks is the source of truth for financial reporting."})]})]})}),e.jsxs(q,{children:[e.jsxs(z,{children:[e.jsxs(U,{className:"text-base flex items-center gap-2",children:[e.jsx(ds,{className:"h-4 w-4"})," Entity Mapping"]}),e.jsx(W,{children:"Configure which entities sync between CrewOps and QuickBooks"})]}),e.jsx(T,{children:e.jsxs(fe,{children:[e.jsx(ye,{children:e.jsxs(M,{children:[e.jsx(A,{children:"CrewOps Entity"}),e.jsx(A,{children:"QB Entity"}),e.jsx(A,{children:"Direction"}),e.jsx(A,{className:"text-center",children:"Last Synced"}),e.jsx(A,{className:"text-center",children:"Errors"}),e.jsx(A,{className:"text-center",children:"Enabled"}),e.jsx(A,{className:"text-right",children:"Actions"})]})}),e.jsx(be,{children:Ne.map(t=>{const j=l(t.type),N=S[t.type]??!0;return e.jsxs(M,{children:[e.jsx(D,{className:"font-medium",children:t.label}),e.jsx(D,{className:"text-muted-foreground",children:t.qbLabel}),e.jsx(D,{children:g(t.direction)}),e.jsx(D,{className:"text-center text-xs text-muted-foreground",children:j.lastSync?R(new Date(j.lastSync.synced_at),{addSuffix:!0}):"—"}),e.jsx(D,{className:"text-center",children:j.errorCount>0?e.jsx(G,{variant:"destructive",className:"text-[10px]",children:j.errorCount}):e.jsx("span",{className:"text-xs text-muted-foreground",children:"0"})}),e.jsx(D,{className:"text-center",children:e.jsx(Le,{checked:N,onCheckedChange:y=>p(s=>
11({...s,[t.type]:y}))})}),e.jsx(D,{className:"text-right",children:e.jsx("div",{className:"flex gap-1 justify-end",children:t.directions.map(y=>e.jsx(u,{variant:"outline",size:"sm",onClick:()=>f(t.type,y),disabled:_||!N,className:"h-7 text-xs",children:_?e.jsx(Z,{className:"h-3 w-3 animate-spin"}):y==="PUSH"?"→ Push":"← Pull"},y))})})]},t.type)})})]})})]}),e.jsx(Re,{open:C,onOpenChange:L,children:e.jsxs(q,{children:[e.jsx(Ze,{asChild:!0,children:e.jsx(z,{className:"cursor-pointer hover:bg-muted/30 transition-colors",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs(U,{className:"text-base flex items-center gap-2",children:[e.jsx(le,{className:"h-4 w-4"})," Sync History"]}),e.jsx(He,{className:`h-4 w-4 text-muted-foreground transition-transform ${C?"rotate-180":""}`})]})})}),e.jsx(Me,{children:e.jsx(T,{children:x?e.jsx("div",{className:"flex justify-center py-6",children:e.jsx(Z,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):r.length===0?e.jsx("p",{className:"text-sm text-muted-foreground text-center py-6",children:"No sync history yet."}):e.jsxs(fe,{children:[e.jsx(ye,{children:e.jsxs(M,{children:[e.jsx(A,{children:"Entity Type"}),e.jsx(A,{children:"Record ID"}),e.jsx(A,{children:"Direction"}),e.jsx(A,{children:"Status"}),e.jsx(A,{children:"Error"}),e.jsx(A,{children:"Time"})]})}),e.jsx(be,{children:r.slice(0,20).map(t=>e.jsxs(M,{children:[e.jsx(D,{className:"font-medium capitalize",children:t.entity_type}),e.jsx(D,{className:"text-xs text-muted-foreground font-mono max-w-[120px] truncate",children:t.entity_id==="00000000-0000-0000-0000-000000000000"?"Bulk":t.entity_id.slice(0,8)}),e.jsx(D,{children:e.jsx(G,{variant:"outline",className:"text-[10px]",children:t.direction})}),e.jsx(D,{children:e.jsxs("div",{className:"flex items-center gap-1",children:[E(t.status),e.jsx("span",{className:"text-xs",children:t.status})]})}),e.jsx(D,{className:"text-xs text-destructive max-w-[200px] truncate",children:t.error_message||"—"}),e.jsx(D,{className:"text-xs text-muted-foreground whitespace-nowrap",children:R(new Date(t.synced_at),{addSuffix:!0})})]},t.id))})]})})})]})})]})}const ve=[{key:"gmail",label:"Gmail",description:"Send estimates, invoices & follow-ups FROM your own email address",icon:e.jsx(Se,{className:"h-5 w-5 text-red-500"}),scopes:"gmail.send, gmail.readonly"},{key:"google_calendar",label:"Google Calendar",description:"Sync job appointments, site visits & crew schedules",icon:e.jsx(Ee,{className:"h-5 w-5 text-blue-500"}),scopes:"calendar.events, calendar.readonly"},{key:"google_drive",label:"Google Drive",description:"Store proposals, contracts & job photos in your Drive",icon:e.jsx(ms,{className:"h-5 w-5 text-amber-500"}),scopes:"drive.file, drive.readonly"},{key:"google_contacts",label:"Google Contacts",description:"Sync customers & leads with your Google Contacts",icon:e.jsx($,{className:"h-5 w-5 text-indigo-500"}),scopes:"contacts.readonly, contacts"},{key:"google_business",label:"Google Business Profile",description:"Manage reviews & business listing",icon:e.jsx(xs,{className:"h-5 w-5 text-orange-500"}),scopes:"business.manage"},{key:"google_maps",label:"Google Maps",description:"Powers route optimization & Customer Finder",icon:e.jsx(Ye,{className:"h-5 w-5 text-green-500"}),scopes:"maps.api",note:"Configured via API key in Platform Admin",isApiKey:!0}],ce="google_workspace_connection";function bs(){try{const a=localStorage.getItem(ce);if(a)return JSON.parse(a)}catch{}return null}function Ns(){const a=bs(),[r,f]=h.useState(!!a),[_,x]=h.useState((a==null?void 0:a.services)||{}),[C,L]=h.useState((a==null?void 0:a.email)||null),S=()=>{const l={gmail:!0,google_calendar:!0,google_drive:!0,google_contacts:!0,google_business:!0},t="[email protected]";f(!0),L(t),x(l),localStorage.setItem(ce,JSON.stringify({email:t,services:l})),v.success("Google Workspace connected — all services synced")},p=()=>{f(!1),L(null),x({}),localStorage.removeItem(ce),v.info("Google Workspace disconnected")},E=Object.values(_).filter(Boolean).length,g=ve.filter(l=>!l.isApiKey).length;return e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Google Worksp
11ace"}),e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:"Connect once to sync Gmail, Calendar, Drive, Contacts & more."})]}),r?e.jsxs(G,{variant:"default",className:"gap-1.5 py-1.5 px-3",children:[e.jsx(pe,{className:"h-3.5 w-3.5"}),E,"/",g," Services Active"]}):e.jsx(G,{variant:"outline",className:"text-muted-foreground",children:"Not Connected"})]}),e.jsx(q,{className:r?"border-green-500/30 bg-green-500/5":"border-primary/30 bg-primary/5",children:e.jsx(T,{className:"pt-6",children:r?e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"h-12 w-12 rounded-full bg-green-500/20 flex items-center justify-center",children:e.jsx(Ke,{className:"h-6 w-6 text-green-600"})}),e.jsxs("div",{children:[e.jsxs("p",{className:"font-bold text-sm",children:["Connected as ",C]}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"All outbound emails (estimates, invoices, follow-ups) will be sent from this address."})]})]}),e.jsxs(u,{variant:"outline",size:"sm",className:"text-destructive border-destructive/30 hover:bg-destructive/10",onClick:p,children:[e.jsx(Ve,{className:"mr-2 h-4 w-4"}),"Disconnect"]})]}):e.jsxs("div",{className:"flex flex-col sm:flex-row items-center gap-4",children:[e.jsxs("div",{className:"flex-1",children:[e.jsx("h3",{className:"font-bold text-base mb-1",children:"Connect Your Google Account"}),e.jsxs("p",{className:"text-sm text-muted-foreground",children:["One-click OAuth connects Gmail, Calendar, Drive & Contacts. Emails sent from CrewOps will come from ",e.jsx("strong",{children:"your business email"})," — not a generic noreply address."]})]}),e.jsxs(u,{size:"lg",className:"gap-2 font-bold shrink-0",onClick:S,children:[e.jsx(gs,{className:"h-5 w-5"}),"Connect Google Account"]})]})})}),e.jsx("div",{className:"grid gap-3 md:grid-cols-2 lg:grid-cols-3",children:ve.map(l=>{const t=l.isApiKey?!1:_[l.key]||!1;return e.jsxs(q,{className:t?"border-green-500/20":"",children:[e.jsxs(z,{className:"flex flex-row items-center gap-3 pb-2 pt-4 px-4",children:[e.jsx("div",{className:"flex h-10 w-10 items-center justify-center rounded-lg bg-muted shrink-0",children:l.icon}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(U,{className:"text-sm",children:l.label}),t?e.jsx(pe,{className:"h-4 w-4 text-green-600 shrink-0"}):l.isApiKey?e.jsx(G,{variant:"outline",className:"text-[10px] text-muted-foreground",children:"API Key"}):e.jsx(de,{className:"h-4 w-4 text-muted-foreground/40 shrink-0"})]}),e.jsx(W,{className:"text-xs mt-0.5",children:l.description})]})]}),e.jsx(T,{className:"px-4 pb-3 pt-0",children:l.isApiKey?e.jsx("p",{className:"text-[11px] text-muted-foreground italic",children:l.note}):t?e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-[11px] text-green-600 font-medium",children:"Syncing"}),e.jsxs("span",{className:"text-[10px] text-muted-foreground",children:["Scopes: ",l.scopes]})]}):e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Connect Google Account above to enable"})})]},l.key)})}),r&&e.jsx(q,{className:"bg-muted/30",children:e.jsx(T,{className:"pt-4 pb-4",children:e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(Se,{className:"h-5 w-5 text-primary mt-0.5 shrink-0"}),e.jsxs("div",{children:[e.jsx("p",{className:"text-sm font-bold",children:"Emails Now Send From Your Address"}),e.jsxs("p",{className:"text-xs text-muted-foreground mt-1",children:["All transactional emails — estimates, invoices, appointment reminders, follow-ups — will be sent from"," ",e.jsx("strong",{className:"text-foreground",children:C})," via the Gmail API. Your customers will see it coming from your real business email, not a system address."]})]})]})})})]})}const oe=["twilio","drop_cowboy","lob","melissa"],vs=us.filter(a=>oe.includes(a.id));function ks(){const{orgId:a}=we(),[r,f]=h.useState({}),[_,x]=h.useState({}),[C,L]=h.useState(new Set),[S,p]=h.useState({}),[E,g]=h.useState(null),[l,t]=h.useState(null),[j,N]=h.useState(!0),y=h.useCallback(async()=>
11{if(!a)return;N(!0);const[n,P]=await Promise.all([w.from("platform_api_keys").select("integration_id, field_key, field_value").eq("organization_id",a).in("integration_id",oe),w.from("platform_integration_status").select("integration_id, status").eq("organization_id",a).in("integration_id",oe)]);if(!n.error&&n.data){const b={},i={};n.data.forEach(d=>{b[d.field_key]=d.field_value,d.field_value&&(i[d.integration_id]=!0)}),f(b),x(b);const m={};Object.keys(i).forEach(d=>{m[d]="connected"}),!P.error&&P.data&&P.data.forEach(d=>{(d.status==="connected"||d.status==="error")&&(m[d.integration_id]=d.status)}),p(m)}N(!1)},[a]);h.useEffect(()=>{a&&y()},[a,y]);const s=n=>S[n.id]?S[n.id]:n.fields.some(b=>_[b.key])?"configured":"not_set",c=(n,P,b)=>{f(i=>({...i,[P]:b})),L(i=>new Set(i).add(n)),p(i=>{const m={...i};return delete m[n],m})},k=async n=>{if(!a)return;t(n.id);const P=n.fields.map(i=>({organization_id:a,integration_id:n.id,field_key:i.key,field_value:r[i.key]||""})),{error:b}=await w.from("platform_api_keys").upsert(P,{onConflict:"organization_id,integration_id,field_key"});if(b)v.error(`Failed to save ${n.name} keys`);else{const i={..._};n.fields.forEach(m=>{i[m.key]=r[m.key]||""}),x(i),L(m=>{const d=new Set(m);return d.delete(n.id),d}),p(m=>({...m,[n.id]:"connected"})),v.success(`${n.name} keys saved`)}t(null)},I=async n=>{if(!n.fields.every(b=>r[b.key])){v.error(`Please fill in all ${n.name} fields first`);return}g(n.id);try{const b={};n.fields.forEach(F=>{b[F.key]=r[F.key]||""});const{data:i,error:m}=await w.functions.invoke("test-api-key",{body:{integration_id:n.id,fields:b}});if(m)throw m;const d=(i==null?void 0:i.success)??!1,H=d?"connected":"error";p(F=>({...F,[n.id]:H})),a&&await w.from("platform_integration_status").upsert({organization_id:a,integration_id:n.id,status:H,tested_at:new Date().toISOString()},{onConflict:"organization_id,integration_id"}),d?v.success((i==null?void 0:i.message)||`${n.name} connected`):v.error((i==null?void 0:i.message)||`${n.name} connection failed`)}catch(b){p(i=>({...i,[n.id]:"error"})),v.error(`Test failed: ${b.message}`)}g(null)},B=async n=>{if(!a||!window.confirm(`Remove all saved ${n.name} keys?`))return;t(n.id);const{error:P}=await w.from("platform_api_keys").delete().eq("organization_id",a).eq("integration_id",n.id);if(P)v.error(`Failed to clear ${n.name} keys`);else{const b={...r},i={..._};n.fields.forEach(m=>{delete b[m.key],delete i[m.key]}),f(b),x(i),p(m=>{const d={...m};return delete d[n.id],d}),await w.from("platform_integration_status").delete().eq("organization_id",a).eq("integration_id",n.id),L(m=>{const d=new Set(m);return d.delete(n.id),d}),v.success(`${n.name} keys cleared`)}t(null)};return e.jsxs("div",{children:[e.jsxs("div",{className:"mb-4",children:[e.jsxs("h2",{className:"text-2xl font-bold tracking-tight flex items-center gap-2",children:[e.jsx(Je,{className:"h-6 w-6 text-primary"}),"Marketing & Communications APIs"]}),e.jsx("p",{className:"text-muted-foreground text-sm mt-1",children:"Connect your marketing service providers to enable SMS, voicemail, postcard, and lead data features."})]}),j?e.jsxs("div",{className:"flex items-center justify-center py-8",children:[e.jsx(Z,{className:"h-6 w-6 animate-spin text-muted-foreground"}),e.jsx("span",{className:"ml-2 text-muted-foreground",children:"Loading…"})]}):e.jsx("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-2",children:vs.map(n=>e.jsx(hs,{integration:n,values:r,status:s(n),isDirty:C.has(n.id),isSaving:l===n.id,isTesting:E===n.id,onFieldChange:c,onSave:k,onTest:I,onClear:B},n.id))})]})}const K=[{value:"google_calendar",label:"Google Calendar",icon:Ee,description:"Sync jobs and appointments"},{value:"google_contacts",label:"Google Contacts",icon:$,description:"Sync customer data"},{value:"markate",label:"Markate CRM",icon:X,description:"Sync jobs and estimates"},{value:"custom",label:"Custom Webhook",icon:re,description:"Any Zapier automation"}];function Gs(){const{webhooks:a,isLoading:r,createWebhook:f,updateWebhook:_,deleteWebhook:x,triggerWebhook:C}=ls(),[L,S]=h.useState(!1),[p,E]=h.useState({webhook_name:"",webhook_url:"",integration_type:""}),g=async()=>{!p.webhook_name||!p.webhook_url||!p.integration_type||(await f.mutateAsync(p),S(!1),E({webhook_name:"",webhook_url:"",integration_type:""}))},l=async(s,c)=>{await _.mutateAsync({id:s,is_active:!c})},t=async s=>{confirm("Are you sure you want to delete this webhook?")&&await x.mutateAsync(s)},j=async s=>{await C.mutateAsync({webhookId:s,payload:{test:!0,message:"Test from CREWOPS"}})},N=s=>{const c=K.find(I=>I.value===s),k=(c==null?void 0:c.icon)||re;return e.jsx(k,{className:"h-5 w-5"})},y=s=>{const c=K.find(k=>k.value===s);return(c==null?void 0:c.label)||s};return e.jsx(Xe,{title:"Integrations",children:e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight mb-4",children:"Accounting"}),e.jsx("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-3",children:e.jsx(fs,{})})]}),e.jsx(ys,{}),e.jsx(Ns,{}),e.jsx(ks,{}),e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight mb-4",children:"Operations & Field Comms"}),e.jsx("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-3",children:e.jsx(cs,{})})]}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Zapier Integrations"}),e.jsx("p",{className:"text-muted-foreground",children:"Connect CREWOPS to thousands of apps via Zapier webhooks"})]}),e.jsxs(ee,{open:L,onOpenChange:S,children:[e.jsx(es,{asChild:!0,children:e.jsxs(u,{children:[e.jsx(ge,{className:"mr-2 h-4 w-4"}),"Add Webhook"]})}),e.jsxs(se,{children:[e.jsxs(te,{children:[e.jsx(ne,{children:"Add Zapier Webhook"}),e.jsx(ae,{children:"Connect a Zapier webhook to sync data with external services"})]}),e.jsxs("div",{className:"space-y-4 py-4",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(Y,{htmlFor:"name",children:"Webhook Name"}),e.jsx(je,{id:"name",placeholder:"e.g., Sync to Google Calendar",value:p.webhook_name,onChange:s=>E(c=>({...c,webhook_name:s.target.value}))})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(Y,{htmlFor:"type",children:"Integration Type"}),e.jsxs(ss,{value:p.integration_type,onValueChange:s=>E(c=>({...c,integration_type:s})),children:[e.jsx(ts,{children:e.jsx(ns,{placeholder:"Select integration type"})}),e.jsx(as,{children:K.map(s=>e.jsx(is,{value:s.value,children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(s.icon,{className:"h-4 w-4"}),e.jsx("span",{children:s.label})]})},s.value))})]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(Y,{htmlFor:"url",children:"Zapier Webhook URL"}),e.jsx(je,{id:"url",placeholder:"https://hooks.zapier.com/...",value:p.webhook_url,onChange:s=>E(c=>({...c,webhook_url:s.target.value}))}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"Get this URL from your Zapier Zap's webhook trigger"})]})]}),e.jsxs(ie,{children:[e.jsx(u,{variant:"outline",onClick:()=>S(!1),children:"Cancel"}),e.jsx(u,{onClick:g,disabled:f.isPending,children:f.isPending?"Creating...":"Create Webhook"})]})]})]})]}),e.jsx("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-3",children:K.map(s=>e.jsxs(q,{className:"relative",children:[e.jsxs(z,{className:"flex flex-row items-center gap-4",children:[e.jsx("div",{className:"flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10",children:e.jsx(s.icon,{className:"h-6 w-6 text-primary"})}),e.jsxs("div",{children:[e.jsx(U,{className:"text-lg",children:s.label}),e.jsx(W,{children:s.description})]})]}),e.jsx(T,{children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("span",{className:"text-sm text-muted-foreground",children:[(a==null?void 0:a.filter(c=>c.integration_type===s.value).length)||0," webhook(s)"]}),e.jsxs(u,{variant:"outline",size:"sm",onClick:()=>{E(c=>({...c,integration_type:s.value})),S(!0)},children:[e.jsx(ge,{className:"mr-1 h-3 w-3"}),"Add"]})]})})]},s.value))}),e.jsxs(q,{children:[e.jsxs(z,{children:[e.jsx(U,{children:"Your Webhooks"}),e.jsx(W,{children:"Manage and test your configured Zapier webhooks"})]}),e.jsx(T,{children:r?e.jsx("div",{className:"flex items-center justify-center py-8",children:e.jsx("div",{className:"h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"})}):a!=null&&a.length?e.jsx("div",{className:"space-y-4",children:a.map(s=>e.jsxs("div",{className:"flex items-center justify-between rounded-lg border p-4",children:[e.jsxs("div",{className:"flex items-center gap-4",children:[e.jsx("div",{className:"flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10",children:N(s.integration_type)}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h4",{className:"font-medium",children:s.webhook_name}),e.jsx(G,{variant:s.is_active?"default":"secondary",children:s.is_active?"Active":"Inactive"})]}),e.jsxs("div",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:[e.jsx("span",{children:y(s.integration_type)}),s.last_triggered_at&&e.jsxs(e.Fragment,{children:[e.jsx("span",{children:"•"}),e.jsxs("span",{children:["Last triggered ",R(new Date(s.last_triggered_at),{addSuffix:!0})]})]})]})]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(Le,{checked:s.is_active,onCheckedChange:()=>l(s.id,s.is_active)}),e.jsx(u,{variant:"outline",size:"icon",onClick:()=>j(s.id),disabled:!s.is_active||C.isPending,children:e.jsx(ps,{className:"h-4 w-4"})}),e.jsx(u,{variant:"outline",size:"icon",onClick:()=>window.open(s.webhook_url,"_blank"),children:e.jsx(Pe,{className:"h-4 w-4"})}),e.jsx(u,{variant:"outline",size:"icon",onClick:()=>t(s.id),disabled:x.isPending,children:e.jsx(rs,{className:"h-4 w-4"})})]})]},s.id))}):e.jsxs("div",{className:"flex flex-col items-center justify-center py-8 text-center",children:[e.jsx(re,{className:"mb-4 h-12 w-12 text-muted-foreground"}),e.jsx("h3",{className:"text-lg font-semibold",children:"No webhooks configured"}),e.jsx("p",{className:"text-muted-foreground",children:"Add a Zapier webhook to start syncing data with external services"})]})})]}),e.jsxs(q,{children:[e.jsx(z,{children:e.jsx(U,{children:"How to Set Up a Zapier Webhook"})}),e.jsx(T,{className:"space-y-4",children:e.jsxs("ol",{className:"list-decimal list-inside space-y-2 text-sm text-muted-foreground",children:[e.jsxs("li",{children:["Go to ",e.jsx("a",{href:"https://zapier.com",target:"_blank",rel:"noopener noreferrer",className:"text-primary underline",children:"zapier.com"})," and create a new Zap"]}),e.jsx("li",{children:'Choose "Webhooks by Zapier" as your trigger'}),e.jsx("li",{children:'Select "Catch Hook" as the trigger event'}),e.jsx("li",{children:"Copy the webhook URL Zapier provides"}),e.jsx("li",{children:"Paste the URL here and add your webhook"}),e.jsx("li",{children:"Configure the action in Zapier (e.g., create Google Calendar event)"}),e.jsx("li",{children:"Test the integration using the play button"})]})})]})]})})}export{Gs as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.