Replace cloudscape on behalf of MUI
parent
26b228197e
commit
44b3ef8b70
File diff suppressed because it is too large
Load Diff
|
|
@ -11,9 +11,12 @@
|
|||
"format": "prettier . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudscape-design/components": "^3.0.1132",
|
||||
"@cloudscape-design/global-styles": "^1.0.47",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/material": "^7.3.5",
|
||||
"@mui/x-date-pickers": "^8.19.0",
|
||||
"axios": "^1.13.2",
|
||||
"dayjs": "^1.11.19",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-intl": "^7.1.14",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
.app-logo {
|
||||
max-width: 40%;
|
||||
margin: 0 30%;
|
||||
max-width: 200px;
|
||||
border-radius: 10%;
|
||||
margin: auto;
|
||||
}
|
||||
|
|
|
|||
32
src/App.tsx
32
src/App.tsx
|
|
@ -1,9 +1,5 @@
|
|||
import "./App.css";
|
||||
import { Login } from "./pages/Login.tsx";
|
||||
import {
|
||||
ContentLayout,
|
||||
SpaceBetween
|
||||
} from "@cloudscape-design/components";
|
||||
import icon from "./paella-icon.png";
|
||||
import { Outlet, Route, Routes } from "react-router";
|
||||
import { SignUp } from "./pages/SignUp.tsx";
|
||||
|
|
@ -14,19 +10,33 @@ import { PreLoginPageContext } from "./context/pre-login-page-context.ts";
|
|||
import { LoggedInPageContext } from "./context/logged-in-page-context.ts";
|
||||
import { NotFound } from "./pages/NotFound.tsx";
|
||||
import { BuyReturn } from "./pages/BuyReturn.tsx";
|
||||
import { Grid, Paper } from "@mui/material";
|
||||
|
||||
function App() {
|
||||
const locallyStoredUserId = window.localStorage.getItem("paysplit-user-id");
|
||||
const locallyStoredUserIdAsNumber = locallyStoredUserId ? parseInt(locallyStoredUserId) : null
|
||||
const [userId, setUserId] = useState<number | null>(locallyStoredUserIdAsNumber);
|
||||
const locallyStoredUserIdAsNumber = locallyStoredUserId
|
||||
? parseInt(locallyStoredUserId)
|
||||
: null;
|
||||
const [userId, setUserId] = useState<number | null>(
|
||||
locallyStoredUserIdAsNumber,
|
||||
);
|
||||
const storeUserId = (id: number) => {
|
||||
window.localStorage.setItem("paysplit-user-id", id.toString());
|
||||
setUserId(id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ContentLayout defaultPadding maxContentWidth={500}>
|
||||
<SpaceBetween size="m">
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
padding: "50px 20px",
|
||||
bgcolor: "lightblue",
|
||||
borderRadius: 4,
|
||||
maxWidth: "400px",
|
||||
margin: "auto",
|
||||
}}
|
||||
>
|
||||
<Grid container direction="column" alignContent="center" spacing={2}>
|
||||
<img className="app-logo" src={icon} alt="App logo" />
|
||||
<Routes>
|
||||
<Route
|
||||
|
|
@ -52,8 +62,8 @@ function App() {
|
|||
</Route>
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</SpaceBetween>
|
||||
</ContentLayout>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@ 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(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<IntlProvider locale="es-ES" messages={{}}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<App />
|
||||
</LocalizationProvider>
|
||||
</IntlProvider>
|
||||
</BrowserRouter>
|
||||
</StrictMode>,
|
||||
|
|
|
|||
|
|
@ -1,33 +1,76 @@
|
|||
import {
|
||||
Button,
|
||||
Container,
|
||||
Header,
|
||||
Input,
|
||||
SpaceBetween,
|
||||
} from "@cloudscape-design/components";
|
||||
import { useContext, useState } from "react";
|
||||
import { startBuyFlow } from "../api/client";
|
||||
import { LoggedInPageContext } from "../context/logged-in-page-context";
|
||||
import {
|
||||
Button,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
Paper,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export const Buy = () => {
|
||||
const [quantity, setQuantity] = useState(0);
|
||||
const { userId } = useContext(LoggedInPageContext)!;
|
||||
const navigate = useNavigate();
|
||||
const handleBuyFlow = () =>
|
||||
startBuyFlow(userId, quantity).then(
|
||||
(response) => (window.location.href = response.data.url),
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SpaceBetween size="s">
|
||||
<Header>Buy tokens</Header>
|
||||
<Input
|
||||
type="number"
|
||||
<Grid container direction="column" spacing={2} alignItems="center">
|
||||
<Typography variant="h3">Buy tokens</Typography>
|
||||
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
p: 5,
|
||||
bgcolor: "aliceblue",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<RadioGroup
|
||||
value={quantity.toString()}
|
||||
onChange={(e) => setQuantity(parseInt(e.detail.value))}
|
||||
onChange={(e) => setQuantity(parseInt(e.target.value))}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="1"
|
||||
control={<Radio />}
|
||||
label="1 token - 4€"
|
||||
/>
|
||||
<Button onClick={handleBuyFlow}>Buy</Button>
|
||||
</SpaceBetween>
|
||||
</Container>
|
||||
<FormControlLabel
|
||||
value="5"
|
||||
control={<Radio />}
|
||||
label="5 tokens - 20€"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="10"
|
||||
control={<Radio />}
|
||||
label="10 tokens - 40"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Paper>
|
||||
|
||||
<Button
|
||||
onClick={handleBuyFlow}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
Buy
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate("/signup")}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Header, SpaceBetween, Spinner } from "@cloudscape-design/components";
|
||||
import { useContext } from "react";
|
||||
import { completeBuyFlow } from "../api/client";
|
||||
import { LoggedInPageContext } from "../context/logged-in-page-context";
|
||||
import { CircularProgress, Grid, Typography } from "@mui/material";
|
||||
|
||||
export const BuyReturn = () => {
|
||||
const { userId } = useContext(LoggedInPageContext)!;
|
||||
|
|
@ -10,9 +10,9 @@ export const BuyReturn = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<SpaceBetween size="m" direction="horizontal">
|
||||
<Spinner size="big" />
|
||||
<Header>We are processing your request</Header>
|
||||
</SpaceBetween>
|
||||
<Grid container direction="column" spacing={6} alignItems="center">
|
||||
<Typography variant="h5">We are processing your request</Typography>
|
||||
<CircularProgress size={50} color="inherit" />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import {
|
||||
Button,
|
||||
Input,
|
||||
Checkbox,
|
||||
Header,
|
||||
SpaceBetween,
|
||||
} from "@cloudscape-design/components";
|
||||
import { useContext, useState } from "react";
|
||||
import { login } from "../api/client";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { useNavigate } from "react-router";
|
||||
import { PreLoginPageContext } from "../context/pre-login-page-context";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
export const Login = () => {
|
||||
const [userName, setUserName] = useState("");
|
||||
|
|
@ -25,31 +26,44 @@ export const Login = () => {
|
|||
});
|
||||
|
||||
return (
|
||||
<SpaceBetween size="s" alignItems={"center"}>
|
||||
<Header>Login</Header>
|
||||
<Input
|
||||
<Grid container direction="column" spacing={2} alignItems="center">
|
||||
<Typography variant="h3">Login</Typography>
|
||||
<TextField
|
||||
value={userName}
|
||||
onChange={(e) => setUserName(e.detail.value)}
|
||||
onChange={(e) => setUserName(e.target.value)}
|
||||
placeholder="User"
|
||||
/>
|
||||
<Input
|
||||
<TextField
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.detail.value)}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={rememberMe}
|
||||
onChange={() => setRememberMe(!rememberMe)}
|
||||
/>
|
||||
}
|
||||
label="Remember me"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleLoginFlow}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ width: "100%" }}
|
||||
>
|
||||
Remember me
|
||||
</Checkbox>
|
||||
<Button onClick={handleLoginFlow}>Login</Button>
|
||||
<Button>
|
||||
<Link to="/register" style={{ textDecoration: "none" }}>
|
||||
Create an account
|
||||
</Link>
|
||||
Login
|
||||
</Button>
|
||||
</SpaceBetween>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
sx={{ width: "100%" }}
|
||||
onClick={() => navigate("/register")}
|
||||
>
|
||||
Create an account
|
||||
</Button>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { useContext, useState } from "react";
|
||||
import * as zod from "zod";
|
||||
import { registerUser } from "../api/client";
|
||||
import { useNavigate } from "react-router";
|
||||
import { PreLoginPageContext } from "../context/pre-login-page-context";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Header,
|
||||
Input,
|
||||
SpaceBetween,
|
||||
} from "@cloudscape-design/components";
|
||||
import * as zod from "zod";
|
||||
import { registerUser } from "../api/client";
|
||||
import { Link, useNavigate } from "react-router";
|
||||
import { PreLoginPageContext } from "../context/pre-login-page-context";
|
||||
Grid,
|
||||
Paper,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
export const Register = () => {
|
||||
const [userName, setUserName] = useState("");
|
||||
|
|
@ -57,39 +58,62 @@ export const Register = () => {
|
|||
});
|
||||
|
||||
return (
|
||||
<SpaceBetween size="s" alignItems={"center"}>
|
||||
<Header>Register</Header>
|
||||
<Input
|
||||
<>
|
||||
<Grid container direction="column" spacing={2} alignItems="center">
|
||||
<Typography variant="h3">Register</Typography>
|
||||
<TextField
|
||||
value={userName}
|
||||
onChange={(e) => setUserName(e.detail.value)}
|
||||
onChange={(e) => setUserName(e.target.value)}
|
||||
placeholder="User name"
|
||||
/>
|
||||
<Input
|
||||
<TextField
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.detail.value)}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
<Input
|
||||
<TextField
|
||||
type="password"
|
||||
value={passwordConfirm}
|
||||
onChange={(e) => setPasswordConfirm(e.detail.value)}
|
||||
onChange={(e) => setPasswordConfirm(e.target.value)}
|
||||
placeholder="Confirm password"
|
||||
/>
|
||||
{passwordErrors.map((error) => (
|
||||
<Alert type="error">{error}</Alert>
|
||||
))}
|
||||
<Button
|
||||
disabled={passwordErrors.length !== 0}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ width: "100%" }}
|
||||
onClick={handleRegisterFlow}
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
<Button>
|
||||
<Link to="/login" style={{ textDecoration: "none" }}>
|
||||
Login with existing account
|
||||
</Link>
|
||||
<Button
|
||||
onClick={() => navigate("/login")}
|
||||
variant="contained"
|
||||
sx={{ width: "100%" }}
|
||||
color="secondary"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</SpaceBetween>
|
||||
</Grid>
|
||||
{!!passwordErrors.length && (
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
p: 1,
|
||||
bgcolor: "white",
|
||||
borderRadius: 4,
|
||||
maxWidth: "400px",
|
||||
margin: "auto",
|
||||
}}
|
||||
>
|
||||
<Grid container direction="column" spacing={2}>
|
||||
{passwordErrors.map((error) => (
|
||||
<Alert severity="error">{error}</Alert>
|
||||
))}
|
||||
</Grid>
|
||||
</Paper>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,65 +1,105 @@
|
|||
import {
|
||||
Calendar,
|
||||
Button,
|
||||
SpaceBetween,
|
||||
Container,
|
||||
Header,
|
||||
} from "@cloudscape-design/components";
|
||||
import { Button, Grid, Paper, Typography } from "@mui/material";
|
||||
import { DateCalendar } from "@mui/x-date-pickers";
|
||||
import { useState } from "react";
|
||||
import { FormattedDate } from "react-intl";
|
||||
import { Link } from "react-router";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Dayjs } from "dayjs";
|
||||
|
||||
export const SignUp = () => {
|
||||
const [selectedDate, setSelectedDate] = useState<string | null>(null);
|
||||
const [selectedDate, setSelectedDate] = useState<Dayjs | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const tuesday = 2;
|
||||
const thursday = 4;
|
||||
const isDateEnabled = (date: Date) =>
|
||||
[tuesday, thursday].includes(date.getDay());
|
||||
const isDateDisabled = (day: Dayjs) =>
|
||||
![tuesday, thursday].includes(day.day());
|
||||
const availableSlots: number = 3;
|
||||
const availableTokens: number = 0;
|
||||
|
||||
return (
|
||||
<SpaceBetween size="s" alignItems={"center"}>
|
||||
<Container>
|
||||
<Header>Pick a date</Header>
|
||||
<Calendar
|
||||
value={selectedDate || ""}
|
||||
onChange={(e) => setSelectedDate(e.detail.value)}
|
||||
isDateEnabled={isDateEnabled}
|
||||
/>
|
||||
{selectedDate && (
|
||||
<>
|
||||
<Header>
|
||||
<FormattedDate
|
||||
<Grid container direction="column" spacing={2} alignItems="center">
|
||||
<Typography variant="h3">Pick a date</Typography>
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
p: 1,
|
||||
bgcolor: "aliceblue",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<DateCalendar
|
||||
value={selectedDate}
|
||||
onChange={setSelectedDate}
|
||||
shouldDisableDate={isDateDisabled}
|
||||
slotProps={{
|
||||
day: {
|
||||
sx: {
|
||||
fontSize: "0.9rem",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
{selectedDate && (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
p: 3,
|
||||
bgcolor: "aliceblue",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5">
|
||||
<FormattedDate
|
||||
value={selectedDate.day()}
|
||||
day="numeric"
|
||||
month="long"
|
||||
year="numeric"
|
||||
/>
|
||||
</Header>
|
||||
</Typography>
|
||||
{availableSlots === 0 ? (
|
||||
<p>There are no available slots for this date.</p>
|
||||
<Typography variant="subtitle1">
|
||||
There are no available slots for this date.
|
||||
</Typography>
|
||||
) : (
|
||||
<>
|
||||
<p>There are {availableSlots} available slots for this date.</p>
|
||||
<Button disabled={!availableTokens}>Sign up</Button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
{!availableTokens && (
|
||||
<Container>
|
||||
<p>
|
||||
You don't have any tokens to sign up to a slot, please buy here.
|
||||
</p>
|
||||
<Button>
|
||||
<Link to="/buy" style={{ textDecoration: "none" }}>
|
||||
Buy
|
||||
</Link>
|
||||
<Typography variant="subtitle1">
|
||||
There are {availableSlots} available slots for this date.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ width: "100%" }}
|
||||
disabled={!availableTokens}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
</SpaceBetween>
|
||||
</Paper>
|
||||
)}
|
||||
{!availableTokens && (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
p: 3,
|
||||
bgcolor: "aliceblue",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1">
|
||||
You don't have any tokens to sign up
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
sx={{ width: "100%" }}
|
||||
onClick={() => navigate("/buy")}
|
||||
>
|
||||
Buy
|
||||
</Button>
|
||||
</Paper>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const baseServerConfigBuilder = (domain: string) => ({
|
|||
export default defineConfig({
|
||||
server: {
|
||||
...baseServerConfigBuilder("localhost"),
|
||||
host: true
|
||||
host: true,
|
||||
},
|
||||
preview: baseServerConfigBuilder("paysplit-app"),
|
||||
plugins: [react()],
|
||||
|
|
|
|||
Loading…
Reference in New Issue