diff --git a/backend/server/src/constants.py b/backend/server/src/constants.py index dbd8419..f580e19 100644 --- a/backend/server/src/constants.py +++ b/backend/server/src/constants.py @@ -1,15 +1,15 @@ -# Patient login email content -EMAIL_SUBJECT = "MobilityMate Account Access" -EMAIL_BODY_TEMPLATE = """Dear {name}, - -Welcome to MobilityMate - your dedicated partner in staying active and healthy! 🌟 - -To access your personalized exercise account and stay up to date with exercises recommended by your practitioner, simply click on the link below: - -🔗 https://mobilitymate-a8b53.web.app/patient/{uid} - -Your well-being is our priority! If you have any questions or need support, our team is here to assist you every step of the way. - -Stay active, stay healthy! -The MobilityMate Team 🏋️♂️ -""" +# Patient login email content +EMAIL_SUBJECT = "MobilityMate Account Access" +EMAIL_BODY_TEMPLATE = """Dear {name}, + +Welcome to MobilityMate - your dedicated partner in staying active and healthy! 🌟 + +To access your personalized exercise account and stay up to date with exercises recommended by your practitioner, simply click on the link below: + +🔗 https://mobilitymate-a8b53.web.app/{practitionId}/patient/{patientId} + +Your well-being is our priority! If you have any questions or need support, our team is here to assist you every step of the way. + +Stay active, stay healthy! +The MobilityMate Team 🏋️♂️ +""" diff --git a/backend/server/src/main.py b/backend/server/src/main.py index 268b3ae..02854c0 100644 --- a/backend/server/src/main.py +++ b/backend/server/src/main.py @@ -1,86 +1,89 @@ -from flask import Flask, request, jsonify, render_template -from flask_mail import Mail, Message -from dotenv import load_dotenv -import os -from constants import * -import time -import firebase_admin -from firebase_admin import credentials -from flask_cors import CORS - -load_dotenv() -app = Flask(__name__) -app.secret_key = os.getenv("SECRET_KEY") - - -def create_service_dict(): - variables_keys = { - "type": os.getenv("TYPE"), - "project_id": os.getenv("PROJECT_ID"), - "private_key_id": os.getenv("PRIVATE_KEY_ID"), - "private_key": os.getenv("PRIVATE_KEY"), - "client_email": os.getenv("CLIENT_EMAIL"), - "client_id": os.getenv("CLIENT_ID"), - "auth_uri": os.getenv("AUTH_URI"), - "token_uri": os.getenv("TOKEN_URI"), - "auth_provider_x509_cert_url": os.getenv("AUTH_PROVIDER_X509_CERT_URL"), - "client_x509_cert_url": os.getenv("CLIENT_X509_CERT_URL"), - "universe_domain": os.getenv("UNIVERSE_DOMAIN"), - } - return variables_keys - - -cred = credentials.Certificate(create_service_dict()) -firebase_admin.initialize_app(cred) - -from conversation.views import conversation_blueprint - -# Register the conversation Blueprint -app.register_blueprint(conversation_blueprint, url_prefix="/conversation") -CORS(app) - -# Load Flask-Mail config from .env -app.config["MAIL_SERVER"] = os.getenv("MAIL_SERVER") -app.config["MAIL_PORT"] = int(os.getenv("MAIL_PORT")) -app.config["MAIL_USE_TLS"] = os.getenv("MAIL_USE_TLS").lower() == "true" -app.config["MAIL_USERNAME"] = os.getenv("MAIL_USERNAME") -app.config["MAIL_PASSWORD"] = os.getenv("MAIL_PASSWORD") -app.config["MAIL_DEFAULT_SENDER"] = os.getenv("MAIL_DEFAULT_SENDER") -mail = Mail(app) - - -@app.route("/patient/send-link", methods=["POST"]) -def send_link(): - try: - data = request.get_json() - uid = data.get("uid") - name = data.get("name") - email = data.get("email") - - # Send patient email with login link - message = Message( - subject=EMAIL_SUBJECT, - recipients=[email], - body=EMAIL_BODY_TEMPLATE.format(name=name, uid=uid), - ) - mail.send(message) - - return jsonify({"success": True, "message": "Email sent successfully"}) - - except Exception as e: - return jsonify({"success": False, "error": str(e)}), 500 - - -def format_server_time(): - server_time = time.localtime() - return time.strftime("%I:%M:%S %p", server_time) - - -@app.route("/") -def index(): - context = {"server_time": format_server_time()} - return render_template("index.html", context=context) - - -if __name__ == "__main__": - app.run(debug=True, port=os.getenv("PORT", default=5000)) +from flask import Flask, request, jsonify, render_template +from flask_mail import Mail, Message +from dotenv import load_dotenv +import os +from constants import * +import time +import firebase_admin +from firebase_admin import credentials +from flask_cors import CORS + +load_dotenv() +app = Flask(__name__) +app.secret_key = os.getenv("SECRET_KEY") + + +def create_service_dict(): + variables_keys = { + "type": os.getenv("TYPE"), + "project_id": os.getenv("PROJECT_ID"), + "private_key_id": os.getenv("PRIVATE_KEY_ID"), + "private_key": os.getenv("PRIVATE_KEY"), + "client_email": os.getenv("CLIENT_EMAIL"), + "client_id": os.getenv("CLIENT_ID"), + "auth_uri": os.getenv("AUTH_URI"), + "token_uri": os.getenv("TOKEN_URI"), + "auth_provider_x509_cert_url": os.getenv("AUTH_PROVIDER_X509_CERT_URL"), + "client_x509_cert_url": os.getenv("CLIENT_X509_CERT_URL"), + "universe_domain": os.getenv("UNIVERSE_DOMAIN"), + } + return variables_keys + + +cred = credentials.Certificate(create_service_dict()) +firebase_admin.initialize_app(cred) + +from conversation.views import conversation_blueprint + +# Register the conversation Blueprint +app.register_blueprint(conversation_blueprint, url_prefix="/conversation") +CORS(app) + +# Load Flask-Mail config from .env +app.config["MAIL_SERVER"] = os.getenv("MAIL_SERVER") +app.config["MAIL_PORT"] = int(os.getenv("MAIL_PORT")) +app.config["MAIL_USE_TLS"] = os.getenv("MAIL_USE_TLS").lower() == "true" +app.config["MAIL_USERNAME"] = os.getenv("MAIL_USERNAME") +app.config["MAIL_PASSWORD"] = os.getenv("MAIL_PASSWORD") +app.config["MAIL_DEFAULT_SENDER"] = os.getenv("MAIL_DEFAULT_SENDER") +mail = Mail(app) + + +@app.route("/patient/send-link", methods=["POST"]) +def send_link(): + try: + data = request.get_json() + practitionId = data.get("practitionId") + patientId = data.get("patientId") + name = data.get("name") + email = data.get("email") + + # Send patient email with login link + message = Message( + subject=EMAIL_SUBJECT, + recipients=[email], + body=EMAIL_BODY_TEMPLATE.format( + name=name, practitionId=practitionId, patientId=patientId + ), + ) + mail.send(message) + + return jsonify({"success": True, "message": "Email sent successfully"}) + + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + +def format_server_time(): + server_time = time.localtime() + return time.strftime("%I:%M:%S %p", server_time) + + +@app.route("/") +def index(): + context = {"server_time": format_server_time()} + return render_template("index.html", context=context) + + +if __name__ == "__main__": + app.run(debug=True, port=os.getenv("PORT", default=5000)) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d140c14..493b1a9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,26 +1,29 @@ -import { BrowserRouter, Routes, Route } from "react-router-dom"; - -import Landing from "./views/landing/Landing"; -import PatientHome from "./views/patient/PatientHome"; -import PractitionerDashboard from "./views/practitioner/PractitionerDashboard"; -import PractitionerSignUp from "./views/practitioner/PractitionerSignUp"; -import PractitionerLogin from "./views/practitioner/PractitionerLogin"; - -const App = () => { - return ( - - - } /> - } /> - } - /> - } /> - } /> - - - ); -}; - -export default App; +import { BrowserRouter, Routes, Route } from "react-router-dom"; + +import Landing from "./views/landing/Landing"; +import PatientHome from "./views/patient/PatientHome"; +import PractitionerDashboard from "./views/practitioner/PractitionerDashboard"; +import PractitionerSignUp from "./views/practitioner/PractitionerSignUp"; +import PractitionerLogin from "./views/practitioner/PractitionerLogin"; + +const App = () => { + return ( + + + } /> + } + /> + } + /> + } /> + } /> + + + ); +}; + +export default App; diff --git a/frontend/src/views/patient/PatientHome.jsx b/frontend/src/views/patient/PatientHome.jsx index 9b95147..55bde83 100644 --- a/frontend/src/views/patient/PatientHome.jsx +++ b/frontend/src/views/patient/PatientHome.jsx @@ -1,13 +1,13 @@ -import Navbar from './components/Navbar'; -import Exercises from './components/Exercises'; -import './styles.css'; -import { useState, useEffect, useCallback } from 'react'; -import VoiceAI from './components/VoiceAI'; -import axios from 'axios'; -import Skeleton from './components/Skeleton'; +import Navbar from "./components/Navbar"; +import Exercises from "./components/Exercises"; +import "./styles.css"; +import { useState, useEffect, useCallback } from "react"; +import VoiceAI from "./components/VoiceAI"; +import axios from "axios"; +import Skeleton from "./components/Skeleton"; import apiUrl from "../../config"; -import { LogOut } from 'lucide-react'; -import { useNavigate } from 'react-router-dom'; +import { LogOut } from "lucide-react"; +import { useNavigate } from "react-router-dom"; const PatientHome = () => { const navigate = useNavigate(); @@ -15,7 +15,7 @@ const PatientHome = () => { user: null, gpt: null, }); - const [userInput, setUserInput] = useState(''); + const [userInput, setUserInput] = useState(""); const handleInputChange = (e) => { setUserInput(e.target.value); @@ -26,8 +26,8 @@ const PatientHome = () => { setConvo((prevConvo) => ({ ...prevConvo, gpt: null })); updateUserMessage(userInput); const queryParams = new URLSearchParams({ - patient: 'demo', - practitioner: 'demo', + patient: "demo", + practitioner: "demo", }); try { const response = await axios.post( @@ -42,9 +42,9 @@ const PatientHome = () => { return prevConvo; }); } catch (error) { - console.error('Error fetching conversation start:', error); + console.error("Error fetching conversation start:", error); } - setUserInput(''); + setUserInput(""); }; const updateUserMessage = useCallback((newMessage) => { @@ -58,8 +58,8 @@ const PatientHome = () => { useEffect(() => { const startConversation = async () => { const queryParams = new URLSearchParams({ - patient: 'demo', - practitioner: 'demo', + patient: "demo", + practitioner: "demo", }); try { const response = await axios.get( @@ -72,7 +72,7 @@ const PatientHome = () => { return prevConvo; }); } catch (error) { - console.error('Error fetching conversation start:', error); + console.error("Error fetching conversation start:", error); } }; startConversation(); @@ -86,14 +86,14 @@ const PatientHome = () => { { // TODO: what are thooooose params: new URLSearchParams({ - patient: 'demo', - practitioner: 'demo', + patient: "demo", + practitioner: "demo", }), } ); - navigate('/'); + navigate("/"); } catch (error) { - console.error('Error ending conversation:', error); + console.error("Error ending conversation:", error); } }; @@ -111,7 +111,7 @@ const PatientHome = () => { Done for the day? @@ -137,7 +137,7 @@ const PatientHome = () => { - Exercises + Exercises diff --git a/frontend/src/views/patient/components/VoiceAI.jsx b/frontend/src/views/patient/components/VoiceAI.jsx index 8d489f5..96e6ac4 100644 --- a/frontend/src/views/patient/components/VoiceAI.jsx +++ b/frontend/src/views/patient/components/VoiceAI.jsx @@ -1,10 +1,10 @@ -import { useState, useEffect, useRef } from 'react'; -import axios from 'axios'; -import apiUrl from '../../../config'; -import gsap from 'gsap'; -import React, { Suspense } from 'react'; +import { useState, useEffect, useRef } from "react"; +import axios from "axios"; +import apiUrl from "../../../config"; +import gsap from "gsap"; +import React, { Suspense } from "react"; -const Spline = React.lazy(() => import('@splinetool/react-spline')); +const Spline = React.lazy(() => import("@splinetool/react-spline")); const VoiceAI = ({ updateUserMessage, updateGptResponse }) => { const sphere = useRef(); @@ -21,26 +21,26 @@ const VoiceAI = ({ updateUserMessage, updateGptResponse }) => { recognition.continuous = true; recognition.interimResults = true; - let accumulatedTranscript = ''; + let accumulatedTranscript = ""; recognition.onresult = (event) => { - accumulatedTranscript = ''; + accumulatedTranscript = ""; for (let i = 0; i < event.results.length; i++) { - accumulatedTranscript += event.results[i][0].transcript.trim() + ' '; + accumulatedTranscript += event.results[i][0].transcript.trim() + " "; } updateUserMessage(accumulatedTranscript); }; setSpeechRecognition(recognition); } else { - console.warn('Speech recognition not supported in this browser.'); + console.warn("Speech recognition not supported in this browser."); } }, [updateUserMessage]); const startRecording = async () => { const queryParams = new URLSearchParams({ - patient: 'demo', - practitioner: 'demo', + patient: "demo", + practitioner: "demo", }); // Start recording audio @@ -57,9 +57,9 @@ const VoiceAI = ({ updateUserMessage, updateGptResponse }) => { recorder.onstop = async () => { updateGptResponse(null); // Process and send the audio data to the server for transcription - const audioBlob = new Blob(chunks, { type: 'audio/wav' }); + const audioBlob = new Blob(chunks, { type: "audio/wav" }); const formData = new FormData(); - formData.append('audioFile', audioBlob, 'recorded_audio.wav'); + formData.append("audioFile", audioBlob, "recorded_audio.wav"); const response = await axios.post( `${apiUrl}/conversation/send_message?${queryParams.toString()}`, @@ -86,7 +86,7 @@ const VoiceAI = ({ updateUserMessage, updateGptResponse }) => { function onLoad(spline) { spline.setZoom(0.1); - const obj = spline.findObjectById('f5f3b334-53b6-4337-8497-c6815ba02c98'); + const obj = spline.findObjectById("f5f3b334-53b6-4337-8497-c6815ba02c98"); sphere.current = obj; } @@ -98,7 +98,7 @@ const VoiceAI = ({ updateUserMessage, updateGptResponse }) => { x: 1.5, y: 1.5, z: 1.5, - ease: 'power3.out', + ease: "power3.out", }); }; @@ -109,7 +109,7 @@ const VoiceAI = ({ updateUserMessage, updateGptResponse }) => { x: 1, y: 1, z: 1, - ease: 'power3.out', + ease: "power3.out", }); }; diff --git a/frontend/src/views/practitioner/components/NewPatientModal.jsx b/frontend/src/views/practitioner/components/NewPatientModal.jsx index 45afa93..ccb1aac 100644 --- a/frontend/src/views/practitioner/components/NewPatientModal.jsx +++ b/frontend/src/views/practitioner/components/NewPatientModal.jsx @@ -1,116 +1,116 @@ -import { useState } from "react"; -import { getCurrentUser, db } from "../../../../firebaseConfig"; -import apiUrl from "../../../config"; -import axios from "axios"; - -const emptyForm = { - name: "", - email: "", - age: "", -}; - -const NewPatientModal = () => { - const [formData, setFormData] = useState(emptyForm); - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prevData) => ({ ...prevData, [name]: value })); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - try { - // Add the new patient data to Firestore - const currentUser = await getCurrentUser(); - const patientDoc = await db - .collection(`practitioners/${currentUser.uid}/patients`) - .add(formData); - - console.log(patientDoc.id); - // Send patient an email access link - await axios.post(`${apiUrl}/patient/send-link`, { - uid: patientDoc.id, - name: formData.name, - email: formData.email, - }); - - handleClose(); - } catch (error) { - console.error("Error adding new patient:", error); - } - }; - - const handleClose = () => { - setFormData(emptyForm); - document.getElementById("new_patient_modal").close(); - }; - - return ( - - - Register a New Patient - {/* Press ESC key or click the button below to close */} - - - - - Name - - - - - - Email - - - - - - Age - - - - - - Submit - - - Close - - - - - - - ); -}; - -export default NewPatientModal; +import { useState } from "react"; +import { getCurrentUser, db } from "../../../../firebaseConfig"; +import apiUrl from "../../../config"; +import axios from "axios"; + +const emptyForm = { + name: "", + email: "", + age: "", +}; + +const NewPatientModal = () => { + const [formData, setFormData] = useState(emptyForm); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prevData) => ({ ...prevData, [name]: value })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + // Add the new patient data to Firestore + const currentUser = await getCurrentUser(); + const patientDoc = await db + .collection(`practitioners/${currentUser.uid}/patients`) + .add(formData); + + // Send patient an email access link + await axios.post(`${apiUrl}/patient/send-link`, { + practitionId: currentUser.uid, + patientId: patientDoc.id, + name: formData.name, + email: formData.email, + }); + + handleClose(); + } catch (error) { + console.error("Error adding new patient:", error); + } + }; + + const handleClose = () => { + setFormData(emptyForm); + document.getElementById("new_patient_modal").close(); + }; + + return ( + + + Register a New Patient + {/* Press ESC key or click the button below to close */} + + + + + Name + + + + + + Email + + + + + + Age + + + + + + Submit + + + Close + + + + + + + ); +}; + +export default NewPatientModal;
Press ESC key or click the button below to close