mirror of
https://github.com/philipredstone/relnet.git
synced 2025-07-08 14:56:39 +02:00
redesign
This commit is contained in:
@ -2,6 +2,8 @@ import React, { useState } from 'react';
|
||||
import { useNetworks } from '../../context/NetworkContext';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { FaPlus, FaNetworkWired, FaTrash, FaEye, FaGlobe, FaLock, FaTimes } from 'react-icons/fa';
|
||||
|
||||
const NetworkList: React.FC = () => {
|
||||
const { networks, loading, error, createNetwork, deleteNetwork } = useNetworks();
|
||||
@ -59,216 +61,285 @@ const NetworkList: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Filter networks by ownership
|
||||
const myNetworks = networks.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId === user.id;
|
||||
});
|
||||
|
||||
const publicNetworks = networks.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId !== user.id;
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <div className="flex justify-center p-8">Loading networks...</div>;
|
||||
return (
|
||||
<div className="flex justify-center items-center h-screen bg-slate-900">
|
||||
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-indigo-500"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-2xl font-bold">My Networks</h1>
|
||||
<button
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
onClick={() => setShowCreateForm(!showCreateForm)}
|
||||
>
|
||||
{showCreateForm ? 'Cancel' : 'Create New Network'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{error}
|
||||
<div className="bg-slate-900 min-h-screen text-white p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h1 className="text-2xl font-bold text-indigo-400">
|
||||
<FaNetworkWired className="inline-block mr-2" />
|
||||
My Networks
|
||||
</h1>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-lg
|
||||
shadow-lg transition-colors duration-200 flex items-center"
|
||||
onClick={() => setShowCreateForm(!showCreateForm)}
|
||||
>
|
||||
{showCreateForm ? <FaTimes className="mr-2" /> : <FaPlus className="mr-2" />}
|
||||
{showCreateForm ? 'Cancel' : 'Create New Network'}
|
||||
</motion.button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Create Network Form */}
|
||||
{showCreateForm && (
|
||||
<div className="bg-gray-100 p-4 rounded-lg mb-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Create New Network</h2>
|
||||
|
||||
{formError && (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{formError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleCreateNetwork}>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="name">
|
||||
Network Name *
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={newNetworkName}
|
||||
onChange={e => setNewNetworkName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="description">
|
||||
Description (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={newNetworkDescription}
|
||||
onChange={e => setNewNetworkDescription(e.target.value)}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mr-2"
|
||||
checked={isPublic}
|
||||
onChange={e => setIsPublic(e.target.checked)}
|
||||
/>
|
||||
<span className="text-gray-700 text-sm font-bold">Make this network public</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
|
||||
disabled={createLoading}
|
||||
>
|
||||
{createLoading ? 'Creating...' : 'Create Network'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Networks List */}
|
||||
{networks.length === 0 ? (
|
||||
<div className="bg-white p-8 rounded-lg text-center">
|
||||
<p className="text-gray-600 mb-4">You don't have any networks yet.</p>
|
||||
{!showCreateForm && (
|
||||
<button
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
onClick={() => setShowCreateForm(true)}
|
||||
>
|
||||
Create Your First Network
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* My Networks Section */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-semibold mb-4">My Networks</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{networks
|
||||
.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId =
|
||||
typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId === user.id;
|
||||
})
|
||||
.map(network => (
|
||||
<div key={network._id} className="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
|
||||
{network.description && (
|
||||
<p className="text-gray-600 mb-4">{network.description}</p>
|
||||
)}
|
||||
<div className="flex items-center mb-4">
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full text-xs ${network.isPublic ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
|
||||
>
|
||||
{network.isPublic ? 'Public' : 'Private'}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 ml-2">
|
||||
Created: {new Date(network.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
className="flex-1 bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
|
||||
onClick={() => navigate(`/networks/${network._id}`)}
|
||||
>
|
||||
View
|
||||
</button>
|
||||
<button
|
||||
className="flex-1 bg-red-500 hover:bg-red-700 text-white py-2 px-4 rounded"
|
||||
onClick={() => handleDeleteNetwork(network._id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{networks.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId === user.id;
|
||||
}).length === 0 && (
|
||||
<p className="text-gray-600 mb-4">You haven't created any networks yet.</p>
|
||||
)}
|
||||
{error && (
|
||||
<div className="bg-red-900 border border-red-700 text-white px-4 py-3 rounded-lg mb-6">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Public Networks Section */}
|
||||
{networks.some(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId !== user.id;
|
||||
}) && (
|
||||
{/* Create Network Form */}
|
||||
<AnimatePresence>
|
||||
{showCreateForm && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
className="bg-slate-800 p-6 rounded-lg shadow-xl mb-8 border border-slate-700"
|
||||
>
|
||||
<h2 className="text-xl font-semibold mb-4 text-indigo-300">Create New Network</h2>
|
||||
|
||||
{formError && (
|
||||
<div className="bg-red-900 border border-red-700 text-white px-4 py-3 rounded-lg mb-4">
|
||||
{formError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleCreateNetwork} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-slate-300 text-sm font-medium mb-2" htmlFor="name">
|
||||
Network Name *
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 text-white rounded-lg
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
value={newNetworkName}
|
||||
onChange={e => setNewNetworkName(e.target.value)}
|
||||
required
|
||||
placeholder="Enter network name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 text-sm font-medium mb-2" htmlFor="description">
|
||||
Description (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 text-white rounded-lg
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
value={newNetworkDescription}
|
||||
onChange={e => setNewNetworkDescription(e.target.value)}
|
||||
rows={3}
|
||||
placeholder="Describe your network"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-checkbox h-5 w-5 text-indigo-600 bg-slate-700 border-slate-600
|
||||
focus:ring-indigo-500 rounded"
|
||||
checked={isPublic}
|
||||
onChange={e => setIsPublic(e.target.checked)}
|
||||
/>
|
||||
<span className="ml-2 text-slate-300 text-sm font-medium">Make this network public</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
type="submit"
|
||||
className="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4
|
||||
rounded-lg shadow-md transition-colors duration-200 flex items-center justify-center"
|
||||
disabled={createLoading}
|
||||
>
|
||||
{createLoading ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white mr-2"></div>
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaPlus className="mr-2" /> Create Network
|
||||
</>
|
||||
)}
|
||||
</motion.button>
|
||||
</form>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Networks List */}
|
||||
{networks.length === 0 ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="bg-slate-800 p-8 rounded-lg text-center border border-slate-700 shadow-xl"
|
||||
>
|
||||
<div className="flex justify-center mb-4">
|
||||
<FaNetworkWired className="text-4xl text-slate-400" />
|
||||
</div>
|
||||
<p className="text-slate-300 mb-6">You don't have any networks yet.</p>
|
||||
{!showCreateForm && (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6
|
||||
rounded-lg shadow-lg transition-colors duration-200 flex items-center mx-auto"
|
||||
onClick={() => setShowCreateForm(true)}
|
||||
>
|
||||
<FaPlus className="mr-2" /> Create Your First Network
|
||||
</motion.button>
|
||||
)}
|
||||
</motion.div>
|
||||
) : (
|
||||
<div className="space-y-12">
|
||||
{/* My Networks Section */}
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">Public Networks From Others</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{networks
|
||||
.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId =
|
||||
typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId !== user.id;
|
||||
})
|
||||
.map(network => (
|
||||
<div
|
||||
<h2 className="text-xl font-semibold mb-4 text-indigo-300 flex items-center">
|
||||
<FaNetworkWired className="mr-2" /> My Networks ({myNetworks.length})
|
||||
</h2>
|
||||
|
||||
{myNetworks.length === 0 ? (
|
||||
<p className="text-slate-400 bg-slate-800 p-4 rounded-lg border border-slate-700">
|
||||
You haven't created any networks yet.
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{myNetworks.map(network => (
|
||||
<motion.div
|
||||
key={network._id}
|
||||
className="bg-white rounded-lg shadow-md overflow-hidden border-l-4 border-green-500"
|
||||
whileHover={{ y: -5 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-slate-800 rounded-lg shadow-xl overflow-hidden border border-slate-700 hover:border-indigo-500 transition-colors duration-200"
|
||||
>
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
|
||||
{network.description && (
|
||||
<p className="text-gray-600 mb-4">{network.description}</p>
|
||||
)}
|
||||
<div className="flex items-center mb-4">
|
||||
<span className="px-2 py-1 rounded-full text-xs bg-green-100 text-green-800">
|
||||
Public
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 ml-2">
|
||||
Created: {new Date(network.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 ml-2">
|
||||
By:{' '}
|
||||
{typeof network.owner === 'string' ? 'Unknown' : network.owner.username}
|
||||
</span>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h3 className="text-xl font-bold text-white">{network.name}</h3>
|
||||
{network.isPublic ? (
|
||||
<FaGlobe className="text-green-400" title="Public" />
|
||||
) : (
|
||||
<FaLock className="text-amber-400" title="Private" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
className="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
|
||||
|
||||
{network.description && (
|
||||
<p className="text-slate-300 mb-4 text-sm">{network.description}</p>
|
||||
)}
|
||||
|
||||
<div className="text-xs text-slate-400 mb-6">
|
||||
Created: {new Date(network.createdAt).toLocaleDateString()}
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4
|
||||
rounded-lg transition-colors duration-200 flex items-center justify-center"
|
||||
onClick={() => navigate(`/networks/${network._id}`)}
|
||||
>
|
||||
View
|
||||
</button>
|
||||
<FaEye className="mr-2" /> View
|
||||
</motion.button>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex-1 bg-red-700 hover:bg-red-800 text-white py-2 px-4
|
||||
rounded-lg transition-colors duration-200 flex items-center justify-center"
|
||||
onClick={() => handleDeleteNetwork(network._id)}
|
||||
>
|
||||
<FaTrash className="mr-2" /> Delete
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Public Networks Section */}
|
||||
{publicNetworks.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4 text-indigo-300 flex items-center">
|
||||
<FaGlobe className="mr-2" /> Public Networks ({publicNetworks.length})
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{publicNetworks.map(network => (
|
||||
<motion.div
|
||||
key={network._id}
|
||||
whileHover={{ y: -5 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-slate-800 rounded-lg shadow-xl overflow-hidden border border-slate-700 border-l-4 border-l-green-500"
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h3 className="text-xl font-bold text-white">{network.name}</h3>
|
||||
<FaGlobe className="text-green-400" title="Public" />
|
||||
</div>
|
||||
|
||||
{network.description && (
|
||||
<p className="text-slate-300 mb-4 text-sm">{network.description}</p>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between mb-6">
|
||||
<span className="text-xs text-slate-400">
|
||||
Created: {new Date(network.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-xs font-medium text-green-400">
|
||||
By: {typeof network.owner === 'string' ? 'Unknown' : network.owner.username}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4
|
||||
rounded-lg transition-colors duration-200 flex items-center justify-center"
|
||||
onClick={() => navigate(`/networks/${network._id}`)}
|
||||
>
|
||||
<FaEye className="mr-2" /> View Network
|
||||
</motion.button>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkList;
|
||||
export default NetworkList;
|
Reference in New Issue
Block a user