149 lines
7.3 KiB
JavaScript
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>
|
|
);
|
|
}
|