first commit
This commit is contained in:
205
frontend/src/Admin.js
Normal file
205
frontend/src/Admin.js
Normal file
@ -0,0 +1,205 @@
|
||||
// Admin.js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, Routes, Route, Link, useParams } from 'react-router-dom';
|
||||
import './App.css';
|
||||
|
||||
function AdminHome() {
|
||||
const [campaigns, setCampaigns] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:4000/api/campaigns')
|
||||
.then((res) => res.json())
|
||||
.then((data) => setCampaigns(data))
|
||||
.catch((err) => console.error(err));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<h1>Panel Administratora</h1>
|
||||
<Link to="/admin/new" className="button" style={{ marginBottom: '20px', display: 'inline-block' }}>
|
||||
Dodaj nową zbiórkę
|
||||
</Link>
|
||||
<h2>Zbiórki</h2>
|
||||
<ul>
|
||||
{campaigns.map((c) => (
|
||||
<li key={c.id} style={{ marginBottom: '10px' }}>
|
||||
<strong>{c.title}</strong> – Cel: {c.target} zł{' '}
|
||||
<Link to={`/admin/campaign/${c.id}`} className="button" style={{ marginLeft: '10px' }}>
|
||||
Zarządzaj
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NewCampaign() {
|
||||
const [title, setTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [target, setTarget] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const token = sessionStorage.getItem('authToken');
|
||||
fetch('http://localhost:4000/api/campaigns', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title, description, target: parseFloat(target), token }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Błąd tworzenia zbiórki');
|
||||
return res.json();
|
||||
})
|
||||
.then(() => {
|
||||
alert('Zbiórka dodana');
|
||||
navigate('/admin');
|
||||
})
|
||||
.catch((err) => alert('Błąd podczas dodawania zbiórki'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h2>Dodaj nową zbiórkę</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<label>Tytuł:</label>
|
||||
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Opis:</label>
|
||||
<textarea value={description} onChange={(e) => setDescription(e.target.value)} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Cel (zł):</label>
|
||||
<input type="number" value={target} onChange={(e) => setTarget(e.target.value)} required />
|
||||
</div>
|
||||
<button type="submit" className="button">Dodaj zbiórkę</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdminCampaignDetail() {
|
||||
const { campaignId } = useParams();
|
||||
const [campaign, setCampaign] = useState(null);
|
||||
const [donations, setDonations] = useState([]);
|
||||
const [newTarget, setNewTarget] = useState('');
|
||||
const [donationAmount, setDonationAmount] = useState('');
|
||||
const [donationDesc, setDonationDesc] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`http://localhost:4000/api/campaigns/${campaignId}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => setCampaign(data))
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
fetch(`http://localhost:4000/api/campaigns/${campaignId}/donations`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => setDonations(data))
|
||||
.catch((err) => console.error(err));
|
||||
}, [campaignId]);
|
||||
|
||||
const handleUpdateTarget = (e) => {
|
||||
e.preventDefault();
|
||||
const token = sessionStorage.getItem('authToken');
|
||||
fetch(`http://localhost:4000/api/campaigns/${campaignId}/target`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ newTarget: parseFloat(newTarget), token }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Błąd aktualizacji celu');
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
setCampaign({ ...campaign, target: data.target });
|
||||
setNewTarget('');
|
||||
alert('Cel zaktualizowany');
|
||||
})
|
||||
.catch((err) => alert('Błąd podczas aktualizacji celu'));
|
||||
};
|
||||
|
||||
const handleAddDonation = (e) => {
|
||||
e.preventDefault();
|
||||
const token = sessionStorage.getItem('authToken');
|
||||
fetch(`http://localhost:4000/api/campaigns/${campaignId}/donations`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount: parseFloat(donationAmount), description: donationDesc, token }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Błąd dodawania wpłaty');
|
||||
return res.json();
|
||||
})
|
||||
.then((newDonation) => {
|
||||
setDonations([newDonation, ...donations]);
|
||||
setDonationAmount('');
|
||||
setDonationDesc('');
|
||||
})
|
||||
.catch((err) => alert('Błąd podczas dodawania wpłaty'));
|
||||
};
|
||||
|
||||
if (!campaign) return <div>Ładowanie...</div>;
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h2>Zbiórka: {campaign.title}</h2>
|
||||
<p>{campaign.description}</p>
|
||||
<p>
|
||||
Cel: <strong>{campaign.target} zł</strong> | Zebrano: <strong>{campaign.totalDonations} zł</strong>
|
||||
</p>
|
||||
<form onSubmit={handleUpdateTarget}>
|
||||
<div className="form-group">
|
||||
<label>Aktualizuj cel:</label>
|
||||
<input type="number" value={newTarget} onChange={(e) => setNewTarget(e.target.value)} required />
|
||||
</div>
|
||||
<button type="submit" className="button">Aktualizuj cel</button>
|
||||
</form>
|
||||
<h3>Dodaj wpłatę</h3>
|
||||
<form onSubmit={handleAddDonation}>
|
||||
<div className="form-group">
|
||||
<label>Kwota:</label>
|
||||
<input type="number" value={donationAmount} onChange={(e) => setDonationAmount(e.target.value)} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Opis:</label>
|
||||
<input type="text" value={donationDesc} onChange={(e) => setDonationDesc(e.target.value)} required />
|
||||
</div>
|
||||
<button type="submit" className="button">Dodaj wpłatę</button>
|
||||
</form>
|
||||
<h3>Historia wpłat</h3>
|
||||
<ul>
|
||||
{donations.map((d) => (
|
||||
<li key={d.id}>
|
||||
Kwota: {d.amount} zł, {d.description}, {new Date(d.date).toLocaleString()}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Admin() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const token = sessionStorage.getItem('authToken');
|
||||
if (!token) navigate('/login');
|
||||
}, [navigate]);
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<AdminHome />} />
|
||||
<Route path="/new" element={<NewCampaign />} />
|
||||
<Route path="/campaign/:campaignId" element={<AdminCampaignDetail />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
export default Admin;
|
87
frontend/src/App.css
Normal file
87
frontend/src/App.css
Normal file
@ -0,0 +1,87 @@
|
||||
/* App.css */
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 30px;
|
||||
background-color: #76c7c0;
|
||||
width: 0;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #76c7c0;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #5aa9a4;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
.progress-bar {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
23
frontend/src/App.js
Normal file
23
frontend/src/App.js
Normal file
@ -0,0 +1,23 @@
|
||||
// App.js
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import HomePage from './HomePage';
|
||||
import CampaignDetail from './CampaignDetail';
|
||||
import Login from './Login';
|
||||
import Admin from './Admin';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/campaign/:id" element={<CampaignDetail />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/admin/*" element={<Admin />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
48
frontend/src/CampaignDetail.js
Normal file
48
frontend/src/CampaignDetail.js
Normal file
@ -0,0 +1,48 @@
|
||||
// CampaignDetail.js
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import ProgressBar from './ProgressBar';
|
||||
import './App.css';
|
||||
|
||||
function CampaignDetail() {
|
||||
const { id } = useParams();
|
||||
const [campaign, setCampaign] = useState(null);
|
||||
const [donations, setDonations] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`http://localhost:4000/api/campaigns/${id}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => setCampaign(data))
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
fetch(`http://localhost:4000/api/campaigns/${id}/donations`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => setDonations(data))
|
||||
.catch((err) => console.error(err));
|
||||
}, [id]);
|
||||
|
||||
if (!campaign) return <div>Ładowanie...</div>;
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h1>{campaign.title}</h1>
|
||||
<p>{campaign.description}</p>
|
||||
<ProgressBar value={campaign.totalDonations} max={campaign.target} />
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<strong>{campaign.totalDonations} zł</strong> z <strong>{campaign.target} zł</strong>
|
||||
</p>
|
||||
<h2>Ostatnie wpłaty</h2>
|
||||
<ul>
|
||||
{donations.map((d) => (
|
||||
<li key={d.id}>
|
||||
Kwota: {d.amount} zł – {d.description} – {new Date(d.date).toLocaleString()}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CampaignDetail;
|
34
frontend/src/HomePage.js
Normal file
34
frontend/src/HomePage.js
Normal file
@ -0,0 +1,34 @@
|
||||
// HomePage.js
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './App.css';
|
||||
|
||||
function HomePage() {
|
||||
const [campaigns, setCampaigns] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:4000/api/campaigns')
|
||||
.then((res) => res.json())
|
||||
.then((data) => setCampaigns(data))
|
||||
.catch((err) => console.error(err));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<h1 className="header">Lista Zbiórek</h1>
|
||||
<div className="card">
|
||||
{campaigns.map((campaign) => (
|
||||
<div key={campaign.id} style={{ marginBottom: '15px' }}>
|
||||
<h2>{campaign.title}</h2>
|
||||
<p>{campaign.description}</p>
|
||||
<Link to={`/campaign/${campaign.id}`} className="button">
|
||||
Zobacz szczegóły
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HomePage;
|
64
frontend/src/Login.js
Normal file
64
frontend/src/Login.js
Normal file
@ -0,0 +1,64 @@
|
||||
// Login.js
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import './App.css';
|
||||
|
||||
function Login() {
|
||||
const [login, setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = (e) => {
|
||||
e.preventDefault();
|
||||
fetch('http://localhost:4000/api/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ login, password }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Błąd logowania');
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
sessionStorage.setItem('authToken', data.token);
|
||||
navigate('/admin');
|
||||
})
|
||||
.catch((err) => {
|
||||
alert('Niepoprawne dane logowania.');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h2>Logowanie do panelu</h2>
|
||||
<form onSubmit={handleLogin}>
|
||||
<div className="form-group">
|
||||
<label>Login</label>
|
||||
<input
|
||||
type="text"
|
||||
value={login}
|
||||
onChange={(e) => setLogin(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Hasło</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" className="button">
|
||||
Zaloguj
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
|
Reference in New Issue
Block a user