From 82a05cde7ffcce106a957e715e906926f8403217 Mon Sep 17 00:00:00 2001 From: snaked2018 Date: Fri, 5 Jan 2024 20:02:31 +0800 Subject: [PATCH] Capstone 3 Added --- .../csp2-reciproco/controllers/order.js | 74 ++-- .../csp2-reciproco/controllers/user.js | 1 - .../csp2-reciproco/model/ShippingMethod.js | 13 + .../frontend/csp3-reciproco/src/App.js | 2 + .../src/components/AddressCollapse.js | 134 +++++++ .../src/components/AddressForm.js | 197 +++++----- .../src/components/BankOption.js | 38 ++ .../src/components/EditAddress.js | 2 +- .../src/components/OrderDetails.js | 73 ++++ .../src/components/OrderSummary.js | 86 +++++ .../src/components/PaymentCollapse.js | 144 ++++++++ .../src/components/PersonalInfoCollapse.js | 60 ++++ .../src/components/ShippingMethodCollapse.js | 132 +++++++ .../csp3-reciproco/src/pages/CheckoutPage.js | 338 ++++-------------- .../src/pages/OrderConfirmationPage.js | 33 ++ 15 files changed, 929 insertions(+), 398 deletions(-) create mode 100644 individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/model/ShippingMethod.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressCollapse.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/BankOption.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/OrderDetails.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/OrderSummary.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/PaymentCollapse.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/PersonalInfoCollapse.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/ShippingMethodCollapse.js create mode 100644 individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/OrderConfirmationPage.js diff --git a/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/order.js b/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/order.js index 7a506d7..a6cb071 100644 --- a/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/order.js +++ b/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/order.js @@ -3,39 +3,51 @@ const User = require("../model/User") exports.createOrder = async (req, res) => { try { - const { userId, products, totalAmount } = req.body - - const user = await User.findById(userId) - - if (!user) { - return res.status(404).json({ message: "User not found" }) - } - - // Check if the user is an admin - if (user.isAdmin) { - return res - .status(403) - .json({ message: "Admins cannot create orders" }) - } - - const newOrder = { - products: products, - totalAmount: totalAmount, - purchaseOn: Date.now(), - } - - user.orderedProducts.push(newOrder) - await user.save() - - res.status(201).json({ - message: "Order created successfully", - order: newOrder, - }) + const { + userId, + products, + totalAmount, + shippingAddress, + personalInfo, + shippingMethod, + paymentMethod, + // Add other details as needed + } = req.body; + + const user = await User.findById(userId); + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + // Check if the user is an admin + if (user.isAdmin) { + return res.status(403).json({ message: "Admins cannot create orders" }); + } + + const newOrder = { + products: products, + totalAmount: totalAmount, + shippingAddress: shippingAddress, + personalInfo: personalInfo, + shippingMethod: shippingMethod, + paymentMethod: paymentMethod, + // Add other details to newOrder as needed + purchaseOn: Date.now(), + }; + + user.orderedProducts.push(newOrder); + await user.save(); + + res.status(201).json({ + message: "Order created successfully", + order: newOrder, + }); } catch (error) { - console.error(error) - res.status(500).json({ message: "Internal Server Error" }) + console.error(error); + res.status(500).json({ message: "Internal Server Error" }); } -} + }; // Retrieve authenticated user's orders exports.getOrders = async (req, res) => { diff --git a/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/user.js b/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/user.js index 750a611..95e2518 100644 --- a/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/user.js +++ b/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/controllers/user.js @@ -532,4 +532,3 @@ exports.updateAddress = async (req, res) => { res.status(500).json({ message: 'Internal server error' }); } }; - \ No newline at end of file diff --git a/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/model/ShippingMethod.js b/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/model/ShippingMethod.js new file mode 100644 index 0000000..078de34 --- /dev/null +++ b/individual/fullstack/fullstack/capstone-3 added/backend/csp2-reciproco/model/ShippingMethod.js @@ -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; diff --git a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/App.js b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/App.js index 6caa485..d8cfd6a 100644 --- a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/App.js +++ b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/App.js @@ -18,6 +18,7 @@ import UpdateProfile from './components/UpdateProfile'; import AdminDashboard from './pages/AdminDashboard'; import ProductPage from './pages/ProductPage'; import CheckoutPage from './pages/CheckoutPage'; +import OrderConfirmationPage from './pages/OrderConfirmationPage'; import Error from './pages/Error'; // Import the NotFound component import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; @@ -108,6 +109,7 @@ function App() { } /> } /> } /> + } /> {/* Catch-all route that redirects to the home page */} } /> diff --git a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressCollapse.js b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressCollapse.js new file mode 100644 index 0000000..889bad9 --- /dev/null +++ b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressCollapse.js @@ -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 ( +
+

+
+ 2. Address + +
+

+ +
+ {isCustomerLoggedIn ? ( + + + The selected address will be used both as your personal address + (for invoice) and as your delivery address. + + + {/* Left column - Add New Address Form Dropdown */} +
+ {addAddressFormOpen && ( + + )} + + +
+ + + + {/* Right column - My Addresses from addressForm */} + {addresses && addresses.length > 0 ? ( + + + + + + + + + {addresses.map((address, index) => ( + + + + + ))} + +
My AddressesActions
+
+ + handleAddressSelect(address._id) + } + /> + +

+ {address.street}, {address.city},{" "} + {address.state}, {address.zipCode},{" "} + {address.country} +

+
+
+ +
+ ) : ( +

No existing addresses. Add a new address below.

+ )} + +
+ ) : ( +

User not logged in. Log in to view and manage addresses.

+ )} +
+
+
+ ); +}; + +export default AddressCollapse; diff --git a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressForm.js b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressForm.js index 90e3a00..669414c 100644 --- a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressForm.js +++ b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/AddressForm.js @@ -1,6 +1,5 @@ -import React from 'react'; -import { Form, Button } from 'react-bootstrap'; - +import React from "react"; +import { Form, Button } from "react-bootstrap"; const AddressForm = ({ selectedAddress, @@ -8,91 +7,92 @@ const AddressForm = ({ handleContinueAddress, addressFormError, }) => { - const philippineStates = [ - "...", - "Abra", - "Agusan del Norte", - "Agusan del Sur", - "Aklan", - "Albay", - "Antique", - "Apayao", - "Aurora", - "Basilan", - "Bataan", - "Batanes", - "Batangas", - "Benguet", - "Biliran", - "Bohol", - "Bukidnon", - "Bulacan", - "Cagayan", - "Camarines Norte", - "Camarines Sur", - "Camiguin", - "Capiz", - "Catanduanes", - "Cavite", - "Cebu", - "Cotabato", - "Davao de Oro (formerly Compostela Valley)", - "Davao del Norte", - "Davao del Sur", - "Davao Occidental", - "Davao Oriental", - "Dinagat Islands", - "Eastern Samar", - "Guimaras", - "Ifugao", - "Ilocos Norte", - "Ilocos Sur", - "Iloilo", - "Isabela", - "Kalinga", - "La Union", - "Laguna", - "Lanao del Norte", - "Lanao del Sur", - "Leyte", - "Maguindanao", - "Marinduque", - "Masbate", - "Misamis Occidental", - "Misamis Oriental", - "Mountain Province", - "Negros Occidental", - "Negros Oriental", - "Northern Samar", - "Nueva Ecija", - "Nueva Vizcaya", - "Occidental Mindoro", - "Oriental Mindoro", - "Palawan", - "Pampanga", - "Pangasinan", - "Quezon", - "Quirino", - "Rizal", - "Romblon", - "Samar (Western Samar)", - "Sarangani", - "Siquijor", - "Sorsogon", - "South Cotabato", - "Southern Leyte", - "Sultan Kudarat", - "Sulu", - "Surigao del Norte", - "Surigao del Sur", - "Tarlac", - "Tawi-Tawi", - "Zambales", - "Zamboanga del Norte", - "Zamboanga del Sur", - "Zamboanga Sibugay", - ]; - // Define the array of Philippine states + const philippineStates = [ + "...", + "Abra", + "Agusan del Norte", + "Agusan del Sur", + "Aklan", + "Albay", + "Antique", + "Apayao", + "Aurora", + "Basilan", + "Bataan", + "Batanes", + "Batangas", + "Benguet", + "Biliran", + "Bohol", + "Bukidnon", + "Bulacan", + "Cagayan", + "Camarines Norte", + "Camarines Sur", + "Camiguin", + "Capiz", + "Catanduanes", + "Cavite", + "Cebu", + "Cotabato", + "Davao de Oro (formerly Compostela Valley)", + "Davao del Norte", + "Davao del Sur", + "Davao Occidental", + "Davao Oriental", + "Dinagat Islands", + "Eastern Samar", + "Guimaras", + "Ifugao", + "Ilocos Norte", + "Ilocos Sur", + "Iloilo", + "Isabela", + "Kalinga", + "La Union", + "Laguna", + "Lanao del Norte", + "Lanao del Sur", + "Leyte", + "Maguindanao", + "Marinduque", + "Masbate", + "Misamis Occidental", + "Misamis Oriental", + "Mountain Province", + "Negros Occidental", + "Negros Oriental", + "Northern Samar", + "Nueva Ecija", + "Nueva Vizcaya", + "Occidental Mindoro", + "Oriental Mindoro", + "Palawan", + "Pampanga", + "Pangasinan", + "Quezon", + "Quirino", + "Rizal", + "Romblon", + "Samar (Western Samar)", + "Sarangani", + "Siquijor", + "Sorsogon", + "South Cotabato", + "Southern Leyte", + "Sultan Kudarat", + "Sulu", + "Surigao del Norte", + "Surigao del Sur", + "Tarlac", + "Tawi-Tawi", + "Zambales", + "Zamboanga del Norte", + "Zamboanga del Sur", + "Zamboanga Sibugay", + ]; + + const countryStates = ["...", "Philippines"]; return (
@@ -142,6 +142,7 @@ const AddressForm = ({ required /> + State + Country - + + setSelectedAddress({ + ...selectedAddress, + country: e.target.value, // Fix: change state to country + }) + } + required + > + + {countryStates.map((country, index) => ( + + ))} + + + + +
+ + Select Payment Option: + handlePaymentOptionChange(e.target.value)} + > + + + {/* Add other payment options here */} + + + + {selectedPaymentOption === "bank" && ( + + )} + + {/* Terms Agreement */} + + + + + {/* Payment Button */} + + + {/* Message below the Payment Button */} + {!termsAgreed && ( +
+ Please agree to the terms of service before submitting the order. +
+ )} +
+
+ + ); +}; + +// 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; diff --git a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/PersonalInfoCollapse.js b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/PersonalInfoCollapse.js new file mode 100644 index 0000000..0ff75a8 --- /dev/null +++ b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/PersonalInfoCollapse.js @@ -0,0 +1,60 @@ +import React from "react"; +import { Button, Collapse } from "react-bootstrap"; + +const PersonalInfoCollapse = ({ personalInfoOpen, setPersonalInfoOpen, isCustomerLoggedIn, userDetails }) => { + return ( +
+

+
+ 1. Personal Information + +
+

+ +
+ {isCustomerLoggedIn ? ( +
+

+ Name: {userDetails?.firstName}{" "} + {userDetails?.lastName} +

+

+ Email: {userDetails?.email} +

+

+ Mobile Number: {userDetails?.mobileNo} +

+

If you want to update your information

+ +
+ ) : ( +
+

+ You are not logged in. Please log in or register to proceed. +

+ + +
+ )} +
+
+
+ ); +}; + +export default PersonalInfoCollapse; diff --git a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/ShippingMethodCollapse.js b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/ShippingMethodCollapse.js new file mode 100644 index 0000000..0358a48 --- /dev/null +++ b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/components/ShippingMethodCollapse.js @@ -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 ( +
+

+
+ 3. Shipping Method + +
+

+ +
+ {/* Infographic-style table for Shipping Methods */} +
+ {/* Option 1 */} +
+
+
+ handleOptionChange("courier")} + /> + + +
+
+
+ + {/* Option 2 */} +
+
+
+ handleOptionChange("curbside")} + /> + + +
+
+
+ + {/* Option 3 */} +
+
+
+ handleOptionChange("express")} + /> + + +
+
+
+
+
+
+
+ ); +}; + +export default ShippingMethodCollapse; diff --git a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/CheckoutPage.js b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/CheckoutPage.js index 44dcce0..bf7a3c6 100644 --- a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/CheckoutPage.js +++ b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/CheckoutPage.js @@ -3,14 +3,12 @@ import { Container, Row, Col, - Table, - Button, - Collapse, - Form, - Modal, } from "react-bootstrap"; -import AddressForm from "../components/AddressForm"; -import EditAddress from "../components/EditAddress"; +import OrderSummary from "../components/OrderSummary"; +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 { toast, ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; @@ -18,14 +16,14 @@ import "react-toastify/dist/ReactToastify.css"; const CheckoutPage = () => { const [cartItems, setCartItems] = useState([]); const [totalPrice, setTotalPrice] = useState(0); - const [shippingFee, setShippingFee] = useState({ + const [shippingFee] = useState({ value: 200, details: "Fixed shipping fee of ₱200", }); const [promoCode, setPromoCode] = useState(""); const [personalInfoOpen, setPersonalInfoOpen] = useState(true); const [addressOpen, setAddressOpen] = useState(false); - const [paymentOpen, setPaymentOpen] = useState(false); + const [shippingMethodOpen, setShippingMethodOpen] = useState(false); const [isCustomerLoggedIn, setIsCustomerLoggedIn] = useState(false); const [userDetails, setUserDetails] = useState(null); const [appliedPromoCode, setAppliedPromoCode] = useState(null); @@ -33,13 +31,9 @@ const CheckoutPage = () => { const [discountAmount, setDiscountAmount] = useState(0); const [addAddressFormOpen, setAddAddressFormOpen] = useState(false); const [addresses, setAddresses] = useState([]); - const [selectedAddressId, setSelectedAddressId] = useState(null); - const [addressFormError, setAddressFormError] = useState(null); - - - const handleAddressSelect = (addressId) => { - setSelectedAddressId(addressId); - }; + const [addressFormError] = useState(null); + const [selectedAddress, setSelectedAddress] = useState(null); + const [selectedShippingMethod, setSelectedShippingMethod] = useState(null); const handleDeleteAddress = async (addressId) => { try { @@ -91,15 +85,6 @@ const CheckoutPage = () => { } }; - const [selectedAddress, setSelectedAddress] = useState({ - street: "", - zipCode: "", - city: "", - state: "", - country: "Philippines", - }); - - const handleContinueAddress = async (e) => { e.preventDefault(); // Prevent the default form submission behavior @@ -108,7 +93,8 @@ const CheckoutPage = () => { !selectedAddress.street || !selectedAddress.zipCode || !selectedAddress.city || - !selectedAddress.state + !selectedAddress.state || + !selectedAddress.country ) { console.error("Please fill in all required address fields"); // Display an error toast @@ -211,11 +197,6 @@ const CheckoutPage = () => { const data = await response.json(); setAddresses(data); - // Preselect the first address if available - if (data.length > 0) { - handleAddressSelect(data[0].id); - } - return data; // Return the fetched addresses } else { console.error("Failed to fetch addresses:", response.status); @@ -455,6 +436,9 @@ const CheckoutPage = () => { fetchAddresses(); }, [isCustomerLoggedIn]); + + + return (

Checkout

@@ -462,256 +446,58 @@ const CheckoutPage = () => { {/* Left side - Collapsible Forms */} {/* Collapsible Table - Personal Information */} -
-

-
- 1. Personal Information - -
-

- -
- {isCustomerLoggedIn ? ( -
-

- Name: {userDetails?.firstName}{" "} - {userDetails?.lastName} -

-

- Email: {userDetails?.email} -

-

- Mobile Number: {userDetails?.mobileNo} -

-

If you want to update your information

- -
- ) : ( -
-

- You are not logged in. Please log in or register to - proceed. -

- - -
- )} -
-
-
- + {/* Collapsible Table - Address */} -
-

-
- 2. Address - -
-

- -
- {isCustomerLoggedIn ? ( - - - The selected address will be used both as your personal - address (for invoice) and as your delivery address. - - - {/* Left column - Add New Address Form Dropdown */} -
- {addAddressFormOpen && ( - - )} - - -
- - - - {/* Right column - My Addresses from addressForm */} - {addresses && addresses.length > 0 ? ( - - - - - - - - - {addresses.map((address, index) => ( - - - - - ))} - -
My AddressesActions
-
- - handleAddressSelect(address.id) - } - /> -

- {address.street}, {address.city},{" "} - {address.state}, {address.zipCode},{" "} - {address.country} -

-
-
- -
- ) : ( -

No existing addresses. Add a new address below.

- )} - -
- ) : ( -

- User not logged in. Log in to view and manage addresses. -

- )} -
-
-
+ + + {/* Collapsible Table - Shipping Method */} + {/* Collapsible Table - Payment */} -
-

-
- 4. Payment - -
-

- -
- {/* Placeholder content for payment table */} -
-
-
+ + + {/* Right side - Order Summary */} - -

Order Summary

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total Items - {cartItems.reduce((total, item) => total + item.quantity, 0)} -
Shipping Fee{shippingFee.value}
Shipping Details{shippingFee.details}
Total - ₱{totalPrice} -
Discount - {discountAmount}% -
- - Promo Code - setPromoCode(e.target.value)} - /> - - - {appliedPromoCode && ( -
-

- Applied Promo Code: {appliedPromoCode} -

-
- )} -
- +
diff --git a/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/OrderConfirmationPage.js b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/OrderConfirmationPage.js new file mode 100644 index 0000000..7f965a4 --- /dev/null +++ b/individual/fullstack/fullstack/capstone-3 added/frontend/csp3-reciproco/src/pages/OrderConfirmationPage.js @@ -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 ( +
+ +

Order Confirmation

+

Error: No order data found.

+
+
+ ); + } + + // Retrieve orderData from location.state + const orderData = location.state.orderData; + + return ( +
+ +

Order Confirmation

+ {/* Use the OrderDetails component to display order details */} + +
+
+ ); +}; + +export default OrderConfirmationPage;