diff --git a/Dockerfile b/Dockerfile index 269821a..360f26f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -RUN npm run build +RUN npm run build:prod # 3. Production image, copy all the files and run next FROM base AS runner diff --git a/loadEnv.js b/loadEnv.js new file mode 100644 index 0000000..ea8cec2 --- /dev/null +++ b/loadEnv.js @@ -0,0 +1,16 @@ +const fs = require('fs'); +const dotenv = require('dotenv'); +const env = process.argv[2] || 'production'; // Default to production + +const envFilePath = `.env.${env}`; +const localEnvFilePath = `${envFilePath}.local`; + +if (fs.existsSync(localEnvFilePath)) { + console.log(`Loading environment variables from ${localEnvFilePath}`); + dotenv.config({ path: localEnvFilePath }); +} else if (fs.existsSync(envFilePath)) { + console.log(`Loading environment variables from ${envFilePath}`); + dotenv.config({ path: envFilePath }); +} else { + console.error(`No environment file found for ${env}`); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6c4244f..250fdf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,9 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cookies-next": "^4.2.1", + "cross-env": "^7.0.3", "date-fns": "^3.6.0", + "dotenv": "^16.4.5", "next": "14.1.3", "react": "^18", "react-chartjs-2": "^5.2.0", @@ -2559,11 +2561,27 @@ "node": ">= 6" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2813,6 +2831,17 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.1.tgz", "integrity": "sha512-HH391uRJXAAeelougod93W++2gECfHIVCqq+B/4znhjCgb2zVPL+iLOVnTYwejqAuNf69Ffc5ILQYdPHsZACJA==" }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4490,8 +4519,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isomorphic-dompurify": { "version": "0.20.0", @@ -5254,7 +5282,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -6116,7 +6143,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6128,7 +6154,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -6932,7 +6957,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, diff --git a/package.json b/package.json index 5298a78..ffc7131 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build:prod": "node loadEnv.js production && next build", + "build:dev": "node loadEnv.js development && next build", "start": "next start", "lint": "next lint" }, @@ -20,7 +21,9 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cookies-next": "^4.2.1", + "cross-env": "^7.0.3", "date-fns": "^3.6.0", + "dotenv": "^16.4.5", "next": "14.1.3", "react": "^18", "react-chartjs-2": "^5.2.0", diff --git a/src/app/(navbar)/patient/visit/[id]/page.js b/src/app/(navbar)/patient/visit/[id]/page.js index 864a889..8e507d5 100644 --- a/src/app/(navbar)/patient/visit/[id]/page.js +++ b/src/app/(navbar)/patient/visit/[id]/page.js @@ -24,7 +24,7 @@ export default function VisitUser({params}){ return ()=> { isMounted = false; } - },[]) + },[id]) return (

Update Visit Detail Page

diff --git a/src/components/MainNavbar.js b/src/components/MainNavbar.js index 5806d9d..03297f7 100644 --- a/src/components/MainNavbar.js +++ b/src/components/MainNavbar.js @@ -26,7 +26,6 @@ export default function MainNavbar(){ const username = user?.content?.clinicUsername setUser({username: username, role: user?.content?.role}) }catch(error){ - await handleLogout() alert(error.message) router.push('/login') } diff --git a/src/components/SessionChecker.js b/src/components/SessionChecker.js index 2a15289..129e490 100644 --- a/src/components/SessionChecker.js +++ b/src/components/SessionChecker.js @@ -1,7 +1,7 @@ "use client" import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { getCookie } from 'cookies-next'; +import { deleteCookie, getCookie } from 'cookies-next'; import { getUserLogin } from '@/actions/formSubmit'; const SessionChecker = () => { @@ -10,15 +10,20 @@ const SessionChecker = () => { useEffect(() => { const interval = setInterval(() => { const checkValidSession = async()=> { - const sessionId = getCookie("JSESSIONID" || ""); - const user = await getUserLogin(sessionId) - if (!user?.content) { - alert("Session Expired") - router.push('/login'); + try{ + const sessionId = getCookie("JSESSIONID" || ""); + const user = await getUserLogin(sessionId) + if (!user?.content) { + deleteCookie("JSESSIONID") + router.push('/login'); + throw new Error("Session Expired") + } + }catch(error){ + alert(error.message) } } checkValidSession() - }, 6000); // Check every 6 sec + }, 30000); // Check every 10 min return () => clearInterval(interval); }, [router]); diff --git a/src/components/employee/ManagerForm.js b/src/components/employee/ManagerForm.js index 1f518f7..85d92d7 100644 --- a/src/components/employee/ManagerForm.js +++ b/src/components/employee/ManagerForm.js @@ -1,10 +1,12 @@ "use client" import { formManagerRegister } from "@/actions/formSubmit"; -import {ButtonForm, UserNameForm, InputForm, SelectForm} from "../form"; -import { useRef, useState } from "react" +import { ButtonForm, UserNameForm, InputForm, SelectForm } from "../form"; +import { useRef, useState, useEffect } from "react" +import { Alert } from "@mui/material" export default function ManagerForm(){ const [role, setRole] = useState('Doctor'); + const [message,setMessage] = useState(null) const formRef = useRef(null); const handleRoleChange = (event) => { setRole(event.target.value); @@ -17,34 +19,55 @@ export default function ManagerForm(){ const managerRegister =await formManagerRegister(formData); formRef.current.reset() if(managerRegister.error) throw new Error(managerRegister.error) - alert(managerRegister?.success) + setMessage(managerRegister?.success) }catch(error){ - console.log(error.message) - alert(error.message) + setMessage({error:error.message}) } } + useEffect(()=>{ + const messageTimeout = setTimeout(()=>{ + setMessage(null) + },1500) + return ()=> clearTimeout(messageTimeout) + },[message]) return ( -
- - {/* */} - - - - - - - - {/* */} - {/* */} - {/* */} - {/* - - - - - */} - - - + <> + { + message?.success && ( + setMessage(null)}> + {message?.success} + + ) + } + { + message?.error && ( + setMessage(null)}> + {message?.error} + + ) + } +
+ + {/* */} + + + + + + + + {/* */} + {/* */} + {/* */} + {/* + + + + + */} + + + + ) } \ No newline at end of file diff --git a/src/components/form/LoginForm.js b/src/components/form/LoginForm.js index 1a11abf..8024d8f 100644 --- a/src/components/form/LoginForm.js +++ b/src/components/form/LoginForm.js @@ -1,25 +1,22 @@ "use client" import InputForm from "./InputForm"; import ButtonForm from "./ButtonForm"; -import { formLogin } from "@/actions/formSubmit"; +import { formLogin, getUserLogin } from "@/actions/formSubmit"; import { useRef,useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import { hasCookie,setCookie } from "cookies-next"; +import { deleteCookie, getCookie, hasCookie,setCookie } from "cookies-next"; import Image from "next/image"; export default function LoginForm(){ const formRef = useRef(null); const router = useRouter(); - const [loading, setLoading] = useState(false) const handleSubmit = async(e) => { e.preventDefault() try { const formData = new FormData(formRef.current) formRef.current.reset() - setLoading(true) const session = await formLogin(formData); - console.log(session) - if(session){ + if(session?.sessionId){ setCookie("JSESSIONID", session.sessionId) }else{ alert("Session Not Founded, Bad Credentials") @@ -32,7 +29,10 @@ export default function LoginForm(){ } } useEffect(()=>{ - if(hasCookie("JSESSIONID")){ + const userLogin = getUserLogin(getCookie("JSESSIONID")); + if(!userLogin){ + deleteCookie("JSESSIONID") + }else { router.push('/dashboard') } },[router]) diff --git a/src/components/patient/RegisterForm.js b/src/components/patient/RegisterForm.js index 77ea82b..160acfd 100644 --- a/src/components/patient/RegisterForm.js +++ b/src/components/patient/RegisterForm.js @@ -1,10 +1,12 @@ "use client" import { patientRegister } from "@/actions/formSubmit"; -import {ButtonForm, InputForm, SelectForm, UserNameForm} from "../form"; -import { useRef } from "react"; +import { ButtonForm, InputForm, SelectForm, UserNameForm } from "../form"; +import { useRef, useState, useEffect } from "react"; +import { Alert } from "@mui/material" export default function RegisterForm(){ const formRef = useRef(null); + const [message, setMessage] = useState(null) const handleSubmit = async(e) => { e.preventDefault() try { @@ -12,29 +14,51 @@ export default function RegisterForm(){ formRef.current.reset() const patientRegistration = await patientRegister(formData); if(patientRegistration.error) throw Error(patientRegistration.error) - alert(patientRegistration.success) + setMessage({success: patientRegistration.success}) }catch(error){ - alert(error.message) + setMessage({error:error.message}) } } + useEffect(()=>{ + const messageTimeout = setTimeout(()=>{ + setMessage(null) + },1500) + return ()=> clearTimeout(messageTimeout) + },[message]) return ( -
- - - - - - - - - {/* */} - {/* */} - {/* */} - {/* */} - {/* - */} - - - + <> + { + message?.success && ( + setMessage(null)}> + {message?.success} + + ) + } + { + message?.error && ( + setMessage(null)}> + {message?.error} + + ) + } +
+ + + + + + + + + {/* */} + {/* */} + {/* */} + {/* */} + {/* + */} + + + + ) } \ No newline at end of file diff --git a/src/components/patient/VisitForm.js b/src/components/patient/VisitForm.js index 3b9f614..e724616 100644 --- a/src/components/patient/VisitForm.js +++ b/src/components/patient/VisitForm.js @@ -1,26 +1,42 @@ "use client" import { getMemberFilter, visitPatientRegister } from "@/actions/formSubmit"; -import {ButtonForm, InputInfoForm} from "../form"; -import { Fragment, useRef, useEffect, useState } from "react"; -// import SearchForm from "../form/SearchForm"; -import {DateTimePicker} from "@mui/x-date-pickers"; +import { ButtonForm } from "../form"; +import { useRef, useEffect, useState } from "react"; +import { Alert, Autocomplete, TextField, ThemeProvider, createTheme } from "@mui/material"; +import { DateTimePicker } from "@mui/x-date-pickers"; import DateTimeProvider from "@/components/DateTimeProvider" export default function VisitForm () { + const customDateTimePickerStyles = { + root: { + '& .MuiInput-root': { + backgroundColor: 'lightblue', + }, + '& .MuiSvgIcon-root': { + color: 'red', + }, + '& .MuiPickersPopper-container': { + backgroundColor: 'lightgray', + }, + }, + }; + const theme = createTheme({ + components: { + MuiPickers: { + styleOverrides: { + ...customDateTimePickerStyles, + }, + }, + }, + }); const [startTime, setStartTime] = useState(new Date()) const [endTime, setEndTime] = useState(new Date()) - const [isFocused1, setIsFocused1] = useState(false) - const [isFocused2, setIsFocused2] = useState(false) - const [value1, setValue1] = useState("") - const [value2, setValue2] = useState("") - const patientRef = useRef(null) - const practitionerRef = useRef(null) const formRef = useRef(null) const [practitioners, setPractitioners] = useState([]) const [patients, setPatients] = useState([]) + const [message, setMessage] = useState(null); useEffect(()=>{ try { - const fetchMember = async() => { const [patient, practitioner] = await Promise.all([ getMemberFilter({role: "ROLE_PATIENT"}), @@ -31,21 +47,13 @@ export default function VisitForm () { } fetchMember() }catch(error){ - alert("Server Error") + setMessage("Server Error") } },[]) - const checkFocus = () => { - if(patientRef.current && practitionerRef.current){ - setIsFocused1(document.activeElement === patientRef.current) - setIsFocused2(document.activeElement === practitionerRef.current) - } - } - const handleValue1 = (e) => { - setValue1(e.target.value) - } - const handleValue2 = (e) => { - setValue2(e.target.value) - } + const patientsFirstName = new Set(patients?.map(patient => patient?.firstName)) + const patientsLastName = new Set(patients?.map(patient => patient?.lastName)) + const practitionersFirstName = new Set(practitioners?.map(practitioner => practitioner?.firstName)) + const practitionersLastName = new Set(practitioners?.map(practitioner => practitioner?.lastName)) const handleSubmit = async(e) => { e.preventDefault() try { @@ -55,72 +63,82 @@ export default function VisitForm () { formData.append("endTime",endTime) const visitPatientRegistration = await visitPatientRegister(formData); if(visitPatientRegistration.error) throw Error(visitPatientRegistration.error) - alert(visitPatientRegistration.success) + setMessage({success:visitPatientRegistration?.success}) }catch(error){ - alert(error.message) + setMessage({error:error?.message}) } } + useEffect(()=>{ + const messageTimeout = setTimeout(()=>{ + setMessage(null) + },1500) + return ()=> clearTimeout(messageTimeout) + },[message]) return ( -
- + <> { - practitioners && isFocused2 && ( -
- { - practitioners?.map(practitioner => ( - - { - practitioner?.firstName?.startsWith(value2) && practitioner?.firstName !== "" && ( - - - ) - } - { - practitioner?.lastName?.startsWith(value2) && practitioner?.lastName !== "" && ( - - ) - } - - )) - } -
+ message?.success && ( + setMessage(null)}> + {message?.success} + ) } - { - patients && isFocused1 && ( -
- { - patients?.map(patient => ( - - { - patient?.firstName?.startsWith(value1) && patient?.firstName !== "" && ( - - - ) - } - { - patient?.lastName?.startsWith(value1) && patient?.lastName !== "" && ( - - ) - } - - )) - } -
+ message?.error && ( + setMessage(null)}> + {message?.error} + ) } -
- - - - -
- {/* - - - */} - - +
+ { + patients && practitioners && ( + <> + } getOptionLabel={(option) => option? option : ""} options={[...new Set([...practitionersFirstName,...practitionersLastName])]}/> + } getOptionLabel={(option) => option? option : ""} options={[...new Set([...patientsFirstName,...patientsLastName])]} /> + + ) + } +
+ + + ( + + )} disablePast slotProps={{ popper: {placement: "right"} }} /> + ( + + )} disablePast slotProps={{ popper: {placement: "right"} }} /> + + +
+ {/* + + + */} + + + ) } diff --git a/src/middleware.js b/src/middleware.js index 1a62450..dfbaaa4 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -7,8 +7,7 @@ export function middleware(req) { const sessionId = parsedCookie.JSESSIONID; if (!sessionId && req.nextUrl.pathname !=="/login") { return NextResponse.redirect(new URL('/login', req.url)); - } - else if(req.nextUrl.pathname === "/patient"){ + }else if(req.nextUrl.pathname === "/patient"){ return NextResponse.redirect(new URL('/patient/register', req.url)) }else if(req.nextUrl.pathname === "/employee"){ return NextResponse.redirect(new URL('/employee/dashboard', req.url)) diff --git a/tailwind.config copy.js b/tailwind.config copy.js deleted file mode 100644 index 0ad31b4..0000000 --- a/tailwind.config copy.js +++ /dev/null @@ -1,39 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", - "./src/components/**/*.{js,ts,jsx,tsx,mdx}", - "./src/app/**/*.{js,ts,jsx,tsx,mdx}", - ], - /* fff9f9 - 002741 - e59be9 - ddeffb - dde0fb - */ - theme: { - extend: { - backgroundImage: { - "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", - "gradient-conic": - "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", - }, - colors: { - "primary":"#002741", - "active": "#e59be9", - "dark":"#fff9f9", - "muted":"#00274170", - "dark-muted":"#dde0fbb2" - }, - backgroundColor: { - "primary": "#fff9f9", - "dark":"#002741", - "secondary": "#021111f9" - }, - screens: { - "xm": "1110px" - } - }, - }, - plugins: [], -};