Set up login/register navigation and API integration

main
MiguelMLorente 2025-11-23 15:48:21 +01:00
parent 21f2232723
commit 7f2624b75a
7 changed files with 100 additions and 12 deletions

View File

@ -2,21 +2,44 @@ import "./App.css";
import { Login } from "./pages/Login.tsx"; import { Login } from "./pages/Login.tsx";
import { ContentLayout, SpaceBetween } from "@cloudscape-design/components"; import { ContentLayout, SpaceBetween } from "@cloudscape-design/components";
import icon from "./paella-icon.png"; import icon from "./paella-icon.png";
import { Route, Routes } from "react-router"; import { Outlet, Route, Routes } from "react-router";
import { SignUp } from "./pages/SignUp.tsx"; import { SignUp } from "./pages/SignUp.tsx";
import { Register } from "./pages/Register.tsx"; import { Register } from "./pages/Register.tsx";
import { Buy } from "./pages/Buy.tsx"; import { Buy } from "./pages/Buy.tsx";
import { useState } from "react";
import { PreLoginPageContext } from "./context/pre-login-page-context.ts";
import { LoggedInPageContext } from "./context/logged-in-page-context.ts";
import { NotFound } from "./pages/NotFound.tsx";
function App() { function App() {
const [userId, setUserId] = useState<number | null>(null);
return ( return (
<ContentLayout defaultPadding maxContentWidth={500}> <ContentLayout defaultPadding maxContentWidth={500}>
<SpaceBetween size="m"> <SpaceBetween size="m">
<img className="app-logo" src={icon} alt="App logo" /> <img className="app-logo" src={icon} alt="App logo" />
<Routes> <Routes>
<Route path="/" element={<Login />} /> <Route
<Route path="/signup" element={<SignUp />} /> element={
<Route path="/register" element={<Register />} /> <PreLoginPageContext.Provider value={{ setUserId }}>
<Route path="/buy" element={<Buy />} /> <Outlet />
</PreLoginPageContext.Provider>
}
>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Route>
<Route
element={
<LoggedInPageContext.Provider value={{ userId: userId! }}>
<Outlet />
</LoggedInPageContext.Provider>
}
>
<Route path="/signup" element={<SignUp />} />
<Route path="/buy" element={<Buy />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes> </Routes>
</SpaceBetween> </SpaceBetween>
</ContentLayout> </ContentLayout>

View File

@ -1,13 +1,20 @@
import axios from "axios"; import axios, { type AxiosResponse } from "axios";
interface AccessResponse {
userId: number;
}
export const login = (name: string, password: string) => export const login = (name: string, password: string) =>
axios.post("/access/login", { axios.post("/access/login", {
name, name,
password, password,
}); }) as Promise<AxiosResponse<AccessResponse, unknown, {}>>;
export const registerUser = (name: string, password: string) => export const registerUser = (name: string, password: string) =>
axios.post("/access/register", { axios.post("/access/register", {
name, name,
password, password,
}); }) as Promise<AxiosResponse<AccessResponse, unknown, {}>>;
export const startBuyFlow = (userId: string) =>
axios.post("/buy/start", { userId });

View File

@ -0,0 +1,8 @@
import { createContext } from "react";
interface LoggedInPageContextProps {
userId: number;
}
export const LoggedInPageContext =
createContext<LoggedInPageContextProps | null>(null);

View File

@ -0,0 +1,8 @@
import { createContext, type Dispatch, type SetStateAction } from "react";
interface PreLoginPageContextProps {
setUserId: Dispatch<SetStateAction<number | null>>;
}
export const PreLoginPageContext =
createContext<PreLoginPageContextProps | null>(null);

View File

@ -5,14 +5,25 @@ import {
Header, Header,
SpaceBetween, SpaceBetween,
} from "@cloudscape-design/components"; } from "@cloudscape-design/components";
import { useState } from "react"; import { useContext, useState } from "react";
import { login } from "../api/client"; import { login } from "../api/client";
import { Link, useNavigate } from "react-router";
import { PreLoginPageContext } from "../context/pre-login-page-context";
export const Login = () => { export const Login = () => {
const [userName, setUserName] = useState(""); const [userName, setUserName] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [rememberMe, setRememberMe] = useState(false); const [rememberMe, setRememberMe] = useState(false);
const { setUserId } = useContext(PreLoginPageContext)!;
const navigate = useNavigate();
const handleLoginFlow = () =>
login(userName, password).then((response) => {
setUserId(response.data.userId);
navigate("/signup");
});
return ( return (
<SpaceBetween size="s" alignItems={"center"}> <SpaceBetween size="s" alignItems={"center"}>
<Header>Login</Header> <Header>Login</Header>
@ -33,7 +44,12 @@ export const Login = () => {
> >
Remember me Remember me
</Checkbox> </Checkbox>
<Button onClick={() => login(userName, password)}>Login</Button> <Button onClick={handleLoginFlow}>Login</Button>
<Button>
<Link to="/register" style={{ textDecoration: "none" }}>
Create an account
</Link>
</Button>
</SpaceBetween> </SpaceBetween>
); );
}; };

10
src/pages/NotFound.tsx Normal file
View File

@ -0,0 +1,10 @@
import { useEffect } from "react";
import { useNavigate } from "react-router";
export const NotFound = () => {
const navigate = useNavigate();
useEffect(() => {
navigate("/login");
});
return <div />;
};

View File

@ -1,4 +1,4 @@
import { useState } from "react"; import { useContext, useState } from "react";
import { import {
Alert, Alert,
Button, Button,
@ -8,11 +8,15 @@ import {
} from "@cloudscape-design/components"; } from "@cloudscape-design/components";
import * as zod from "zod"; import * as zod from "zod";
import { registerUser } from "../api/client"; import { registerUser } from "../api/client";
import { Link, useNavigate } from "react-router";
import { PreLoginPageContext } from "../context/pre-login-page-context";
export const Register = () => { export const Register = () => {
const [userName, setUserName] = useState(""); const [userName, setUserName] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [passwordConfirm, setPasswordConfirm] = useState(""); const [passwordConfirm, setPasswordConfirm] = useState("");
const { setUserId } = useContext(PreLoginPageContext)!;
const navigate = useNavigate();
const PASSWORD_MAX_LENGTH = 64; const PASSWORD_MAX_LENGTH = 64;
const PASSWORD_MIN_LENGTH = 8; const PASSWORD_MIN_LENGTH = 8;
@ -45,6 +49,13 @@ export const Register = () => {
} }
} }
const handleRegisterFlow = () =>
registerUser(userName, password).then((response) => {
console.log(response);
setUserId(response.data.userId);
navigate("/signup");
});
return ( return (
<SpaceBetween size="s" alignItems={"center"}> <SpaceBetween size="s" alignItems={"center"}>
<Header>Register</Header> <Header>Register</Header>
@ -70,10 +81,15 @@ export const Register = () => {
))} ))}
<Button <Button
disabled={passwordErrors.length !== 0} disabled={passwordErrors.length !== 0}
onClick={() => registerUser(userName, password)} onClick={handleRegisterFlow}
> >
Register Register
</Button> </Button>
<Button>
<Link to="/login" style={{ textDecoration: "none" }}>
Login with existing account
</Link>
</Button>
</SpaceBetween> </SpaceBetween>
); );
}; };