Skip to content

Commit 08ad913

Browse files
committed
finalized frontend
1 parent 4d8c134 commit 08ad913

File tree

12 files changed

+686
-133
lines changed

12 files changed

+686
-133
lines changed

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import CategoryPage from "./pages/SodaShop/MerchCategory"
1515
import ItemPage from "./pages/SodaShop/ItemPage"
1616
import ProfilePage from "./pages/SodaShop/UserProfile"
1717
import InstructionsPage from "./pages/SodaShop/Instructions"
18-
import UserCartPage from "./pages/SodaShop/Cart"
18+
import UserCartPage from "./pages/SodaShop/CartCheckout"
1919
import NotFound from "./pages/NotFound"
2020
import ScrollToTop from "./components/ScrollToTop"
2121

src/app.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,4 +528,5 @@ background-position: center;
528528

529529
.hidden {
530530
display: none;
531-
}
531+
}
532+
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"use client"
2+
3+
import type React from "react"
4+
5+
import { useState } from "react"
6+
7+
interface CartCheckoutBoxProps {
8+
cartItems: Array<{
9+
id: string
10+
name: string
11+
price: string
12+
quantity: number
13+
}>
14+
currentBalance?: number
15+
}
16+
17+
export default function CartCheckoutBox({ cartItems, currentBalance = 500 }: CartCheckoutBoxProps) {
18+
const [formData, setFormData] = useState({
19+
firstName: "",
20+
lastName: "",
21+
email: "",
22+
})
23+
24+
// Calculate cart total
25+
const cartTotal = cartItems.reduce((total, item) => {
26+
return total + Number.parseFloat(item.price) * item.quantity
27+
}, 0)
28+
29+
const remainingBalance = currentBalance - cartTotal
30+
31+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
32+
const { name, value } = e.target
33+
setFormData((prev) => ({
34+
...prev,
35+
[name]: value,
36+
}))
37+
}
38+
39+
const handleConfirmOrder = (e: React.FormEvent) => {
40+
e.preventDefault()
41+
// TODO: Implement order confirmation logic
42+
console.log("Order confirmed:", { formData, cartItems, cartTotal })
43+
alert("Order functionality will be implemented with backend!")
44+
}
45+
46+
const isFormValid = formData.firstName && formData.lastName && formData.email
47+
48+
return (
49+
<div className="sticky top-10 space-y-6">
50+
{/* Cart Summary */}
51+
<div className="bg-gray-800 border border-gray-700 rounded-lg p-6">
52+
<div className="space-y-3 text-white">
53+
<div className="flex justify-between items-center">
54+
<span>Current Balance:</span>
55+
<span className="font-semibold">{currentBalance}</span>
56+
</div>
57+
58+
<div className="flex justify-between items-center text-red-400">
59+
<span>- Cart Total:</span>
60+
<span className="font-semibold">{cartTotal.toFixed(2)}</span>
61+
</div>
62+
63+
<hr className="border-gray-600" />
64+
65+
<div className="flex justify-between items-center font-bold text-lg">
66+
<span>Remaining Balance:</span>
67+
<span className={remainingBalance >= 0 ? "text-green-400" : "text-red-400"}>
68+
{remainingBalance.toFixed(2)}
69+
</span>
70+
</div>
71+
</div>
72+
</div>
73+
74+
{/* Pickup Details Form */}
75+
<div className="bg-gray-800 border border-gray-700 rounded-lg p-6">
76+
<h3 className="text-white font-semibold mb-4">Please enter your details for pickup:</h3>
77+
78+
<form onSubmit={handleConfirmOrder} className="space-y-4">
79+
<div>
80+
<input
81+
type="text"
82+
name="firstName"
83+
placeholder="First Name"
84+
value={formData.firstName}
85+
onChange={handleInputChange}
86+
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
87+
required
88+
/>
89+
</div>
90+
91+
<div>
92+
<input
93+
type="text"
94+
name="lastName"
95+
placeholder="Last Name"
96+
value={formData.lastName}
97+
onChange={handleInputChange}
98+
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
99+
required
100+
/>
101+
</div>
102+
103+
<div>
104+
<input
105+
type="email"
106+
name="email"
107+
placeholder="Email"
108+
value={formData.email}
109+
onChange={handleInputChange}
110+
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
111+
required
112+
/>
113+
</div>
114+
115+
<button
116+
type="submit"
117+
disabled={!isFormValid || remainingBalance < 0}
118+
className="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white font-semibold rounded-md transition-colors"
119+
>
120+
Confirm Order
121+
</button>
122+
123+
{remainingBalance < 0 && (
124+
<p className="text-red-400 text-sm text-center">Insufficient balance. Please remove some items.</p>
125+
)}
126+
</form>
127+
</div>
128+
</div>
129+
)
130+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"use client"
2+
3+
import type React from "react"
4+
import { useState, useEffect, useRef } from "react"
5+
import { Trash2, ChevronDown } from "lucide-react"
6+
import { useNavigate } from "react-router-dom"
7+
8+
interface CartItem {
9+
id: string
10+
name: string
11+
price: string
12+
quantity: number
13+
size?: string
14+
image?: string
15+
}
16+
17+
interface CartCheckoutItemListProps {
18+
items: CartItem[]
19+
onRemoveItem?: (itemId: string) => void
20+
onUpdateQuantity?: (itemId: string, quantity: number) => void
21+
}
22+
23+
export default function CartCheckoutItemList({ items, onRemoveItem, onUpdateQuantity }: CartCheckoutItemListProps) {
24+
const navigate = useNavigate()
25+
const [showScrollIndicator, setShowScrollIndicator] = useState(false)
26+
const scrollContainerRef = useRef<HTMLDivElement>(null)
27+
28+
const handleItemClick = (itemName: string) => {
29+
// Format the item name for URL (same pattern as ProductCard)
30+
const formattedName = encodeURIComponent(itemName.toLowerCase().replace(/\s+/g, "-"))
31+
navigate(`/shop/item/${formattedName}`)
32+
}
33+
34+
const handleRemoveItem = (e: React.MouseEvent, itemId: string) => {
35+
e.stopPropagation() // Prevent item click when deleting
36+
onRemoveItem?.(itemId)
37+
}
38+
39+
const handleQuantityChange = (e: React.ChangeEvent<HTMLSelectElement>, itemId: string) => {
40+
e.stopPropagation()
41+
const newQuantity = Number.parseInt(e.target.value)
42+
onUpdateQuantity?.(itemId, newQuantity)
43+
}
44+
45+
const checkScrollPosition = () => {
46+
const container = scrollContainerRef.current
47+
if (!container) return
48+
49+
const { scrollTop, scrollHeight, clientHeight } = container
50+
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10 // 10px threshold
51+
const hasScrollableContent = scrollHeight > clientHeight
52+
53+
setShowScrollIndicator(hasScrollableContent && !isAtBottom)
54+
}
55+
56+
useEffect(() => {
57+
const container = scrollContainerRef.current
58+
if (!container) return
59+
60+
// Check initial scroll position
61+
checkScrollPosition()
62+
63+
// Add scroll event listener
64+
container.addEventListener("scroll", checkScrollPosition)
65+
66+
// Check when items change (in case items are added/removed)
67+
const resizeObserver = new ResizeObserver(checkScrollPosition)
68+
resizeObserver.observe(container)
69+
70+
return () => {
71+
container.removeEventListener("scroll", checkScrollPosition)
72+
resizeObserver.disconnect()
73+
}
74+
}, [items])
75+
76+
return (
77+
<div className="relative">
78+
{/* Scrollable Container */}
79+
<div ref={scrollContainerRef} className="space-y-4 overflow-y-auto" style={{ maxHeight: "550px" }}>
80+
{items.map((item) => (
81+
<div
82+
key={item.id}
83+
className="flex gap-4 p-4 border border-gray-700 rounded-lg hover:bg-gray-800/50 transition-colors cursor-pointer"
84+
onClick={() => handleItemClick(item.name)}
85+
>
86+
{/* Product Image */}
87+
<div className="w-25 h-25 bg-gray-600 rounded-md flex-shrink-0">
88+
{item.image ? (
89+
<img
90+
src={item.image || "/placeholder.svg?height=80&width=80"}
91+
alt={item.name}
92+
className="w-full h-full object-cover rounded-md"
93+
/>
94+
) : (
95+
<div className="w-full h-full bg-gray-600 rounded-md" />
96+
)}
97+
</div>
98+
99+
{/* Product Details */}
100+
<div className="flex-1 min-w-0 flex flex-col justify-between">
101+
<div className="flex justify-between items-center pt-1">
102+
<h3 className="font-semibold text-white text-xl truncate">{item.name}</h3>
103+
{/* Delete Button */}
104+
<button
105+
onClick={(e) => handleRemoveItem(e, item.id)}
106+
className="p-2 text-gray-400 hover:text-red-400 hover:bg-gray-700 rounded-md transition-colors"
107+
aria-label="Remove item"
108+
>
109+
<Trash2 size={20} />
110+
</button>
111+
</div>
112+
113+
{item.size && <p className="text-gray-400 text-sm">Size: {item.size}</p>}
114+
115+
<div className="flex justify-between">
116+
117+
{/* Quantity Selector */}
118+
<div className="pb-1">
119+
<label className="text-gray-400 text-sm mr-2">Qty:</label>
120+
<select
121+
value={item.quantity}
122+
onChange={(e) => handleQuantityChange(e, item.id)}
123+
className="bg-gray-800 border border-gray-700 rounded px-2 py-1 text-white text-sm"
124+
onClick={(e) => e.stopPropagation()}
125+
>
126+
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((num) => (
127+
<option key={num} value={num}>
128+
{num}
129+
</option>
130+
))}
131+
</select>
132+
</div>
133+
<div className="text-right"> {/* mt-auto pushes to bottom */}
134+
<p className="text-lg">{item.price}</p>
135+
</div>
136+
</div>
137+
</div>
138+
</div>
139+
))}
140+
141+
{items.length === 0 && (
142+
<div className="text-center py-12 text-gray-400">
143+
<p className="text-lg">Your cart is empty</p>
144+
<p className="text-sm mt-2">Add some items to get started!</p>
145+
</div>
146+
)}
147+
</div>
148+
149+
{/* Scroll Down Indicator */}
150+
{showScrollIndicator && (
151+
<div className="absolute bottom-0 left-0 right-0 flex justify-center">
152+
<div className="bg-gradient-to-t from-gray-900 via-gray-900/80 to-transparent pt-8 pb-2 px-4">
153+
<div className="flex items-center gap-2 text-gray-400 text-sm animate-bounce">
154+
<ChevronDown size={16} />
155+
<span>Scroll down for more items</span>
156+
<ChevronDown size={16} />
157+
</div>
158+
</div>
159+
</div>
160+
)}
161+
</div>
162+
)
163+
}

src/components/SodaShop/CartPopup.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { X } from "lucide-react"
44
import { Trash2 } from 'lucide-react';
55

6+
import { Link, NavLink } from "react-router-dom"
7+
68

79
interface CartItem {
810
id: string
@@ -83,7 +85,9 @@ export default function CartPopup({ isOpen, onClose, items = [] }: CartPopupProp
8385
</div>
8486
</div>
8587
<div className="py-4 border-t">
86-
<button className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">View Cart</button>
88+
<NavLink to="/shop/cart">
89+
<button className="cursor-pointer w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">View Cart</button>
90+
</NavLink>
8791
</div>
8892
</section>
8993
)}

0 commit comments

Comments
 (0)