diff --git a/back/api/@me.js b/back/api/@me.js index 28b5bac..080731e 100644 --- a/back/api/@me.js +++ b/back/api/@me.js @@ -1,11 +1,11 @@ const express = require('express'); const router = express.Router(); -const { getConnection, getUserAccounts, getUserCards } = require('../libs/mysql'); +const { getConnection, getUserAccounts, getUserCards, getUserTransfers, setAccountBalance, getAccount, addTransfer } = require('../libs/mysql'); const { checkAuth } = require('../libs/middlewares'); router.post('/', checkAuth, async (req, res) => { const user = req.user; - res.send({ id: user.id, name: user.name, lastname: user.lastname, admin: user.admin }); + res.send(user); }); router.post('/accounts', checkAuth, async (req, res) => { @@ -13,11 +13,7 @@ router.post('/accounts', checkAuth, async (req, res) => { const connection = await getConnection(); const accounts = await getUserAccounts(connection, user.id); connection.end(); - - if (!accounts[0]) { - return res.status(404).send({ error: 'No accounts found' }); - } - res.send({ accounts: accounts }); + res.send(accounts); }); router.post('/cards', checkAuth, async (req, res) => { @@ -25,11 +21,47 @@ router.post('/cards', checkAuth, async (req, res) => { const connection = await getConnection(); const cards = await getUserCards(connection, user.id); connection.end(); + res.send(cards); +}); - if (!cards[0]) { - return res.status(404).send({ error: 'No cards found' }); +router.post('/transfers', checkAuth, async (req, res) => { + const user = req.user; + const connection = await getConnection(); + const transfers = await getUserTransfers(connection, user.id); + connection.end(); + res.send(transfers); +}); + +router.post('/send-money', checkAuth, async (req, res) => { + const user = req.user; + const { account_from_id, account_to_id, amount, name } = req.body; + + if (!account_from_id || !account_to_id || !amount || !name) { + return res.status(400).send({ error: 'Missing required fields' }); } - res.send({ cards: cards }); + + const connection = await getConnection(); + const accountFrom = await getAccount(connection, account_from_id); + const accountTo = await getAccount(connection, account_to_id); + + if (!accountFrom[0] || !accountTo[0]) { + return res.status(400).send({ error: 'Invalid account ID' }); + } + + if (accountFrom[0].client_id !== user.id) { + return res.status(403).send({ error: 'You are not authorized to send money from this account' }); + } + + if (accountFrom[0].balance < amount) { + return res.status(400).send({ error: 'Insufficient funds' }); + } + + await setAccountBalance(connection, account_from_id, accountFrom[0].balance - amount); + await setAccountBalance(connection, account_to_id, accountTo[0].balance + amount); + await addTransfer(connection, account_from_id, account_to_id, name, amount); + connection.end(); + + res.send({ message: 'Money sent successfully' }); }); module.exports = router; \ No newline at end of file diff --git a/back/api/accounts.js b/back/api/accounts.js deleted file mode 100644 index 51a18c9..0000000 --- a/back/api/accounts.js +++ /dev/null @@ -1,9 +0,0 @@ -const express = require('express'); -const { getConnection } = require('../libs/mysql'); -const { checkAuth } = require('../libs/middlewares'); - -const router = express.Router(); - - - -module.exports = router; \ No newline at end of file diff --git a/back/api/users.js b/back/api/users.js index 9e53487..eab56f5 100644 --- a/back/api/users.js +++ b/back/api/users.js @@ -1,9 +1,212 @@ const express = require('express'); -const { getConnection, addUser } = require('../libs/mysql'); +const { getConnection, addUser, getUsers, getUser, getUserAccounts, addAccount, removeAccount, getUserCards, addCard, removeCard, setAccountBalance, getAccount } = require('../libs/mysql'); const { checkAuth } = require('../libs/middlewares'); const router = express.Router(); +router.post('/', checkAuth, async (req, res) => { + const user = req.user; + + if (!user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const connection = await getConnection(); + const users = await getUsers(connection); + connection.end(); + + res.send(users ); +}); + +router.post('/:id', checkAuth, async (req, res) => { + const this_user = req.user; + const { id } = req.params; + + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, id); + connection.end(); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + res.send(user[0]); +}); + +router.post('/:id/accounts', checkAuth, async (req, res) => { + const this_user = req.user; + const { id } = req.params; + + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, id); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + const accounts = await getUserAccounts(connection, id); + connection.end(); + + res.send(accounts); +}); + +router.post('/:user_id/accounts/:account_id/delete', checkAuth, async (req, res) => { + const this_user = req.user; + const { user_id, account_id } = req.params; + + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, user_id); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + await removeAccount(connection, account_id); + connection.end(); + + res.send({ message: 'Account removed' }); +}); + +router.post('/:user_id/accounts/:account_id/add-balance', checkAuth, async (req, res) => { + const this_user = req.user; + const { user_id, account_id } = req.params; + const { balance } = req.body; + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + if (!balance) { + return res.status(400).json({ error: 'Balance is required' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, user_id); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + const account = await getAccount(connection, account_id); + + if (!account[0]) { + return res.status(404).send({ error: 'Account not found' }); + } + + await setAccountBalance(connection, account_id, account[0].balance + balance); + connection.end(); + + res.send({ message: 'Balance added' }); +}); + + +router.post('/:id/create-account', checkAuth, async (req, res) => { + const this_user = req.user; + const { name } = req.body; + const { id } = req.params; + + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + if (!name) { + return res.status(400).json({ error: 'Name is required' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, id); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + await addAccount(connection, id, name); + connection.end(); + + res.send({ message: 'Account created' }); +}); + +router.post('/:id/cards', checkAuth, async (req, res) => { + const this_user = req.user; + const { id } = req.params; + + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, id); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + const cards = await getUserCards(connection, id); + connection.end(); + + res.send(cards); +}); + +router.post('/:user_id/cards/:card_id/delete', checkAuth, async (req, res) => { + const this_user = req.user; + const { user_id, card_id } = req.params; + + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, user_id); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + await removeCard(connection, card_id); + connection.end(); + + res.send({ message: 'Card removed' }); +}); + +router.post('/:user_id/create-card', checkAuth, async (req, res) => { + const this_user = req.user; + const { user_id } = req.params; + const { account_id } = req.body; + + if (!this_user.admin) { + return res.status(403).json({ error: 'Permission denied' }); + } + + const connection = await getConnection(); + const user = await getUser(connection, user_id); + + if (!user[0]) { + return res.status(404).send({ error: 'User not found' }); + } + + const numero = Math.floor(Math.random() * 1000000000); + const expiration = new Date(); + expiration.setFullYear(expiration.getFullYear() + 5); + const expirationString = `${expiration.getMonth() + 1}/${expiration.getFullYear() % 100}`; + const cvc = Math.floor(Math.random() * 1000); + + await addCard(connection, account_id, numero, expirationString, cvc); + connection.end(); + + res.send({ message: 'Card created' }); +}); + router.post('/add', checkAuth, async (req, res) => { const user = req.user; const { name, lastname, email, numero, password } = req.body; diff --git a/back/bank.sql b/back/bank.sql index ab82d52..abd6e08 100644 --- a/back/bank.sql +++ b/back/bank.sql @@ -2,8 +2,8 @@ -- version 5.2.1 -- https://www.phpmyadmin.net/ -- --- Host: leizour.fr --- Generation Time: Apr 09, 2025 at 02:41 PM +-- Host: mysql +-- Generation Time: Apr 27, 2025 at 01:27 PM -- Server version: 10.11.3-MariaDB-1:10.11.3+maria~ubu2204 -- PHP Version: 8.1.19 @@ -20,6 +20,8 @@ SET time_zone = "+00:00"; -- -- Database: `bank` -- +CREATE DATABASE IF NOT EXISTS `bank` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; +USE `bank`; -- -------------------------------------------------------- @@ -27,12 +29,14 @@ SET time_zone = "+00:00"; -- Table structure for table `accounts` -- -CREATE TABLE `accounts` ( - `id` int(11) NOT NULL, - `balance` bigint(20) NOT NULL, +CREATE TABLE IF NOT EXISTS `accounts` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `balance` bigint(20) NOT NULL DEFAULT 0, `client_id` int(11) NOT NULL, `name` varchar(20) NOT NULL, - `interest` float NOT NULL DEFAULT 0 + `interest` float NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `client_id` (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -------------------------------------------------------- @@ -41,12 +45,14 @@ CREATE TABLE `accounts` ( -- Table structure for table `cards` -- -CREATE TABLE `cards` ( - `id` int(11) NOT NULL, +CREATE TABLE IF NOT EXISTS `cards` ( + `id` int(11) NOT NULL AUTO_INCREMENT, `account_id` int(11) NOT NULL, `number` int(11) NOT NULL, `expiration` timestamp NOT NULL, - `cvc` int(11) NOT NULL + `cvc` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `account_id` (`account_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -------------------------------------------------------- @@ -55,12 +61,15 @@ CREATE TABLE `cards` ( -- Table structure for table `transfers` -- -CREATE TABLE `transfers` ( - `id` int(11) NOT NULL, +CREATE TABLE IF NOT EXISTS `transfers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, `account_from_id` int(11) NOT NULL, `account_to_id` int(11) NOT NULL, `name` varchar(30) NOT NULL, - `value` int(11) NOT NULL + `value` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `account_from_id` (`account_from_id`), + KEY `account_to_id` (`account_to_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -------------------------------------------------------- @@ -69,71 +78,39 @@ CREATE TABLE `transfers` ( -- Table structure for table `users` -- -CREATE TABLE `users` ( - `id` int(11) NOT NULL, +CREATE TABLE IF NOT EXISTS `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `lastname` varchar(20) NOT NULL, `password` varchar(150) NOT NULL, `email` varchar(100) NOT NULL, `numero` varchar(20) NOT NULL, - `admin` tinyint(1) NOT NULL + `admin` tinyint(1) DEFAULT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- --- Indexes for dumped tables +-- Constraints for dumped tables -- -- --- Indexes for table `accounts` +-- Constraints for table `accounts` -- ALTER TABLE `accounts` - ADD PRIMARY KEY (`id`); + ADD CONSTRAINT `client_id` FOREIGN KEY (`client_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- --- Indexes for table `cards` +-- Constraints for table `cards` -- ALTER TABLE `cards` - ADD PRIMARY KEY (`id`); + ADD CONSTRAINT `account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- --- Indexes for table `transfers` +-- Constraints for table `transfers` -- ALTER TABLE `transfers` - ADD PRIMARY KEY (`id`); - --- --- Indexes for table `users` --- -ALTER TABLE `users` - ADD PRIMARY KEY (`id`); - --- --- AUTO_INCREMENT for dumped tables --- - --- --- AUTO_INCREMENT for table `accounts` --- -ALTER TABLE `accounts` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - --- --- AUTO_INCREMENT for table `cards` --- -ALTER TABLE `cards` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - --- --- AUTO_INCREMENT for table `transfers` --- -ALTER TABLE `transfers` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - --- --- AUTO_INCREMENT for table `users` --- -ALTER TABLE `users` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + ADD CONSTRAINT `account_from_id` FOREIGN KEY (`account_from_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `account_to_id` FOREIGN KEY (`account_to_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/back/libs/mysql.js b/back/libs/mysql.js index 6120765..98433bb 100644 --- a/back/libs/mysql.js +++ b/back/libs/mysql.js @@ -91,6 +91,66 @@ function getUserAccounts(connection, id) { }); } +function getAccount(connection, id) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT * FROM accounts WHERE id = ?`, + [id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function addAccount(connection, client_id, name) { + return new Promise((resolve, reject) => { + connection.query( + `INSERT INTO accounts (client_id, name) VALUES (?, ?)`, + [client_id, name], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function removeAccount(connection, id) { + return new Promise((resolve, reject) => { + connection.query( + `DELETE FROM accounts WHERE id = ?`, + [id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function setAccountBalance(connection, id, balance) { + return new Promise((resolve, reject) => { + connection.query( + `UPDATE accounts SET balance = ? WHERE id = ?`, + [balance, id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + // +-------------------------------+ // | Cards | // +-------------------------------+ @@ -98,10 +158,11 @@ function getUserAccounts(connection, id) { function getUserCards(connection, id) { return new Promise((resolve, reject) => { connection.query( - `SELECT cards.* + `SELECT cards.*, accounts.name AS account_name FROM cards JOIN accounts ON cards.account_id = accounts.id - WHERE client_id = ?`, + JOIN users ON accounts.client_id = users.id + WHERE users.id = ?`, [id], (error, result) => { if (error) { @@ -113,6 +174,74 @@ function getUserCards(connection, id) { }); } +function addCard(connection, account_id, number, expiration, cvc) { + return new Promise((resolve, reject) => { + connection.query( + `INSERT INTO cards (account_id, number, expiration, cvc) VALUES (?, ?, ?, ?)`, + [account_id, number, expiration, cvc], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function removeCard(connection, id) { + return new Promise((resolve, reject) => { + connection.query( + `DELETE FROM cards WHERE id = ?`, + [id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +// +-------------------------------+ +// | Transfers | +// +-------------------------------+ + +function getUserTransfers(connection, id) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT * + FROM transfers + JOIN accounts as sender ON transfers.account_from_id = sender.id + JOIN accounts as receiver ON transfers.account_to_id = receiver.id + WHERE sender.client_id = ? OR receiver.client_id = ?`, + [id, id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function addTransfer(connection, account_from_id, account_to_id, name, amount) { + return new Promise((resolve, reject) => { + connection.query( + `INSERT INTO transfers (account_from_id, account_to_id, name, value) VALUES (?, ?, ?, ?)`, + [account_from_id, account_to_id, name, amount], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + module.exports = { getConnection, @@ -122,6 +251,15 @@ module.exports = { getUserByEmail, getUserAccounts, + getAccount, + addAccount, + removeAccount, + setAccountBalance, getUserCards, + addCard, + removeCard, + + getUserTransfers, + addTransfer, }; diff --git a/front/src/components/TopBar.tsx b/front/src/components/TopBar.tsx new file mode 100644 index 0000000..e6ddb70 --- /dev/null +++ b/front/src/components/TopBar.tsx @@ -0,0 +1,67 @@ +import { useState } from "react" +import { Link } from "react-router-dom" +import { User } from "../types" + +export default function TopBar({ user }: { user: User | undefined }) { + const [burgerMenuOpen, setBurgerMenuOpen] = useState(false) + + return ( +
+
+ Home + {user?.admin == 1 && ( + Admin + )} +
+
+

Tanuki's Bank

+
+ {user ? ( +
+ {user.name} + +
+ ) : ( +
+ Login + Register +
+ )} +
+ {burgerMenuOpen && ( +
+ Home + {user?.admin == 1 && ( + Admin + )} + {user ? ( +
+ {user.name} + +
+ ) : ( +
+ Login + Register +
+ )} +
+ )} + +
+ +
+ ) +} \ No newline at end of file diff --git a/front/src/index.css b/front/src/index.css index e69de29..25f6747 100644 --- a/front/src/index.css +++ b/front/src/index.css @@ -0,0 +1,82 @@ +html { + background: #2a6c9b; + background: linear-gradient(167deg, rgba(42, 108, 155, 1) 0%, rgba(87, 199, 133, 1) 50%, rgba(163, 237, 83, 1) 100%); +} + +.bank-page { + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + gap: 10px; +} + +.bank-section { + width: 97%; + display: flex; + flex-direction: column; + border: 0.5px solid #000; + padding: 10px; + background: #fcf2b5; +} + +.topbar { + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.topbar-left { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; +} + +.topbar-right { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; +} + +.burger-menu { + display: none; +} + +.burger-menu-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +.burger-menu-user { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +@media (max-width: 780px) { + .topbar { + flex-direction: column; + align-items: center; + } + + .topbar-left { + display: none; + } + + .topbar-right { + display: none; + } + + .burger-menu { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + } +} diff --git a/front/src/main.tsx b/front/src/main.tsx index 6a4d58e..6f13613 100644 --- a/front/src/main.tsx +++ b/front/src/main.tsx @@ -6,7 +6,7 @@ import Home from './pages/Home' import Login from './pages/Login' import Register from './pages/Register' import Admin from './pages/Admin' -import AddUser from './pages/AddUser' +import UserPage from './pages/UserPage' createRoot(document.getElementById('root')!).render( @@ -15,7 +15,7 @@ createRoot(document.getElementById('root')!).render( } /> } /> } /> - } /> + } /> , ) diff --git a/front/src/pages/AddUser.tsx b/front/src/pages/AddUser.tsx deleted file mode 100644 index 1d0d61e..0000000 --- a/front/src/pages/AddUser.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import axios from 'axios'; - -export default function AddUser () { - const navigate = useNavigate(); - const [token, setToken] = useState(""); - const [name, setName] = useState(""); - const [lastname, setLastname] = useState(""); - const [email, setEmail] = useState(""); - const [numero, setNumero] = useState(""); - const [password, setPassword] = useState(""); - - useEffect(() => { - const storedToken = localStorage.getItem("token"); - if (storedToken) { - setToken(storedToken); - } else { - navigate("/login"); - } - }, []); - - function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - if (name === "" || lastname === "" || email === "" || numero === "" || password === "") { - alert("Please fill in all fields"); - return; - } - axios - .post("/api/users/add", { - token, - name, - lastname, - email, - numero, - password - }) - .then(() => { - navigate("/admin"); - }) - .catch((error) => { - console.log(error.response.data); - }); - } - - return ( -
-

Add User

-
- setName(e.target.value)} /> - setLastname(e.target.value)} /> - setEmail(e.target.value)} /> - setNumero(e.target.value)} /> - setPassword(e.target.value)} /> - -
-
- ) -} \ No newline at end of file diff --git a/front/src/pages/Admin.tsx b/front/src/pages/Admin.tsx index a2f187c..5bb8571 100644 --- a/front/src/pages/Admin.tsx +++ b/front/src/pages/Admin.tsx @@ -1,14 +1,77 @@ -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import axios from 'axios' +import { User, Users } from '../types'; + +import TopBar from '../components/TopBar'; export default function Admin () { + const navigate = useNavigate(); + const [user, setUser] = useState(); + const [users, setUsers] = useState([]); + + useEffect(() => { + const token = localStorage.getItem('token'); + if (!token) { + navigate('/login'); + } else { + axios + .post('/api/@me', { + token: token + }) + .then((response) => { + setUser(response.data); + }) + .catch(() => { + navigate('/login'); + }); + + axios + .post('/api/users', { + token: token + }) + .then((response) => { + setUsers(response.data); + }) + .catch((error) => { + console.error('Error fetching users:', error.response.data); + }); + } + }, []); + + if (!user) { + return ( +
+ +
+

Loading...

+
+
+ ); + } + return ( -
-

Admin

-

Welcome Admin

-
+
+ +

Admin Panel

Manage users, accounts, and cards.

+
+

Users

+ {users.length > 0 ? ( + users.map((user) => ( +
+

+ {user.name} - {user.lastname} +

+ View Details +
+ )) + ) : ( +

No users found.

+ )} +
); } \ No newline at end of file diff --git a/front/src/pages/Home.tsx b/front/src/pages/Home.tsx index 4ec5da7..a4e9f1b 100644 --- a/front/src/pages/Home.tsx +++ b/front/src/pages/Home.tsx @@ -1,19 +1,54 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { User, Accounts, Cards } from '../types'; +import { User, Accounts, Cards, Transfers } from '../types'; import axios from 'axios'; +import TopBar from '../components/TopBar'; + export default function Home () { const navigate = useNavigate(); - const [token, setToken] = useState(""); const [user, setUser] = useState(); + const [transfers, setTransfers] = useState([]); const [accounts, setAccounts] = useState([]); const [cards, setCards] = useState([]); + const [accountId, setAccountId] = useState(0); + const [accountToId, setAccountToId] = useState(0); + const [amount, setAmount] = useState(0); + const [message, setMessage] = useState(""); + const token = localStorage.getItem("token"); + + function sendMoney (e: React.FormEvent) { + e.preventDefault(); + + axios + .post("/api/@me/send-money", { + token: token, + account_from_id: accountId, + account_to_id: accountToId, + amount: amount, + name: message + }) + .then(() => { + axios + .post("/api/@me/accounts", { token: token }) + .then((response) => { + setAccounts(response.data); + if (response.data.length > 0) { + setAccountId(response.data[0].id); + } + }) + .catch(() => { + console.error("Failed to fetch accounts"); + }); + }) + .catch((error) => { + console.error("Failed to send money", error.response.data); + }); + } useEffect(() => { const storedToken = localStorage.getItem("token"); if (storedToken) { - setToken(storedToken); axios .post("/api/@me", { token: storedToken }) .then((response) => { @@ -23,10 +58,22 @@ export default function Home () { navigate("/login"); }); + axios + .post("/api/@me/transfers", { token: storedToken }) + .then((response) => { + setTransfers(response.data); + }) + .catch(() => { + console.error("Failed to fetch transfers"); + }); + axios .post("/api/@me/accounts", { token: storedToken }) .then((response) => { setAccounts(response.data); + if (response.data.length > 0) { + setAccountId(response.data[0].id); + } }) .catch(() => { console.error("Failed to fetch accounts"); @@ -40,22 +87,62 @@ export default function Home () { .catch(() => { console.error("Failed to fetch cards"); }); + } else { + navigate("/login"); } }, []); if (!user) { return ( -
-

Loading...

+
+ +
+

Loading...

+
); } return ( -
-

Home

-

Welcome {user.name} {user.lastname}

-
+
+ +
+

Home

+

Welcome {user.name} {user.lastname}

+
+
+

Your Transfers

+ {transfers.length > 0 ? ( +
    + {transfers.map((transfer) => ( +
  • + Transfer ID: {transfer.id}, Amount: {transfer.value} +
  • + ))} +
+ ) : ( +

No transfers found.

+ )} +
+ {accounts.length > 0 && ( +
+

Send money

+
+ + setAmount(Number(e.target.value))} value={amount}/> + setAccountToId(Number(e.target.value))} value={accountToId}/> + setMessage(e.target.value)} value={message}/> + +
+
+ )} +

Your Accounts

{accounts.length > 0 ? (
    @@ -69,7 +156,7 @@ export default function Home () {

    No accounts found.

    )}
-
+

Your Cards

{cards.length > 0 ? (
    diff --git a/front/src/pages/Login.tsx b/front/src/pages/Login.tsx index e8e60cb..cebfc8f 100644 --- a/front/src/pages/Login.tsx +++ b/front/src/pages/Login.tsx @@ -2,6 +2,8 @@ import axios from "axios" import { useState } from "react" import { useNavigate } from "react-router-dom" +import TopBar from "../components/TopBar" + export default function Login () { const navigate = useNavigate() const [email, setEmail] = useState("") @@ -29,13 +31,16 @@ export default function Login () { } return ( -
    -

    Login

    -
    - setEmail(e.target.value)} placeholder="Email" /> - setPassword(e.target.value)} placeholder="Password" /> - -
    +
    + +
    +

    Login

    +
    + setEmail(e.target.value)} placeholder="Email" /> + setPassword(e.target.value)} placeholder="Password" /> + +
    +
    ) } \ No newline at end of file diff --git a/front/src/pages/Register.tsx b/front/src/pages/Register.tsx index 878eac1..6b8a9d2 100644 --- a/front/src/pages/Register.tsx +++ b/front/src/pages/Register.tsx @@ -1,7 +1,9 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; +import TopBar from '../components/TopBar'; + export default function Register () { const navigate = useNavigate(); const [name, setName] = useState(""); @@ -44,16 +46,19 @@ export default function Register () { } return ( -
    -

    Add User

    -
    - setName(e.target.value)} /> - setLastname(e.target.value)} /> - setEmail(e.target.value)} /> - setNumero(e.target.value)} /> - setPassword(e.target.value)} /> - -
    +
    + +
    +

    Register

    +
    + setName(e.target.value)} /> + setLastname(e.target.value)} /> + setEmail(e.target.value)} /> + setNumero(e.target.value)} /> + setPassword(e.target.value)} /> + +
    +
    ) } \ No newline at end of file diff --git a/front/src/pages/UserPage.tsx b/front/src/pages/UserPage.tsx new file mode 100644 index 0000000..6f10167 --- /dev/null +++ b/front/src/pages/UserPage.tsx @@ -0,0 +1,261 @@ +import { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import axios from 'axios'; +import { User } from '../types'; + +import TopBar from '../components/TopBar'; + +export default function UserPage() { + const navigate = useNavigate(); + const [user, setUser] = useState(); + const [thisUser, setThisUser] = useState(); + const [accounts, setAccounts] = useState([]); + const [cards, setCards] = useState([]); + const [name, setName] = useState(''); + const [balance, setBalance] = useState(0); + const [accountId, setAccountId] = useState(); + const token = localStorage.getItem('token'); + const { id } = useParams(); + + function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + axios + .post(`/api/users/${id}/create-account`, { token: token, name: name }) + .then(() => { + axios + .post(`/api/users/${id}/accounts`, { token: token }) + .then((response) => { + setAccounts(response.data); + setName(''); + setAccountId(response.data[0]?.id); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + }) + .catch((error) => { + console.error('Error creating account:', error.response.data); + }); + } + + function deleteAccount(accountId: number) { + if (!window.confirm('Are you sure you want to delete this account?')) { + return; + } + axios + .post(`/api/users/${id}/accounts/${accountId}/delete`, { token: token }) + .then(() => { + axios + .post(`/api/users/${id}/accounts`, { token: token }) + .then((response) => { + setAccounts(response.data); + setName(''); + setAccountId(response.data[0]?.id); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + }) + .catch((error) => { + console.error('Error deleting account:', error.response.data); + }); + } + + function addCard(event: React.FormEvent) { + event.preventDefault(); + + axios + .post(`/api/users/${id}/create-card`, { token: token, account_id: accountId }) + .then(() => { + axios + .post(`/api/users/${id}/cards`, { token: token }) + .then((response) => { + setCards(response.data); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + }) + .catch((error) => { + console.error('Error creating account:', error.response.data); + }); + } + + function deleteCard(cardId: number) { + if (!window.confirm('Are you sure you want to delete this card?')) { + return; + } + axios + .post(`/api/users/${id}/cards/${cardId}/delete`, { token: token }) + .then(() => { + axios + .post(`/api/users/${id}/cards`, { token: token }) + .then((response) => { + setCards(response.data); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + }) + .catch((error) => { + console.error('Error deleting account:', error.response.data); + }); + } + + function addBalance(event: React.FormEvent) { + event.preventDefault(); + + axios + .post(`/api/users/${id}/accounts/${accountId}/add-balance`, { token: token, balance: balance }) + .then(() => { + axios + .post(`/api/users/${id}/accounts`, { token: token }) + .then((response) => { + setAccounts(response.data); + setBalance(0); + setAccountId(response.data[0]?.id); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + }) + .catch((error) => { + console.error('Error creating account:', error.response.data); + }); + } + + useEffect(() => { + if (token) { + axios + .post('/api/@me', { token: token }) + .then((response) => { + setThisUser(response.data); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + + axios + .post(`/api/users/${id}`, { token: token }) + .then((response) => { + setUser(response.data); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + + axios + .post(`/api/users/${id}/accounts`, { token: token }) + .then((response) => { + setAccounts(response.data); + setAccountId(response.data[0]?.id); + }) + .catch((error) => { + console.error('Error fetching user:', error.response.data); + }); + + axios + .post(`/api/users/${id}/cards`, { token: token }) + .then((response) => { + setCards(response.data); + }) + .catch((error) => { + console.error('Error fetching cards:', error.response.data); + }); + } else { + navigate('/login'); + } + }, [token]); + + if (!user || !thisUser) { + return ( +
    + +
    +

    Loading...

    +
    +
    + ) + } + + return ( +
    + +
    +

    {user.name} {user.lastname}

    +

    Email: {user.email}

    +

    Phone Number: {user.numero}

    +

    Admin: {user.admin === 1 ? 'Yes' : 'No'}

    +
    +
    +

    User Accounts

    + {accounts.length > 0 ? ( + accounts.map((account: any) => ( +
    +

    + Name: {account.name} - Balance: {account.balance} - Interest: {account.interest} +

    + +
    + )) + ) : ( +

    No accounts found.

    + )} +
    + {accounts.length > 0 && ( +
    +

    Add balance

    +
    + + setBalance(Number(e.target.value))} value={balance} /> + +
    +
    + )} +
    +

    Create Account

    +
    + setName(e.target.value)} value={name} /> + +
    +
    +
    +

    User Cards

    + {cards.length > 0 ? ( + cards.map((card: any) => ( +
    +

    + Card Account: {card.account_name} - Card Number: {card.number} - Expiration: {card.expiration} - CVC: {card.cvc} +

    + +
    + )) + ) : ( +

    No cards found.

    + )} +
    + {accounts.length > 0 && ( +
    +

    Add card

    +
    + + +
    +
    + )} +
    + ); +} \ No newline at end of file diff --git a/front/src/types.ts b/front/src/types.ts index 989c999..0d51f2e 100644 --- a/front/src/types.ts +++ b/front/src/types.ts @@ -2,9 +2,24 @@ export type User = { id: number; name: string; lastname: string; + email: string; + numero: string; + password: string; admin: number; } +export type Users = User[]; + +export type Transfer = { + id: number; + account_from_id: number; + account_to_id: number; + name: string; + value: number; +} + +export type Transfers = Transfer[]; + export type Account = { id: number; balance: number;