Capstone 3 Added

master
Ron Reciproco 11 months ago
parent 12b87b8d2f
commit 82a05cde7f

@ -3,39 +3,51 @@ const User = require("../model/User")
exports.createOrder = async (req, res) => { exports.createOrder = async (req, res) => {
try { try {
const { userId, products, totalAmount } = req.body const {
userId,
products,
totalAmount,
shippingAddress,
personalInfo,
shippingMethod,
paymentMethod,
// Add other details as needed
} = req.body;
const user = await User.findById(userId) const user = await User.findById(userId);
if (!user) { if (!user) {
return res.status(404).json({ message: "User not found" }) return res.status(404).json({ message: "User not found" });
} }
// Check if the user is an admin // Check if the user is an admin
if (user.isAdmin) { if (user.isAdmin) {
return res return res.status(403).json({ message: "Admins cannot create orders" });
.status(403) }
.json({ message: "Admins cannot create orders" })
}
const newOrder = { const newOrder = {
products: products, products: products,
totalAmount: totalAmount, totalAmount: totalAmount,
purchaseOn: Date.now(), shippingAddress: shippingAddress,
} personalInfo: personalInfo,
shippingMethod: shippingMethod,
paymentMethod: paymentMethod,
// Add other details to newOrder as needed
purchaseOn: Date.now(),
};
user.orderedProducts.push(newOrder) user.orderedProducts.push(newOrder);
await user.save() await user.save();
res.status(201).json({ res.status(201).json({
message: "Order created successfully", message: "Order created successfully",
order: newOrder, order: newOrder,
}) });
} catch (error) { } catch (error) {
console.error(error) console.error(error);
res.status(500).json({ message: "Internal Server Error" }) res.status(500).json({ message: "Internal Server Error" });
} }
} };
// Retrieve authenticated user's orders // Retrieve authenticated user's orders
exports.getOrders = async (req, res) => { exports.getOrders = async (req, res) => {

@ -532,4 +532,3 @@ exports.updateAddress = async (req, res) => {
res.status(500).json({ message: 'Internal server error' }); res.status(500).json({ message: 'Internal server error' });
} }
}; };

@ -0,0 +1,13 @@
// models/ShippingMethod.js
const mongoose = require('mongoose');
const shippingMethodSchema = new mongoose.Schema({
name: { type: String, required: true },
description: { type: String, required: true },
// Add other fields as needed
});
const ShippingMethod = mongoose.model('ShippingMethod', shippingMethodSchema);
module.exports = ShippingMethod;

@ -18,6 +18,7 @@ import UpdateProfile from './components/UpdateProfile';
import AdminDashboard from './pages/AdminDashboard'; import AdminDashboard from './pages/AdminDashboard';
import ProductPage from './pages/ProductPage'; import ProductPage from './pages/ProductPage';
import CheckoutPage from './pages/CheckoutPage'; import CheckoutPage from './pages/CheckoutPage';
import OrderConfirmationPage from './pages/OrderConfirmationPage';
import Error from './pages/Error'; // Import the NotFound component import Error from './pages/Error'; // Import the NotFound component
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
@ -108,6 +109,7 @@ function App() {
<Route path="/404" element={<Error />} /> <Route path="/404" element={<Error />} />
<Route path="/product/:productId" element={<ProductPage />} /> <Route path="/product/:productId" element={<ProductPage />} />
<Route path="/checkout" element={<CheckoutPage />} /> <Route path="/checkout" element={<CheckoutPage />} />
<Route path="/order-confirmation" element={<OrderConfirmationPage />} />
{/* Catch-all route that redirects to the home page */} {/* Catch-all route that redirects to the home page */}
<Route path="*" element={<Navigate to="/404" />} /> <Route path="*" element={<Navigate to="/404" />} />
</Routes> </Routes>

@ -0,0 +1,134 @@
import React from "react";
import { Button, Collapse, Col, Row, Table, Form } from "react-bootstrap";
import AddressForm from "./AddressForm"; // Assuming the path is correct
const AddressCollapse = ({
addressOpen,
setAddressOpen,
isCustomerLoggedIn,
selectedAddress,
setSelectedAddress,
handleContinueAddress,
addAddressFormOpen,
setAddAddressFormOpen,
addressFormError,
addresses,
handleDeleteAddress,
}) => {
const handleAddressSelect = (addressId) => {
// Update the selectedAddressId
setSelectedAddress(addressId);
// If you also want to update the selectedAddress object, find the corresponding address
const selected = addresses.find((address) => address._id === addressId);
setSelectedAddress(selected);
};
return (
<div className="border rounded mb-3 p-3">
<h3>
<div className="d-flex justify-content-between align-items-start">
<span className="fs-6">2. Address</span>
<Button
variant="link"
onClick={() => setAddressOpen(!addressOpen)}
aria-controls="addressCollapse"
aria-expanded={addressOpen}
>
<i className={`bi bi-chevron-${addressOpen ? "up" : "down"}`}></i>
</Button>
</div>
</h3>
<Collapse in={addressOpen}>
<div id="addressCollapse">
{isCustomerLoggedIn ? (
<Row>
<span className="mb-2" style={{ fontSize: "16px" }}>
The selected address will be used both as your personal address
(for invoice) and as your delivery address.
</span>
<Col className="border rounded mb-3 p-3" md={6}>
{/* Left column - Add New Address Form Dropdown */}
<div>
{addAddressFormOpen && (
<AddressForm
selectedAddress={selectedAddress}
setSelectedAddress={setSelectedAddress}
handleContinueAddress={handleContinueAddress}
addressFormError={addressFormError}
/>
)}
<Button
className="my-3"
variant="primary"
onClick={() => setAddAddressFormOpen(!addAddressFormOpen)}
>
Add New Address
</Button>
</div>
</Col>
<Col md={6}>
{/* Right column - My Addresses from addressForm */}
{addresses && addresses.length > 0 ? (
<Table striped bordered hover>
<thead>
<tr>
<th>My Addresses</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{addresses.map((address, index) => (
<tr key={index}>
<td>
<div className="d-flex justify-content-between align-items-center">
<Form.Check
type="radio"
id={`addressRadio${index}`}
name="addressRadio"
checked={
selectedAddress &&
selectedAddress._id === address._id
}
onChange={() =>
handleAddressSelect(address._id)
}
/>
<p className="mx-3">
{address.street}, {address.city},{" "}
{address.state}, {address.zipCode},{" "}
{address.country}
</p>
</div>
</td>
<td>
<Button
variant="link"
className="text-danger"
onClick={() => handleDeleteAddress(address._id)}
>
<i className="bi bi-trash"></i>
</Button>
</td>
</tr>
))}
</tbody>
</Table>
) : (
<p>No existing addresses. Add a new address below.</p>
)}
</Col>
</Row>
) : (
<p>User not logged in. Log in to view and manage addresses.</p>
)}
</div>
</Collapse>
</div>
);
};
export default AddressCollapse;

@ -1,6 +1,5 @@
import React from 'react'; import React from "react";
import { Form, Button } from 'react-bootstrap'; import { Form, Button } from "react-bootstrap";
const AddressForm = ({ const AddressForm = ({
selectedAddress, selectedAddress,
@ -8,91 +7,92 @@ const AddressForm = ({
handleContinueAddress, handleContinueAddress,
addressFormError, addressFormError,
}) => { }) => {
const philippineStates = [ const philippineStates = [
"...", "...",
"Abra", "Abra",
"Agusan del Norte", "Agusan del Norte",
"Agusan del Sur", "Agusan del Sur",
"Aklan", "Aklan",
"Albay", "Albay",
"Antique", "Antique",
"Apayao", "Apayao",
"Aurora", "Aurora",
"Basilan", "Basilan",
"Bataan", "Bataan",
"Batanes", "Batanes",
"Batangas", "Batangas",
"Benguet", "Benguet",
"Biliran", "Biliran",
"Bohol", "Bohol",
"Bukidnon", "Bukidnon",
"Bulacan", "Bulacan",
"Cagayan", "Cagayan",
"Camarines Norte", "Camarines Norte",
"Camarines Sur", "Camarines Sur",
"Camiguin", "Camiguin",
"Capiz", "Capiz",
"Catanduanes", "Catanduanes",
"Cavite", "Cavite",
"Cebu", "Cebu",
"Cotabato", "Cotabato",
"Davao de Oro (formerly Compostela Valley)", "Davao de Oro (formerly Compostela Valley)",
"Davao del Norte", "Davao del Norte",
"Davao del Sur", "Davao del Sur",
"Davao Occidental", "Davao Occidental",
"Davao Oriental", "Davao Oriental",
"Dinagat Islands", "Dinagat Islands",
"Eastern Samar", "Eastern Samar",
"Guimaras", "Guimaras",
"Ifugao", "Ifugao",
"Ilocos Norte", "Ilocos Norte",
"Ilocos Sur", "Ilocos Sur",
"Iloilo", "Iloilo",
"Isabela", "Isabela",
"Kalinga", "Kalinga",
"La Union", "La Union",
"Laguna", "Laguna",
"Lanao del Norte", "Lanao del Norte",
"Lanao del Sur", "Lanao del Sur",
"Leyte", "Leyte",
"Maguindanao", "Maguindanao",
"Marinduque", "Marinduque",
"Masbate", "Masbate",
"Misamis Occidental", "Misamis Occidental",
"Misamis Oriental", "Misamis Oriental",
"Mountain Province", "Mountain Province",
"Negros Occidental", "Negros Occidental",
"Negros Oriental", "Negros Oriental",
"Northern Samar", "Northern Samar",
"Nueva Ecija", "Nueva Ecija",
"Nueva Vizcaya", "Nueva Vizcaya",
"Occidental Mindoro", "Occidental Mindoro",
"Oriental Mindoro", "Oriental Mindoro",
"Palawan", "Palawan",
"Pampanga", "Pampanga",
"Pangasinan", "Pangasinan",
"Quezon", "Quezon",
"Quirino", "Quirino",
"Rizal", "Rizal",
"Romblon", "Romblon",
"Samar (Western Samar)", "Samar (Western Samar)",
"Sarangani", "Sarangani",
"Siquijor", "Siquijor",
"Sorsogon", "Sorsogon",
"South Cotabato", "South Cotabato",
"Southern Leyte", "Southern Leyte",
"Sultan Kudarat", "Sultan Kudarat",
"Sulu", "Sulu",
"Surigao del Norte", "Surigao del Norte",
"Surigao del Sur", "Surigao del Sur",
"Tarlac", "Tarlac",
"Tawi-Tawi", "Tawi-Tawi",
"Zambales", "Zambales",
"Zamboanga del Norte", "Zamboanga del Norte",
"Zamboanga del Sur", "Zamboanga del Sur",
"Zamboanga Sibugay", "Zamboanga Sibugay",
]; ];
// Define the array of Philippine states
const countryStates = ["...", "Philippines"];
return ( return (
<Form onSubmit={handleContinueAddress}> <Form onSubmit={handleContinueAddress}>
@ -142,6 +142,7 @@ const AddressForm = ({
required required
/> />
</Form.Group> </Form.Group>
<Form.Group controlId="state"> <Form.Group controlId="state">
<Form.Label>State</Form.Label> <Form.Label>State</Form.Label>
<Form.Control <Form.Control
@ -160,9 +161,27 @@ const AddressForm = ({
))} ))}
</Form.Control> </Form.Control>
</Form.Group> </Form.Group>
<Form.Group controlId="country"> <Form.Group controlId="country">
<Form.Label>Country</Form.Label> <Form.Label>Country</Form.Label>
<Form.Control type="text" placeholder="Philippines" disabled /> <Form.Control
as="select"
value={selectedAddress?.country || ""}
onChange={(e) =>
setSelectedAddress({
...selectedAddress,
country: e.target.value, // Fix: change state to country
})
}
required
>
<option value="">Select Country</option>
{countryStates.map((country, index) => (
<option key={index} value={country}>
{country}
</option>
))}
</Form.Control>
</Form.Group> </Form.Group>
<Button className="mt-3" variant="primary" type="submit"> <Button className="mt-3" variant="primary" type="submit">

@ -0,0 +1,38 @@
const BankOption = ({ totalPrice }) => {
return (
<div>
<h4>Bank Payment Details</h4>
<div style={{ border: '1px solid #ddd', padding: '10px', borderRadius: '5px', marginBottom: '10px' }}>
<p>
Please transfer the amount to the following bank account:
</p>
<div>
<strong>Bank:</strong> BPI Bank
</div>
<div>
<strong>Account Holder:</strong> istore philippines
</div>
<div>
<strong>Account Number:</strong> 1234-1234-1234-1234
</div>
<div>
<strong>Amount:</strong> {new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP',
}).format(totalPrice)}
</div>
</div>
<div className="mt-3">
<p>
After completing the bank transfer, please submit the reference number{' '}
<a href="mailto:customercare@istore.com.ph">customercare@istore.com.ph</a>.
</p>
</div>
</div>
);
};
export default BankOption;

@ -3,7 +3,7 @@ import { Modal, Button, Form } from "react-bootstrap";
const EditAddress = ({ show, onHide, onSave, address }) => { const EditAddress = ({ show, onHide, onSave, address }) => {
const [editedAddress, setEditedAddress] = useState({ ...address }); const [editedAddress, setEditedAddress] = useState({ ...address });
const [updateStatus, setUpdateStatus] = useState(null); const [setUpdateStatus] = useState(null);
useEffect(() => { useEffect(() => {
setEditedAddress({ ...address }); setEditedAddress({ ...address });

@ -0,0 +1,73 @@
// OrderDetails.js
import React from "react";
import PropTypes from "prop-types";
const OrderDetails = ({ orderData }) => {
if (!orderData) {
// Handle the case where orderData is not available
return <div>Loading...</div>;
}
const {
userDetails,
selectedAddress,
selectedShippingMethod,
cartItems,
appliedPromoCode,
selectedPaymentOption,
termsAgreed,
// Include any other necessary data
} = orderData;
return (
<div>
<h2>Order Details</h2>
<h3>User Details</h3>
<pre>{JSON.stringify(userDetails, null, 2)}</pre>
<h3>Selected Address</h3>
<pre>{JSON.stringify(selectedAddress, null, 2)}</pre>
<h3>Selected Shipping Method</h3>
<pre>{selectedShippingMethod}</pre>
<h3>Cart Items</h3>
<ul>
{cartItems.map((item, index) => (
<li key={index}>
<h4>Item {index + 1}</h4>
<pre>{JSON.stringify(item, null, 2)}</pre>
</li>
))}
</ul>
<h3>Applied Promo Code</h3>
<pre>{appliedPromoCode}</pre>
<h3>Selected Payment Option</h3>
<pre>{selectedPaymentOption}</pre>
<h3>Terms Agreed</h3>
<pre>{termsAgreed ? "Agreed" : "Not Agreed"}</pre>
{/* Include additional sections as needed for other data */}
</div>
);
};
// Specify PropTypes for the orderData prop
OrderDetails.propTypes = {
orderData: PropTypes.shape({
userDetails: PropTypes.object,
selectedAddress: PropTypes.object,
selectedShippingMethod: PropTypes.string,
cartItems: PropTypes.array,
appliedPromoCode: PropTypes.string,
selectedPaymentOption: PropTypes.string,
termsAgreed: PropTypes.bool,
// Include PropTypes for other data properties
}),
};
export default OrderDetails;

@ -0,0 +1,86 @@
// components/OrderSummary.js
import React from 'react';
import { Col, Table, Form, Button } from 'react-bootstrap';
const OrderSummary = ({
cartItems,
shippingFee,
totalPrice,
discountAmount,
promoCode,
setPromoCode,
handleApplyPromoCode,
appliedPromoCode,
}) => {
return (
<Col xs={12} md={4} className="text-start">
<h3>Order Summary</h3>
<Table striped bordered hover>
<tbody>
<tr>
<td>Total Items</td>
<td>
{cartItems.reduce((total, item) => total + item.quantity, 0)}
</td>
</tr>
<tr>
<td>Shipping Fee</td>
<td>{new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP',
}).format(shippingFee.value)}</td>
</tr>
<tr>
<td>Shipping Details</td>
<td>{shippingFee.details}</td>
</tr>
<tr>
<td>Total</td>
<td>
<strong>{new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP',
}).format(totalPrice)}</strong>
</td>
</tr>
<tr>
<td>Discount</td>
<td>
<strong>{discountAmount}%</strong>
</td>
</tr>
<tr>
<td colSpan="2">
<Form.Group controlId="promoCode">
<Form.Label>Promo Code</Form.Label>
<Form.Control
type="text"
placeholder="Enter promo code"
value={promoCode}
onChange={(e) => setPromoCode(e.target.value)}
/>
<Button
className="mt-3"
variant="primary"
onClick={handleApplyPromoCode}
>
Apply Promo Code
</Button>
</Form.Group>
{appliedPromoCode && (
<div>
<p className="mt-2">
Applied Promo Code: <strong>{appliedPromoCode}</strong>
</p>
</div>
)}
</td>
</tr>
</tbody>
</Table>
</Col>
);
};
export default OrderSummary;

@ -0,0 +1,144 @@
import React, { useState, useEffect } from "react";
import { Button, Collapse, Form } from "react-bootstrap";
import { useNavigate } from 'react-router-dom';
import PropTypes from "prop-types"; // Import PropTypes
import BankOption from "./BankOption";
const PaymentCollapse = ({
totalPrice,
userDetails,
selectedAddress,
selectedShippingMethod,
cartItems,
appliedPromoCode,
}) => {
const [paymentOpen, setPaymentOpen] = useState(false);
const [selectedPaymentOption, setSelectedPaymentOption] = useState(null);
const [termsAgreed, setTermsAgreed] = useState(false);
const navigate = useNavigate();
const handlePaymentOptionChange = (option) => {
setSelectedPaymentOption(option);
};
const handleTermsAgreementChange = () => {
setTermsAgreed(!termsAgreed);
};
const isSubmitButtonDisabled = !termsAgreed || selectedPaymentOption === null;
const handleOrderSubmission = () => {
try {
const orderData = {
userDetails,
selectedAddress,
selectedShippingMethod,
cartItems,
appliedPromoCode,
selectedPaymentOption,
termsAgreed,
// Include any other necessary data
};
// Log the order data to the console for testing
console.log("Order Data:", orderData);
// Display a success message to the user (for testing)
console.log("Order submission simulated successfully!");
// Navigate to the order confirmation page with orderData
navigate("/order-confirmation", { state: { orderData } });
} catch (error) {
console.error("Error submitting order:", error);
// Handle general error (for testing)
console.log("Order submission simulated failed.");
}
};
return (
<div className="border rounded mb-3 p-3">
<h3>
<div className="d-flex justify-content-between align-items-start">
<span className="fs-6">4. Payment</span>
<Button
variant="link"
onClick={() => setPaymentOpen(!paymentOpen)}
aria-controls="paymentCollapse"
aria-expanded={paymentOpen}
>
<i className={`bi bi-chevron-${paymentOpen ? "up" : "down"}`}></i>
</Button>
</div>
</h3>
<Collapse in={paymentOpen}>
<div id="paymentCollapse">
<Form.Group className="mb-3">
<Form.Label>Select Payment Option:</Form.Label>
<Form.Control
as="select"
value={selectedPaymentOption}
onChange={(e) => handlePaymentOptionChange(e.target.value)}
>
<option value="">Select</option>
<option value="bank">Bank Option</option>
{/* Add other payment options here */}
</Form.Control>
</Form.Group>
{selectedPaymentOption === "bank" && (
<BankOption totalPrice={totalPrice} />
)}
{/* Terms Agreement */}
<Form.Group
className="mb-3"
style={{
backgroundColor: "#343a40",
padding: "10px",
borderRadius: "5px",
}}
>
<Form.Check
type="checkbox"
label="I agree to the terms of service and will adhere to them unconditionally."
checked={termsAgreed}
onChange={handleTermsAgreementChange}
style={{ color: "white" }}
/>
</Form.Group>
{/* Payment Button */}
<Button
variant="primary"
onClick={handleOrderSubmission}
disabled={isSubmitButtonDisabled}
>
Submit Order
</Button>
{/* Message below the Payment Button */}
{!termsAgreed && (
<div className="mt-2 text-danger">
Please agree to the terms of service before submitting the order.
</div>
)}
</div>
</Collapse>
</div>
);
};
// Specify PropTypes for each prop used in the component
PaymentCollapse.propTypes = {
totalPrice: PropTypes.number.isRequired,
userDetails: PropTypes.object,
selectedAddress: PropTypes.object,
selectedShippingMethod: PropTypes.string,
cartItems: PropTypes.array,
appliedPromoCode: PropTypes.string,
};
export default PaymentCollapse;

@ -0,0 +1,60 @@
import React from "react";
import { Button, Collapse } from "react-bootstrap";
const PersonalInfoCollapse = ({ personalInfoOpen, setPersonalInfoOpen, isCustomerLoggedIn, userDetails }) => {
return (
<div className="border rounded mb-3 p-3">
<h3>
<div className="d-flex justify-content-between align-items-start">
<span className="fs-6">1. Personal Information</span>
<Button
variant="link"
onClick={() => setPersonalInfoOpen(!personalInfoOpen)}
aria-controls="personalInfoCollapse"
aria-expanded={personalInfoOpen}
>
<i
className={`bi bi-chevron-${personalInfoOpen ? "up" : "down"}`}
></i>
</Button>
</div>
</h3>
<Collapse in={personalInfoOpen}>
<div id="personalInfoCollapse">
{isCustomerLoggedIn ? (
<div>
<p>
<strong>Name:</strong> {userDetails?.firstName}{" "}
{userDetails?.lastName}
</p>
<p>
<strong>Email:</strong> {userDetails?.email}
</p>
<p>
<strong>Mobile Number:</strong> {userDetails?.mobileNo}
</p>
<p>If you want to update your information</p>
<Button variant="primary" href="/update-profile">
Update Information
</Button>
</div>
) : (
<div className="mx-4">
<p>
You are not logged in. Please log in or register to proceed.
</p>
<Button variant="primary" href="/register">
Register
</Button>
<Button className="mx-2" variant="primary" href="/login">
Login
</Button>
</div>
)}
</div>
</Collapse>
</div>
);
};
export default PersonalInfoCollapse;

@ -0,0 +1,132 @@
import React, { useState } from "react";
import { Button, Collapse } from "react-bootstrap";
const ShippingMethodCollapse = ({ shippingMethodOpen, setShippingMethodOpen, setSelectedShippingMethod }) => {
const [selectedOption, setSelectedOption] = useState(null);
const handleOptionChange = (option) => {
setSelectedOption(option);
setSelectedShippingMethod(option); // Update the parent component state
};
return (
<div className="border rounded mb-3 p-3">
<h3>
<div className="d-flex justify-content-between align-items-start">
<span className="fs-6">3. Shipping Method</span>
<Button
variant="link"
onClick={() => setShippingMethodOpen(!shippingMethodOpen)}
aria-controls="shippingMethodCollapse"
aria-expanded={shippingMethodOpen}
>
<i
className={`bi bi-chevron-${shippingMethodOpen ? "up" : "down"}`}
></i>
</Button>
</div>
</h3>
<Collapse in={shippingMethodOpen}>
<div id="shippingMethodCollapse">
{/* Infographic-style table for Shipping Methods */}
<div className="row">
{/* Option 1 */}
<div className="col-md-4 mb-3">
<div
className="border p-3 text-center"
style={{ height: "auto" }}
>
<div className="d-grid gap-2 justify-content-center">
<input
type="checkbox"
className="form-check-input d-flex justify-content-center mx-auto fs-5"
id="option1Checkbox"
checked={selectedOption === "courier"}
onChange={() => handleOptionChange("courier")}
/>
<i
className="bi bi-truck text-primary"
style={{ fontSize: "3.7rem" }}
></i>
<label
className="form-check-label"
htmlFor="option1Checkbox"
>
<strong> Pickup by Courier (Lalamove/Grab)</strong>
<br />
Note: Customer pays for shipping. Wait for us to notify you
that your order is ready before you book your courier.
</label>
</div>
</div>
</div>
{/* Option 2 */}
<div className="col-md-4 mb-3">
<div
className="border p-3 text-center"
style={{ height: "auto" }}
>
<div className="d-grid gap-2 justify-content-center">
<input
type="checkbox"
className="form-check-input d-flex justify-content-center mx-auto fs-5"
id="option2Checkbox"
checked={selectedOption === "curbside"}
onChange={() => handleOptionChange("curbside")}
/>
<i
className="bi bi-shop text-primary"
style={{ fontSize: "3.7rem" }}
></i>
<label
className="form-check-label"
htmlFor="option2Checkbox"
>
<strong>Curbside Pickup</strong>
<br />
Note: Wait for us to notify you that your order is ready
before proceeding to our designated pickup area in front of
our building.
</label>
</div>
</div>
</div>
{/* Option 3 */}
<div className="col-md-4 mb-3">
<div
className="border p-3 text-center"
style={{ height: "auto" }}
>
<div className="d-grid gap-2 justify-content-center">
<input
type="checkbox"
className="form-check-input d-flex justify-content-center mx-auto fs-5"
id="option3Checkbox"
checked={selectedOption === "express"}
onChange={() => handleOptionChange("express")}
/>
<i
className="bi bi-clock text-primary"
style={{ fontSize: "3.7rem" }}
></i>
<label
className="form-check-label"
htmlFor="option3Checkbox"
>
<strong>Local Express Delivery</strong>
<br />
within 24 - 48 hours during business days
</label>
</div>
</div>
</div>
</div>
</div>
</Collapse>
</div>
);
};
export default ShippingMethodCollapse;

@ -3,14 +3,12 @@ import {
Container, Container,
Row, Row,
Col, Col,
Table,
Button,
Collapse,
Form,
Modal,
} from "react-bootstrap"; } from "react-bootstrap";
import AddressForm from "../components/AddressForm"; import OrderSummary from "../components/OrderSummary";
import EditAddress from "../components/EditAddress"; import PersonalInfoCollapse from "../components/PersonalInfoCollapse";
import AddressCollapse from "../components/AddressCollapse";
import PaymentCollapse from "../components/PaymentCollapse";
import ShippingMethodCollapse from "../components/ShippingMethodCollapse";
import { jwtDecode } from "jwt-decode"; import { jwtDecode } from "jwt-decode";
import { toast, ToastContainer } from "react-toastify"; import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css"; import "react-toastify/dist/ReactToastify.css";
@ -18,14 +16,14 @@ import "react-toastify/dist/ReactToastify.css";
const CheckoutPage = () => { const CheckoutPage = () => {
const [cartItems, setCartItems] = useState([]); const [cartItems, setCartItems] = useState([]);
const [totalPrice, setTotalPrice] = useState(0); const [totalPrice, setTotalPrice] = useState(0);
const [shippingFee, setShippingFee] = useState({ const [shippingFee] = useState({
value: 200, value: 200,
details: "Fixed shipping fee of ₱200", details: "Fixed shipping fee of ₱200",
}); });
const [promoCode, setPromoCode] = useState(""); const [promoCode, setPromoCode] = useState("");
const [personalInfoOpen, setPersonalInfoOpen] = useState(true); const [personalInfoOpen, setPersonalInfoOpen] = useState(true);
const [addressOpen, setAddressOpen] = useState(false); const [addressOpen, setAddressOpen] = useState(false);
const [paymentOpen, setPaymentOpen] = useState(false); const [shippingMethodOpen, setShippingMethodOpen] = useState(false);
const [isCustomerLoggedIn, setIsCustomerLoggedIn] = useState(false); const [isCustomerLoggedIn, setIsCustomerLoggedIn] = useState(false);
const [userDetails, setUserDetails] = useState(null); const [userDetails, setUserDetails] = useState(null);
const [appliedPromoCode, setAppliedPromoCode] = useState(null); const [appliedPromoCode, setAppliedPromoCode] = useState(null);
@ -33,13 +31,9 @@ const CheckoutPage = () => {
const [discountAmount, setDiscountAmount] = useState(0); const [discountAmount, setDiscountAmount] = useState(0);
const [addAddressFormOpen, setAddAddressFormOpen] = useState(false); const [addAddressFormOpen, setAddAddressFormOpen] = useState(false);
const [addresses, setAddresses] = useState([]); const [addresses, setAddresses] = useState([]);
const [selectedAddressId, setSelectedAddressId] = useState(null); const [addressFormError] = useState(null);
const [addressFormError, setAddressFormError] = useState(null); const [selectedAddress, setSelectedAddress] = useState(null);
const [selectedShippingMethod, setSelectedShippingMethod] = useState(null);
const handleAddressSelect = (addressId) => {
setSelectedAddressId(addressId);
};
const handleDeleteAddress = async (addressId) => { const handleDeleteAddress = async (addressId) => {
try { try {
@ -91,15 +85,6 @@ const CheckoutPage = () => {
} }
}; };
const [selectedAddress, setSelectedAddress] = useState({
street: "",
zipCode: "",
city: "",
state: "",
country: "Philippines",
});
const handleContinueAddress = async (e) => { const handleContinueAddress = async (e) => {
e.preventDefault(); // Prevent the default form submission behavior e.preventDefault(); // Prevent the default form submission behavior
@ -108,7 +93,8 @@ const CheckoutPage = () => {
!selectedAddress.street || !selectedAddress.street ||
!selectedAddress.zipCode || !selectedAddress.zipCode ||
!selectedAddress.city || !selectedAddress.city ||
!selectedAddress.state !selectedAddress.state ||
!selectedAddress.country
) { ) {
console.error("Please fill in all required address fields"); console.error("Please fill in all required address fields");
// Display an error toast // Display an error toast
@ -211,11 +197,6 @@ const CheckoutPage = () => {
const data = await response.json(); const data = await response.json();
setAddresses(data); setAddresses(data);
// Preselect the first address if available
if (data.length > 0) {
handleAddressSelect(data[0].id);
}
return data; // Return the fetched addresses return data; // Return the fetched addresses
} else { } else {
console.error("Failed to fetch addresses:", response.status); console.error("Failed to fetch addresses:", response.status);
@ -455,6 +436,9 @@ const CheckoutPage = () => {
fetchAddresses(); fetchAddresses();
}, [isCustomerLoggedIn]); }, [isCustomerLoggedIn]);
return ( return (
<Container className="my-5 text-start"> <Container className="my-5 text-start">
<h2>Checkout</h2> <h2>Checkout</h2>
@ -462,256 +446,58 @@ const CheckoutPage = () => {
{/* Left side - Collapsible Forms */} {/* Left side - Collapsible Forms */}
<Col xs={12} md={8}> <Col xs={12} md={8}>
{/* Collapsible Table - Personal Information */} {/* Collapsible Table - Personal Information */}
<div className="border rounded mb-3 p-3"> <PersonalInfoCollapse
<h3> personalInfoOpen={personalInfoOpen}
<div className="d-flex justify-content-between align-items-start"> setPersonalInfoOpen={setPersonalInfoOpen}
<span className="fs-6">1. Personal Information</span> isCustomerLoggedIn={isCustomerLoggedIn}
<Button userDetails={userDetails}
variant="link" />
onClick={() => setPersonalInfoOpen(!personalInfoOpen)}
aria-controls="personalInfoCollapse"
aria-expanded={personalInfoOpen}
>
<i
className={`bi bi-chevron-${
personalInfoOpen ? "up" : "down"
}`}
></i>
</Button>
</div>
</h3>
<Collapse in={personalInfoOpen}>
<div id="personalInfoCollapse">
{isCustomerLoggedIn ? (
<div>
<p>
<strong>Name:</strong> {userDetails?.firstName}{" "}
{userDetails?.lastName}
</p>
<p>
<strong>Email:</strong> {userDetails?.email}
</p>
<p>
<strong>Mobile Number:</strong> {userDetails?.mobileNo}
</p>
<p>If you want to update your information</p>
<Button variant="primary" href="/update-profile">
Update Information
</Button>
</div>
) : (
<div className="mx-4">
<p>
You are not logged in. Please log in or register to
proceed.
</p>
<Button variant="primary" href="/register">
Register
</Button>
<Button className="mx-2" variant="primary" href="/login">
Login
</Button>
</div>
)}
</div>
</Collapse>
</div>
{/* Collapsible Table - Address */} {/* Collapsible Table - Address */}
<div className="border rounded mb-3 p-3"> <AddressCollapse
<h3> addressOpen={addressOpen}
<div className="d-flex justify-content-between align-items-start"> setAddressOpen={setAddressOpen}
<span className="fs-6">2. Address</span> isCustomerLoggedIn={isCustomerLoggedIn}
<Button selectedAddress={selectedAddress}
variant="link" setSelectedAddress={setSelectedAddress}
onClick={() => setAddressOpen(!addressOpen)} handleContinueAddress={handleContinueAddress}
aria-controls="addressCollapse" addAddressFormOpen={addAddressFormOpen}
aria-expanded={addressOpen} setAddAddressFormOpen={setAddAddressFormOpen}
> addressFormError={addressFormError}
<i addresses={addresses}
className={`bi bi-chevron-${addressOpen ? "up" : "down"}`} handleDeleteAddress={handleDeleteAddress}
></i> />
</Button>
</div> {/* Collapsible Table - Shipping Method */}
</h3> <ShippingMethodCollapse
<Collapse in={addressOpen}> shippingMethodOpen={shippingMethodOpen}
<div id="addressCollapse"> setShippingMethodOpen={setShippingMethodOpen}
{isCustomerLoggedIn ? ( selectedShippingMethod={selectedShippingMethod}
<Row> setSelectedShippingMethod={setSelectedShippingMethod}
<span className="mb-2" style={{ fontSize: "16px" }}> />
The selected address will be used both as your personal
address (for invoice) and as your delivery address.
</span>
<Col className="border rounded mb-3 p-3" md={6}>
{/* Left column - Add New Address Form Dropdown */}
<div>
{addAddressFormOpen && (
<AddressForm
selectedAddress={selectedAddress}
setSelectedAddress={setSelectedAddress}
handleContinueAddress={handleContinueAddress}
addressFormError={addressFormError}
/>
)}
<Button
className="my-3"
variant="primary"
onClick={() =>
setAddAddressFormOpen(!addAddressFormOpen)
}
>
Add New Address
</Button>
</div>
</Col>
<Col md={6}>
{/* Right column - My Addresses from addressForm */}
{addresses && addresses.length > 0 ? (
<Table striped bordered hover>
<thead>
<tr>
<th>My Addresses</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{addresses.map((address, index) => (
<tr key={index}>
<td>
<div className="d-flex justify-content-between align-items-center">
<Form.Check
type="radio"
id={`addressRadio${index}`}
name="addressRadio"
checked={selectedAddressId === address.id}
onChange={() =>
handleAddressSelect(address.id)
}
/>
<p className="mx-3">
{address.street}, {address.city},{" "}
{address.state}, {address.zipCode},{" "}
{address.country}
</p>
</div>
</td>
<td>
<Button
variant="link"
className="text-danger"
onClick={() =>
handleDeleteAddress(address._id)
}
>
<i className="bi bi-trash"></i>
</Button>
</td>
</tr>
))}
</tbody>
</Table>
) : (
<p>No existing addresses. Add a new address below.</p>
)}
</Col>
</Row>
) : (
<p>
User not logged in. Log in to view and manage addresses.
</p>
)}
</div>
</Collapse>
</div>
{/* Collapsible Table - Payment */} {/* Collapsible Table - Payment */}
<div className="border rounded mb-3 p-3"> <PaymentCollapse
<h3> totalPrice={totalPrice}
<div className="d-flex justify-content-between align-items-start"> userDetails={userDetails}
<span className="fs-6">4. Payment</span> selectedShippingMethod={selectedShippingMethod}
<Button cartItems={cartItems}
variant="link" appliedPromoCode={appliedPromoCode}
onClick={() => setPaymentOpen(!paymentOpen)} selectedAddress={selectedAddress}
aria-controls="paymentCollapse" />
aria-expanded={paymentOpen}
>
<i
className={`bi bi-chevron-${paymentOpen ? "up" : "down"}`}
></i>
</Button>
</div>
</h3>
<Collapse in={paymentOpen}>
<div id="paymentCollapse">
{/* Placeholder content for payment table */}
</div>
</Collapse>
</div>
</Col> </Col>
{/* Right side - Order Summary */} {/* Right side - Order Summary */}
<Col xs={12} md={4} className="text-start"> <OrderSummary
<h3>Order Summary</h3> cartItems={cartItems}
<Table striped bordered hover> shippingFee={shippingFee}
<tbody> totalPrice={totalPrice}
<tr> discountAmount={discountAmount}
<td>Total Items</td> promoCode={promoCode}
<td> setPromoCode={setPromoCode}
{cartItems.reduce((total, item) => total + item.quantity, 0)} handleApplyPromoCode={handleApplyPromoCode}
</td> appliedPromoCode={appliedPromoCode}
</tr> />
<tr>
<td>Shipping Fee</td>
<td>{shippingFee.value}</td>
</tr>
<tr>
<td>Shipping Details</td>
<td>{shippingFee.details}</td>
</tr>
<tr>
<td>Total</td>
<td>
<strong>{totalPrice}</strong>
</td>
</tr>
<tr>
<td>Discount</td>
<td>
<strong>{discountAmount}%</strong>
</td>
</tr>
<tr>
<td colSpan="2">
<Form.Group controlId="promoCode">
<Form.Label>Promo Code</Form.Label>
<Form.Control
type="text"
placeholder="Enter promo code"
value={promoCode}
onChange={(e) => setPromoCode(e.target.value)}
/>
<Button
className="mt-3"
variant="primary"
onClick={handleApplyPromoCode}
>
Apply Promo Code
</Button>
</Form.Group>
{appliedPromoCode && (
<div>
<p className="mt-2">
Applied Promo Code: <strong>{appliedPromoCode}</strong>
</p>
</div>
)}
</td>
</tr>
</tbody>
</Table>
</Col>
</Row> </Row>
<ToastContainer position="bottom-right" /> <ToastContainer position="bottom-right" />
</Container> </Container>

@ -0,0 +1,33 @@
// OrderConfirmationPage.js
import React from "react";
import OrderDetails from "../components/OrderDetails";
import { Container } from "react-bootstrap";
const OrderConfirmationPage = ({ location }) => {
// Check if location is undefined
if (!location || !location.state) {
return (
<div>
<Container>
<h2>Order Confirmation</h2>
<p>Error: No order data found.</p>
</Container>
</div>
);
}
// Retrieve orderData from location.state
const orderData = location.state.orderData;
return (
<div>
<Container>
<h2>Order Confirmation</h2>
{/* Use the OrderDetails component to display order details */}
<OrderDetails orderData={orderData} />
</Container>
</div>
);
};
export default OrderConfirmationPage;
Loading…
Cancel
Save