diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 45edf1b7..4145c686 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,82 +1,81 @@ -name: Staging Deployment -on: - pull_request: - types: [opened, closed] - branches: [staging] - push: - branches: [staging] - -env: - AWS_REGION: "eu-west-3" - AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} - AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} - AWS_ECR_REGISTRY: ${{secrets.AWS_ECR_REGISTRY}} - AWS_ECR_REPOSITORY: ${{secrets.AWS_ECR_REPOSITORY}} - SSH_STAGING_HOST: ${{secrets.SSH_STAGING_HOST}} - CURAMAP_SSH_USER: ${{secrets.CURAMAP_SSH_USER}} - SSH_STAGING_PASSWORD: ${{secrets.SSH_STAGING_PASSWORD}} - -concurrency: - group: ${{github.head_ref || github.run_id}} - cancel-in-progress: true - - -jobs: - Build_curamap_image: - name: Build Docker Image and Push to ECR - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Log-in to Elastic Container Registry - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - - name: Build Frontend Image and Push to ECR - uses: docker/build-push-action@v6 - with: - context: ./Frontend - push: true - tags: ${{ env.AWS_ECR_REGISTRY }}/${{ env.AWS_ECR_REPOSITORY }}:frontend - - - name: Build Backend Image and Push to ECR - uses: docker/build-push-action@v6 - with: - context: ./Backend - push: true - tags: ${{ env.AWS_ECR_REGISTRY }}/${{ env.AWS_ECR_REPOSITORY }}:backend - - - Deploy_to_Staging: - name: Deploy to Staging EC2 Server - if: github.ref == 'refs/heads/staging' - needs: [Build_curamap_image] - runs-on: ubuntu-latest - steps: - - name: Deploy to EC2 Server ⏳ - uses: appleboy/ssh-action@master - with: - host: ${{ env.SSH_STAGING_HOST }} - username: ${{ env.CURAMAP_SSH_USER }} - password: ${{ env.SSH_STAGING_PASSWORD }} - script: | - set -ex - export IMAGE_TAG=latest - cd ~/curamap - aws ecr get-login-password --region eu-west-3 | docker login --username AWS --password-stdin ${{ env.AWS_ECR_REGISTRY }} - docker compose pull curamap-frontend curamap-backend - docker compose down -v --remove-orphans - docker compose up -d --force-recreate curamap-frontend curamap-backend - docker image prune -af - +name: Staging Deployment +on: + pull_request: + types: [opened, closed] + branches: [staging] + push: + branches: [staging] + +env: + AWS_REGION: "eu-west-3" + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + AWS_ECR_REGISTRY: ${{secrets.AWS_ECR_REGISTRY}} + AWS_ECR_REPOSITORY: ${{secrets.AWS_ECR_REPOSITORY}} + SSH_STAGING_HOST: ${{secrets.SSH_STAGING_HOST}} + CURAMAP_SSH_USER: ${{secrets.CURAMAP_SSH_USER}} + SSH_STAGING_PASSWORD: ${{secrets.SSH_STAGING_PASSWORD}} + +concurrency: + group: ${{github.head_ref || github.run_id}} + cancel-in-progress: true + + +jobs: + Build_curamap_image: + name: Build Docker Image and Push to ECR + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Log-in to Elastic Container Registry + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Build Frontend Image and Push to ECR + uses: docker/build-push-action@v6 + with: + context: ./Frontend + push: true + tags: ${{ env.AWS_ECR_REGISTRY }}/${{ env.AWS_ECR_REPOSITORY }}:frontend + + - name: Build Backend Image and Push to ECR + uses: docker/build-push-action@v6 + with: + context: ./Backend + push: true + tags: ${{ env.AWS_ECR_REGISTRY }}/${{ env.AWS_ECR_REPOSITORY }}:backend + + + Deploy_to_Staging: + name: Deploy to Staging EC2 Server + if: github.ref == 'refs/heads/staging' + needs: [Build_curamap_image] + runs-on: ubuntu-latest + steps: + - name: Deploy to EC2 Server ⏳ + uses: appleboy/ssh-action@master + with: + host: ${{ env.SSH_STAGING_HOST }} + username: ${{ env.CURAMAP_SSH_USER }} + password: ${{ env.SSH_STAGING_PASSWORD }} + script: | + set -ex + export IMAGE_TAG=latest + cd ~/curamap + aws ecr get-login-password --region eu-west-3 | docker login --username AWS --password-stdin ${{ env.AWS_ECR_REGISTRY }} + docker compose pull curamap-frontend curamap-backend + docker compose down -v --remove-orphans + docker compose up -d --force-recreate curamap-frontend curamap-backend + docker image prune -af diff --git a/Backend/controllers/Medicine.js b/Backend/controllers/Medicine.js new file mode 100644 index 00000000..c4f1c9d3 --- /dev/null +++ b/Backend/controllers/Medicine.js @@ -0,0 +1,59 @@ +// medicines.controller.js +const Medicine = require("../models/Medicine"); + +// Create a new medicine +exports.createMedicine = async (req, res, next) => { + try { + const medicine = new Medicine(req.body); + await medicine.save(); + res.status(201).json(medicine); + } catch (err) { + next(err); + } +}; + +// Get all medicines +exports.getMedicines = async (req, res, next) => { + try { + const meds = await Medicine.find(); + res.json(meds); + } catch (err) { + next(err); + } +}; + +// Get one by ID +exports.getMedicineById = async (req, res, next) => { + try { + const med = await Medicine.findById(req.params.id); + if (!med) return res.status(404).json({ message: "Medicine not found" }); + res.json(med); + } catch (err) { + next(err); + } +}; + +// Update by ID +exports.updateMedicine = async (req, res, next) => { + try { + const med = await Medicine.findByIdAndUpdate(req.params.id, req.body, { + new: true, + runValidators: true, + }); + if (!med) return res.status(404).json({ message: "Medicine not found" }); + res.json(med); + } catch (err) { + next(err); + } +}; + +// Delete by ID +exports.deleteMedicine = async (req, res, next) => { + try { + const med = await Medicine.findByIdAndDelete(req.params.id); + if (!med) return res.status(404).json({ message: "Medicine not found" }); + res.status(204).end(); + } catch (err) { + next(err); + } +}; diff --git a/Backend/controllers/pharmacyController.js b/Backend/controllers/pharmacyController.js index dc89321f..0312ecc9 100644 --- a/Backend/controllers/pharmacyController.js +++ b/Backend/controllers/pharmacyController.js @@ -173,7 +173,9 @@ exports.toggleClosingStatus = async (req, res) => { // @desc post pharmacy Forgot password // @route POST /api/pharmacies/forgot-password exports.forgotPassword = async (req, res) => { - const pharmacy = await pharmacy.findOne({ email: req.body.email.trim().toLowerCase() }); + const pharmacy = await pharmacy.findOne({ + email: req.body.email.trim().toLowerCase(), + }); if (!pharmacy) { return res.status(404).json({ message: "Pharmacy not found" }); } @@ -204,8 +206,7 @@ exports.forgotPassword = async (req, res) => { message: "Error sending email. Please try again later.", }); } -} - +}; // @desc patch pharmacy reset password // @route PATCH /api/pharmacies/reset-password/:token @@ -221,17 +222,17 @@ exports.resetPassword = async (req, res) => { passwordResetToken: hashedToken, passwordResetExpires: { $gt: Date.now() }, }); - + if (!pharmacy) { return res.status(400).json({ message: "Invalid or expired token" }); } - + pharmacy.password = req.body.password; pharmacy.passwordResetToken = undefined; pharmacy.passwordResetExpires = undefined; - + await pharmacy.save(); - + res.status(200).json({ message: "Password reset successful" }); //login pharmacy const loginToken = generateToken(pharmacy); @@ -240,6 +241,6 @@ exports.resetPassword = async (req, res) => { token: loginToken, }); } catch (err) { - res.status(500).json({message: "Server error", error: err.message }); + res.status(500).json({ message: "Server error", error: err.message }); } -} \ No newline at end of file +}; diff --git a/Backend/data/medicines.js b/Backend/data/medicines.js new file mode 100644 index 00000000..b16f335d --- /dev/null +++ b/Backend/data/medicines.js @@ -0,0 +1,66 @@ +module.exports = [ + { + name: "Paracetamol", + description: "Pain reliever and fever reducer", + dosages: ["500mg", "650mg"], + packageSizes: ["10 tablets", "20 tablets"], + prescription: false, + prices: { + Nigeria: { + "500mg": 100, + "650mg": 120, + }, + Ghana: { + "500mg": 80, + "650mg": 100, + }, + }, + }, + { + name: "Amoxicillin", + description: "Used to treat bacterial infections", + dosages: ["250mg", "500mg"], + packageSizes: ["capsules", "syrup"], + prescription: true, + prices: { + Nigeria: { + "250mg": 150, + "500mg": 200, + }, + Kenya: { + "250mg": 130, + "500mg": 180, + }, + }, + }, + { + name: "Ibuprofen", + description: "Reduces fever and treats pain or inflammation", + dosages: ["200mg", "400mg"], + packageSizes: ["24 tablets", "12 tablets"], + prescription: false, + prices: { + Nigeria: { + "200mg": 90, + "400mg": 160, + }, + Ghana: { + "200mg": 75, + "400mg": 140, + }, + }, + }, + { + name: "Vitamin C", + description: "Boosts immune system", + dosages: ["100mg", "500mg"], + packageSizes: ["30 tablets", "60 tablets"], + prescription: false, + prices: { + Nigeria: { + "100mg": 70, + "500mg": 120, + }, + }, + }, +]; diff --git a/Backend/index.js b/Backend/index.js index 76d420fa..f56e3d65 100644 --- a/Backend/index.js +++ b/Backend/index.js @@ -8,6 +8,7 @@ const app = express(); // Route Imports const pharmacyRoutes = require("./routes/pharmacyRoutes"); const patientRoutes = require("./routes/patientRoutes"); +const medicineRoutes = require("./routes/medicine"); // Middleware app.use(express.json()); @@ -27,6 +28,8 @@ app.use(cors()); // Routes app.use("/api/pharmacies", pharmacyRoutes); app.use("/api/patients", patientRoutes); +app.use("/api/medicines", medicineRoutes); + // Database Connection mongoose .connect(process.env.DB_URI, { serverSelectionTimeoutMS: 60000 }) diff --git a/Backend/models/Medicine.js b/Backend/models/Medicine.js new file mode 100644 index 00000000..f71e4c7b --- /dev/null +++ b/Backend/models/Medicine.js @@ -0,0 +1,27 @@ +// medicines.model.js +const mongoose = require("mongoose"); +const { Schema } = mongoose; + +const medicineSchema = new Schema( + { + name: { type: String, required: true }, + description: { type: String }, + dosages: [{ type: String }], + packageSizes: [{ type: String }], + prescription: { type: Boolean, default: false }, + prices: { + type: Map, + of: new Schema( + { + type: Map, + of: Number, + }, + { _id: false } + ), + required: true, + }, + }, + { timestamps: true } +); + +module.exports = mongoose.model("Medicine", medicineSchema); diff --git a/Backend/models/PharmacyModel.js b/Backend/models/PharmacyModel.js index 3a043354..5f8d457a 100644 --- a/Backend/models/PharmacyModel.js +++ b/Backend/models/PharmacyModel.js @@ -8,15 +8,15 @@ const pharmacySchema = new mongoose.Schema( default: () => new mongoose.Types.ObjectId().toString(), }, name: { type: String, required: true }, // Pharmacy Name - owner: { type: String, required: true }, // Owner's Name + owner: { type: String }, // Owner's Name email: { type: String, required: true, unique: true }, - phone: { type: String, required: true }, - address: { type: String, required: true }, - city: { type: String, required: true }, - state: { type: String, required: true }, - country: { type: String, required: true }, + phone: { type: String }, + address: { type: String }, + city: { type: String }, + state: { type: String }, + country: { type: String }, website: { type: String }, // Optional website - licenseNumber: { type: String, required: true, unique: true }, // Regulatory License Number + licenseNumber: { type: String, unique: true }, // Regulatory License Number password: { type: String, required: true }, passwordChangeAt: { type: Date }, passwordResetToken: { type: String }, @@ -28,7 +28,6 @@ const pharmacySchema = new mongoose.Schema( }, openingHours: { type: String, - required: true, // e.g., "8:00 AM - 10:00 PM" }, closingStatus: { type: Boolean, @@ -59,7 +58,10 @@ pharmacySchema.methods.comparePassword = async function (password) { pharmacySchema.methods.generatePasswordResetToken = function () { const resetToken = crypto.randomBytes(32).toString("hex"); // Hashing the token for protection - this.passwordResetToken = crypto.createHash("sha256").update(resetToken).digest("hex"); + this.passwordResetToken = crypto + .createHash("sha256") + .update(resetToken) + .digest("hex"); // Set the expiration time for the token this.passwordResetExpires = Date.now() + 10 * 60 * 1000; // 10 minutes expiration diff --git a/Backend/routes/medicine.js b/Backend/routes/medicine.js new file mode 100644 index 00000000..6bc4a4fe --- /dev/null +++ b/Backend/routes/medicine.js @@ -0,0 +1,14 @@ +// routes/medicineRoutes.js + +const express = require("express"); +const router = express.Router(); +const ctrl = require("../controllers/Medicine"); + +router.post("/", ctrl.createMedicine); +router.get("/", ctrl.getMedicines); +router.get("/:id", ctrl.getMedicineById); +router.put("/:id", ctrl.updateMedicine); +router.delete("/:id", ctrl.deleteMedicine); + +// ✅ Export just the router +module.exports = router; diff --git a/Backend/seeds/seedMedicines.js b/Backend/seeds/seedMedicines.js new file mode 100644 index 00000000..50541321 --- /dev/null +++ b/Backend/seeds/seedMedicines.js @@ -0,0 +1,25 @@ +const mongoose = require("mongoose"); +const Medicine = require("../models/Medicine"); +const medicines = require("../data/medicines"); +require("dotenv").config(); + +const MONGO_URI = process.env.DB_URI; + +const seed = async () => { + try { + await mongoose.connect(MONGO_URI, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + + await Medicine.deleteMany(); + await Medicine.insertMany(medicines); + console.log("✅ Medicines seeded successfully."); + process.exit(0); + } catch (err) { + console.error("❌ Error seeding medicines:", err); + process.exit(1); + } +}; + +seed(); diff --git a/Frontend/.gitignore b/Frontend/.gitignore index a547bf36..5c0b45a0 100644 --- a/Frontend/.gitignore +++ b/Frontend/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# Ignore the .env file +.env diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index 1c2dbc0a..7eb1b5c8 100644 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -17,11 +17,12 @@ import OrderSummary from "./pages/OrderSummary"; // import ProfileSignup from "./pages/ProfileSignup"; import FindMedsLoading from "./components/FindMedsLoading"; import UpdatedCart from "./pages/UpdatedCart"; -import PhamarcySignUp from "./components/forms/PhamarcySignUp"; +import NewPharmarcySignUp from "./components/forms/NewPharmarcySignUp"; import MedicineTable from "./components/MedicineTable"; import UploadPrescription from "./pages/UploadPrescription"; import PharmacyOtp from "./components/PharmacyOtp"; import OtpConfirmed from "./components/OtpConfirmed"; +import Pharmarcysignin from "./components/forms/Pharmarcysignin"; // Code splitted Components (Lazy Loading)... // N.B- Please do not touch if you're new to how lazy loading works.. @@ -133,7 +134,8 @@ function App() { {/* Pharmacy Routing */} } /> } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/Frontend/src/components/PaymentModal.jsx b/Frontend/src/components/PaymentModal.jsx index dce43d04..3882a348 100644 --- a/Frontend/src/components/PaymentModal.jsx +++ b/Frontend/src/components/PaymentModal.jsx @@ -1,16 +1,25 @@ // import React from "react"; import React, { useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; import "../styles/paymentmodal.css"; import { FaHospital, FaMoneyBillWave, FaLock } from "react-icons/fa"; const PaymentModal = () => { + const { state } = useLocation(); + const total = state?.total || 0; // Default value if not provided const [selectedMethod, setSelectedMethod] = useState("pharmacy"); + const navigate = useNavigate(); + + const handleOtppage = () => { + navigate("/otp-page") + } + return (

Update payment method

-

Pay 40,000

+

Pay {total}

@@ -46,14 +59,15 @@ const PaymentModal = () => { value="transfer" checked={selectedMethod === "transfer"} onChange={() => setSelectedMethod("transfer")} + disabled /> {" "} Pay with Transfer
@@ -61,7 +75,9 @@ const PaymentModal = () => { {/* Buttons */}
- +
{/* Security Info */} diff --git a/Frontend/src/components/forms/NewPharmarcySignUp.jsx b/Frontend/src/components/forms/NewPharmarcySignUp.jsx new file mode 100644 index 00000000..8ed1ad11 --- /dev/null +++ b/Frontend/src/components/forms/NewPharmarcySignUp.jsx @@ -0,0 +1,170 @@ +import React, { useState } from "react"; +import styles from "../../styles/newpharmacysignup.module.css"; +import caretleft from "../../assets/CaretLeft.png"; +import cancelicon from "../../assets/X.png"; + +const InputField = ({ id, name, type, placeholder, value, onChange }) => ( + +); + +const NewPharmarcySignUp = () => { + const [formData, setFormData] = useState({ + name: "", + email: "", + password: "", + confirmPassword: "", + phone: "", + address: "", + }); + + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + const [loading, setLoading] = useState(false); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(""); + setSuccess(false); + + // Basic validation + if ( + !formData.name || + !formData.email || + !formData.password || + !formData.confirmPassword || + !formData.phone || + !formData.address + ) { + setError("All fields are required."); + return; + } + + if (formData.password !== formData.confirmPassword) { + setError("Passwords do not match."); + return; + } + + setLoading(true); + + try { + const response = await fetch( + `${import.meta.env.VITE_API_URL}/pharmacies/signup`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + } + ); + + if (!response.ok) { + throw new Error("Failed to sign up. Please try again."); + } + + const data = await response.json(); + setSuccess(true); + setFormData({ + name: "", + email: "", + password: "", + confirmPassword: "", + phone: "", + address: "", + }); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+ go back +

Create Account

+ close +
+
+ + + + + + + + {error &&

{error}

} + {success &&

Sign up successful!

} + + + +
+

Already have an account?

+ Log in +
+
+
+ ); +}; + +export default NewPharmarcySignUp; diff --git a/Frontend/src/components/forms/PhamarcySignUp.jsx b/Frontend/src/components/forms/PhamarcySignUp.jsx deleted file mode 100644 index f9360ab5..00000000 --- a/Frontend/src/components/forms/PhamarcySignUp.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; -import "../../styles/pharmacysignup.css"; -import caretleft from "../../assets/CaretLeft.png"; -import cancelicon from "../../assets/X.png" -const PhamarcySignUp = () => { - return ( -
-
- caretleft - caretleft -
-
- - - - - -
-
- {" "} -

Already have An Account

- Login in -
-
- ); -}; - -export default PhamarcySignUp; diff --git a/Frontend/src/components/forms/Pharmarcysignin.jsx b/Frontend/src/components/forms/Pharmarcysignin.jsx new file mode 100644 index 00000000..cfeb306b --- /dev/null +++ b/Frontend/src/components/forms/Pharmarcysignin.jsx @@ -0,0 +1,130 @@ +import React, { useState } from "react"; +import styles from "../../styles/pharmacysignin.module.css"; +import caretleft from "../../assets/CaretLeft.png"; +import cancelicon from "../../assets/X.png"; +import { useNavigate } from "react-router-dom"; + +const Pharmarcysignin = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [rememberMe, setRememberMe] = useState(false); + const [error, setError] = useState(""); + + const handleSignIn = async (e) => { + // Prevent Default submission + e.preventDefault(); + setError(""); + + try { + const response = await fetch( + // "https://new-cura.onrender.com/api/pharmacies/signin", + `${import.meta.env.VITE_API_URL}/pharmacies/signin`, + + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + } + ); + + const data = await response.json(); + + if (response.ok) { + localStorage.setItem("authtoken", data.token); + navigate("/dashboard"); + } else if (response.status === 401) { + const data = await response.json(); + setError(data.message || "Invalid email or password."); + } else if (response.status === 404) { + setError("Account not found. Please check your email."); + } else { + setError(data.message || "Sign-in failed. Please try again."); + } + } catch (error) { + setError("Network error. Please try again later."); + console.error("Sign-in error:", error); + } + }; + + const navigate = useNavigate(); + + const handleSignUp = () => { + navigate("/signup-email"); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + }; + return ( +
+
+
+ go back +

Log In

+ close +
+
+ setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> +
+ + + Forgot password? + +
+ + + {/* Error message */} + {error && ( +

+ {error} +

+ )} +
+
+

Don't have an account?

+ Sign up +
+
+
+ ); +}; + +export default Pharmarcysignin; diff --git a/Frontend/src/components/forms/SignIn.jsx b/Frontend/src/components/forms/SignIn.jsx index bbc47f4a..19e3158c 100644 --- a/Frontend/src/components/forms/SignIn.jsx +++ b/Frontend/src/components/forms/SignIn.jsx @@ -16,7 +16,8 @@ const SignIn = () => { try { const response = await fetch( - "https://new-cura.onrender.com/api/patients/signin", + `${import.meta.env.VITE_API_URL}/patients/signin`, + { method: "POST", headers: { @@ -95,7 +96,7 @@ const SignIn = () => { /> Remember Me - Forgot password? + Forgot password?
- {error &&

{error}

} {/* Complete Profile Section */}

Complete your Profile

-
e.preventDefault()} - > +
-

Phone No

+234 -
-
-
{error &&

{error}

} @@ -246,9 +300,9 @@ const SignUp = () => {
@@ -259,4 +313,4 @@ const SignUp = () => { ); }; -export default SignUp; +export default SignUp; \ No newline at end of file diff --git a/Frontend/src/main.jsx b/Frontend/src/main.jsx index 44e5058c..178d0cac 100644 --- a/Frontend/src/main.jsx +++ b/Frontend/src/main.jsx @@ -5,10 +5,12 @@ import "./index.css"; import App from "./App.jsx"; + createRoot(document.getElementById("root")).render( + ); diff --git a/Frontend/src/pages/LandingPage.jsx b/Frontend/src/pages/LandingPage.jsx index 011720a6..a2a7c571 100644 --- a/Frontend/src/pages/LandingPage.jsx +++ b/Frontend/src/pages/LandingPage.jsx @@ -54,13 +54,17 @@ const LandingPage = () => {
to prioritize your wellbeing without the hassle.

-
- - +
+
+ + +
+ + Banner picture
- Banner picture +

Connecting Patients to Pharmacies For Easy Medication Access.

@@ -120,7 +124,7 @@ const LandingPage = () => {
-

How is curamap Different ?

+

How is curamap Different ?

@@ -341,7 +345,10 @@ const LandingPage = () => {
  • Youtube
  • +

    Terms And Conditions | Privacy Policy

    +
    +
    ); diff --git a/Frontend/src/pages/OrderSummary.jsx b/Frontend/src/pages/OrderSummary.jsx index 5eb842d3..6c6a0d3e 100644 --- a/Frontend/src/pages/OrderSummary.jsx +++ b/Frontend/src/pages/OrderSummary.jsx @@ -16,11 +16,13 @@ const OrderSummary = () => { const backToSelection = () => navigate("/findmeds", { state: { selectedMedicines } }); const proceedToCheckout = () => - navigate("/payment-modal", { state: { selectedMedicines, pharmacy } }); + navigate("/payment-modal", { + state: { selectedMedicines, pharmacy, total }, + }); const subtotal = selectedMedicines.reduce((acc, medicine) => { const { price, quantity } = medicine; - const calculatedPrice = Number(price) * Number(quantity); + const calculatedPrice = Number(price) + Number(quantity); const totalMedicinePrice = calculatedPrice * (quantity || 1); return acc + totalMedicinePrice; @@ -28,7 +30,7 @@ const OrderSummary = () => { const tax = 1500; - const total = subtotal + tax; + const total = (subtotal + tax).toFixed(2); // Total price including tax // Check if any prescription medicines are selected @@ -63,11 +65,11 @@ const OrderSummary = () => {
    -
    + {/*

    Cart

    Checkout

    -
    -

    100% Secure

    +
    */} + {/*

    100% Secure

    */}
    {
    @@ -148,7 +150,7 @@ const OrderSummary = () => {
    Total: - ₦{total.toFixed(2)} + ₦{total}
    diff --git a/Frontend/src/pages/OtpPage.jsx b/Frontend/src/pages/OtpPage.jsx index ad55c349..013fd8bd 100644 --- a/Frontend/src/pages/OtpPage.jsx +++ b/Frontend/src/pages/OtpPage.jsx @@ -2,39 +2,52 @@ import "../styles/otppage.css"; import onePic from "../assets/one.com.png"; import twoPic from "../assets/two.com.png"; import threePic from "../assets/three.com.png"; +import { useNavigate } from "react-router-dom"; -function OtpPage(){ +function OtpPage() { + const navigate = useNavigate(); - return( -
    -
    + const handleContinue = () => { + navigate("/locate-pharmacy"); + }; -
    -

    Check your Email or phone number for OTP

    -
    + return ( +
    +
    +
    +

    Check your Email or phone number for OTP

    +
    -Here’s how it works + Here’s how it works -
    -
    -
    -
    Arrive at the pharmacy
    +
    +
    +
    + +
    +
    Arrive at the pharmacy
    +
    +
    +
    + +
    +
    Meet the pharmacist and provide OTP
    +
    +
    +
    + +
    +
    Pharmacist confirms your pick up
    +
    +
    +
    Use your drugs
    +
    + +
    +
    -
    -
    -
    Meet the pharmacist and provide OTP
    -
    -
    -
    -
    Pharmacist confirms your pick up
    -
    -
    -
    Use your drugs
    -
    - -
    -
    -
    - ); + ); } -export default OtpPage; \ No newline at end of file +export default OtpPage; diff --git a/Frontend/src/pages/SearchpageSummary.jsx b/Frontend/src/pages/SearchpageSummary.jsx index b3b75106..b216c8cf 100644 --- a/Frontend/src/pages/SearchpageSummary.jsx +++ b/Frontend/src/pages/SearchpageSummary.jsx @@ -28,8 +28,8 @@ const SearchpageSummary = () => {

    Selected Medicines:

      {selectedMedicines.map((medicine) => { - const { price, quantity } = medicine; - const calculatedPrice = Number(price) * Number(quantity); + const { price } = medicine; + const calculatedPrice = Number(price); return (
    • @@ -50,7 +50,7 @@ const SearchpageSummary = () => { onClick={viewShoppingCart} className="summary-btn view-cart-btn" > - View Shopping Cart + Find more Meds
    {/* Prescription warning message */} diff --git a/Frontend/src/pages/UpdatedCart.jsx b/Frontend/src/pages/UpdatedCart.jsx index b9dc82ae..1177faca 100644 --- a/Frontend/src/pages/UpdatedCart.jsx +++ b/Frontend/src/pages/UpdatedCart.jsx @@ -26,24 +26,24 @@ const UpdatedCart = () => {
    diff --git a/Frontend/src/styles/landingpage.css b/Frontend/src/styles/landingpage.css index 56ef69c8..794519bb 100644 --- a/Frontend/src/styles/landingpage.css +++ b/Frontend/src/styles/landingpage.css @@ -14,7 +14,7 @@ body { .landing-page-container { display: flex; flex-direction: column; - gap: 7rem; + gap: 0rem; } .Logo { @@ -23,6 +23,7 @@ body { justify-items: center; padding-left: 1.5rem; padding-top: 1.5rem; + margin-left: 4em; } .Logo img:nth-child(1) { @@ -33,18 +34,12 @@ body { .MainContent { display: flex; - align-items: center; - justify-content: center; + align-content: flex-start; height: 100%; gap: 10rem; } -.TextContent { - display: flex; - flex-direction: column; - gap: 3rem; - max-width: 600px; -} + .TextContent h1 { font-size: 3rem; @@ -60,12 +55,18 @@ body { line-height: 1.6; } -.Buttons { +.Buttons-and-banner { display: flex; - gap: 1rem; + justify-content: center; + align-items: flex-end; + gap: 28rem; + margin-top: -23em; } -.Buttons button { +.buttons-container { + display: flex; +} +.Buttons-and-banner button { padding: 0.5rem 2rem; border-radius: 4px; font-size: 1rem; @@ -74,23 +75,29 @@ body { transition: all 0.2s; } -.Buttons button:first-child { +.button-1 { background-color: transparent; + height: 4em; + width: 10em; + text-wrap: nowrap; color: #007e85; border: none; } -.Buttons button:first-child:hover { +.button-1:hover { color: #0f0f0f; } -.Buttons button:last-child { +.button-2 { background-color: #007e85; + height: 4em; + width: 10em; + text-wrap: nowrap; color: #f0f7ff; border: 1px solid #4a9eff; } -.Buttons button:last-child:hover { +.button-2:hover { color: #080808; } @@ -101,6 +108,7 @@ body { max-width: 100%; } .MainContent img:nth-child(2) { + margin-top: 1em; max-width: 550px; width: 100%; height: auto; @@ -270,6 +278,9 @@ body { flex-direction: column; } +.fourth-level-content h2 { + font-size: 1.5em; +} /* BEFORE YOU UNCOMMENT THIS CODE MAKE SURE YOU GIVE IT A CLASS SO IT STOPS AFFECTING OTHER PEOPLE'S CODE */ /* .name-pre h4, p { @@ -325,8 +336,14 @@ p { .fifth-level-content { display: flex; flex-direction: column; + margin-bottom: 4em; } +.fifth-level-content h2 { + font-size: 1.6em; + margin-left: 2em; + margin-bottom: 4em; +} .fifth-card-container { display: flex; justify-content: center; @@ -393,6 +410,11 @@ p { .card-6 p { margin-bottom: 4em; } + +.colorbuttons-container { + margin: 0 auto; + margin-top: 4em; +} /* sixth levl content*/ .sixth-level-content { @@ -523,6 +545,10 @@ p { gap: 1.2em; list-style-type: none; } + +.Terms-container { + margin-top: 12em; +} /* Responsive Design */ /* Small Mobile Devices (up to 576px) */ diff --git a/Frontend/src/styles/newpharmacysignup.module.css b/Frontend/src/styles/newpharmacysignup.module.css new file mode 100644 index 00000000..868dcc77 --- /dev/null +++ b/Frontend/src/styles/newpharmacysignup.module.css @@ -0,0 +1,63 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + background-image: url("../assets/Background.svg"); + background-size: cover; + background-position: center; +} +.signupSection { + width: 100%; + max-width: 400px; + background-color: #fff; + border-radius: 10px; + padding: 20px 30px 20px 30px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 1; + + > .headerNav { + display: flex; + width: 100%; + justify-content: space-between; + margin-bottom: 20px; + } + > form { + display: flex; + flex-direction: column; + gap: 1rem; + > input { + width: 100%; + padding: 10px; + border-radius: 4px; + border: 1px solid #110f0f; + font-size: 16px; + } + > .signupbtn { + width: 100%; + padding: 7px; + border-radius: 4px; + border: none; + outline: none; + font-size: 16px; + background-color: #83c0b9; + color: white; + cursor: pointer; + margin-bottom: 5px; + &:hover { + background-color: #6da8a0; + } + } + } + > .redirectLink { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; + > a { + text-decoration: none; + color: #83c0b9; + } + } +} diff --git a/Frontend/src/styles/order-summary.css b/Frontend/src/styles/order-summary.css index e2d2aef3..476a7967 100644 --- a/Frontend/src/styles/order-summary.css +++ b/Frontend/src/styles/order-summary.css @@ -1,5 +1,5 @@ .shopping-cart { - padding: 0px 30px; + padding: 20px 30px; } .search-header { @@ -54,6 +54,7 @@ .cart-text { display: flex; align-items: center; + padding-block: 1rem; } .cart-text-1 { diff --git a/Frontend/src/styles/otppage.css b/Frontend/src/styles/otppage.css index 627777ce..f29e363b 100644 --- a/Frontend/src/styles/otppage.css +++ b/Frontend/src/styles/otppage.css @@ -1,47 +1,58 @@ -.bodyotp{ - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; - - +.bodyotp { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; } - .boxOne { - width:800px; - height: 80vh; + width: 800px; + min-height: 80vh; padding: 35px; - background-color: white; + background-color: white; } -.topText{ - text-align: center; - color: grey; +.topText { + text-align: center; + color: grey; } .bodyotp { -line-height: 2.4; + line-height: 2.4; } .boxTwo { - display: flex; - flex-direction: column; - gap: 20px; + display: flex; + flex-direction: column; + gap: 20px; + align-items: center; } - + .boxchilds { - border: 1px solid black; - height: 10vh; - width: 400px; - margin-left: 80px; - border-radius: 8px; - display: flex; - padding: 20px; - gap: 50px; - color:grey -} -.onlyAchord{ - margin: 0 auto; + border: 1px solid black; + min-height: 10vh; + width: 400px; + margin-left: 80px; + border-radius: 8px; + display: flex; + padding: 20px; + gap: 50px; + color: grey; +} +.onlyAchord { + margin: 0 auto; +} + +.otp-cta { + width: 150px; + border: none; + background-color: rgba(0, 168, 67, 0.795); + color: #fff; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: rgb(0, 84, 0); + } } diff --git a/Frontend/src/styles/pharmacylogin.css b/Frontend/src/styles/pharmacylogin.css new file mode 100644 index 00000000..e2fce678 --- /dev/null +++ b/Frontend/src/styles/pharmacylogin.css @@ -0,0 +1,65 @@ +.pharmacy-login-container { + position: relative; + background-image: url("../assets/Background.svg"); + width: 100%; + height: 150vh; + margin: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: -2; + } + + .direction-header { + position: absolute; + top: 6em; + display: flex; + gap: 20rem; + } + + form { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + max-width: 600px; + margin-top: -250px; + background: #f9fdfd; + border-radius: 10px; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: 50rem; + height: 100vh; + z-index: -1; + } + + input[type="email"], + input[type="password"] { + width: 100%; + padding: 10px 90px; + margin: 10px 0; + outline: none; + border: none; + border-radius: 4px; + border: 2px solid #110f0f; + font-size: 16px; + } + + button { + width: 50px 60px; + padding: 10px 150px; + border-radius: 4px; + border: 1px solid #0e0d0d; + font-size: 16px; + margin-top: 40px; + } + .redirect-link { + margin-top: -50px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + } + \ No newline at end of file diff --git a/Frontend/src/styles/pharmacysignin.module.css b/Frontend/src/styles/pharmacysignin.module.css new file mode 100644 index 00000000..04d8e9c0 --- /dev/null +++ b/Frontend/src/styles/pharmacysignin.module.css @@ -0,0 +1,83 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + background-image: url("../assets/Background.svg"); + background-size: cover; + background-position: center; +} + +.signupSection { + width: 100%; + max-width: 400px; + background-color: #fff; + border-radius: 10px; + padding: 20px 30px 20px 30px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 1; + + > .headerNav { + display: flex; + width: 100%; + justify-content: space-between; + margin-bottom: 20px; + } + > form { + display: flex; + flex-direction: column; + gap: 1rem; + > input { + width: 100%; + padding: 10px; + border-radius: 4px; + border: 1px solid #110f0f; + font-size: 16px; + } + > .options { + display: flex; + justify-content: space-between; + align-items: center; + > label { + font-size: 16px; + color: #110f0f; + display: flex; + align-items: center; + > input[type="checkbox"] { + width: 14px; + cursor: pointer; + } + } + > .forgot { + text-decoration: none; + color: #110f0f; + } + } + > .signupbtn { + width: 100%; + padding: 7px; + border-radius: 4px; + border: none; + outline: none; + font-size: 16px; + background-color: #83c0b9; + color: white; + cursor: pointer; + margin-bottom: 5px; + &:hover { + background-color: #6da8a0; + } + } + } + > .redirectLink { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; + > a { + text-decoration: none; + color: #83c0b9; + } + } +} diff --git a/Frontend/src/styles/pharmacysignup.css b/Frontend/src/styles/pharmacysignup.module.css similarity index 74% rename from Frontend/src/styles/pharmacysignup.css rename to Frontend/src/styles/pharmacysignup.module.css index f618b8e7..c7d69eb4 100644 --- a/Frontend/src/styles/pharmacysignup.css +++ b/Frontend/src/styles/pharmacysignup.module.css @@ -1,72 +1,67 @@ - - -.pharmacy-signup-container { - position: relative; - background-image: url("../assets/Background.svg"); - width: 100%; - height: 150vh; - margin: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: -2; -} - - -.direction-header{ - position: absolute; - top: 4em; - display: flex; - gap: 20rem; - } - -/* BEFORE YOU UNCOMMENT THIS CODE MAKE SURE YOU GIVE IT A CLASS SO IT STOPS AFFECTING OTHER PEOPLE'S CODE */ -/* form { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - width: 100%; - max-width: 600px; - margin-top: -250px; - background: #f9fdfd; - border-radius: 10px; - padding: 20px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - width: 50rem; - height: 100vh; - z-index: -1; -} */ - -/* BEFORE YOU UNCOMMENT THIS CODE MAKE SURE YOU GIVE IT A CLASS SO IT STOPS AFFECTING OTHER PEOPLE'S CODE */ -/* input[type="text"], -input[type="email"], -input[type="password"] { - width: 100%; - padding: 10px 90px; - margin: 10px 0; - outline: none; - border-radius: 4px; - border: 2px solid #110f0f; - font-size: 16px; -} */ - -/* BEFORE YOU UNCOMMENT THIS CODE MAKE SURE YOU GIVE IT A CLASS SO IT STOPS AFFECTING OTHER PEOPLE'S CODE */ -/* button { - width: 50px 60px; - padding: 10px 150px; - border-radius: 4px; - border: 1px solid #0e0d0d; - font-size: 16px; - margin-top: 40px; -} */ - -.redirect-link { - margin-top: -50px; - display: flex; - align-items: center; - justify-content: center; - gap: 10px; -} - +.pharmacySignupContainer { + position: relative; + background-image: url("../assets/Background.svg"); + width: 100%; + height: 100vh; + margin: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: -2; +} + +.directionHeader { + top: 4em; + display: flex; + gap: 20rem; +} + +/* BEFORE YOU UNCOMMENT THIS CODE MAKE SURE YOU GIVE IT A CLASS SO IT STOPS AFFECTING OTHER PEOPLE'S CODE */ +form { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1rem; + width: 100%; + max-width: 500px; + margin-top: -250px; + background: #f9fdfd; + border-radius: 10px; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: -1; +} + +/* BEFORE YOU UNCOMMENT THIS CODE MAKE SURE YOU GIVE IT A CLASS SO IT STOPS AFFECTING OTHER PEOPLE'S CODE */ +input[type="text"], +input[type="email"], +input[type="password"], +input[type="tel"], +input[type="address"] { + width: 100%; + padding: 10px 90px; + outline: none; + border-radius: 4px; + border: 2px solid #110f0f; + font-size: 16px; +} + +/* BEFORE YOU UNCOMMENT THIS CODE MAKE SURE YOU GIVE IT A CLASS SO IT STOPS AFFECTING OTHER PEOPLE'S CODE */ +button { + width: 50px 60px; + padding: 10px 150px; + border-radius: 4px; + border: 1px solid #0e0d0d; + font-size: 16px; + margin-top: 40px; +} + +.redirectLink { + margin-top: -50px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} diff --git a/Frontend/src/styles/searchpage.css b/Frontend/src/styles/searchpage.css index b6ba60b8..6ac4aacf 100644 --- a/Frontend/src/styles/searchpage.css +++ b/Frontend/src/styles/searchpage.css @@ -171,4 +171,7 @@ input[type="number"]::-webkit-inner-spin-button { border-radius: 10px; cursor: pointer; font-size: 16px; + &:hover { + background-color: #399b90; + } } diff --git a/Frontend/src/styles/signup.module.css b/Frontend/src/styles/signup.module.css index 92edd6ff..1059864f 100644 --- a/Frontend/src/styles/signup.module.css +++ b/Frontend/src/styles/signup.module.css @@ -296,8 +296,6 @@ button[type="submit"] { } .signupbtn { - -webkit-margin-before: 1rem; - margin-block-start: 1rem; padding-block: 0.5rem; border: 1px solid #338f85; border-radius: 4px; @@ -452,6 +450,11 @@ button[type="submit"] { } } +.error { + color: #ff0000; + font-size: 12px; +} + .btnbox { display: flex; flex-direction: row; @@ -508,4 +511,4 @@ button[type="submit"] { .profileheading { text-align: center; } -} +} \ No newline at end of file diff --git a/Frontend/src/styles/updatedcart.module.css b/Frontend/src/styles/updatedcart.module.css index e4a4464f..c6a2a27e 100644 --- a/Frontend/src/styles/updatedcart.module.css +++ b/Frontend/src/styles/updatedcart.module.css @@ -26,7 +26,7 @@ margin-block: 20px; } -.shoppingCartBtn { +.continueBtn { background: #83c0b9; height: 48px; width: 160px; @@ -37,7 +37,7 @@ cursor: pointer; } -.continueBtn { +.backBtn { background-color: #ffffff; height: 48px; width: 160px; diff --git a/docker-compose.yml b/docker-compose.yml index bdc261b0..5057c891 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,52 +1,52 @@ -services: - curamap-frontend: - container_name: curamap-frontend - build: ./Frontend - depends_on: - - curamap-backend - restart: "always" - environment: - - BACKEND_APP_API_URL=${BACKEND_APP_API_URL} - ports: - - 8000:80 - networks: - - curamap-network - - - curamap-backend: - container_name: curamap-backend - build: ./Backend - depends_on: - - curamap-db - restart: "always" - environment: - - MONGO_URI=${MONGO_URI} - ports: - - 5000:5000 - networks: - - curamap-network - - - curamap-db: - container_name: curamap-db - image: mongo - environment: - - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} - - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} - - MONGO_INITDB_DATABASE=${MONGO_INITDB_DATABASE} - ports: - - ${MONGODB_EXTERNAL_PORT}:27017 - networks: - - curamap-network - volumes: - - mongo-db:/data/db - - -volumes: - mongo-data: - - -networks: - curamap-network: - name: curamap-network - driver: bridge +services: + curamap-frontend: + container_name: curamap-frontend + build: ./Frontend + depends_on: + - curamap-backend + restart: "always" + environment: + - BACKEND_APP_API_URL=${BACKEND_APP_API_URL} + ports: + - 8000:80 + networks: + - curamap-network + + + curamap-backend: + container_name: curamap-backend + build: ./Backend + depends_on: + - curamap-db + restart: "always" + environment: + - MONGO_URI=${MONGO_URI} + ports: + - 5000:5000 + networks: + - curamap-network + + + curamap-db: + container_name: curamap-db + image: mongo + environment: + - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD} + - MONGO_INITDB_DATABASE=${MONGO_INITDB_DATABASE} + ports: + - ${MONGODB_EXTERNAL_PORT}:27017 + networks: + - curamap-network + volumes: + - mongo-db:/data/db + + +volumes: + mongo-data: + + +networks: + curamap-network: + name: curamap-network + driver: bridge \ No newline at end of file