diff --git a/client/src/assets/spinner.gif b/client/src/assets/spinner.gif new file mode 100644 index 0000000..0424aa4 Binary files /dev/null and b/client/src/assets/spinner.gif differ diff --git a/client/src/components/Layout/AddWallet/AddWallet.jsx b/client/src/components/Layout/AddWallet/AddWallet.jsx index 39bf772..f33e1fb 100644 --- a/client/src/components/Layout/AddWallet/AddWallet.jsx +++ b/client/src/components/Layout/AddWallet/AddWallet.jsx @@ -22,7 +22,7 @@ import AppContext from '../../../context/app/context'; // api import { postWallets } from '../../../utils/http-request'; -// import getWallets from '../../../utils/http-request'; +import { getWallets } from '../../../utils/http-request'; const walletSchema = z.object({ name: z.string(), @@ -46,7 +46,7 @@ const AddWallet = ({ open, handleClose }) => { setWallets(response); }; fetchWallets(); - }, [wallets]); + }, []); // useEffect(() => { // setWallets(appContext.wallets); diff --git a/client/src/components/Layout/Sidebar/SidebarDesktop.jsx b/client/src/components/Layout/Sidebar/SidebarDesktop.jsx index 4a8efe6..6aa7934 100644 --- a/client/src/components/Layout/Sidebar/SidebarDesktop.jsx +++ b/client/src/components/Layout/Sidebar/SidebarDesktop.jsx @@ -7,7 +7,7 @@ import { AiOutlineCaretRight, AiOutlineMenu } from 'react-icons/ai'; const buckets = [ { - name: 'Sign In', + name: 'About us', color: '#FDD652', }, ]; @@ -32,7 +32,6 @@ const Sidebar = ({ isMinimized, setIsMinimized, }) => { - // const [isMinimized, setIsMinimized] = useState(false); const toggleIsMinimized = () => { setIsMinimized(!isMinimized); @@ -50,7 +49,6 @@ const Sidebar = ({ color: 'gray ', zIndex: '10', backgroundColor: '#f5f6f7', - // backgroundColor: 'background.dark', boxShadow: '0 0 10px rgba(0,0,0,0.2)', transition: 'width 0.2s', minWidth: '50px', diff --git a/client/src/components/Layout/Spinner/Spinner.jsx b/client/src/components/Layout/Spinner/Spinner.jsx new file mode 100644 index 0000000..5caf0e6 --- /dev/null +++ b/client/src/components/Layout/Spinner/Spinner.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import spinner from '../../../assets/spinner.gif'; + +const Spinner = () => { + return ( +
+ Loading... +
+ ); +}; + +export default Spinner; diff --git a/client/src/components/Layout/wrapper/LayoutWrapper.jsx b/client/src/components/Layout/wrapper/LayoutWrapper.jsx index fc2677a..cff15e2 100644 --- a/client/src/components/Layout/wrapper/LayoutWrapper.jsx +++ b/client/src/components/Layout/wrapper/LayoutWrapper.jsx @@ -33,7 +33,7 @@ const LayoutWrapper = ({ children }) => { > -
{children}
+
{children}
); diff --git a/client/src/components/general/TableData/TableData.css b/client/src/components/general/TableData/TableData.css deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/components/general/TableData/TableData.jsx b/client/src/components/general/TableData/TableData.jsx index da54d4c..9a2a02c 100644 --- a/client/src/components/general/TableData/TableData.jsx +++ b/client/src/components/general/TableData/TableData.jsx @@ -1,377 +1,54 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; -import { alpha } from '@mui/material/styles'; -import Box from '@mui/material/Box'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TablePagination from '@mui/material/TablePagination'; -import TableRow from '@mui/material/TableRow'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import Toolbar from '@mui/material/Toolbar'; -import Typography from '@mui/material/Typography'; -import Paper from '@mui/material/Paper'; -import Checkbox from '@mui/material/Checkbox'; -import IconButton from '@mui/material/IconButton'; -import Tooltip from '@mui/material/Tooltip'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Switch from '@mui/material/Switch'; -import DeleteIcon from '@mui/icons-material/Delete'; -import FilterListIcon from '@mui/icons-material/FilterList'; -import { Button } from '@mui/material'; -import { visuallyHidden } from '@mui/utils'; +import Chip from '@mui/material/Chip'; +import MUIDataTable, { TableFilterList } from 'mui-datatables'; +import { formatDate } from '../../../utils/formatDate'; - -function descendingComparator(a, b, orderBy) { - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; -} - -function getComparator(order, orderBy) { - return order === 'desc' - ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy); -} - -function stableSort(array, comparator) { - const stabilizedThis = array.map((el, index) => [el, index]); - stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]); - if (order !== 0) { - return order; - } - return a[1] - b[1]; - }); - return stabilizedThis.map((el) => el[0]); -} - -const headCells = [ +const columns = [ { - id: 'name', - numeric: false, - disablePadding: true, + name: 'name', label: 'Wallet Name', + options: { + filter: true, + sort: true, + }, }, { - id: 'walletID', - numeric: true, - disablePadding: false, - label: 'ID', - }, - { - id: 'description', - numeric: false, - disablePadding: false, + name: 'description', label: 'Description', + options: { + filter: false, + sort: false, + }, }, { - id: 'createdAt', - numeric: true, - disablePadding: false, - label: 'Date', + name: 'createdAt', + label: 'Date created', + options: { + filter: true, + sort: true, + customBodyRender: (value) => formatDate(value), // Format the date using formatDate function + }, }, ]; -function EnhancedTableHead({ - onSelectAllClick, - order, - orderBy, - numSelected, - rowCount, - onRequestSort, - data, -}) { - const createSortHandler = (property) => (event) => { - onRequestSort(event, property); - }; - - return ( - - - - 0 && numSelected < rowCount} - checked={rowCount > 0 && numSelected === rowCount} - onChange={onSelectAllClick} - inputProps={{ - 'aria-label': 'select all desserts', - }} - /> - - {headCells.map((headCell) => ( - - - {headCell.label} - {orderBy === headCell.id ? ( - - {order === 'desc' ? 'sorted descending' : 'sorted ascending'} - - ) : null} - - - ))} - - - ); -} - -EnhancedTableHead.propTypes = { - numSelected: PropTypes.number.isRequired, - onRequestSort: PropTypes.func.isRequired, - onSelectAllClick: PropTypes.func.isRequired, - order: PropTypes.oneOf(['asc', 'desc']).isRequired, - orderBy: PropTypes.string.isRequired, - rowCount: PropTypes.number.isRequired, - data: PropTypes.array.isRequired, +const options = { + filterType: 'checkbox', + textLabels: { + body: { + noMatch: 'Loading wallets... hihi ^^', + }, + }, }; -function EnhancedTableToolbar({ numSelected, data}) { - const [row, setRow] = React.useState([...data]); - const [selected, setSelected] = React.useState([]); - const handleDelete = () => { - // const updatedRows = row.filter((e) => !selected.includes(e.name)); - // setRow(updatedRows); - // setSelected([]); - }; - +const TableData = ({ data }) => { return ( - 0 && { - bgcolor: (theme) => - alpha( - theme.palette.primary.main, - theme.palette.action.activatedOpacity - ), - }), - }} - > - {numSelected > 0 ? ( - - {numSelected} selected - - ) : ( - - Tracking - - )} - - {numSelected > 0 ? ( - - - - - - ) : ( - - - - - - )} - + ); -} - -EnhancedTableToolbar.propTypes = { - numSelected: PropTypes.number.isRequired, - data: PropTypes.array.isRequired, }; -export default function EnhancedTable({ data }) { - const [order, setOrder] = React.useState('asc'); - const [orderBy, setOrderBy] = React.useState('id'); - const [selected, setSelected] = React.useState([]); - const [page, setPage] = React.useState(0); - const [dense, setDense] = React.useState(false); - const [rowsPerPage, setRowsPerPage] = React.useState(5); - - const handleRequestSort = (event, property) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - const handleSelectAllClick = (event) => { - if (event.target.checked) { - const newSelected = data.map((n) => n.name); - setSelected(newSelected); - return; - } - setSelected([]); - }; - - const handleClick = (event, name) => { - const selectedIndex = selected.indexOf(name); - let newSelected = []; - - if (selectedIndex === -1) { - newSelected = newSelected.concat(selected, name); - } else if (selectedIndex === 0) { - newSelected = newSelected.concat(selected.slice(1)); - } else if (selectedIndex === selected.length - 1) { - newSelected = newSelected.concat(selected.slice(0, -1)); - } else if (selectedIndex > 0) { - newSelected = newSelected.concat( - selected.slice(0, selectedIndex), - selected.slice(selectedIndex + 1) - ); - } - - setSelected(newSelected); - }; - - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = (event) => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - - const handleChangeDense = (event) => { - setDense(event.target.checked); - }; - - const isSelected = (name) => selected.indexOf(name) !== -1; - - // Avoid a layout jump when reaching the last page with empty rows. - const emptyRows = - page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0; - - const visibleRows = React.useMemo( - () => - stableSort(data, getComparator(order, orderBy)).slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ), - [order, orderBy, page, rowsPerPage] - ); - - // const selectedRows = visibleRows.filter((row) => - // newSelected.includes(row.name) - // ); - // setSelectedRows(selectedRows); - - return ( - - - - - - - - {visibleRows.map((row, index) => { - const isItemSelected = isSelected(row.name); - const labelId = `enhanced-table-checkbox-${index}`; - - return ( - handleClick(event, row.name)} - role='checkbox' - aria-checked={isItemSelected} - tabIndex={-1} - key={row.name} - selected={isItemSelected} - sx={{ cursor: 'pointer' }} - > - - - - - {row.name} - - {row.walletID} - {row.description} - {row.createdAt} - - ); - })} - {emptyRows > 0 && ( - - - - )} - -
-
- -
- } - label='Dense padding' - /> -
- ); -} +export default TableData; diff --git a/client/src/components/general/TransactionAdd.jsx b/client/src/components/general/TransactionAdd.jsx index 4da97a5..960a44d 100644 --- a/client/src/components/general/TransactionAdd.jsx +++ b/client/src/components/general/TransactionAdd.jsx @@ -48,13 +48,11 @@ const TransactionAdd = ({ open, handleClose }) => { const fetchWallets = async () => { const response = await getWallets(); setWallets(response); - // await appContext.getWallets(); - // setWallets(appContext.wallets); }; fetchWallets(); - }, [wallets]); + }, []); - + //fetch transactions useEffect(() => { const fetchTransactions = async () => { const response = await getTransactions(); diff --git a/client/src/components/general/table/CustomTable.jsx b/client/src/components/general/table/CustomTable.jsx index 77470e5..c2f5416 100644 --- a/client/src/components/general/table/CustomTable.jsx +++ b/client/src/components/general/table/CustomTable.jsx @@ -1,43 +1,45 @@ -import * as React from "react"; -import Chip from "@mui/material/Chip"; -import MUIDataTable, { TableFilterList } from "mui-datatables"; +import * as React from 'react'; +import Chip from '@mui/material/Chip'; +import MUIDataTable, { TableFilterList } from 'mui-datatables'; +import appContext from '../../../context/app/context'; +import Spinner from '../../Layout/Spinner/Spinner'; const columns = [ { - name: "walletName", - label: "Wallet Name", + name: 'walletName', + label: 'Wallet Name', options: { filter: true, sort: true, }, }, { - name: "amount", - label: "Amount", + name: 'amount', + label: 'Amount', options: { filter: true, sort: true, }, }, { - name: "createdAt", - label: "Date created", + name: 'createdAt', + label: 'Date created', options: { filter: true, sort: true, }, }, { - name: "description", - label: "Description", + name: 'description', + label: 'Description', options: { filter: false, sort: false, }, }, { - name: "type", - label: "Type", + name: 'type', + label: 'Type', options: { filter: true, sort: true, @@ -46,15 +48,20 @@ const columns = [ ]; const options = { - filterType: "checkbox", + filterType: 'checkbox', + textLabels: { + body: { + noMatch: 'Let sip a tea while the transactions are loaded... hihi :)', + }, + }, }; -const CustomTable = ({transactions, loading}) => { +const CustomTable = ({ transactions }) => { const data = transactions; - + const { loading } = React.useContext(appContext); return ( : data} columns={columns} options={options} loading={loading} diff --git a/client/src/components/pages/dashboard/Dashboard.css b/client/src/components/pages/dashboard/Dashboard.css new file mode 100644 index 0000000..8ece7d8 --- /dev/null +++ b/client/src/components/pages/dashboard/Dashboard.css @@ -0,0 +1,68 @@ +.rubberBand:active { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} +@-webkit-keyframes rubberBand { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} +@keyframes rubberBand { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} diff --git a/client/src/components/pages/dashboard/Dashboard.jsx b/client/src/components/pages/dashboard/Dashboard.jsx index 97a1ff0..0fc6f31 100644 --- a/client/src/components/pages/dashboard/Dashboard.jsx +++ b/client/src/components/pages/dashboard/Dashboard.jsx @@ -3,23 +3,28 @@ import Section from '../../Layout/Section/Section'; import LineChart from './component/LineChart/LineChart'; import PieCharts from './component/PieChart/PieCharts'; import DBCard from './component/DBCard/DBCard'; -// import CustomTable from '../../general/table/CustomTable'; +import RecentTable from './component/RecentTable/RecentTable'; import { Grid, Stack, Typography, useMediaQuery } from '@mui/material'; -import { HiCurrencyDollar } from 'react-icons/hi'; //api -import { getTransactions } from '../../../utils/http-request'; -import AddWallet from '../../Layout/AddWallet/AddWallet'; +import { getTransactions, getWallets } from '../../../utils/http-request'; +// import AppContext from '../../../../context/app-context'; //image import char5 from '../../../assets/char5.svg'; -import pointDown from '../../../assets/pointDown.jpg'; +import { HiCurrencyDollar } from 'react-icons/hi'; import assistant from '../../../assets/assistant.webp'; +//styling +import './Dashboard.css'; + const Dashboard = () => { const [transactionData, setTransactionData] = useState([]); + const [wallets, setWallets] = useState([]); const isSmallScreen = useMediaQuery((theme) => theme.breakpoints.down('sm')); + // const { getTransactions } = React.useContext(AppContext); + //fetch transaction data useEffect(() => { const fetchTransactions = async () => { try { @@ -29,10 +34,22 @@ const Dashboard = () => { console.log(error); } }; - fetchTransactions(); }, []); + //fetch wallet data + useEffect(() => { + const fetchWallets = async () => { + try { + const wallets = await getWallets(); + setWallets(wallets); + } catch (error) { + console.log(error); + } + }; + fetchWallets(); + }, []); + //hardcoded data for the charts const lineData = [ { @@ -157,6 +174,7 @@ const Dashboard = () => { value: '23444', }, ]; + return (
{ spacing={2} > { marginBottom: '20px', }} > - - Welcome back, Trang! - + {isSmallScreen ? ( + + Welcome back, Trang! + + ) : ( + + Welcome back, Trang! + + )} + { alignItems: 'center', height: 'fit-content', width: '100%', - padding: '20px', + padding: '5px', background: 'white', borderRadius: '10px', marginTop: '20px', @@ -249,7 +278,7 @@ const Dashboard = () => { : { display: 'flex', flexDirection: 'column', - justifyContent: 'space-between', + justifyContent: 'center', alignItems: 'center', width: '100%', padding: '30px', @@ -278,10 +307,16 @@ const Dashboard = () => { >

Recent Transactions

Latest transaction all the time

- {/* */} +
- +
diff --git a/client/src/components/pages/dashboard/component/LineChart/LineChart.jsx b/client/src/components/pages/dashboard/component/LineChart/LineChart.jsx index 1bd890c..95d0f60 100644 --- a/client/src/components/pages/dashboard/component/LineChart/LineChart.jsx +++ b/client/src/components/pages/dashboard/component/LineChart/LineChart.jsx @@ -12,12 +12,7 @@ import { import PropTypes from 'prop-types'; const LineChart = ({ data }) => { return ( - + diff --git a/client/src/components/pages/dashboard/component/PieChart/PieCharts.jsx b/client/src/components/pages/dashboard/component/PieChart/PieCharts.jsx index 601cc2b..a415d25 100644 --- a/client/src/components/pages/dashboard/component/PieChart/PieCharts.jsx +++ b/client/src/components/pages/dashboard/component/PieChart/PieCharts.jsx @@ -20,12 +20,12 @@ const COLORS = [ const PieCharts = ({ pieData }) => { return ( - + { + // Convert the data object to an array and slice it to only include the first 5 elements + const mergedData = transactionsData.map((transaction) => { + const wallet = walletsData.find((wallet) => wallet.id === transaction.walletId); + return { + name: wallet?.name || 'Unknown Wallet', + amount: transaction.amount, + }; + }); + + // Sort the mergedData by the most recent transactions + const sortedData = mergedData.sort((a, b) => b.createdAt - a.createdAt); + + // Slice the data array to only include the first 5 elements + const slicedData = sortedData.slice(0, 5); + + return ( + + + + + + Transaction + Amount + + + + {slicedData.map((row) => ( + + + {row.name} + + {row.amount} + + ))} + +
+ View More +
+
+ ); +}; + +export default RecentTable; diff --git a/client/src/components/pages/transaction/TransactionsPage.jsx b/client/src/components/pages/transaction/TransactionsPage.jsx index 6dc4604..fa4676c 100644 --- a/client/src/components/pages/transaction/TransactionsPage.jsx +++ b/client/src/components/pages/transaction/TransactionsPage.jsx @@ -1,31 +1,126 @@ -import { useContext, useState, useEffect, useCallback } from "react"; -import "./Transactions.css"; -import { Stack } from "@mui/material"; -import CustomTabs from "@/components/general/CustomTabs"; +// import { useContext, useState, useEffect, useCallback } from 'react'; +// import './Transactions.css'; +// import { Stack } from '@mui/material'; +// import CustomTabs from '@/components/general/CustomTabs'; -import Section from "../../Layout/Section/Section"; -import TransactionGridView from "./components/TransactionGridView/TransactionGridView"; +// import Section from '../../Layout/Section/Section'; +// import TransactionGridView from './components/TransactionGridView/TransactionGridView'; +// import CustomTable from '../../general/table/CustomTable'; +// import { +// formatTransactionList, +// generateFakeTransactionData, +// generateFakeWallets, +// pushTransactions, +// pushWallets, +// } from '@/utils/helper'; +// // import { getTransactions } from "@/utils/http-request"; +// import AppContext from '@/context/app/context'; + +// const TransactionPage = () => { +// const [activeTab, setView] = useState(0); +// const [transactionData, setTransactionData] = useState([]); + +// const { transactions, getTransactions, loading } = useContext(AppContext); +// const changeView = (event, newView) => { +// setView(newView); +// }; + +// useEffect(() => { +// const feedData = async () => { +// const wallets = generateFakeWallets(1); +// try { +// await pushWallets(wallets); +// } catch (error) { +// console.log(error); +// } + +// try { +// const trans = await generateFakeTransactionData(1); +// console.log(trans); +// await pushTransactions(trans); +// } catch (error) { +// console.log(error); +// } +// }; +// const fetchTransactions = async () => { +// try { +// await getTransactions(); +// // memoize the function +// const formattedTransaction = await formatTransactionList(transactions); + +// setTransactionData(formattedTransaction); +// } catch (error) { +// console.log(error); +// } +// }; +// if (transactions.length === 0 || transactionData.length === 0) { +// // feedData(); +// fetchTransactions(); +// } +// }, [transactionData]); + +// const tabs = [ +// { +// id: 'list', +// label: 'List', +// component: , +// }, +// { +// id: 'grid', +// label: 'Grid', +// component: , +// }, +// ]; +// return ( +//
+//
+// +// +// +// +// +// {tabs[activeTab].component} +//
+//
+// ); +// }; + +// export default TransactionPage; + +import { useContext, useState, useEffect, useCallback } from 'react'; +import './Transactions.css'; +import { Stack } from '@mui/material'; +import CustomTabs from '@/components/general/CustomTabs'; + +import Section from '../../Layout/Section/Section'; +import TransactionGridView from './components/TransactionGridView/TransactionGridView'; import { formatTransactionList, generateFakeTransactionData, generateFakeWallets, pushTransactions, pushWallets, -} from "@/utils/helper"; +} from '@/utils/helper'; // import { getTransactions } from "@/utils/http-request"; -import AppContext from "@/context/app/context"; -import CustomTable from "../../general/table/CustomTable"; +import AppContext from '@/context/app/context'; +import CustomTable from '../../general/table/CustomTable'; const TransactionPage = () => { const [activeTab, setView] = useState(0); const [transactionData, setTransactionData] = useState([]); - - const {transactions, getTransactions, loading} = useContext(AppContext); + + const { transactions, getTransactions, loading } = useContext(AppContext); const changeView = (event, newView) => { setView(newView); }; - - + useEffect(() => { const feedData = async () => { const wallets = generateFakeWallets(1); @@ -48,9 +143,8 @@ const TransactionPage = () => { await getTransactions(); // memoize the function const formattedTransaction = await formatTransactionList(transactions); - + setTransactionData(formattedTransaction); - } catch (error) { console.log(error); } @@ -59,31 +153,28 @@ const TransactionPage = () => { // feedData(); fetchTransactions(); } - - }, [transactionData]); - const tabs = [ { - id: "list", - label: "List", + id: 'list', + label: 'List', component: , }, { - id: "grid", - label: "Grid", + id: 'grid', + label: 'Grid', component: , }, ]; return (
-
+
- + { +// const appContext = useContext(AppContext); +// var [activeTab, setView] = useState(0); + +// const changeView = (event, newView) => { +// setView(newView); +// }; + +// const {wallets, loading, getWallets} = appContext; + + +// // CORS + + +// console.log(wallets); +// useEffect(() => { +// const fetchWallets = async () => { +// await getWallets(); +// }; + +// fetchWallets(); +// }, []); // Use the memoized function as a dependency instead of getWallets + + +// // useEffect(() => { +// // setWalletData(wallets); +// // }, [wallets]); + +// const tabs = [ +// { +// id: 'list', +// label: 'List', +// component: , +// }, +// { +// id: 'grid', +// label: 'Grid', +// component: , +// }, +// ]; + +// return ( +//
+// +// +// +// +// +// {tabs[activeTab].component} +//
+// ); +// }; + +// export default WalletsPage; + import { useEffect, useState, useContext, useCallback } from 'react'; //components @@ -74,4 +152,4 @@ const WalletsPage = () => { ); }; -export default WalletsPage; +export default WalletsPage; \ No newline at end of file diff --git a/client/src/components/pages/wallet/components/WalletCard/WalletCard.css b/client/src/components/pages/wallet/components/WalletCard/WalletCard.css deleted file mode 100644 index 9752ccb..0000000 --- a/client/src/components/pages/wallet/components/WalletCard/WalletCard.css +++ /dev/null @@ -1,7 +0,0 @@ -#wallet-card-div { - display: inline-flex; - align-items: center; - justify-content: flex-start; - text-align: left; - gap: 60px; -} diff --git a/client/src/components/pages/wallet/components/WalletCard/WalletCard.jsx b/client/src/components/pages/wallet/components/WalletCard/WalletCard.jsx index 892329a..67b74ee 100644 --- a/client/src/components/pages/wallet/components/WalletCard/WalletCard.jsx +++ b/client/src/components/pages/wallet/components/WalletCard/WalletCard.jsx @@ -6,9 +6,9 @@ import CardContent from '@mui/material/CardContent'; import CardMedia from '@mui/material/CardMedia'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import './WalletCard.css'; +import { Stack } from '@mui/material'; -const WalletCard = ({ name, id, date, description }) => { +const WalletCard = ({ name, date, description }) => { // const [walletName, setWalletName] = useState(''); const buttonStyle = { @@ -34,53 +34,31 @@ const WalletCard = ({ name, id, date, description }) => { fontFamily: 'Courier New, Courier, monospace', }; - const dateAmountStyles = { - margin: 'auto', - textAlign: 'left', - fontSize: '11px', - }; - return ( - - - {name} - -
- {/* - ${amount} - */} + + + + {name} + - {date} + {description} - - {/* - {balance} - */} -
- - {description} - +
{/* category for wallet coming soon */} diff --git a/client/src/components/pages/wallet/components/WalletsView/WalletContent.jsx b/client/src/components/pages/wallet/components/WalletsView/WalletContent.jsx index a1aaada..82575ab 100644 --- a/client/src/components/pages/wallet/components/WalletsView/WalletContent.jsx +++ b/client/src/components/pages/wallet/components/WalletsView/WalletContent.jsx @@ -9,7 +9,7 @@ const WalletContent = ({ data }) => {
{ }} key={wallet.id} > - +
))}
diff --git a/client/src/context/app/state.jsx b/client/src/context/app/state.jsx index 8c738db..d1a5248 100644 --- a/client/src/context/app/state.jsx +++ b/client/src/context/app/state.jsx @@ -21,6 +21,13 @@ import GET_TOTAL_EXPENSES, GET_TOTAL_BALANCE, SET_LOADING, + AUTH_ERROR, + REGISTER_SUCCESS, + REGISTER_FAIL, + USER_LOADED, + LOGIN_SUCCESS, + LOGIN_FAIL, + LOGOUT, } from '../types'; const AppState = (props) => { @@ -34,47 +41,42 @@ const AppState = (props) => { totalExpenses: [], totalBalance: [], error: null, - loading: true, + loading: false, + token: localStorage.getItem('token'), + isAuthenticated: null, + user: null, }; const [state, dispatch] = useReducer(AppReducer, initialState); + const baseUrl = 'http://localhost:3000/api'; + + // Load User + + // Register User + + // Login User + + // Logout + + // Clear Errors // Get Wallets const getWallets = async () => { - dispatch({ type: SET_LOADING }); - try { - console.log(baseUrl); - const res = await axios.get(baseUrl + '/wallet'); - - dispatch({ - type: GET_WALLETS, - payload: res.data, - }); - } catch (err) { - - dispatch({ - type: WALLET_ERROR, - payload: err.response.msg, - }); - } + setLoading(); + // try { + const res = await axios.get(baseUrl + '/wallet'); + dispatch({ + type: GET_WALLETS, + payload: res.data, + }); + // } catch (err) { + // dispatch({ + // type: WALLET_ERROR, + // payload: err.response.msg, + // }); + // } }; - // Get Wallets by ID - // const getWallet = async (id) => { - // setLoading(); - // try { - // const res = await axios.get(baseUrl + `/wallet/${id}`); - // dispatch({ - // type: GET_WALLETS, - // payload: res.data, - // }); - // } catch (err) { - // dispatch({ - // type: WALLET_ERROR, - // payload: err.response.msg, - // }); - // } - // }; // Add Wallet const addWallet = async (wallet) => { @@ -101,6 +103,7 @@ const AppState = (props) => { // Delete Wallet const deleteWallet = async (id) => { + setLoading(); try { await axios.delete(baseUrl + `/wallet/${id}`); dispatch({ @@ -207,6 +210,10 @@ const AppState = (props) => { return ( { transactionData.push(formatteData); } return transactionData; -}; - -// Format the list of wallets -export const formatWalletList = async (data) => { - let walletData = []; - for (let i = 0; i < data.length; i++) { - const wallet = data[i]; - let formatteData = { - id: wallet.id, - name: wallet.name, - // amount: wallet.amount, - createdAt: formatDate(wallet.createdAt), - description: wallet.description, - // type: wallet.type, - }; - walletData.push(formatteData); - } - return walletData; -}; +}; \ No newline at end of file diff --git a/server/prisma/migrations/20230720024706_quan_meeting_late/migration.sql b/server/prisma/migrations/20230720024706_quan_meeting_late/migration.sql deleted file mode 100644 index 207af10..0000000 --- a/server/prisma/migrations/20230720024706_quan_meeting_late/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `image` on the `Transaction` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "Transaction" DROP COLUMN "image"; diff --git a/server/prisma/migrations/20230721163036_work_di_ma/migration.sql b/server/prisma/migrations/20230721163036_work_di_ma/migration.sql deleted file mode 100644 index a36b179..0000000 --- a/server/prisma/migrations/20230721163036_work_di_ma/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Transaction" ADD COLUMN "image" TEXT; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 9740d06..7e7ed36 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -14,10 +14,13 @@ model User { id String @id @default(uuid()) email String @unique name String + wallets Wallet[] } model Wallet { id String @id @default(uuid()) + userId String? + user User? @relation(fields: [userId], references: [id]) name String balance Float? createdAt DateTime @default(now()) diff --git a/server/src/controllers/transaction.js b/server/src/controllers/transaction.js index cafdb1e..6c5be89 100644 --- a/server/src/controllers/transaction.js +++ b/server/src/controllers/transaction.js @@ -6,45 +6,44 @@ const prisma = require('../db/prisma'); // get all transactions // endpoint: /api/transaction const getTransaction = async (req, res) => { - try { - const transactions = await prisma.transaction.findMany(); - res.status(200).json(transactions); - } catch (error) { - res.status(500).json(error); - } + try { + const transactions = await prisma.transaction.findMany(); + res.status(200).json(transactions); + } catch (error) { + res.status(500).json(error); + } }; // get transaction by id // endpoint: /api/transaction/:id const getTransactionById = async (req, res) => { - const id = req.params.id; - try { - const transaction = await prisma.transaction.findUnique({ - where: { - id: id - } - }); - res.status(200).json(transaction); - } catch (error) { - res.status(500).json(error); - } + const id = req.params.id; + try { + const transaction = await prisma.transaction.findUnique({ + where: { + id: id, + }, + }); + res.status(200).json(transaction); + } catch (error) { + res.status(500).json(error); + } }; - // get all transactions by wallet id // endpoint: /api/transaction/wallet/:id const getTransactionByWalletId = async (req, res) => { - const id = req.params.id; - try { - const transactions = await prisma.transaction.findMany({ - where: { - walletId: id - } - }); - res.status(200).json(transactions); - } catch (error) { - res.status(500).json(error); - } + const id = req.params.id; + try { + const transactions = await prisma.transaction.findMany({ + where: { + walletId: id, + }, + }); + res.status(200).json(transactions); + } catch (error) { + res.status(500).json(error); + } }; // create transaction @@ -70,47 +69,47 @@ const createTransaction = async (req, res) => { // delete transaction // endpoint: /api/transaction/delete/:id const deleteTransaction = async (req, res) => { - const id = req.params.id; - try { - const result = await prisma.transaction.delete({ - where: { - id: id - } - }); - res.status(200).json(result); - } catch (error) { - res.status(500).json(error); - } + const id = req.params.id; + try { + const result = await prisma.transaction.delete({ + where: { + id: id, + }, + }); + res.status(200).json(result); + } catch (error) { + res.status(500).json(error); + } }; // update transaction // endpoint: /api/transaction/update/:id const updateTransaction = async (req, res) => { - const id = req.params.id; - const { amount, description, type, walletId } = req.body; - try { - const result = await prisma.transaction.update({ - where: { - id: id - }, - data: { - amount: amount, - description: description, - type: type, - walletId: walletId - } - }); - res.status(200).json(result); - } catch (error) { - res.status(500).json(error); - } + const id = req.params.id; + const { amount, description, type, walletId } = req.body; + try { + const result = await prisma.transaction.update({ + where: { + id: id, + }, + data: { + amount: amount, + description: description, + type: type, + walletId: walletId, + }, + }); + res.status(200).json(result); + } catch (error) { + res.status(500).json(error); + } }; module.exports = { - getTransaction, - getTransactionById, - getTransactionByWalletId, - createTransaction, - deleteTransaction, - updateTransaction -} \ No newline at end of file + getTransaction, + getTransactionById, + getTransactionByWalletId, + createTransaction, + deleteTransaction, + updateTransaction, +}; diff --git a/server/src/controllers/user.js b/server/src/controllers/user.js new file mode 100644 index 0000000..7020464 --- /dev/null +++ b/server/src/controllers/user.js @@ -0,0 +1,53 @@ +const prisma = require('../db/prisma'); + +const getUsers = async (req, res) => { + try { + const users = await prisma.user.findMany(); + res.json(users); + } catch (error) { + res.json(error); + } +}; + +const getUserById = async (req, res) => { + try { + const { id } = req.params; + const user = await prisma.user.findUnique({ + where: { + id: id, + }, + }); + res.json(user); + } catch (error) { + res.json(error); + } +}; + +const createUser = async (req, res) => { + try { + const { email, password } = req.body; + const result = await prisma.user.create({ + data: { + email: email, + password: password, + }, + }); + res.status(201).json(result); + } catch (error) { + res.json(error); + } +}; + +const getWalletsByUserId = async (req, res) => { + try { + const { id } = req.params; + const wallets = await prisma.wallet.findMany({ + where: { + id: id, + }, + }); + res.json(wallets); + } catch (error) { + res.json(error); + } +}; diff --git a/server/src/controllers/wallet.js b/server/src/controllers/wallet.js index f6bd3f7..6215f5c 100644 --- a/server/src/controllers/wallet.js +++ b/server/src/controllers/wallet.js @@ -1,4 +1,4 @@ -const prisma = require("../db/prisma"); +const prisma = require('../db/prisma'); // get all wallets //endpoint: /api/wallet @@ -15,76 +15,77 @@ const getAllWallets = async (req, res) => { // get wallet by id // endpoint: /api/wallet/:id const getWalletById = async (req, res) => { - const id = req.params.id; - try { - const wallet = await prisma.wallet.findUnique({ - where: { - id: id - } - }); - res.status(200).json(wallet); - } catch (error) { - res.status(500).json(error); - } + const id = req.params.id; + try { + const wallet = await prisma.wallet.findUnique({ + where: { + id: id, + }, + }); + res.status(200).json(wallet); + } catch (error) { + res.status(500).json(error); + } }; // create wallet // endpoint: /api/wallet/create const createWallet = async (req, res) => { - const name = req.body.name; - try { - const result = await prisma.wallet.create({ - data: { - name: name - } - }); - res.status(201).json(result) - } catch (error) { - res.status(500).json(error); - } + const { name, description, createdAt } = req.body; + try { + const result = await prisma.wallet.create({ + data: { + name: name, + description: description, + createdAt: createdAt, + }, + }); + res.status(201).json(result); + } catch (error) { + res.status(500).json(error); + } }; - // delete wallet // endpoint: /api/wallet/delete/:id const deleteWallet = async (req, res) => { - const id = req.params.id; - try { - const result = await prisma.wallet.delete({ - where: { - id: id - } - }); - res.status(201).json(result); - } catch (error) { - res.status(500).json(error); - } + const id = req.params.id; + try { + const result = await prisma.wallet.delete({ + where: { + id: id, + }, + }); + res.status(201).json(result); + } catch (error) { + res.status(500).json(error); + } }; // name change // endpoint: /api/wallet/name/:id const changeWalletName = async (req, res) => { - const id = req.params.id; - const name = req.body.name; - try { - const result = await prisma.wallet.update({ - where: { - id: id - }, - data: { - name: name - } - }); - res.status(201).json(result); - } catch (error) { - res.status(500).json(error); - } + const id = req.params.id; + const name = req.body.name; + try { + const result = await prisma.wallet.update({ + where: { + id: id, + }, + data: { + name: name, + }, + }); + res.status(201).json(result); + } catch (error) { + res.status(500).json(error); + } }; module.exports = { - getAllWallets, - getWalletById, - createWallet, - deleteWallet, - changeWalletName -}; \ No newline at end of file + getAllWallets, + getWalletById, + createWallet, + deleteWallet, + changeWalletName, +}; diff --git a/server/src/index.js b/server/src/index.js index 37d32dd..96c0eba 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -52,3 +52,4 @@ console.log("Backend is running"); app.listen(PORT, () => { console.log('Listening on port ' + PORT); }); + diff --git a/server/src/router/user.js b/server/src/router/user.js new file mode 100644 index 0000000..b995cda --- /dev/null +++ b/server/src/router/user.js @@ -0,0 +1,21 @@ +const express = require('express'); +const validate = require('../middleware/validate'); +const userSchema = require('../validations/user.validation'); + +const { getUser, getUserById, createUser } = require('../controllers/user'); + +const router = express.Router(); + +//get all users +router.get('/', getUser); + +//get user by id +router.get('/:id', getUserById); + +//create user +router.post('/create', validate(userSchema), createUser); + +//get wallets by user id +router.get('/:id/wallets', getUserById); + +module.exports = router; \ No newline at end of file diff --git a/server/src/validations/transaction.validation.js b/server/src/validations/transaction.validation.js index 83239de..0e716ba 100644 --- a/server/src/validations/transaction.validation.js +++ b/server/src/validations/transaction.validation.js @@ -1,4 +1,4 @@ -const {z} = require('zod'); +const { z } = require('zod'); const transactionSchema = z.object({ amount: z.number().positive(), @@ -8,4 +8,4 @@ const transactionSchema = z.object({ date: z.string().optional() }); -module.exports = transactionSchema; \ No newline at end of file +module.exports = transactionSchema; diff --git a/server/src/validations/user.validation.js b/server/src/validations/user.validation.js new file mode 100644 index 0000000..592416e --- /dev/null +++ b/server/src/validations/user.validation.js @@ -0,0 +1,16 @@ +const {z} = require('zod'); + +const userSchema = a.onject({ + // username: z.string().min(1).max(255), + // password: z.string().min(1).max(255), + email: z.string().email().min(1).max(255), + fullname: z.string().min(1).max(255), + numberOfWallets: z.number().positive().optional(), +}); + +const userLoginSchema = z.object({ + username: z.string().min(1).max(255), + password: z.string().min(1).max(255), +}); + +module.exports = {userSchema, userLoginSchema}; \ No newline at end of file diff --git a/server/src/validations/wallet.validation.js b/server/src/validations/wallet.validation.js index 203ee6d..5250c88 100644 --- a/server/src/validations/wallet.validation.js +++ b/server/src/validations/wallet.validation.js @@ -7,6 +7,7 @@ const z = require('zod'); const walletSchema = z.object({ name: z.string().min(3).max(255), description: z.string().min(3).max(255).optional(), + createdAt: z.string().optional(), }); module.exports = walletSchema; \ No newline at end of file