litenet-website/src/components/dashboard/SipCredentialsCard.jsx
rocord01 e6a9bdcdc8
All checks were successful
Deploy to Server / deploy (push) Successful in 1m52s
show secret, update connected devices, and add checkmark anim for copy
2025-07-14 07:52:34 -04:00

149 lines
7.3 KiB
JavaScript

"use client";
import { useState } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Copy, KeyRound, Server, User, Check } from 'lucide-react';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Skeleton } from '@/components/ui/skeleton';
function CredentialRow({ icon, label, value, onCopy, isCopied }) {
return (
<div className="flex items-center justify-between bg-gray-900 p-3 rounded-lg border border-gray-800">
<div className="flex items-center gap-3">
{icon}
<div>
<div className="text-xs text-gray-400">{label}</div>
<code className="text-white">{value}</code>
</div>
</div>
<Button variant="ghost" size="icon" onClick={onCopy}>
{isCopied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
);
}
export function SipCredentialsCard({ details, loading, refetchDetails }) {
const { token } = useAuth();
const [newSecret, setNewSecret] = useState(null);
const [isResetting, setIsResetting] = useState(false);
const [copiedState, setCopiedState] = useState({});
const handleResetSecret = async () => {
if (!token) return;
setIsResetting(true);
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/resetsecret`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
setNewSecret(data.newSecret);
if (refetchDetails) {
refetchDetails();
}
} catch (error) {
console.error("Failed to reset secret", error);
alert('Failed to reset secret.');
} finally {
setIsResetting(false);
}
};
const copyToClipboard = (text, id) => {
if (!text) return;
navigator.clipboard.writeText(text).then(() => {
setCopiedState(prev => ({ ...prev, [id]: true }));
setTimeout(() => {
setCopiedState(prev => ({ ...prev, [id]: false }));
}, 2000);
});
};
if (loading) {
return (
<Card className="bg-gray-950/50 border-gray-800">
<CardHeader>
<Skeleton className="h-6 w-48" />
<Skeleton className="h-4 w-64 mt-2" />
</CardHeader>
<CardContent className="space-y-4 pt-4">
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-10 w-full" />
</CardContent>
</Card>
);
}
return (
<Card className="bg-gray-950/50 border-gray-800">
<CardHeader>
<CardTitle>SIP Credentials</CardTitle>
<CardDescription>Use these details to connect your SIP client.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<CredentialRow icon={<Server className="h-5 w-5 text-gray-400" />} label="SIP Server" value="pbx.litenet.tel" onCopy={() => copyToClipboard('pbx.litenet.tel', 'server')} isCopied={copiedState['server']} />
<CredentialRow icon={<User className="h-5 w-5 text-gray-400" />} label="SIP Username" value={details?.extensionId} onCopy={() => copyToClipboard(details?.extensionId, 'username')} isCopied={copiedState['username']} />
<CredentialRow icon={<KeyRound className="h-5 w-5 text-gray-400" />} label="SIP Secret" value="••••••••••••" onCopy={() => copyToClipboard(details?.user?.extPassword, 'secret')} isCopied={copiedState['secret']} />
<Dialog open={!!newSecret} onOpenChange={(open) => { if (!open) { setNewSecret(null); setCopiedState(prev => ({ ...prev, newSecret: false })); } }}>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" disabled={isResetting} className="w-full">
<KeyRound className="h-4 w-4 mr-2" />
{isResetting ? 'Resetting...' : 'Reset SIP Secret'}
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="bg-gray-950 border-gray-800 text-white">
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription className="text-gray-400">
Resetting your SIP secret will require you to update it on all devices registered to this extension. This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="border-gray-700 hover:bg-gray-800">Cancel</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={handleResetSecret}
>
Continue
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<DialogContent className="bg-gray-950 border-gray-800 text-white">
<DialogHeader>
<DialogTitle>Secret Reset Successfully</DialogTitle>
<DialogDescription className="text-gray-400">
Your new SIP secret is shown below. Copy it now, as you will not be able to see it again.
</DialogDescription>
</DialogHeader>
<div className="flex items-center space-x-2 bg-black p-3 rounded-md border border-gray-700">
<code className="text-lg font-mono flex-grow text-green-400">{newSecret}</code>
<Button variant="ghost" size="icon" onClick={() => copyToClipboard(newSecret, 'newSecret')}>
{copiedState['newSecret'] ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
</DialogContent>
</Dialog>
</CardContent>
</Card>
);
}