mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-01 15:49:08 +00:00
353 lines
17 KiB
TypeScript
353 lines
17 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Credenza,
|
|
CredenzaBody,
|
|
CredenzaClose,
|
|
CredenzaContent,
|
|
CredenzaDescription,
|
|
CredenzaFooter,
|
|
CredenzaHeader,
|
|
CredenzaTitle
|
|
} from "@app/components/Credenza";
|
|
import { useTranslations } from "next-intl";
|
|
import { createApiClient } from "@app/lib/api";
|
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
|
import { toast } from "@app/hooks/useToast";
|
|
import { formatAxiosError } from "@app/lib/api";
|
|
import { ListUserOlmsResponse } from "@server/routers/olm";
|
|
import { ResponseT } from "@server/types/Response";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow
|
|
} from "@app/components/ui/table";
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@app/components/ui/tabs";
|
|
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
|
import { Loader2, RefreshCw } from "lucide-react";
|
|
import moment from "moment";
|
|
import { useUserContext } from "@app/hooks/useUserContext";
|
|
|
|
type ViewDevicesDialogProps = {
|
|
open: boolean;
|
|
setOpen: (val: boolean) => void;
|
|
};
|
|
|
|
type Device = {
|
|
olmId: string;
|
|
dateCreated: string;
|
|
version: string | null;
|
|
name: string | null;
|
|
clientId: number | null;
|
|
userId: string | null;
|
|
archived: boolean;
|
|
};
|
|
|
|
export default function ViewDevicesDialog({
|
|
open,
|
|
setOpen
|
|
}: ViewDevicesDialogProps) {
|
|
const t = useTranslations();
|
|
const { env } = useEnvContext();
|
|
const api = createApiClient({ env });
|
|
const { user } = useUserContext();
|
|
|
|
const [devices, setDevices] = useState<Device[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [isArchiveModalOpen, setIsArchiveModalOpen] = useState(false);
|
|
const [selectedDevice, setSelectedDevice] = useState<Device | null>(null);
|
|
const [activeTab, setActiveTab] = useState<"available" | "archived">("available");
|
|
|
|
const fetchDevices = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await api.get<ResponseT<ListUserOlmsResponse>>(
|
|
`/user/${user?.userId}/olms`
|
|
);
|
|
if (res.data.success && res.data.data) {
|
|
setDevices(res.data.data.olms);
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Error fetching devices:", error);
|
|
toast({
|
|
variant: "destructive",
|
|
title: t("errorLoadingDevices") || "Error loading devices",
|
|
description: formatAxiosError(
|
|
error,
|
|
t("failedToLoadDevices") || "Failed to load devices"
|
|
)
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
fetchDevices();
|
|
}
|
|
}, [open]);
|
|
|
|
const archiveDevice = async (olmId: string) => {
|
|
try {
|
|
await api.post(`/user/${user?.userId}/olm/${olmId}/archive`);
|
|
toast({
|
|
title: t("deviceArchived") || "Device archived",
|
|
description:
|
|
t("deviceArchivedDescription") ||
|
|
"The device has been successfully archived."
|
|
});
|
|
// Update the device's archived status in the local state
|
|
setDevices(
|
|
devices.map((d) =>
|
|
d.olmId === olmId ? { ...d, archived: true } : d
|
|
)
|
|
);
|
|
setIsArchiveModalOpen(false);
|
|
setSelectedDevice(null);
|
|
} catch (error: any) {
|
|
console.error("Error archiving device:", error);
|
|
toast({
|
|
variant: "destructive",
|
|
title: t("errorArchivingDevice"),
|
|
description: formatAxiosError(
|
|
error,
|
|
t("failedToArchiveDevice")
|
|
)
|
|
});
|
|
}
|
|
};
|
|
|
|
function reset() {
|
|
setDevices([]);
|
|
setSelectedDevice(null);
|
|
setIsArchiveModalOpen(false);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Credenza
|
|
open={open}
|
|
onOpenChange={(val) => {
|
|
setOpen(val);
|
|
if (!val) {
|
|
reset();
|
|
}
|
|
}}
|
|
>
|
|
<CredenzaContent className="max-w-4xl">
|
|
<CredenzaHeader>
|
|
<CredenzaTitle>
|
|
{t("viewDevices") || "View Devices"}
|
|
</CredenzaTitle>
|
|
<CredenzaDescription>
|
|
{t("viewDevicesDescription") ||
|
|
"Manage your connected devices"}
|
|
</CredenzaDescription>
|
|
</CredenzaHeader>
|
|
<CredenzaBody>
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-8">
|
|
<Loader2 className="h-6 w-6 animate-spin" />
|
|
</div>
|
|
) : (
|
|
<Tabs
|
|
value={activeTab}
|
|
onValueChange={(value) =>
|
|
setActiveTab(value as "available" | "archived")
|
|
}
|
|
className="w-full"
|
|
>
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
<TabsTrigger value="available">
|
|
{t("available") || "Available"} (
|
|
{
|
|
devices.filter(
|
|
(d) => !d.archived
|
|
).length
|
|
}
|
|
)
|
|
</TabsTrigger>
|
|
<TabsTrigger value="archived">
|
|
{t("archived") || "Archived"} (
|
|
{
|
|
devices.filter(
|
|
(d) => d.archived
|
|
).length
|
|
}
|
|
)
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="available" className="mt-4">
|
|
{devices.filter((d) => !d.archived)
|
|
.length === 0 ? (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
{t("noDevices") ||
|
|
"No devices found"}
|
|
</div>
|
|
) : (
|
|
<div className="rounded-md border">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="pl-3">
|
|
{t("name") || "Name"}
|
|
</TableHead>
|
|
<TableHead>
|
|
{t("dateCreated") ||
|
|
"Date Created"}
|
|
</TableHead>
|
|
<TableHead>
|
|
{t("actions") ||
|
|
"Actions"}
|
|
</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{devices
|
|
.filter(
|
|
(d) => !d.archived
|
|
)
|
|
.map((device) => (
|
|
<TableRow
|
|
key={device.olmId}
|
|
>
|
|
<TableCell className="font-medium">
|
|
{device.name ||
|
|
t(
|
|
"unnamedDevice"
|
|
) ||
|
|
"Unnamed Device"}
|
|
</TableCell>
|
|
<TableCell>
|
|
{moment(
|
|
device.dateCreated
|
|
).format(
|
|
"lll"
|
|
)}
|
|
</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => {
|
|
setSelectedDevice(
|
|
device
|
|
);
|
|
setIsArchiveModalOpen(
|
|
true
|
|
);
|
|
}}
|
|
>
|
|
{t(
|
|
"archive"
|
|
) ||
|
|
"Archive"}
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
<TabsContent value="archived" className="mt-4">
|
|
{devices.filter((d) => d.archived)
|
|
.length === 0 ? (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
{t("noArchivedDevices") ||
|
|
"No archived devices found"}
|
|
</div>
|
|
) : (
|
|
<div className="rounded-md border">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="pl-3">
|
|
{t("name") || "Name"}
|
|
</TableHead>
|
|
<TableHead>
|
|
{t("dateCreated") ||
|
|
"Date Created"}
|
|
</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{devices
|
|
.filter(
|
|
(d) => d.archived
|
|
)
|
|
.map((device) => (
|
|
<TableRow
|
|
key={device.olmId}
|
|
>
|
|
<TableCell className="font-medium">
|
|
{device.name ||
|
|
t(
|
|
"unnamedDevice"
|
|
) ||
|
|
"Unnamed Device"}
|
|
</TableCell>
|
|
<TableCell>
|
|
{moment(
|
|
device.dateCreated
|
|
).format(
|
|
"lll"
|
|
)}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
)}
|
|
</CredenzaBody>
|
|
<CredenzaFooter>
|
|
<CredenzaClose asChild>
|
|
<Button variant="outline">
|
|
{t("close") || "Close"}
|
|
</Button>
|
|
</CredenzaClose>
|
|
</CredenzaFooter>
|
|
</CredenzaContent>
|
|
</Credenza>
|
|
|
|
{selectedDevice && (
|
|
<ConfirmDeleteDialog
|
|
open={isArchiveModalOpen}
|
|
setOpen={(val) => {
|
|
setIsArchiveModalOpen(val);
|
|
if (!val) {
|
|
setSelectedDevice(null);
|
|
}
|
|
}}
|
|
dialog={
|
|
<div className="space-y-2">
|
|
<p>
|
|
{t("deviceQuestionArchive") ||
|
|
"Are you sure you want to archive this device?"}
|
|
</p>
|
|
<p>
|
|
{t("deviceMessageArchive") ||
|
|
"The device will be archived and removed from your active devices list."}
|
|
</p>
|
|
</div>
|
|
}
|
|
buttonText={t("deviceArchiveConfirm") || "Archive Device"}
|
|
onConfirm={async () => archiveDevice(selectedDevice.olmId)}
|
|
string={selectedDevice.name || selectedDevice.olmId}
|
|
title={t("archiveDevice") || "Archive Device"}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|