@accounter/client
Version:
Accounter client application
1 lines • 15.6 kB
JavaScript
import{s as e}from"./dist-C51EwTaa.js";import{i as t,o as n}from"./utils-DdVdMk8X.js";import{Fi as r,Fn as i,Io as a,Lo as o,Pi as s,ei as c,ki as l,ni as u,r as d,t as f,ti as ee,wr as te}from"./error-handling-Dpfd9svJ.js";import{c as ne,t as re,y as ie}from"./routes-DUvpW9-n.js";import{_t as p,a as m,c as h,d as g,f as _,ft as v,i as y,l as b,m as x,n as S,p as C,r as w,s as T,t as E,u as D}from"./select-CrsvQyhZ.js";import{t as O}from"./button-Dzp2INW0.js";import{B as k,C as ae,D as A,F as j,K as oe,L as M,N as se,P as ce,R as N,U as le,a as P,h as F,i as I,k as ue,n as L,o as R,r as z,t as B,v as V,z as H}from"./table--bF0wbxq.js";import{D as U,Dr as de,E as fe,Nr as W,O as pe,Yr as G,ci as me,k as K,ot as q,vi as he}from"./index-CL8-JoMZ.js";import{n as J,r as Y,t as X}from"./alert-D_jNoG9N.js";var ge=p(`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`}]]),_e=p(`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`}]]),Z=e(n(),1),ve=`generateApiKey`,ye=()=>{let[{fetching:e,error:t},n]=a(te);return{fetching:e,error:t,generateApiKey:(0,Z.useCallback)(async({name:e,roleId:t})=>{let r=`Error generating API key`,i=ve;d.loading(`Generating API key`,{id:i});try{let a=f(await n({name:e,roleId:t}),r,i);if(a)return d.success(`Success`,{id:i,description:`API key generated successfully`}),a.generateApiKey}catch(e){console.error(r,e),d.error(`Error`,{id:i,description:r,duration:1e4,closeButton:!0})}},[n])}},be=`revokeApiKey`,xe=()=>{let[{fetching:e,error:t},n]=a(s);return{fetching:e,error:t,revokeApiKey:(0,Z.useCallback)(async e=>{let t=`Error revoking API key`,r=be;d.loading(`Revoking API key`,{id:r});try{let i=f(await n({id:e}),t,r);if(i)return i.revokeApiKey?(d.success(`Success`,{id:r,description:`API key revoked successfully`}),!0):(d.error(`Error`,{id:r,description:`Failed to revoke API key. It may have already been revoked.`}),!1)}catch(e){console.error(t,e),d.error(`Error`,{id:r,description:t,duration:1e4,closeButton:!0})}},[n])}},Q=t(),Se=[{value:`scraper`,label:`Scraper`},{value:`gmail_listener`,label:`Gmail Listener`}],Ce=A({name:ue().trim().min(1,`Name is required`),roleId:V([`scraper`,`gmail_listener`])});function we(){let[{data:e,fetching:t,error:n},r]=o({query:c}),{revokeApiKey:i,fetching:a}=xe(),s=(0,Z.useCallback)(()=>r({requestPolicy:`network-only`}),[r]),l=(0,Z.useCallback)(async e=>{await i(e)!==void 0&&s()},[i,s]),u=e?.listApiKeys??[];return(0,Q.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,Q.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,Q.jsxs)(`div`,{children:[(0,Q.jsx)(`h2`,{className:`text-lg font-medium`,children:`API Keys`}),(0,Q.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Programmatic access keys for automated roles.`})]}),(0,Q.jsx)(Te,{onCreated:s})]}),n?(0,Q.jsxs)(X,{variant:`destructive`,children:[(0,Q.jsx)(Y,{children:`Failed to load API keys`}),(0,Q.jsx)(J,{children:n.message})]}):t?(0,Q.jsx)(`div`,{className:`flex justify-center p-8`,children:(0,Q.jsx)(v,{className:`h-8 w-8 animate-spin`})}):u.length===0?(0,Q.jsx)(`p`,{className:`p-6 text-center text-sm text-muted-foreground`,children:`No API keys yet.`}):(0,Q.jsxs)(B,{children:[(0,Q.jsx)(P,{children:(0,Q.jsxs)(R,{children:[(0,Q.jsx)(I,{children:`Name`}),(0,Q.jsx)(I,{children:`Role`}),(0,Q.jsx)(I,{children:`Last Used`}),(0,Q.jsx)(I,{children:`Created`}),(0,Q.jsx)(I,{className:`text-right`,children:`Actions`})]})}),(0,Q.jsx)(L,{children:u.map(e=>(0,Q.jsxs)(R,{children:[(0,Q.jsx)(z,{className:`font-medium`,children:e.name}),(0,Q.jsx)(z,{children:Se.find(t=>t.value===e.roleId)?.label??e.roleId}),(0,Q.jsx)(z,{children:e.lastUsedAt?new Date(e.lastUsedAt).toLocaleString():`Never`}),(0,Q.jsx)(z,{children:new Date(e.createdAt).toLocaleDateString()}),(0,Q.jsx)(z,{className:`text-right`,children:(0,Q.jsx)(q,{onConfirm:()=>l(e.id),title:`Revoke "${e.name}"? This cannot be undone and any client using it will lose access.`,children:(0,Q.jsxs)(O,{variant:`destructive`,size:`sm`,disabled:a,children:[(0,Q.jsx)(G,{className:`size-4`}),`Revoke`]})})})]},e.id))})]})]})}function Te({onCreated:e}){let[t,n]=(0,Z.useState)(!1),[r,i]=(0,Z.useState)(null),{generateApiKey:a,fetching:o}=ye(),s=le({resolver:F(Ce),defaultValues:{name:``,roleId:`scraper`}}),c=(0,Z.useCallback)(async t=>{let n=await a(t);n?.apiKey&&(i(n.apiKey),e())},[a,e]),l=(0,Z.useCallback)(e=>{n(e),e||(i(null),s.reset())},[s]),u=(0,Z.useCallback)(()=>{r&&(de(r),d.success(`Copied`,{description:`API key copied to clipboard`}))},[r]);return(0,Q.jsxs)(h,{open:t,onOpenChange:l,children:[(0,Q.jsx)(x,{asChild:!0,children:(0,Q.jsxs)(O,{children:[(0,Q.jsx)(me,{className:`size-4`}),`Generate New Key`]})}),(0,Q.jsx)(b,{onPointerDownOutside:e=>{r&&e.preventDefault()},onEscapeKeyDown:e=>{r&&e.preventDefault()},children:r?(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(_,{children:[(0,Q.jsx)(C,{children:`API Key Created`}),(0,Q.jsx)(D,{children:`Copy your key now — it will not be shown again.`})]}),(0,Q.jsxs)(X,{variant:`destructive`,children:[(0,Q.jsx)(ge,{className:`size-4`}),(0,Q.jsx)(Y,{children:`Save this key immediately`}),(0,Q.jsx)(J,{children:`For security, the plaintext key is shown only once and cannot be retrieved later.`})]}),(0,Q.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,Q.jsx)(T,{readOnly:!0,value:r,className:`font-mono text-xs`}),(0,Q.jsxs)(O,{type:`button`,variant:`outline`,size:`icon`,onClick:u,children:[(0,Q.jsx)(he,{className:`size-4`}),(0,Q.jsx)(`span`,{className:`sr-only`,children:`Copy API key`})]})]}),(0,Q.jsx)(g,{children:(0,Q.jsx)(O,{type:`button`,onClick:()=>l(!1),children:`Done`})})]}):(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(_,{children:[(0,Q.jsx)(C,{children:`Generate New API Key`}),(0,Q.jsx)(D,{children:`Create a key for programmatic access. Choose a descriptive name and a role.`})]}),(0,Q.jsx)(ce,{...s,children:(0,Q.jsxs)(`form`,{onSubmit:s.handleSubmit(c),className:`flex flex-col gap-4`,children:[(0,Q.jsx)(M,{control:s.control,name:`name`,render:({field:e})=>(0,Q.jsxs)(N,{children:[(0,Q.jsx)(H,{children:`Name`}),(0,Q.jsx)(j,{children:(0,Q.jsx)(T,{placeholder:`e.g. Production Scraper`,...e})}),(0,Q.jsx)(k,{})]})}),(0,Q.jsx)(M,{control:s.control,name:`roleId`,render:({field:e})=>(0,Q.jsxs)(N,{children:[(0,Q.jsx)(H,{children:`Role`}),(0,Q.jsxs)(E,{onValueChange:e.onChange,value:e.value,children:[(0,Q.jsx)(j,{children:(0,Q.jsx)(y,{children:(0,Q.jsx)(m,{placeholder:`Select a role`})})}),(0,Q.jsx)(S,{children:Se.map(e=>(0,Q.jsx)(w,{value:e.value,children:e.label},e.value))})]}),(0,Q.jsx)(k,{})]})}),(0,Q.jsx)(g,{children:(0,Q.jsxs)(O,{type:`submit`,disabled:o,children:[o&&(0,Q.jsx)(v,{className:`size-4 animate-spin`}),`Generate`]})})]})})]})})]})}var Ee=`removeBusinessUser`,De=()=>{let[{fetching:e,error:t},n]=a(l);return{fetching:e,error:t,removeBusinessUser:(0,Z.useCallback)(async e=>{let t=`Error removing user`,r=Ee;d.loading(`Removing user`,{id:r});try{let i=f(await n({userId:e}),t,r);if(i)return i.removeBusinessUser?(d.success(`Success`,{id:r,description:`User removed successfully`}),!0):(d.error(`Error`,{id:r,description:`Failed to remove user. They may have already been removed.`}),!1)}catch(e){console.error(t,e),d.error(`Error`,{id:r,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]=o({query:ee}),{removeBusinessUser:i,fetching:a}=De(),s=(0,Z.useCallback)(()=>r({requestPolicy:`network-only`}),[r]),c=(0,Z.useCallback)(async e=>{await i(e)!==void 0&&s()},[i,s]),l=e?.listBusinessUsers??[];return(0,Q.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,Q.jsxs)(`div`,{children:[(0,Q.jsx)(`h2`,{className:`text-lg font-medium`,children:`Users`}),(0,Q.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`People with access to this business.`})]}),n?(0,Q.jsxs)(X,{variant:`destructive`,children:[(0,Q.jsx)(Y,{children:`Failed to load users`}),(0,Q.jsx)(J,{children:n.message})]}):t?(0,Q.jsx)(je,{}):l.length===0?(0,Q.jsx)(`p`,{className:`p-6 text-center text-sm text-muted-foreground`,children:`No users found.`}):(0,Q.jsxs)(B,{children:[(0,Q.jsx)(P,{children:(0,Q.jsxs)(R,{children:[(0,Q.jsx)(I,{children:`Name`}),(0,Q.jsx)(I,{children:`Email`}),(0,Q.jsx)(I,{children:`Role`}),(0,Q.jsx)(I,{className:`text-right`,children:`Actions`})]})}),(0,Q.jsx)(L,{children:l.map(e=>(0,Q.jsxs)(R,{children:[(0,Q.jsx)(z,{className:`font-medium`,children:e.name?.trim()||(0,Q.jsx)(`span`,{className:`text-muted-foreground`,children:`—`})}),(0,Q.jsx)(z,{children:e.email}),(0,Q.jsx)(z,{children:(0,Q.jsx)(se,{variant:`secondary`,children:ke(e.roleId)})}),(0,Q.jsx)(z,{className:`text-right`,children:(0,Q.jsx)(q,{onConfirm:()=>c(e.id),title:`Remove ${e.name?.trim()?`${e.name.trim()} (${e.email})`:e.email} from this business? They will lose access immediately.`,children:(0,Q.jsxs)(O,{variant:`destructive`,size:`sm`,disabled:a,children:[(0,Q.jsx)(G,{className:`size-4`}),`Remove User`]})})})]},e.id))})]})]})}function je(){return(0,Q.jsxs)(B,{children:[(0,Q.jsx)(P,{children:(0,Q.jsxs)(R,{children:[(0,Q.jsx)(I,{children:`Name`}),(0,Q.jsx)(I,{children:`Email`}),(0,Q.jsx)(I,{children:`Role`}),(0,Q.jsx)(I,{className:`text-right`,children:`Actions`})]})}),(0,Q.jsx)(L,{children:Array.from({length:4}).map((e,t)=>(0,Q.jsxs)(R,{children:[(0,Q.jsx)(z,{children:(0,Q.jsx)(W,{className:`h-4 w-32`})}),(0,Q.jsx)(z,{children:(0,Q.jsx)(W,{className:`h-4 w-48`})}),(0,Q.jsx)(z,{children:(0,Q.jsx)(W,{className:`h-6 w-24`})}),(0,Q.jsx)(z,{className:`text-right`,children:(0,Q.jsx)(W,{className:`ml-auto h-8 w-28`})})]},t))})]})}var Me=`createInvitation`,Ne=()=>{let[{fetching:e,error:t},n]=a(i);return{fetching:e,error:t,createInvitation:(0,Z.useCallback)(async({email:e,roleId:t})=>{let r=`Error creating invitation`,i=Me;d.loading(`Creating invitation`,{id:i});try{let a=f(await n({email:e,roleId:t}),r,i);if(a)return d.success(`Success`,{id:i,description:`Invitation sent successfully`}),a.createInvitation}catch(e){console.error(r,e),d.error(`Error`,{id:i,description:e instanceof Error?e.message:r,duration:1e4,closeButton:!0})}},[n])}},Pe=`revokeInvitation`,Fe=()=>{let[{fetching:e,error:t},n]=a(r);return{fetching:e,error:t,revokeInvitation:(0,Z.useCallback)(async e=>{let t=`Error revoking invitation`,r=Pe;d.loading(`Revoking invitation`,{id:r});try{let i=f(await n({id:e}),t,r);if(i)return i.revokeInvitation?(d.success(`Success`,{id:r,description:`Invitation revoked successfully`}),!0):(d.error(`Error`,{id:r,description:`Failed to revoke invitation. It may have already been revoked.`}),!1)}catch(e){console.error(t,e),d.error(`Error`,{id:r,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=A({email:ae(`Please enter a valid email address`),roleId:V([`business_owner`,`accountant`,`employee`])});function ze(){let[{data:e,fetching:t,error:n},r]=o({query:u}),{revokeInvitation:i,fetching:a}=Fe(),s=(0,Z.useCallback)(()=>r({requestPolicy:`network-only`}),[r]),c=(0,Z.useCallback)(async e=>{await i(e)!==void 0&&s()},[i,s]),l=e?.listInvitations??[];return(0,Q.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,Q.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,Q.jsxs)(`div`,{children:[(0,Q.jsx)(`h2`,{className:`text-lg font-medium`,children:`Invitations`}),(0,Q.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pending invitations to join this business.`})]}),(0,Q.jsx)(Be,{onInvited:s})]}),n?(0,Q.jsxs)(X,{variant:`destructive`,children:[(0,Q.jsx)(Y,{children:`Failed to load invitations`}),(0,Q.jsx)(J,{children:n.message})]}):t?(0,Q.jsx)(`div`,{className:`flex justify-center p-8`,children:(0,Q.jsx)(v,{className:`h-8 w-8 animate-spin`})}):l.length===0?(0,Q.jsx)(`p`,{className:`p-6 text-center text-sm text-muted-foreground`,children:`No pending invitations.`}):(0,Q.jsxs)(B,{children:[(0,Q.jsx)(P,{children:(0,Q.jsxs)(R,{children:[(0,Q.jsx)(I,{children:`Email`}),(0,Q.jsx)(I,{children:`Role`}),(0,Q.jsx)(I,{children:`Expires`}),(0,Q.jsx)(I,{className:`text-right`,children:`Actions`})]})}),(0,Q.jsx)(L,{children:l.map(e=>(0,Q.jsxs)(R,{children:[(0,Q.jsx)(z,{className:`font-medium`,children:e.email}),(0,Q.jsx)(z,{children:(0,Q.jsx)(se,{variant:`secondary`,children:Le(e.roleId)})}),(0,Q.jsx)(z,{children:new Date(e.expiresAt).toLocaleDateString()}),(0,Q.jsx)(z,{className:`text-right`,children:(0,Q.jsx)(q,{onConfirm:()=>c(e.id),title:`Revoke the invitation for "${e.email}"? They will no longer be able to accept it.`,children:(0,Q.jsxs)(O,{variant:`destructive`,size:`sm`,disabled:a,children:[(0,Q.jsx)(G,{className:`size-4`}),`Revoke`]})})})]},e.id))})]})]})}function Be({onInvited:e}){let[t,n]=(0,Z.useState)(!1),{createInvitation:r,fetching:i}=Ne(),a=le({resolver:F(Re),defaultValues:{email:``,roleId:`employee`}}),o=(0,Z.useCallback)(async t=>{await r(t)&&(e(),n(!1),a.reset())},[r,e,a]);return(0,Q.jsxs)(h,{open:t,onOpenChange:(0,Z.useCallback)(e=>{n(e),e||a.reset()},[a]),children:[(0,Q.jsx)(x,{asChild:!0,children:(0,Q.jsxs)(O,{children:[(0,Q.jsx)(_e,{className:`size-4`}),`Invite User`]})}),(0,Q.jsxs)(b,{children:[(0,Q.jsxs)(_,{children:[(0,Q.jsx)(C,{children:`Invite User`}),(0,Q.jsx)(D,{children:`Send an invitation to join this business with the selected role.`})]}),(0,Q.jsx)(ce,{...a,children:(0,Q.jsxs)(`form`,{onSubmit:a.handleSubmit(o),className:`flex flex-col gap-4`,children:[(0,Q.jsx)(M,{control:a.control,name:`email`,render:({field:e})=>(0,Q.jsxs)(N,{children:[(0,Q.jsx)(H,{children:`Email`}),(0,Q.jsx)(j,{children:(0,Q.jsx)(T,{type:`email`,placeholder:`user@example.com`,...e})}),(0,Q.jsx)(k,{})]})}),(0,Q.jsx)(M,{control:a.control,name:`roleId`,render:({field:e})=>(0,Q.jsxs)(N,{children:[(0,Q.jsx)(H,{children:`Role`}),(0,Q.jsxs)(E,{onValueChange:e.onChange,value:e.value,children:[(0,Q.jsx)(j,{children:(0,Q.jsx)(y,{children:(0,Q.jsx)(m,{placeholder:`Select a role`})})}),(0,Q.jsx)(S,{children:Ie.map(e=>(0,Q.jsx)(w,{value:e.value,children:e.label},e.value))})]}),(0,Q.jsx)(k,{})]})}),(0,Q.jsx)(g,{children:(0,Q.jsxs)(O,{type:`submit`,disabled:i,children:[i&&(0,Q.jsx)(v,{className:`size-4 animate-spin`}),`Send Invitation`]})})]})})]})]})}function Ve(){let{businessId:e}=ie(),{userContext:t}=(0,Z.useContext)(oe),n=t?.context.adminBusinessId;return!n||n!==e?(0,Q.jsx)(ne,{to:re.HOME,replace:!0}):(0,Q.jsxs)(`div`,{className:`flex flex-col gap-4 p-4`,children:[(0,Q.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Access Management`}),(0,Q.jsxs)(fe,{defaultValue:`api-keys`,className:`w-full`,children:[(0,Q.jsxs)(pe,{children:[(0,Q.jsx)(K,{value:`api-keys`,children:`API Keys`}),(0,Q.jsx)(K,{value:`invitations`,children:`Invitations`}),(0,Q.jsx)(K,{value:`users`,children:`Users`})]}),(0,Q.jsx)(U,{value:`api-keys`,children:(0,Q.jsx)(we,{})}),(0,Q.jsx)(U,{value:`invitations`,children:(0,Q.jsx)(ze,{})}),(0,Q.jsx)(U,{value:`users`,children:(0,Q.jsx)(Ae,{})})]})]})}export{Ve as AuthManagement};