├── .env
├── .eslintrc.json
├── .gitignore
├── PH Health Care Landing Page.jpg
├── PH Health Care Patient Register Page.png
├── PH Health Care User Login Page.png
├── README.md
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── next.svg
└── vercel.svg
├── src
├── app
│ ├── (withCommonLayout)
│ │ ├── doctors
│ │ │ ├── [id]
│ │ │ │ └── page.tsx
│ │ │ ├── components
│ │ │ │ └── DoctorScheduleSlots.tsx
│ │ │ └── page.tsx
│ │ ├── forgot-password
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── payment
│ │ │ └── page.tsx
│ │ ├── reset-password
│ │ │ └── page.tsx
│ │ └── video
│ │ │ └── page.tsx
│ ├── (withDashboardLayout)
│ │ ├── dashboard
│ │ │ ├── admin
│ │ │ │ ├── appointments
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── doctors
│ │ │ │ │ ├── components
│ │ │ │ │ │ └── DoctorModal.tsx
│ │ │ │ │ ├── edit
│ │ │ │ │ │ └── [doctorId]
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── schedules
│ │ │ │ │ ├── components
│ │ │ │ │ │ └── ScheduleModal.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── specialties
│ │ │ │ │ ├── components
│ │ │ │ │ └── SpecialtyModal.tsx
│ │ │ │ │ └── page.tsx
│ │ │ ├── change-password
│ │ │ │ └── page.tsx
│ │ │ ├── doctor
│ │ │ │ ├── appointment
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── profile
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── DoctorInformations.tsx
│ │ │ │ │ │ ├── MultipleSelectChip.tsx
│ │ │ │ │ │ └── ProfileUpdateModal.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── schedules
│ │ │ │ │ ├── components
│ │ │ │ │ ├── DoctorScheduleModal.tsx
│ │ │ │ │ └── MultipleSelectFieldChip.tsx
│ │ │ │ │ └── page.tsx
│ │ │ ├── page.tsx
│ │ │ ├── patient
│ │ │ │ ├── appointments
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ └── super_admin
│ │ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── login
│ │ └── page.tsx
│ ├── not-found.tsx
│ └── register
│ │ └── page.tsx
├── assets
│ ├── choose-us.png
│ ├── doctor-image1.png
│ ├── doctor-image2.png
│ ├── how-it-works-img.png
│ ├── icons
│ │ ├── appointment-icon.png
│ │ ├── charity-icon.png
│ │ ├── doctor-icon.png
│ │ └── search-icon.png
│ ├── images
│ │ ├── Stetoscope.png
│ │ ├── doctor1.png
│ │ ├── doctor2.png
│ │ ├── doctor3.png
│ │ └── familyOnBeach.png
│ ├── index.ts
│ ├── landing_page
│ │ ├── atm-card.png
│ │ ├── badge.png
│ │ ├── calender.png
│ │ ├── diagnostic.png
│ │ ├── doctors.png
│ │ ├── facebook.png
│ │ ├── folder.png
│ │ ├── instagram.png
│ │ ├── linkedin.png
│ │ ├── location.png
│ │ ├── search.jpg
│ │ ├── search.png
│ │ ├── twitter.png
│ │ ├── user.png
│ │ └── video_call.png
│ └── svgs
│ │ ├── Cardiology.svg
│ │ ├── Dentist.svg
│ │ ├── Neurology.svg
│ │ ├── Ophthalmology.svg
│ │ ├── Orthopedic.svg
│ │ ├── Urology.svg
│ │ ├── arrow.svg
│ │ ├── award-icon.svg
│ │ ├── brain.svg
│ │ ├── calender.svg
│ │ ├── call-icon.svg
│ │ ├── care-icon.svg
│ │ ├── dna.svg
│ │ ├── doctorSearch.svg
│ │ ├── grid.svg
│ │ ├── kidney.svg
│ │ ├── location.svg
│ │ ├── logo.svg
│ │ ├── medical-equipment-icon.svg
│ │ ├── profile.svg
│ │ ├── schedule.svg
│ │ ├── search.svg
│ │ └── solution.svg
├── components
│ ├── Dashboard
│ │ ├── AccountMenu
│ │ │ └── AccountMenu.tsx
│ │ ├── DashboardDrawer
│ │ │ └── DashboardDrawer.tsx
│ │ └── SideBar
│ │ │ ├── SideBar.tsx
│ │ │ └── SidebarItem.tsx
│ ├── Forms
│ │ ├── AutoFileUploader.tsx
│ │ ├── PHDatePicker.tsx
│ │ ├── PHFileUploader.tsx
│ │ ├── PHForm.tsx
│ │ ├── PHInput.tsx
│ │ ├── PHSelectField.tsx
│ │ └── PHTimePicker.tsx
│ ├── Shared
│ │ ├── Footer
│ │ │ └── Footer.tsx
│ │ ├── Navbar
│ │ │ └── Navbar.tsx
│ │ ├── PHModal
│ │ │ ├── PHAlert.tsx
│ │ │ ├── PHFullScreenModal.tsx
│ │ │ └── PHModal.tsx
│ │ └── PhChip
│ │ │ └── PhChips.tsx
│ └── UI
│ │ ├── AuthButton
│ │ └── AuthButton.tsx
│ │ ├── Doctor
│ │ ├── DashedLine.tsx
│ │ ├── DoctorCard.tsx
│ │ └── ScrollCategory.tsx
│ │ ├── HomePage
│ │ ├── HeroSection
│ │ │ └── HeroSection.tsx
│ │ ├── HowItWorks
│ │ │ └── HowItWorks.tsx
│ │ ├── Specialist
│ │ │ └── Specialist.tsx
│ │ ├── Stats
│ │ │ └── Stats.tsx
│ │ ├── TopRatedDoctors
│ │ │ └── TopRatedDoctors.tsx
│ │ └── WhyUs
│ │ │ └── WhyUs.tsx
│ │ └── VideoCall
│ │ └── VideoCall.tsx
├── contants
│ ├── authkey.ts
│ └── role.ts
├── helpers
│ └── axios
│ │ ├── axiosBaseQuery.ts
│ │ └── axiosInstance.ts
├── hooks
│ └── useUserInfo.tsx
├── lib
│ ├── Providers
│ │ └── Providers.tsx
│ └── theme
│ │ └── theme.ts
├── middleware.ts
├── redux
│ ├── api
│ │ ├── appointmentApi.ts
│ │ ├── authApi.ts
│ │ ├── baseApi.ts
│ │ ├── doctorApi.ts
│ │ ├── doctorScheduleApi.ts
│ │ ├── myProfile.ts
│ │ ├── paymentApi.ts
│ │ ├── scheduleApi.ts
│ │ ├── specialtiesApi.ts
│ │ └── userApi.ts
│ ├── hooks.ts
│ ├── rootReducer.ts
│ ├── store.ts
│ └── tag-types.ts
├── services
│ ├── actions
│ │ ├── deleteCookies.ts
│ │ ├── logoutUser.ts
│ │ ├── registerPatient.ts
│ │ ├── setAccessToken.ts
│ │ └── userLogin.ts
│ └── auth.services.ts
├── types
│ ├── common.ts
│ ├── doctor
│ │ └── index.ts
│ ├── doctorSchedules
│ │ └── index.tsx
│ ├── index.ts
│ ├── schedule
│ │ └── index.ts
│ └── specialties
│ │ └── specialties.ts
└── utils
│ ├── dateFormatter.ts
│ ├── drawerItems.ts
│ ├── jwt.ts
│ ├── local-storage.ts
│ ├── modifyPayload.ts
│ └── timeFormatter.ts
├── tailwind.config.ts
└── tsconfig.json
/.env:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_BACKEND_API_URL=http://localhost:5000/api/v1
2 | NEXT_PUBLIC_VIDEO_CALL_APP_ID="dc0163fa709945c29446cc895354247e"
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.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 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/PH Health Care Landing Page.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/PH Health Care Landing Page.jpg
--------------------------------------------------------------------------------
/PH Health Care Patient Register Page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/PH Health Care Patient Register Page.png
--------------------------------------------------------------------------------
/PH Health Care User Login Page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/PH Health Care User Login Page.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | protocol: "https",
7 | hostname: "**",
8 | },
9 | ],
10 | },
11 | };
12 |
13 | module.exports = nextConfig;
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ph-healthcare-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@emotion/cache": "^11.11.0",
13 | "@emotion/react": "^11.11.4",
14 | "@emotion/styled": "^11.11.0",
15 | "@hookform/resolvers": "^3.3.4",
16 | "@mui/icons-material": "^5.15.13",
17 | "@mui/lab": "^5.0.0-alpha.170",
18 | "@mui/material": "^5.15.13",
19 | "@mui/material-nextjs": "^5.15.11",
20 | "@mui/x-data-grid": "^7.1.0",
21 | "@mui/x-date-pickers": "^7.1.0",
22 | "@reduxjs/toolkit": "^2.2.2",
23 | "agora-react-uikit": "^1.2.0",
24 | "axios": "^1.6.8",
25 | "dayjs": "^1.11.10",
26 | "jwt-decode": "^4.0.0",
27 | "next": "14.0.3",
28 | "react": "^18",
29 | "react-dom": "^18",
30 | "react-hook-form": "^7.51.1",
31 | "react-redux": "^9.1.0",
32 | "sonner": "^1.4.41",
33 | "zod": "^3.22.4"
34 | },
35 | "devDependencies": {
36 | "@types/node": "^20",
37 | "@types/react": "^18",
38 | "@types/react-dom": "^18",
39 | "autoprefixer": "^10.0.1",
40 | "eslint": "^8",
41 | "eslint-config-next": "14.0.3",
42 | "postcss": "^8",
43 | "tailwindcss": "^3.3.0",
44 | "typescript": "^5"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/(withCommonLayout)/doctors/page.tsx:
--------------------------------------------------------------------------------
1 | import DashedLine from '@/components/UI/Doctor/DashedLine';
2 | import DoctorCard from '@/components/UI/Doctor/DoctorCard';
3 | import ScrollCategory from '@/components/UI/Doctor/ScrollCategory';
4 | import { Doctor } from '@/types/doctor';
5 | import { Box, Container } from '@mui/material';
6 | import React from 'react';
7 |
8 | interface PropType {
9 | searchParams: { specialties: string };
10 | }
11 |
12 | const Doctors = async ({ searchParams }: PropType) => {
13 | let res;
14 |
15 | if (searchParams.specialties) {
16 | res = await fetch(
17 | `http://localhost:5000/api/v1/doctor?specialties=${searchParams.specialties}`
18 | );
19 | } else {
20 | res = await fetch('http://localhost:5000/api/v1/doctor');
21 | }
22 |
23 | const { data } = await res.json();
24 |
25 | // console.log(data);
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 | {data?.map((doctor: Doctor, index: number) => (
35 |
36 |
37 |
38 | {index === data.length - 1 ? null : }
39 |
40 | ))}
41 |
42 | {data.length === 0 && (
43 | No Doctor Found With This Specialty
44 | )}
45 |
46 |
47 | );
48 | };
49 |
50 | export default Doctors;
51 |
--------------------------------------------------------------------------------
/src/app/(withCommonLayout)/forgot-password/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { zodResolver } from '@hookform/resolvers/zod';
4 | import { Alert, Box, Button, Grid, Stack, Typography } from '@mui/material';
5 | import { z } from 'zod';
6 | import KeyIcon from '@mui/icons-material/Key';
7 | import PHForm from '@/components/Forms/PHForm';
8 | import PHInput from '@/components/Forms/PHInput';
9 | import { FieldValues } from 'react-hook-form';
10 | import { useForgotPasswordMutation } from '@/redux/api/authApi';
11 | import { toast } from 'sonner';
12 | import CheckIcon from '@mui/icons-material/Check';
13 |
14 | const validationSchema = z.object({
15 | email: z.string().email('Please enter a valid email address!'),
16 | });
17 |
18 | const ForgotPassword = () => {
19 | const [forgotPassword, { isSuccess }] = useForgotPasswordMutation();
20 |
21 | const onSubmit = async (values: FieldValues) => {
22 | try {
23 | const res = await forgotPassword(values);
24 |
25 | if ('data' in res && res.data.status === 200) {
26 | toast.success('Check Your Email for Reset Link');
27 | } else {
28 | throw new Error('Something Went Wrong, Try Again');
29 | }
30 | } catch (error) {
31 | console.log(error);
32 | }
33 | };
34 |
35 | return (
36 |
43 |
53 |
54 |
62 |
63 |
64 |
65 | Forgot password
66 |
67 |
68 |
69 | {isSuccess && (
70 |
71 | }
73 | severity='success'
74 | >
75 | An Email with reset password link was sent to your email
76 |
77 |
78 | )}
79 |
80 | {!isSuccess && (
81 |
86 |
87 |
88 |
95 |
96 |
97 |
98 |
101 |
102 | )}
103 |
104 |
105 | );
106 | };
107 |
108 | export default ForgotPassword;
109 |
--------------------------------------------------------------------------------
/src/app/(withCommonLayout)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Footer from "@/components/Shared/Footer/Footer";
2 | import Navbar from "@/components/Shared/Navbar/Navbar";
3 | import { Box } from "@mui/material";
4 |
5 | const CommonLayout = ({ children }: { children: React.ReactNode }) => {
6 | return (
7 | <>
8 |
9 | {children}
10 |
11 | >
12 | );
13 | };
14 |
15 | export default CommonLayout;
16 |
--------------------------------------------------------------------------------
/src/app/(withCommonLayout)/page.tsx:
--------------------------------------------------------------------------------
1 | import HeroSection from "@/components/UI/HomePage/HeroSection/HeroSection";
2 | import HowItWorks from "@/components/UI/HomePage/HowItWorks/HowItWorks";
3 | import Specialist from "@/components/UI/HomePage/Specialist/Specialist";
4 | import Stats from "@/components/UI/HomePage/Stats/Stats";
5 | import TopRatedDoctors from "@/components/UI/HomePage/TopRatedDoctors/TopRatedDoctors";
6 | import WhyUs from "@/components/UI/HomePage/WhyUs/WhyUs";
7 |
8 | const HomePage = () => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 |
17 | >
18 | );
19 | };
20 |
21 | export default HomePage;
22 |
--------------------------------------------------------------------------------
/src/app/(withCommonLayout)/payment/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material';
4 | import CheckCircleIcon from '@mui/icons-material/CheckCircle';
5 | import CancelIcon from '@mui/icons-material/Cancel';
6 | import ErrorIcon from '@mui/icons-material/Error';
7 | import Link from 'next/link';
8 |
9 | interface PropTypes {
10 | searchParams: { status: string };
11 | }
12 |
13 | const PaymentStatusPage = ({ searchParams }: PropTypes) => {
14 | const status = searchParams.status; // could be success, cancel, failed
15 |
16 | let icon;
17 | let title;
18 |
19 | switch (status) {
20 | case 'success':
21 | icon = ;
22 | title = 'Payment Successful';
23 | break;
24 | case 'cancel':
25 | icon = ;
26 | title = 'Payment Cancelled';
27 | break;
28 | case 'failed':
29 | icon = ;
30 | title = 'Payment Failed';
31 | break;
32 | default:
33 | icon = null;
34 | title = 'Unknown Status!';
35 | }
36 |
37 | return (
38 |
39 |
51 |
52 | {icon}
53 |
54 | {title}
55 |
56 | {status === 'success' && (
57 |
62 | )}
63 | {status !== 'success' && (
64 |
67 | )}
68 |
69 |
70 |
71 | );
72 | };
73 |
74 | export default PaymentStatusPage;
75 |
--------------------------------------------------------------------------------
/src/app/(withCommonLayout)/reset-password/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { zodResolver } from '@hookform/resolvers/zod';
4 | import { Box, Button, Grid, Stack, Typography } from '@mui/material';
5 | import { FieldValues } from 'react-hook-form';
6 | import { z } from 'zod';
7 | import KeyIcon from '@mui/icons-material/Key';
8 | import PHInput from '@/components/Forms/PHInput';
9 | import PHForm from '@/components/Forms/PHForm';
10 | import { useSearchParams } from 'next/navigation';
11 | import { authApi, useResetPasswordMutation } from '@/redux/api/authApi';
12 | import { useEffect } from 'react';
13 | import { authKey } from '@/contants/authkey';
14 | import { toast } from 'sonner';
15 | import { useRouter } from 'next/navigation';
16 | import { deleteCookies } from '@/services/actions/deleteCookies';
17 |
18 | const validationSchema = z.object({
19 | newPassword: z.string().min(6, 'Must be at least 6 characters long'),
20 | });
21 | const ResetPassword = () => {
22 | const searchParams = useSearchParams();
23 | const id = searchParams.get('id');
24 | const token = searchParams.get('token');
25 | console.log({ id, token });
26 | const router = useRouter();
27 |
28 | const [resetPassword] = useResetPasswordMutation();
29 |
30 | useEffect(() => {
31 | if (!token) return;
32 | localStorage.setItem(authKey, token);
33 | }, [token]);
34 |
35 | const onSubmit = async (values: FieldValues) => {
36 | console.log(values);
37 | const updatedData = { ...values, id };
38 |
39 | try {
40 | const res = await resetPassword(updatedData);
41 |
42 | if ('data' in res && res.data.status === 200) {
43 | toast.success('Password Reset Successful');
44 | localStorage.removeItem(authKey);
45 | deleteCookies([authKey, 'refreshToken']);
46 | router.push('/login');
47 | } else {
48 | throw new Error('Something Went Wrong, Try Again');
49 | }
50 | } catch (error) {
51 | toast.success('Something Went Wrong, Try Again');
52 | }
53 | };
54 | return (
55 |
67 |
68 |
76 |
77 |
78 |
79 | Reset password
80 |
81 |
82 |
87 |
88 |
89 |
96 |
97 |
98 |
99 |
102 |
103 |
104 | );
105 | };
106 |
107 | export default ResetPassword;
108 |
--------------------------------------------------------------------------------
/src/app/(withCommonLayout)/video/page.tsx:
--------------------------------------------------------------------------------
1 | import VideoCall from '@/components/UI/VideoCall/VideoCall';
2 | import React from 'react';
3 |
4 | const VideoCalling = ({
5 | searchParams,
6 | }: {
7 | searchParams: { videoCallingId: string };
8 | }) => {
9 | const videoCallingId = searchParams.videoCallingId;
10 |
11 | return ;
12 | };
13 |
14 | export default VideoCalling;
15 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/admin/appointments/page.tsx:
--------------------------------------------------------------------------------
1 | const AppointmentsPage = () => {
2 | return (
3 |
4 |
Appointments Page
5 |
6 | );
7 | };
8 |
9 | export default AppointmentsPage;
10 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/admin/doctors/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Box, Button, IconButton, Stack, TextField } from "@mui/material";
3 | import DoctorModal from "./components/DoctorModal";
4 | import { useState } from "react";
5 | import {
6 | useDeleteDoctorMutation,
7 | useGetAllDoctorsQuery,
8 | } from "@/redux/api/doctorApi";
9 | import { DataGrid, GridColDef } from "@mui/x-data-grid";
10 | import DeleteIcon from "@mui/icons-material/Delete";
11 | import { useDebounced } from "@/redux/hooks";
12 | import { toast } from "sonner";
13 | import EditIcon from "@mui/icons-material/Edit";
14 | import Link from "next/link";
15 |
16 | const DoctorsPage = () => {
17 | const [isModalOpen, setIsModalOpen] = useState(false);
18 | const query: Record = {};
19 | const [searchTerm, setSearchTerm] = useState("");
20 | // console.log(searchTerm);
21 |
22 | const debouncedTerm = useDebounced({
23 | searchQuery: searchTerm,
24 | delay: 600,
25 | });
26 |
27 | if (!!debouncedTerm) {
28 | query["searchTerm"] = searchTerm;
29 | }
30 |
31 | const { data, isLoading } = useGetAllDoctorsQuery({ ...query });
32 | const [deleteDoctor] = useDeleteDoctorMutation();
33 |
34 | // console.log(data);
35 | const doctors = data?.doctors;
36 | const meta = data?.meta;
37 | // console.log(doctors);
38 |
39 | const handleDelete = async (id: string) => {
40 | // console.log(id);
41 | try {
42 | const res = await deleteDoctor(id).unwrap();
43 | // console.log(res);
44 | if (res?.id) {
45 | toast.success("Doctor deleted successfully!!!");
46 | }
47 | } catch (err: any) {
48 | console.error(err.message);
49 | }
50 | };
51 |
52 | const columns: GridColDef[] = [
53 | { field: "name", headerName: "Name", flex: 1 },
54 | { field: "email", headerName: "Email", flex: 1 },
55 | { field: "contactNumber", headerName: "Contact Number", flex: 1 },
56 | { field: "gender", headerName: "Gender", flex: 1 },
57 | { field: "apointmentFee", headerName: "Appointment Fee", flex: 1 },
58 | {
59 | field: "action",
60 | headerName: "Action",
61 | flex: 1,
62 | headerAlign: "center",
63 | align: "center",
64 | renderCell: ({ row }) => {
65 | return (
66 |
67 | handleDelete(row.id)}
69 | aria-label="delete"
70 | >
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | },
81 | },
82 | ];
83 |
84 | return (
85 |
86 |
87 |
88 |
89 | setSearchTerm(e.target.value)}
91 | size="small"
92 | placeholder="search doctors"
93 | />
94 |
95 | {!isLoading ? (
96 |
97 |
98 |
99 | ) : (
100 | Loading.....
101 | )}
102 |
103 | );
104 | };
105 |
106 | export default DoctorsPage;
107 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/admin/page.tsx:
--------------------------------------------------------------------------------
1 | const AdminPage = () => {
2 | return (
3 |
4 |
Admin Dashboard
5 |
6 | );
7 | };
8 |
9 | export default AdminPage;
10 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/admin/schedules/components/ScheduleModal.tsx:
--------------------------------------------------------------------------------
1 | import PHDatePicker from "@/components/Forms/PHDatePicker";
2 | import PHForm from "@/components/Forms/PHForm";
3 | import PHTimePicker from "@/components/Forms/PHTimePicker";
4 | import PHModal from "@/components/Shared/PHModal/PHModal";
5 | import { useCreateScheduleMutation } from "@/redux/api/scheduleApi";
6 | import { dateFormatter } from "@/utils/dateFormatter";
7 | import { timeFormatter } from "@/utils/timeFormatter";
8 | import { Button, Grid } from "@mui/material";
9 | import { FieldValues } from "react-hook-form";
10 | import { toast } from "sonner";
11 |
12 | type TProps = {
13 | open: boolean;
14 | setOpen: React.Dispatch>;
15 | };
16 |
17 | const ScheduleModal = ({ open, setOpen }: TProps) => {
18 | const [createSchedule] = useCreateScheduleMutation();
19 |
20 | const handleFormSubmit = async (values: FieldValues) => {
21 | // console.log(values);
22 | values.startDate = dateFormatter(values.startDate);
23 | values.endDate = dateFormatter(values.endDate);
24 | values.startTime = timeFormatter(values.startTime);
25 | values.endTime = timeFormatter(values.endTime);
26 | // console.log(values);
27 | try {
28 | const res = await createSchedule(values).unwrap();
29 | // console.log(res);
30 | if (res?.length) {
31 | toast.success("Schedules created successfully!");
32 | setOpen(false);
33 | }
34 | } catch (err: any) {
35 | console.error(err.message);
36 | }
37 | };
38 |
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default ScheduleModal;
65 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/admin/schedules/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Box, Button, IconButton } from '@mui/material';
3 | import ScheduleModal from './components/ScheduleModal';
4 | import { useEffect, useState } from 'react';
5 | import { DataGrid, GridColDef } from '@mui/x-data-grid';
6 | import DeleteIcon from '@mui/icons-material/Delete';
7 |
8 | import { useGetAllSchedulesQuery } from '@/redux/api/scheduleApi';
9 | import dayjs from 'dayjs';
10 | import { ISchedule } from '@/types/schedule';
11 | import { dateFormatter } from '@/utils/dateFormatter';
12 |
13 | const SchedulesPage = () => {
14 | const [isModalOpen, setIsModalOpen] = useState(false);
15 | const [allSchedule, setAllSchedule] = useState([]);
16 | const { data, isLoading } = useGetAllSchedulesQuery({});
17 |
18 | const schedules = data?.schedules;
19 | const meta = data?.meta;
20 |
21 | console.log(schedules);
22 |
23 | useEffect(() => {
24 | const updateData = schedules?.map(
25 | (schedule: ISchedule, index: number) => {
26 | return {
27 | sl: index + 1,
28 | id: schedule?.id,
29 | startDate: dateFormatter(schedule.startDate),
30 | endDate: dateFormatter(schedule.endDate),
31 | startTime: dayjs(schedule?.startDate).format('hh:mm a'),
32 | endTime: dayjs(schedule?.endDate).format('hh:mm a'),
33 | };
34 | }
35 | );
36 | setAllSchedule(updateData);
37 | }, [schedules]);
38 |
39 | const columns: GridColDef[] = [
40 | { field: 'sl', headerName: 'SL' },
41 | { field: 'startDate', headerName: 'Date', flex: 1 },
42 | { field: 'startTime', headerName: 'Start Time', flex: 1 },
43 | { field: 'endTime', headerName: 'End Time', flex: 1 },
44 | {
45 | field: 'action',
46 | headerName: 'Action',
47 | flex: 1,
48 | headerAlign: 'center',
49 | align: 'center',
50 | renderCell: ({ row }) => {
51 | return (
52 |
53 |
54 |
55 | );
56 | },
57 | },
58 | ];
59 | return (
60 |
61 |
62 |
63 | {!isLoading ? (
64 |
65 |
66 |
67 | ) : (
68 | Loading.....
69 | )}
70 |
71 | );
72 | };
73 |
74 | export default SchedulesPage;
75 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/admin/specialties/components/SpecialtyModal.tsx:
--------------------------------------------------------------------------------
1 | import PHFileUploader from "@/components/Forms/PHFileUploader";
2 | import PHForm from "@/components/Forms/PHForm";
3 | import PHInput from "@/components/Forms/PHInput";
4 | import PHModal from "@/components/Shared/PHModal/PHModal";
5 | import { useCreateSpecialtyMutation } from "@/redux/api/specialtiesApi";
6 | import { modifyPayload } from "@/utils/modifyPayload";
7 | import { Button, Grid } from "@mui/material";
8 |
9 | import React from "react";
10 | import { FieldValues } from "react-hook-form";
11 | import { toast } from "sonner";
12 |
13 | type TProps = {
14 | open: boolean;
15 | setOpen: React.Dispatch>;
16 | };
17 |
18 | const SpecialtyModal = ({ open, setOpen }: TProps) => {
19 | const [createSpecialty] = useCreateSpecialtyMutation();
20 |
21 | const handleFormSubmit = async (values: FieldValues) => {
22 | const data = modifyPayload(values);
23 | try {
24 | const res = await createSpecialty(data).unwrap();
25 | console.log(res);
26 | if (res?.id) {
27 | toast.success("Specialty created successfully!!");
28 | setOpen(false);
29 | }
30 | } catch (err: any) {
31 | console.error(err.message);
32 | }
33 | };
34 |
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
49 |
50 |
51 | );
52 | };
53 |
54 | export default SpecialtyModal;
55 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/admin/specialties/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Box, Button, IconButton, Stack, TextField } from "@mui/material";
3 | import SpecialtyModal from "./components/SpecialtyModal";
4 | import { useState } from "react";
5 | import {
6 | useDeleteSpecialtyMutation,
7 | useGetAllSpecialtiesQuery,
8 | } from "@/redux/api/specialtiesApi";
9 | import { DataGrid, GridColDef } from "@mui/x-data-grid";
10 | import Image from "next/image";
11 | import DeleteIcon from "@mui/icons-material/Delete";
12 | import { toast } from "sonner";
13 |
14 | const SpecialtiesPage = () => {
15 | const [isModalOpen, setIsModalOpen] = useState(false);
16 | const { data, isLoading } = useGetAllSpecialtiesQuery({});
17 | const [deleteSpecialty] = useDeleteSpecialtyMutation();
18 |
19 | const handleDelete = async (id: string) => {
20 | try {
21 | const res = await deleteSpecialty(id).unwrap();
22 | if (res?.id) {
23 | toast.success("Specialty deleted successfully!!!");
24 | }
25 | } catch (err: any) {
26 | console.error(err.message);
27 | }
28 | };
29 |
30 | // console.log(data);
31 | const columns: GridColDef[] = [
32 | { field: "title", headerName: "Title", width: 400 },
33 | {
34 | field: "icon",
35 | headerName: "Icon",
36 | flex: 1,
37 | renderCell: ({ row }) => {
38 | return (
39 |
40 |
41 |
42 | );
43 | },
44 | },
45 | {
46 | field: "action",
47 | headerName: "Action",
48 | flex: 1,
49 | headerAlign: "center",
50 | align: "center",
51 | renderCell: ({ row }) => {
52 | return (
53 | handleDelete(row.id)} aria-label="delete">
54 |
55 |
56 | );
57 | },
58 | },
59 | ];
60 |
61 | return (
62 |
63 |
64 |
65 |
66 |
67 |
68 | {!isLoading ? (
69 |
70 |
71 |
72 | ) : (
73 | Loading.....
74 | )}
75 |
76 | );
77 | };
78 |
79 | export default SpecialtiesPage;
80 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/change-password/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import PHForm from '@/components/Forms/PHForm';
4 | import PHInput from '@/components/Forms/PHInput';
5 | import { zodResolver } from '@hookform/resolvers/zod';
6 | import { Box, Button, Grid, Stack, Typography } from '@mui/material';
7 | import { FieldValues } from 'react-hook-form';
8 | import { z } from 'zod';
9 | import KeyIcon from '@mui/icons-material/Key';
10 | import { useChangePasswordMutation } from '@/redux/api/authApi';
11 | import { toast } from 'sonner';
12 | import { useRouter } from 'next/navigation';
13 | import { logoutUser } from '@/services/actions/logoutUser';
14 |
15 | const validationSchema = z.object({
16 | oldPassword: z.string().min(6, 'Must be at least 6 characters long'),
17 | newPassword: z.string().min(6, 'Must be at least 6 characters long'),
18 | });
19 |
20 | const ChangePassword = () => {
21 | const [changePassword] = useChangePasswordMutation();
22 | const router = useRouter();
23 | const onSubmit = async (values: FieldValues) => {
24 | try {
25 | const res = await changePassword(values);
26 |
27 | if ('data' in res && res.data.status === 200) {
28 | logoutUser(router);
29 | toast.success('Password Changed Successfully');
30 | } else {
31 | throw new Error('Incorrect Old Password');
32 | }
33 | } catch (error) {
34 | toast.success('Incorrect Old Password');
35 | console.log(error);
36 | }
37 | };
38 |
39 | return (
40 |
55 |
56 |
64 |
65 |
66 |
67 | Change password
68 |
69 |
70 |
75 |
76 |
77 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
99 |
100 |
101 | );
102 | };
103 |
104 | export default ChangePassword;
105 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/appointment/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useGetMyAppointmentsQuery } from '@/redux/api/appointmentApi';
3 | import { Box, IconButton } from '@mui/material';
4 | import { DataGrid, GridColDef } from '@mui/x-data-grid';
5 | import VideocamIcon from '@mui/icons-material/Videocam';
6 | import Link from 'next/link';
7 | import { dateFormatter } from '@/utils/dateFormatter';
8 | import { getTimeIn12HourFormat } from '../schedules/components/MultipleSelectFieldChip';
9 |
10 | const PatientAppointmentsPage = () => {
11 | const { data, isLoading } = useGetMyAppointmentsQuery({});
12 | const appointments = data?.appointments;
13 | const meta = data?.meta;
14 | // console.log(appointments);
15 |
16 | const columns: GridColDef[] = [
17 | {
18 | field: 'name',
19 | headerName: 'Patient Name',
20 | flex: 1,
21 | renderCell: ({ row }) => {
22 | return row?.patient?.name;
23 | },
24 | },
25 | {
26 | field: 'contactNumber',
27 | headerName: 'Contact Number',
28 | flex: 1,
29 | renderCell: ({ row }) => {
30 | return row?.patient?.contactNumber;
31 | },
32 | },
33 | {
34 | field: 'appointmentDate',
35 | headerName: 'Appointment Date',
36 | headerAlign: 'center',
37 | align: 'center',
38 | flex: 1,
39 | renderCell: ({ row }) => {
40 | return dateFormatter(row.schedule.startDate);
41 | },
42 | },
43 | {
44 | field: 'appointmentTime',
45 | headerName: 'Appointment Time',
46 | headerAlign: 'center',
47 | align: 'center',
48 | flex: 1,
49 | renderCell: ({ row }) => {
50 | return getTimeIn12HourFormat(row?.schedule?.startDate);
51 | },
52 | },
53 |
54 | {
55 | field: 'paymentStatus',
56 | headerName: 'Payment Status',
57 | flex: 1,
58 | headerAlign: 'center',
59 | align: 'center',
60 | },
61 | {
62 | field: 'action',
63 | headerName: 'Join',
64 | flex: 1,
65 | headerAlign: 'center',
66 | align: 'center',
67 | renderCell: ({ row }) => {
68 | return (
69 |
70 |
71 |
72 |
73 |
74 | );
75 | },
76 | },
77 | ];
78 |
79 | return (
80 |
81 | {!isLoading ? (
82 |
83 |
88 |
89 | ) : (
90 | Loading.....
91 | )}
92 |
93 | );
94 | };
95 |
96 | export default PatientAppointmentsPage;
97 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/page.tsx:
--------------------------------------------------------------------------------
1 | const DoctorPage = () => {
2 | return (
3 |
4 |
Doctor Dashboard
5 |
6 | );
7 | };
8 |
9 | export default DoctorPage;
10 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/profile/components/DoctorInformations.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Stack, styled, Typography } from '@mui/material';
2 |
3 | const StyledInformationBox = styled(Box)(({ theme }) => ({
4 | background: '#f4f7fe',
5 | borderRadius: theme.spacing(1),
6 | width: '45%',
7 | padding: '8px 16px',
8 | '& p': {
9 | fontWeight: 600,
10 | },
11 | }));
12 |
13 | const DoctorInformation = ({ data }: any) => {
14 | return (
15 | <>
16 |
17 | Personal Information
18 |
19 |
20 |
25 |
26 |
27 | Role
28 |
29 | {data?.role}
30 |
31 |
32 |
33 | Name
34 |
35 | {data?.name}
36 |
37 |
38 |
39 | Email
40 |
41 | {data?.email}
42 |
43 |
44 |
45 | Gender
46 |
47 | {data?.gender}
48 |
49 |
50 |
51 | Designation
52 |
53 | {data?.designation}
54 |
55 |
56 |
57 |
58 | Professional Information
59 |
60 |
65 |
66 |
67 | Anointment Fee
68 |
69 | {data?.apointmentFee}
70 |
71 |
72 |
73 | Qualification
74 |
75 | {data?.qualification}
76 |
77 |
78 |
79 | Current Working Place
80 |
81 | {data?.currentWorkingPlace}
82 |
83 |
84 |
85 | Joined
86 |
87 |
88 | {data
89 | ? new Date(data.createdAt).toLocaleDateString('en-US', {
90 | month: '2-digit',
91 | day: '2-digit',
92 | year: '2-digit',
93 | })
94 | : null}
95 |
96 |
97 |
98 |
99 | Current Status
100 |
101 | {data?.status}
102 |
103 |
104 |
105 | Average Rating
106 |
107 | {data?.averageRating}
108 |
109 |
110 |
111 | experience
112 |
113 | {data?.experience}
114 |
115 |
116 | >
117 | );
118 | };
119 |
120 | export default DoctorInformation;
121 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/profile/components/MultipleSelectChip.tsx:
--------------------------------------------------------------------------------
1 | import Box from '@mui/material/Box';
2 | import Chip from '@mui/material/Chip';
3 | import FormControl from '@mui/material/FormControl';
4 | import InputLabel from '@mui/material/InputLabel';
5 | import MenuItem from '@mui/material/MenuItem';
6 | import OutlinedInput from '@mui/material/OutlinedInput';
7 | import Select, { SelectChangeEvent } from '@mui/material/Select';
8 | import { Theme, useTheme } from '@mui/material/styles';
9 | import * as React from 'react';
10 |
11 | const ITEM_HEIGHT = 48;
12 | const ITEM_PADDING_TOP = 8;
13 | const MenuProps = {
14 | PaperProps: {
15 | style: {
16 | maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
17 | width: 250,
18 | },
19 | },
20 | };
21 |
22 | function getStyles(name: string, personName: readonly string[], theme: Theme) {
23 | return {
24 | fontWeight:
25 | personName.indexOf(name) === -1
26 | ? theme.typography.fontWeightRegular
27 | : theme.typography.fontWeightMedium,
28 | };
29 | }
30 |
31 | export default function MultipleSelectChip({
32 | allSpecialties,
33 | setSelectedIds,
34 | selectedIds,
35 | }: any) {
36 | const theme = useTheme();
37 |
38 | const handleChange = (event: SelectChangeEvent) => {
39 | const {
40 | target: { value },
41 | } = event;
42 |
43 | setSelectedIds(typeof value === 'string' ? value.split(',') : value);
44 | };
45 |
46 | return (
47 |
48 |
49 | 0 ? 0 : -1 }}
52 | >
53 | Specialties
54 |
55 |
67 | }
68 | renderValue={(selected) => (
69 |
70 | {selected.map((value: any) => (
71 | item.id === value
77 | )
78 | ? `${
79 | allSpecialties.find(
80 | (item: any) => item.id === value
81 | )?.title
82 | }`
83 | : ''
84 | }
85 | />
86 | ))}
87 |
88 | )}
89 | MenuProps={MenuProps}
90 | >
91 | {allSpecialties?.map((item: any) => (
92 |
99 | ))}
100 |
101 |
102 |
103 | );
104 | }
105 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/profile/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | useGetMYProfileQuery,
5 | useUpdateMYProfileMutation,
6 | } from '@/redux/api/myProfile';
7 | import { Box, Button, Container } from '@mui/material';
8 | import Grid from '@mui/material/Unstable_Grid2'; // Grid version 2
9 | import Image from 'next/image';
10 | import React, { useState } from 'react';
11 | import DoctorInformation from './components/DoctorInformations';
12 | import AutoFileUploader from '@/components/Forms/AutoFileUploader';
13 | import CloudUploadIcon from '@mui/icons-material/CloudUpload';
14 | import ProfileUpdateModal from './components/ProfileUpdateModal';
15 | import ModeEditIcon from '@mui/icons-material/ModeEdit';
16 |
17 | const Profile = () => {
18 | const [isModalOpen, setIsModalOpen] = useState(false);
19 |
20 | const { data, isLoading } = useGetMYProfileQuery(undefined);
21 | const [updateMYProfile, { isLoading: updating }] =
22 | useUpdateMYProfileMutation();
23 |
24 | const fileUploadHandler = (file: File) => {
25 | const formData = new FormData();
26 | formData.append('file', file);
27 | formData.append('data', JSON.stringify({}));
28 |
29 | updateMYProfile(formData);
30 | };
31 |
32 | if (isLoading) {
33 | Loading...
;
34 | }
35 |
36 | return (
37 | <>
38 |
43 |
44 |
45 |
46 |
54 |
60 |
61 |
62 | {updating ? (
63 | Uploading...
64 | ) : (
65 | }
69 | onFileUpload={fileUploadHandler}
70 | variant='text'
71 | />
72 | )}
73 |
74 |
75 | }
78 | onClick={() => setIsModalOpen(true)}
79 | >
80 | Edit Profile
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | >
89 | );
90 | };
91 |
92 | export default Profile;
93 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/schedules/components/DoctorScheduleModal.tsx:
--------------------------------------------------------------------------------
1 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
2 | import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
3 | import { DatePicker } from '@mui/x-date-pickers/DatePicker';
4 | import PHModal from '@/components/Shared/PHModal/PHModal';
5 | import dayjs, { Dayjs } from 'dayjs';
6 | import { useState } from 'react';
7 | import { useGetAllSchedulesQuery } from '@/redux/api/scheduleApi';
8 | import MultipleSelectFieldChip from './MultipleSelectFieldChip';
9 | import { Stack } from '@mui/material';
10 | import LoadingButton from '@mui/lab/LoadingButton';
11 | import { useCreateDoctorScheduleMutation } from '@/redux/api/doctorScheduleApi';
12 |
13 | type TProps = {
14 | open: boolean;
15 | setOpen: React.Dispatch>;
16 | };
17 |
18 | const DoctorScheduleModal = ({ open, setOpen }: TProps) => {
19 | const [selectedDate, setSelectedDate] = useState(
20 | dayjs(new Date()).toISOString()
21 | );
22 |
23 | const [selectedScheduleIds, setSelectedScheduleIds] = useState([]);
24 |
25 | const query: Record = {};
26 |
27 | if (!!selectedDate) {
28 | query['startDate'] = dayjs(selectedDate)
29 | .hour(0)
30 | .minute(0)
31 | .millisecond(0)
32 | .toISOString();
33 | query['endDate'] = dayjs(selectedDate)
34 | .hour(23)
35 | .minute(59)
36 | .millisecond(999)
37 | .toISOString();
38 | }
39 |
40 | const { data } = useGetAllSchedulesQuery(query);
41 | const schedules = data?.schedules;
42 |
43 | const [createDoctorSchedule, { isLoading }] =
44 | useCreateDoctorScheduleMutation();
45 |
46 | console.log(selectedScheduleIds);
47 |
48 | const onSubmit = async () => {
49 | try {
50 | const res = await createDoctorSchedule({
51 | scheduleIds: selectedScheduleIds,
52 | });
53 | console.log(res);
54 | setOpen(false);
55 | } catch (error) {
56 | console.log(error);
57 | }
58 | };
59 |
60 | return (
61 |
62 |
63 |
64 |
68 | setSelectedDate(dayjs(newValue).toISOString())
69 | }
70 | sx={{ width: '100%' }}
71 | />
72 |
73 |
78 |
79 |
86 | Submit
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default DoctorScheduleModal;
94 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/schedules/components/MultipleSelectFieldChip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Theme, useTheme } from '@mui/material/styles';
3 | import Box from '@mui/material/Box';
4 | import OutlinedInput from '@mui/material/OutlinedInput';
5 | import InputLabel from '@mui/material/InputLabel';
6 | import MenuItem from '@mui/material/MenuItem';
7 | import FormControl from '@mui/material/FormControl';
8 | import Select, { SelectChangeEvent } from '@mui/material/Select';
9 | import Chip from '@mui/material/Chip';
10 |
11 | const ITEM_HEIGHT = 48;
12 | const ITEM_PADDING_TOP = 8;
13 | const MenuProps = {
14 | PaperProps: {
15 | style: {
16 | maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
17 | width: 250,
18 | },
19 | },
20 | };
21 |
22 | export function getTimeIn12HourFormat(dateTimeString: string): string {
23 | const date: Date = new Date(dateTimeString);
24 | const hours: number = date.getHours();
25 | const minutes: number = date.getMinutes();
26 | const ampm: string = hours >= 12 ? 'PM' : 'AM';
27 | const formattedHours: number = hours % 12 === 0 ? 12 : hours % 12;
28 | const formattedMinutes: string =
29 | minutes < 10 ? '0' + minutes : minutes.toString();
30 | return `${formattedHours}:${formattedMinutes} ${ampm}`;
31 | }
32 |
33 | const names = [
34 | 'Oliver Hansen',
35 | 'Van Henry',
36 | 'April Tucker',
37 | 'Ralph Hubbard',
38 | 'Omar Alexander',
39 | 'Carlos Abbott',
40 | 'Miriam Wagner',
41 | 'Bradley Wilkerson',
42 | 'Virginia Andrews',
43 | 'Kelly Snyder',
44 | ];
45 |
46 | function getStyles(name: string, personName: readonly string[], theme: Theme) {
47 | return {
48 | fontWeight:
49 | personName.indexOf(name) === -1
50 | ? theme.typography.fontWeightRegular
51 | : theme.typography.fontWeightMedium,
52 | };
53 | }
54 |
55 | export default function MultipleSelectFieldChip({
56 | schedules,
57 | selectedScheduleIds,
58 | setSelectedScheduleIds,
59 | }: any) {
60 | const theme = useTheme();
61 | // const [personName, setPersonName] = React.useState([]);
62 |
63 | const handleChange = (
64 | event: SelectChangeEvent
65 | ) => {
66 | const {
67 | target: { value },
68 | } = event;
69 | setSelectedScheduleIds(
70 | // On autofill we get a stringified value.
71 | typeof value === 'string' ? value.split(',') : value
72 | );
73 | };
74 |
75 | return (
76 |
77 |
78 | Chip
79 | }
86 | renderValue={(selected) => {
87 | return (
88 |
89 | {selected.map((value: any) => {
90 | const selectedSchedule = schedules.find(
91 | (schedule: any) => schedule.id === value
92 | );
93 |
94 | if (!selectedSchedule) return null;
95 |
96 | const formattedTimeSlot = `${getTimeIn12HourFormat(
97 | selectedSchedule.startDate
98 | )} - ${getTimeIn12HourFormat(
99 | selectedSchedule.endDate
100 | )}`;
101 |
102 | return (
103 |
104 | );
105 | })}
106 |
107 | );
108 | }}
109 | MenuProps={MenuProps}
110 | >
111 | {schedules.map((schedule: any) => (
112 |
121 | ))}
122 |
123 |
124 |
125 | );
126 | }
127 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/doctor/schedules/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Box, Button, IconButton, Pagination } from '@mui/material';
3 | import DoctorScheduleModal from './components/DoctorScheduleModal';
4 | import { useEffect, useState } from 'react';
5 | import { DataGrid, GridColDef } from '@mui/x-data-grid';
6 | import DeleteIcon from '@mui/icons-material/Delete';
7 | import { dateFormatter } from '@/utils/dateFormatter';
8 | import { ISchedule } from '@/types/schedule';
9 | import dayjs from 'dayjs';
10 | import { useGetAllDoctorSchedulesQuery } from '@/redux/api/doctorScheduleApi';
11 | import AddIcon from '@mui/icons-material/Add';
12 |
13 | const DoctorSchedulesPage = () => {
14 | const [isModalOpen, setIsModalOpen] = useState(false);
15 |
16 | const query: Record = {};
17 |
18 | const [page, setPage] = useState(1);
19 | const [limit, setLimit] = useState(3);
20 |
21 | query['page'] = page;
22 | query['limit'] = limit;
23 |
24 | const [allSchedule, setAllSchedule] = useState([]);
25 | const { data, isLoading } = useGetAllDoctorSchedulesQuery({ ...query });
26 | console.log(data);
27 |
28 | const schedules = data?.doctorSchedules;
29 | const meta = data?.meta;
30 |
31 | console.log({ schedules });
32 |
33 | let pageCount: number;
34 |
35 | if (meta?.total) {
36 | pageCount = Math.ceil(meta.total / limit);
37 | }
38 |
39 | const handleChange = (event: React.ChangeEvent, value: number) => {
40 | setPage(value);
41 | };
42 |
43 | useEffect(() => {
44 | const updateData = schedules?.map(
45 | (schedule: ISchedule, index: number) => {
46 | return {
47 | id: schedule?.scheduleId,
48 | startDate: dateFormatter(schedule?.schedule?.startDate),
49 | startTime: dayjs(schedule?.startDate).format('hh:mm a'),
50 | endTime: dayjs(schedule?.endDate).format('hh:mm a'),
51 | };
52 | }
53 | );
54 | setAllSchedule(updateData);
55 | }, [schedules]);
56 |
57 | const columns: GridColDef[] = [
58 | { field: 'startDate', headerName: 'Date', flex: 1 },
59 | { field: 'startTime', headerName: 'Start Time', flex: 1 },
60 | { field: 'endTime', headerName: 'End Time', flex: 1 },
61 | {
62 | field: 'action',
63 | headerName: 'Action',
64 | flex: 1,
65 | headerAlign: 'center',
66 | align: 'center',
67 | renderCell: ({ row }) => {
68 | return (
69 |
70 |
71 |
72 | );
73 | },
74 | },
75 | ];
76 |
77 | return (
78 |
79 |
86 |
87 |
88 |
89 |
90 | {!isLoading ? (
91 |
92 | {
98 | return (
99 |
106 |
112 |
113 | );
114 | },
115 | }}
116 | />
117 |
118 | ) : (
119 | Loading.....
120 | )}
121 |
122 |
123 | );
124 | };
125 |
126 | export default DoctorSchedulesPage;
127 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/page.tsx:
--------------------------------------------------------------------------------
1 | const DashboardHomePage = () => {
2 | return (
3 |
4 |
Welcome to dashboard
5 |
6 | );
7 | };
8 |
9 | export default DashboardHomePage;
10 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/patient/appointments/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useGetMyAppointmentsQuery } from '@/redux/api/appointmentApi';
3 | import { Box, Chip, IconButton } from '@mui/material';
4 | import { DataGrid, GridColDef } from '@mui/x-data-grid';
5 | import VideocamIcon from '@mui/icons-material/Videocam';
6 | import Link from 'next/link';
7 | import { dateFormatter } from '@/utils/dateFormatter';
8 | import { getTimeIn12HourFormat } from '../../doctor/schedules/components/MultipleSelectFieldChip';
9 | import PhChips from '@/components/Shared/PhChip/PhChips';
10 |
11 | const PatientAppointmentsPage = () => {
12 | const { data, isLoading } = useGetMyAppointmentsQuery({});
13 | const appointments = data?.appointments;
14 | const meta = data?.meta;
15 |
16 | const columns: GridColDef[] = [
17 | {
18 | field: 'name',
19 | headerName: 'Doctor Name',
20 | flex: 1,
21 | renderCell: ({ row }) => {
22 | return row.doctor.name;
23 | },
24 | },
25 | {
26 | field: 'appointmentDate',
27 | headerName: 'Appointment Date',
28 | headerAlign: 'center',
29 | align: 'center',
30 | flex: 1,
31 | renderCell: ({ row }) => {
32 | return dateFormatter(row.schedule.startDate);
33 | },
34 | },
35 | {
36 | field: 'appointmentTime',
37 | headerName: 'Appointment Time',
38 | headerAlign: 'center',
39 | align: 'center',
40 | flex: 1,
41 | renderCell: ({ row }) => {
42 | return getTimeIn12HourFormat(row.schedule.startDate);
43 | },
44 | },
45 |
46 | {
47 | field: 'paymentStatus',
48 | headerName: 'Payment Status',
49 | flex: 1,
50 | headerAlign: 'center',
51 | align: 'center',
52 | renderCell: ({ row }) => {
53 | return row.paymentStatus === 'PAID' ? (
54 |
55 | ) : (
56 |
57 | );
58 | },
59 | },
60 | {
61 | field: 'action',
62 | headerName: 'Join',
63 | flex: 1,
64 | headerAlign: 'center',
65 | align: 'center',
66 | renderCell: ({ row }) => {
67 | return (
68 |
73 |
79 |
80 | );
81 | },
82 | },
83 | ];
84 |
85 | return (
86 |
87 | {!isLoading ? (
88 |
89 |
94 |
95 | ) : (
96 | Loading.....
97 | )}
98 |
99 | );
100 | };
101 |
102 | export default PatientAppointmentsPage;
103 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/patient/page.tsx:
--------------------------------------------------------------------------------
1 | const PatientPage = () => {
2 | return (
3 |
4 |
Patient Dashboard
5 |
6 | );
7 | };
8 |
9 | export default PatientPage;
10 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/dashboard/super_admin/page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const SuperAdminPage = () => {
4 | return (
5 |
6 |
Super Admin Dashboard
7 |
8 | );
9 | };
10 |
11 | export default SuperAdminPage;
12 |
--------------------------------------------------------------------------------
/src/app/(withDashboardLayout)/layout.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import DashboardDrawer from '@/components/Dashboard/DashboardDrawer/DashboardDrawer';
3 | import { isLoggedIn } from '@/services/auth.services';
4 | import { useRouter } from 'next/navigation';
5 |
6 | const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
7 | const router = useRouter();
8 | if (!isLoggedIn()) {
9 | return router.push('/login');
10 | }
11 | return {children} ;
12 | };
13 |
14 | export default DashboardLayout;
15 |
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&display=swap');
2 |
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
7 | *{
8 | padding: 0;
9 | margin: 0;
10 | box-sizing: border-box;
11 | font-family: "Roboto Condensed", sans-serif;
12 | }
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next';
2 | import { Inter } from 'next/font/google';
3 | import './globals.css';
4 | import { AppRouterCacheProvider } from '@mui/material-nextjs/v13-appRouter';
5 | import Providers from '@/lib/Providers/Providers';
6 | import { Toaster } from 'sonner';
7 |
8 | // const inter = Inter({ subsets: ["latin"] });
9 |
10 | export const metadata: Metadata = {
11 | title: 'PH Health Care',
12 | description: 'Dashboard',
13 | };
14 |
15 | export default function RootLayout({
16 | children,
17 | }: {
18 | children: React.ReactNode;
19 | }) {
20 | return (
21 |
22 |
23 |
24 |
25 | <>
26 |
27 | {children}
28 | >
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Box, Button, Container, Stack, Typography } from '@mui/material';
4 | import React from 'react';
5 | import HomeIcon from '@mui/icons-material/Home';
6 | import ArrowBackIcon from '@mui/icons-material/ArrowBack';
7 | import Link from 'next/link';
8 |
9 | const NotFoundPage = () => {
10 | return (
11 |
12 |
24 |
25 | 404
26 |
27 |
28 | Oops! Page not found.
29 |
30 |
31 |
32 |
33 | }
37 | >
38 | Home
39 |
40 |
41 |
48 |
49 |
50 |
51 | );
52 | };
53 |
54 | export default NotFoundPage;
55 |
--------------------------------------------------------------------------------
/src/assets/choose-us.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/choose-us.png
--------------------------------------------------------------------------------
/src/assets/doctor-image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/doctor-image1.png
--------------------------------------------------------------------------------
/src/assets/doctor-image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/doctor-image2.png
--------------------------------------------------------------------------------
/src/assets/how-it-works-img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/how-it-works-img.png
--------------------------------------------------------------------------------
/src/assets/icons/appointment-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/icons/appointment-icon.png
--------------------------------------------------------------------------------
/src/assets/icons/charity-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/icons/charity-icon.png
--------------------------------------------------------------------------------
/src/assets/icons/doctor-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/icons/doctor-icon.png
--------------------------------------------------------------------------------
/src/assets/icons/search-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/icons/search-icon.png
--------------------------------------------------------------------------------
/src/assets/images/Stetoscope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/images/Stetoscope.png
--------------------------------------------------------------------------------
/src/assets/images/doctor1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/images/doctor1.png
--------------------------------------------------------------------------------
/src/assets/images/doctor2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/images/doctor2.png
--------------------------------------------------------------------------------
/src/assets/images/doctor3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/images/doctor3.png
--------------------------------------------------------------------------------
/src/assets/images/familyOnBeach.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/images/familyOnBeach.png
--------------------------------------------------------------------------------
/src/assets/index.ts:
--------------------------------------------------------------------------------
1 | const assets = {
2 | images: {
3 | // images will go here
4 |
5 | doctors: require("./landing_page/doctors.png"),
6 | atm: require("./landing_page/atm-card.png"),
7 | videoCall: require("./landing_page/video_call.png"),
8 | report: require("./landing_page/diagnostic.png"),
9 | budge: require("./landing_page/badge.png"),
10 | folder: require("./landing_page/folder.png"),
11 | doctor1: require("./images/doctor1.png"),
12 | doctor2: require("./images/doctor2.png"),
13 | doctor3: require("./images/doctor3.png"),
14 | stethoscope: require("./images/Stetoscope.png"),
15 | familyOnBeach: require("./images/familyOnBeach.png"),
16 | },
17 | svgs: {
18 | logo: require("./svgs/logo.svg"),
19 | search: require("./svgs/search.svg"),
20 | calender: require("./svgs/calender.svg"),
21 | location: require("./svgs/location.svg"),
22 | kidney: require("./svgs/kidney.svg"),
23 | brain: require("./svgs/brain.svg"),
24 | dna: require("./svgs/dna.svg"),
25 | doctorSearch: require("./svgs/doctorSearch.svg"),
26 | profile: require("./svgs/profile.svg"),
27 | schedule: require("./svgs/schedule.svg"),
28 | solution: require("./svgs/solution.svg"),
29 | grid: require("./svgs/grid.svg"),
30 | arrow: require("./svgs/arrow.svg"),
31 | cardiology: require("./svgs/Cardiology.svg"),
32 | neurology: require("./svgs/Neurology.svg"),
33 | urology: require("./svgs/Urology.svg"),
34 | orthopedic: require("./svgs/Orthopedic.svg"),
35 | dentist: require("./svgs/Dentist.svg"),
36 | ophthalmology: require("./svgs/Ophthalmology.svg"),
37 | award: require("./svgs/award-icon.svg"),
38 | care: require("./svgs/care-icon.svg"),
39 | equipment: require("./svgs/medical-equipment-icon.svg"),
40 | call: require("./svgs/call-icon.svg"),
41 | },
42 | };
43 |
44 | export default assets;
45 |
--------------------------------------------------------------------------------
/src/assets/landing_page/atm-card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/atm-card.png
--------------------------------------------------------------------------------
/src/assets/landing_page/badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/badge.png
--------------------------------------------------------------------------------
/src/assets/landing_page/calender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/calender.png
--------------------------------------------------------------------------------
/src/assets/landing_page/diagnostic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/diagnostic.png
--------------------------------------------------------------------------------
/src/assets/landing_page/doctors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/doctors.png
--------------------------------------------------------------------------------
/src/assets/landing_page/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/facebook.png
--------------------------------------------------------------------------------
/src/assets/landing_page/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/folder.png
--------------------------------------------------------------------------------
/src/assets/landing_page/instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/instagram.png
--------------------------------------------------------------------------------
/src/assets/landing_page/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/linkedin.png
--------------------------------------------------------------------------------
/src/assets/landing_page/location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/location.png
--------------------------------------------------------------------------------
/src/assets/landing_page/search.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/search.jpg
--------------------------------------------------------------------------------
/src/assets/landing_page/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/search.png
--------------------------------------------------------------------------------
/src/assets/landing_page/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/twitter.png
--------------------------------------------------------------------------------
/src/assets/landing_page/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/user.png
--------------------------------------------------------------------------------
/src/assets/landing_page/video_call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/PH-HealthCare-Frontend/c4cad3ca08de608c041ca19bf3a460f485bc5d45/src/assets/landing_page/video_call.png
--------------------------------------------------------------------------------
/src/assets/svgs/Orthopedic.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/svgs/award-icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/svgs/call-icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/svgs/care-icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svgs/doctorSearch.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/svgs/kidney.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/assets/svgs/location.svg:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/src/assets/svgs/logo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/svgs/medical-equipment-icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svgs/profile.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/svgs/schedule.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/assets/svgs/search.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/svgs/solution.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/Dashboard/AccountMenu/AccountMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Box from '@mui/material/Box';
3 | import Avatar from '@mui/material/Avatar';
4 | import Menu from '@mui/material/Menu';
5 | import MenuItem from '@mui/material/MenuItem';
6 | import ListItemIcon from '@mui/material/ListItemIcon';
7 | import Divider from '@mui/material/Divider';
8 | import IconButton from '@mui/material/IconButton';
9 | import Tooltip from '@mui/material/Tooltip';
10 | import Logout from '@mui/icons-material/Logout';
11 | import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
12 | import { useRouter } from 'next/navigation';
13 | import { logoutUser } from '@/services/actions/logoutUser';
14 |
15 | const menuStyles = {
16 | paper: {
17 | elevation: 0,
18 | overflow: 'visible',
19 | filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
20 | mt: 1.5,
21 | '& .MuiAvatar-root': {
22 | width: 32,
23 | height: 32,
24 | ml: -0.5,
25 | mr: 1,
26 | },
27 | '&:before': {
28 | content: '""',
29 | display: 'block',
30 | position: 'absolute',
31 | top: 0,
32 | right: 14,
33 | width: 10,
34 | height: 10,
35 | bgcolor: 'background.paper',
36 | transform: 'translateY(-50%) rotate(45deg)',
37 | zIndex: 0,
38 | },
39 | },
40 | };
41 |
42 | export default function AccountMenu() {
43 | const [anchorEl, setAnchorEl] = React.useState(null);
44 | const open = Boolean(anchorEl);
45 | const router = useRouter();
46 | const handleClick = (event: React.MouseEvent) => {
47 | setAnchorEl(event.currentTarget);
48 | };
49 |
50 | const handleClose = () => {
51 | setAnchorEl(null);
52 | };
53 | const handleLogout = () => {
54 | setAnchorEl(null);
55 | logoutUser(router);
56 | };
57 |
58 | return (
59 |
60 |
63 |
74 |
87 |
88 |
89 |
90 |
91 |
119 |
120 | );
121 | }
122 |
--------------------------------------------------------------------------------
/src/components/Dashboard/DashboardDrawer/DashboardDrawer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import AppBar from "@mui/material/AppBar";
5 | import Box from "@mui/material/Box";
6 | import CssBaseline from "@mui/material/CssBaseline";
7 | import Drawer from "@mui/material/Drawer";
8 | import IconButton from "@mui/material/IconButton";
9 | import MenuIcon from "@mui/icons-material/Menu";
10 | import Toolbar from "@mui/material/Toolbar";
11 | import Typography from "@mui/material/Typography";
12 | import SideBar from "../SideBar/SideBar";
13 | import { Avatar, Badge, Stack } from "@mui/material";
14 | import AccountMenu from "../AccountMenu/AccountMenu";
15 | import NotificationsNoneIcon from "@mui/icons-material/NotificationsNone";
16 | import { useGetSingleUserQuery } from "@/redux/api/userApi";
17 |
18 | const drawerWidth = 240;
19 |
20 | export default function DashboardDrawer({
21 | children,
22 | }: {
23 | children: React.ReactNode;
24 | }) {
25 | const [mobileOpen, setMobileOpen] = React.useState(false);
26 | const [isClosing, setIsClosing] = React.useState(false);
27 |
28 | const handleDrawerClose = () => {
29 | setIsClosing(true);
30 | setMobileOpen(false);
31 | };
32 |
33 | const handleDrawerTransitionEnd = () => {
34 | setIsClosing(false);
35 | };
36 |
37 | const handleDrawerToggle = () => {
38 | if (!isClosing) {
39 | setMobileOpen(!mobileOpen);
40 | }
41 | };
42 |
43 | const { data, isLoading } = useGetSingleUserQuery({});
44 | // console.log(data);
45 |
46 | return (
47 |
48 |
49 |
60 |
61 |
68 |
69 |
70 |
78 |
79 |
85 | Hi, {isLoading ? "Loading..." : data?.name},
86 |
87 |
93 | Welcome to PH Health Care!
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
113 | {/* The implementation can be swapped with js to avoid SEO duplication of links. */}
114 |
130 |
131 |
132 |
143 |
144 |
145 |
146 |
154 |
155 | {children}
156 |
157 |
158 | );
159 | }
160 |
--------------------------------------------------------------------------------
/src/components/Dashboard/SideBar/SideBar.tsx:
--------------------------------------------------------------------------------
1 | import { Box, List, Stack, Typography } from "@mui/material";
2 | import Image from "next/image";
3 | import assets from "@/assets";
4 | import Link from "next/link";
5 | import { drawerItems } from "@/utils/drawerItems";
6 | import { UserRole } from "@/types";
7 | import SidebarItem from "./SidebarItem";
8 | import { getUserInfo } from "@/services/auth.services";
9 | import { useEffect, useState } from "react";
10 |
11 | const SideBar = () => {
12 | const [userRole, setUserRole] = useState("");
13 |
14 | useEffect(() => {
15 | const { role } = getUserInfo() as any;
16 | setUserRole(role);
17 | }, []);
18 |
19 | return (
20 |
21 |
33 |
34 |
41 | PH Health Care
42 |
43 |
44 |
45 | {drawerItems(userRole as UserRole).map((item, index) => (
46 |
47 | ))}
48 |
49 |
50 | );
51 | };
52 |
53 | export default SideBar;
54 |
--------------------------------------------------------------------------------
/src/components/Dashboard/SideBar/SidebarItem.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import {
3 | ListItem,
4 | ListItemButton,
5 | ListItemIcon,
6 | ListItemText,
7 | } from '@mui/material';
8 | import { DrawerItem } from '@/types';
9 | import { usePathname } from 'next/navigation';
10 |
11 | type IProps = {
12 | item: DrawerItem;
13 | };
14 |
15 | const SidebarItem = ({ item }: IProps) => {
16 | const linkPath = `/dashboard/${item.path}`;
17 | const pathname = usePathname();
18 |
19 | console.log({ pathname, linkPath });
20 | return (
21 |
22 |
36 |
37 | {item.icon && }
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default SidebarItem;
46 |
--------------------------------------------------------------------------------
/src/components/Forms/AutoFileUploader.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { CloudUpload as CloudUploadIcon } from "@mui/icons-material";
3 | import { Box, Button, Input, SvgIconProps, SxProps } from "@mui/material";
4 | import { ReactElement } from "react";
5 |
6 | interface IFileUploadButton {
7 | name: string;
8 | label?: string;
9 | accept?: string;
10 | sx?: SxProps;
11 | icon?: ReactElement;
12 | variant?: "contained" | "text";
13 | onFileUpload: (file: File) => void;
14 | }
15 |
16 | const AutoFileUploader = ({
17 | name,
18 | label,
19 | accept,
20 | sx,
21 | icon,
22 | variant = "contained",
23 | onFileUpload,
24 | }: IFileUploadButton) => {
25 | return (
26 |
27 | }
33 | sx={{ ...sx }}
34 | >
35 | {label || "Upload file"}
36 | {
41 | const fileInput = e.target as HTMLInputElement;
42 | const file = fileInput.files?.[0];
43 | if (file) {
44 | onFileUpload(file);
45 | }
46 | }}
47 | />
48 |
49 |
50 | );
51 | };
52 |
53 | export default AutoFileUploader;
54 |
--------------------------------------------------------------------------------
/src/components/Forms/PHDatePicker.tsx:
--------------------------------------------------------------------------------
1 | import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
2 | import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
3 | import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
4 | import dayjs from "dayjs";
5 | import { SxProps } from "@mui/material";
6 | import { Controller, useFormContext } from "react-hook-form";
7 |
8 | interface IDatePicker {
9 | name: string;
10 | size?: "small" | "medium";
11 | label?: string;
12 | required?: boolean;
13 | fullWidth?: boolean;
14 | sx?: SxProps;
15 | }
16 |
17 | const PHDatePicker = ({
18 | name,
19 | size = "small",
20 | label,
21 | required,
22 | fullWidth = true,
23 | sx,
24 | }: IDatePicker) => {
25 | const { control } = useFormContext();
26 | return (
27 | {
32 | return (
33 |
34 | onChange(date)}
40 | value={value || Date.now()}
41 | slotProps={{
42 | textField: {
43 | required: required,
44 | size: size,
45 | sx: {
46 | ...sx,
47 | },
48 | variant: "outlined",
49 | fullWidth: fullWidth,
50 | },
51 | }}
52 | />
53 |
54 | );
55 | }}
56 | />
57 | );
58 | };
59 |
60 | export default PHDatePicker;
61 |
--------------------------------------------------------------------------------
/src/components/Forms/PHFileUploader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { SxProps, styled } from "@mui/material/styles";
3 | import Button from "@mui/material/Button";
4 | import CloudUploadIcon from "@mui/icons-material/CloudUpload";
5 | import { Controller, useFormContext } from "react-hook-form";
6 | import { Input } from "@mui/material";
7 |
8 | type TProps = {
9 | name: string;
10 | label?: string;
11 | sx?: SxProps;
12 | };
13 |
14 | export default function PHFileUploader({ name, label, sx }: TProps) {
15 | const { control } = useFormContext();
16 | return (
17 | {
21 | return (
22 | }
28 | sx={{ ...sx }}
29 | >
30 | {label || "Upload file"}
31 |
36 | onChange((e?.target as HTMLInputElement).files?.[0])
37 | }
38 | style={{ display: "none" }}
39 | />
40 |
41 | );
42 | }}
43 | />
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/Forms/PHForm.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | FieldValues,
3 | FormProvider,
4 | SubmitHandler,
5 | useForm,
6 | } from "react-hook-form";
7 |
8 | type TFormConfig = {
9 | resolver?: any;
10 | defaultValues?: Record;
11 | };
12 |
13 | type TFormProps = {
14 | children: React.ReactNode;
15 | onSubmit: SubmitHandler;
16 | } & TFormConfig;
17 |
18 | const PHForm = ({
19 | children,
20 | onSubmit,
21 | resolver,
22 | defaultValues,
23 | }: TFormProps) => {
24 | const formConfig: TFormConfig = {};
25 |
26 | if (resolver) {
27 | formConfig["resolver"] = resolver;
28 | }
29 |
30 | if (defaultValues) {
31 | formConfig["defaultValues"] = defaultValues;
32 | }
33 |
34 | const methods = useForm(formConfig);
35 | const { handleSubmit, reset } = methods;
36 |
37 | const submit: SubmitHandler = (data) => {
38 | // console.log(data);
39 | onSubmit(data);
40 | reset();
41 | };
42 |
43 | return (
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default PHForm;
51 |
--------------------------------------------------------------------------------
/src/components/Forms/PHInput.tsx:
--------------------------------------------------------------------------------
1 | import { SxProps, TextField } from "@mui/material";
2 | import { Controller, useFormContext } from "react-hook-form";
3 |
4 | type TInputProps = {
5 | name: string;
6 | label?: string;
7 | type?: string;
8 | size?: "small" | "medium";
9 | fullWidth?: boolean;
10 | sx?: SxProps;
11 | placeholder?: string;
12 | required?: boolean;
13 | };
14 |
15 | const PHInput = ({
16 | name,
17 | label,
18 | type = "text",
19 | size = "small",
20 | fullWidth,
21 | sx,
22 | required,
23 | }: TInputProps) => {
24 | const { control } = useFormContext();
25 | return (
26 | (
30 |
43 | )}
44 | />
45 | );
46 | };
47 |
48 | export default PHInput;
49 |
--------------------------------------------------------------------------------
/src/components/Forms/PHSelectField.tsx:
--------------------------------------------------------------------------------
1 | import { MenuItem, SxProps, TextField } from "@mui/material";
2 | import React from "react";
3 | import { Controller, useFormContext } from "react-hook-form";
4 |
5 | interface ITextField {
6 | name: string;
7 | size?: "small" | "medium";
8 | placeholder?: string;
9 | label?: string;
10 | required?: boolean;
11 | fullWidth?: boolean;
12 | sx?: SxProps;
13 | items: string[];
14 | }
15 |
16 | const PHSelectField = ({
17 | items,
18 | name,
19 | label,
20 | size = "small",
21 | required,
22 | fullWidth = true,
23 | sx,
24 | }: ITextField) => {
25 | const { control, formState } = useFormContext();
26 | const isError = formState.errors[name] !== undefined;
27 |
28 | return (
29 | (
33 |
48 | {items.map((name) => (
49 |
52 | ))}
53 |
54 | )}
55 | />
56 | );
57 | };
58 |
59 | export default PHSelectField;
60 |
--------------------------------------------------------------------------------
/src/components/Forms/PHTimePicker.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SxProps } from "@mui/material";
3 | import { Controller, useFormContext } from "react-hook-form";
4 | import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
5 | import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
6 | import { TimePicker } from "@mui/x-date-pickers";
7 | import dayjs from "dayjs";
8 |
9 | interface ITimePicker {
10 | name: string;
11 | size?: "small" | "medium";
12 | placeholder?: string;
13 | label?: string;
14 | required?: boolean;
15 | fullWidth?: boolean;
16 | sx?: SxProps;
17 | }
18 |
19 | const PHTimePicker = ({
20 | name,
21 | label,
22 | size = "small",
23 | required,
24 | fullWidth = true,
25 | sx,
26 | }: ITimePicker) => {
27 | const { control, formState } = useFormContext();
28 | const isError = formState.errors[name] !== undefined;
29 |
30 | return (
31 | {
36 | return (
37 |
38 | onChange(time)}
43 | timezone="system"
44 | slotProps={{
45 | textField: {
46 | required: required,
47 | fullWidth: fullWidth,
48 | size: size,
49 | sx: {
50 | ...sx,
51 | },
52 | variant: "outlined",
53 | error: isError,
54 | helperText: isError
55 | ? (formState.errors[name]?.message as string)
56 | : "",
57 | },
58 | }}
59 | />
60 |
61 | );
62 | }}
63 | />
64 | );
65 | };
66 |
67 | export default PHTimePicker;
68 |
--------------------------------------------------------------------------------
/src/components/Shared/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Container, Stack, Typography } from "@mui/material";
2 | import Image from "next/image";
3 | import Link from "next/link";
4 | import facebookIcon from "@/assets/landing_page/facebook.png";
5 | import instagramIcon from "@/assets/landing_page/instagram.png";
6 | import twitterIcon from "@/assets/landing_page/twitter.png";
7 | import linkedIcon from "@/assets/landing_page/linkedin.png";
8 |
9 | const Footer = () => {
10 | return (
11 |
12 |
13 |
14 |
15 | Consultation
16 |
17 | Health Plans
18 | Medicine
19 | Diagnostics
20 | NGOs
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {/* */}
30 |
35 |
36 |
43 |
44 | ©2024 Ph HealthCare. All Rights Reserved.
45 |
46 |
53 | P
54 |
55 | H
56 | {" "}
57 | Health Care
58 |
59 |
60 | Privacy Policy! Terms & Conditions
61 |
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | export default Footer;
69 |
--------------------------------------------------------------------------------
/src/components/Shared/Navbar/Navbar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import useUserInfo from '@/hooks/useUserInfo';
4 | import { logoutUser } from '@/services/actions/logoutUser';
5 | import { Box, Button, Container, Stack, Typography } from '@mui/material';
6 | import Link from 'next/link';
7 | import { useRouter } from 'next/navigation';
8 |
9 | const Navbar = () => {
10 | const userInfo = useUserInfo();
11 | const router = useRouter();
12 |
13 | const handleLogOut = () => {
14 | logoutUser(router);
15 | };
16 |
17 | return (
18 |
23 |
24 |
30 |
36 | P
37 |
38 | H
39 | {' '}
40 | Health Care
41 |
42 |
43 |
44 |
49 | Consultation
50 |
51 |
52 | Diagnostics
53 |
54 | Doctors
55 |
56 |
57 | {userInfo?.userId ? (
58 |
63 | Dashboard
64 |
65 | ) : null}
66 |
67 |
68 | {userInfo?.userId ? (
69 |
76 | ) : (
77 |
80 | )}
81 |
82 |
83 |
84 | );
85 | };
86 |
87 | export default Navbar;
88 |
--------------------------------------------------------------------------------
/src/components/Shared/PHModal/PHAlert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Button from "@mui/material/Button";
3 | import Dialog from "@mui/material/Dialog";
4 | import DialogActions from "@mui/material/DialogActions";
5 | import DialogContent from "@mui/material/DialogContent";
6 | import DialogContentText from "@mui/material/DialogContentText";
7 | import DialogTitle from "@mui/material/DialogTitle";
8 |
9 | type TProps = {
10 | open: boolean;
11 | setOpen: React.Dispatch>;
12 | children: React.ReactNode;
13 | title: string;
14 | handleConfirm: () => void;
15 | handleCancel: () => void;
16 | };
17 |
18 | // ok
19 |
20 | export default function PHAlert({
21 | open,
22 | setOpen,
23 | title,
24 | handleConfirm,
25 | handleCancel,
26 | children,
27 | }: TProps) {
28 | return (
29 |
30 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/Shared/PHModal/PHFullScreenModal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import IconButton from '@mui/material/IconButton';
3 | import CloseIcon from '@mui/icons-material/Close';
4 | import Slide from '@mui/material/Slide';
5 | import { TransitionProps } from '@mui/material/transitions';
6 | import { DialogContent, DialogTitle, SxProps } from '@mui/material';
7 | import { BootstrapDialog } from './PHModal';
8 |
9 | type TModalProps = {
10 | open: boolean;
11 | setOpen: React.Dispatch>;
12 | title: string;
13 | children: React.ReactNode;
14 | sx?: SxProps;
15 | };
16 |
17 | const Transition = React.forwardRef(function Transition(
18 | props: TransitionProps & {
19 | children: React.ReactElement;
20 | },
21 | ref: React.Ref
22 | ) {
23 | return ;
24 | });
25 |
26 | export default function PHFullScreenModal({
27 | open = false,
28 | setOpen,
29 | title = '',
30 | children,
31 | sx,
32 | }: TModalProps) {
33 | const handleClose = () => {
34 | setOpen(false);
35 | };
36 |
37 | return (
38 |
39 |
47 |
51 | {title}
52 |
53 | theme.palette.grey[500],
61 | }}
62 | >
63 |
64 |
65 | {children}
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/Shared/PHModal/PHModal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import * as React from "react";
3 |
4 | import { SxProps, styled } from "@mui/material/styles";
5 | import Dialog from "@mui/material/Dialog";
6 | import DialogTitle from "@mui/material/DialogTitle";
7 | import DialogContent from "@mui/material/DialogContent";
8 |
9 | import IconButton from "@mui/material/IconButton";
10 | import CloseIcon from "@mui/icons-material/Close";
11 |
12 | export const BootstrapDialog = styled(Dialog)(({ theme }) => ({
13 | "& .MuiDialogContent-root": {
14 | padding: theme.spacing(2),
15 | },
16 | "& .MuiDialogActions-root": {
17 | padding: theme.spacing(1),
18 | },
19 | }));
20 |
21 | type TModalProps = {
22 | open: boolean;
23 | setOpen: React.Dispatch>;
24 | title: string;
25 | children: React.ReactNode;
26 | sx?: SxProps;
27 | };
28 |
29 | export default function PHModal({
30 | open = false,
31 | setOpen,
32 | title = "",
33 | children,
34 | sx,
35 | }: TModalProps) {
36 | const handleClose = () => {
37 | setOpen(false);
38 | };
39 |
40 | return (
41 |
42 |
48 |
49 | {title}
50 |
51 | theme.palette.grey[500],
59 | }}
60 | >
61 |
62 |
63 | {children}
64 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/Shared/PhChip/PhChips.tsx:
--------------------------------------------------------------------------------
1 | import { Chip } from '@mui/material';
2 | import React from 'react';
3 |
4 | type ChipType = 'error' | 'success' | 'warning' | 'info';
5 |
6 | const PhChips = ({ label, type }: { label: string; type: ChipType }) => {
7 | let chipStyles = {
8 | bgcolor: '#cdffe0',
9 | color: '#00592e',
10 | };
11 |
12 | if (type === 'success') {
13 | chipStyles = {
14 | bgcolor: '#cdffe0',
15 | color: '#00592e',
16 | };
17 | } else if (type === 'warning') {
18 | chipStyles = {
19 | bgcolor: '#fff3cd',
20 | color: '#856404',
21 | };
22 | } else if (type === 'info') {
23 | chipStyles = {
24 | bgcolor: '#d1ecf1',
25 | color: '#0c5460',
26 | };
27 | } else if (type === 'error') {
28 | chipStyles = {
29 | bgcolor: '#f8d7da',
30 | color: '#721c24',
31 | };
32 | }
33 |
34 | return ;
35 | };
36 |
37 | export default PhChips;
38 |
--------------------------------------------------------------------------------
/src/components/UI/AuthButton/AuthButton.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import useUserInfo from '@/hooks/useUserInfo';
4 | import { logoutUser } from '@/services/actions/logoutUser';
5 | import { getUserInfo } from '@/services/auth.services';
6 | import { Button } from '@mui/material';
7 | import Link from 'next/link';
8 | import { useRouter } from 'next/navigation';
9 |
10 | const AuthButton = () => {
11 | const userInfo = useUserInfo();
12 | const router = useRouter();
13 |
14 | const handleLogOut = () => {
15 | logoutUser(router);
16 | };
17 | return (
18 | <>
19 | {userInfo?.userId ? (
20 |
23 | ) : (
24 |
27 | )}
28 | >
29 | );
30 | };
31 |
32 | export default AuthButton;
33 |
--------------------------------------------------------------------------------
/src/components/UI/Doctor/DashedLine.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Box, styled } from '@mui/material';
4 | import React from 'react';
5 |
6 | const StyledDashedLine = styled(Box)(({ theme }) => ({
7 | borderBottom: '2px dashed',
8 | borderColor: theme.palette.secondary.main,
9 | marginTop: theme.spacing(4),
10 | marginBottom: theme.spacing(4),
11 | }));
12 |
13 | const DashedLine = () => {
14 | return (
15 | <>
16 |
17 | >
18 | );
19 | };
20 |
21 | export default DashedLine;
22 |
--------------------------------------------------------------------------------
/src/components/UI/Doctor/DoctorCard.tsx:
--------------------------------------------------------------------------------
1 | import { Doctor } from '@/types/doctor';
2 | import { Box, Button, Stack, Typography } from '@mui/material';
3 | import Image from 'next/image';
4 | import Link from 'next/link';
5 |
6 | const DoctorCard = ({ doctor }: { doctor: Doctor }) => {
7 | const placeholder =
8 | 'https://static.vecteezy.com/system/resources/thumbnails/026/489/224/small_2x/muslim-malay-woman-doctor-in-hospital-with-copy-space-ai-generated-photo.jpg';
9 |
10 | return (
11 |
12 |
18 |
19 |
28 |
29 |
30 |
31 |
32 | {doctor?.name}
33 |
34 |
35 | {doctor?.designation}
36 |
37 |
41 | {doctor?.doctorSpecialties?.length
42 | ? 'Specialties in' +
43 | ' ' +
44 | doctor?.doctorSpecialties?.map(
45 | (specialty) => specialty?.specialties?.title
46 | )
47 | : ''}
48 |
49 |
50 |
57 |
58 |
59 |
60 |
64 | Taka : {doctor?.apointmentFee}
65 |
66 |
74 | (incl. Vat)
75 |
76 |
77 |
78 | Per consultation
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | Working in
92 |
93 | {doctor?.currentWorkingPlace}
94 |
95 |
96 |
103 |
104 |
105 |
106 | Total Experience
107 |
108 |
109 | {doctor?.experience}+ Years
110 |
111 |
112 |
113 |
116 |
117 |
118 |
119 |
120 | );
121 | };
122 |
123 | export default DoctorCard;
124 |
--------------------------------------------------------------------------------
/src/components/UI/Doctor/ScrollCategory.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import { useGetAllSpecialtiesQuery } from '@/redux/api/specialtiesApi';
5 | import Tabs from '@mui/material/Tabs';
6 | import Tab from '@mui/material/Tab';
7 | import Box from '@mui/material/Box';
8 | import { useRouter } from 'next/navigation';
9 |
10 | const ScrollCategory = ({ specialties }: { specialties: string }) => {
11 | const { data } = useGetAllSpecialtiesQuery(undefined);
12 | const [value, setValue] = React.useState(specialties || '');
13 | const router = useRouter();
14 |
15 | const handleChange = (event: React.SyntheticEvent, newValue: string) => {
16 | setValue(newValue);
17 | router.push(`/doctors?specialties=${newValue}`);
18 | };
19 |
20 | return (
21 |
22 |
29 | {data?.map((specialty: any) => (
30 |
36 | ))}
37 |
38 |
39 | );
40 | };
41 |
42 | export default ScrollCategory;
43 |
--------------------------------------------------------------------------------
/src/components/UI/HomePage/HeroSection/HeroSection.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Container, Typography } from "@mui/material";
2 | import Image from "next/image";
3 | import assets from "@/assets";
4 |
5 | const HeroSection = () => {
6 | return (
7 |
14 |
15 |
23 |
24 |
25 |
26 | Healthier Hearts
27 |
28 |
29 | Come From
30 |
31 |
37 | Preventive Care
38 |
39 |
40 | Lorem ipsum dolor, sit amet consectetur adipisicing elit. Fugit eum
41 | iusto consequatur eius, doloribus nesciunt facere aliquid eveniet et.
42 | Rerum maiores saepe cupiditate repellat recusandae atque sed. Saepe,
43 | vitae id?
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
61 |
68 |
69 |
70 |
76 |
77 |
83 |
84 |
85 |
91 |
92 |
93 |
100 |
106 |
107 |
115 |
121 |
122 |
123 |
124 | );
125 | };
126 |
127 | export default HeroSection;
128 |
--------------------------------------------------------------------------------
/src/components/UI/HomePage/Specialist/Specialist.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Container, Stack, Typography } from '@mui/material';
2 | import Image from 'next/image';
3 | import Link from 'next/link';
4 |
5 | const Specialist = async () => {
6 | const res = await fetch('http://localhost:5000/api/v1/specialties', {
7 | next: {
8 | revalidate: 30,
9 | },
10 | });
11 | const { data: specialties } = await res.json();
12 | // console.log(specialties);
13 |
14 | return (
15 |
16 |
22 |
27 |
28 | Explore Treatments Across Specialties
29 |
30 |
31 | Experienced Doctors Across All Specialties
32 |
33 |
34 |
35 | {specialties.slice(0, 6).map((specialty: any) => (
36 |
61 |
67 |
68 |
74 | {specialty.title}
75 |
76 |
77 |
78 | ))}
79 |
80 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default Specialist;
94 |
--------------------------------------------------------------------------------
/src/components/UI/HomePage/Stats/Stats.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Box, Container, Grid, Typography } from "@mui/material";
4 |
5 | const Stats = () => {
6 | return (
7 |
8 |
15 |
16 |
17 |
23 | 180+
24 |
25 |
31 | Expert Doctors
32 |
33 |
34 |
35 |
41 | 26+
42 |
43 |
49 | Expert Services
50 |
51 |
52 |
53 |
59 | 10K+
60 |
61 |
67 | Happy Patients
68 |
69 |
70 |
71 |
77 | 150+
78 |
79 |
85 | Best Award Winners
86 |
87 |
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default Stats;
95 |
--------------------------------------------------------------------------------
/src/components/UI/HomePage/TopRatedDoctors/TopRatedDoctors.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Card,
5 | CardActions,
6 | CardContent,
7 | CardMedia,
8 | Container,
9 | Grid,
10 | Typography,
11 | } from '@mui/material';
12 | import Image from 'next/image';
13 | import LocationOnIcon from '@mui/icons-material/LocationOn';
14 | import Link from 'next/link';
15 |
16 | const TopRatedDoctors = async () => {
17 | const res = await fetch(
18 | 'http://localhost:5000/api/v1/doctor?page=1&limit=3'
19 | );
20 | const { data: doctors } = await res.json();
21 | // console.log(doctors);
22 | return (
23 |
31 |
32 |
33 | Our Top Rated Doctors
34 |
35 |
41 | Access to expert physicians and surgeons, advanced technologies
42 |
43 |
44 | and top-quality surgery facilities right here.
45 |
46 |
47 |
48 |
49 |
50 | {doctors.map((doctor: any) => (
51 |
52 |
53 |
65 |
71 |
72 |
73 |
78 | {doctor.name}
79 |
80 |
81 | {doctor.qualification}, {doctor.designation}
82 |
83 |
88 | {doctor.address}
89 |
90 |
91 |
98 |
99 |
100 |
101 |
102 |
103 | ))}
104 |
105 |
110 |
120 |
121 |
122 |
123 | );
124 | };
125 |
126 | export default TopRatedDoctors;
127 |
--------------------------------------------------------------------------------
/src/components/UI/VideoCall/VideoCall.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useState } from 'react';
4 | import AgoraUIKit from 'agora-react-uikit';
5 | import { Button, Stack } from '@mui/material';
6 | import VideoCallIcon from '@mui/icons-material/VideoCall';
7 | import Image from 'next/image';
8 | import { useRouter } from 'next/navigation';
9 |
10 | const VideoCall = ({ videoCallingId }: { videoCallingId: string }) => {
11 | const [startVideoCall, setStartVideoCall] = useState(false);
12 |
13 | const router = useRouter();
14 |
15 | const rtcProps = {
16 | appId: process.env.NEXT_PUBLIC_VIDEO_CALL_APP_ID || 'test',
17 | channel: videoCallingId, // your agora channel
18 | token: null, // use null or skip if using app in testing mode
19 | };
20 |
21 | const callbacks = {
22 | EndCall: () => {
23 | setStartVideoCall(false);
24 | router.push('/dashboard');
25 | },
26 | };
27 | return startVideoCall ? (
28 |
31 | ) : (
32 |
44 |
51 |
57 |
58 | );
59 | };
60 |
61 | export default VideoCall;
62 |
--------------------------------------------------------------------------------
/src/contants/authkey.ts:
--------------------------------------------------------------------------------
1 | export const authKey = "accessToken";
2 |
--------------------------------------------------------------------------------
/src/contants/role.ts:
--------------------------------------------------------------------------------
1 | export const USER_ROLE = {
2 | SUPER_ADMIN: "super_admin",
3 | ADMIN: "admin",
4 | DOCTOR: "doctor",
5 | PATIENT: "patient",
6 | };
7 |
--------------------------------------------------------------------------------
/src/helpers/axios/axiosBaseQuery.ts:
--------------------------------------------------------------------------------
1 | import { IMeta } from "@/types";
2 | import type { BaseQueryFn } from "@reduxjs/toolkit/query";
3 |
4 | import type { AxiosRequestConfig, AxiosError } from "axios";
5 | import { instance as axiosInstance } from "./axiosInstance";
6 |
7 | export const axiosBaseQuery =
8 | (
9 | { baseUrl }: { baseUrl: string } = { baseUrl: "" }
10 | ): BaseQueryFn<
11 | {
12 | url: string;
13 | method?: AxiosRequestConfig["method"];
14 | data?: AxiosRequestConfig["data"];
15 | params?: AxiosRequestConfig["params"];
16 | headers?: AxiosRequestConfig["headers"];
17 | meta?: IMeta;
18 | contentType?: string;
19 | },
20 | unknown,
21 | unknown
22 | > =>
23 | async ({ url, method, data, params, headers, contentType }) => {
24 | try {
25 | const result = await axiosInstance({
26 | url: baseUrl + url,
27 | method,
28 | data,
29 | params,
30 | headers: {
31 | "Content-Type": contentType || "application/json",
32 | },
33 | });
34 | return result;
35 | } catch (axiosError) {
36 | const err = axiosError as AxiosError;
37 | return {
38 | error: {
39 | status: err.response?.status,
40 | data: err.response?.data || err.message,
41 | },
42 | };
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/src/helpers/axios/axiosInstance.ts:
--------------------------------------------------------------------------------
1 | import { authKey } from '@/contants/authkey';
2 | import setAccessToken from '@/services/actions/setAccessToken';
3 | import { getNewAccessToken } from '@/services/auth.services';
4 | import { IGenericErrorResponse, ResponseSuccessType } from '@/types';
5 | import { getFromLocalStorage, setToLocalStorage } from '@/utils/local-storage';
6 | import axios from 'axios';
7 |
8 | const instance = axios.create();
9 | instance.defaults.headers.post['Content-Type'] = 'application/json';
10 | instance.defaults.headers['Accept'] = 'application/json';
11 | instance.defaults.timeout = 60000;
12 |
13 | // Add a request interceptor
14 | instance.interceptors.request.use(
15 | function (config) {
16 | // Do something before request is sent
17 | const accessToken = getFromLocalStorage(authKey);
18 |
19 | if (accessToken) {
20 | config.headers.Authorization = accessToken;
21 | }
22 | return config;
23 | },
24 | function (error) {
25 | // Do something with request error
26 | return Promise.reject(error);
27 | }
28 | );
29 |
30 | // Add a response interceptor
31 | instance.interceptors.response.use(
32 | //@ts-ignore
33 | function (response) {
34 | // Any status code that lie within the range of 2xx cause this function to trigger
35 | // Do something with response data
36 | const responseObject: ResponseSuccessType = {
37 | data: response?.data?.data,
38 | meta: response?.data?.meta,
39 | };
40 | return responseObject;
41 | },
42 | async function (error) {
43 | // Any status codes that falls outside the range of 2xx cause this function to trigger
44 | // Do something with response error
45 | // console.log(error);
46 | const config = error.config;
47 | // console.log(config);
48 | if (error?.response?.status === 500 && !config.sent) {
49 | config.sent = true;
50 | const response = await getNewAccessToken();
51 | const accessToken = response?.data?.accessToken;
52 | config.headers['Authorization'] = accessToken;
53 | setToLocalStorage(authKey, accessToken);
54 | setAccessToken(accessToken);
55 | return instance(config);
56 | } else {
57 | const responseObject: IGenericErrorResponse = {
58 | statusCode: error?.response?.data?.statusCode || 500,
59 | message:
60 | error?.response?.data?.message || 'Something went wrong!!!',
61 | errorMessages: error?.response?.data?.message,
62 | };
63 | // return Promise.reject(error);
64 | return responseObject;
65 | }
66 | }
67 | );
68 |
69 | export { instance };
70 |
--------------------------------------------------------------------------------
/src/hooks/useUserInfo.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { getFromLocalStorage } from '@/utils/local-storage';
3 | import { decodedToken } from '@/utils/jwt';
4 | import { authKey } from '@/contants/authkey';
5 | import { JwtPayload } from 'jwt-decode';
6 |
7 | const useUserInfo = (): any | string => {
8 | const [userInfo, setUserInfo] = useState('');
9 |
10 | useEffect(() => {
11 | const fetchUserInfo = () => {
12 | const authToken = getFromLocalStorage(authKey);
13 | if (authToken) {
14 | const decodedData: JwtPayload & { role: any } = decodedToken(
15 | authToken
16 | ) as JwtPayload & {
17 | role: any;
18 | };
19 | const userInfo: any = {
20 | ...decodedData,
21 | role: decodedData.role?.toLowerCase() || '',
22 | };
23 | setUserInfo(userInfo);
24 | } else {
25 | setUserInfo('');
26 | }
27 | };
28 |
29 | fetchUserInfo();
30 | }, []);
31 |
32 | return userInfo;
33 | };
34 |
35 | export default useUserInfo;
36 |
--------------------------------------------------------------------------------
/src/lib/Providers/Providers.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ThemeProvider } from "@mui/material";
4 | import { theme } from "../theme/theme";
5 | import { Provider } from "react-redux";
6 | import { store } from "@/redux/store";
7 |
8 | const Providers = ({ children }: { children: React.ReactNode }) => {
9 | return (
10 |
11 | {children}
12 |
13 | );
14 | };
15 |
16 | export default Providers;
17 |
--------------------------------------------------------------------------------
/src/lib/theme/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material/styles';
2 |
3 | export const theme = createTheme({
4 | palette: {
5 | primary: {
6 | main: '#1586FD',
7 | },
8 | secondary: {
9 | main: '#666f73',
10 | light: '#f8f8f8',
11 | },
12 | },
13 |
14 | components: {
15 | MuiButton: {
16 | defaultProps: {
17 | variant: 'contained',
18 | },
19 | styleOverrides: {
20 | root: {
21 | padding: '8px 24px',
22 | },
23 | },
24 | },
25 | MuiContainer: {
26 | defaultProps: {
27 | maxWidth: 'lg',
28 | },
29 | },
30 | },
31 | typography: {
32 | body1: {
33 | color: '#0B1134CC',
34 | },
35 | },
36 | });
37 |
38 | theme.shadows[1] = '0px 5px 22px lightgray';
39 |
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { jwtDecode } from 'jwt-decode';
2 | import { cookies } from 'next/headers';
3 | import { NextResponse } from 'next/server';
4 | import type { NextRequest } from 'next/server';
5 |
6 | type Role = keyof typeof roleBasedPrivateRoutes;
7 |
8 | const AuthRoutes = ['/login', '/register'];
9 | const commonPrivateRoutes = [
10 | '/dashboard',
11 | '/dashboard/change-password',
12 | '/doctors',
13 | ];
14 | const roleBasedPrivateRoutes = {
15 | PATIENT: [/^\/dashboard\/patient/],
16 | DOCTOR: [/^\/dashboard\/doctor/],
17 | ADMIN: [/^\/dashboard\/admin/],
18 | SUPER_ADMIN: [/^\/dashboard\/super-admin/],
19 | };
20 |
21 | export function middleware(request: NextRequest) {
22 | const { pathname } = request.nextUrl;
23 |
24 | const accessToken = cookies().get('accessToken')?.value;
25 |
26 | if (!accessToken) {
27 | if (AuthRoutes.includes(pathname)) {
28 | return NextResponse.next();
29 | } else {
30 | return NextResponse.redirect(new URL('/login', request.url));
31 | }
32 | }
33 |
34 | if (
35 | accessToken &&
36 | (commonPrivateRoutes.includes(pathname) ||
37 | commonPrivateRoutes.some((route) => pathname.startsWith(route)))
38 | ) {
39 | return NextResponse.next();
40 | }
41 |
42 | let decodedData = null;
43 |
44 | if (accessToken) {
45 | decodedData = jwtDecode(accessToken) as any;
46 | }
47 |
48 | const role = decodedData?.role;
49 |
50 | // if (role === 'ADMIN' && pathname.startsWith('/dashboard/admin')) {
51 | // return NextResponse.next();
52 | // }
53 |
54 | if (role && roleBasedPrivateRoutes[role as Role]) {
55 | const routes = roleBasedPrivateRoutes[role as Role];
56 | if (routes.some((route) => pathname.match(route))) {
57 | return NextResponse.next();
58 | }
59 | }
60 |
61 | return NextResponse.redirect(new URL('/', request.url));
62 | }
63 |
64 | export const config = {
65 | matcher: ['/login', '/register', '/dashboard/:page*', '/doctors/:page*'],
66 | };
67 |
--------------------------------------------------------------------------------
/src/redux/api/appointmentApi.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from "./baseApi";
2 | import { tagTypes } from "../tag-types";
3 | import { IMeta } from "@/types/common";
4 |
5 | export const appointmentApi = baseApi.injectEndpoints({
6 | endpoints: (build) => ({
7 | createAppointment: build.mutation({
8 | query: (data) => ({
9 | url: "/appointment",
10 | method: "POST",
11 | data,
12 | }),
13 | invalidatesTags: [tagTypes.appointment],
14 | }),
15 | getAllAppointments: build.query({
16 | query: (arg: Record) => {
17 | return {
18 | url: "/appointment",
19 | method: "GET",
20 | params: arg,
21 | };
22 | },
23 | transformResponse: (response: [], meta: IMeta) => {
24 | return {
25 | appointments: response,
26 | meta,
27 | };
28 | },
29 | providesTags: [tagTypes.appointment],
30 | }),
31 | getMyAppointments: build.query({
32 | query: (arg: Record) => {
33 | return {
34 | url: "/appointment/my-appointments",
35 | method: "GET",
36 | params: arg,
37 | };
38 | },
39 | transformResponse: (response: [], meta: IMeta) => {
40 | return {
41 | appointments: response,
42 | meta,
43 | };
44 | },
45 | providesTags: [tagTypes.appointment],
46 | }),
47 | getAppointment: build.query({
48 | query: (id: string | string[] | undefined) => ({
49 | url: `/appointment/${id}`,
50 | method: "GET",
51 | }),
52 | providesTags: [tagTypes.appointment],
53 | }),
54 | appointmentStatusChange: build.mutation({
55 | query: (data) => ({
56 | url: `/appointment/status/${data.id}`,
57 | method: "PATCH",
58 | data: data.body,
59 | }),
60 | invalidatesTags: [tagTypes.appointment],
61 | }),
62 | deleteAppointment: build.mutation({
63 | query: (id) => ({
64 | url: `/appointment/soft/${id}`,
65 | method: "DELETE",
66 | }),
67 | invalidatesTags: [tagTypes.appointment],
68 | }),
69 | }),
70 | });
71 |
72 | export const {
73 | useCreateAppointmentMutation,
74 | useGetAllAppointmentsQuery,
75 | useGetMyAppointmentsQuery,
76 | useGetAppointmentQuery,
77 | useAppointmentStatusChangeMutation,
78 | useDeleteAppointmentMutation,
79 | } = appointmentApi;
80 |
--------------------------------------------------------------------------------
/src/redux/api/authApi.ts:
--------------------------------------------------------------------------------
1 | import { tagTypes } from '../tag-types';
2 | import { baseApi } from './baseApi';
3 | const AUTH_URL = '/auth';
4 |
5 | export const authApi = baseApi.injectEndpoints({
6 | endpoints: (build) => ({
7 | userLogin: build.mutation({
8 | query: (loginData) => ({
9 | url: `${AUTH_URL}/login`,
10 | method: 'POST',
11 | data: loginData,
12 | }),
13 | invalidatesTags: [tagTypes.user],
14 | }),
15 | changePassword: build.mutation({
16 | query: (data) => ({
17 | url: `${AUTH_URL}/change-password`,
18 | method: 'POST',
19 | contentType: 'application/json',
20 | data: data,
21 | }),
22 | invalidatesTags: [tagTypes.user],
23 | }),
24 | forgotPassword: build.mutation({
25 | query: (data) => ({
26 | url: `${AUTH_URL}/forgot-password`,
27 | method: 'POST',
28 | data: data,
29 | }),
30 | invalidatesTags: [tagTypes.user],
31 | }),
32 | resetPassword: build.mutation({
33 | query: (data) => ({
34 | url: `${AUTH_URL}/reset-password`,
35 | method: 'POST',
36 | data: data,
37 | }),
38 | invalidatesTags: [tagTypes.user],
39 | }),
40 | }),
41 | });
42 |
43 | export const {
44 | useUserLoginMutation,
45 | useChangePasswordMutation,
46 | useForgotPasswordMutation,
47 | useResetPasswordMutation,
48 | } = authApi;
49 |
--------------------------------------------------------------------------------
/src/redux/api/baseApi.ts:
--------------------------------------------------------------------------------
1 | import { axiosBaseQuery } from "@/helpers/axios/axiosBaseQuery";
2 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
3 | import { tagTypesList } from "../tag-types";
4 |
5 | // Define a service using a base URL and expected endpoints
6 | export const baseApi = createApi({
7 | reducerPath: "api",
8 | baseQuery: axiosBaseQuery({ baseUrl: "http://localhost:5000/api/v1" }),
9 | endpoints: () => ({}),
10 | tagTypes: tagTypesList,
11 | });
12 |
--------------------------------------------------------------------------------
/src/redux/api/doctorApi.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from './baseApi';
2 | import { tagTypes } from '../tag-types';
3 | import { IMeta } from '@/types/common';
4 | import { IDoctor } from '@/types/doctor';
5 |
6 | export const doctorApi = baseApi.injectEndpoints({
7 | endpoints: (build) => ({
8 | createDoctor: build.mutation({
9 | query: (data) => ({
10 | url: '/user/create-doctor',
11 | method: 'POST',
12 | contentType: 'multipart/form-data',
13 | data,
14 | }),
15 | invalidatesTags: [tagTypes.doctor],
16 | }),
17 |
18 | getAllDoctors: build.query({
19 | query: (arg: Record) => ({
20 | url: '/doctor',
21 | method: 'GET',
22 | params: arg,
23 | }),
24 | transformResponse: (response: IDoctor[], meta: IMeta) => {
25 | return {
26 | doctors: response,
27 | meta,
28 | };
29 | },
30 | providesTags: [tagTypes.doctor],
31 | }),
32 |
33 | deleteDoctor: build.mutation({
34 | query: (id) => ({
35 | url: `/doctor/soft/${id}`,
36 | method: 'DELETE',
37 | }),
38 | invalidatesTags: [tagTypes.doctor],
39 | }),
40 | //get single doctor
41 | getDoctor: build.query({
42 | query: (id: string | string[] | undefined) => ({
43 | url: `/doctor/${id}`,
44 | method: 'GET',
45 | }),
46 | providesTags: [tagTypes.doctor],
47 | }),
48 | // update a doctor
49 | updateDoctor: build.mutation({
50 | query: (data) => {
51 | console.log(data);
52 | return {
53 | url: `/doctor/${data.id}`,
54 | method: 'PATCH',
55 | data: data.body,
56 | };
57 | },
58 | invalidatesTags: [tagTypes.doctor, tagTypes.user],
59 | }),
60 | }),
61 | });
62 |
63 | export const {
64 | useCreateDoctorMutation,
65 | useGetAllDoctorsQuery,
66 | useDeleteDoctorMutation,
67 | useGetDoctorQuery,
68 | useUpdateDoctorMutation,
69 | } = doctorApi;
70 |
--------------------------------------------------------------------------------
/src/redux/api/doctorScheduleApi.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from './baseApi';
2 | import { tagTypes } from '../tag-types';
3 | import { IMeta } from '@/types/common';
4 |
5 | export const doctorScheduleApi = baseApi.injectEndpoints({
6 | endpoints: (build) => ({
7 | createDoctorSchedule: build.mutation({
8 | query: (data) => ({
9 | url: '/doctor-schedule',
10 | method: 'POST',
11 | data,
12 | }),
13 | invalidatesTags: [tagTypes.doctorSchedule],
14 | }),
15 | getAllDoctorSchedules: build.query({
16 | query: (arg: Record) => {
17 | return {
18 | url: '/doctor-schedule',
19 | method: 'GET',
20 | params: arg,
21 | };
22 | },
23 | transformResponse: (response: [], meta: IMeta) => {
24 | return {
25 | doctorSchedules: response,
26 | meta,
27 | };
28 | },
29 | providesTags: [tagTypes.doctorSchedule],
30 | }),
31 | getDoctorSchedule: build.query({
32 | query: (id: string | string[] | undefined) => ({
33 | url: `/doctor-schedule/${id}`,
34 | method: 'GET',
35 | }),
36 | providesTags: [tagTypes.doctorSchedule],
37 | }),
38 | getMySchedule: build.query({
39 | query: () => ({
40 | url: '/doctor-schedule/my-schedules',
41 | method: 'GET',
42 | }),
43 | providesTags: [tagTypes.doctorSchedule],
44 | }),
45 |
46 | deleteDoctorSchedule: build.mutation({
47 | query: (id: string) => ({
48 | url: `/doctor-schedule/${id}`,
49 | method: 'DELETE',
50 | }),
51 | invalidatesTags: [tagTypes.doctorSchedule],
52 | }),
53 | }),
54 | });
55 |
56 | export const {
57 | useCreateDoctorScheduleMutation,
58 | useGetAllDoctorSchedulesQuery,
59 | useGetDoctorScheduleQuery,
60 | useGetMyScheduleQuery,
61 | useDeleteDoctorScheduleMutation,
62 | } = doctorScheduleApi;
63 |
--------------------------------------------------------------------------------
/src/redux/api/myProfile.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from './baseApi';
2 | import { tagTypes } from '../tag-types';
3 |
4 | export const profileAPi = baseApi.injectEndpoints({
5 | endpoints: (build) => ({
6 | getMYProfile: build.query({
7 | query: () => {
8 | return {
9 | url: '/user/me',
10 | method: 'GET',
11 | };
12 | },
13 | providesTags: [tagTypes.user],
14 | }),
15 | updateMYProfile: build.mutation({
16 | query: (data) => {
17 | return {
18 | url: '/user/update-my-profile',
19 | method: 'PATCH',
20 | data,
21 | contentType: 'multipart/form-data',
22 | };
23 | },
24 | invalidatesTags: [tagTypes.user],
25 | }),
26 | }),
27 | });
28 |
29 | export const { useGetMYProfileQuery, useUpdateMYProfileMutation } = profileAPi;
30 |
--------------------------------------------------------------------------------
/src/redux/api/paymentApi.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from './baseApi';
2 | import { tagTypes } from '../tag-types';
3 |
4 | export const BASE_STUDENT_SEMESTER_PAYMENT = '/student-semester-payments';
5 |
6 | const paymentApi = baseApi.injectEndpoints({
7 | endpoints: (build) => ({
8 | initialPayment: build.mutation({
9 | query: (id: string) => ({
10 | url: `/payment/init/${id}`,
11 | method: 'POST',
12 | }),
13 | invalidatesTags: [tagTypes.payment],
14 | }),
15 | }),
16 | });
17 |
18 | export const { useInitialPaymentMutation } = paymentApi;
19 |
20 | export default paymentApi;
21 |
--------------------------------------------------------------------------------
/src/redux/api/scheduleApi.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from "./baseApi";
2 | import { tagTypes } from "../tag-types";
3 | import { IMeta } from "@/types/common";
4 |
5 | export const scheduleApi = baseApi.injectEndpoints({
6 | endpoints: (build) => ({
7 | createSchedule: build.mutation({
8 | query: (data) => ({
9 | url: "/schedule",
10 | method: "POST",
11 | data,
12 | }),
13 | invalidatesTags: [tagTypes.schedule],
14 | }),
15 | getAllSchedules: build.query({
16 | query: (arg: Record) => {
17 | return {
18 | url: "/schedule",
19 | method: "GET",
20 | params: arg,
21 | };
22 | },
23 | transformResponse: (response: [], meta: IMeta) => {
24 | return {
25 | schedules: response,
26 | meta,
27 | };
28 | },
29 | providesTags: [tagTypes.schedule],
30 | }),
31 |
32 | deleteSchedule: build.mutation({
33 | query: (id) => ({
34 | url: `/schedule/${id}`,
35 | method: "DELETE",
36 | }),
37 | invalidatesTags: [tagTypes.schedule],
38 | }),
39 | }),
40 | });
41 |
42 | export const {
43 | useCreateScheduleMutation,
44 | useGetAllSchedulesQuery,
45 | useDeleteScheduleMutation,
46 | } = scheduleApi;
47 |
--------------------------------------------------------------------------------
/src/redux/api/specialtiesApi.ts:
--------------------------------------------------------------------------------
1 | import { tagTypes } from "../tag-types";
2 | import { baseApi } from "./baseApi";
3 |
4 | const specialtiesApi = baseApi.injectEndpoints({
5 | endpoints: (build) => ({
6 | createSpecialty: build.mutation({
7 | query: (data) => ({
8 | url: "/specialties",
9 | method: "POST",
10 | contentType: "multipart/form-data",
11 | data,
12 | }),
13 | invalidatesTags: [tagTypes.specialties],
14 | }),
15 |
16 | getAllSpecialties: build.query({
17 | query: () => ({
18 | url: "/specialties",
19 | method: "GET",
20 | }),
21 | providesTags: [tagTypes.specialties],
22 | }),
23 |
24 | deleteSpecialty: build.mutation({
25 | query: (id) => ({
26 | url: `/specialties/${id}`,
27 | method: "DELETE",
28 | }),
29 | invalidatesTags: [tagTypes.specialties],
30 | }),
31 | }),
32 | });
33 |
34 | export const {
35 | useCreateSpecialtyMutation,
36 | useGetAllSpecialtiesQuery,
37 | useDeleteSpecialtyMutation,
38 | } = specialtiesApi;
39 |
--------------------------------------------------------------------------------
/src/redux/api/userApi.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from "./baseApi";
2 | import { tagTypes } from "../tag-types";
3 |
4 | export const userApi = baseApi.injectEndpoints({
5 | endpoints: (build) => ({
6 | getSingleUser: build.query({
7 | query: () => ({
8 | url: "/user/me",
9 | method: "GET",
10 | }),
11 | providesTags: [tagTypes.user],
12 | }),
13 | }),
14 | });
15 |
16 | export const { useGetSingleUserQuery } = userApi;
17 |
--------------------------------------------------------------------------------
/src/redux/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import type { TypedUseSelectorHook } from "react-redux";
3 | import type { RootState, AppDispatch } from "./store";
4 | import { useEffect, useState } from "react";
5 |
6 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
7 | export const useAppDispatch: () => AppDispatch = useDispatch;
8 | export const useAppSelector: TypedUseSelectorHook = useSelector;
9 |
10 | type TDebouncedProps = {
11 | searchQuery: string;
12 | delay: number;
13 | };
14 |
15 | export const useDebounced = ({ searchQuery, delay }: TDebouncedProps) => {
16 | const [debouncedValue, setDebouncedValue] = useState(searchQuery);
17 |
18 | useEffect(() => {
19 | const handler = setTimeout(() => {
20 | setDebouncedValue(searchQuery);
21 | }, delay);
22 |
23 | return () => {
24 | clearTimeout(handler);
25 | };
26 | }, [searchQuery, delay]);
27 |
28 | return debouncedValue;
29 | };
30 |
--------------------------------------------------------------------------------
/src/redux/rootReducer.ts:
--------------------------------------------------------------------------------
1 | import { baseApi } from "./api/baseApi";
2 |
3 | export const reducer = {
4 | [baseApi.reducerPath]: baseApi.reducer,
5 | };
6 |
--------------------------------------------------------------------------------
/src/redux/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import { reducer } from "./rootReducer";
3 | import { baseApi } from "./api/baseApi";
4 | export const store = configureStore({
5 | reducer,
6 | middleware: (getDefaultMiddleware) =>
7 | getDefaultMiddleware().concat(baseApi.middleware),
8 | });
9 |
10 | // Infer the `RootState` and `AppDispatch` types from the store itself
11 | export type RootState = ReturnType;
12 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
13 | export type AppDispatch = typeof store.dispatch;
14 |
--------------------------------------------------------------------------------
/src/redux/tag-types.ts:
--------------------------------------------------------------------------------
1 | export enum tagTypes {
2 | specialties = 'specialties',
3 | admin = 'admin',
4 | doctor = 'doctor',
5 | patient = 'patient',
6 | schedule = 'schedule',
7 | appointment = 'appointment',
8 | doctorSchedule = 'doctorSchedule',
9 | user = 'user',
10 | prescription = 'prescription',
11 | review = 'review',
12 | payment = 'payment',
13 | }
14 |
15 | export const tagTypesList = [
16 | tagTypes.specialties,
17 | tagTypes.admin,
18 | tagTypes.doctor,
19 | tagTypes.patient,
20 | tagTypes.schedule,
21 | tagTypes.appointment,
22 | tagTypes.doctorSchedule,
23 | tagTypes.user,
24 | tagTypes.prescription,
25 | tagTypes.review,
26 | tagTypes.payment,
27 | ];
28 |
--------------------------------------------------------------------------------
/src/services/actions/deleteCookies.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { cookies } from 'next/headers';
4 |
5 | export const deleteCookies = (keys: string[]) => {
6 | keys.forEach((key) => {
7 | cookies().delete(key);
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/src/services/actions/logoutUser.ts:
--------------------------------------------------------------------------------
1 | import { authKey } from '@/contants/authkey';
2 | import { deleteCookies } from './deleteCookies';
3 | import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
4 |
5 | export const logoutUser = (router: AppRouterInstance) => {
6 | localStorage.removeItem(authKey);
7 | deleteCookies([authKey, 'refreshToken']);
8 | router.push('/');
9 | router.refresh();
10 | };
11 |
--------------------------------------------------------------------------------
/src/services/actions/registerPatient.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | export const registerPatient = async (formData: FormData) => {
4 | const res = await fetch(
5 | `${process.env.NEXT_PUBLIC_BACKEND_API_URL}/user/create-patient`,
6 | {
7 | method: "POST",
8 | body: formData,
9 | cache: "no-store",
10 | }
11 | );
12 |
13 | const patientInfo = await res.json();
14 | return patientInfo;
15 | };
16 |
--------------------------------------------------------------------------------
/src/services/actions/setAccessToken.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { cookies } from 'next/headers';
4 |
5 | import { authKey } from '@/contants/authkey';
6 | import { redirect } from 'next/navigation';
7 |
8 | const setAccessToken = (token: string, option?: any) => {
9 | cookies().set(authKey, token);
10 | if (option && option.passwordChangeRequired) {
11 | redirect('/dashboard/change-password');
12 | }
13 | if (option && !option.passwordChangeRequired && option.redirect) {
14 | redirect(option.redirect);
15 | }
16 | };
17 |
18 | export default setAccessToken;
19 |
--------------------------------------------------------------------------------
/src/services/actions/userLogin.ts:
--------------------------------------------------------------------------------
1 | // "use server";
2 |
3 | import { FieldValues } from 'react-hook-form';
4 | import setAccessToken from './setAccessToken';
5 |
6 | export const userLogin = async (data: FieldValues) => {
7 | const res = await fetch(
8 | `${process.env.NEXT_PUBLIC_BACKEND_API_URL}/auth/login`,
9 | {
10 | method: 'POST',
11 | headers: {
12 | 'Content-Type': 'application/json',
13 | },
14 | body: JSON.stringify(data),
15 | credentials: 'include',
16 | // cache: "no-store",
17 | }
18 | );
19 | const userInfo = await res.json();
20 |
21 | const passwordChangeRequired = userInfo.data.needPasswordChange;
22 |
23 | if (userInfo.data.accessToken) {
24 | setAccessToken(userInfo.data.accessToken, {
25 | redirect: '/dashboard',
26 | passwordChangeRequired,
27 | });
28 | }
29 |
30 | return userInfo;
31 | };
32 |
--------------------------------------------------------------------------------
/src/services/auth.services.ts:
--------------------------------------------------------------------------------
1 | import { authKey } from '@/contants/authkey';
2 | import { instance as axiosInstance } from '@/helpers/axios/axiosInstance';
3 | import { decodedToken } from '@/utils/jwt';
4 |
5 | import {
6 | getFromLocalStorage,
7 | removeFromLocalStorage,
8 | setToLocalStorage,
9 | } from '@/utils/local-storage';
10 |
11 | export const storeUserInfo = ({ accessToken }: { accessToken: string }) => {
12 | // console.log(accessToken);
13 | return setToLocalStorage(authKey, accessToken);
14 | };
15 |
16 | export const getUserInfo = () => {
17 | const authToken = getFromLocalStorage(authKey);
18 | // console.log(authToken);
19 | if (authToken) {
20 | const decodedData: any = decodedToken(authToken);
21 | return {
22 | ...decodedData,
23 | role: decodedData?.role?.toLowerCase(),
24 | };
25 | } else {
26 | return '';
27 | }
28 | };
29 |
30 | export const isLoggedIn = () => {
31 | const authToken = getFromLocalStorage(authKey);
32 | if (authToken) {
33 | return !!authToken;
34 | }
35 | };
36 |
37 | export const removeUser = () => {
38 | return removeFromLocalStorage(authKey);
39 | };
40 |
41 | export const getNewAccessToken = async () => {
42 | return await axiosInstance({
43 | url: 'http://localhost:5000/api/v1/auth/refresh-token',
44 | method: 'POST',
45 | headers: { 'Content-Type': 'application/json' },
46 | withCredentials: true,
47 | });
48 | };
49 |
--------------------------------------------------------------------------------
/src/types/common.ts:
--------------------------------------------------------------------------------
1 | import { USER_ROLE } from "@/contants/role";
2 | import { SvgIconTypeMap } from "@mui/material";
3 | import { OverridableComponent } from "@mui/material/OverridableComponent";
4 |
5 | export type IMeta = {
6 | page: number;
7 | limit: number;
8 | total: number;
9 | };
10 |
11 | export type UserRole = keyof typeof USER_ROLE;
12 |
13 | export interface DrawerItem {
14 | title: string;
15 | path: string;
16 | parentPath?: string;
17 | icon?: OverridableComponent> & { muiName: string };
18 | child?: DrawerItem[];
19 | }
20 |
21 | export type ResponseSuccessType = {
22 | data: any;
23 | meta?: IMeta;
24 | };
25 |
26 | export type IGenericErrorResponse = {
27 | statusCode: number;
28 | message: string;
29 | errorMessages: IGenericErrorMessage[];
30 | };
31 |
32 | export type IGenericErrorMessage = {
33 | path: string | number;
34 | message: string;
35 | };
36 |
37 | export const Gender = ["MALE", "FEMALE"];
38 |
--------------------------------------------------------------------------------
/src/types/doctor/index.ts:
--------------------------------------------------------------------------------
1 | export interface Doctor {
2 | id: string;
3 | email: string;
4 | name: string;
5 | profilePhoto: string;
6 | contactNumber: string;
7 | address: string;
8 | registrationNumber: string;
9 | experience: number;
10 | gender: 'MALE' | 'FEMALE' | 'OTHER';
11 | apointmentFee: number;
12 | qualification: string;
13 | currentWorkingPlace: string;
14 | designation: string;
15 | isDeleted: boolean;
16 | createdAt: string;
17 | updatedAt: string;
18 | averageRating: number;
19 | review: any[]; // You may want to specify the structure of the review object if known
20 | doctorSpecialties: DoctorSpecialty[];
21 | }
22 |
23 | export interface DoctorSpecialty {
24 | specialtiesId: string;
25 | doctorId: string;
26 | specialties: any; // You may want to specify the structure of the specialties object if known
27 | }
28 |
29 | export interface IDoctor {
30 | id: string;
31 | name: string;
32 | profilePhoto: string;
33 | contactNumber: string;
34 | address: string;
35 | registrationNumber: string;
36 | experience: number | undefined;
37 | gender: 'MALE' | 'FEMALE';
38 | apointmentFee: number | undefined;
39 | qualification: string;
40 | currentWorkingPlace: string;
41 | designation: string;
42 | specialties?: ISpecialties[];
43 | }
44 |
45 | export interface ISpecialties {
46 | specialtiesId: string;
47 | isDeleted?: null;
48 | }
49 |
50 | export interface IDoctorFormData {
51 | doctor: IDoctor;
52 | password: string;
53 | }
54 |
--------------------------------------------------------------------------------
/src/types/doctorSchedules/index.tsx:
--------------------------------------------------------------------------------
1 | export interface DoctorSchedule {
2 | doctorId: string;
3 | scheduleId: string;
4 | isBooked: boolean;
5 | createdAt: string;
6 | updatedAt: string;
7 | appointmentId: string | null;
8 | doctor: Doctor;
9 | schedule: Schedule;
10 | }
11 |
12 | export interface Doctor {
13 | id: string;
14 | email: string;
15 | name: string;
16 | profilePhoto: string;
17 | contactNumber: string;
18 | address: string;
19 | registrationNumber: string;
20 | experience: number;
21 | gender: string;
22 | appointmentFee: number;
23 | qualification: string;
24 | currentWorkingPlace: string;
25 | designation: string;
26 | isDeleted: boolean;
27 | createdAt: string;
28 | updatedAt: string;
29 | averageRating: number;
30 | }
31 |
32 | export interface Schedule {
33 | id: string;
34 | startDate: string;
35 | endDate: string;
36 | createdAt: string;
37 | updatedAt: string;
38 | }
39 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./common";
2 |
--------------------------------------------------------------------------------
/src/types/schedule/index.ts:
--------------------------------------------------------------------------------
1 | export type ISchedule = {
2 | [x: string]: any;
3 | id?: string;
4 | startDate: string;
5 | endDate: string;
6 | };
7 |
8 | export type IScheduleFrom = {
9 | startDate: Date;
10 | endDate: Date;
11 | startTime: string;
12 | endTime: string;
13 | };
14 |
--------------------------------------------------------------------------------
/src/types/specialties/specialties.ts:
--------------------------------------------------------------------------------
1 | export type ISpecialties = {
2 | title: string;
3 | icon?: string;
4 | id?: string;
5 | };
6 |
--------------------------------------------------------------------------------
/src/utils/dateFormatter.ts:
--------------------------------------------------------------------------------
1 | export const dateFormatter = (value: string) => {
2 | const date = new Date(value);
3 | // Extract year, month, and day
4 | const year = date.getFullYear();
5 | const month = (date.getMonth() + 1).toString().padStart(2, "0"); // Adding 1 because getMonth() returns zero-based month index
6 | const day = date.getDate().toString().padStart(2, "0");
7 | // Construct the desired format
8 | const formattedDate = `${year}-${month}-${day}`;
9 |
10 | return formattedDate;
11 | };
12 |
--------------------------------------------------------------------------------
/src/utils/drawerItems.ts:
--------------------------------------------------------------------------------
1 | import { USER_ROLE } from '@/contants/role';
2 | import { DrawerItem, UserRole } from '@/types';
3 |
4 | //icons
5 | import DashboardIcon from '@mui/icons-material/Dashboard';
6 | import GroupIcon from '@mui/icons-material/Group';
7 | import MedicalInformationIcon from '@mui/icons-material/MedicalInformation';
8 | import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
9 | import ReviewsIcon from '@mui/icons-material/Reviews';
10 | import AirlineSeatIndividualSuiteIcon from '@mui/icons-material/AirlineSeatIndividualSuite';
11 | import TryIcon from '@mui/icons-material/Try';
12 | import PersonIcon from '@mui/icons-material/Person';
13 | import KeyIcon from '@mui/icons-material/Key';
14 | import BookOnlineIcon from '@mui/icons-material/BookOnline';
15 | import ReceiptLongIcon from '@mui/icons-material/ReceiptLong';
16 | import AttachMoneyIcon from '@mui/icons-material/AttachMoney';
17 |
18 | export const drawerItems = (role: UserRole): DrawerItem[] => {
19 | const roleMenus: DrawerItem[] = [];
20 |
21 | const defaultMenus = [
22 | {
23 | title: 'Profile',
24 | path: `${role}/profile`,
25 | icon: PersonIcon,
26 | },
27 | {
28 | title: 'Change Password',
29 | path: `change-password`,
30 | icon: KeyIcon,
31 | },
32 | ];
33 |
34 | switch (role) {
35 | case USER_ROLE.SUPER_ADMIN:
36 | roleMenus.push(
37 | {
38 | title: 'Dashboard',
39 | path: `${role}`,
40 | icon: DashboardIcon,
41 | },
42 | {
43 | title: 'Manage Users',
44 | path: `${role}/manage-users`,
45 | icon: GroupIcon,
46 | }
47 | );
48 | break;
49 |
50 | case USER_ROLE.ADMIN:
51 | roleMenus.push(
52 | {
53 | title: 'Dashboard',
54 | path: `${role}`,
55 | icon: DashboardIcon,
56 | },
57 | {
58 | title: 'Specialties',
59 | path: `${role}/specialties`,
60 | icon: TryIcon,
61 | },
62 | {
63 | title: 'Doctors',
64 | path: `${role}/doctors`,
65 | icon: MedicalInformationIcon,
66 | },
67 | {
68 | title: 'Schedules',
69 | path: `${role}/schedules`,
70 | icon: CalendarMonthIcon,
71 | },
72 | {
73 | title: 'Appointments',
74 | path: `${role}/appointments`,
75 | icon: BookOnlineIcon,
76 | },
77 | {
78 | title: 'Reviews',
79 | path: `${role}/reviews`,
80 | icon: ReviewsIcon,
81 | }
82 | );
83 | break;
84 |
85 | case USER_ROLE.DOCTOR:
86 | roleMenus.push(
87 | {
88 | title: 'Dashboard',
89 | path: `${role}`,
90 | icon: DashboardIcon,
91 | },
92 | {
93 | title: 'Schedules',
94 | path: `${role}/schedules`,
95 | icon: CalendarMonthIcon,
96 | },
97 | {
98 | title: 'Appointments',
99 | path: `${role}/appointment`,
100 | icon: BookOnlineIcon,
101 | }
102 | );
103 | break;
104 |
105 | case USER_ROLE.PATIENT:
106 | roleMenus.push(
107 | {
108 | title: 'Appointments',
109 | path: `${role}/appointments`,
110 | icon: BookOnlineIcon,
111 | },
112 | {
113 | title: 'Prescriptions',
114 | path: `${role}/prescriptions`,
115 | icon: ReceiptLongIcon,
116 | },
117 | {
118 | title: 'Payment History',
119 | path: `${role}/payment-history`,
120 | icon: AttachMoneyIcon,
121 | }
122 | );
123 | break;
124 |
125 | default:
126 | break;
127 | }
128 |
129 | return [...roleMenus, ...defaultMenus];
130 | };
131 |
--------------------------------------------------------------------------------
/src/utils/jwt.ts:
--------------------------------------------------------------------------------
1 | import { jwtDecode } from "jwt-decode";
2 |
3 | export const decodedToken = (token: string) => {
4 | return jwtDecode(token);
5 | };
6 |
--------------------------------------------------------------------------------
/src/utils/local-storage.ts:
--------------------------------------------------------------------------------
1 | export const setToLocalStorage = (key: string, token: string) => {
2 | if (!key || typeof window === "undefined") {
3 | return "";
4 | }
5 | return localStorage.setItem(key, token);
6 | };
7 |
8 | export const getFromLocalStorage = (key: string) => {
9 | if (!key || typeof window === "undefined") {
10 | return "";
11 | }
12 | return localStorage.getItem(key);
13 | };
14 |
15 | export const removeFromLocalStorage = (key: string) => {
16 | if (!key || typeof window === "undefined") {
17 | return "";
18 | }
19 | return localStorage.removeItem(key);
20 | };
21 |
--------------------------------------------------------------------------------
/src/utils/modifyPayload.ts:
--------------------------------------------------------------------------------
1 | export const modifyPayload = (values: any) => {
2 | const obj = { ...values };
3 | const file = obj["file"];
4 | delete obj["file"];
5 | const data = JSON.stringify(obj);
6 | const formData = new FormData();
7 | formData.append("data", data);
8 | formData.append("file", file as Blob);
9 |
10 | return formData;
11 | };
12 |
--------------------------------------------------------------------------------
/src/utils/timeFormatter.ts:
--------------------------------------------------------------------------------
1 | export const timeFormatter = (time: string) => {
2 | const date = new Date(time);
3 |
4 | const hours = String(date.getHours()).padStart(2, "0");
5 | const minutes = String(date.getMinutes()).padStart(2, "0");
6 |
7 | const formattedTime = `${hours}:${minutes}`;
8 | return formattedTime;
9 | };
10 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------