├── src ├── constant │ ├── role.js │ ├── storageKey.js │ └── appointmentStatus.js ├── images │ ├── logo.png │ ├── avatar.jpg │ ├── chair.png │ ├── blogIcon.png │ ├── doc │ │ ├── doc1.jpg │ │ ├── doc4.jpg │ │ ├── doctor 3.jpg │ │ ├── doctor 5.jpg │ │ └── doctor chair 2.jpg │ ├── img │ │ ├── banner.jpg │ │ ├── hero-bg.jpg │ │ └── doctors-bg.jpg │ ├── features │ │ ├── baby.png │ │ ├── feature.png │ │ ├── feature-01.jpg │ │ ├── feature-02.jpg │ │ ├── feature-03.jpg │ │ ├── feature-05.jpg │ │ └── feature-06.jpg │ └── specialities │ │ ├── cavity.png │ │ ├── teath.png │ │ ├── specialities-01.png │ │ ├── specialities-02.png │ │ ├── specialities-03.png │ │ ├── specialities-04.png │ │ └── specialities-05.png ├── helpers │ ├── config │ │ └── envConfig.js │ └── axios │ │ ├── axiosBaseQuery.js │ │ └── axiosInstance.js ├── utils │ ├── jwt.js │ ├── truncate.js │ ├── copyClipBoard.js │ ├── messageSideEffect.js │ ├── hooks │ │ ├── useDebounced.js │ │ ├── useFetch.js │ │ └── Context │ │ │ └── AuthContext.jsx │ └── local-storage.js ├── components │ ├── Doctor │ │ ├── Reviews │ │ │ └── Reviews.css │ │ ├── PatientFavourite │ │ │ └── index.css │ │ ├── Prescription │ │ │ └── index.css │ │ ├── SearchDoctor │ │ │ └── index.css │ │ ├── Dashboard │ │ │ ├── doctor │ │ │ │ ├── index.css │ │ │ │ └── DoctorDashCard.jsx │ │ │ └── Dashboard.jsx │ │ ├── Appointments │ │ │ ├── index.css │ │ │ └── Appointments.css │ │ ├── ProfileSetting │ │ │ └── ProfileSetting.jsx │ │ ├── DashboardLayout │ │ │ └── DashboardLayout.jsx │ │ ├── DoctorProfile │ │ │ ├── index.css │ │ │ └── DoctorProfile.jsx │ │ ├── ChangePassword │ │ │ └── ChangePassword.jsx │ │ └── MyPatients │ │ │ └── MyPatients.jsx │ ├── Home │ │ ├── Services │ │ │ ├── index.css │ │ │ └── Service.jsx │ │ ├── Testimonial │ │ │ └── index.css │ │ ├── Gallery │ │ │ ├── index.css │ │ │ └── Gallery.jsx │ │ ├── AvailableFeatures │ │ │ ├── index.css │ │ │ ├── Available.jsx │ │ │ └── AvailableServiceContent.jsx │ │ ├── HeroSection │ │ │ ├── HeroSection.jsx │ │ │ └── index.css │ │ ├── Home │ │ │ └── Home.jsx │ │ ├── InfoPage │ │ │ └── InfoPage.css │ │ ├── OurDoctor │ │ │ ├── index.css │ │ │ └── OurDoctors.jsx │ │ ├── BookOurDoctor │ │ │ └── BookDoctor.css │ │ └── ClinicAndSpecialities │ │ │ ├── index.css │ │ │ └── ClinicAndSpecialities.jsx │ ├── UI │ │ ├── UseModal.jsx │ │ ├── NotFound.jsx │ │ ├── form │ │ │ ├── TimePicer.jsx │ │ │ ├── SelectForm.jsx │ │ │ ├── TabForm.jsx │ │ │ ├── ImageUpload.jsx │ │ │ ├── SelectFormForMedicine.jsx │ │ │ ├── MedicineRangePickerForm.jsx │ │ │ └── InputAutoCompleteForm.jsx │ │ ├── component │ │ │ └── CustomTable.jsx │ │ ├── PatientAppointment.css │ │ ├── DashboardSidebar.css │ │ └── AdminHeader.jsx │ ├── Admin │ │ ├── Doctors │ │ │ ├── Doctors.jsx │ │ │ └── Doctors.css │ │ ├── Profile │ │ │ ├── Profile.jsx │ │ │ └── Profile.css │ │ ├── Patients │ │ │ ├── Patients.jsx │ │ │ └── Patients.css │ │ ├── Reviews │ │ │ ├── Reviews.jsx │ │ │ └── Reviews.css │ │ ├── Specialites │ │ │ ├── Specialites.jsx │ │ │ └── Specialites.css │ │ ├── Transactions │ │ │ ├── Transactions.jsx │ │ │ └── Transactions.css │ │ ├── AdminLayout │ │ │ ├── AdminLayout.css │ │ │ └── AdminLayout.jsx │ │ ├── Dashboard │ │ │ └── Dashboard.css │ │ └── Appointments │ │ │ ├── Appointments.css │ │ │ └── Appointments.jsx │ ├── Blog │ │ └── index.css │ ├── Shared │ │ ├── PrivateOutlet.jsx │ │ ├── TopHeader │ │ │ ├── index.css │ │ │ └── TopHeader.jsx │ │ ├── SubHeader.jsx │ │ └── Footer │ │ │ └── Footer.css │ ├── About │ │ └── index.css │ ├── Booking │ │ ├── DoctorBooking │ │ │ └── index.css │ │ ├── BookingCheckout │ │ │ └── BookingCheckout.css │ │ ├── SelectDateAndTime.jsx │ │ └── BookingInvoice │ │ │ └── BookingInvoice.css │ ├── Contact │ │ └── index.css │ ├── Login │ │ ├── SocialSignUp.jsx │ │ └── SignInForm.jsx │ ├── Appointment │ │ └── index.css │ ├── Service │ │ └── Service.jsx │ └── TrackAppointment │ │ ├── index.css │ │ └── AppointmentTimeLine.jsx ├── redux │ ├── rootReducer.js │ ├── store.js │ ├── api │ │ ├── contactApi.js │ │ ├── baseApi.js │ │ ├── patientApi.js │ │ ├── favouriteApi.js │ │ ├── medicineApi.js │ │ ├── doctorApi.js │ │ ├── authApi.js │ │ ├── blogApi.js │ │ ├── reviewsApi.js │ │ ├── timeSlotApi.js │ │ └── prescriptionApi.js │ ├── feature │ │ └── invoiceSlice.js │ ├── hooks.js │ ├── tag-types.js │ └── hooks │ │ └── useAuthCheck.js ├── index.js └── service │ └── auth.service.js ├── public ├── _redirects ├── favicon.ico ├── manifest.json └── index.html ├── api ├── .eslintignore ├── README.md ├── .gitignore ├── src │ ├── interfaces │ │ ├── error.ts │ │ ├── index.d.ts │ │ ├── common.ts │ │ └── file.ts │ ├── shared │ │ ├── prisma.ts │ │ ├── pick.ts │ │ ├── catchAsync.ts │ │ ├── sendResponse.ts │ │ ├── paginationHelper.ts │ │ └── logger.ts │ ├── app │ │ ├── modules │ │ │ ├── appointment │ │ │ │ ├── appointment.interface.ts │ │ │ │ └── appointment.route.ts │ │ │ ├── blog │ │ │ │ ├── blog.interface.ts │ │ │ │ ├── blog.route.ts │ │ │ │ └── blog.controller.ts │ │ │ ├── contact │ │ │ │ ├── contact.route.ts │ │ │ │ ├── contact.controller.ts │ │ │ │ └── contact.service.ts │ │ │ ├── doctor │ │ │ │ ├── doctor.interface.ts │ │ │ │ ├── doctor.route.ts │ │ │ │ └── doctor.controller.ts │ │ │ ├── medicines │ │ │ │ ├── medicine.route.ts │ │ │ │ ├── medicine.controller.ts │ │ │ │ └── medicine.service.ts │ │ │ ├── favourites │ │ │ │ ├── favourites.route.ts │ │ │ │ ├── favourites.controller.ts │ │ │ │ └── favourites.service.ts │ │ │ ├── auth │ │ │ │ └── auth.route.ts │ │ │ ├── reviews │ │ │ │ └── reviews.route.ts │ │ │ ├── patient │ │ │ │ ├── patient.route.ts │ │ │ │ ├── patientService.ts │ │ │ │ ├── patient.controller.ts │ │ │ │ └── patient.service.ts │ │ │ ├── doctorTimeSlot │ │ │ │ └── doctorTimeSlot.route.ts │ │ │ └── prescription │ │ │ │ └── prescription.route.ts │ │ ├── middlewares │ │ │ └── auth.ts │ │ └── routes │ │ │ └── index.ts │ ├── helpers │ │ ├── Transporter.ts │ │ ├── jwtHelper.ts │ │ ├── uploadHelper.ts │ │ └── emailTransporter.ts │ ├── enums │ │ └── index.ts │ ├── errors │ │ ├── apiError.ts │ │ ├── handleCastError.ts │ │ ├── handleZodError.ts │ │ └── handleValidationError.ts │ ├── constants │ │ └── index.ts │ ├── server.ts │ ├── config │ │ └── index.ts │ └── app.ts ├── utils │ ├── error.js │ └── verifyToken.js ├── vercel.json ├── .env.example ├── .eslintrc ├── package.json └── template │ └── meeting.html ├── .env.development ├── .env.production ├── .gitignore ├── project_setup.txt └── package.json /src/constant/role.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /api/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .env -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # Doctor-Appointment-Backend 2 | -------------------------------------------------------------------------------- /src/constant/storageKey.js: -------------------------------------------------------------------------------- 1 | export const authKey = 'accessToken'; -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | **/*.env 3 | prisma/migrations/ 4 | .vercel 5 | dist/ -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_NODE_ENV=development 2 | REACT_APP_API_BASE_URL=http://localhost:5000/api/v1 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/logo.png -------------------------------------------------------------------------------- /src/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/avatar.jpg -------------------------------------------------------------------------------- /src/images/chair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/chair.png -------------------------------------------------------------------------------- /src/images/blogIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/blogIcon.png -------------------------------------------------------------------------------- /src/images/doc/doc1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/doc/doc1.jpg -------------------------------------------------------------------------------- /src/images/doc/doc4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/doc/doc4.jpg -------------------------------------------------------------------------------- /src/images/doc/doctor 3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/doc/doctor 3.jpg -------------------------------------------------------------------------------- /src/images/doc/doctor 5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/doc/doctor 5.jpg -------------------------------------------------------------------------------- /src/images/img/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/img/banner.jpg -------------------------------------------------------------------------------- /src/images/img/hero-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/img/hero-bg.jpg -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_NODE_ENV=production 2 | REACT_APP_API_BASE_URL=https://doctor-on-call-backend.vercel.app/api/v1 -------------------------------------------------------------------------------- /src/images/features/baby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/features/baby.png -------------------------------------------------------------------------------- /src/images/img/doctors-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/img/doctors-bg.jpg -------------------------------------------------------------------------------- /src/images/features/feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/features/feature.png -------------------------------------------------------------------------------- /src/helpers/config/envConfig.js: -------------------------------------------------------------------------------- 1 | const url = process.env.REACT_APP_API_BASE_URL; 2 | 3 | export const getBaseUrl = () =>{return url} -------------------------------------------------------------------------------- /src/images/doc/doctor chair 2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/doc/doctor chair 2.jpg -------------------------------------------------------------------------------- /src/images/features/feature-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/features/feature-01.jpg -------------------------------------------------------------------------------- /src/images/features/feature-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/features/feature-02.jpg -------------------------------------------------------------------------------- /src/images/features/feature-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/features/feature-03.jpg -------------------------------------------------------------------------------- /src/images/features/feature-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/features/feature-05.jpg -------------------------------------------------------------------------------- /src/images/features/feature-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/features/feature-06.jpg -------------------------------------------------------------------------------- /src/images/specialities/cavity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/specialities/cavity.png -------------------------------------------------------------------------------- /src/images/specialities/teath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/specialities/teath.png -------------------------------------------------------------------------------- /api/src/interfaces/error.ts: -------------------------------------------------------------------------------- 1 | export type IGenericErrorMessage = { 2 | path: string | number; 3 | message: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/utils/jwt.js: -------------------------------------------------------------------------------- 1 | import jwtDecode from "jwt-decode" 2 | 3 | export const decodeToken = (token) =>{ 4 | return jwtDecode(token); 5 | } -------------------------------------------------------------------------------- /src/utils/truncate.js: -------------------------------------------------------------------------------- 1 | export const truncate = (str, max) =>{ 2 | return str.length > max ? str.substring(0, max-1) + '...' : str; 3 | } -------------------------------------------------------------------------------- /src/images/specialities/specialities-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/specialities/specialities-01.png -------------------------------------------------------------------------------- /src/images/specialities/specialities-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/specialities/specialities-02.png -------------------------------------------------------------------------------- /src/images/specialities/specialities-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/specialities/specialities-03.png -------------------------------------------------------------------------------- /src/images/specialities/specialities-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/specialities/specialities-04.png -------------------------------------------------------------------------------- /src/images/specialities/specialities-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky030511/doctor-appoint/HEAD/src/images/specialities/specialities-05.png -------------------------------------------------------------------------------- /api/src/shared/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prisma = new PrismaClient({ 4 | errorFormat: 'minimal' 5 | }); 6 | 7 | export default prisma; -------------------------------------------------------------------------------- /api/utils/error.js: -------------------------------------------------------------------------------- 1 | export const createError = (status, message) =>{ 2 | const err = new Error(); 3 | err.status = status; 4 | err.message = message; 5 | return err; 6 | } -------------------------------------------------------------------------------- /api/src/app/modules/appointment/appointment.interface.ts: -------------------------------------------------------------------------------- 1 | export const AppointmentsFilterable = ['searchTerm', 'appointmentTime','status'] 2 | export const AppointmentsSearchable = ['appointmentTime','status', 'purpose'] -------------------------------------------------------------------------------- /api/src/app/modules/blog/blog.interface.ts: -------------------------------------------------------------------------------- 1 | export type IBlogFilters = { 2 | searchTerm?: string; 3 | title?: string; 4 | description?: string; 5 | } 6 | export const blogSearchablFields = ['title', 'description'] -------------------------------------------------------------------------------- /api/src/interfaces/index.d.ts: -------------------------------------------------------------------------------- 1 | import { JwtPayload } from "jsonwebtoken"; 2 | 3 | declare global { 4 | namespace Express{ 5 | interface Request{ 6 | user: JwtPayload | null 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/components/Doctor/Reviews/Reviews.css: -------------------------------------------------------------------------------- 1 | .review-img{ 2 | height: 40px; 3 | width: 40px; 4 | border-radius: 50%; 5 | } 6 | .review-img img{ 7 | object-fit: cover; 8 | object-position: top; 9 | } 10 | -------------------------------------------------------------------------------- /src/redux/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { baseApi } from "./api/baseApi"; 2 | import invoiceSlice from "./feature/invoiceSlice"; 3 | export const reducer = { 4 | [baseApi.reducerPath]: baseApi.reducer, 5 | invoice: invoiceSlice 6 | } -------------------------------------------------------------------------------- /src/components/Doctor/PatientFavourite/index.css: -------------------------------------------------------------------------------- 1 | .fav-img img{ 2 | border: 6px solid #efefef; 3 | border-radius: 50%; 4 | object-fit: cover; 5 | object-position: top; 6 | height: 120px; 7 | width: 120px; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Doctor/Prescription/index.css: -------------------------------------------------------------------------------- 1 | .symptoms-section h5{ 2 | font-size: 14px; 3 | font-weight: 700; 4 | } 5 | 6 | .symptoms-section p{ 7 | font-size: 13px; 8 | font-weight: 500; 9 | color: #3f3f3f; 10 | } -------------------------------------------------------------------------------- /src/components/Home/Services/index.css: -------------------------------------------------------------------------------- 1 | .service-img img{ 2 | border-radius: 5px; 3 | box-shadow: 0px 0px 30px 0px rgba(0, 42, 106, 0.1) 4 | } 5 | .service-content h2{ 6 | color: #223a66; 7 | font-size: 44px; 8 | font-weight: bold; 9 | } -------------------------------------------------------------------------------- /api/src/app/modules/contact/contact.route.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ContactController } from './contact.controller'; 3 | 4 | const router = express.Router(); 5 | router.post('/', ContactController.ContactUs); 6 | 7 | export const ContactRouter = router; -------------------------------------------------------------------------------- /api/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "dist/server.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "dist/server.js" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /api/src/helpers/Transporter.ts: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | import config from '../config'; 3 | 4 | export const Transporter = nodemailer.createTransport({ 5 | service: 'gmail', 6 | auth: { 7 | user: config.gmail_app_Email, 8 | pass: config.emailPass 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/Doctor/SearchDoctor/index.css: -------------------------------------------------------------------------------- 1 | .doc-img-fluid img { 2 | height: 200px; 3 | width: 200px; 4 | border: 8px solid #e0e0e0; 5 | border-radius: 50%; 6 | overflow: hidden; 7 | object-fit: cover; 8 | object-position: top; 9 | } 10 | .doc-info a{ 11 | color: #1977cc !important; 12 | } -------------------------------------------------------------------------------- /src/components/Home/Testimonial/index.css: -------------------------------------------------------------------------------- 1 | .review-img img{ 2 | height: 40px !important; 3 | width: 40px !important; 4 | border-radius: 50%; 5 | object-fit: cover; 6 | object-position: top; 7 | } 8 | .recomended{ 9 | color: #20b420; 10 | } 11 | .swiper { 12 | width: inherit !important; 13 | } 14 | -------------------------------------------------------------------------------- /api/src/enums/index.ts: -------------------------------------------------------------------------------- 1 | export enum BloodGroup { 2 | 'O+', 'O-', 'B+', 'B-', 'AB+', 'AB-', 'A+', 'A-' 3 | } 4 | 5 | enum USER_RULES { 6 | "ADMIN", 7 | "PATIENT", 8 | "DOCTOR" 9 | } 10 | export enum AuthUser { 11 | ADMIN = 'admin', 12 | DOCTOR = 'doctor', 13 | PATIENT = 'patient', 14 | SUPER_ADMIN = 'super_admin' 15 | } -------------------------------------------------------------------------------- /src/components/UI/UseModal.jsx: -------------------------------------------------------------------------------- 1 | import { Modal } from 'antd'; 2 | const UseModal = ({ children, title, isModaOpen, handleCancel, handleOk }) => { 3 | return ( 4 | 5 | {children} 6 | 7 | ) 8 | } 9 | 10 | export default UseModal -------------------------------------------------------------------------------- /src/components/Admin/Doctors/Doctors.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminLayout from '../AdminLayout/AdminLayout' 3 | import './Doctors.css'; 4 | 5 | const Doctors = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | export default Doctors; -------------------------------------------------------------------------------- /src/components/Admin/Profile/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminLayout from '../AdminLayout/AdminLayout' 3 | import './Profile.css'; 4 | 5 | const Profile = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | export default Profile; -------------------------------------------------------------------------------- /api/src/shared/pick.ts: -------------------------------------------------------------------------------- 1 | const pick = (obj: T, keys: k[]): Partial => { 2 | const finalObj: Partial = {} 3 | 4 | for(const key of keys){ 5 | if(obj && Object.hasOwnProperty.call(obj, key)){ 6 | finalObj[key] = obj[key] 7 | } 8 | } 9 | return finalObj; 10 | } 11 | export default pick; -------------------------------------------------------------------------------- /src/components/Admin/Patients/Patients.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminLayout from '../AdminLayout/AdminLayout' 3 | import './Patients.css'; 4 | 5 | const Patients = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | export default Patients; -------------------------------------------------------------------------------- /src/components/Admin/Reviews/Reviews.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminLayout from '../AdminLayout/AdminLayout' 3 | import './Reviews.css'; 4 | 5 | const AdminReviews = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | export default AdminReviews; -------------------------------------------------------------------------------- /api/src/shared/catchAsync.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, RequestHandler, Response } from "express"; 2 | 3 | const catchAsync = (fn: RequestHandler) => async (req: Request, res: Response, next: NextFunction):Promise => { 4 | try { 5 | await fn(req, res, next) 6 | } catch (error) { 7 | next(error) 8 | } 9 | } 10 | export default catchAsync; -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { baseApi } from './api/baseApi'; 2 | import { configureStore } from '@reduxjs/toolkit' 3 | import { reducer } from './rootReducer'; 4 | export const store = configureStore({ 5 | reducer, 6 | middleware: (getDefaultMiddleware) => 7 | getDefaultMiddleware().concat(baseApi.middleware), 8 | devTools: process.env.NODE_ENV !== 'production' 9 | }) -------------------------------------------------------------------------------- /src/components/Admin/Specialites/Specialites.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminLayout from '../AdminLayout/AdminLayout'; 3 | import './Specialites.css'; 4 | 5 | const Specialites = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | export default Specialites; -------------------------------------------------------------------------------- /src/components/Admin/Transactions/Transactions.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminLayout from '../AdminLayout/AdminLayout' 3 | 4 | import './Transactions.css'; 5 | 6 | const Transactions = () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | export default Transactions; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | api/logs/winston 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development 19 | .env.test.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/components/Doctor/Dashboard/doctor/index.css: -------------------------------------------------------------------------------- 1 | .dash-card-icon{ 2 | text-align: center; 3 | border: 6px solid #1977cc; 4 | border-radius: 50%; 5 | height: 75px; 6 | width: 75px; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | color: #1378bb; 11 | } 12 | .dash-card-icon .icon{ 13 | font-size: 2.5rem; 14 | } 15 | .dash-card{ 16 | border-right: 1px solid #c0bebe; 17 | } -------------------------------------------------------------------------------- /src/utils/copyClipBoard.js: -------------------------------------------------------------------------------- 1 | import { message } from "antd"; 2 | 3 | export const clickToCopyClipBoard = (id) => { 4 | const textField = document.createElement('textarea'); 5 | textField.innerText = id; 6 | document.body.appendChild(textField); 7 | textField.select(); 8 | document.execCommand('copy'); 9 | document.body.removeChild(textField); 10 | message.success("Copied To Clipboard") 11 | } -------------------------------------------------------------------------------- /src/components/Blog/index.css: -------------------------------------------------------------------------------- 1 | .blog-title{ 2 | color: #2c4964; 3 | font-weight: 600; 4 | } 5 | .categories-title h6, .categories-title .icon{ 6 | color: #05335c; 7 | } 8 | .categories-title{ 9 | color: #1977cc; 10 | cursor: pointer; 11 | transition: 0.6s ease-in; 12 | } 13 | .categories-title:hover > h6{ 14 | color: #7054F2; 15 | } 16 | .categories-title:hover > .icon{ 17 | color: #7054F2; 18 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import 'bootstrap/dist/css/bootstrap.min.css'; 5 | import { Provider } from 'react-redux'; 6 | import { store } from './redux/store'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); -------------------------------------------------------------------------------- /src/redux/api/contactApi.js: -------------------------------------------------------------------------------- 1 | import { baseApi } from "./baseApi" 2 | 3 | export const contactApi = baseApi.injectEndpoints({ 4 | endpoints: (build) => ({ 5 | contact: build.mutation({ 6 | query: (data) => ({ 7 | url: `/contact`, 8 | method: 'POST', 9 | data: data, 10 | }) 11 | }), 12 | 13 | }) 14 | }) 15 | 16 | export const { useContactMutation } = contactApi; -------------------------------------------------------------------------------- /src/components/UI/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Result } from 'antd'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const NotFound = () => { 5 | return ( 6 | } 11 | /> 12 | ) 13 | } 14 | export default NotFound -------------------------------------------------------------------------------- /src/redux/api/baseApi.js: -------------------------------------------------------------------------------- 1 | import { createApi } from '@reduxjs/toolkit/query/react' 2 | import { tagTypeList } from '../tag-types' 3 | import { axiosBaseQuery } from '../../helpers/axios/axiosBaseQuery' 4 | import { getBaseUrl } from '../../helpers/config/envConfig' 5 | 6 | export const baseApi = createApi({ 7 | reducerPath: 'api', 8 | baseQuery: axiosBaseQuery({ baseUrl: getBaseUrl() }), 9 | endpoints: () => ({}), 10 | tagTypes: tagTypeList 11 | }) -------------------------------------------------------------------------------- /api/src/errors/apiError.ts: -------------------------------------------------------------------------------- 1 | class ApiError extends Error { 2 | statusCode: number; 3 | constructor(statusCode: number, message: string | undefined, stack = '') { 4 | super(message); 5 | this.statusCode = statusCode; 6 | 7 | if (this.stack) { 8 | this.stack = stack; 9 | } 10 | else { 11 | Error.captureStackTrace(this, this.constructor) 12 | } 13 | } 14 | 15 | } 16 | export default ApiError; -------------------------------------------------------------------------------- /src/redux/feature/invoiceSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | data: {} 5 | } 6 | 7 | export const invoiceSlice = createSlice({ 8 | name: 'invoice', 9 | initialState, 10 | reducers: { 11 | addInvoice: (state, action) => { 12 | state.data = action.payload; 13 | } 14 | } 15 | }) 16 | 17 | export const { addInvoice } = invoiceSlice.actions; 18 | export default invoiceSlice.reducer; -------------------------------------------------------------------------------- /api/src/helpers/jwtHelper.ts: -------------------------------------------------------------------------------- 1 | import jwt, { JwtPayload, Secret } from 'jsonwebtoken'; 2 | 3 | const createToken = (payload: {}, secret: Secret, expireTime: string) => { 4 | return jwt.sign(payload, secret, { 5 | expiresIn: expireTime 6 | }) 7 | } 8 | 9 | const verifyToken = (token: string, secret: Secret): JwtPayload => { 10 | return jwt.verify(token, secret) as JwtPayload 11 | } 12 | 13 | export const JwtHelper = { 14 | verifyToken, 15 | createToken 16 | } -------------------------------------------------------------------------------- /api/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { IBloodGroup } from "../interfaces/common"; 2 | 3 | export const BloodGroup: IBloodGroup[] = ['O+', 'O-' , 'B+' , 'B-' , 'AB+' , 'AB-' , 'A+' , 'A-'] 4 | export const patientCondition: string[] = [ "Critical", "Serious", "Stable", "Acute", "Chronic", "Critical but Stable", "Non-Critical", "Terminal", "Emergent", "Urgent"]; 5 | export const IAuthRules = ['ADMIN' , "PATIENT" , "DOCTOR"] 6 | 7 | export const IOptions = ['limit', 'page', 'sortBy', 'sortOrder'] -------------------------------------------------------------------------------- /src/utils/messageSideEffect.js: -------------------------------------------------------------------------------- 1 | import { message } from "antd" 2 | import { useEffect } from "react" 3 | 4 | export const useMessageEffect = (isLoading, isSuccess, isError, error, successMessage) => { 5 | useEffect(() => { 6 | if(!isLoading && isError){ 7 | message.error(error?.data?.message); 8 | } 9 | if(isSuccess){ 10 | message.success(successMessage) 11 | } 12 | }, [isLoading, isSuccess, isError, error, successMessage]) 13 | } -------------------------------------------------------------------------------- /src/components/Shared/PrivateOutlet.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet, useNavigate } from 'react-router-dom'; 2 | import { getUserInfo } from '../../service/auth.service'; 3 | import { useEffect } from 'react'; 4 | 5 | const PrivateOutlet = () => { 6 | const navigate = useNavigate(); 7 | 8 | useEffect(() => { 9 | const localAuth = getUserInfo(); 10 | if (!localAuth) {navigate('/login', {replace: true})} 11 | }, [navigate]) 12 | 13 | return 14 | }; 15 | 16 | export default PrivateOutlet; -------------------------------------------------------------------------------- /api/src/interfaces/common.ts: -------------------------------------------------------------------------------- 1 | import { IGenericErrorMessage } from './error'; 2 | 3 | export type IGenericResponse = { 4 | meta: { 5 | page: number | undefined; 6 | limit: number | undefined; 7 | total: number | undefined; 8 | }; 9 | data: T; 10 | }; 11 | 12 | export type IGenericErrorResponse = { 13 | statusCode: number; 14 | message: string; 15 | errorMessages: IGenericErrorMessage[]; 16 | }; 17 | 18 | export type IBloodGroup = 'O+' | 'O-' | 'B+' | 'B-' | 'AB+' | 'AB-' | 'A+' | 'A-' -------------------------------------------------------------------------------- /src/redux/hooks.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export const useDebounced = ({ searchQuery, delay }) => { 4 | const [debouncedValue, setDebouncedValue] = useState(searchQuery); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(searchQuery); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | } 14 | }, [searchQuery, delay]); 15 | 16 | return debouncedValue; 17 | } -------------------------------------------------------------------------------- /src/utils/hooks/useDebounced.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | export const useDebounced = ({ searchQuery, delay }) => { 4 | const [debouncedValue, setDebouncedValue] = useState(searchQuery); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(searchQuery); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | } 14 | }, [searchQuery, delay]); 15 | return debouncedValue; 16 | } -------------------------------------------------------------------------------- /src/utils/local-storage.js: -------------------------------------------------------------------------------- 1 | export const setUserInfo = ({accessToken}) =>{ 2 | return setLocalStorage('accessToken',accessToken) 3 | } 4 | 5 | export const setLocalStorage = (key, token) => { 6 | if (!key || typeof window === 'undefined') { 7 | return '' 8 | } 9 | return localStorage.setItem(key, token) 10 | } 11 | 12 | export const getFromLocalStorage = (key) => { 13 | if (!key || typeof window === 'undefined') { 14 | return '' 15 | } 16 | return localStorage.getItem(key) 17 | } -------------------------------------------------------------------------------- /src/components/UI/form/TimePicer.jsx: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { TimePicker } from 'antd'; 3 | 4 | const TimePicer = ({ id, time, handleFunction, showValue = false }) => { 5 | const defaultTime = moment(time, 'h:mm a') 6 | return ( 7 | handleFunction(id, time, timeString)} 12 | /> 13 | ) 14 | }; 15 | export default TimePicer; -------------------------------------------------------------------------------- /src/components/Doctor/Appointments/index.css: -------------------------------------------------------------------------------- 1 | page { 2 | background: white; 3 | display: block; 4 | margin: 0 auto; 5 | margin-bottom: 0.5cm; 6 | /* border: 1px solid gray; */ 7 | /* box-shadow: 0 0 0.5cm rgba(0,0,0,0.5); */ 8 | } 9 | page[size="A4"] { 10 | width: 21cm; 11 | height: 29.7cm; 12 | } 13 | page[size="A4"][layout="portrait"] { 14 | width: 29.7cm; 15 | height: 21cm; 16 | } 17 | @media print { 18 | body, page { 19 | margin: 0; 20 | box-shadow: 0; 21 | } 22 | } -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "type": "image/png", 12 | "sizes": "192x192" 13 | }, 14 | { 15 | "type": "image/png", 16 | "sizes": "512x512" 17 | } 18 | ], 19 | "start_url": ".", 20 | "display": "standalone", 21 | "theme_color": "#000000", 22 | "background_color": "#ffffff" 23 | } 24 | -------------------------------------------------------------------------------- /api/src/app/modules/doctor/doctor.interface.ts: -------------------------------------------------------------------------------- 1 | export type IDoctorFilters = { 2 | searchTerm?: string; 3 | firstName?: string; 4 | gender?: string; 5 | city?: string; 6 | max?: string; 7 | min?: string; 8 | specialist?: string; 9 | } 10 | export const IDoctorFiltersData = ['searchTerm','firstName','lastName','gender','city', 'max', 'min', 'specialist'] 11 | export const IDoctorOptions = ['limit', 'page', 'sortBy', 'sortOrder'] 12 | 13 | export const DoctorSearchableFields = ['firstName', 'lastName', 'address', 'specialization', 'degree'] -------------------------------------------------------------------------------- /api/src/app/modules/medicines/medicine.route.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { auth } from '../../middlewares/auth'; 3 | import { AuthUser } from '../../../enums'; 4 | import { MedicineController } from './medicine.controller'; 5 | 6 | const router = express.Router(); 7 | 8 | router.patch('/',auth(AuthUser.DOCTOR), MedicineController.updateMedicine); 9 | router.post('/',auth(AuthUser.DOCTOR), MedicineController.createMedicine); 10 | router.delete('/:id',auth(AuthUser.DOCTOR), MedicineController.deleteMedicine); 11 | 12 | export const MedicineRouter = router; -------------------------------------------------------------------------------- /src/components/About/index.css: -------------------------------------------------------------------------------- 1 | .award-img { 2 | height: 120px; 3 | margin-bottom: 10px; 4 | align-items: center; 5 | display: flex; 6 | justify-content: center; 7 | background: #eff0f3; 8 | object-fit: cover; 9 | overflow: hidden; 10 | border-radius: 15px; 11 | } 12 | .say-about { 13 | position: relative; 14 | } 15 | .say-about:before { 16 | width: 48%; 17 | height: 100%; 18 | top: 0; 19 | left: 0px; 20 | position: absolute; 21 | content: ""; 22 | background: url("../../images/doc/doc4.jpg") no-repeat 50% 50%; 23 | } -------------------------------------------------------------------------------- /api/src/errors/handleCastError.ts: -------------------------------------------------------------------------------- 1 | // import mongoose from 'mongoose'; 2 | // import { IGenericErrorMessage } from '../interfaces/error'; 3 | 4 | // const handleCastError = (error: mongoose.Error.CastError) => { 5 | // const errors: IGenericErrorMessage[] = [ 6 | // { 7 | // path: error.path, 8 | // message: 'Invalid Id', 9 | // }, 10 | // ]; 11 | 12 | // const statusCode = 400; 13 | // return { 14 | // statusCode, 15 | // message: 'Cast Error', 16 | // errorMessages: errors, 17 | // }; 18 | // }; 19 | 20 | // export default handleCastError; -------------------------------------------------------------------------------- /api/src/app/modules/favourites/favourites.route.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { auth } from '../../middlewares/auth'; 3 | import { AuthUser } from '../../../enums'; 4 | import { FavouriteController } from './favourites.controller'; 5 | 6 | const router = express.Router(); 7 | 8 | router.get('/',auth(AuthUser.PATIENT), FavouriteController.getPatientFavourites); 9 | router.post('/add',auth(AuthUser.PATIENT), FavouriteController.addFavourite); 10 | router.post('/remove',auth(AuthUser.PATIENT), FavouriteController.removeFavourite); 11 | 12 | export const FavouriteRouter = router; -------------------------------------------------------------------------------- /api/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=YOUR DATABASE URL 2 | JWT=W16aQUoCDwHm8AAAAadWpqYWx6YW1hbkBERVNLVE9QLUlLNkVITkUBdfdf 3 | PORT=5000 4 | NODE_ENV=development 5 | JWT_SCRET=f9Hr6v38sK2nA5xGt4wDcR7uJ1mZlP0b 6 | JWT_EXPIRED_IN=30d 7 | JWT_REFRESH_SECRET=f9Hr6v38sK2nA5xGt4wDcR7uJ1mZlP0b 8 | JWT_SCRET_SALT_ROUND=10 9 | DOCTOR_PASS=@doctor123 10 | PATIENT_PASS=@patient 11 | CLOUND_NAME=YOUR_CLOUD_NAME 12 | API_KEY=YOUR_CLOUDINARY_API_KEY 13 | API_SECRET=YOUR_CLOUDINARY_API_SECRET 14 | EMAIL_PASS=YOUR_EMAIL_APP_PASSWORD_WITH_NO_SPACE 15 | ADMIN_EMAIL=your_default_email@gmail.com 16 | GMAIL_APP_EMAIL=your_email@gmail.com -------------------------------------------------------------------------------- /api/src/app/modules/auth/auth.route.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { AuthController } from './auth.controller'; 3 | 4 | const router = express.Router(); 5 | 6 | router.post('/login', AuthController.Login); 7 | router.post('/reset-password', AuthController.resetPassword); 8 | router.post('/reset-password/confirm', AuthController.PasswordResetConfirm); 9 | router.get('/user/verify/:userId/:uniqueString', AuthController.VerifyUser); 10 | router.get('/verified', AuthController.Verified); 11 | router.get('/expired/link', AuthController.VerficationExpired); 12 | 13 | export const AuthRouter = router; -------------------------------------------------------------------------------- /src/components/Admin/AdminLayout/AdminLayout.css: -------------------------------------------------------------------------------- 1 | .page-header { 2 | margin-bottom: 1.875rem; 3 | } 4 | 5 | .page-title { 6 | color: #333; 7 | margin-bottom: 5px; 8 | } 9 | .main-wrapper { 10 | width: 100%; 11 | height: 100vh; 12 | min-height: 100vh; 13 | } 14 | .page-wrapper { 15 | margin-left: 240px; 16 | padding-top: 60px; 17 | position: relative; 18 | transition: all 0.4s ease; 19 | } 20 | .page-wrapper > .content { 21 | padding: 1.875rem 1.875rem 0; 22 | } 23 | .page-header { 24 | margin-bottom: 1.875rem; 25 | } 26 | .page-title { 27 | color: #333; 28 | margin-bottom: 5px; 29 | } -------------------------------------------------------------------------------- /src/components/Home/Gallery/index.css: -------------------------------------------------------------------------------- 1 | .gallery{ 2 | margin-top: 3rem; 3 | margin-bottom: 8rem; 4 | } 5 | .gallery-item { 6 | overflow: hidden; 7 | border-right: 3px solid #fff; 8 | border-bottom: 3px solid #fff; 9 | } 10 | .gallery-item img { 11 | transition: all ease-in-out 0.4s; 12 | } 13 | .gallery-item:hover img { 14 | transform: scale(1.1); 15 | } 16 | .galelry-lightbox { 17 | max-height: 200px; 18 | width: 100%; 19 | overflow: hidden; 20 | cursor: pointer; 21 | } 22 | .galelry-lightbox img{ 23 | object-fit:contain; 24 | object-position: top; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Booking/DoctorBooking/index.css: -------------------------------------------------------------------------------- 1 | .booking-doc-img img { 2 | height: 70px; 3 | width: 70px; 4 | object-fit: cover; 5 | object-position: top; 6 | border: 6px solid #efefef; 7 | border-radius: 50%; 8 | margin-top: 10px; 9 | } 10 | .booking-doc-img a { 11 | font-weight: 600; 12 | color: #1977cc; 13 | } 14 | 15 | .booking-doc-img { 16 | background: #f8f9fa; 17 | display: flex; 18 | align-items: center; 19 | gap: 10px; 20 | } 21 | .date-card{ 22 | background: #f8f9fa; 23 | height: 50vh; 24 | overflow: hidden; 25 | overflow-y: scroll; 26 | } -------------------------------------------------------------------------------- /src/components/Contact/index.css: -------------------------------------------------------------------------------- 1 | .info .icon { 2 | margin-top: 2px; 3 | font-size: 22px; 4 | color: #1977cc; 5 | padding: 5px; 6 | background: #d6e9fa; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | border-radius: 50px; 11 | transition: all 0.3s ease-in-out; 12 | } 13 | .contact .info h4 { 14 | font-size: 20px; 15 | font-weight: 600; 16 | margin-bottom: 5px; 17 | color: #2c4964; 18 | } 19 | .contact .info p { 20 | font-size: 14px; 21 | color: #4b7dab; 22 | } 23 | .contact .appointment-btn { 24 | border: 0 !important; 25 | } 26 | -------------------------------------------------------------------------------- /src/redux/tag-types.js: -------------------------------------------------------------------------------- 1 | export const tagTypes = { 2 | favourite: 'favourite', 3 | prescription: 'prescription', 4 | patient: 'patient', 5 | appointments: 'appointments', 6 | doctor: 'doctor', 7 | reviews: 'reviews', 8 | timeSlot: 'timeSlot', 9 | blogs: 'blogs', 10 | medicine: 'medicine', 11 | } 12 | 13 | export const tagTypeList = [ 14 | tagTypes.favourite, 15 | tagTypes.prescription, 16 | tagTypes.patient, 17 | tagTypes.appointments, 18 | tagTypes.doctor, 19 | tagTypes.reviews, 20 | tagTypes.timeSlot, 21 | tagTypes.blogs, 22 | tagTypes.medicine, 23 | ] -------------------------------------------------------------------------------- /src/components/Home/AvailableFeatures/index.css: -------------------------------------------------------------------------------- 1 | .section-features { 2 | background-color: #fff; 3 | padding: 80px 0; 4 | margin: 7rem 0; 5 | } 6 | .features-img img { 7 | text-align: center; 8 | margin: 0 auto; 9 | } 10 | .section-features { 11 | background-color: #fff; 12 | padding: 80px 0; 13 | } 14 | .feature-item { 15 | transition: 0.6s ease-in; 16 | } 17 | .feature-item img { 18 | border-radius: 100%; 19 | box-shadow: 1px 6px 14px rgba(0,0,0,0.2); 20 | height: 115px; 21 | object-fit: cover; 22 | width: 115px; 23 | } 24 | .feature-item p { 25 | font-weight: 500; 26 | margin: 20px 0 0; 27 | } -------------------------------------------------------------------------------- /src/components/Doctor/ProfileSetting/ProfileSetting.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DashboardLayout from '../DashboardLayout/DashboardLayout'; 3 | import PatientProfileSetting from './PatientProfileSetting'; 4 | import DoctorProfileSetting from './DoctorProfileSetting'; 5 | import useAuthCheck from '../../../redux/hooks/useAuthCheck'; 6 | 7 | const ProfileSetting = () => { 8 | const { role } = useAuthCheck(); 9 | return ( 10 | 11 | {role === 'doctor' ? : } 12 | 13 | ) 14 | } 15 | export default ProfileSetting; -------------------------------------------------------------------------------- /api/src/app/modules/contact/contact.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import catchAsync from "../../../shared/catchAsync"; 3 | import sendResponse from "../../../shared/sendResponse"; 4 | import { ContactService } from "./contact.service"; 5 | 6 | const ContactUs = catchAsync(async (req: Request, res: Response) => { 7 | const result = await ContactService.contactUs(req.body); 8 | sendResponse(res, { 9 | statusCode: 200, 10 | message: 'Successfully Email Send !!', 11 | success: true, 12 | data: result 13 | }) 14 | }) 15 | 16 | export const ContactController = { 17 | ContactUs 18 | } -------------------------------------------------------------------------------- /src/components/Doctor/DashboardLayout/DashboardLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DashboardSidebar from '../../UI/DashboardSidebar'; 3 | import Header from '../../Shared/Header/Header'; 4 | const DashboardLayout = ({ children }) => { 5 | return ( 6 | <> 7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 | {children} 15 |
16 |
17 |
18 | 19 | ) 20 | } 21 | 22 | export default DashboardLayout -------------------------------------------------------------------------------- /api/src/errors/handleZodError.ts: -------------------------------------------------------------------------------- 1 | import { ZodError, ZodIssue } from 'zod'; 2 | import { IGenericErrorResponse } from '../interfaces/common'; 3 | import { IGenericErrorMessage } from '../interfaces/error'; 4 | 5 | const handleZodError = (error: ZodError): IGenericErrorResponse => { 6 | const errors: IGenericErrorMessage[] = error.issues.map((issue: ZodIssue) => { 7 | return { 8 | path: issue?.path[issue.path.length - 1], 9 | message: issue?.message, 10 | }; 11 | }); 12 | 13 | const statusCode = 400; 14 | 15 | return { 16 | statusCode, 17 | message: 'Validation Error', 18 | errorMessages: errors, 19 | }; 20 | }; 21 | 22 | export default handleZodError; 23 | -------------------------------------------------------------------------------- /api/src/shared/sendResponse.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express" 2 | 3 | type IApiResponse = { 4 | statusCode: number; 5 | success: boolean; 6 | message?: string; 7 | meta?: { 8 | page: number; 9 | limit: number; 10 | total: number; 11 | }; 12 | data?: T | null 13 | } 14 | 15 | const sendResponse = (res: Response, data: IApiResponse): void => { 16 | const response = { 17 | statusCode: data.statusCode, 18 | success: data.success, 19 | message: data.message || null, 20 | meta: data.meta || null || undefined, 21 | data: data.data || null || undefined, 22 | } 23 | res.status(data.statusCode).json(response); 24 | } 25 | export default sendResponse; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Doctor On Call. 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/service/auth.service.js: -------------------------------------------------------------------------------- 1 | import {authKey} from '../constant/storageKey'; 2 | import {decodeToken} from '../utils/jwt'; 3 | import { getFromLocalStorage, setLocalStorage } from '../utils/local-storage'; 4 | export const setUserInfo = ({ accessToken }) => { 5 | return setLocalStorage(authKey, accessToken); 6 | } 7 | 8 | export const getUserInfo = () => { 9 | const authToken = getFromLocalStorage(authKey); 10 | if (authToken) { 11 | const decodedToken = decodeToken(authToken); 12 | return decodedToken 13 | } else { 14 | return null 15 | } 16 | } 17 | export const isLoggedIn = () => { 18 | const authToken = getFromLocalStorage(authKey); 19 | return !!authToken; 20 | } 21 | export const loggedOut = () => { 22 | return localStorage.removeItem(authKey) 23 | } -------------------------------------------------------------------------------- /api/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 12, 5 | "sourceType": "module" 6 | }, 7 | "plugins": ["@typescript-eslint"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "prettier" 12 | ], 13 | "rules": { 14 | "no-unused-vars": "error", 15 | "prefer-const": "error", 16 | "no-unused-expressions": "error", 17 | "no-undef": "error", 18 | "no-console": "warn", 19 | "@typescript-eslint/consistent-type-definitions": ["error", "type"] 20 | }, 21 | "env": { 22 | "browser": true, 23 | "es2021": true, 24 | "node":true 25 | }, 26 | "globals": { 27 | "process":"readonly" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /api/src/interfaces/file.ts: -------------------------------------------------------------------------------- 1 | export type IUpload = { 2 | fieldname: string, 3 | originalname: string, 4 | encoding: string, 5 | mimetype: string, 6 | destination: string, 7 | filename: string, 8 | path: string, 9 | size: number 10 | } 11 | export type ICloudinaryResponse = { 12 | asset_id: string, 13 | public_id: string, 14 | version: number, 15 | version_id: string, 16 | signature: string, 17 | width: number, 18 | height: number, 19 | format: string, 20 | resource_type: string, 21 | created_at: string, 22 | tags: string[], 23 | bytes: number, 24 | type: string, 25 | etag: string, 26 | placeholder: boolean, 27 | url: string, 28 | secure_url: string, 29 | folder: string, 30 | original_filename: string, 31 | api_key: string 32 | } -------------------------------------------------------------------------------- /api/src/errors/handleValidationError.ts: -------------------------------------------------------------------------------- 1 | // import mongoose from 'mongoose'; 2 | // import { IGenericErrorMessage } from '../interfaces/error'; 3 | // import { IGenericErrorResponse } from '../interfaces/common'; 4 | 5 | // const handleValidationError = ( 6 | // error: mongoose.Error.ValidationError 7 | // ): IGenericErrorResponse => { 8 | // const errors: IGenericErrorMessage[] = Object.values(error.errors).map( 9 | // (el: mongoose.Error.ValidatorError | mongoose.Error.CastError) => { 10 | // return { 11 | // path: el?.path, 12 | // message: el?.message, 13 | // }; 14 | // } 15 | // ); 16 | // const statusCode = 400; 17 | // return { 18 | // statusCode, 19 | // message: 'Validation Error', 20 | // errorMessages: errors, 21 | // }; 22 | // }; 23 | 24 | // export default handleValidationError; 25 | -------------------------------------------------------------------------------- /api/src/shared/paginationHelper.ts: -------------------------------------------------------------------------------- 1 | export type IOption = { 2 | page?: number | undefined; 3 | limit?: number | undefined; 4 | sortBy?: string; 5 | sortOrder?: 'asc' | 'dsc'; 6 | } 7 | 8 | type IOptionResult = { 9 | page?: number | undefined; 10 | limit?: number | undefined; 11 | sortBy?: string; 12 | sortOrder?: any; 13 | skip?: number | undefined; 14 | } 15 | 16 | const calculatePagination = (options: IOption): IOptionResult => { 17 | const page = Number(options.page || 1); 18 | const limit = Number(options.limit || 10); 19 | const skip = (page - 1) * limit; 20 | 21 | const sortBy = options.sortBy || 'createdAt'; 22 | const sortOrder = options.sortOrder || 'desc'; 23 | 24 | return { 25 | page, limit, skip, sortBy, sortOrder 26 | } 27 | } 28 | 29 | export default calculatePagination; -------------------------------------------------------------------------------- /api/src/app/modules/reviews/reviews.route.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ReviewController } from './reviews.controller'; 3 | import { auth } from '../../middlewares/auth'; 4 | import { AuthUser } from '../../../enums'; 5 | 6 | const router = express.Router(); 7 | router.get('/doctor-review/:id', auth(AuthUser.DOCTOR, AuthUser.PATIENT), ReviewController.getDoctorReviews); 8 | router.get('/:id', ReviewController.getSingleReview); 9 | router.post('/', auth(AuthUser.PATIENT), ReviewController.creatReview); 10 | router.get('/', ReviewController.getAllReview); 11 | router.delete('/:id', auth(AuthUser.ADMIN), ReviewController.deleteReview); 12 | router.patch('/:id/reply', auth(AuthUser.DOCTOR), ReviewController.replyReviewByDoctor); 13 | router.patch('/:id', auth(AuthUser.ADMIN), ReviewController.updateReview); 14 | 15 | export const ReviewRouter = router; -------------------------------------------------------------------------------- /src/helpers/axios/axiosBaseQuery.js: -------------------------------------------------------------------------------- 1 | import { instance } from './axiosInstance'; 2 | 3 | export const axiosBaseQuery = 4 | ({ baseUrl } = { baseUrl: '' }) => 5 | async ({ url, method, data, params, headers }) => { 6 | try { 7 | const result = await instance({ 8 | url: baseUrl + url, 9 | method, 10 | data, 11 | params, 12 | headers: headers 13 | }) 14 | return result 15 | } catch (axiosError) { 16 | const err = axiosError 17 | return { 18 | error: { 19 | status: err.response?.status, 20 | data: err.response?.data || err.message, 21 | }, 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/helpers/axios/axiosInstance.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getFromLocalStorage } from "../../utils/local-storage"; 3 | 4 | export const instance = axios.create(); 5 | 6 | instance.defaults.headers.post['Accept'] = 'application/json'; 7 | instance.defaults.timeout = 60000; 8 | 9 | instance.interceptors.request.use(function (config) { 10 | const accessToken = getFromLocalStorage('accessToken'); 11 | if (accessToken) { 12 | config.headers.Authorization = accessToken; 13 | } 14 | return config; 15 | }, function (error) { 16 | return Promise.reject(error); 17 | }); 18 | 19 | instance.interceptors.response.use(function (response) { 20 | const responseObj = { 21 | data: response?.data?.data, 22 | meta: response?.data?.meta 23 | } 24 | return responseObj; 25 | }, function (error) { 26 | return Promise.reject(error); 27 | }); -------------------------------------------------------------------------------- /api/src/app/modules/patient/patient.route.ts: -------------------------------------------------------------------------------- 1 | import express, { NextFunction, Request, Response } from 'express'; 2 | import { PatientController } from './patient.controller'; 3 | import { auth } from '../../middlewares/auth'; 4 | import { AuthUser } from '../../../enums'; 5 | import { CloudinaryHelper } from '../../../helpers/uploadHelper'; 6 | 7 | const router = express.Router(); 8 | 9 | router.get('/', PatientController.getAllPatients); 10 | router.post('/', PatientController.createPatient); 11 | router.get('/:id', PatientController.getPatient); 12 | router.delete('/:id', PatientController.deletePatient); 13 | router.patch('/:id', 14 | CloudinaryHelper.upload.single('file'), 15 | auth(AuthUser.PATIENT), 16 | (req: Request, res: Response, next: NextFunction) => { 17 | return PatientController.updatePatient(req, res, next) 18 | } 19 | ); 20 | 21 | export const PatientRouter = router; -------------------------------------------------------------------------------- /api/src/app/modules/doctor/doctor.route.ts: -------------------------------------------------------------------------------- 1 | import express, { NextFunction, Request, Response } from 'express'; 2 | import { DoctorController } from './doctor.controller'; 3 | import { AuthUser } from '../../../enums'; 4 | import { auth } from '../../middlewares/auth'; 5 | import { CloudinaryHelper } from '../../../helpers/uploadHelper'; 6 | 7 | const router = express.Router(); 8 | 9 | router.get('/', DoctorController.getAllDoctors); 10 | router.post('/', DoctorController.createDoctor); 11 | router.get('/:id', DoctorController.getDoctor); 12 | router.delete('/:id', auth(AuthUser.DOCTOR), DoctorController.deleteDoctor); 13 | router.patch('/:id', 14 | CloudinaryHelper.upload.single('file'), 15 | auth(AuthUser.DOCTOR), 16 | (req: Request, res: Response, next: NextFunction) => { 17 | return DoctorController.updateDoctor(req, res, next); 18 | } 19 | ); 20 | 21 | export const DoctorRouter = router; -------------------------------------------------------------------------------- /src/components/UI/form/SelectForm.jsx: -------------------------------------------------------------------------------- 1 | import { Select } from 'antd' 2 | import React from 'react' 3 | 4 | const SelectForm = ({ showSearch, options, setSelectData, mode=false, defaultValue=undefined }) => { 5 | const onChange = (value) => { 6 | setSelectData(value) 7 | }; 8 | const filterOption = (input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()); 9 | return ( 10 | 23 | 24 |
25 | Allowed JPG, GIF or PNG. Max size of 2MB 26 |
27 | 28 | ); 29 | }; 30 | export default ImageUpload; -------------------------------------------------------------------------------- /src/components/Home/HeroSection/HeroSection.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.css'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const HeroSection = () => { 6 | return ( 7 |
8 |
9 |
10 | TOTAL HEALTH CARE SOLUTION 11 |

Your Most Trusted
Health Partner

12 | A repudiandae ipsam labore ipsa voluptatum quidem quae laudantium quisquam aperiam maiores sunt fugit, deserunt rem suscipit placeat. 13 |
14 |
15 | Get Started 16 | Track Appointment 17 |
18 |
19 |
20 | ) 21 | } 22 | export default HeroSection; -------------------------------------------------------------------------------- /api/src/app/modules/blog/blog.route.ts: -------------------------------------------------------------------------------- 1 | import express, { NextFunction, Request, Response } from 'express'; 2 | import { auth } from '../../middlewares/auth'; 3 | import { AuthUser } from '../../../enums'; 4 | import { BlogController } from './blog.controller'; 5 | import { CloudinaryHelper } from '../../../helpers/uploadHelper'; 6 | 7 | const router = express.Router(); 8 | router.post('/', 9 | auth(AuthUser.DOCTOR), 10 | CloudinaryHelper.upload.single('file'), 11 | (req: Request, res: Response, next: NextFunction) => { 12 | return BlogController.createBlog(req, res, next); 13 | }); 14 | router.get('/', BlogController.getAllBlogs); 15 | router.get('/:id', BlogController.getBlog); 16 | router.delete('/:id', auth(AuthUser.DOCTOR, AuthUser.SUPER_ADMIN), BlogController.deleteBlog); 17 | router.patch('/:id', 18 | CloudinaryHelper.upload.single('file'), 19 | auth(AuthUser.DOCTOR), 20 | (req: Request, res: Response, next: NextFunction) => { 21 | return BlogController.updateBlog(req, res, next); 22 | } 23 | ) 24 | 25 | export const BlogRoutes = router; -------------------------------------------------------------------------------- /api/src/app/middlewares/auth.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import ApiError from "../../errors/apiError"; 3 | import { JwtHelper } from "../../helpers/jwtHelper"; 4 | import config from "../../config"; 5 | import { Secret } from "jsonwebtoken"; 6 | 7 | export const auth = (...rules: string[]) => async (req: Request, res: Response, next: NextFunction) => { 8 | try { 9 | const token = req.headers.authorization; 10 | if (!token) { 11 | throw new ApiError(404, "Token is not Found !!") 12 | } 13 | let verifiedUser; 14 | try { 15 | verifiedUser = await JwtHelper.verifyToken(token, config.jwt.secret as Secret); 16 | } catch (error) { 17 | throw new ApiError(403, "User is not Found !!") 18 | } 19 | req.user = verifiedUser; 20 | 21 | if (rules.length && !rules.includes(verifiedUser.role)) { 22 | throw new ApiError(403, "You are not Authorised !!") 23 | } 24 | next(); 25 | } catch (error) { 26 | next(error) 27 | } 28 | } -------------------------------------------------------------------------------- /api/src/helpers/uploadHelper.ts: -------------------------------------------------------------------------------- 1 | import { v2 as cloudinary } from 'cloudinary' 2 | import config from '../config'; 3 | import multer from 'multer'; 4 | import { ICloudinaryResponse } from '../interfaces/file'; 5 | 6 | cloudinary.config({ 7 | cloud_name: config.cloudinary.name, 8 | api_key: config.cloudinary.key, 9 | api_secret: config.cloudinary.secret 10 | }); 11 | 12 | const upload = multer({storage: multer.memoryStorage()}); 13 | 14 | const uploadFile = async (file: any): Promise => { 15 | if (!file || !file.buffer) { 16 | throw new Error('File not Provided or Invalid'); 17 | } 18 | return new Promise((resolve, reject) => { 19 | cloudinary.uploader.upload_stream( 20 | { resource_type: 'auto', folder: 'doctorOnCall' }, 21 | (error: any, result: any) => { 22 | if (error) { 23 | reject(error) 24 | } else { 25 | resolve(result) 26 | } 27 | } 28 | ).end(file.buffer); 29 | }) 30 | }; 31 | 32 | export const CloudinaryHelper = { 33 | uploadFile, 34 | upload 35 | } -------------------------------------------------------------------------------- /api/src/app/modules/prescription/prescription.route.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { auth } from '../../middlewares/auth'; 3 | import { AuthUser } from '../../../enums'; 4 | import { PrescriptionController } from './prescription.controller'; 5 | 6 | const router = express.Router(); 7 | 8 | router.get('/doctor/prescription', auth(AuthUser.DOCTOR), PrescriptionController.getDoctorPrescriptionById); 9 | router.get('/patient/prescription', auth(AuthUser.PATIENT), PrescriptionController.getPatientPrescriptionById); 10 | 11 | router.get('/:id', PrescriptionController.getPrescriptionById); 12 | router.get('/', PrescriptionController.getAllPrescriptions); 13 | 14 | router.post('/create', auth(AuthUser.DOCTOR, AuthUser.ADMIN), PrescriptionController.createPrescription); 15 | 16 | router.delete('/:', auth(AuthUser.DOCTOR, AuthUser.ADMIN), PrescriptionController.deletePrescription); 17 | router.patch('/', auth(AuthUser.DOCTOR, AuthUser.ADMIN), PrescriptionController.updatePrescription); 18 | router.patch('/update-prescription-appointment', auth(AuthUser.DOCTOR, AuthUser.ADMIN), PrescriptionController.updatePrescriptionAndAppointment); 19 | 20 | export const PrescriptionRouter = router; -------------------------------------------------------------------------------- /src/components/Admin/AdminLayout/AdminLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminSidebar from '../../UI/AdminSidebar' 3 | import AdminHeader from '../../UI/AdminHeader' 4 | import './AdminLayout.css'; 5 | const AdminLayout = ({ children }) => { 6 | return ( 7 |
8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |

Welcome Admin!

16 |
    17 |
  • Dashboard
  • 18 |
19 |
20 |
21 |
22 | {children} 23 |
24 |
25 |
26 | ) 27 | } 28 | 29 | export default AdminLayout -------------------------------------------------------------------------------- /src/redux/api/favouriteApi.js: -------------------------------------------------------------------------------- 1 | import { tagTypes } from "../tag-types"; 2 | import { baseApi } from "./baseApi" 3 | 4 | const FAVOURITE_URL = '/favourite' 5 | 6 | export const favouriteApi = baseApi.injectEndpoints({ 7 | endpoints: (build) => ({ 8 | addFavourite: build.mutation({ 9 | query: (data) => ({ 10 | url: `${FAVOURITE_URL}/add`, 11 | method: 'POST', 12 | data: data 13 | }), 14 | invalidatesTags: [tagTypes.favourite] 15 | }), 16 | removeFavourite: build.mutation({ 17 | query: (data) => ({ 18 | url: `${FAVOURITE_URL}/remove`, 19 | method: 'POST', 20 | data: data 21 | }), 22 | invalidatesTags: [tagTypes.favourite] 23 | }), 24 | getFavourite: build.query({ 25 | query: () => ({ 26 | url: `${FAVOURITE_URL}/`, 27 | method: 'GET' 28 | }), 29 | providesTags: [tagTypes.favourite] 30 | }), 31 | }) 32 | }) 33 | 34 | export const { useGetFavouriteQuery, useAddFavouriteMutation, useRemoveFavouriteMutation } = favouriteApi; -------------------------------------------------------------------------------- /src/components/Home/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Blog from '../Blog/Blog'; 3 | import Footer from '../../Shared/Footer/Footer'; 4 | import Testimonial from '../Testimonial/Testimonial'; 5 | import ClinicAndSpecialities from '../ClinicAndSpecialities/ClinicAndSpecialities'; 6 | import BookDoctor from '../BookOurDoctor/BookDoctor'; 7 | import Availabe from '../AvailableFeatures/Available'; 8 | import HeroSection from '../HeroSection/HeroSection'; 9 | import InfoPage from '../InfoPage/InfoPage'; 10 | import Header from '../../Shared/Header/Header'; 11 | import Service from '../Services/Service'; 12 | import Gallery from '../Gallery/Gallery'; 13 | import OurDoctors from '../OurDoctor/OurDoctors'; 14 | 15 | const Home = () => { 16 | return ( 17 | <> 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |