Working UI with auth
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// BotsManagement.tsx
|
||||
"use client";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
@@ -10,17 +11,19 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { API_BASE_URL } from '@/constants/api';
|
||||
import { DiscordId, System, ErrorResponse } from '@/types'; // Import types
|
||||
import { DiscordId, System, ErrorResponse } from '@/types';
|
||||
import { authAwareFetch } from '@/utils/AuthAwareFetch';
|
||||
|
||||
interface BotsManagementProps {
|
||||
token: string;
|
||||
token: string | null; // Allow token to be null initially
|
||||
logoutUser: () => void;
|
||||
}
|
||||
|
||||
const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
const BotsManagement: React.FC<BotsManagementProps> = ({ token, logoutUser }) => {
|
||||
const [bots, setBots] = useState<string[]>([]);
|
||||
const [discordIds, setDiscordIds] = useState<DiscordId[]>([]);
|
||||
const [systems, setSystems] = useState<System[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState<boolean>(true); // Set to true initially
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
const [isAddIdDialogOpen, setIsAddIdDialogOpen] = useState<boolean>(false);
|
||||
@@ -39,15 +42,22 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
const [assignDismissAction, setAssignDismissAction] = useState<'assign' | 'dismiss'>('assign');
|
||||
|
||||
const fetchData = async (): Promise<void> => {
|
||||
// Only attempt to fetch if a token is present
|
||||
if (!token) {
|
||||
setLoading(false); // Stop loading if no token
|
||||
setError('Authentication token is missing. Please log in.');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
let accumulatedError = '';
|
||||
|
||||
try {
|
||||
const [nodesRes, discordIdsRes, systemsRes] = await Promise.all([
|
||||
fetch(`${API_BASE_URL}/nodes/`, { headers: { Authorization: `Bearer ${token}` } }),
|
||||
fetch(`${API_BASE_URL}/bots/tokens/`, { headers: { Authorization: `Bearer ${token}` } }),
|
||||
fetch(`${API_BASE_URL}/systems/`, { headers: { Authorization: `Bearer ${token}` } }),
|
||||
authAwareFetch('/nodes/', { token }, logoutUser),
|
||||
authAwareFetch('/bots/tokens/', { token }, logoutUser),
|
||||
authAwareFetch('/systems/', { token }, logoutUser),
|
||||
]);
|
||||
|
||||
if (nodesRes.ok) {
|
||||
@@ -91,6 +101,11 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
// Catch specific unauthorized errors and let authAwareFetch handle logout
|
||||
if (err.message === 'Unauthorized: Session expired or invalid token.') {
|
||||
setLoading(false); // already handled by authAwareFetch
|
||||
return;
|
||||
}
|
||||
setError('Failed to fetch data. Check server connection or console.');
|
||||
console.error(err);
|
||||
} finally {
|
||||
@@ -99,30 +114,43 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// This effect runs whenever 'token' changes.
|
||||
// If token becomes available, it triggers fetchData.
|
||||
if (token) {
|
||||
fetchData();
|
||||
} else {
|
||||
// If token is explicitly null (e.g., after logout or initial load without a token)
|
||||
setLoading(false);
|
||||
// No need to set error "Please log in" here, as AppContent handles the redirect
|
||||
// This is a component internal message if it somehow renders without a token when it shouldn't
|
||||
setError('Authentication required to load data.');
|
||||
setBots([]); // Clear data if token is gone
|
||||
setDiscordIds([]);
|
||||
setSystems([]);
|
||||
}
|
||||
}, [token]);
|
||||
}, [token]); // Dependency on token ensures fetchData is called when token becomes available
|
||||
|
||||
|
||||
const handleAddId = async (): Promise<void> => {
|
||||
setError('');
|
||||
if (!token) {
|
||||
setError('Authentication token is missing. Please log in.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload: Omit<DiscordId, '_id'> = {
|
||||
...newIdData,
|
||||
guild_ids: newIdData.guild_ids.split(',').map(id => id.trim()).filter(id => id),
|
||||
};
|
||||
const method = editingId ? 'PUT' : 'POST';
|
||||
const url = editingId ? `${API_BASE_URL}/bots/token/${editingId._id}` : `${API_BASE_URL}/bots/token`;
|
||||
const url = editingId ? `/bots/token/${editingId._id}` : `/bots/token`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const response = await authAwareFetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
token,
|
||||
}, logoutUser);
|
||||
|
||||
if (response.ok) {
|
||||
fetchData();
|
||||
@@ -138,6 +166,10 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
setError(errorMsg);
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.message === 'Unauthorized: Session expired or invalid token.') {
|
||||
setLoading(false); // already handled by authAwareFetch
|
||||
return;
|
||||
}
|
||||
setError(`Network error during ${editingId ? 'update' : 'add'} Discord ID.`);
|
||||
console.error(err);
|
||||
}
|
||||
@@ -146,11 +178,12 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
const handleDeleteId = async (id: string): Promise<void> => {
|
||||
if (!window.confirm('Are you sure you want to delete this Discord ID?')) return;
|
||||
setError('');
|
||||
if (!token) {
|
||||
setError('Authentication token is missing. Please log in.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/bots/token/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
const response = await authAwareFetch(`/bots/token/${id}`, { method: 'DELETE', token }, logoutUser);
|
||||
if (response.ok) {
|
||||
fetchData();
|
||||
} else {
|
||||
@@ -162,6 +195,10 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
setError(errorMsg);
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.message === 'Unauthorized: Session expired or invalid token.') {
|
||||
setLoading(false); // already handled by authAwareFetch
|
||||
return;
|
||||
}
|
||||
setError('Network error during delete Discord ID.');
|
||||
console.error(err);
|
||||
}
|
||||
@@ -173,16 +210,18 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
setError("Bot Client ID and System must be selected.");
|
||||
return;
|
||||
}
|
||||
if (!token) {
|
||||
setError('Authentication token is missing. Please log in.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const endpoint = assignDismissAction === 'assign' ? 'assign' : 'dismiss';
|
||||
const response = await fetch(`${API_BASE_URL}/systems/${selectedSystemId}/${endpoint}`, {
|
||||
const response = await authAwareFetch(`/systems/${selectedSystemId}/${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ client_id: selectedBotClientId }),
|
||||
});
|
||||
token,
|
||||
}, logoutUser);
|
||||
if (response.ok) {
|
||||
fetchData();
|
||||
setIsAssignDismissDialogOpen(false);
|
||||
@@ -197,13 +236,21 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
setError(errorMsg);
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.message === 'Unauthorized: Session expired or invalid token.') {
|
||||
setLoading(false); // already handled by authAwareFetch
|
||||
return;
|
||||
}
|
||||
setError(`Network error during ${assignDismissAction} bot.`);
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <p>Loading bots and Discord IDs...</p>;
|
||||
if (error && !loading) return <p className="text-red-500 whitespace-pre-line">{error}</p>;
|
||||
// Only show loading if actively fetching or waiting for token
|
||||
if (loading && token) return <p>Loading bots and Discord IDs...</p>;
|
||||
// Show error if there's an error
|
||||
if (error) return <p className="text-red-500 whitespace-pre-line">{error}</p>;
|
||||
// If not loading and no token, it implies no action can be taken or a login is needed
|
||||
if (!loading && !token) return <p className="text-orange-500">Please log in to view and manage bots.</p>;
|
||||
|
||||
|
||||
return (
|
||||
@@ -252,8 +299,8 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-2">Manage Discord IDs</h3>
|
||||
<Button onClick={() => { setEditingId(null); setNewIdData({ discord_id: '', name: '', token: '', active: false, guild_ids: '' }); setIsAddIdDialogOpen(true);}} className="mb-4">Add New Discord ID</Button>
|
||||
<Button onClick={() => setIsAssignDismissDialogOpen(true)} className="mb-4 ml-2">Assign/Dismiss Bot System</Button>
|
||||
<Button onClick={() => { setEditingId(null); setNewIdData({ discord_id: '', name: '', token: '', active: false, guild_ids: '' }); setIsAddIdDialogOpen(true);}} className="mb-4" disabled={!token}>Add New Discord ID</Button>
|
||||
<Button onClick={() => setIsAssignDismissDialogOpen(true)} className="mb-4 ml-2" disabled={!token}>Assign/Dismiss Bot System</Button>
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
@@ -287,10 +334,11 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
guild_ids: dId.guild_ids?.join(', '),
|
||||
});
|
||||
setIsAddIdDialogOpen(true);
|
||||
}}>
|
||||
}}
|
||||
disabled={!token}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" onClick={() => handleDeleteId(dId._id)}>
|
||||
<Button variant="destructive" size="sm" onClick={() => handleDeleteId(dId._id)} disabled={!token}>
|
||||
Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
@@ -359,7 +407,9 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={handleAddId}>{editingId ? 'Save Changes' : 'Add Discord ID'}</Button>
|
||||
<Button onClick={handleAddId} disabled={!token}>
|
||||
{editingId ? 'Save Changes' : 'Add Discord ID'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -413,7 +463,7 @@ const BotsManagement: React.FC<BotsManagementProps> = ({ token }) => {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={handleAssignDismiss} disabled={!selectedBotClientId || !selectedSystemId}>Perform Action</Button>
|
||||
<Button onClick={handleAssignDismiss} disabled={!selectedBotClientId || !selectedSystemId || !token}>Perform Action</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user