Skip to content

Commit

Permalink
Create signin page
Browse files Browse the repository at this point in the history
Consume sign in auth api
  • Loading branch information
Fingertips18 committed Sep 13, 2024
1 parent c45cf6d commit 86bb5ca
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 71 deletions.
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Route, Routes } from "react-router-dom";

import { AppRoutes } from "@/constants/routes";
import SignUpPage from "@/pages/sign-up/page";
import SignInPage from "@/pages/sign-in/page";
import RootPage from "@/pages/root/page";

function App() {
Expand All @@ -10,6 +11,7 @@ function App() {
<Routes>
<Route path={AppRoutes.Root} element={<RootPage />} />
<Route path={AppRoutes.SignUp} element={<SignUpPage />} />
<Route path={AppRoutes.SignIn} element={<SignInPage />} />
</Routes>
</main>
);
Expand Down
11 changes: 11 additions & 0 deletions client/src/components/or.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const Or = () => {
return (
<div className="flex-between gap-x-2 w-[400px]">
<span className="h-px w-full flex-1 bg-foreground/40" />
<p className="text-lg font-semibold text-foreground/40">Or</p>
<span className="h-px flex-1 bg-foreground/40" />
</div>
);
};

export { Or };
23 changes: 23 additions & 0 deletions client/src/components/switch-auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Link } from "react-router-dom";

interface SwitchAuthProps {
label: string;
tag: string;
href: string;
}

const SwitchAuth = ({ label, tag, href }: SwitchAuthProps) => {
return (
<div className="p-4 w-[400px] rounded-md border border-secondary/50 bg-secondary/15 drop-shadow-2xl flex-center gap-x-2">
<p className="font-medium">{label}</p>
<Link
to={href}
className="font-bold underline-offset-4 hover:underline text-secondary transition-all hover:drop-shadow-secondary-glow"
>
{tag}
</Link>
</div>
);
};

export { SwitchAuth };
21 changes: 21 additions & 0 deletions client/src/constants/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,24 @@ export const SIGNUPINPUTS = [
validation: ValidateConfirmPassword,
},
];

export const SIGNININPUTS = [
{
name: "email",
label: "Email Address",
placeholder: "e.g. [email protected]",
type: "email",
autoComplete: "email",
suffixIcon: Mail,
validation: ValidateEmail,
},
{
name: "password",
label: "Password",
placeholder: "e.g. m#P52s@ap$V",
type: "password",
autoComplete: "new-password",
suffixIcon: Lock,
validation: ValidatePassword,
},
];
1 change: 1 addition & 0 deletions client/src/constants/keys.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const SIGNUPKEY = "sign-up";
export const SIGNINKEY = "sign-in";
4 changes: 4 additions & 0 deletions client/src/lib/DTO/sign-in.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type SignInDTO = {
email: string;
password: string;
};
8 changes: 8 additions & 0 deletions client/src/lib/services/auth-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SignUpDTO } from "@/lib/DTO/sign-up.dto";
import { SignInDTO } from "@/lib/DTO/sign-in.dto";
import { AppRoutes } from "@/constants/routes";

const baseURL =
Expand All @@ -14,4 +15,11 @@ export const AuthService = {
body: JSON.stringify(signUp),
});
},
signIn: async (signIn: SignInDTO) => {
return await fetch(`${baseURL}${AppRoutes.SignIn}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(signIn),
});
},
};
2 changes: 1 addition & 1 deletion client/src/pages/root/_components/header/toggle-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ToggleMode = () => {

return (
<button
className="w-10 h-10 rounded-full flex-center transition-colors bg-accent/40 hover:bg-accent"
className="w-10 h-10 rounded-full flex-center transition-colors bg-accent/40 hover:bg-accent hover:drop-shadow-accent-glow"
onClick={onClick}
>
{theme === Theme.Light ? <Sun size={22} /> : <Moon size={22} />}
Expand Down
66 changes: 66 additions & 0 deletions client/src/pages/sign-in/_components/sign-in-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useMutation } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { FormEvent } from "react";
import { toast } from "sonner";

import { AuthService } from "@/lib/services/auth-service";
import { SIGNININPUTS } from "@/constants/collections";
import { SignInDTO } from "@/lib/DTO/sign-in.dto";
import { AppRoutes } from "@/constants/routes";
import { SIGNINKEY } from "@/constants/keys";
import { Button } from "@/components/button";
import { Input } from "@/components/input";

const SignInForm = () => {
const navigate = useNavigate();

const { mutate, isPending } = useMutation({
mutationKey: [SIGNINKEY],
mutationFn: AuthService.signIn,
onSuccess: () => {
toast.success("Welcome! You have signed in");
navigate(AppRoutes.Root);
},
onError: ({ message }) => toast.error(message || "Unable to login"),
});

const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

const formData = new FormData(e.currentTarget);

const signInData = Object.fromEntries(formData.entries()) as SignInDTO;

mutate(signInData);
};

return (
<form
onSubmit={onSubmit}
className="p-4 lg:p-6 rounded-md border border-primary/50 bg-primary/15 drop-shadow-2xl space-y-6"
>
<h2 className="text-2xl font-extrabold text-center uppercase">
Access Your Account
</h2>

{SIGNININPUTS.map((s) => (
<Input
key={s.label}
required
disabled={isPending}
{...s}
validation={s.validation}
/>
))}

<Button
label="Sign In"
disabled={isPending}
loading={isPending}
type="submit"
/>
</form>
);
};

export { SignInForm };
23 changes: 23 additions & 0 deletions client/src/pages/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SwitchAuth } from "@/components/switch-auth";
import { AppRoutes } from "@/constants/routes";
import { Or } from "@/components/or";

import { SignInForm } from "./_components/sign-in-form";

const SignInPage = () => {
return (
<section className="h-full flex-center flex-center flex-col gap-y-6">
<SignInForm />

<Or />

<SwitchAuth
label="Don't have an account yet?"
tag="Sign Up"
href={AppRoutes.SignUp}
/>
</section>
);
};

export default SignInPage;
79 changes: 79 additions & 0 deletions client/src/pages/sign-up/_components/sign-up-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useMutation } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { FormEvent, useState } from "react";
import { toast } from "sonner";

import { AuthService } from "@/lib/services/auth-service";
import { SIGNUPINPUTS } from "@/constants/collections";
import { SignUpDTO } from "@/lib/DTO/sign-up.dto";
import { AppRoutes } from "@/constants/routes";
import { SIGNUPKEY } from "@/constants/keys";
import { Button } from "@/components/button";
import { Input } from "@/components/input";

const SignUpForm = () => {
const [confirmPassword, setConfirmPassword] = useState("");
const navigate = useNavigate();

const { mutate, isPending } = useMutation({
mutationKey: [SIGNUPKEY],
mutationFn: AuthService.signUp,
onSuccess: () => {
toast.success("Registered successfully");
navigate(AppRoutes.SignIn);
},
onError: ({ message }) => toast.error(message || "Unable to register"),
});

const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

const formData = new FormData(e.currentTarget);

const signUpData = Object.fromEntries(formData.entries()) as SignUpDTO;

mutate(signUpData);
};

return (
<form
onSubmit={onSubmit}
className="p-4 lg:p-6 rounded-md border border-primary/50 bg-primary/15 drop-shadow-2xl space-y-6"
>
<h2 className="text-2xl font-extrabold text-center uppercase">
Create Account
</h2>

{SIGNUPINPUTS.map((s) => (
<Input
key={s.label}
required
disabled={isPending}
{...s}
validation={(value) => {
if (s.name === "password") {
setConfirmPassword(value);
}
if (s.name === undefined) {
return s.validation({
pass1: value,
pass2: confirmPassword,
});
}

return s.validation(value);
}}
/>
))}

<Button
label="Sign Up"
disabled={isPending}
loading={isPending}
type="submit"
/>
</form>
);
};

export { SignUpForm };
82 changes: 12 additions & 70 deletions client/src/pages/sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,21 @@
import { useMutation } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { FormEvent, useState } from "react";
import { toast } from "sonner";

import { AuthService } from "@/lib/services/auth-service";
import { SIGNUPINPUTS } from "@/constants/collections";
import { SignUpDTO } from "@/lib/DTO/sign-up.dto";
import { SwitchAuth } from "@/components/switch-auth";
import { AppRoutes } from "@/constants/routes";
import { Button } from "@/components/button";
import { SIGNUPKEY } from "@/constants/keys";
import { Input } from "@/components/input";

const SignUpPage = () => {
const [confirmPassword, setConfirmPassword] = useState("");
const navigate = useNavigate();

const { mutate, isPending } = useMutation({
mutationKey: [SIGNUPKEY],
mutationFn: AuthService.signUp,
onSuccess: () => {
toast.success("Registered successfully");
navigate(AppRoutes.Root);
},
onError: ({ message }) => toast.error(message || "Unable to register"),
});

const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
import { Or } from "@/components/or";

const formData = new FormData(e.currentTarget);

const signUpData = Object.fromEntries(formData.entries()) as SignUpDTO;

mutate(signUpData);
};
import { SignUpForm } from "./_components/sign-up-form";

const SignUpPage = () => {
return (
<section className="h-full flex-center">
<form
onSubmit={onSubmit}
className="p-4 lg:p-6 rounded-md border border-primary/50 bg-primary/15 drop-shadow-2xl space-y-6"
>
<h2 className="text-2xl font-extrabold text-center uppercase">
Create Account
</h2>

{SIGNUPINPUTS.map((s) => (
<Input
key={s.label}
required
disabled={isPending}
{...s}
validation={(value) => {
if (s.name === "password") {
setConfirmPassword(value);
}
if (s.name === undefined) {
return s.validation({
pass1: value,
pass2: confirmPassword,
});
}
<section className="h-full flex-center flex-center flex-col gap-y-6">
<SignUpForm />

return s.validation(value);
}}
/>
))}
<Or />

<Button
label="Sign Up"
disabled={isPending}
loading={isPending}
type="submit"
/>
</form>
<SwitchAuth
label="Already have an account?"
tag="Sign In"
href={AppRoutes.SignIn}
/>
</section>
);
};
Expand Down

0 comments on commit 86bb5ca

Please sign in to comment.