Skip to content

Commit 5a46ac0

Browse files
committed
feat: wip products page, cart module and interactive with alpine js
1 parent 2b7abbd commit 5a46ac0

28 files changed

+1358
-281
lines changed

app/Http/Controllers/Admin/PageController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,5 @@ public function destroy(Page $page)
6666
return redirect()->route('admin.pages.index')
6767
->with('success', 'Page deleted successfully.');
6868
}
69+
6970
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Frontend;
4+
5+
use Illuminate\Http\Request;
6+
use App\Services\CartService;
7+
use App\Http\Controllers\Controller;
8+
9+
class CartController extends Controller
10+
{
11+
public function add(Request $request)
12+
{
13+
$productId = $request->input('product_id');
14+
$quantity = $request->input('quantity', 1);
15+
16+
app(CartService::class)->add($productId, $quantity);
17+
18+
$carts = session()->get('cart') ?? [];
19+
return response()->json(['success' => true,'message' => 'Successfully added to cart!','data' => $carts]);
20+
}
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Frontend;
4+
5+
use App\Models\Product;
6+
use Illuminate\Http\Request;
7+
use App\Services\CartService;
8+
use App\Http\Controllers\Controller;
9+
10+
class ProductController extends Controller
11+
{
12+
public function index()
13+
{
14+
$products = Product::with('category')->paginate(10);
15+
return view('frontend.products.index', compact('products'));
16+
}
17+
}

app/Services/CartService.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
4+
namespace App\Services;
5+
6+
use App\Models\Product;
7+
8+
class CartService
9+
{
10+
public function add($productId, $quantity = 1)
11+
{
12+
$product = Product::findOrFail($productId);
13+
14+
$cart = session()->get('cart', []);
15+
16+
if (isset($cart['items'][$productId])) {
17+
$cart['items'][$productId]['quantity'] += $quantity;
18+
} else {
19+
$cart['items'][$productId] = [
20+
'product_id' => $product->id,
21+
'title' => $product->title,
22+
'price' => $product->price,
23+
'image' => $product->image ? asset($product->image) : 'https://placehold.co/400',
24+
'sku' => $product->sku,
25+
'quantity' => $quantity,
26+
];
27+
}
28+
29+
$total = 0;
30+
foreach ($cart['items'] as $key => $item) {
31+
// Skip if key is 'total' itself to avoid issues
32+
if ($key === 'total') {
33+
continue;
34+
}
35+
$total += $item['price'] * $item['quantity'];
36+
}
37+
$cart['attributes']['total'] = number_format($total, 2);
38+
39+
session()->put('cart', $cart);
40+
}
41+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"autoprefixer": "^10.4.21",
1111
"axios": "^1.10.0",
1212
"concurrently": "^9.0.1",
13+
"daisyui": "^5.0.50",
1314
"laravel-vite-plugin": "^1.2.0",
1415
"postcss": "^8.5.6",
1516
"vite": "^6.0.11"

pnpm-lock.yaml

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/frontend/app.css

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,22 @@
1-
@import 'tailwindcss';
1+
@import "tailwindcss";
2+
3+
/* Frontend Blade views */
4+
@source "../views/frontend/**/*.blade.php";
5+
6+
/* Frontend JS files */
7+
@source "../js/frontend/**/*.js";
8+
9+
/* Laravel default pagination views (still used on frontend) */
10+
/* @source "../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php"; */
11+
12+
/* Cached frontend views */
13+
@source "../../storage/framework/views/*.php";
14+
15+
@plugin "daisyui" {
16+
themes: light --default;
17+
root: ":root";
18+
include: ;
19+
exclude: ;
20+
prefix: ;
21+
logs: true;
22+
}

resources/views/admin/components/media/items.blade.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@
8282
}
8383
</style>
8484

85-
86-
8785
@if ($folderId)
8886
@php $parentFolder = \App\Models\MediaFolder::find($folderId); @endphp
8987
<div class="mb-3 d-flex align-items-center gap-2">

resources/views/admin/media/index.blade.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ function success(response, refresh) {
185185
let url = window.location.href;
186186
const target = document.querySelector(".media-container");
187187
const container = document.getElementById("ajax-container");
188-
console.log(url, container, target)
189188
loadData(url, container, target)
190189
document.querySelector('.ajax-form').reset()
191190
}

resources/views/frontend/app.blade.php

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,32 @@
1111
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap" rel="stylesheet">
1212

1313
<style>
14-
body{
15-
font-family: "Outfit", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
16-
}
14+
body {
15+
font-family: "Outfit", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
16+
}
17+
18+
[x-cloak] {
19+
display: none !important;
20+
}
1721
</style>
1822

1923
@stack('styles')
2024
</head>
2125

22-
<body>
26+
<body class="light">
2327
@include('frontend.partials.header') {{-- optional --}}
2428
<main>
2529
@yield('content')
2630
</main>
31+
32+
33+
<div x-init="console.log($store.toast)" x-cloak x-show="$store.toast.visible" x-transition @click="$store.toast.visible = false"
34+
class="toast fixed top-4 right-4 z-50 cursor-pointer">
35+
<div :class="`alert ${$store.toast.type === 'success' ? 'alert-success' : 'alert-error'} text-white`">
36+
<span x-text="$store.toast.message"></span>
37+
</div>
38+
</div>
39+
2740
@include('frontend.partials.footer') {{-- optional --}}
2841

2942
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
@@ -32,6 +45,61 @@
3245
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
3346
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
3447
@stack('scripts')
48+
49+
50+
<script>
51+
document.addEventListener('alpine:init', () => {
52+
Alpine.store('cart', {
53+
items: @json(session()->get('cart')['items'] ?? []),
54+
attributes: @json(session()->get('cart')['attributes'] ?? []),
55+
reset(cart) {
56+
this.items = cart.items ?? [];
57+
this.attributes = cart.attributes ?? [];
58+
}
59+
});
60+
61+
62+
Alpine.store('toast', {
63+
visible: false,
64+
message: '',
65+
type: 'success', // 'success' or 'error'
66+
67+
show(success, message) {
68+
this.type = success ? 'success' : 'error';
69+
this.message = message;
70+
this.visible = true;
71+
72+
setTimeout(() => {
73+
this.visible = false;
74+
}, 3000);
75+
}
76+
});
77+
78+
Alpine.data('cart', () => ({
79+
addToCart(productId) {
80+
axios.post('/cart/add', {
81+
product_id: productId,
82+
quantity: 1
83+
})
84+
.then(response => {
85+
Alpine.store('cart').reset(response.data.data);
86+
// Alpine.store('cart').addItem({
87+
// id: productId,
88+
// title: response.data.title || 'Product ' + productId,
89+
// quantity: 1
90+
// });
91+
Alpine.store('toast').show(true, response.data.message ||
92+
'Added to cart!');
93+
})
94+
.catch(error => {
95+
Alpine.store('toast').show(false, error.response?.data?.message ||
96+
'Add to cart failed.');
97+
});
98+
}
99+
}));
100+
101+
});
102+
</script>
35103
</body>
36104
37105
</html>

0 commit comments

Comments
 (0)