"use client";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { cn } from "@app/lib/cn";
import { formatFingerprintInfo } from "@app/lib/formatDeviceFingerprint";
import {
approvalFiltersSchema,
approvalQueries,
type ApprovalItem
} from "@app/lib/queries";
import { useQuery } from "@tanstack/react-query";
import { ArrowRight, Ban, Check, LaptopMinimal, RefreshCw } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { Fragment, useActionState } from "react";
import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
import { Card, CardHeader } from "./ui/card";
import { Label } from "./ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from "./ui/select";
import { Separator } from "./ui/separator";
import { InfoPopup } from "./ui/info-popup";
import { ApprovalsEmptyState } from "./ApprovalsEmptyState";
export type ApprovalFeedProps = {
orgId: string;
hasApprovalsEnabled: boolean;
};
export function ApprovalFeed({
orgId,
hasApprovalsEnabled
}: ApprovalFeedProps) {
const searchParams = useSearchParams();
const path = usePathname();
const t = useTranslations();
const router = useRouter();
const filters = approvalFiltersSchema.parse(
Object.fromEntries(searchParams.entries())
);
const { data, isFetching, refetch } = useQuery(
approvalQueries.listApprovals(orgId, filters)
);
const approvals = data?.approvals ?? [];
// Show empty state if no approvals are enabled for any role
if (!hasApprovalsEnabled) {
return ;
}
return (
{approvals.map((approval, index) => (
-
refetch()}
/>
{index < approvals.length - 1 && }
))}
{approvals.length === 0 && (
-
{t("approvalListEmpty")}
)}
);
}
type ApprovalRequestProps = {
approval: ApprovalItem;
orgId: string;
onSuccess?: () => void;
};
function ApprovalRequest({ approval, orgId, onSuccess }: ApprovalRequestProps) {
const t = useTranslations();
const [_, formAction, isSubmitting] = useActionState(onSubmit, null);
const api = createApiClient(useEnvContext());
async function onSubmit(_previousState: any, formData: FormData) {
const decision = formData.get("decision");
const res = await api
.put(`/org/${orgId}/approvals/${approval.approvalId}`, { decision })
.catch((e) => {
toast({
variant: "destructive",
title: t("accessApprovalErrorUpdate"),
description: formatAxiosError(
e,
t("accessApprovalErrorUpdateDescription")
)
});
});
if (res && res.status === 200) {
const result = res.data.data;
toast({
variant: "default",
title: t("accessApprovalUpdated"),
description:
result.decision === "approved"
? t("accessApprovalApprovedDescription")
: t("accessApprovalDeniedDescription")
});
onSuccess?.();
}
}
return (
{getUserDisplayName({
email: approval.user.email,
name: approval.user.name,
username: approval.user.username
})}
{approval.type === "user_device" && (
{approval.deviceName ? (
<>
{t("requestingNewDeviceApproval")}:{" "}
{approval.niceId ? (
{approval.deviceName}
) : (
{approval.deviceName}
)}
{approval.fingerprint && (
{t("deviceInformation")}
{formatFingerprintInfo(approval.fingerprint, t)}
)}
>
) : (
{t("requestingNewDeviceApproval")}
)}
)}
{approval.decision === "pending" && (
)}
{approval.decision === "approved" && (
{t("approved")}
)}
{approval.decision === "denied" && (
{t("denied")}
)}
);
}