add prettifier

This commit is contained in:
philipredstone
2025-04-15 14:46:06 +02:00
parent c078610c4d
commit eceacf2117
43 changed files with 10946 additions and 6173 deletions

16
frontend/.prettierignore Normal file
View File

@ -0,0 +1,16 @@
# Ignore build outputs
/dist
/build
# Ignore dependencies
/node_modules
# Ignore coverage reports
/coverage
# Ignore logs
*.log
# Ignore configuration files
.env
.env.*

12
frontend/.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "es5",
"arrowParens": "avoid",
"endOfLine": "lf",
"bracketSpacing": true,
"jsxSingleQuote": false,
"bracketSameLine": false
}

2853
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,9 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"format": "prettier --write \"src/**/*.{tsx,ts,js,jsx,json,css,html}\"",
"format:check": "prettier --check \"src/**/*.{tsx,ts,js,jsx,json,css,html}\""
},
"author": "",
"license": "ISC",
@ -22,6 +24,7 @@
"@types/react": "^19.1.2",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.4.0",
"prettier": "^3.5.3",
"ts-node": "^10.9.2",
"typescript": "^5.8.3",
"vite": "^6.2.6",

View File

@ -1,67 +1,67 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import { NetworkProvider } from './context/NetworkContext';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import NetworkList from './components/networks/NetworkList';
import FriendshipNetwork from './components/FriendshipNetwork'; // Your existing component
import Header from './components/layout/Header';
// Protected route component
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user, loading } = useAuth();
if (loading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}
if (!user) {
return <Navigate to="/login" />;
}
return <>{children}</>;
};
const App: React.FC = () => {
return (
<AuthProvider>
<NetworkProvider>
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow">
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/networks"
element={
<ProtectedRoute>
<NetworkList />
</ProtectedRoute>
}
/>
<Route
path="/networks/:id"
element={
<ProtectedRoute>
<FriendshipNetwork />
</ProtectedRoute>
}
/>
<Route path="/" element={<Navigate to="/networks" />} />
<Route path="*" element={<Navigate to="/networks" />} />
</Routes>
</main>
</div>
</Router>
</NetworkProvider>
</AuthProvider>
);
};
export default App;
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import { NetworkProvider } from './context/NetworkContext';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import NetworkList from './components/networks/NetworkList';
import FriendshipNetwork from './components/FriendshipNetwork'; // Your existing component
import Header from './components/layout/Header';
// Protected route component
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user, loading } = useAuth();
if (loading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}
if (!user) {
return <Navigate to="/login" />;
}
return <>{children}</>;
};
const App: React.FC = () => {
return (
<AuthProvider>
<NetworkProvider>
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow">
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/networks"
element={
<ProtectedRoute>
<NetworkList />
</ProtectedRoute>
}
/>
<Route
path="/networks/:id"
element={
<ProtectedRoute>
<FriendshipNetwork />
</ProtectedRoute>
}
/>
<Route path="/" element={<Navigate to="/networks" />} />
<Route path="*" element={<Navigate to="/networks" />} />
</Routes>
</main>
</div>
</Router>
</NetworkProvider>
</AuthProvider>
);
};
export default App;

View File

@ -1,60 +1,60 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Configure axios
axios.defaults.withCredentials = true;
// Types
export interface RegisterData {
username: string;
email: string;
password: string;
}
export interface LoginData {
email: string;
password: string;
}
export interface User {
id: string;
username: string;
email: string;
}
export interface AuthResponse {
success: boolean;
user: User;
}
// Register user
export const register = async (data: RegisterData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/register`, data);
return response.data.user;
};
// Login user
export const login = async (data: LoginData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/login`, data);
return response.data.user;
};
// Logout user
export const logout = async (): Promise<void> => {
await axios.post(`${API_URL}/auth/logout`);
};
// Get current user
export const getCurrentUser = async (): Promise<User | null> => {
try {
const response = await axios.get<AuthResponse>(`${API_URL}/auth/me`);
return response.data.user;
} catch (error) {
return null;
}
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Configure axios
axios.defaults.withCredentials = true;
// Types
export interface RegisterData {
username: string;
email: string;
password: string;
}
export interface LoginData {
email: string;
password: string;
}
export interface User {
id: string;
username: string;
email: string;
}
export interface AuthResponse {
success: boolean;
user: User;
}
// Register user
export const register = async (data: RegisterData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/register`, data);
return response.data.user;
};
// Login user
export const login = async (data: LoginData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/login`, data);
return response.data.user;
};
// Logout user
export const logout = async (): Promise<void> => {
await axios.post(`${API_URL}/auth/logout`);
};
// Get current user
export const getCurrentUser = async (): Promise<User | null> => {
try {
const response = await axios.get<AuthResponse>(`${API_URL}/auth/me`);
return response.data.user;
} catch (error) {
return null;
}
};

View File

@ -1,64 +1,72 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface NetworkOwner {
_id: string;
username: string;
}
export interface Network {
_id: string;
name: string;
description?: string;
owner: string | NetworkOwner; // Can be either string ID or populated object
isPublic: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateNetworkData {
name: string;
description?: string;
isPublic?: boolean;
}
export interface UpdateNetworkData {
name?: string;
description?: string;
isPublic?: boolean;
}
// Get all networks for current user
export const getUserNetworks = async (): Promise<Network[]> => {
const response = await axios.get<{ success: boolean; data: Network[] }>(`${API_URL}/networks`);
return response.data.data;
};
// Create a new network
export const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
const response = await axios.post<{ success: boolean; data: Network }>(`${API_URL}/networks`, data);
return response.data.data;
};
// Get a specific network
export const getNetwork = async (id: string): Promise<Network> => {
const response = await axios.get<{ success: boolean; data: Network }>(`${API_URL}/networks/${id}`);
return response.data.data;
};
// Update a network
export const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
const response = await axios.put<{ success: boolean; data: Network }>(`${API_URL}/networks/${id}`, data);
return response.data.data;
};
// Delete a network
export const deleteNetwork = async (id: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${id}`);
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface NetworkOwner {
_id: string;
username: string;
}
export interface Network {
_id: string;
name: string;
description?: string;
owner: string | NetworkOwner; // Can be either string ID or populated object
isPublic: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateNetworkData {
name: string;
description?: string;
isPublic?: boolean;
}
export interface UpdateNetworkData {
name?: string;
description?: string;
isPublic?: boolean;
}
// Get all networks for current user
export const getUserNetworks = async (): Promise<Network[]> => {
const response = await axios.get<{ success: boolean; data: Network[] }>(`${API_URL}/networks`);
return response.data.data;
};
// Create a new network
export const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
const response = await axios.post<{ success: boolean; data: Network }>(
`${API_URL}/networks`,
data
);
return response.data.data;
};
// Get a specific network
export const getNetwork = async (id: string): Promise<Network> => {
const response = await axios.get<{ success: boolean; data: Network }>(
`${API_URL}/networks/${id}`
);
return response.data.data;
};
// Update a network
export const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
const response = await axios.put<{ success: boolean; data: Network }>(
`${API_URL}/networks/${id}`,
data
);
return response.data.data;
};
// Delete a network
export const deleteNetwork = async (id: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${id}`);
};

View File

@ -1,77 +1,77 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface Person {
_id: string;
firstName: string;
lastName: string;
birthday?: string;
network: string;
position: {
x: number;
y: number;
};
createdAt: string;
updatedAt: string;
}
export interface CreatePersonData {
firstName: string;
lastName: string;
birthday?: string;
position?: {
x: number;
y: number;
};
}
export interface UpdatePersonData {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: {
x: number;
y: number;
};
}
// Get all people in a network
export const getPeople = async (networkId: string): Promise<Person[]> => {
const response = await axios.get<{ success: boolean; data: Person[] }>(
`${API_URL}/networks/${networkId}/people`
);
return response.data.data;
};
// Add a person to the network
export const addPerson = async (networkId: string, data: CreatePersonData): Promise<Person> => {
const response = await axios.post<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people`,
data
);
return response.data.data;
};
// Update a person
export const updatePerson = async (
networkId: string,
personId: string,
data: UpdatePersonData
): Promise<Person> => {
const response = await axios.put<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people/${personId}`,
data
);
return response.data.data;
};
// Remove a person from the network
export const removePerson = async (networkId: string, personId: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/people/${personId}`);
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface Person {
_id: string;
firstName: string;
lastName: string;
birthday?: string;
network: string;
position: {
x: number;
y: number;
};
createdAt: string;
updatedAt: string;
}
export interface CreatePersonData {
firstName: string;
lastName: string;
birthday?: string;
position?: {
x: number;
y: number;
};
}
export interface UpdatePersonData {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: {
x: number;
y: number;
};
}
// Get all people in a network
export const getPeople = async (networkId: string): Promise<Person[]> => {
const response = await axios.get<{ success: boolean; data: Person[] }>(
`${API_URL}/networks/${networkId}/people`
);
return response.data.data;
};
// Add a person to the network
export const addPerson = async (networkId: string, data: CreatePersonData): Promise<Person> => {
const response = await axios.post<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people`,
data
);
return response.data.data;
};
// Update a person
export const updatePerson = async (
networkId: string,
personId: string,
data: UpdatePersonData
): Promise<Person> => {
const response = await axios.put<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people/${personId}`,
data
);
return response.data.data;
};
// Remove a person from the network
export const removePerson = async (networkId: string, personId: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/people/${personId}`);
};

View File

@ -1,69 +1,72 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface Relationship {
_id: string;
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
network: string;
createdAt: string;
updatedAt: string;
}
export interface CreateRelationshipData {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
export interface UpdateRelationshipData {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
// Get all relationships in a network
export const getRelationships = async (networkId: string): Promise<Relationship[]> => {
const response = await axios.get<{ success: boolean; data: Relationship[] }>(
`${API_URL}/networks/${networkId}/relationships`
);
return response.data.data;
};
// Add a relationship to the network
export const addRelationship = async (
networkId: string,
data: CreateRelationshipData
): Promise<Relationship> => {
const response = await axios.post<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships`,
data
);
return response.data.data;
};
// Update a relationship
export const updateRelationship = async (
networkId: string,
relationshipId: string,
data: UpdateRelationshipData
): Promise<Relationship> => {
const response = await axios.put<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships/${relationshipId}`,
data
);
return response.data.data;
};
// Remove a relationship
export const removeRelationship = async (networkId: string, relationshipId: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/relationships/${relationshipId}`);
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface Relationship {
_id: string;
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
network: string;
createdAt: string;
updatedAt: string;
}
export interface CreateRelationshipData {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
export interface UpdateRelationshipData {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
// Get all relationships in a network
export const getRelationships = async (networkId: string): Promise<Relationship[]> => {
const response = await axios.get<{ success: boolean; data: Relationship[] }>(
`${API_URL}/networks/${networkId}/relationships`
);
return response.data.data;
};
// Add a relationship to the network
export const addRelationship = async (
networkId: string,
data: CreateRelationshipData
): Promise<Relationship> => {
const response = await axios.post<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships`,
data
);
return response.data.data;
};
// Update a relationship
export const updateRelationship = async (
networkId: string,
relationshipId: string,
data: UpdateRelationshipData
): Promise<Relationship> => {
const response = await axios.put<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships/${relationshipId}`,
data
);
return response.data.data;
};
// Remove a relationship
export const removeRelationship = async (
networkId: string,
relationshipId: string
): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/relationships/${relationshipId}`);
};

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +1,89 @@
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
await login({ email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Login failed. Please check your credentials.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Login</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
</button>
<a
href="/register"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Register
</a>
</div>
</form>
</div>
</div>
);
};
export default Login;
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
await login({ email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Login failed. Please check your credentials.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Login</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
</button>
<a
href="/register"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Register
</a>
</div>
</form>
</div>
</div>
);
};
export default Login;

View File

@ -1,131 +1,131 @@
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Register: React.FC = () => {
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { register } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
// Basic validation
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}
if (password.length < 6) {
setError('Password must be at least 6 characters');
return;
}
setLoading(true);
try {
await register({ username, email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Registration failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Register</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="username">
Username
</label>
<input
id="username"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="confirmPassword">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Registering...' : 'Register'}
</button>
<a
href="/login"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Login
</a>
</div>
</form>
</div>
</div>
);
};
export default Register;
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Register: React.FC = () => {
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { register } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
// Basic validation
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}
if (password.length < 6) {
setError('Password must be at least 6 characters');
return;
}
setLoading(true);
try {
await register({ username, email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Registration failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Register</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="username">
Username
</label>
<input
id="username"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={username}
onChange={e => setUsername(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="confirmPassword">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Registering...' : 'Register'}
</button>
<a
href="/login"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Login
</a>
</div>
</form>
</div>
</div>
);
};
export default Register;

View File

@ -1,58 +1,58 @@
import React from 'react';
import {Link, useNavigate} from 'react-router-dom';
import {useAuth} from '../../context/AuthContext';
const Header: React.FC = () => {
const {user, logout} = useAuth();
const navigate = useNavigate();
const handleLogout = async () => {
try {
await logout();
navigate('/login');
} catch (error) {
console.error('Logout failed:', error);
}
};
return (
<header className="bg-blue-600 text-white shadow-md">
<div className="container mx-auto py-4 px-6 flex justify-between items-center">
<Link to="/" className="text-xl font-bold">
Friendship Network
</Link>
<nav>
{user ? (
<div className="flex items-center space-x-4">
<span>Hello, {user.username}</span>
<Link to="/networks" className="hover:underline">
My Networks
</Link>
<button
onClick={handleLogout}
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded"
>
Logout
</button>
</div>
) : (
<div className="space-x-4">
<Link to="/login" className="hover:underline">
Login
</Link>
<Link
to="/register"
className="bg-white text-blue-600 hover:bg-gray-100 font-bold py-1 px-3 rounded"
>
Register
</Link>
</div>
)}
</nav>
</div>
</header>
);
};
export default Header;
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
const Header: React.FC = () => {
const { user, logout } = useAuth();
const navigate = useNavigate();
const handleLogout = async () => {
try {
await logout();
navigate('/login');
} catch (error) {
console.error('Logout failed:', error);
}
};
return (
<header className="bg-blue-600 text-white shadow-md">
<div className="container mx-auto py-4 px-6 flex justify-between items-center">
<Link to="/" className="text-xl font-bold">
Friendship Network
</Link>
<nav>
{user ? (
<div className="flex items-center space-x-4">
<span>Hello, {user.username}</span>
<Link to="/networks" className="hover:underline">
My Networks
</Link>
<button
onClick={handleLogout}
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded"
>
Logout
</button>
</div>
) : (
<div className="space-x-4">
<Link to="/login" className="hover:underline">
Login
</Link>
<Link
to="/register"
className="bg-white text-blue-600 hover:bg-gray-100 font-bold py-1 px-3 rounded"
>
Register
</Link>
</div>
)}
</nav>
</div>
</header>
);
};
export default Header;

View File

@ -1,274 +1,274 @@
import React, { useState } from 'react';
import { useNetworks } from '../../context/NetworkContext';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const NetworkList: React.FC = () => {
const { networks, loading, error, createNetwork, deleteNetwork } = useNetworks();
const { user } = useAuth();
const [showCreateForm, setShowCreateForm] = useState(false);
const [newNetworkName, setNewNetworkName] = useState('');
const [newNetworkDescription, setNewNetworkDescription] = useState('');
const [isPublic, setIsPublic] = useState(false);
const [formError, setFormError] = useState<string | null>(null);
const [createLoading, setCreateLoading] = useState(false);
const navigate = useNavigate();
const handleCreateNetwork = async (e: React.FormEvent) => {
e.preventDefault();
setFormError(null);
if (!newNetworkName.trim()) {
setFormError('Network name is required');
return;
}
setCreateLoading(true);
try {
const network = await createNetwork({
name: newNetworkName.trim(),
description: newNetworkDescription.trim() || undefined,
isPublic
});
// Reset form
setNewNetworkName('');
setNewNetworkDescription('');
setIsPublic(false);
setShowCreateForm(false);
// Navigate to the new network
navigate(`/networks/${network._id}`);
} catch (err: any) {
setFormError(err.response?.data?.message || 'Failed to create network');
} finally {
setCreateLoading(false);
}
};
const handleDeleteNetwork = async (id: string) => {
if (window.confirm('Are you sure you want to delete this network? This action cannot be undone.')) {
try {
await deleteNetwork(id);
} catch (err: any) {
alert(err.response?.data?.message || 'Failed to delete network');
}
}
};
if (loading) {
return <div className="flex justify-center p-8">Loading networks...</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>
)}
{/* 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>
)}
</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;
}) && (
<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 key={network._id} className="bg-white rounded-lg shadow-md overflow-hidden border-l-4 border-green-500">
<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>
<div className="flex space-x-2">
<button
className="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
</>
)}
</div>
);
};
export default NetworkList;
import React, { useState } from 'react';
import { useNetworks } from '../../context/NetworkContext';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const NetworkList: React.FC = () => {
const { networks, loading, error, createNetwork, deleteNetwork } = useNetworks();
const { user } = useAuth();
const [showCreateForm, setShowCreateForm] = useState(false);
const [newNetworkName, setNewNetworkName] = useState('');
const [newNetworkDescription, setNewNetworkDescription] = useState('');
const [isPublic, setIsPublic] = useState(false);
const [formError, setFormError] = useState<string | null>(null);
const [createLoading, setCreateLoading] = useState(false);
const navigate = useNavigate();
const handleCreateNetwork = async (e: React.FormEvent) => {
e.preventDefault();
setFormError(null);
if (!newNetworkName.trim()) {
setFormError('Network name is required');
return;
}
setCreateLoading(true);
try {
const network = await createNetwork({
name: newNetworkName.trim(),
description: newNetworkDescription.trim() || undefined,
isPublic,
});
// Reset form
setNewNetworkName('');
setNewNetworkDescription('');
setIsPublic(false);
setShowCreateForm(false);
// Navigate to the new network
navigate(`/networks/${network._id}`);
} catch (err: any) {
setFormError(err.response?.data?.message || 'Failed to create network');
} finally {
setCreateLoading(false);
}
};
const handleDeleteNetwork = async (id: string) => {
if (
window.confirm('Are you sure you want to delete this network? This action cannot be undone.')
) {
try {
await deleteNetwork(id);
} catch (err: any) {
alert(err.response?.data?.message || 'Failed to delete network');
}
}
};
if (loading) {
return <div className="flex justify-center p-8">Loading networks...</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>
)}
{/* 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>
)}
</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;
}) && (
<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
key={network._id}
className="bg-white rounded-lg shadow-md overflow-hidden border-l-4 border-green-500"
>
<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>
<div className="flex space-x-2">
<button
className="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
</>
)}
</div>
);
};
export default NetworkList;

View File

@ -1,70 +1,78 @@
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import { User, getCurrentUser, login as apiLogin, register as apiRegister, logout as apiLogout, LoginData, RegisterData } from '../api/auth';
interface AuthContextProps {
user: User | null;
loading: boolean;
login: (data: LoginData) => Promise<void>;
register: (data: RegisterData) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextProps>({} as AuthContextProps);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadUser = async () => {
try {
const currentUser = await getCurrentUser();
setUser(currentUser);
} catch (error) {
console.error('Error loading user:', error);
} finally {
setLoading(false);
}
};
loadUser();
}, []);
const login = async (data: LoginData) => {
try {
const loggedInUser = await apiLogin(data);
setUser(loggedInUser);
} catch (error) {
console.error('Login error:', error);
throw error;
}
};
const register = async (data: RegisterData) => {
try {
const newUser = await apiRegister(data);
setUser(newUser);
} catch (error) {
console.error('Registration error:', error);
throw error;
}
};
const logout = async () => {
try {
await apiLogout();
setUser(null);
} catch (error) {
console.error('Logout error:', error);
throw error;
}
};
return (
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import {
User,
getCurrentUser,
login as apiLogin,
register as apiRegister,
logout as apiLogout,
LoginData,
RegisterData,
} from '../api/auth';
interface AuthContextProps {
user: User | null;
loading: boolean;
login: (data: LoginData) => Promise<void>;
register: (data: RegisterData) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextProps>({} as AuthContextProps);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadUser = async () => {
try {
const currentUser = await getCurrentUser();
setUser(currentUser);
} catch (error) {
console.error('Error loading user:', error);
} finally {
setLoading(false);
}
};
loadUser();
}, []);
const login = async (data: LoginData) => {
try {
const loggedInUser = await apiLogin(data);
setUser(loggedInUser);
} catch (error) {
console.error('Login error:', error);
throw error;
}
};
const register = async (data: RegisterData) => {
try {
const newUser = await apiRegister(data);
setUser(newUser);
} catch (error) {
console.error('Registration error:', error);
throw error;
}
};
const logout = async () => {
try {
await apiLogout();
setUser(null);
} catch (error) {
console.error('Logout error:', error);
throw error;
}
};
return (
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);

View File

@ -1,110 +1,108 @@
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import {
Network,
getUserNetworks,
createNetwork as apiCreateNetwork,
updateNetwork as apiUpdateNetwork,
deleteNetwork as apiDeleteNetwork,
CreateNetworkData,
UpdateNetworkData
} from '../api/network';
import { useAuth } from './AuthContext';
interface NetworkContextProps {
networks: Network[];
loading: boolean;
error: string | null;
createNetwork: (data: CreateNetworkData) => Promise<Network>;
updateNetwork: (id: string, data: UpdateNetworkData) => Promise<Network>;
deleteNetwork: (id: string) => Promise<void>;
refreshNetworks: () => Promise<void>;
}
const NetworkContext = createContext<NetworkContextProps>({} as NetworkContextProps);
export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [networks, setNetworks] = useState<Network[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { user } = useAuth();
const loadNetworks = async () => {
if (!user) {
setNetworks([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const fetchedNetworks = await getUserNetworks();
setNetworks(fetchedNetworks);
} catch (err: any) {
setError(err.message || 'Failed to load networks');
console.error('Error loading networks:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadNetworks();
}, [user]);
const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
try {
const newNetwork = await apiCreateNetwork(data);
setNetworks([...networks, newNetwork]);
return newNetwork;
} catch (err: any) {
setError(err.message || 'Failed to create network');
throw err;
}
};
const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
try {
const updatedNetwork = await apiUpdateNetwork(id, data);
setNetworks(networks.map(network =>
network._id === id ? updatedNetwork : network
));
return updatedNetwork;
} catch (err: any) {
setError(err.message || 'Failed to update network');
throw err;
}
};
const deleteNetwork = async (id: string): Promise<void> => {
try {
await apiDeleteNetwork(id);
setNetworks(networks.filter(network => network._id !== id));
} catch (err: any) {
setError(err.message || 'Failed to delete network');
throw err;
}
};
const refreshNetworks = async (): Promise<void> => {
await loadNetworks();
};
return (
<NetworkContext.Provider
value={{
networks,
loading,
error,
createNetwork,
updateNetwork,
deleteNetwork,
refreshNetworks
}}
>
{children}
</NetworkContext.Provider>
);
};
export const useNetworks = () => useContext(NetworkContext);
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import {
Network,
getUserNetworks,
createNetwork as apiCreateNetwork,
updateNetwork as apiUpdateNetwork,
deleteNetwork as apiDeleteNetwork,
CreateNetworkData,
UpdateNetworkData,
} from '../api/network';
import { useAuth } from './AuthContext';
interface NetworkContextProps {
networks: Network[];
loading: boolean;
error: string | null;
createNetwork: (data: CreateNetworkData) => Promise<Network>;
updateNetwork: (id: string, data: UpdateNetworkData) => Promise<Network>;
deleteNetwork: (id: string) => Promise<void>;
refreshNetworks: () => Promise<void>;
}
const NetworkContext = createContext<NetworkContextProps>({} as NetworkContextProps);
export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [networks, setNetworks] = useState<Network[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { user } = useAuth();
const loadNetworks = async () => {
if (!user) {
setNetworks([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const fetchedNetworks = await getUserNetworks();
setNetworks(fetchedNetworks);
} catch (err: any) {
setError(err.message || 'Failed to load networks');
console.error('Error loading networks:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadNetworks();
}, [user]);
const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
try {
const newNetwork = await apiCreateNetwork(data);
setNetworks([...networks, newNetwork]);
return newNetwork;
} catch (err: any) {
setError(err.message || 'Failed to create network');
throw err;
}
};
const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
try {
const updatedNetwork = await apiUpdateNetwork(id, data);
setNetworks(networks.map(network => (network._id === id ? updatedNetwork : network)));
return updatedNetwork;
} catch (err: any) {
setError(err.message || 'Failed to update network');
throw err;
}
};
const deleteNetwork = async (id: string): Promise<void> => {
try {
await apiDeleteNetwork(id);
setNetworks(networks.filter(network => network._id !== id));
} catch (err: any) {
setError(err.message || 'Failed to delete network');
throw err;
}
};
const refreshNetworks = async (): Promise<void> => {
await loadNetworks();
};
return (
<NetworkContext.Provider
value={{
networks,
loading,
error,
createNetwork,
updateNetwork,
deleteNetwork,
refreshNetworks,
}}
>
{children}
</NetworkContext.Provider>
);
};
export const useNetworks = () => useContext(NetworkContext);

View File

@ -1,210 +1,221 @@
import { useState, useEffect, useCallback } from 'react';
import { Person, getPeople, addPerson, updatePerson, removePerson } from '../api/people';
import { Relationship, getRelationships, addRelationship, updateRelationship, removeRelationship } from '../api/relationships';
interface PersonNode extends Person {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
interface RelationshipEdge extends Relationship {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
// Custom hook to manage friendship network data
export const useFriendshipNetwork = (networkId: string | null) => {
const [people, setPeople] = useState<PersonNode[]>([]);
const [relationships, setRelationships] = useState<RelationshipEdge[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Load network data
const loadNetworkData = useCallback(async () => {
if (!networkId) {
setPeople([]);
setRelationships([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
// Fetch people and relationships in parallel
const [peopleData, relationshipsData] = await Promise.all([
getPeople(networkId),
getRelationships(networkId)
]);
// Transform to add the id property needed by the visualization
const peopleNodes: PersonNode[] = peopleData.map(person => ({
...person,
id: person._id
}));
const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({
...rel,
id: rel._id
}));
setPeople(peopleNodes);
setRelationships(relationshipEdges);
} catch (err: any) {
setError(err.message || 'Failed to load network data');
console.error('Error loading network data:', err);
} finally {
setLoading(false);
}
}, [networkId]);
useEffect(() => {
loadNetworkData();
}, [loadNetworkData]);
// Add a new person
const createPerson = async (personData: {
firstName: string;
lastName: string;
birthday?: string;
position?: { x: number; y: number }
}): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const newPerson = await addPerson(networkId, personData);
const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id };
setPeople([...people, newPersonNode]);
return newPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to create person');
throw err;
}
};
// Update a person
const updatePersonData = async (
personId: string,
personData: {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: { x: number; y: number }
}
): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedPerson = await updatePerson(networkId, personId, personData);
const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id };
setPeople(people.map(person =>
person._id === personId ? updatedPersonNode : person
));
return updatedPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to update person');
throw err;
}
};
// Remove a person
const deletePerson = async (personId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removePerson(networkId, personId);
// Remove the person
setPeople(people.filter(person => person._id !== personId));
// Remove all relationships involving this person
setRelationships(relationships.filter(
rel => rel.source !== personId && rel.target !== personId
));
} catch (err: any) {
setError(err.message || 'Failed to delete person');
throw err;
}
};
// Create a relationship
const createRelationship = async (relationshipData: {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const newRelationship = await addRelationship(networkId, relationshipData);
const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id };
setRelationships([...relationships, newRelationshipEdge]);
return newRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to create relationship');
throw err;
}
};
// Update a relationship
const updateRelationshipData = async (
relationshipId: string,
relationshipData: {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedRelationship = await updateRelationship(networkId, relationshipId, relationshipData);
const updatedRelationshipEdge: RelationshipEdge = { ...updatedRelationship, id: updatedRelationship._id };
setRelationships(relationships.map(rel =>
rel._id === relationshipId ? updatedRelationshipEdge : rel
));
return updatedRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to update relationship');
throw err;
}
};
// Remove a relationship
const deleteRelationship = async (relationshipId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removeRelationship(networkId, relationshipId);
setRelationships(relationships.filter(rel => rel._id !== relationshipId));
} catch (err: any) {
setError(err.message || 'Failed to delete relationship');
throw err;
}
};
// Refresh the network data
const refreshNetwork = async (): Promise<void> => {
await loadNetworkData();
};
return {
people,
relationships,
loading,
error,
createPerson,
updatePerson: updatePersonData,
deletePerson,
createRelationship,
updateRelationship: updateRelationshipData,
deleteRelationship,
refreshNetwork
};
};
import { useState, useEffect, useCallback } from 'react';
import { Person, getPeople, addPerson, updatePerson, removePerson } from '../api/people';
import {
Relationship,
getRelationships,
addRelationship,
updateRelationship,
removeRelationship,
} from '../api/relationships';
interface PersonNode extends Person {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
interface RelationshipEdge extends Relationship {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
// Custom hook to manage friendship network data
export const useFriendshipNetwork = (networkId: string | null) => {
const [people, setPeople] = useState<PersonNode[]>([]);
const [relationships, setRelationships] = useState<RelationshipEdge[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Load network data
const loadNetworkData = useCallback(async () => {
if (!networkId) {
setPeople([]);
setRelationships([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
// Fetch people and relationships in parallel
const [peopleData, relationshipsData] = await Promise.all([
getPeople(networkId),
getRelationships(networkId),
]);
// Transform to add the id property needed by the visualization
const peopleNodes: PersonNode[] = peopleData.map(person => ({
...person,
id: person._id,
}));
const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({
...rel,
id: rel._id,
}));
setPeople(peopleNodes);
setRelationships(relationshipEdges);
} catch (err: any) {
setError(err.message || 'Failed to load network data');
console.error('Error loading network data:', err);
} finally {
setLoading(false);
}
}, [networkId]);
useEffect(() => {
loadNetworkData();
}, [loadNetworkData]);
// Add a new person
const createPerson = async (personData: {
firstName: string;
lastName: string;
birthday?: string;
position?: { x: number; y: number };
}): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const newPerson = await addPerson(networkId, personData);
const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id };
setPeople([...people, newPersonNode]);
return newPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to create person');
throw err;
}
};
// Update a person
const updatePersonData = async (
personId: string,
personData: {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: { x: number; y: number };
}
): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedPerson = await updatePerson(networkId, personId, personData);
const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id };
setPeople(people.map(person => (person._id === personId ? updatedPersonNode : person)));
return updatedPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to update person');
throw err;
}
};
// Remove a person
const deletePerson = async (personId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removePerson(networkId, personId);
// Remove the person
setPeople(people.filter(person => person._id !== personId));
// Remove all relationships involving this person
setRelationships(
relationships.filter(rel => rel.source !== personId && rel.target !== personId)
);
} catch (err: any) {
setError(err.message || 'Failed to delete person');
throw err;
}
};
// Create a relationship
const createRelationship = async (relationshipData: {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const newRelationship = await addRelationship(networkId, relationshipData);
const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id };
setRelationships([...relationships, newRelationshipEdge]);
return newRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to create relationship');
throw err;
}
};
// Update a relationship
const updateRelationshipData = async (
relationshipId: string,
relationshipData: {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedRelationship = await updateRelationship(
networkId,
relationshipId,
relationshipData
);
const updatedRelationshipEdge: RelationshipEdge = {
...updatedRelationship,
id: updatedRelationship._id,
};
setRelationships(
relationships.map(rel => (rel._id === relationshipId ? updatedRelationshipEdge : rel))
);
return updatedRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to update relationship');
throw err;
}
};
// Remove a relationship
const deleteRelationship = async (relationshipId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removeRelationship(networkId, relationshipId);
setRelationships(relationships.filter(rel => rel._id !== relationshipId));
} catch (err: any) {
setError(err.message || 'Failed to delete relationship');
throw err;
}
};
// Refresh the network data
const refreshNetwork = async (): Promise<void> => {
await loadNetworkData();
};
return {
people,
relationships,
loading,
error,
createPerson,
updatePerson: updatePersonData,
deletePerson,
createRelationship,
updateRelationship: updateRelationshipData,
deleteRelationship,
refreshNetwork,
};
};

View File

@ -1,17 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// Create root and render the App component into the HTML element with ID 'root'
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
} else {
console.error('Root element not found');
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// Create root and render the App component into the HTML element with ID 'root'
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
} else {
console.error('Root element not found');
}

File diff suppressed because it is too large Load Diff