diff --git a/client/app/(patient)/profile/layout.tsx b/client/app/(patient)/profile/layout.tsx index 44bc8de5..5dcd2e27 100644 --- a/client/app/(patient)/profile/layout.tsx +++ b/client/app/(patient)/profile/layout.tsx @@ -1,6 +1,10 @@ 'use client'; import NavSection from "@/components/(patient)/profile/NavSection"; import { memo, ReactNode, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { getUserProfile } from "@/services/api/patient"; +import { ErrorResponse, IPatient } from "@/types"; +import { AxiosError } from "axios"; interface Props { children: ReactNode; @@ -9,15 +13,29 @@ interface Props { } const ProfilePageLayout = ({ children, appointments, records }: Props) => { - const [section,setSection] = useState<"profile"|"appointments"|"records">('profile') + const [section, setSection] = useState<"profile" | "appointments" | "records">('profile'); + + const { data , isLoading, isError } = useQuery>({ + queryKey:['patientProfile'], + queryFn:getUserProfile + }); + + if (isLoading) { + return
Loading...
; + } + + if (isError) { + return
Error loading profile data
; + } + return (
- - {section==='profile'&&children} - {section==='appointments'&&appointments} - {section==='records'&&records} + + {section === 'profile' && children} + {section === 'appointments' && appointments} + {section === 'records' && records}
); diff --git a/client/app/(patient)/profile/page.tsx b/client/app/(patient)/profile/page.tsx index 64aff8e3..0078b330 100644 --- a/client/app/(patient)/profile/page.tsx +++ b/client/app/(patient)/profile/page.tsx @@ -1,4 +1,3 @@ - import PersonalInformation from "@/components/(patient)/profile/PersonalInformation"; import UpcomingAppointment from "@/components/(patient)/profile/UpcomingAppointment"; import AllergiesAndConditions from "@/components/(patient)/profile/AllergiesAndConditions"; diff --git a/client/app/(patient)/signin/(.)otp-verification/page.tsx b/client/app/(patient)/signin/(.)otp-verification/page.tsx index 37b30d22..70e04418 100644 --- a/client/app/(patient)/signin/(.)otp-verification/page.tsx +++ b/client/app/(patient)/signin/(.)otp-verification/page.tsx @@ -1,15 +1,21 @@ -'use client' +"use client"; import { FormEvent } from "react"; import OtpVerificationModel from "@/components/models/OtpVerificationModel"; -const OptInterceptor = () =>{ - const handleVerify = (e:FormEvent)=>{ - e.preventDefault(); - }; - const handleResend = ()=>{} - return ( - - ); -} +const OptInterceptor = () => { + const handleVerify = (e: FormEvent) => { + e.preventDefault(); + }; + const handleResend = () => {}; + return ( + + ); +}; export default OptInterceptor; diff --git a/client/app/(patient)/signin/components/FormSection.tsx b/client/app/(patient)/signin/components/FormSection.tsx new file mode 100644 index 00000000..60b3da43 --- /dev/null +++ b/client/app/(patient)/signin/components/FormSection.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import SigninForm from "../../../../components/common/forms/SigninForm"; +import Image from "next/image"; +import Link from "next/link"; +import { Banners } from "../../../../constants"; + +const FromSection = () => { + + return ( +
+
+
+ patient + +
+

© 2024 AVM Ayurveda's

+ + Staff-Login + +
+
+
+ patient +
+ ); +}; + +export default FromSection; diff --git a/client/app/(patient)/signin/otp-verification/page.tsx b/client/app/(patient)/signin/otp-verification/page.tsx index 5cd21b9e..4e58ee27 100644 --- a/client/app/(patient)/signin/otp-verification/page.tsx +++ b/client/app/(patient)/signin/otp-verification/page.tsx @@ -1,26 +1,28 @@ "use client"; import OtpVerificationSection from "@/components/common/forms/OtpForms"; import Image from "next/image"; -import React, { FormEvent, useState } from "react"; -import { useValidateOtpPatient } from "@/lib/hooks/usePatinet"; +import { FormEvent, useState } from "react"; +import { useValidateOtpPatient } from "@/lib/hooks/usePatientAuth"; import { useToast } from "@/components/ui/use-toast"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import Link from "next/link"; import { Banners } from "@/constants"; +import { useDispatch } from "react-redux"; const OtpVerificationPage = () => { const [otp, setOtp] = useState(""); const { mutate: validateOtp, isPending } = useValidateOtpPatient(); const { toast } = useToast(); const navigate = useRouter(); - + const dispatch = useDispatch(); + const handleVerify = async (e: FormEvent) => { e.preventDefault(); validateOtp( { email: "muhammedsinan0549@gmail.com", otp: parseInt(otp) }, { - onSuccess: (data) => { + onSuccess: () => { toast({ title: "Otp Verification Success ✅", description: "Authentication Completed!. let's book your first appointment", @@ -31,7 +33,7 @@ const OtpVerificationPage = () => { ), }); - console.log(data); + navigate.push("/"); }, onError: (error) => { toast({ diff --git a/client/app/(patient)/signin/page.tsx b/client/app/(patient)/signin/page.tsx index 9d8abe65..b77115ea 100644 --- a/client/app/(patient)/signin/page.tsx +++ b/client/app/(patient)/signin/page.tsx @@ -1,44 +1,12 @@ -import SigninForm from "@/components/common/forms/SigninForm"; -import Image from "next/image"; -import Link from "next/link"; import { Metadata } from "next"; -import { Banners } from "@/constants"; +import FormSection from './components/FormSection' export const metadata: Metadata = { title: "SignIn", }; const SignIn = () => { return ( -
-
-
- patient - -
-

- © 2024 AVM Ayurveda's -

- - Staff-Login - -
-
-
- - patient -
+ ); }; diff --git a/client/app/(patient)/signup/page.tsx b/client/app/(patient)/signup/page.tsx index 8ecffbc9..54cbc5c2 100644 --- a/client/app/(patient)/signup/page.tsx +++ b/client/app/(patient)/signup/page.tsx @@ -1,7 +1,7 @@ import Image from "next/image"; import SignupForm from "@/components/common/forms/SignupForm"; import { Metadata } from "next"; -import { Banners } from "@/constants"; +import { Banners } from "@/constants";; export const metadata: Metadata = { title: "SignUp", diff --git a/client/components/common/forms/SigninForm.tsx b/client/components/common/forms/SigninForm.tsx index fbb94cfb..d79eab3c 100644 --- a/client/components/common/forms/SigninForm.tsx +++ b/client/components/common/forms/SigninForm.tsx @@ -12,7 +12,7 @@ import Link from "next/link"; import { FormFieldType } from "@/types/fromTypes"; import { useToast } from "../../ui/use-toast"; import { useRouter } from "next/navigation"; -import { useSignInPatient } from "@/lib/hooks/usePatinet"; +import { useSignInPatient } from "@/lib/hooks/usePatientAuth"; const LoginForm = () => { const [error, setError] = useState(""); diff --git a/client/components/common/forms/SignupForm.tsx b/client/components/common/forms/SignupForm.tsx index c25f4015..9a049e77 100644 --- a/client/components/common/forms/SignupForm.tsx +++ b/client/components/common/forms/SignupForm.tsx @@ -13,7 +13,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useToast } from "../../ui/use-toast"; import { Button } from "../../ui/button"; -import { useSignUpPatient } from "@/lib/hooks/usePatinet"; +import { useSignUpPatient } from "@/lib/hooks/usePatientAuth"; const RegistrationForm = () => { const [error, setError] = useState(""); diff --git a/client/components/layout/NavBar.tsx b/client/components/layout/NavBar.tsx index 1f3376e9..34f76e16 100644 --- a/client/components/layout/NavBar.tsx +++ b/client/components/layout/NavBar.tsx @@ -20,11 +20,11 @@ import { useSelector } from "react-redux"; export const NavBar = () => { const path = usePathname(); - const patientToken = useSelector(selectPatientToken); + const patientToken = useSelector(selectPatientToken); if (path.includes("signup") || path.includes("staff") || path.includes("signin")) { return null; - } + }; return (
diff --git a/client/components/wrapper/PatientWithAuth.tsx b/client/components/wrapper/PatientWithAuth.tsx deleted file mode 100644 index c5abf458..00000000 --- a/client/components/wrapper/PatientWithAuth.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { ReactNode } from 'react'; -import { useSelector } from 'react-redux'; -import { selectPatientToken } from '@/lib/features/authSlice'; -import { useRouter } from 'next/navigation'; - -interface WithAuthProps { - children: ReactNode; -} - -const PatientWithAuth: React.FC = ({ children }) => { - const token = useSelector(selectPatientToken); - const router = useRouter(); - - if (!token) { - if (typeof window !== 'undefined') { - router.push('/singin'); - } - return null; - } - - return <>{children}; -}; - -export default PatientWithAuth; diff --git a/client/components/wrapper/StaffWithAuth.tsx b/client/components/wrapper/StaffWithAuth.tsx deleted file mode 100644 index 5ba08e0f..00000000 --- a/client/components/wrapper/StaffWithAuth.tsx +++ /dev/null @@ -1,26 +0,0 @@ -'use client' -import { ReactNode } from 'react'; -import { useSelector } from 'react-redux'; -import { selectAdminToken, selectDoctorToken } from '@/lib/features/authSlice'; -import { useRouter } from 'next/navigation'; - -interface WithAuthProps { - children: ReactNode; -} - -const StaffWithAuth: React.FC = ({ children }) => { - const AdminToken = useSelector(selectAdminToken); - const DoctorToken = useSelector(selectDoctorToken); - const router = useRouter(); - - if (!AdminToken&&!DoctorToken) { - if (typeof window !== 'undefined') { - router.push('/staff/signin'); - } - return null; - } - - return <>{children}; -}; - -export default StaffWithAuth; diff --git a/client/lib/features/authSlice.ts b/client/lib/features/authSlice.ts index 54f0edcb..63d5e507 100644 --- a/client/lib/features/authSlice.ts +++ b/client/lib/features/authSlice.ts @@ -29,19 +29,6 @@ const authSlice = createSlice({ name: "auth", initialState, reducers: { - setCredentials: (state, action: PayloadAction) => { - const { accessToken, type } = action.payload; - if (type === "patient") { - state.patientToken = accessToken; - } else if (type === "doctor") { - state.doctorToken = accessToken; - } else if (type === "admin") { - state.adminToken = accessToken; - } - if (typeof window !== "undefined") { - localStorage.setItem("auth", JSON.stringify(state)); - } - }, logOut: (state, action: PayloadAction) => { const { type } = action.payload; if (type === "patient") { @@ -58,7 +45,7 @@ const authSlice = createSlice({ }, }); -export const { setCredentials, logOut } = authSlice.actions; +export const { logOut } = authSlice.actions; export const selectPatientToken = (state: RootState) => state.auth.patientToken; export const selectAdminToken = (state: RootState) => state.auth.adminToken; diff --git a/client/lib/hooks/usePatientAuth.ts b/client/lib/hooks/usePatientAuth.ts new file mode 100644 index 00000000..53a9b149 --- /dev/null +++ b/client/lib/hooks/usePatientAuth.ts @@ -0,0 +1,35 @@ +import { ErrorResponse, IPatient } from "@/types"; +import { useMutation } from "@tanstack/react-query"; +import { signInPatient, signUpPatient, validateOtpPatient } from "@/services/api/auth/patientAuth"; +import { AxiosError } from "axios"; + +export const useSignUpPatient = () => { + return useMutation<{ message: string }, AxiosError, IPatient>({ + mutationFn: (patient) => signUpPatient(patient), + onError: (error: AxiosError) => { + console.log("Error in creating patient:", error); + }, + }); +}; + +export const useSignInPatient = () => { + return useMutation< + { message: string; email: string }, + AxiosError, + { email: string; password: string } + >({ + mutationFn: ({ email, password }) => signInPatient(email, password), + onError: (error) => { + console.log("Error in signing in:", error); + }, + }); +}; + +export const useValidateOtpPatient = () => { + return useMutation<{ accessToken: string }, AxiosError, { email: string; otp: number }>({ + mutationFn: ({ email, otp }) => validateOtpPatient(email, otp), + onError: (error) => { + console.log("Error in OTP validation:", error); + }, + }); +}; diff --git a/client/lib/hooks/usePatinet.ts b/client/lib/hooks/usePatinet.ts deleted file mode 100644 index c9059c3d..00000000 --- a/client/lib/hooks/usePatinet.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ErrorResponse, IPatient } from "@/types"; -import { useMutation } from "@tanstack/react-query"; -import { signInPatient, signUpPatient, validateOtpPatient } from "@/services/api/auth/patientAuth" -import { AxiosError } from "axios"; - - -export const useSignUpPatient = () => { - return useMutation<{ message: string }, AxiosError, IPatient>({ - mutationFn: (patient) => signUpPatient(patient), - onError: (error:AxiosError) => { - console.log('Error in creating patient:', error); - }, - }); -}; - -export const useSignInPatient = () => { - return useMutation<{ message: string, email: string }, AxiosError, { email: string, password: string }>({ - mutationFn: ({ email, password }) => signInPatient(email, password), - onError: (error) => { - console.log('Error in signing in:', error); - }, - }); -}; - -export const useValidateOtpPatient = () => { - return useMutation, { email: string, otp: number }>({ - mutationFn: ({ email, otp }) => validateOtpPatient(email, otp), - onError: (error) => { - console.log('Error in OTP validation:', error); - }, - }); -}; diff --git a/client/services/api/auth/patientAuth.ts b/client/services/api/auth/patientAuth.ts index 5ff570ec..145638ac 100644 --- a/client/services/api/auth/patientAuth.ts +++ b/client/services/api/auth/patientAuth.ts @@ -1,6 +1,26 @@ +import axios from 'axios'; import { IPatient } from "@/types"; -import axiosInstance from "./patientInstance"; +const axiosInstance = axios.create({ + baseURL: `${process.env.NEXT_PUBLIC_API_URL}/patient/auth`, + headers: { + 'Content-Type': 'application/json', + }, + withCredentials:true +}); + +axiosInstance.interceptors.response.use( + (response:any)=>{ + if(response.data.accessToken){ + const auth = JSON.parse(localStorage.getItem("auth")||"{}"); + console.log(auth); + auth.patientToken = response.data.accessToken; + localStorage.setItem("auth",JSON.stringify(auth)); + + } + return response + } +) export const signUpPatient = async (patient: IPatient) => { const response = await axiosInstance.post(`/`, patient); diff --git a/client/services/api/auth/patientInstance.ts b/client/services/api/auth/patientInstance.ts deleted file mode 100644 index be54365d..00000000 --- a/client/services/api/auth/patientInstance.ts +++ /dev/null @@ -1,10 +0,0 @@ -import axios from 'axios'; - -const axiosInstance = axios.create({ - baseURL: `${process.env.NEXT_PUBLIC_API_URL}/patient`, - headers: { - 'Content-Type': 'application/json', - }, -}); - -export default axiosInstance; diff --git a/client/services/api/patient.ts b/client/services/api/patient.ts new file mode 100644 index 00000000..5c94b0d2 --- /dev/null +++ b/client/services/api/patient.ts @@ -0,0 +1,68 @@ +import axios from "axios"; + +const axiosInstance = axios.create({ + baseURL: `${process.env.NEXT_PUBLIC_API_URL}/patient`, + headers: { + "Content-Type": "application/json", + }, + withCredentials: true, +}); + +axiosInstance.interceptors.request.use( + (config) => { + const tokens = JSON.parse(localStorage.getItem("auth") || "{}"); + if (tokens.patientToken) { + config.headers.Authorization = `Bearer ${tokens.patientToken}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +axiosInstance.interceptors.response.use( + (response) => { + return response; + }, + async (error: any) => { + const originalRequest = error.config; + + if (error.response?.status === 401) { + + try { + const tokens = JSON.parse(localStorage.getItem("auth") || "{}"); + const refreshResponse = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/patient/auth/refresh`,{ + withCredentials:true + }); + + const newAccessToken = refreshResponse.data.accessToken; + + localStorage.setItem( + "auth", + JSON.stringify({ + ...tokens, + patientToken: newAccessToken, + }) + ); + + console.log('token refreshed'); + + + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + + + return axiosInstance(originalRequest); + } catch (refreshError) { + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } +); + +export const getUserProfile = async () => { + const response = await axiosInstance.get(`/profile`); + return response.data; +}; diff --git a/server/src/index.ts b/server/src/index.ts index a1849cd5..bf3f72c6 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -7,7 +7,7 @@ import cookieParser from "cookie-parser"; dotenv.config(); -const port = process.env.PORT || 8080; +const port = process.env.PORT ||8080; const app = express(); diff --git a/server/src/infrastructure/services/TokenService.ts b/server/src/infrastructure/services/TokenService.ts index 57292a78..3240d614 100644 --- a/server/src/infrastructure/services/TokenService.ts +++ b/server/src/infrastructure/services/TokenService.ts @@ -1,5 +1,5 @@ import ITokenService from "../../interface/services/ITokenService"; -import jwt, { JwtPayload } from "jsonwebtoken"; +import jwt, { JwtPayload, TokenExpiredError } from "jsonwebtoken"; export default class TokenService implements ITokenService { private signToken(payload:object,secret:string,expiresIn:string):string{ @@ -9,6 +9,9 @@ export default class TokenService implements ITokenService { try { return jwt.verify(token,secret) as JwtPayload } catch (error) { + if(error instanceof TokenExpiredError){ + throw new Error("Token Expired"); + } throw new Error("Invalid token") } } @@ -23,7 +26,7 @@ export default class TokenService implements ITokenService { } createAccessToken(email: string, id: string): string { - return this.signToken({ email, id }, process.env.ACCESS_TOKEN_SECRET!, "15m"); + return this.signToken({ email, id }, process.env.ACCESS_TOKEN_SECRET!, "15m"); } verifyAccessToken(token: string): { email: string; id: string } { diff --git a/server/src/presentation/controllers/PatientController.ts b/server/src/presentation/controllers/PatientController.ts index 9ae33d10..560929bd 100644 --- a/server/src/presentation/controllers/PatientController.ts +++ b/server/src/presentation/controllers/PatientController.ts @@ -83,7 +83,7 @@ export default class PatientController { async refreshAccessToken(req: Request, res: Response, next: NextFunction) { try { const cookies = req.cookies; - if (!cookies?.patient_token) return res.status(401).json({ message: "Unauthorized" }); + if (!cookies?.patient_token) return res.status(403).json({ message: "Unauthenticated" }); const newAccessToken = await this.loginPatientUseCase.refreshAccessToken(cookies.patient_token); diff --git a/server/src/presentation/middlewares/PatientAuthMiddleware.ts b/server/src/presentation/middlewares/PatientAuthMiddleware.ts index 69c09379..61c736b4 100644 --- a/server/src/presentation/middlewares/PatientAuthMiddleware.ts +++ b/server/src/presentation/middlewares/PatientAuthMiddleware.ts @@ -11,13 +11,13 @@ export default class PatientAuthMiddleware { const tokenString = Array.isArray(authHeader) ? authHeader[0] : authHeader; if (!tokenString?.startsWith("Bearer ")) { - return res.status(401).json({ message: "Unauthorized: No or invalid token provided" }); + return res.status(401).json({ message: "Unauthorized: No or invalid Access token provided" }); } const token = tokenString.split(" ")[1]; if (!token) { - return res.status(401).json({ message: "Unauthorized: Token is missing" }); + return res.status(401).json({ message: "Unauthorized: Access Token is missing" }); } const decodedToken = this.tokenService.verifyAccessToken(token); @@ -26,8 +26,11 @@ export default class PatientAuthMiddleware { id: decodedToken.id, }; next(); - } catch (error) { - res.status(401).json({ message: "Forbidden" }); + } catch (error:any) { + if (error.message === "TokenExpired") { + return res.status(401).json({ message: "Access token expired" }); + } + return res.status(401).json({ message: "Unauthorized: Invalid Access token" }); } }; } diff --git a/server/src/presentation/middlewares/errorHandler.ts b/server/src/presentation/middlewares/errorHandler.ts index da632035..11478fee 100644 --- a/server/src/presentation/middlewares/errorHandler.ts +++ b/server/src/presentation/middlewares/errorHandler.ts @@ -18,6 +18,8 @@ export const errorHandler = (err: any, req: Request, res: Response, next: NextFu return res.status(403).json({ message: err.message }); }else if (err.message==="Patient not found"){ return res.status(404).json({message:err.message}) + }else if (err.message===' getaddrinfo ENOTFOUND smtp.gmail.com'){ + return res.status(500).json({message:"We are Having Issue with Email Service"}) } res.status(statusCode).json({ diff --git a/server/src/presentation/routers/index.ts b/server/src/presentation/routers/index.ts index 740b0b79..fd0bf3df 100644 --- a/server/src/presentation/routers/index.ts +++ b/server/src/presentation/routers/index.ts @@ -1,11 +1,12 @@ import express from "express"; import patientAuthRoutes from "./patient/PatientAuthRoutes"; import { errorHandler } from "../middlewares/errorHandler"; +import patientRoutes from "./patient/patientRoutes"; const app = express(); - -app.use("/patient", patientAuthRoutes); - +patientRoutes +app.use("/patient/auth", patientAuthRoutes); +app.use("/patient", patientRoutes); app.use(errorHandler); export default app; diff --git a/server/src/presentation/routers/patient/PatientAuthRoutes.ts b/server/src/presentation/routers/patient/PatientAuthRoutes.ts index 18013fc9..9d1f1332 100644 --- a/server/src/presentation/routers/patient/PatientAuthRoutes.ts +++ b/server/src/presentation/routers/patient/PatientAuthRoutes.ts @@ -38,5 +38,4 @@ route.post('/logout',patientAuthMiddleWare.exec,(req,res,next)=>{ patientController.logout(req,res,next) }); - export default route; diff --git a/server/src/presentation/routers/patient/patientRoutes.ts b/server/src/presentation/routers/patient/patientRoutes.ts new file mode 100644 index 00000000..eb10ce06 --- /dev/null +++ b/server/src/presentation/routers/patient/patientRoutes.ts @@ -0,0 +1,16 @@ +import express from 'express' +import PatientAuthMiddleware from '../../middlewares/PatientAuthMiddleware'; +import TokenService from '../../../infrastructure/services/TokenService'; + + +const patientRoutes = express(); + +const tokenService = new TokenService() +const authenticatePatient = new PatientAuthMiddleware(tokenService) + +patientRoutes.get("/profile",authenticatePatient.exec,(req,res,next)=>{ + res.status(200).json({name:"sinan",age:19,phone:12312312312}) + }); + + +export default patientRoutes \ No newline at end of file diff --git a/server/src/use_case/patient/FindPatientUseCase.ts b/server/src/use_case/patient/FindPatientUseCase.ts deleted file mode 100644 index 79378e78..00000000 --- a/server/src/use_case/patient/FindPatientUseCase.ts +++ /dev/null @@ -1,13 +0,0 @@ -import IPatientRepository from "../../interface/repositories/IPatientRepository"; - -export default class FindPatientUseCase { - constructor(private patientRepository: IPatientRepository) {} - - async findByPatientEmail(email: string) { - return await this.patientRepository.findByEmail(email); - } - - async findByPatientId(id: string) { - return await this.patientRepository.findById(id); - } -} diff --git a/server/src/use_case/patient/LoginPatientUseCase.ts b/server/src/use_case/patient/LoginPatientUseCase.ts index 83a7b553..b1da47b0 100644 --- a/server/src/use_case/patient/LoginPatientUseCase.ts +++ b/server/src/use_case/patient/LoginPatientUseCase.ts @@ -46,7 +46,7 @@ export default class LoginPatientUseCase { await this.patientRepository.update(patient!); - await this.otpRepository.deleteMany(otp,email); + // await this.otpRepository.deleteMany(otp,email); return { accessToken, refreshToken }; }