Introduce invocing page
parent
68149c1976
commit
c15012afa7
10
src/App.tsx
10
src/App.tsx
|
|
@ -1,14 +1,15 @@
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { Login } from "./pages/Login.tsx";
|
import { Login } from "./pages/auth/Login.tsx";
|
||||||
import icon from "./paella-icon.png";
|
import icon from "./paella-icon.png";
|
||||||
import { Outlet, Route, Routes } from "react-router";
|
import { Outlet, Route, Routes } from "react-router";
|
||||||
import { SignUpPage } from "./pages/signup/SignUpPage.tsx";
|
import { SignUpPage } from "./pages/signup/SignUpPage.tsx";
|
||||||
import { Register } from "./pages/Register.tsx";
|
import { Register } from "./pages/auth/Register.tsx";
|
||||||
import { Buy } from "./pages/Buy.tsx";
|
import { Buy } from "./pages/buy/Buy.tsx";
|
||||||
import { NotFound } from "./pages/NotFound.tsx";
|
import { NotFound } from "./pages/NotFound.tsx";
|
||||||
import { BuyReturn } from "./pages/BuyReturn.tsx";
|
import { BuyReturn } from "./pages/buy/BuyReturn.tsx";
|
||||||
import { Grid, Paper } from "@mui/material";
|
import { Grid, Paper } from "@mui/material";
|
||||||
import { AuthenticatedRoute } from "./pages/auth/AuthenticatedRoute.tsx";
|
import { AuthenticatedRoute } from "./pages/auth/AuthenticatedRoute.tsx";
|
||||||
|
import { InvoicePage } from "./pages/invoice/InvoicePage.tsx";
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Paper
|
<Paper
|
||||||
|
|
@ -38,6 +39,7 @@ const App = () => (
|
||||||
<Route path="/signup" element={<SignUpPage />} />
|
<Route path="/signup" element={<SignUpPage />} />
|
||||||
<Route path="/buy" element={<Buy />} />
|
<Route path="/buy" element={<Buy />} />
|
||||||
<Route path="/buy/return" element={<BuyReturn />} />
|
<Route path="/buy/return" element={<BuyReturn />} />
|
||||||
|
<Route path="/summary" element={<InvoicePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
|
|
@ -86,3 +86,23 @@ export const getAvailableTokenCount = () =>
|
||||||
(axios.get("/tokens", getJwtHeader()) as Promise<AxiosResponse<Tokens>>).then(
|
(axios.get("/tokens", getJwtHeader()) as Promise<AxiosResponse<Tokens>>).then(
|
||||||
(response) => response.data,
|
(response) => response.data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export interface InvoiceSummary {
|
||||||
|
purchaseData: {
|
||||||
|
product: string;
|
||||||
|
units: number;
|
||||||
|
status: string;
|
||||||
|
date: string;
|
||||||
|
}[];
|
||||||
|
sessionsData: {
|
||||||
|
date: string;
|
||||||
|
status: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getInvoicingSummary = () =>
|
||||||
|
(
|
||||||
|
axios.get("/invoice", getJwtHeader()) as Promise<
|
||||||
|
AxiosResponse<InvoiceSummary>
|
||||||
|
>
|
||||||
|
).then((response) => response.data);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { login } from "../api/client";
|
import { login } from "../../api/client";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import * as zod from "zod";
|
import * as zod from "zod";
|
||||||
import { registerUser } from "../api/client";
|
import { registerUser } from "../../api/client";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { startBuyFlow } from "../api/client";
|
import { startBuyFlow } from "../../api/client";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
|
|
@ -22,7 +22,6 @@ export const Buy = () => {
|
||||||
return (
|
return (
|
||||||
<Grid container direction="column" spacing={2} alignItems="center">
|
<Grid container direction="column" spacing={2} alignItems="center">
|
||||||
<Typography variant="h3">Buy tokens</Typography>
|
<Typography variant="h3">Buy tokens</Typography>
|
||||||
|
|
||||||
<Paper
|
<Paper
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { completeBuyFlow } from "../api/client";
|
import { completeBuyFlow } from "../../api/client";
|
||||||
import { CircularProgress, Grid, Typography } from "@mui/material";
|
import { CircularProgress, Grid, Typography } from "@mui/material";
|
||||||
|
|
||||||
export const BuyReturn = () => {
|
export const BuyReturn = () => {
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Async } from "react-async";
|
||||||
|
import { getInvoicingSummary, type InvoiceSummary } from "../../api/client";
|
||||||
|
import { Alert, Button, Grid, Typography } from "@mui/material";
|
||||||
|
import { PurchaseSummaryTable } from "./PurchaseSummaryTable";
|
||||||
|
import { SessionSummaryTable } from "./SessionSummaryTable";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
|
export const InvoicePage = () => {
|
||||||
|
const invoiceSummaryPromise = getInvoicingSummary();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container direction="column" spacing={2} alignItems="center">
|
||||||
|
<Typography variant="h3">Summary</Typography>
|
||||||
|
<Async promise={invoiceSummaryPromise}>
|
||||||
|
<Async.Fulfilled<InvoiceSummary>>
|
||||||
|
{(invoiceSummary) => (
|
||||||
|
<Grid container direction="column" spacing={2}>
|
||||||
|
<PurchaseSummaryTable invoiceSummary={invoiceSummary} />
|
||||||
|
<SessionSummaryTable invoiceSummary={invoiceSummary} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ width: "100%" }}
|
||||||
|
onClick={() => navigate("/signup")}
|
||||||
|
>
|
||||||
|
Back to sessions
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Async.Fulfilled>
|
||||||
|
<Async.Rejected>
|
||||||
|
<Alert severity="error">Something went wrong</Alert>
|
||||||
|
</Async.Rejected>
|
||||||
|
</Async>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { type InvoiceSummary } from "../../api/client";
|
||||||
|
import {
|
||||||
|
Paper,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FormattedDate } from "react-intl";
|
||||||
|
|
||||||
|
export const PurchaseSummaryTable = (props: {
|
||||||
|
invoiceSummary: InvoiceSummary;
|
||||||
|
}) => {
|
||||||
|
const { invoiceSummary } = props;
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
const rowsPerPage = 5;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
p: 3,
|
||||||
|
bgcolor: "aliceblue",
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
paddingBottom: "10px",
|
||||||
|
}}
|
||||||
|
variant="h6"
|
||||||
|
>
|
||||||
|
Purchases summary
|
||||||
|
</Typography>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center" sx={{ fontWeight: "bold" }}>
|
||||||
|
Purchase date
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" sx={{ fontWeight: "bold" }}>
|
||||||
|
Units
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" sx={{ fontWeight: "bold" }}>
|
||||||
|
Purchase status
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{invoiceSummary.purchaseData
|
||||||
|
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||||
|
.map(({ date, units, status }) => (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center">
|
||||||
|
<FormattedDate
|
||||||
|
value={new Date(date)}
|
||||||
|
day="numeric"
|
||||||
|
month="short"
|
||||||
|
year="numeric"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">{units}</TableCell>
|
||||||
|
<TableCell align="center">{status}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
<TablePagination
|
||||||
|
component="div"
|
||||||
|
count={invoiceSummary.purchaseData.length}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
page={page}
|
||||||
|
onPageChange={(_, newPage) => setPage(newPage)}
|
||||||
|
rowsPerPageOptions={[]}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { type InvoiceSummary } from "../../api/client";
|
||||||
|
import {
|
||||||
|
Paper,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FormattedDate } from "react-intl";
|
||||||
|
|
||||||
|
export const SessionSummaryTable = (props: {
|
||||||
|
invoiceSummary: InvoiceSummary;
|
||||||
|
}) => {
|
||||||
|
const { invoiceSummary } = props;
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
const rowsPerPage = 5;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
p: 3,
|
||||||
|
bgcolor: "aliceblue",
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
paddingBottom: "10px",
|
||||||
|
}}
|
||||||
|
variant="h6"
|
||||||
|
>
|
||||||
|
Sessions summary
|
||||||
|
</Typography>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center" sx={{ fontWeight: "bold" }}>
|
||||||
|
Session date
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" sx={{ fontWeight: "bold" }}>
|
||||||
|
Session status
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{invoiceSummary.sessionsData
|
||||||
|
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||||
|
.map(({ date, status }) => (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center">
|
||||||
|
<FormattedDate
|
||||||
|
value={new Date(date)}
|
||||||
|
day="numeric"
|
||||||
|
month="short"
|
||||||
|
year="numeric"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">{status}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
<TablePagination
|
||||||
|
component="div"
|
||||||
|
count={invoiceSummary.purchaseData.length}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
page={page}
|
||||||
|
onPageChange={(_, newPage) => setPage(newPage)}
|
||||||
|
rowsPerPageOptions={[]}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -68,9 +68,9 @@ export const TokensSummary = (props: { tokens: Tokens }) => {
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
sx={{ width: "100%" }}
|
sx={{ width: "100%" }}
|
||||||
onClick={() => navigate("/invoices")}
|
onClick={() => navigate("/summary")}
|
||||||
>
|
>
|
||||||
See all purchases
|
See your summary
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const serverRoutes = [
|
||||||
"/buy/cancel",
|
"/buy/cancel",
|
||||||
"/session",
|
"/session",
|
||||||
"/tokens",
|
"/tokens",
|
||||||
|
"/invoice",
|
||||||
];
|
];
|
||||||
const baseServerConfigBuilder = (domain: string) => ({
|
const baseServerConfigBuilder = (domain: string) => ({
|
||||||
proxy: Object.fromEntries(
|
proxy: Object.fromEntries(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue