Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix up login flow #218

Merged
merged 3 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading