@accounter/client
Version:
Accounter client application
1 lines • 15.8 kB
JavaScript
import{s as e}from"./dist-ypP48_Ax.js";import{i as t,o as n}from"./utils-CbS-oR5I.js";import{r,t as i}from"./error-handling-CKe_iwSC.js";import{c as a,t as o,y as s}from"./routes-Cawu7UJi.js";import{n as c,t as l}from"./loader-circle-CfW9jOIr.js";import{H as u,Lr as ee,a as d,i as f,o as te,rr as ne,s as p,ur as m}from"./common-D-vYt_Yl.js";import{a as h,c as g,d as _,f as v,i as y,l as b,m as re,n as x,p as S,r as C,s as w,t as T,u as E}from"./select-Zdatwv1b.js";import{y as D}from"./separator-BvKylNhK.js";import{t as ie}from"./key-round-CvtIluGg.js";import{t as O}from"./button-DUNsVBiu.js";import{$c as ae,Bc as k,Gc as A,Hc as j,Kc as M,Ln as N,Vc as oe,Wc as P,Xc as F,Xn as se,a as I,en as ce,f as L,i as R,jt as z,n as B,o as V,qc as H,r as U,t as W}from"./table-AJA7Uyxy.js";import{$r as le,Bi as ue,Cr as de,Gi as fe,Ki as pe,Qo as G,Zo as K,ei as me,jn as he,ti as ge}from"./graphql-DC5GYCFE.js";import{n as q,r as J,t as Y}from"./alert-55QsYEfR.js";var _e=c(`shield-alert`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`M12 8v4`,key:`1got3b`}],[`path`,{d:`M12 16h.01`,key:`1drbdi`}]]),ve=c(`user-plus`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}],[`line`,{x1:`19`,x2:`19`,y1:`8`,y2:`14`,key:`1bvyxn`}],[`line`,{x1:`22`,x2:`16`,y1:`11`,y2:`11`,key:`1shjgl`}]]),X=e(n(),1),ye=`generateApiKey`,be=()=>{let[{fetching:e,error:t},n]=K(de);return{fetching:e,error:t,generateApiKey:(0,X.useCallback)(async({name:e,roleId:t})=>{let a=`Error generating API key`,o=ye;r.loading(`Generating API key`,{id:o});try{let s=await n({name:e,roleId:t}),c=i(s,a,o);if(c)return r.success(`Success`,{id:o,description:`API key generated successfully`}),c.generateApiKey}catch(e){console.error(a,e),r.error(`Error`,{id:o,description:a,duration:1e4,closeButton:!0})}},[n])}},xe=`revokeApiKey`,Se=()=>{let[{fetching:e,error:t},n]=K(fe);return{fetching:e,error:t,revokeApiKey:(0,X.useCallback)(async e=>{let t=`Error revoking API key`,a=xe;r.loading(`Revoking API key`,{id:a});try{let o=await n({id:e}),s=i(o,t,a);if(s)return s.revokeApiKey?(r.success(`Success`,{id:a,description:`API key revoked successfully`}),!0):(r.error(`Error`,{id:a,description:`Failed to revoke API key. It may have already been revoked.`}),!1)}catch(e){console.error(t,e),r.error(`Error`,{id:a,description:t,duration:1e4,closeButton:!0})}},[n])}},Z=t(),Q=[{value:`scraper`,label:`Scraper`},{value:`gmail_listener`,label:`Gmail Listener`}],Ce=N({name:se().trim().min(1,`Name is required`),roleId:z([`scraper`,`gmail_listener`])});function we(){let[{data:e,fetching:t,error:n},r]=G({query:le}),{revokeApiKey:i,fetching:a}=Se(),o=(0,X.useCallback)(()=>r({requestPolicy:`network-only`}),[r]),s=(0,X.useCallback)(async e=>{await i(e)!==void 0&&o()},[i,o]),c=e?.listApiKeys??[];return(0,Z.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,Z.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,Z.jsxs)(`div`,{children:[(0,Z.jsx)(`h2`,{className:`text-lg font-medium`,children:`API Keys`}),(0,Z.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Programmatic access keys for automated roles.`})]}),(0,Z.jsx)(Te,{onCreated:o})]}),n?(0,Z.jsxs)(Y,{variant:`destructive`,children:[(0,Z.jsx)(J,{children:`Failed to load API keys`}),(0,Z.jsx)(q,{children:n.message})]}):t?(0,Z.jsx)(`div`,{className:`flex justify-center p-8`,children:(0,Z.jsx)(l,{className:`h-8 w-8 animate-spin`})}):c.length===0?(0,Z.jsx)(`p`,{className:`p-6 text-center text-sm text-muted-foreground`,children:`No API keys yet.`}):(0,Z.jsxs)(W,{children:[(0,Z.jsx)(I,{children:(0,Z.jsxs)(V,{children:[(0,Z.jsx)(R,{children:`Name`}),(0,Z.jsx)(R,{children:`Role`}),(0,Z.jsx)(R,{children:`Last Used`}),(0,Z.jsx)(R,{children:`Created`}),(0,Z.jsx)(R,{className:`text-right`,children:`Actions`})]})}),(0,Z.jsx)(B,{children:c.map(e=>(0,Z.jsxs)(V,{children:[(0,Z.jsx)(U,{className:`font-medium`,children:e.name}),(0,Z.jsx)(U,{children:Q.find(t=>t.value===e.roleId)?.label??e.roleId}),(0,Z.jsx)(U,{children:e.lastUsedAt?new Date(e.lastUsedAt).toLocaleString():`Never`}),(0,Z.jsx)(U,{children:new Date(e.createdAt).toLocaleDateString()}),(0,Z.jsx)(U,{className:`text-right`,children:(0,Z.jsx)(u,{onConfirm:()=>s(e.id),title:`Revoke "${e.name}"? This cannot be undone and any client using it will lose access.`,children:(0,Z.jsxs)(O,{variant:`destructive`,size:`sm`,disabled:a,children:[(0,Z.jsx)(D,{className:`size-4`}),`Revoke`]})})})]},e.id))})]})]})}function Te({onCreated:e}){let[t,n]=(0,X.useState)(!1),[i,a]=(0,X.useState)(null),{generateApiKey:o,fetching:s}=be(),c=F({resolver:L(Ce),defaultValues:{name:``,roleId:`scraper`}}),u=(0,X.useCallback)(async t=>{let n=await o(t);n?.apiKey&&(a(n.apiKey),e())},[o,e]),d=(0,X.useCallback)(e=>{n(e),e||(a(null),c.reset())},[c]),f=(0,X.useCallback)(()=>{i&&(ne(i),r.success(`Copied`,{description:`API key copied to clipboard`}))},[i]);return(0,Z.jsxs)(g,{open:t,onOpenChange:d,children:[(0,Z.jsx)(re,{asChild:!0,children:(0,Z.jsxs)(O,{children:[(0,Z.jsx)(ie,{className:`size-4`}),`Generate New Key`]})}),(0,Z.jsx)(b,{onPointerDownOutside:e=>{i&&e.preventDefault()},onEscapeKeyDown:e=>{i&&e.preventDefault()},children:i?(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsxs)(v,{children:[(0,Z.jsx)(S,{children:`API Key Created`}),(0,Z.jsx)(E,{children:`Copy your key now — it will not be shown again.`})]}),(0,Z.jsxs)(Y,{variant:`destructive`,children:[(0,Z.jsx)(_e,{className:`size-4`}),(0,Z.jsx)(J,{children:`Save this key immediately`}),(0,Z.jsx)(q,{children:`For security, the plaintext key is shown only once and cannot be retrieved later.`})]}),(0,Z.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,Z.jsx)(w,{readOnly:!0,value:i,className:`font-mono text-xs`}),(0,Z.jsxs)(O,{type:`button`,variant:`outline`,size:`icon`,onClick:f,children:[(0,Z.jsx)(ee,{className:`size-4`}),(0,Z.jsx)(`span`,{className:`sr-only`,children:`Copy API key`})]})]}),(0,Z.jsx)(_,{children:(0,Z.jsx)(O,{type:`button`,onClick:()=>d(!1),children:`Done`})})]}):(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsxs)(v,{children:[(0,Z.jsx)(S,{children:`Generate New API Key`}),(0,Z.jsx)(E,{children:`Create a key for programmatic access. Choose a descriptive name and a role.`})]}),(0,Z.jsx)(oe,{...c,children:(0,Z.jsxs)(`form`,{onSubmit:c.handleSubmit(u),className:`flex flex-col gap-4`,children:[(0,Z.jsx)(P,{control:c.control,name:`name`,render:({field:e})=>(0,Z.jsxs)(A,{children:[(0,Z.jsx)(M,{children:`Name`}),(0,Z.jsx)(j,{children:(0,Z.jsx)(w,{placeholder:`e.g. Production Scraper`,...e})}),(0,Z.jsx)(H,{})]})}),(0,Z.jsx)(P,{control:c.control,name:`roleId`,render:({field:e})=>(0,Z.jsxs)(A,{children:[(0,Z.jsx)(M,{children:`Role`}),(0,Z.jsxs)(T,{onValueChange:e.onChange,value:e.value,children:[(0,Z.jsx)(j,{children:(0,Z.jsx)(y,{children:(0,Z.jsx)(h,{placeholder:`Select a role`})})}),(0,Z.jsx)(x,{children:Q.map(e=>(0,Z.jsx)(C,{value:e.value,children:e.label},e.value))})]}),(0,Z.jsx)(H,{})]})}),(0,Z.jsx)(_,{children:(0,Z.jsxs)(O,{type:`submit`,disabled:s,children:[s&&(0,Z.jsx)(l,{className:`size-4 animate-spin`}),`Generate`]})})]})})]})})]})}var Ee=`removeBusinessUser`,De=()=>{let[{fetching:e,error:t},n]=K(ue);return{fetching:e,error:t,removeBusinessUser:(0,X.useCallback)(async e=>{let t=`Error removing user`,a=Ee;r.loading(`Removing user`,{id:a});try{let o=await n({userId:e}),s=i(o,t,a);if(s)return s.removeBusinessUser?(r.success(`Success`,{id:a,description:`User removed successfully`}),!0):(r.error(`Error`,{id:a,description:`Failed to remove user. They may have already been removed.`}),!1)}catch(e){console.error(t,e),r.error(`Error`,{id:a,description:t,duration:1e4,closeButton:!0})}},[n])}},Oe={business_owner:`Business Owner`,accountant:`Accountant`,employee:`Employee`,scraper:`Scraper`,gmail_listener:`Gmail Listener`},ke=e=>Oe[e]??e;function Ae(){let[{data:e,fetching:t,error:n},r]=G({query:me}),{removeBusinessUser:i,fetching:a}=De(),o=(0,X.useCallback)(()=>r({requestPolicy:`network-only`}),[r]),s=(0,X.useCallback)(async e=>{await i(e)!==void 0&&o()},[i,o]),c=e?.listBusinessUsers??[];return(0,Z.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,Z.jsxs)(`div`,{children:[(0,Z.jsx)(`h2`,{className:`text-lg font-medium`,children:`Users`}),(0,Z.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`People with access to this business.`})]}),n?(0,Z.jsxs)(Y,{variant:`destructive`,children:[(0,Z.jsx)(J,{children:`Failed to load users`}),(0,Z.jsx)(q,{children:n.message})]}):t?(0,Z.jsx)(je,{}):c.length===0?(0,Z.jsx)(`p`,{className:`p-6 text-center text-sm text-muted-foreground`,children:`No users found.`}):(0,Z.jsxs)(W,{children:[(0,Z.jsx)(I,{children:(0,Z.jsxs)(V,{children:[(0,Z.jsx)(R,{children:`Name`}),(0,Z.jsx)(R,{children:`Email`}),(0,Z.jsx)(R,{children:`Role`}),(0,Z.jsx)(R,{className:`text-right`,children:`Actions`})]})}),(0,Z.jsx)(B,{children:c.map(e=>(0,Z.jsxs)(V,{children:[(0,Z.jsx)(U,{className:`font-medium`,children:e.name?.trim()||(0,Z.jsx)(`span`,{className:`text-muted-foreground`,children:`—`})}),(0,Z.jsx)(U,{children:e.email}),(0,Z.jsx)(U,{children:(0,Z.jsx)(k,{variant:`secondary`,children:ke(e.roleId)})}),(0,Z.jsx)(U,{className:`text-right`,children:(0,Z.jsx)(u,{onConfirm:()=>s(e.id),title:`Remove ${e.name?.trim()?`${e.name.trim()} (${e.email})`:e.email} from this business? They will lose access immediately.`,children:(0,Z.jsxs)(O,{variant:`destructive`,size:`sm`,disabled:a,children:[(0,Z.jsx)(D,{className:`size-4`}),`Remove User`]})})})]},e.id))})]})]})}function je(){return(0,Z.jsxs)(W,{children:[(0,Z.jsx)(I,{children:(0,Z.jsxs)(V,{children:[(0,Z.jsx)(R,{children:`Name`}),(0,Z.jsx)(R,{children:`Email`}),(0,Z.jsx)(R,{children:`Role`}),(0,Z.jsx)(R,{className:`text-right`,children:`Actions`})]})}),(0,Z.jsx)(B,{children:Array.from({length:4}).map((e,t)=>(0,Z.jsxs)(V,{children:[(0,Z.jsx)(U,{children:(0,Z.jsx)(m,{className:`h-4 w-32`})}),(0,Z.jsx)(U,{children:(0,Z.jsx)(m,{className:`h-4 w-48`})}),(0,Z.jsx)(U,{children:(0,Z.jsx)(m,{className:`h-6 w-24`})}),(0,Z.jsx)(U,{className:`text-right`,children:(0,Z.jsx)(m,{className:`ml-auto h-8 w-28`})})]},t))})]})}var Me=`createInvitation`,Ne=()=>{let[{fetching:e,error:t},n]=K(he);return{fetching:e,error:t,createInvitation:(0,X.useCallback)(async({email:e,roleId:t})=>{let a=`Error creating invitation`,o=Me;r.loading(`Creating invitation`,{id:o});try{let s=await n({email:e,roleId:t}),c=i(s,a,o);if(c)return r.success(`Success`,{id:o,description:`Invitation sent successfully`}),c.createInvitation}catch(e){console.error(a,e),r.error(`Error`,{id:o,description:e instanceof Error?e.message:a,duration:1e4,closeButton:!0})}},[n])}},Pe=`revokeInvitation`,Fe=()=>{let[{fetching:e,error:t},n]=K(pe);return{fetching:e,error:t,revokeInvitation:(0,X.useCallback)(async e=>{let t=`Error revoking invitation`,a=Pe;r.loading(`Revoking invitation`,{id:a});try{let o=await n({id:e}),s=i(o,t,a);if(s)return s.revokeInvitation?(r.success(`Success`,{id:a,description:`Invitation revoked successfully`}),!0):(r.error(`Error`,{id:a,description:`Failed to revoke invitation. It may have already been revoked.`}),!1)}catch(e){console.error(t,e),r.error(`Error`,{id:a,description:t,duration:1e4,closeButton:!0})}},[n])}},$={business_owner:`Business Owner`,accountant:`Accountant`,employee:`Employee`,scraper:`Scraper`,gmail_listener:`Gmail Listener`},Ie=[{value:`business_owner`,label:$.business_owner},{value:`accountant`,label:$.accountant},{value:`employee`,label:$.employee}],Le=e=>$[e]??e,Re=N({email:ce(`Please enter a valid email address`),roleId:z([`business_owner`,`accountant`,`employee`])});function ze(){let[{data:e,fetching:t,error:n},r]=G({query:ge}),{revokeInvitation:i,fetching:a}=Fe(),o=(0,X.useCallback)(()=>r({requestPolicy:`network-only`}),[r]),s=(0,X.useCallback)(async e=>{await i(e)!==void 0&&o()},[i,o]),c=e?.listInvitations??[];return(0,Z.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,Z.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,Z.jsxs)(`div`,{children:[(0,Z.jsx)(`h2`,{className:`text-lg font-medium`,children:`Invitations`}),(0,Z.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pending invitations to join this business.`})]}),(0,Z.jsx)(Be,{onInvited:o})]}),n?(0,Z.jsxs)(Y,{variant:`destructive`,children:[(0,Z.jsx)(J,{children:`Failed to load invitations`}),(0,Z.jsx)(q,{children:n.message})]}):t?(0,Z.jsx)(`div`,{className:`flex justify-center p-8`,children:(0,Z.jsx)(l,{className:`h-8 w-8 animate-spin`})}):c.length===0?(0,Z.jsx)(`p`,{className:`p-6 text-center text-sm text-muted-foreground`,children:`No pending invitations.`}):(0,Z.jsxs)(W,{children:[(0,Z.jsx)(I,{children:(0,Z.jsxs)(V,{children:[(0,Z.jsx)(R,{children:`Email`}),(0,Z.jsx)(R,{children:`Role`}),(0,Z.jsx)(R,{children:`Expires`}),(0,Z.jsx)(R,{className:`text-right`,children:`Actions`})]})}),(0,Z.jsx)(B,{children:c.map(e=>(0,Z.jsxs)(V,{children:[(0,Z.jsx)(U,{className:`font-medium`,children:e.email}),(0,Z.jsx)(U,{children:(0,Z.jsx)(k,{variant:`secondary`,children:Le(e.roleId)})}),(0,Z.jsx)(U,{children:new Date(e.expiresAt).toLocaleDateString()}),(0,Z.jsx)(U,{className:`text-right`,children:(0,Z.jsx)(u,{onConfirm:()=>s(e.id),title:`Revoke the invitation for "${e.email}"? They will no longer be able to accept it.`,children:(0,Z.jsxs)(O,{variant:`destructive`,size:`sm`,disabled:a,children:[(0,Z.jsx)(D,{className:`size-4`}),`Revoke`]})})})]},e.id))})]})]})}function Be({onInvited:e}){let[t,n]=(0,X.useState)(!1),{createInvitation:r,fetching:i}=Ne(),a=F({resolver:L(Re),defaultValues:{email:``,roleId:`employee`}}),o=(0,X.useCallback)(async t=>{await r(t)&&(e(),n(!1),a.reset())},[r,e,a]),s=(0,X.useCallback)(e=>{n(e),e||a.reset()},[a]);return(0,Z.jsxs)(g,{open:t,onOpenChange:s,children:[(0,Z.jsx)(re,{asChild:!0,children:(0,Z.jsxs)(O,{children:[(0,Z.jsx)(ve,{className:`size-4`}),`Invite User`]})}),(0,Z.jsxs)(b,{children:[(0,Z.jsxs)(v,{children:[(0,Z.jsx)(S,{children:`Invite User`}),(0,Z.jsx)(E,{children:`Send an invitation to join this business with the selected role.`})]}),(0,Z.jsx)(oe,{...a,children:(0,Z.jsxs)(`form`,{onSubmit:a.handleSubmit(o),className:`flex flex-col gap-4`,children:[(0,Z.jsx)(P,{control:a.control,name:`email`,render:({field:e})=>(0,Z.jsxs)(A,{children:[(0,Z.jsx)(M,{children:`Email`}),(0,Z.jsx)(j,{children:(0,Z.jsx)(w,{type:`email`,placeholder:`user@example.com`,...e})}),(0,Z.jsx)(H,{})]})}),(0,Z.jsx)(P,{control:a.control,name:`roleId`,render:({field:e})=>(0,Z.jsxs)(A,{children:[(0,Z.jsx)(M,{children:`Role`}),(0,Z.jsxs)(T,{onValueChange:e.onChange,value:e.value,children:[(0,Z.jsx)(j,{children:(0,Z.jsx)(y,{children:(0,Z.jsx)(h,{placeholder:`Select a role`})})}),(0,Z.jsx)(x,{children:Ie.map(e=>(0,Z.jsx)(C,{value:e.value,children:e.label},e.value))})]}),(0,Z.jsx)(H,{})]})}),(0,Z.jsx)(_,{children:(0,Z.jsxs)(O,{type:`submit`,disabled:i,children:[i&&(0,Z.jsx)(l,{className:`size-4 animate-spin`}),`Send Invitation`]})})]})})]})]})}function Ve(){let{businessId:e}=s(),{userContext:t}=(0,X.useContext)(ae),n=t?.context.adminBusinessId;return!n||n!==e?(0,Z.jsx)(a,{to:o.HOME,replace:!0}):(0,Z.jsxs)(`div`,{className:`flex flex-col gap-4 p-4`,children:[(0,Z.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Access Management`}),(0,Z.jsxs)(f,{defaultValue:`api-keys`,className:`w-full`,children:[(0,Z.jsxs)(te,{children:[(0,Z.jsx)(p,{value:`api-keys`,children:`API Keys`}),(0,Z.jsx)(p,{value:`invitations`,children:`Invitations`}),(0,Z.jsx)(p,{value:`users`,children:`Users`})]}),(0,Z.jsx)(d,{value:`api-keys`,children:(0,Z.jsx)(we,{})}),(0,Z.jsx)(d,{value:`invitations`,children:(0,Z.jsx)(ze,{})}),(0,Z.jsx)(d,{value:`users`,children:(0,Z.jsx)(Ae,{})})]})]})}export{Ve as AuthManagement};