Skip to content

Commit 6d85cd8

Browse files
d20
1 parent 6be1200 commit 6d85cd8

File tree

3 files changed

+257
-7
lines changed

3 files changed

+257
-7
lines changed

app/Services/CartService.php

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use App\Models\Cart;
6+
use App\Models\Product;
7+
use App\Models\VariationTypeOption;
8+
use Illuminate\Support\Facades\Auth;
9+
use Illuminate\Support\Facades\Cookie;
10+
use Illuminate\Support\Facades\Log;
11+
12+
class CartSevice
13+
{
14+
private ?array $cachedCartItems = null;
15+
private const COOKIE_NAME = 'cartItems';
16+
protected const COOKIE_LIFETIME = 60 * 24 * 365;
17+
18+
public function addItemCart(Product $product, int $quantity = 1, array $optionIds = [])
19+
{
20+
if (!$optionIds) {
21+
$optionIds = $product->getFirstOptionMap();
22+
}
23+
24+
$price = $product->getPriceForOptions($optionIds);
25+
26+
if (Auth::check()) {
27+
// save to the database
28+
$this->saveItemToDatabase($product->id, $quantity, $price, $optionIds);
29+
} else {
30+
//save to the cookie
31+
$this->saveItemToCookies($product->id, $quantity, $price, $optionIds);
32+
}
33+
}
34+
35+
public function updateItemQuantity(int $productId, int $quantity, array $optionIds = [])
36+
{
37+
if (Auth::check()) {
38+
$this->updateItemQuantityToDatabase($productId, $quantity, $optionIds);
39+
} else {
40+
$this->updateItemQuantityToCookies($productId, $quantity, $optionIds);
41+
}
42+
}
43+
44+
public function removeItemFromCart(int $productId, array $optionIds = [])
45+
{
46+
if (Auth::check()) {
47+
$this->removeItemFromDatabase($productId, $optionIds);
48+
} else {
49+
$this->removeItemFromCookies($productId, $optionIds);
50+
}
51+
}
52+
53+
54+
protected function updateItemQuantityToDatabase(int $productId, int $quantity, array $optionIds = []): void
55+
{
56+
$userId = Auth::id();
57+
krsort($optionIds);
58+
59+
$cartItem = Cart::where('product_id', $productId)->where('user_id', $userId)
60+
->where('variation_type_option_ids', $optionIds)
61+
->first();
62+
63+
if ($cartItem) {
64+
$cartItem->quantity = $quantity;
65+
$cartItem->save();
66+
}
67+
}
68+
69+
protected function updateItemQuantityToCookies(int $productId, int $quantity, array $optionIds = []): void
70+
{
71+
$cartItems = $this->getCartItemsFromCookies();
72+
krsort($optionIds);
73+
$cartItemKey = $productId . '_' . json_encode($optionIds);
74+
if (isset($cartItems[$cartItemKey])) {
75+
$cartItems[$cartItemKey]['quantity'] = $quantity;
76+
}
77+
Cookie::queue(self::COOKIE_NAME, json_encode($cartItems), self::COOKIE_LIFETIME);
78+
}
79+
80+
protected function removeItemFromDatabase(int $productId, array $optionIds = []): void
81+
{
82+
$userId = Auth::id();
83+
krsort($optionIds);
84+
Cart::where('product_id', $productId)
85+
->where('user_id', $userId)
86+
->where('variation_type_option_ids', $optionIds)
87+
->delete();
88+
}
89+
90+
protected function removeItemFromCookies(int $productId, array $optionIds = []): void
91+
{
92+
$cartItems = $this->getCartItemsFromCookies();
93+
krsort($optionIds);
94+
$cartItemKey = $productId . '_' . json_encode($optionIds);
95+
if (isset($cartItems[$cartItemKey])) {
96+
unset($cartItems[$cartItemKey]);
97+
}
98+
Cookie::queue(self::COOKIE_NAME, json_encode($cartItems), self::COOKIE_LIFETIME);
99+
}
100+
101+
102+
public function getCartItems(): array
103+
{
104+
try {
105+
if ($this->cachedCartItems === null) {
106+
if (Auth::check()) {
107+
$cartItems = $this->getCartItemsFromDatabase();
108+
} else {
109+
$cartItems = $this->getCartItemsFromCookies();
110+
}
111+
112+
$productIds = collect($cartItems)->map(fn($item) => $item['product_id']);
113+
114+
$products = Product::whereIn('id', $productIds)->get()->keyBy('id');
115+
116+
$cartItemData = [];
117+
foreach ($cartItems as $cartItem) {
118+
$product = data_get($products, $cartItem['product_id']);
119+
if (!$product) continue;
120+
121+
$optionInfo = [];
122+
123+
$options = VariationTypeOption::with('variationType')->whereIn('id', $cartItem['option_ids'])->get()->keyBy('id');
124+
125+
$imageUrl = null;
126+
foreach ($cartItem['option_ids'] as $optionId) {
127+
$option = data_get($options, $optionId);
128+
129+
if (!$imageUrl) {
130+
$imageUrl = $option->getFirstMediaUrl('images', 'small');
131+
}
132+
133+
$optionInfo[] = [
134+
'id' => $option->id,
135+
'name' => $option->name,
136+
'type' => [
137+
'id' => $option->variationType->id,
138+
'name' => $option->variationType->name,
139+
],
140+
];
141+
}
142+
143+
$cartItemData[] = [
144+
'id' => $cartItem['id'],
145+
'product_id' => $product->id,
146+
'name' => $product->name,
147+
'slug' => $product->slug,
148+
'quantity' => $cartItem['quantity'],
149+
'price' => $cartItem['price'],
150+
'option_ids' => $cartItem['option_ids'],
151+
'options' => $optionInfo,
152+
'image' => $imageUrl ?: $product->getFirstMediaUrl('images', 'small'),
153+
];
154+
}
155+
$this->cachedCartItems = $cartItemData;
156+
}
157+
return $this->cachedCartItems;
158+
} catch (\Exception $e) {
159+
//throw $th;
160+
Log::error($e->getMessage() . PHP_EOL . $e->getTraceAsString());
161+
}
162+
return [];
163+
}
164+
165+
166+
protected function saveItemToDatabase(int $productId, int $quantity, int $price, array $optionIds): void
167+
{
168+
$userId = Auth::id();
169+
krsort($optionIds);
170+
171+
$cartItem = Cart::where('product_id', $productId)
172+
->where('user_id', $userId)
173+
->where('variation_type_option_ids', $optionIds)
174+
->first();
175+
176+
if ($cartItem) {
177+
$cartItem->quantity = $cartItem->quantity + $quantity;
178+
$cartItem->save();
179+
} else {
180+
$cartItem = new Cart();
181+
$cartItem->user_id = $userId;
182+
$cartItem->product_id = $productId;
183+
$cartItem->quantity = $quantity;
184+
$cartItem->price = $price;
185+
$cartItem->variation_type_option_ids = json_encode($optionIds);
186+
$cartItem->save();
187+
}
188+
}
189+
190+
protected function saveItemToCookies(int $productId, int $quantity, int $price, array $optionIds): void
191+
{
192+
$cartItems = $this->getCartItemsFromCookies();
193+
krsort($optionIds);
194+
$cartItemKey = $productId . '_' . json_encode($optionIds);
195+
if (!isset($cartItems[$cartItemKey])) {
196+
$cartItems[$cartItemKey] = [
197+
'id' => uniqid(),
198+
'product_id' => $productId,
199+
'quantity' => $quantity,
200+
'price' => $price,
201+
'option_ids' => $optionIds
202+
];
203+
} else {
204+
$cartItems[$cartItemKey]['quantity'] += $quantity;
205+
}
206+
207+
Cookie::queue(self::COOKIE_NAME, json_encode($cartItems), self::COOKIE_LIFETIME);
208+
}
209+
210+
public function getCartItemsFromDatabase(): array
211+
{
212+
$userId = Auth::id();
213+
$cartItems = Cart::where('user_id', $userId)->get()
214+
->map(function ($item) {
215+
return [
216+
'id' => $item->id,
217+
'product_id' => $item->product_id,
218+
'quantity' => $item->quantity,
219+
'price' => $item->price,
220+
'option_ids' => $item->variation_type_option_ids
221+
];
222+
})
223+
->toArray();
224+
return $cartItems;
225+
}
226+
227+
public function getCartItemsFromCookies(): array
228+
{
229+
$cartItems = json_decode(Cookie::get(self::COOKIE_NAME, '[]'), true);
230+
return $cartItems;
231+
}
232+
233+
public function clearCart()
234+
{
235+
if (Auth::check()) {
236+
Cart::where('user_id', Auth::id())->delete();
237+
} else {
238+
Cookie::queue(self::COOKIE_NAME, json_encode([]), self::COOKIE_LIFETIME);
239+
}
240+
}
241+
}

resources/js/components/ecommerce/HomePage/ProductCard.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { ProductListItem } from '@/types';
22
import { router } from '@inertiajs/react';
33
import { Heart, ShoppingBag } from 'lucide-react';
4-
5-
const ProductCard = (product: ProductListItem) => {
4+
type Props = {
5+
product?: ProductListItem;
6+
};
7+
const ProductCard = ({ product }: { product: ProductListItem }) => {
8+
if (!product) return null;
69
const handleDetail = (slug: string) => {
710
router.visit(route('product.detail', { slug }));
811
};
@@ -14,10 +17,10 @@ const ProductCard = (product: ProductListItem) => {
1417
return (
1518
<div className="group overflow-hidden rounded-lg bg-white shadow-sm">
1619
<div className="relative">
17-
<img src={product.image.replace('localhost', '127.0.0.1:8000')} alt="Product" className="h-64 w-full object-cover" />
18-
{product.isDiscount && (
19-
<div className="absolute top-0 right-0 m-2 rounded-md bg-red-500 px-2 py-1 text-sm text-white">-{product.discount}%</div>
20+
{product?.image && (
21+
<img src={product.image.replace('localhost', '127.0.0.1:8000')} alt="Product" className="h-64 w-full object-cover" />
2022
)}
23+
2124
<div className="bg-opacity-20 absolute inset-0 flex items-center justify-center bg-black/30 opacity-0 transition-opacity group-hover:opacity-100">
2225
<button className="mx-2 rounded-full bg-white p-3 text-gray-800 transition hover:bg-indigo-600 hover:text-white">
2326
<ShoppingBag className="h-5 w-5" />
@@ -35,7 +38,12 @@ const ProductCard = (product: ProductListItem) => {
3538
<div className="flex items-center justify-between">
3639
<div>
3740
<span className="font-bold text-indigo-600">${product.price}</span>
38-
{product.isDiscount && <span className="ml-2 text-gray-400 line-through">$119.99</span>}
41+
{Number(product.discount) > 0 && (
42+
<div className="absolute top-0 right-0 m-2 rounded-md bg-red-500 px-2 py-1 text-sm text-white">
43+
-{product.discount}%
44+
</div>
45+
)}
46+
3947
</div>
4048
<div className="flex text-yellow-400">
4149
<i className="fas fa-star"></i>

routes/web.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
Route::get('/index', function () {
1010
return Inertia::render('welcome');
1111
})->name('home');
12-
Route::get('/product/{slug}',[HomeController::class,'productDetail'])->name('product.detail');
12+
Route::get('/product/{slug}',[HomeController::class,'productDetail'])
13+
->name('product.detail');
1314

1415

1516
Route::middleware(['auth', 'verified'])->group(function () {

0 commit comments

Comments
 (0)