Skip to content

Commit

Permalink
fix up login flow (#218)
Browse files Browse the repository at this point in the history
* fix up login flow

* nav buttons

* headers + listings
  • Loading branch information
codekansas authored Aug 1, 2024
1 parent 0ecd9ed commit 1925e95
Show file tree
Hide file tree
Showing 23 changed files with 483 additions and 579 deletions.
6 changes: 1 addition & 5 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import { AlertQueue, AlertQueueProvider } from "hooks/alerts";
import { AuthenticationProvider } from "hooks/auth";
import { ThemeProvider } from "hooks/theme";
import About from "pages/About";
import Login from "pages/auth/Login";
import Signup from "pages/auth/Signup";
import Home from "pages/Home";
import ListingDetails from "pages/ListingDetails";
import Listings from "pages/Listings";
import Login from "pages/Login";
import Logout from "pages/Logout";
import MyListings from "pages/MyListings";
import NewListing from "pages/NewListing";
import NotFound from "pages/NotFound";
import { Container } from "react-bootstrap";
Expand All @@ -33,12 +31,10 @@ const App = () => {
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route path="/logout" element={<Logout />} />
<Route path="/listings/add" element={<NewListing />} />
<Route path="/listings/:page?" element={<Listings />} />
<Route path="/listing/:id" element={<ListingDetails />} />
<Route path="/listings/me/:page?" element={<MyListings />} />
<Route path="/404" element={<NotFound />} />
<Route path="*" element={<NotFoundRedirect />} />
</Routes>
Expand Down
95 changes: 95 additions & 0 deletions frontend/src/components/auth/AuthBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import BackButton from "components/ui/Button/BackButton";
import { Card, CardContent, CardFooter, CardHeader } from "components/ui/Card";
import Header from "components/ui/Header";
import { useAlertQueue } from "hooks/alerts";
import { useAuthentication } from "hooks/auth";
import { useEffect, useState } from "react";
import { Spinner } from "react-bootstrap";
import AuthProvider from "./AuthProvider";
import LoginForm from "./LoginForm";
import SignupForm from "./SignupForm";

export const AuthBlockInner = () => {
const auth = useAuthentication();
const { addErrorAlert } = useAlertQueue();

const [isSignup, setIsSignup] = useState(false);
const [useSpinner, setUseSpinner] = useState(false);

const handleGithubSubmit = async (
event: React.MouseEvent<HTMLButtonElement>,
) => {
event.preventDefault();

const { data, error } = await auth.client.GET("/users/github/login");
if (error) {
addErrorAlert(error);
} else {
window.open(data, "_self");
}
};

useEffect(() => {
(async () => {
// Get the code from the query string to carry out OAuth login.
const search = window.location.search;
const params = new URLSearchParams(search);
const code = params.get("code");

if (code) {
setUseSpinner(true);
const { data, error } = await auth.client.POST("/users/github/code", {
body: { code },
});

if (error) {
addErrorAlert(error);
setUseSpinner(false);
} else {
auth.login(data.api_key);
setUseSpinner(false);
}
}
})();
}, []);

if (useSpinner) {
return (
<CardContent className="flex justify-center">
<Spinner animation="border" />
</CardContent>
);
}

return (
<>
<CardContent>{isSignup ? <SignupForm /> : <LoginForm />}</CardContent>
<CardFooter>
<AuthProvider handleGithubSubmit={handleGithubSubmit} />
</CardFooter>
<CardFooter>
<BackButton
onClick={() => setIsSignup((s) => !s)}
label={
isSignup
? "Already have an account? Login here."
: "Don't have an account? Create a new account."
}
/>
</CardFooter>
</>
);
};

const AuthBlock = () => {
return (
<Card className="w-[400px] shadow-md h-full mb-40">
<CardHeader>
<Header />
</CardHeader>
<AuthBlockInner />
</Card>
);
};

export default AuthBlock;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FaGithub } from "react-icons/fa";
import { FcGoogle } from "react-icons/fc";
import { Button } from "./Button/Button";
import { Button } from "../ui/Button/Button";

interface AuthProvider {
handleGoogleSubmit?: () => void;
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/auth/AuthenticationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Modal } from "react-bootstrap";
import { useNavigate } from "react-router-dom";
import { AuthBlockInner } from "./AuthBlock";

const LogInModal = () => {
const navigate = useNavigate();
Expand All @@ -10,8 +11,11 @@ const LogInModal = () => {

return (
<Modal show={true} onHide={navigateToPreviousPage} centered>
<Modal.Header closeButton>
<Modal.Title>Sign in to see this page</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Sign in to see this page</p>
<AuthBlockInner />
</Modal.Body>
<Modal.Footer>
<small>
Expand Down
62 changes: 62 additions & 0 deletions frontend/src/components/auth/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "components/ui/Button/Button";
import ErrorMessage from "components/ui/ErrorMessage";
import { Input } from "components/ui/Input/Input";
import { useState } from "react";
import { Eye } from "react-bootstrap-icons";
import { SubmitHandler, useForm } from "react-hook-form";
import { LoginSchema, LoginType } from "types";

const LoginForm = () => {
const [showPassword, setShowPassword] = useState<boolean>(false);

const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginType>({
resolver: zodResolver(LoginSchema),
});

const onSubmit: SubmitHandler<LoginType> = async (data: LoginType) => {
// TODO: Add an api endpoint to send the credentials details to backend and email verification.
console.log(data);
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
className="grid grid-cols-1 space-y-6"
>
{/* Email */}
<div>
<Input placeholder="Email" type="text" {...register("email")} />
{errors?.email && <ErrorMessage>{errors?.email?.message}</ErrorMessage>}
</div>

{/* Password */}
<div className="relative">
<Input
placeholder="Password"
type={showPassword ? "text" : "password"}
{...register("password")}
/>
{errors?.password && (
<ErrorMessage>{errors?.password?.message}</ErrorMessage>
)}
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
<Eye
onClick={() => setShowPassword((p) => !p)}
className="cursor-pointer"
/>
</div>
</div>

<Button type="submit" className="w-full">
Login
</Button>
</form>
);
};

export default LoginForm;
81 changes: 81 additions & 0 deletions frontend/src/components/auth/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "components/ui/Button/Button";
import ErrorMessage from "components/ui/ErrorMessage";
import { Input } from "components/ui/Input/Input";
import { useState } from "react";
import { Eye } from "react-bootstrap-icons";
import { SubmitHandler, useForm } from "react-hook-form";
import { SignUpSchema, SignupType } from "types";

const SignupForm = () => {
const [showPassword, setShowPassword] = useState<boolean>(false);
const [showConfirmPassword, setShowConfirmPassword] =
useState<boolean>(false);

const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupType>({
resolver: zodResolver(SignUpSchema),
});

const onSubmit: SubmitHandler<SignupType> = async (data: SignupType) => {
// TODO: Add an api endpoint to send the credentials details to backend and email verification.
console.log(data);
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
className="grid grid-cols-1 space-y-6"
>
{/* Email */}
<div>
<Input placeholder="Email" type="text" {...register("email")} />
{errors?.email && <ErrorMessage>{errors?.email?.message}</ErrorMessage>}
</div>

{/* Password */}
<div className="relative">
<Input
placeholder="Password"
type={showPassword ? "text" : "password"}
{...register("password")}
/>
{errors?.password && (
<ErrorMessage>{errors?.password?.message}</ErrorMessage>
)}
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
<Eye
onClick={() => setShowPassword((p) => !p)}
className="cursor-pointer"
/>
</div>
</div>

{/* Confirm Password */}
<div className="relative">
<Input
placeholder="Confirm Password"
type={showConfirmPassword ? "text" : "password"}
{...register("confirmPassword")}
/>
{errors?.confirmPassword && (
<ErrorMessage>{errors?.confirmPassword?.message}</ErrorMessage>
)}
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
<Eye
onClick={() => setShowConfirmPassword((p) => !p)}
className="cursor-pointer"
/>
</div>
</div>
<Button type="submit" className="w-full">
Signup
</Button>
</form>
);
};

export default SignupForm;
63 changes: 63 additions & 0 deletions frontend/src/components/listings/ListingGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { paths } from "gen/api";
import { useAlertQueue } from "hooks/alerts";
import { useAuthentication } from "hooks/auth";
import { useEffect, useState } from "react";
import { Col, Row, Spinner } from "react-bootstrap";
import ListingGridCard from "./ListingGridCard";

type ListingInfo =
paths["/listings/batch"]["get"]["responses"][200]["content"]["application/json"]["listings"];

interface Props {
listingIds: string[] | null;
}

const ListingGrid = (props: Props) => {
const { listingIds } = props;
const auth = useAuthentication();
const { addErrorAlert } = useAlertQueue();

const [listingInfo, setListingInfoResponse] = useState<ListingInfo | null>(
null,
);

useEffect(() => {
if (listingIds !== null && listingIds.length > 0) {
(async () => {
console.log("LISTING IDS:", listingIds);
const { data, error } = await auth.client.GET("/listings/batch", {
params: {
query: {
ids: listingIds,
},
},
});

if (error) {
addErrorAlert(error);
return;
}

setListingInfoResponse(data.listings);
})();
}
}, [listingIds]);

return (
<Row className="mt-5">
{listingIds === null ? (
<Col className="text-center">
<Spinner animation="border" />
</Col>
) : (
listingIds.map((listingId) => (
<Col key={listingId} lg={2} md={3} sm={6} xs={12}>
<ListingGridCard listingId={listingId} listingInfo={listingInfo} />
</Col>
))
)}
</Row>
);
};

export default ListingGrid;
Loading

0 comments on commit 1925e95

Please sign in to comment.