diff --git a/server/private/routers/approvals/listApprovals.ts b/server/private/routers/approvals/listApprovals.ts index e00d4ec8..93d20e76 100644 --- a/server/private/routers/approvals/listApprovals.ts +++ b/server/private/routers/approvals/listApprovals.ts @@ -47,14 +47,20 @@ const querySchema = z.strictObject({ .enum(["pending", "approved", "denied", "all"]) .optional() .default("all") - .catch("all") + .catch("all"), + clientId: z + .string() + .optional() + .transform((val) => (val ? Number(val) : undefined)) + .pipe(z.number().int().positive().optional()) }); async function queryApprovals( orgId: string, limit: number, offset: number, - approvalState: z.infer["approvalState"] + approvalState: z.infer["approvalState"], + clientId?: number ) { let state: Array = []; switch (approvalState) { @@ -109,7 +115,8 @@ async function queryApprovals( .where( and( eq(approvals.orgId, orgId), - sql`${approvals.decision} in ${state}` + sql`${approvals.decision} in ${state}`, + ...(clientId ? [eq(approvals.clientId, clientId)] : []) ) ) .orderBy( @@ -202,7 +209,7 @@ export async function listApprovals( ) ); } - const { limit, offset, approvalState } = parsedQuery.data; + const { limit, offset, approvalState, clientId } = parsedQuery.data; const { orgId } = parsedParams.data; @@ -223,7 +230,8 @@ export async function listApprovals( orgId.toString(), limit, offset, - approvalState + approvalState, + clientId ); const [{ count }] = await db diff --git a/src/components/UserDevicesTable.tsx b/src/components/UserDevicesTable.tsx index 79be6cbd..a9555999 100644 --- a/src/components/UserDevicesTable.tsx +++ b/src/components/UserDevicesTable.tsx @@ -184,6 +184,90 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { }); }; + const approveDevice = async (clientRow: ClientRow) => { + try { + // Fetch approvalId for this client using clientId query parameter + const approvalsRes = await api.get<{ + data: { approvals: Array<{ approvalId: number; clientId: number }> }; + }>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`); + + const approval = approvalsRes.data.data.approvals[0]; + + if (!approval) { + toast({ + variant: "destructive", + title: t("error"), + description: t("accessApprovalErrorUpdateDescription") + }); + return; + } + + await api.put(`/org/${clientRow.orgId}/approvals/${approval.approvalId}`, { + decision: "approved" + }); + + toast({ + title: t("accessApprovalUpdated"), + description: t("accessApprovalApprovedDescription") + }); + + startTransition(() => { + router.refresh(); + }); + } catch (e) { + toast({ + variant: "destructive", + title: t("accessApprovalErrorUpdate"), + description: formatAxiosError( + e, + t("accessApprovalErrorUpdateDescription") + ) + }); + } + }; + + const denyDevice = async (clientRow: ClientRow) => { + try { + // Fetch approvalId for this client using clientId query parameter + const approvalsRes = await api.get<{ + data: { approvals: Array<{ approvalId: number; clientId: number }> }; + }>(`/org/${clientRow.orgId}/approvals?approvalState=pending&clientId=${clientRow.id}`); + + const approval = approvalsRes.data.data.approvals[0]; + + if (!approval) { + toast({ + variant: "destructive", + title: t("error"), + description: t("accessApprovalErrorUpdateDescription") + }); + return; + } + + await api.put(`/org/${clientRow.orgId}/approvals/${approval.approvalId}`, { + decision: "denied" + }); + + toast({ + title: t("accessApprovalUpdated"), + description: t("accessApprovalDeniedDescription") + }); + + startTransition(() => { + router.refresh(); + }); + } catch (e) { + toast({ + variant: "destructive", + title: t("accessApprovalErrorUpdate"), + description: formatAxiosError( + e, + t("accessApprovalErrorUpdateDescription") + ) + }); + } + }; + // Check if there are any rows without userIds in the current view's data const hasRowsWithoutUserId = useMemo(() => { return userClients.some((client) => !client.userId); @@ -464,6 +548,20 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) { + {clientRow.approvalState === "pending" && ( + <> + approveDevice(clientRow)} + > + {t("approve")} + + denyDevice(clientRow)} + > + {t("deny")} + + + )} { if (clientRow.archived) {