Implement signup sessions fetch

main
MiguelMLorente 2025-12-01 21:13:41 +01:00
parent ad28e29e85
commit 703408fc12
7 changed files with 98 additions and 168 deletions

148
package-lock.json generated
View File

@ -11,10 +11,9 @@
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.5", "@mui/material": "^7.3.5",
"@mui/x-date-pickers": "^8.19.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"dayjs": "^1.11.19",
"react": "^19.2.0", "react": "^19.2.0",
"react-async": "^10.0.1",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-intl": "^7.1.14", "react-intl": "^7.1.14",
"react-router": "^7.9.6" "react-router": "^7.9.6"
@ -1075,103 +1074,6 @@
"integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@mui/x-date-pickers": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.19.0.tgz",
"integrity": "sha512-TQ4FsGUsiGJVs+Ie4q7nHXUmFqZADXL/1hVtZpOKsdr3WQXwpX7C5YmeakZGFR2NZnuv4snFj+WTee3kgyFbyQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
"@mui/utils": "^7.3.5",
"@mui/x-internals": "8.19.0",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
"dayjs": "^1.10.7",
"luxon": "^3.0.2",
"moment": "^2.29.4",
"moment-hijri": "^2.1.2 || ^3.0.0",
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"date-fns": {
"optional": true
},
"date-fns-jalali": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
},
"moment-hijri": {
"optional": true
},
"moment-jalaali": {
"optional": true
}
}
},
"node_modules/@mui/x-date-pickers/node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@mui/x-internals": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.19.0.tgz",
"integrity": "sha512-mMmiyJAN5fW27srXJjhXhXJa+w2xGO45rwcjws6OQc9rdXGdJqRXhBwJd+OT7J1xwSdFIIUhjZRTz1KAfCSGBg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
"@mui/utils": "^7.3.5",
"reselect": "^5.1.1",
"use-sync-external-store": "^1.6.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@napi-rs/wasm-runtime": { "node_modules/@napi-rs/wasm-runtime": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
@ -2253,30 +2155,6 @@
"integrity": "sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g==", "integrity": "sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -3852,6 +3730,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-async": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/react-async/-/react-async-10.0.1.tgz",
"integrity": "sha512-ORUz5ca0B57QgBIzEZM5SuhJ6xFjkvEEs0gylLNlWf06vuVcLZsjIw3wx58jJkZG38p+0nUAxRgFW2b7mnVZzA==",
"license": "ISC",
"peerDependencies": {
"react": ">=16.3.1"
}
},
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "19.2.0", "version": "19.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
@ -3943,12 +3830,6 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.11", "version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@ -4333,15 +4214,6 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/vite": { "node_modules/vite": {
"name": "rolldown-vite", "name": "rolldown-vite",
"version": "7.2.2", "version": "7.2.2",

View File

@ -14,10 +14,9 @@
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.5", "@mui/material": "^7.3.5",
"@mui/x-date-pickers": "^8.19.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"dayjs": "^1.11.19",
"react": "^19.2.0", "react": "^19.2.0",
"react-async": "^10.0.1",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-intl": "^7.1.14", "react-intl": "^7.1.14",
"react-router": "^7.9.6" "react-router": "^7.9.6"

View File

@ -14,7 +14,7 @@ import { Grid, Paper } from "@mui/material";
function App() { function App() {
const [userId, setUserId] = useState<string | null>( const [userId, setUserId] = useState<string | null>(
window.localStorage.getItem("paysplit-user-id") window.localStorage.getItem("paysplit-user-id"),
); );
const storeUserId = (id: string) => { const storeUserId = (id: string) => {
window.localStorage.setItem("paysplit-user-id", id.toString()); window.localStorage.setItem("paysplit-user-id", id.toString());

View File

@ -25,3 +25,16 @@ export const completeBuyFlow = (userId: string) =>
axios.post("/buy/complete", { userId }) as Promise< axios.post("/buy/complete", { userId }) as Promise<
AxiosResponse<{ url: string }, unknown, {}> AxiosResponse<{ url: string }, unknown, {}>
>; >;
export interface Session {
id: string;
size: number;
userCount: number;
includesRequester: boolean;
date: string;
}
export const getAllSessions = (userId: string) =>
(
axios.get(`/session?userId=${userId}`) as Promise<AxiosResponse<Session[]>>
).then((response) => response.data);

View File

@ -52,7 +52,6 @@ export const Register = () => {
const handleRegisterFlow = () => const handleRegisterFlow = () =>
registerUser(userName, password).then((response) => { registerUser(userName, password).then((response) => {
console.log(response);
setUserId(response.data.userId); setUserId(response.data.userId);
navigate("/signup"); navigate("/signup");
}); });

View File

@ -1,19 +1,32 @@
import { Button, Grid, Paper, Typography } from "@mui/material"; import {
import { DateCalendar } from "@mui/x-date-pickers"; Alert,
import { useState } from "react"; Button,
FormControlLabel,
Grid,
Paper,
Radio,
RadioGroup,
Typography,
} from "@mui/material";
import { useContext, useMemo, useState } from "react";
import { FormattedDate } from "react-intl"; import { FormattedDate } from "react-intl";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { Dayjs } from "dayjs"; import { Async } from "react-async";
import { getAllSessions, type Session } from "../api/client";
import { LoggedInPageContext } from "../context/logged-in-page-context";
export const SignUp = () => { export const SignUp = () => {
const [selectedDate, setSelectedDate] = useState<Dayjs | null>(null);
const navigate = useNavigate(); const navigate = useNavigate();
const { userId } = useContext(LoggedInPageContext)!;
const sessionsPromise = useMemo(() => getAllSessions(userId), [])
const [selectedSession, setSelectedSession] = useState<Session | null>(null);
const tuesday = 2; const setSelectedSessionFromId = (sessions: Session[], id: string) => setSelectedSession(
const thursday = 4; sessions.find(s => s.id === id) || null);
const isDateDisabled = (day: Dayjs) =>
![tuesday, thursday].includes(day.day()); const selectedSessionId = selectedSession && selectedSession.id;
const availableSlots: number = 3; const selectedDate = selectedSession && new Date(selectedSession.date);
const availableSlots = selectedSession && selectedSession?.size - selectedSession.userCount;
const availableTokens: number = 0; const availableTokens: number = 0;
return ( return (
@ -22,23 +35,51 @@ export const SignUp = () => {
<Paper <Paper
variant="outlined" variant="outlined"
sx={{ sx={{
p: 1, p: 3,
bgcolor: "aliceblue", bgcolor: "aliceblue",
borderRadius: 4, borderRadius: 4,
}} }}
> >
<DateCalendar <Async promise={sessionsPromise}>
value={selectedDate} <Async.Loading>Loading</Async.Loading>
onChange={setSelectedDate} <Async.Fulfilled<Session[]>>
shouldDisableDate={isDateDisabled} {(sessions) =>
slotProps={{ sessions.length === 0 ? (
day: { <Alert severity="warning">No sessions are available</Alert>
sx: { ) : (
fontSize: "0.9rem", <>
}, <RadioGroup
}, value={selectedSessionId}
}} onChange={(e) => setSelectedSessionFromId(sessions, e.target.value)}
/> >
{sessions.map((session) => {
const date = new Date(session.date);
return (
<FormControlLabel
key={session.id}
value={session.id}
control={<Radio />}
label={
<FormattedDate
value={date}
day="numeric"
month="long"
year="numeric"
/>
}
/>
);
})}
</RadioGroup>
<Button></Button>
</>
)
}
</Async.Fulfilled>
<Async.Rejected>
<Alert severity="error">Something went wrong</Alert>
</Async.Rejected>
</Async>
</Paper> </Paper>
{selectedDate && ( {selectedDate && (
<Paper <Paper
@ -51,13 +92,13 @@ export const SignUp = () => {
> >
<Typography variant="h5"> <Typography variant="h5">
<FormattedDate <FormattedDate
value={selectedDate.day()} value={selectedDate}
day="numeric" day="numeric"
month="long" month="long"
year="numeric" year="numeric"
/> />
</Typography> </Typography>
{availableSlots === 0 ? ( {!availableSlots ? (
<Typography variant="subtitle1"> <Typography variant="subtitle1">
There are no available slots for this date. There are no available slots for this date.
</Typography> </Typography>

View File

@ -1,7 +1,13 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
const serverRoutes = ["/access", "/buy/start", "/buy/complete", "/buy/cancel"]; const serverRoutes = [
"/access",
"/buy/start",
"/buy/complete",
"/buy/cancel",
"/session",
];
const baseServerConfigBuilder = (domain: string) => ({ const baseServerConfigBuilder = (domain: string) => ({
proxy: Object.fromEntries( proxy: Object.fromEntries(
serverRoutes.map((key) => [ serverRoutes.map((key) => [