Skip to content

Commit

Permalink
initial login setup completed
Browse files Browse the repository at this point in the history
  • Loading branch information
sinanptm committed Aug 27, 2024
1 parent 3d440b4 commit c657750
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 75 deletions.
4 changes: 2 additions & 2 deletions client/app/(patient)/signin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const SignIn = () => {
<p className="justify-items-end text-dark-600 xl:text-left">
© 2024 AVM Ayurveda&apos;s
</p>
<Link href={"/signin/otp-verification"} className="text-green-500">
OTP
<Link href={"/signup"} className="text-green-500 text-xs">
Staff-Login
</Link>
</div>
</div>
Expand Down
51 changes: 37 additions & 14 deletions client/components/forms/SigninForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,65 @@ import SubmitButton from "@/components/utils/SubmitButton";
import { signinFormValidation } from "@/lib/validators/userValidation";
import Link from "next/link";
import { FormFieldType } from "@/types/fromTypes";
import { useSignInMutation } from "@/lib/features/api/authApi";
import { useToast } from "../ui/use-toast";
import { useRouter } from "next/navigation";

const LoginForm = () => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [signIn, { isLoading: isPosting, data, error: signinError }] = useSignInMutation();
const [error, setError] = useState("");
const { toast } = useToast();
const router = useRouter();

const form = useForm<z.infer<typeof signinFormValidation>>({
resolver: zodResolver(signinFormValidation),
defaultValues: {
phone: "",
email: "",
password: "",
},
});

const onSubmit = async (values: z.infer<typeof signinFormValidation>) => {
setIsLoading(true);
console.log("clicked");
setIsLoading(false);
try {
const result = await signIn(values).unwrap();
router.push("/signin/otp-verification");
toast({
title: "OTP Verification",
description: "Please check your email for the OTP.",
variant: "default",
});
} catch (error: any) {
setError(error.data?.message || "An error occurred during sign-in.");
console.log(error);

toast({
title: "Sign-In Failed",
description: error.data?.message || "Please try again.",
variant: "destructive",
});
}
};

return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 flex-1"
>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 flex-1">
<section className="mb-12 space-y-4">
<h1 className="header">Welcome Back 👋</h1>
<p className="text-dark-700">
Schedule your first appointment{" "}
<Link href={"/signup"} className="text-blue-400">
<Link href="/signup" className="text-blue-400">
Sign Up
</Link>
</p>
</section>

<CustomFormField
control={form.control}
fieldType={FormFieldType.PHONE_INPUT}
name="phone"
label="Phone Number"
fieldType={FormFieldType.INPUT}
name="email"
label="Email Address"
placeholder="[email protected]"
iconSrc="/assets/icons/email.svg"
/>

<CustomFormField
Expand All @@ -58,7 +79,9 @@ const LoginForm = () => {
placeholder="Enter your password"
/>

<SubmitButton isLoading={isLoading}>Sign In</SubmitButton>
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}

<SubmitButton isLoading={isPosting}>Sign In</SubmitButton>
</form>
</Form>
);
Expand Down
2 changes: 1 addition & 1 deletion client/components/forms/SignupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const RegistrationForm = () => {
<h1 className="header">Hi There👋</h1>
<p className="text-dark-700">
Already have an account?{" "}
<Link href="/patient/signin" className="text-blue-400">
<Link href="/signin" className="text-blue-400">
Sign In
</Link>
</p>
Expand Down
7 changes: 3 additions & 4 deletions client/lib/features/api/authApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { SignInCredentials, SignInResponse, SignUpResponse } from "@/types/auth";
import { IPatient } from "@/types";

export const authApi = createApi({
Expand All @@ -8,14 +7,14 @@ export const authApi = createApi({
baseUrl: `${process.env.NEXT_PUBLIC_SERVER_URL}/api/patient`,
}),
endpoints: (builder) => ({
signIn: builder.mutation<SignInResponse, SignInCredentials>({
signIn: builder.mutation<{ patient: IPatient; token: string }, IPatient>({
query: (credentials) => ({
url: "/signin",
url: "/login",
method: "POST",
body: credentials,
}),
}),
signUp: builder.mutation<SignUpResponse, IPatient>({
signUp: builder.mutation<{ message: string }, IPatient>({
query: (credentials) => ({
url: "/",
method: "POST",
Expand Down
38 changes: 9 additions & 29 deletions client/lib/validators/userValidation.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { z } from "zod";

export const signinFormValidation = z.object({
phone: z
.string()
.trim()
.refine(
phone => /^\+?[1-9]\d{1,14}$/.test(phone),
"Invalid phone number",
),
password: z
.string()
.trim()
.min(4, "Password must be at least 4 characters long"),
email: z.string().trim().email("Invalid email address"),
password: z.string().trim().min(4, "Password must be at least 4 characters long"),
});

export const signupFormValidation = z
Expand All @@ -35,14 +26,8 @@ export const signupFormValidation = z
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
.regex(/[0-9]/, "Password must contain at least one number")
.regex(
/[@$!%*?&#]/,
"Password must contain at least one special character",
),
confirmPassword: z
.string()
.trim()
.min(6, "Password must be at least 6 characters long"),
.regex(/[@$!%*?&#]/, "Password must contain at least one special character"),
confirmPassword: z.string().trim().min(6, "Password must be at least 6 characters long"),
})
.superRefine(({ confirmPassword, password }, ctx) => {
if (confirmPassword !== password) {
Expand All @@ -55,28 +40,26 @@ export const signupFormValidation = z
});

export const registerFormValidation = z.object({
birthDate: z.coerce
.date()
.max(new Date(Date.now()), "Please select a birth date before todays."),
birthDate: z.coerce.date().max(new Date(Date.now()), "Please select a birth date before todays."),
gender: z.enum(["Male", "Female", "Other"]),
bloodType: z.enum(["A+", "A-", "B+", "B-", "O+", "O-", "AB+", "AB-"]),
disease: z.string().trim().min(1, "Primary Disease is required"),
privacyConsent: z
.boolean()
.default(false)
.refine(val => val === true, {
.refine((val) => val === true, {
message: "You must agree to the Privacy Consent ",
}),
concent: z
.boolean()
.default(false)
.refine(val => val === true, {
.refine((val) => val === true, {
message: "You must Consent to Treatment",
}),
disclosureConsent: z
.boolean()
.default(false)
.refine(val => val === true, {
.refine((val) => val === true, {
message: "You must consent to disclosure in order to proceed",
}),
address: z.string().trim().min(4, "Address is required"),
Expand All @@ -85,10 +68,7 @@ export const registerFormValidation = z.object({

export const appointmentFormValidation = z.object({
appointmentType: z.enum(["outpatient", "inpatient"]),
reason: z
.string()
.trim()
.min(5, "Reason must be at least 5 characters long"),
reason: z.string().trim().min(5, "Reason must be at least 5 characters long"),
note: z.string().trim().min(5, "Notes must be at least 5 characters long"),
schedule: z.coerce.date(),
payment: z.enum(["online", "Op"]),
Expand Down
16 changes: 0 additions & 16 deletions client/types/auth.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@

export interface SignInCredentials {
email: string;
password: string;
}

export interface SignInResponse {
user: {
_id: string;
email: string;
name: string;
phone: string;
};
token: string;
}

export interface AuthState {
token: string | null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class PatientRepository implements IPatientRepository {
const patientModel = new this.model(patient);
return await patientModel.save();
} catch (error: any) {
if (error.code === 11000) {
if (error.code === 11000) {
throw new Error("Patient With Email Already Exists!.");
}
throw error;
Expand All @@ -23,12 +23,15 @@ export default class PatientRepository implements IPatientRepository {
return await this.model.findByIdAndUpdate(id, { $set: { isBlocked: !status } });
}
async findByEmail(email: string): Promise<IPatient | null> {
return await this.model.findOne({ email });
return await this.model.findOne({ email }).select("-password");
}
async findById(id: string): Promise<IPatient | null> {
if (!isValidObjectId(id)) {
throw new Error(`Invalid Object id : ${id}`);
}
return await this.model.findById(id);
return await this.model.findById(id).select("-password");
}
async findByEmailWithPassword(email: string): Promise<IPatient | null> {
return await this.model.findOne({ email });
}
}
1 change: 1 addition & 0 deletions server/src/interface/repositories/IPatientRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export default interface IPatientRepository {
changeStatus(id: string, status: boolean): Promise<IPatient | null>;
findByEmail(email: string): Promise<IPatient | null>;
findById(id: string): Promise<IPatient | null>;
findByEmailWithPassword(email:string):Promise<IPatient|null>
}
24 changes: 19 additions & 5 deletions server/src/presentation/controllers/PatientController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default class PatientController {

async registerPatient(req: Request, res: Response, next: NextFunction) {
try {
const patient = req.body as IPatient;
const patient: IPatient = req.body;

// email validation
if (!patient.email?.trim()) return res.status(400).json({ message: "Email is Required" });
Expand All @@ -31,17 +31,31 @@ export default class PatientController {
if (!patient.phone?.toString().trim()) return res.status(400).json({ message: "Phone number is required" });

res.status(200).json({ message: await this.registerPatientUseCase.execute(patient) });

} catch (error) {
next(error);
}
}

async loginPatient(req: Request, res: Response, next: NextFunction) {
try {
this.loginPatientUseCase.execute(req.body.patient);
} catch (error) {
next(error);
let patient: IPatient = req.body;

// Validate the input data
if (!patient.email?.trim()) return res.status(400).json({ message: "Email is required" });
if (!isValidEmail(patient.email)) return res.status(422).json({ message: "Invalid email format" });

if (!patient.password?.trim()) return res.status(400).json({ message: "Password is required" });

const loggedInPatient = await this.loginPatientUseCase.execute(patient);
res.status(200).json({ message: "Login successful", patient: loggedInPatient });
} catch (error: any) {
if (error.message === "User Not Found") {
return res.status(404).json({ message: "User not found" });
} else if (error.message === "Invalid Credentials") {
return res.status(401).json({ message: "Invalid credentials" });
} else {
next(error);
}
}
}
}
12 changes: 11 additions & 1 deletion server/src/use_case/patient/LoginPatientUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,15 @@ export default class LoginPatientUseCase {
private patientRepository: IPatientRepository,
private passwordService: IPasswordServiceRepository
) {}
execute(patient: IPatient) {}

async execute(patient: IPatient): Promise<IPatient> {
const existingPatient = await this.patientRepository.findByEmailWithPassword(patient.email!);
if (!existingPatient) throw new Error("User Not Found");

const isPasswordValid = await this.passwordService.compare(patient.password!, existingPatient.password!);
if (!isPasswordValid) throw new Error("Invalid Credentials");

const { password, ...patientWithoutPassword } = existingPatient;
return patientWithoutPassword as IPatient;
}
}

1 comment on commit c657750

@vercel
Copy link

@vercel vercel bot commented on c657750 Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.