first commit

This commit is contained in:
Mateusz Gruszczyński
2025-03-07 17:32:02 +01:00
commit 7facd84b99
10 changed files with 634 additions and 0 deletions

205
frontend/src/Admin.js Normal file
View 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 ():</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} </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} , {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
View 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
View 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;

View 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} </strong> z <strong>{campaign.target} zł</strong>
</p>
<h2>Ostatnie wpłaty</h2>
<ul>
{donations.map((d) => (
<li key={d.id}>
Kwota: {d.amount} {d.description} {new Date(d.date).toLocaleString()}
</li>
))}
</ul>
</div>
</div>
);
}
export default CampaignDetail;

34
frontend/src/HomePage.js Normal file
View 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
View 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;