From 97fee33264dc91d7673e0fbec31c1a237ee0a7ed Mon Sep 17 00:00:00 2001 From: MiguelMLorente Date: Sat, 6 Dec 2025 11:29:02 +0100 Subject: [PATCH] Implement session selector and summary --- src/App.tsx | 9 +- src/api/client.ts | 13 +++ src/main.tsx | 6 +- src/pages/SignUp.tsx | 140 ----------------------------- src/pages/signup/SessionAction.tsx | 106 ++++++++++++++++++++++ src/pages/signup/SessionPicker.tsx | 62 +++++++++++++ src/pages/signup/SignUpPage.tsx | 59 ++++++++++++ src/pages/signup/TokensSummary.tsx | 78 ++++++++++++++++ vite.config.ts | 1 + 9 files changed, 325 insertions(+), 149 deletions(-) delete mode 100644 src/pages/SignUp.tsx create mode 100644 src/pages/signup/SessionAction.tsx create mode 100644 src/pages/signup/SessionPicker.tsx create mode 100644 src/pages/signup/SignUpPage.tsx create mode 100644 src/pages/signup/TokensSummary.tsx diff --git a/src/App.tsx b/src/App.tsx index 6626304..7bc067b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import "./App.css"; import { Login } from "./pages/Login.tsx"; import icon from "./paella-icon.png"; import { Outlet, Route, Routes } from "react-router"; -import { SignUp } from "./pages/SignUp.tsx"; +import { SignUpPage } from "./pages/signup/SignUpPage.tsx"; import { Register } from "./pages/Register.tsx"; import { Buy } from "./pages/Buy.tsx"; import { useState } from "react"; @@ -25,10 +25,11 @@ function App() { @@ -52,7 +53,7 @@ function App() { } > - } /> + } /> } /> } /> diff --git a/src/api/client.ts b/src/api/client.ts index 773e8fe..1975e07 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -31,6 +31,7 @@ export interface Session { size: number; userCount: number; includesRequester: boolean; + status: "SCHEDULED" | "OPEN" | "CLOSED" | "CANCELLED"; date: string; } @@ -52,3 +53,15 @@ export const leaveSession = (userId: string, sessionId: string) => AxiosResponse > ).then((response) => response.data); + +export interface Tokens { + available: number; + purchased: number; + consumed: number; + used: number; +} + +export const getAvailableTokenCount = (userId: string) => + ( + axios.get(`/tokens?userId=${userId}`) as Promise> + ).then((response) => response.data); diff --git a/src/main.tsx b/src/main.tsx index e07bd59..ffd97c5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,16 +3,12 @@ import { createRoot } from "react-dom/client"; import App from "./App.tsx"; import { BrowserRouter } from "react-router"; import { IntlProvider } from "react-intl"; -import { LocalizationProvider } from "@mui/x-date-pickers"; -import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; createRoot(document.getElementById("root")!).render( - - - + , diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx deleted file mode 100644 index b126649..0000000 --- a/src/pages/SignUp.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { - Alert, - Button, - Grid, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography, -} from "@mui/material"; -import { useContext, useMemo } from "react"; -import { FormattedDate } from "react-intl"; -import { useNavigate } from "react-router"; -import { Async } from "react-async"; -import { - getAllSessions, - joinSession, - leaveSession, - type Session, -} from "../api/client"; -import { LoggedInPageContext } from "../context/logged-in-page-context"; - -export const SignUp = () => { - const navigate = useNavigate(); - const { userId } = useContext(LoggedInPageContext)!; - const sessionsPromise = useMemo(() => getAllSessions(userId), []); - - const availableTokens: number = 0; - - return ( - - Pick a date - - - Loading - > - {(sessions) => - sessions.length === 0 ? ( - No sessions are available - ) : ( - - - - - Date - Slots - Sign-in/out - - - - {sessions.map((session) => ( - - - - - - {session.userCount}/{session.size} - - - {session.includesRequester ? ( - - ) : session.userCount >= session.size ? ( - "UNAVAILABLE" - ) : ( - - )} - - - ))} - -
-
- ) - } - - - Something went wrong - -
-
- {!availableTokens && ( - - - You don't have any tokens to sign up - - - - )} -
- ); -}; diff --git a/src/pages/signup/SessionAction.tsx b/src/pages/signup/SessionAction.tsx new file mode 100644 index 0000000..f3cf974 --- /dev/null +++ b/src/pages/signup/SessionAction.tsx @@ -0,0 +1,106 @@ +import { Button } from "@mui/material"; +import { + joinSession, + leaveSession, + type Session, + type Tokens, +} from "../../api/client"; +import { LoggedInPageContext } from "../../context/logged-in-page-context"; +import { useContext } from "react"; +import { Async } from "react-async"; +import { useNavigate } from "react-router"; + +export const SessionAction = (props: { + session: Session; + tokensPromise: Promise; +}) => { + const { session, tokensPromise } = props; + const { userId } = useContext(LoggedInPageContext)!; + const navigate = useNavigate(); + + if (session.status !== "OPEN") { + return ( + + ); + } + + if (session.includesRequester) { + return ( + + ); + } + + if (session.userCount >= session.size) { + return ( + + ); + } + + const renderLoadingButton = () => ( + + ); + + const renderBuyTokensButton = () => ( + + ); + + const renderSignInButton = () => ( + + ); + + return ( + + {renderLoadingButton()} + > + {(tokens) => + tokens.available === 0 + ? renderBuyTokensButton() + : renderSignInButton() + } + + + ); +}; diff --git a/src/pages/signup/SessionPicker.tsx b/src/pages/signup/SessionPicker.tsx new file mode 100644 index 0000000..0e3d38c --- /dev/null +++ b/src/pages/signup/SessionPicker.tsx @@ -0,0 +1,62 @@ +import { + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, +} from "@mui/material"; +import { FormattedDate } from "react-intl"; +import { type Session, type Tokens } from "../../api/client"; +import { SessionAction } from "./SessionAction"; + +export const SessionPicker = (props: { + sessions: Session[]; + tokensPromise: Promise; +}) => { + const { sessions, tokensPromise } = props; + + return ( + + + + + + Date + + + Slots + + + Sign-in/out + + + + + {sessions.map((session) => ( + + + + + + {session.userCount}/{session.size} + + + + + + ))} + +
+
+ ); +}; diff --git a/src/pages/signup/SignUpPage.tsx b/src/pages/signup/SignUpPage.tsx new file mode 100644 index 0000000..45da4b6 --- /dev/null +++ b/src/pages/signup/SignUpPage.tsx @@ -0,0 +1,59 @@ +import { Alert, Grid, Paper, Typography } from "@mui/material"; +import { useContext, useMemo } from "react"; +import { Async } from "react-async"; +import { + getAllSessions, + getAvailableTokenCount, + type Session, + type Tokens, +} from "../../api/client"; +import { LoggedInPageContext } from "../../context/logged-in-page-context"; +import { SessionPicker } from "./SessionPicker"; +import { TokensSummary } from "./TokensSummary"; + +export const SignUpPage = () => { + const { userId } = useContext(LoggedInPageContext)!; + const sessionsPromise = useMemo(() => getAllSessions(userId), []); + const tokensPromise = getAvailableTokenCount(userId); + + return ( + + Pick a date + + + Loading + > + {(sessions) => + sessions.length === 0 ? ( + No sessions are available + ) : ( + + ) + } + + + Something went wrong + + + + + > + {(tokens) => } + + + Something went wrong + + + + ); +}; diff --git a/src/pages/signup/TokensSummary.tsx b/src/pages/signup/TokensSummary.tsx new file mode 100644 index 0000000..3fc4e2d --- /dev/null +++ b/src/pages/signup/TokensSummary.tsx @@ -0,0 +1,78 @@ +import { useNavigate } from "react-router"; +import type { Tokens } from "../../api/client"; +import { + Button, + Grid, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; + +export const TokensSummary = (props: { tokens: Tokens }) => { + const { tokens } = props; + const { purchased, consumed, used, available } = tokens; + const navigate = useNavigate(); + return ( + + + Your tokens summary + + + + + + Purchased + + + Consumed + + + Allocated + + + Available + + + + + + {purchased} + {consumed} + {used} + {available} + + +
+
+ + +
+
+ ); +}; diff --git a/vite.config.ts b/vite.config.ts index a2ae31f..0fdf14b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,7 @@ const serverRoutes = [ "/buy/complete", "/buy/cancel", "/session", + "/tokens", ]; const baseServerConfigBuilder = (domain: string) => ({ proxy: Object.fromEntries(