new use api handler which does autologin when session is revoked
All checks were successful
Deploy to Server / deploy (push) Successful in 2m6s
All checks were successful
Deploy to Server / deploy (push) Successful in 2m6s
This commit is contained in:
parent
c9ecd95459
commit
fcfccf084a
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
@ -16,6 +17,7 @@ export default function ConferenceDetails({ conferenceId }) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const { token, isLoggedIn } = useAuth();
|
const { token, isLoggedIn } = useAuth();
|
||||||
|
const apiFetch = useApi();
|
||||||
const [isConnecting, setIsConnecting] = useState(false);
|
const [isConnecting, setIsConnecting] = useState(false);
|
||||||
const [currentUserExtension, setCurrentUserExtension] = useState(null);
|
const [currentUserExtension, setCurrentUserExtension] = useState(null);
|
||||||
|
|
||||||
|
@ -23,20 +25,20 @@ export default function ConferenceDetails({ conferenceId }) {
|
||||||
if (isLoggedIn && token) {
|
if (isLoggedIn && token) {
|
||||||
const fetchUserDetails = async () => {
|
const fetchUserDetails = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/me`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/me`);
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setCurrentUserExtension(data.extensionId);
|
setCurrentUserExtension(data.extensionId);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to fetch user details", err);
|
if (err.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to fetch user details", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchUserDetails();
|
fetchUserDetails();
|
||||||
}
|
}
|
||||||
}, [isLoggedIn, token]);
|
}, [isLoggedIn, token, apiFetch]);
|
||||||
|
|
||||||
const fetchDetails = useCallback(async () => {
|
const fetchDetails = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -44,8 +46,8 @@ export default function ConferenceDetails({ conferenceId }) {
|
||||||
try {
|
try {
|
||||||
// Fetch both participants and conference room info
|
// Fetch both participants and conference room info
|
||||||
const [participantsRes, roomsRes] = await Promise.all([
|
const [participantsRes, roomsRes] = await Promise.all([
|
||||||
fetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences/${conferenceId}`),
|
apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences/${conferenceId}`),
|
||||||
fetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences`)
|
apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences`)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!participantsRes.ok) {
|
if (!participantsRes.ok) {
|
||||||
|
@ -66,12 +68,14 @@ export default function ConferenceDetails({ conferenceId }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to fetch details for ${conferenceId}`, err);
|
if (err.message !== 'Unauthorized') {
|
||||||
|
console.error(`Failed to fetch details for ${conferenceId}`, err);
|
||||||
|
}
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [conferenceId]);
|
}, [conferenceId, apiFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDetails();
|
fetchDetails();
|
||||||
|
@ -83,9 +87,8 @@ export default function ConferenceDetails({ conferenceId }) {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
setIsConnecting(true);
|
setIsConnecting(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences/${conferenceId}/connectme`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences/${conferenceId}/connectme`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const errorData = await res.json();
|
const errorData = await res.json();
|
||||||
|
@ -93,8 +96,10 @@ export default function ConferenceDetails({ conferenceId }) {
|
||||||
}
|
}
|
||||||
// Optionally show a success message
|
// Optionally show a success message
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to connect", err);
|
if (err.message !== 'Unauthorized') {
|
||||||
alert(err.message);
|
console.error("Failed to connect", err);
|
||||||
|
alert(err.message);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsConnecting(false);
|
setIsConnecting(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
@ -12,6 +13,7 @@ export default function ConferenceList() {
|
||||||
const [conferences, setConferences] = useState([]);
|
const [conferences, setConferences] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const apiFetch = useApi();
|
||||||
|
|
||||||
const fetchConferences = useCallback(async () => {
|
const fetchConferences = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
@ -19,7 +21,7 @@ export default function ConferenceList() {
|
||||||
try {
|
try {
|
||||||
// Acting as an unauthenticated user, so no auth token is sent.
|
// Acting as an unauthenticated user, so no auth token is sent.
|
||||||
// This assumes the endpoint is public.
|
// This assumes the endpoint is public.
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences`);
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/conferences`);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const errorData = await res.json();
|
const errorData = await res.json();
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
|
@ -30,12 +32,14 @@ export default function ConferenceList() {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setConferences(Array.isArray(data) ? data : []);
|
setConferences(Array.isArray(data) ? data : []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to fetch conferences", err);
|
if (err.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to fetch conferences", err);
|
||||||
|
}
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [apiFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchConferences();
|
fetchConferences();
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
@ -38,6 +39,7 @@ function CredentialRow({ icon, label, value, onCopy, isCopied }) {
|
||||||
|
|
||||||
export function SipCredentialsCard({ details, loading, refetchDetails }) {
|
export function SipCredentialsCard({ details, loading, refetchDetails }) {
|
||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
|
const apiFetch = useApi();
|
||||||
const [newSecret, setNewSecret] = useState(null);
|
const [newSecret, setNewSecret] = useState(null);
|
||||||
const [isResetting, setIsResetting] = useState(false);
|
const [isResetting, setIsResetting] = useState(false);
|
||||||
const [copiedState, setCopiedState] = useState({});
|
const [copiedState, setCopiedState] = useState({});
|
||||||
|
@ -46,9 +48,8 @@ export function SipCredentialsCard({ details, loading, refetchDetails }) {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
setIsResetting(true);
|
setIsResetting(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/resetsecret`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/resetsecret`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setNewSecret(data.newSecret);
|
setNewSecret(data.newSecret);
|
||||||
|
@ -56,8 +57,10 @@ export function SipCredentialsCard({ details, loading, refetchDetails }) {
|
||||||
refetchDetails();
|
refetchDetails();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to reset secret", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
alert('Failed to reset secret.');
|
console.error("Failed to reset secret", error);
|
||||||
|
alert('Failed to reset secret.');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsResetting(false);
|
setIsResetting(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||||
|
@ -23,6 +24,7 @@ import {
|
||||||
|
|
||||||
export function ActionsCard({ details }) {
|
export function ActionsCard({ details }) {
|
||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
|
const apiFetch = useApi();
|
||||||
const [isCalling, setIsCalling] = useState(false);
|
const [isCalling, setIsCalling] = useState(false);
|
||||||
const [callMode, setCallMode] = useState('hold');
|
const [callMode, setCallMode] = useState('hold');
|
||||||
const [callerId, setCallerId] = useState('');
|
const [callerId, setCallerId] = useState('');
|
||||||
|
@ -38,14 +40,15 @@ export function ActionsCard({ details }) {
|
||||||
if (callerId) {
|
if (callerId) {
|
||||||
url += `&callerId=${encodeURIComponent(callerId)}`;
|
url += `&callerId=${encodeURIComponent(callerId)}`;
|
||||||
}
|
}
|
||||||
await fetch(url, {
|
await apiFetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
});
|
||||||
setIsCallMeDialogOpen(false);
|
setIsCallMeDialogOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to initiate call", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
alert('Failed to initiate call.');
|
console.error("Failed to initiate call", error);
|
||||||
|
alert('Failed to initiate call.');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsCalling(false);
|
setIsCalling(false);
|
||||||
}
|
}
|
||||||
|
@ -55,15 +58,16 @@ export function ActionsCard({ details }) {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
setIsResetting(true);
|
setIsResetting(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/resetsecret`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/resetsecret`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setNewSecret(data.newSecret);
|
setNewSecret(data.newSecret);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to reset secret", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
alert('Failed to reset secret.');
|
console.error("Failed to reset secret", error);
|
||||||
|
alert('Failed to reset secret.');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsResetting(false);
|
setIsResetting(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
@ -34,6 +35,7 @@ function CallCard({ call }) {
|
||||||
|
|
||||||
export function ActiveCalls() {
|
export function ActiveCalls() {
|
||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
|
const apiFetch = useApi();
|
||||||
const [calls, setCalls] = useState([]);
|
const [calls, setCalls] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||||
|
@ -44,13 +46,13 @@ export function ActiveCalls() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/calls`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/calls`);
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setCalls(Array.isArray(data) ? data : []);
|
setCalls(Array.isArray(data) ? data : []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch active calls", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to fetch active calls", error);
|
||||||
|
}
|
||||||
setCalls([]);
|
setCalls([]);
|
||||||
} finally {
|
} finally {
|
||||||
if (isInitialLoad || isManualRefresh) {
|
if (isInitialLoad || isManualRefresh) {
|
||||||
|
@ -60,7 +62,7 @@ export function ActiveCalls() {
|
||||||
setIsInitialLoad(false);
|
setIsInitialLoad(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [token, isInitialLoad]);
|
}, [token, isInitialLoad, apiFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token) {
|
if (token) {
|
||||||
|
@ -75,13 +77,14 @@ export function ActiveCalls() {
|
||||||
const handleHangup = async () => {
|
const handleHangup = async () => {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
try {
|
try {
|
||||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/calls`, {
|
await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/calls`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
});
|
||||||
fetchCalls(true); // Refresh immediately and show loading state
|
fetchCalls(true); // Refresh immediately and show loading state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to hangup calls", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to hangup calls", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
|
@ -25,6 +26,7 @@ import { Badge } from "@/components/ui/badge"
|
||||||
|
|
||||||
export default function ApiKeys() {
|
export default function ApiKeys() {
|
||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
|
const apiFetch = useApi();
|
||||||
const [keys, setKeys] = useState([]);
|
const [keys, setKeys] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [newKey, setNewKey] = useState(null);
|
const [newKey, setNewKey] = useState(null);
|
||||||
|
@ -40,18 +42,18 @@ export default function ApiKeys() {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/keys`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/keys`);
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setKeys(Array.isArray(data) ? data : []);
|
setKeys(Array.isArray(data) ? data : []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch API keys", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to fetch API keys", error);
|
||||||
|
}
|
||||||
setKeys([]);
|
setKeys([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [token]);
|
}, [token, apiFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchKeys();
|
fetchKeys();
|
||||||
|
@ -61,10 +63,9 @@ export default function ApiKeys() {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/keys`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/keys`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ expiresInDays })
|
body: JSON.stringify({ expiresInDays })
|
||||||
|
@ -87,8 +88,10 @@ export default function ApiKeys() {
|
||||||
setKeys(prevKeys => [keyForState, ...prevKeys]);
|
setKeys(prevKeys => [keyForState, ...prevKeys]);
|
||||||
setIsCreateDialogOpen(false); // Close the creation dialog
|
setIsCreateDialogOpen(false); // Close the creation dialog
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create key", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
alert(`Failed to create key: ${error.message}`);
|
console.error("Failed to create key", error);
|
||||||
|
alert(`Failed to create key: ${error.message}`);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
}
|
}
|
||||||
|
@ -97,9 +100,8 @@ export default function ApiKeys() {
|
||||||
const handleDeleteKey = async (keyToDelete) => {
|
const handleDeleteKey = async (keyToDelete) => {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/keys/${keyToDelete}`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/keys/${keyToDelete}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const errorData = await res.json().catch(() => ({ error: 'Failed to delete key.' }));
|
const errorData = await res.json().catch(() => ({ error: 'Failed to delete key.' }));
|
||||||
|
@ -107,8 +109,10 @@ export default function ApiKeys() {
|
||||||
}
|
}
|
||||||
setKeys(prevKeys => prevKeys.filter(key => key.key !== keyToDelete));
|
setKeys(prevKeys => prevKeys.filter(key => key.key !== keyToDelete));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete key", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
alert(`Failed to delete key: ${error.message}`);
|
console.error("Failed to delete key", error);
|
||||||
|
alert(`Failed to delete key: ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import ExtensionDetails from './extension-details';
|
import ExtensionDetails from './extension-details';
|
||||||
|
@ -43,6 +44,7 @@ export default function DashboardClient() {
|
||||||
const [detailsLoading, setDetailsLoading] = useState(true);
|
const [detailsLoading, setDetailsLoading] = useState(true);
|
||||||
const [deviceStatus, setDeviceStatus] = useState(null);
|
const [deviceStatus, setDeviceStatus] = useState(null);
|
||||||
const [deviceStatusLoading, setDeviceStatusLoading] = useState(true);
|
const [deviceStatusLoading, setDeviceStatusLoading] = useState(true);
|
||||||
|
const apiFetch = useApi();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
@ -53,9 +55,7 @@ export default function DashboardClient() {
|
||||||
const fetchDetails = async () => {
|
const fetchDetails = async () => {
|
||||||
setDetailsLoading(true);
|
setDetailsLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me`);
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
console.log("Fetched details:", data);
|
console.log("Fetched details:", data);
|
||||||
|
@ -64,7 +64,9 @@ export default function DashboardClient() {
|
||||||
setDetails(null);
|
setDetails(null);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to fetch details", err);
|
if (err.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to fetch details", err);
|
||||||
|
}
|
||||||
setDetails(null);
|
setDetails(null);
|
||||||
} finally {
|
} finally {
|
||||||
setDetailsLoading(false);
|
setDetailsLoading(false);
|
||||||
|
@ -72,7 +74,7 @@ export default function DashboardClient() {
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchDetails();
|
fetchDetails();
|
||||||
}, [token]);
|
}, [token, apiFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
@ -83,9 +85,7 @@ export default function DashboardClient() {
|
||||||
const fetchDeviceStatus = async () => {
|
const fetchDeviceStatus = async () => {
|
||||||
// No need to set loading to true every time for a silent refresh
|
// No need to set loading to true every time for a silent refresh
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/devicestatus`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/devicestatus`);
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setDeviceStatus(data);
|
setDeviceStatus(data);
|
||||||
|
@ -93,7 +93,9 @@ export default function DashboardClient() {
|
||||||
setDeviceStatus(null);
|
setDeviceStatus(null);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to fetch device status", err);
|
if (err.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to fetch device status", err);
|
||||||
|
}
|
||||||
setDeviceStatus(null);
|
setDeviceStatus(null);
|
||||||
} finally {
|
} finally {
|
||||||
if (deviceStatusLoading) setDeviceStatusLoading(false);
|
if (deviceStatusLoading) setDeviceStatusLoading(false);
|
||||||
|
@ -103,7 +105,7 @@ export default function DashboardClient() {
|
||||||
fetchDeviceStatus();
|
fetchDeviceStatus();
|
||||||
const interval = setInterval(fetchDeviceStatus, 5000); // Poll every 5 seconds
|
const interval = setInterval(fetchDeviceStatus, 5000); // Poll every 5 seconds
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [token, deviceStatusLoading]);
|
}, [token, deviceStatusLoading, apiFetch]);
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
window.location.href = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/auth/discord`;
|
window.location.href = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/auth/discord`;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useApi } from '@/hooks/use-api';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
@ -68,6 +69,7 @@ export function RegisteredDevices() {
|
||||||
const [devices, setDevices] = useState([]);
|
const [devices, setDevices] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||||
|
const apiFetch = useApi();
|
||||||
|
|
||||||
const fetchDevices = useCallback(async (isManualRefresh = false) => {
|
const fetchDevices = useCallback(async (isManualRefresh = false) => {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
@ -75,9 +77,7 @@ export function RegisteredDevices() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/endpoint`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/endpoint`);
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setDevices(Array.isArray(data) ? data : []);
|
setDevices(Array.isArray(data) ? data : []);
|
||||||
|
@ -85,7 +85,9 @@ export function RegisteredDevices() {
|
||||||
setDevices([]);
|
setDevices([]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch registered devices", error);
|
if (error.message !== 'Unauthorized') {
|
||||||
|
console.error("Failed to fetch registered devices", error);
|
||||||
|
}
|
||||||
setDevices([]);
|
setDevices([]);
|
||||||
} finally {
|
} finally {
|
||||||
if (isInitialLoad || isManualRefresh) {
|
if (isInitialLoad || isManualRefresh) {
|
||||||
|
@ -95,7 +97,7 @@ export function RegisteredDevices() {
|
||||||
setIsInitialLoad(false);
|
setIsInitialLoad(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [token, isInitialLoad]);
|
}, [token, isInitialLoad, apiFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useEffect, useState, useCallback, useMemo } from "react";
|
import { useEffect, useState, useCallback, useMemo } from "react";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
|
import { useApi } from "@/hooks/use-api";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
@ -48,6 +49,7 @@ function formatVoicemailDate(dateStr) {
|
||||||
|
|
||||||
export function VoicemailPanel({ extensionId: extProp }) {
|
export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
|
const apiFetch = useApi();
|
||||||
const [extensionId, setExtensionId] = useState(extProp || null);
|
const [extensionId, setExtensionId] = useState(extProp || null);
|
||||||
const [loadingExt, setLoadingExt] = useState(!extProp);
|
const [loadingExt, setLoadingExt] = useState(!extProp);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
@ -65,30 +67,28 @@ export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
setLoadingExt(true);
|
setLoadingExt(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/me`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/me`);
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setExtensionId(data.extensionId);
|
setExtensionId(data.extensionId);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// silent
|
if (e.message !== 'Unauthorized') {
|
||||||
|
// silent
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingExt(false);
|
setLoadingExt(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
load();
|
load();
|
||||||
}, [token, extProp]);
|
}, [token, extProp, apiFetch]);
|
||||||
|
|
||||||
const fetchVoicemails = useCallback(async () => {
|
const fetchVoicemails = useCallback(async () => {
|
||||||
if (!token || !extensionId) return;
|
if (!token || !extensionId) return;
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
if (voicemails.length === 0) setLoading(true);
|
if (voicemails.length === 0) setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails`);
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setVoicemails(Array.isArray(data) ? data : []);
|
setVoicemails(Array.isArray(data) ? data : []);
|
||||||
|
@ -96,12 +96,14 @@ export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
setVoicemails([]);
|
setVoicemails([]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setVoicemails([]);
|
if (e.message !== 'Unauthorized') {
|
||||||
|
setVoicemails([]);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
}, [token, extensionId, voicemails.length]);
|
}, [token, extensionId, voicemails.length, apiFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (extensionId) fetchVoicemails();
|
if (extensionId) fetchVoicemails();
|
||||||
|
@ -128,15 +130,15 @@ export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
if (isOpening && vm.hasAudio && !audioMap[vm.messageId]?.url) {
|
if (isOpening && vm.hasAudio && !audioMap[vm.messageId]?.url) {
|
||||||
setAudioMap(m => ({ ...m, [vm.messageId]: { url: null, loading: true } }));
|
setAudioMap(m => ({ ...m, [vm.messageId]: { url: null, loading: true } }));
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vm.messageId}/download`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vm.messageId}/download`);
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Download failed');
|
if (!res.ok) throw new Error('Download failed');
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
setAudioMap(m => ({ ...m, [vm.messageId]: { url, loading: false } }));
|
setAudioMap(m => ({ ...m, [vm.messageId]: { url, loading: false } }));
|
||||||
} catch {
|
} catch(e) {
|
||||||
setAudioMap(m => ({ ...m, [vm.messageId]: { url: null, loading: false, error: true } }));
|
if (e.message !== 'Unauthorized') {
|
||||||
|
setAudioMap(m => ({ ...m, [vm.messageId]: { url: null, loading: false, error: true } }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -145,10 +147,9 @@ export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
if (target === vm.folder) return;
|
if (target === vm.folder) return;
|
||||||
setMovingId(vm.messageId);
|
setMovingId(vm.messageId);
|
||||||
try {
|
try {
|
||||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vm.messageId}/move`, {
|
await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vm.messageId}/move`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ targetFolder: target })
|
body: JSON.stringify({ targetFolder: target })
|
||||||
|
@ -157,8 +158,10 @@ export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
if (target !== activeFolder) {
|
if (target !== activeFolder) {
|
||||||
setExpandedId(null);
|
setExpandedId(null);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch(e) {
|
||||||
// silent fail
|
if (e.message !== 'Unauthorized') {
|
||||||
|
// silent fail
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setMovingId(null);
|
setMovingId(null);
|
||||||
}
|
}
|
||||||
|
@ -167,9 +170,7 @@ export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
const downloadVoicemail = async (vm) => {
|
const downloadVoicemail = async (vm) => {
|
||||||
if (!vm.hasAudio) return;
|
if (!vm.hasAudio) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vm.messageId}/download`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vm.messageId}/download`);
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
@ -180,24 +181,27 @@ export function VoicemailPanel({ extensionId: extProp }) {
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} catch {
|
} catch(e) {
|
||||||
// ignore
|
if (e.message !== 'Unauthorized') {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!vmToDelete || !token || !extensionId) return;
|
if (!vmToDelete || !token || !extensionId) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vmToDelete.messageId}`, {
|
const res = await apiFetch(`${process.env.NEXT_PUBLIC_API_URL}/extensions/${extensionId}/voicemails/${vmToDelete.messageId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setVoicemails(list => list.filter(v => v.messageId !== vmToDelete.messageId));
|
setVoicemails(list => list.filter(v => v.messageId !== vmToDelete.messageId));
|
||||||
if (expandedId === vmToDelete.messageId) setExpandedId(null);
|
if (expandedId === vmToDelete.messageId) setExpandedId(null);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch(e) {
|
||||||
// silent
|
if (e.message !== 'Unauthorized') {
|
||||||
|
// silent
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setVmToDelete(null);
|
setVmToDelete(null);
|
||||||
}
|
}
|
||||||
|
|
30
src/hooks/use-api.js
Normal file
30
src/hooks/use-api.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
|
||||||
|
export const useApi = () => {
|
||||||
|
const { token, logout } = useAuth();
|
||||||
|
|
||||||
|
const apiFetch = useCallback(async (url, options = {}) => {
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, { ...options, headers });
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
await logout();
|
||||||
|
throw new Error('Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}, [token, logout]);
|
||||||
|
|
||||||
|
return apiFetch;
|
||||||
|
};
|
Loading…
Reference in a new issue