This commit is contained in:
Bayu Lukman Yusuf 2026-01-21 15:20:35 +07:00
parent a86ba91db7
commit 8c21d4dc7c
14 changed files with 1000 additions and 497 deletions

View File

@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Member\Cart\MemberCartRequest;
use App\Http\Requests\Member\Cart\UpdateMemberCartRequest;
use App\Models\Cart;
use App\Repositories\Member\Cart\MemberCartRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class CartController extends Controller
{
public function count(Request $request, MemberCartRepository $repository)
{
$count = $repository->getCount($request->input('location_id'));
return response()->json([
'count' => $count
]);
}
public function index(Request $request, MemberCartRepository $repository)
{
$request->merge(['location_id' => $request->input('location_id', session('location_id', 22))]);
$carts = $repository->getList($request);
// Log::info($items);
return view('checkout.v1-cart',[
'carts' => $carts
]);
}
public function add(MemberCartRequest $request, MemberCartRepository $repository)
{
Log::info($request->all());
$data = $request->validated();
$item = $repository->create($data);
if ($request->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Item added to cart successfully',
'item' => $item
]);
}
return redirect()->route('cart.index');
}
public function update($cart_id, UpdateMemberCartRequest $request, MemberCartRepository $repository)
{
$data = $request->validated();
$item = $repository->update($cart_id, $data);
if ($request->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Cart updated successfully',
'item' => $item
]);
}
return redirect()->route('cart.index');
}
public function delete($cart_id, MemberCartRepository $repository)
{
$repository->delete($cart_id);
if (request()->expectsJson()) {
return response()->json([
'success' => true,
'message' => 'Item removed from cart successfully'
]);
}
return redirect()->route('cart.index');
}
}

View File

@ -24,15 +24,15 @@ class ProductController extends Controller
foreach ($genders as $gender) {
$isActive = $currentGenderId == $gender->id;
$genderHtml .= '<li class="nav-item mb-1">';
$genderHtml .= '<a class="nav-link d-block fw-normal p-0 ' . ($isActive ? 'active text-primary' : '') . '" ';
$genderHtml .= 'href="#" data-gender-id="' . $gender->id . '">';
$genderHtml .= '<a class="nav-link d-block fw-normal p-0 '.($isActive ? 'active text-primary' : '').'" ';
$genderHtml .= 'href="#" data-gender-id="'.$gender->id.'">';
$genderHtml .= $gender->name;
$genderHtml .= '</a></li>';
}
return response()->json([
'success' => true,
'genders' => $genderHtml
'genders' => $genderHtml,
]);
}
@ -48,15 +48,15 @@ class ProductController extends Controller
foreach ($categories as $category) {
$isActive = $currentCategoryId == $category->id;
$categoryHtml .= '<li class="nav-item mb-1">';
$categoryHtml .= '<a class="nav-link d-block fw-normal p-0 ' . ($isActive ? 'active text-primary' : '') . '" ';
$categoryHtml .= 'href="#" data-category-id="' . $category->id . '">';
$categoryHtml .= '<a class="nav-link d-block fw-normal p-0 '.($isActive ? 'active text-primary' : '').'" ';
$categoryHtml .= 'href="#" data-category-id="'.$category->id.'">';
$categoryHtml .= $category->name;
$categoryHtml .= '</a></li>';
}
return response()->json([
'success' => true,
'categories' => $categoryHtml
'categories' => $categoryHtml,
]);
}
@ -123,7 +123,7 @@ class ProductController extends Controller
// filter
$filter = $request->filter ?? [];
if (isset($filter['category']) && $filter['category']){
if (isset($filter['category']) && $filter['category']) {
$category = StoreCategory::find($filter['category']);
if ($category) {
@ -161,7 +161,7 @@ class ProductController extends Controller
'products' => $productHtml,
'count' => count($products),
'has_more' => $hasMore,
'current_page' => $page
'current_page' => $page,
]);
}
@ -170,16 +170,12 @@ class ProductController extends Controller
$productRepository = new ProductRepository;
$products = [];
$filters = [];
$min_max_price = $productRepository->getMinMaxPrice();
return view('shop.catalog-fashion', [
'products' => $products,
'min_max_price' => $min_max_price,
]);
@ -197,15 +193,15 @@ class ProductController extends Controller
foreach ($brands as $brand) {
$isActive = $currentBrandId == $brand->id;
$brandHtml .= '<li class="nav-item mb-1">';
$brandHtml .= '<a class="nav-link d-block fw-normal p-0 ' . ($isActive ? 'active text-primary' : '') . '" ';
$brandHtml .= 'href="#" data-brand-id="' . $brand->id . '">';
$brandHtml .= '<a class="nav-link d-block fw-normal p-0 '.($isActive ? 'active text-primary' : '').'" ';
$brandHtml .= 'href="#" data-brand-id="'.$brand->id.'">';
$brandHtml .= $brand->name;
$brandHtml .= '</a></li>';
}
return response()->json([
'success' => true,
'brands' => $brandHtml
'brands' => $brandHtml,
]);
}
@ -216,31 +212,31 @@ class ProductController extends Controller
[
'icon' => '🎉',
'message' => 'Free Shipping on orders over $250',
'detail' => "Don't miss a discount!"
'detail' => "Don't miss a discount!",
],
[
'icon' => '💰',
'message' => 'Money back guarantee',
'detail' => 'We return money within 30 days'
'detail' => 'We return money within 30 days',
],
[
'icon' => '💪',
'message' => 'Friendly 24/7 customer support',
'detail' => "We've got you covered!"
]
'detail' => "We've got you covered!",
],
];
// Render announcement slides HTML
$announcementHtml = '';
foreach ($announcements as $announcement) {
$announcementHtml .= '<div class="swiper-slide text-truncate text-center">';
$announcementHtml .= $announcement['icon'] . ' ' . $announcement['message'] . ' <span class="d-none d-sm-inline">' . $announcement['detail'] . '</span>';
$announcementHtml .= $announcement['icon'].' '.$announcement['message'].' <span class="d-none d-sm-inline">'.$announcement['detail'].'</span>';
$announcementHtml .= '</div>';
}
return response()->json([
'success' => true,
'announcements' => $announcementHtml
'announcements' => $announcementHtml,
]);
}
@ -313,7 +309,7 @@ class ProductController extends Controller
'success' => true,
'products' => $productHtml,
'count' => count($products),
'type' => $type
'type' => $type,
]);
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Member\Cart;
use Illuminate\Foundation\Http\FormRequest;
class MemberCartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'item_reference_id' => 'required|integer|exists:item_reference,id',
'qty' => 'required|numeric|min:0',
'location_id' => 'required|numeric',
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Member\Cart;
use Illuminate\Foundation\Http\FormRequest;
class UpdateMemberCartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'qty' => 'required|numeric|min:0'
];
}
}

39
app/Models/Cart.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Cart extends Model
{
use HasFactory;
protected $fillable = ['item_id', 'price','is_checked', 'item_variant_id', 'item_reference_id', 'user_id', 'qty','location_id'];
public function item()
{
return $this->belongsTo(Items::class,"item_id");
}
public function itemVariant()
{
return $this->belongsTo(ItemVariant::class, 'item_variant_id', 'id');
}
public function itemReference()
{
return $this->belongsTo(ItemReference::class, 'item_reference_id', 'id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function getDisplayPriceAttribute()
{
return $this->itemVariant->reference->item->display_price;
}
}

29
app/Models/Categories.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Categories extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults();
}
protected $table = 'categories';
protected $primaryKey = 'id';
protected $fillable = [
'code',
'name',
'image',
'is_publish'
];
}

View File

@ -72,7 +72,10 @@ class ItemVariant extends Model
return $this->belongsTo(Items::class, 'item_id');
}
public function getDisplayPriceAttribute()
{
return $this->reference->item->display_price;
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace App\Repositories\Member\Cart;
use App\Models\Cart;
use App\Models\ItemReference;
use Illuminate\Support\Facades\DB;
class MemberCartRepository
{
static public function getCount($locationId = null)
{
$location = $locationId ?? session('location_id', 22);
return Cart::where('user_id', auth()->user()->id)
->where('location_id', $location)
->count();
}
public function getList($request)
{
$location = (int) $request->input("location_id");
$cart = Cart::where('user_id', auth()->user()->id)
->where("location_id", $location)
->orderBy('created_at','desc')
->paginate($request->limit ?? 1000);
return $cart;
}
public function create($data)
{
$model = DB::transaction(function () use ($data) {
$location = (int) @$data["location_id"];
$itemreference = ItemReference::findOrfail($data['item_reference_id']);
$model = Cart::where('item_reference_id', $data['item_reference_id'])
->where('location_id', $location)
->where('user_id', auth()->user()->id)->first();
if ($model) {
$model->qty = $model->qty + $data['qty'];
$model->save();
} else {
$model = Cart::create([
'item_id' => $itemreference->item_id,
'item_variant_id' => $itemreference->item_variant_id,
'location_id' => $data['location_id'],
'item_reference_id' => $data['item_reference_id'],
'user_id' => auth()->user()->id,
'qty' => $data['qty']
]);
}
return $model;
});
return $model;
}
public function update($cartId, $data)
{
$cart = DB::transaction(function () use ($cartId, $data) {
$cart = Cart::find($cartId);
if (!$cart) {
throw new \Exception('Cart item not found');
}
$cart->qty = $data['qty'] ?? $cart->qty;
$cart->save();
return $cart;
});
return $cart;
}
public function delete($cartId)
{
$cart = DB::transaction(function () use ($cartId) {
$cart = Cart::find($cartId);
$cart->delete();
return $cart;
});
return $cart;
}
}

View File

@ -2,151 +2,10 @@
@section('content')
<!-- Authentication offcanvas -->
<div class="offcanvas offcanvas-end pb-sm-2 px-sm-2" id="authForm" tabindex="-1" aria-labelledby="authFormLabel"
style="width: 500px">
<div class="offcanvas-header flex-column align-items-start py-3 pt-lg-4">
<div class="d-flex align-items-center justify-content-between w-100 mb-3 mb-lg-4">
<h4 class="offcanvas-title" id="authFormLabel">Login to continue</h4>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<ul class="nav nav-underline" role="tablist">
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" id="login-tab" data-bs-toggle="tab"
data-bs-target="#login-tab-pane" role="tab" aria-controls="login-tab-pane"
aria-selected="false">
Login
</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link active" id="register-tab" data-bs-toggle="tab"
data-bs-target="#register-tab-pane" role="tab" aria-controls="register-tab-pane"
aria-selected="true">
Register
</button>
</li>
</ul>
</div>
<div class="offcanvas-body tab-content pt-2">
<!-- Login form -->
<div class="tab-pane fade" id="login-tab-pane" role="tabpanel" aria-labelledby="login-tab">
<form class="needs-validation" novalidate>
<div class="position-relative mb-4">
<label for="login-email" class="form-label">Email</label>
<input type="email" class="form-control form-control-lg" id="login-email" required>
<div class="invalid-tooltip bg-transparent py-0">Enter a valid email address!</div>
</div>
<div class="mb-4">
<label for="login-password" class="form-label">Password</label>
<div class="password-toggle">
<input type="password" class="form-control form-control-lg" id="login-password" required>
<div class="invalid-tooltip bg-transparent py-0">Password is incorrect!</div>
<label class="password-toggle-button fs-lg" aria-label="Show/hide password">
<input type="checkbox" class="btn-check">
</label>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mb-4">
<div class="form-check me-2">
<input type="checkbox" class="form-check-input" id="remember-30">
<label for="remember-30" class="form-check-label">Remember for 30 days</label>
</div>
<div class="nav">
<a class="nav-link animate-underline p-0" href="{{ route('second', ['account', 'password-recovery']) }}">
<span class="animate-target">Forgot password?</span>
</a>
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary w-100">
Login to your account
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
</button>
</form>
</div>
<!-- Register form -->
<div class="tab-pane fade show active" id="register-tab-pane" role="tabpanel" aria-labelledby="register-tab">
<form class="needs-validation" novalidate>
<div class="position-relative mb-4">
<label for="register-email" class="form-label">Email</label>
<input type="email" class="form-control form-control-lg" id="register-email" required>
<div class="invalid-tooltip bg-transparent py-0">Enter a valid email address!</div>
</div>
<div class="mb-4">
<label for="register-password" class="form-label">Password</label>
<div class="password-toggle">
<input type="password" class="form-control form-control-lg" id="register-password"
minlength="8" placeholder="Minimum 8 characters" required>
<div class="invalid-tooltip bg-transparent py-0">Password does not meet the required criteria!
</div>
<label class="password-toggle-button fs-lg" aria-label="Show/hide password">
<input type="checkbox" class="btn-check">
</label>
</div>
</div>
<div class="d-flex flex-column gap-2 mb-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="save-pass">
<label for="save-pass" class="form-check-label">Save the password</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="privacy" required>
<label for="privacy" class="form-check-label">I have read and accept the <a
class="text-dark-emphasis" href="#!">Privacy Policy</a></label>
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary w-100">
Create an account
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
</button>
<div class="pt-5">
<h6>AsiaGolf account benefits</h6>
<ul class="list-unstyled d-flex flex-column gap-2 fs-sm mb-0">
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-mail fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Subscribe to your favorite products</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-settings fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">View and manage your orders and withlist</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-gift fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Earn rewards for future purchases</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-percent fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Receive exclusive offers and discounts</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-heart fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Create multiple wishlists</div>
</li>
</ul>
</div>
</form>
</div>
</div>
</div>
@include('layouts.partials/offcanvas')
@include('layouts.partials/navbar', ['wishlist' => true])
<x-layout.header />
<main class="content-wrapper">
@ -168,7 +27,7 @@
<!-- Items list -->
<div class="col-lg-8">
<div class="pe-lg-2 pe-xl-3 me-xl-3">
<p class="fs-sm">Buy <span class="text-dark-emphasis fw-semibold">$183</span> more to get <span
{{-- <p class="fs-sm">Buy <span class="text-dark-emphasis fw-semibold">$183</span> more to get <span
class="text-dark-emphasis fw-semibold">Free Shipping</span></p>
<div class="progress w-100 overflow-visible mb-4" role="progressbar"
aria-label="Free shipping progress" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"
@ -180,7 +39,7 @@
<i class="ci-star-filled text-warning"></i>
</div>
</div>
</div>
</div> --}}
<!-- Table of items -->
<table class="table position-relative z-2 mb-4">
@ -205,193 +64,80 @@
</thead>
<tbody class="align-middle">
<!-- Item -->
<tr>
<td class="py-3 ps-0">
<div class="d-flex align-items-center">
<a class="flex-shrink-0" href="{{ route('second', ['shop', 'product-general-electronics']) }}">
<img src="/img/shop/electronics/thumbs/08.png" width="110"
alt="iPhone 14">
</a>
<div class="w-100 min-w-0 ps-2 ps-xl-3">
<h5 class="d-flex animate-underline mb-2">
<a class="d-block fs-sm fw-medium text-truncate animate-target"
href="{{ route('second', ['shop', 'product-general-electronics']) }}">Apple iPhone 14
128GB</a>
</h5>
<ul class="list-unstyled gap-1 fs-xs mb-0">
<li><span class="text-body-secondary">Color:</span> <span
class="text-dark-emphasis fw-medium">White</span></li>
<li><span class="text-body-secondary">Model:</span> <span
class="text-dark-emphasis fw-medium">128GB</span></li>
<li class="d-xl-none"><span class="text-body-secondary">Price:</span>
<span class="text-dark-emphasis fw-medium">$899.00</span></li>
</ul>
<div class="count-input rounded-2 d-md-none mt-3">
<button type="button" class="btn btn-sm btn-icon" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control form-control-sm"
value="1" readonly>
<button type="button" class="btn btn-sm btn-icon" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
@foreach ($carts as $key => $cart)
<tr>
<td class="py-3 ps-0">
<div class="d-flex align-items-center">
<a class="flex-shrink-0"
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
<img src="{{ $cart->itemReference->item->image_url ?? '' }}" width="110"
alt="{{ $cart->name ?? 'Product' }}">
</a>
<div class="w-100 min-w-0 ps-2 ps-xl-3">
<h5 class="d-flex animate-underline mb-2">
<a class="d-block fs-sm fw-medium text-truncate animate-target"
href="{{ $cart->itemVariant->item->slug != null ? route('product.detail', [$cart->item->slug]) : '#' }}">{{ $cart->itemVariant->display_name ?? $cart->itemVariant->description ?? 'Product Name' }}</a>
</h5>
{{-- <ul class="list-unstyled gap-1 fs-xs mb-0">
<li><span class="text-body-secondary">Color:</span> <span
class="text-dark-emphasis fw-medium">{{ $cart->color ?? 'N/A' }}</span></li>
<li><span class="text-body-secondary">Model:</span> <span
class="text-dark-emphasis fw-medium">{{ $cart->model ?? 'N/A' }}</span></li>
<li class="d-xl-none"><span
class="text-body-secondary">Price:</span>
<span class="text-dark-emphasis fw-medium">${{ number_format($cart->itemReference->display_price ?? 0, 2) }}</span>
</li>
</ul> --}}
<div class="count-input rounded-2 d-md-none mt-3">
<button type="button" class="btn btn-sm btn-icon" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control form-control-sm"
value="{{ $cart->qty ?? 1 }}" readonly>
<button type="button" class="btn btn-sm btn-icon" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
</div>
</div>
</div>
</div>
</td>
<td class="h6 py-3 d-none d-xl-table-cell">$899.00</td>
<td class="py-3 d-none d-md-table-cell">
<div class="count-input">
<button type="button" class="btn btn-icon" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control" value="1" readonly>
<button type="button" class="btn btn-icon" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
</div>
</td>
<td class="h6 py-3 d-none d-md-table-cell">$899.00</td>
<td class="text-end py-3 px-0">
<button type="button" class="btn-close fs-sm" data-bs-toggle="tooltip"
data-bs-custom-class="tooltip-sm" data-bs-title="Remove"
aria-label="Remove from cart"></button>
</td>
</tr>
</td>
<td class="h6 py-3 d-none d-xl-table-cell" >
<input type="hidden" id="price_{{ $cart->id }}" value="{{ $cart->itemVariant->display_price ?? 0 }}">
Rp {{ number_format($cart->itemVariant->display_price ?? 0, 0,",",".") }}</td>
<td class="py-3 d-none d-md-table-cell">
<div class="count-input">
<button type="button" class="btn btn-icon" data-decrement
aria-label="Decrement quantity" onclick="updateCartItem({{ $cart->id }}, 'decrement', this)">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control" value="{{ $cart->qty ?? 1 }}" readonly>
<button type="button" class="btn btn-icon" data-increment
aria-label="Increment quantity" onclick="updateCartItem({{ $cart->id }}, 'increment', this)">
<i class="ci-plus"></i>
</button>
</div>
</td>
<td class="h6 py-3 d-none d-md-table-cell total-row">Rp {{ number_format(($cart->display_price ?? 0) * ($cart->qty ?? 1), 0,",",".") }}</td>
<td class="text-end py-3 px-0">
<form action="{{ route('cart.delete', $cart->id) }}" method="POST" onsubmit="deleteCartItem(event, this, {{ $cart->id }})">
@csrf
@method('DELETE')
<button type="submit" class="btn-close fs-sm" data-bs-toggle="tooltip"
data-bs-custom-class="tooltip-sm" data-bs-title="Remove"
aria-label="Remove from cart"></button>
</form>
</td>
</tr>
@endforeach
<!-- Item -->
<tr>
<td class="py-3 ps-0">
<div class="d-flex align-items-center">
<a class="position-relative flex-shrink-0"
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
<span
class="badge text-bg-danger position-absolute top-0 start-0">-10%</span>
<img src="/img/shop/electronics/thumbs/09.png" width="110"
alt="iPad Pro">
</a>
<div class="w-100 min-w-0 ps-2 ps-xl-3">
<h5 class="d-flex animate-underline mb-2">
<a class="d-block fs-sm fw-medium text-truncate animate-target"
href="{{ route('second', ['shop', 'product-general-electronics']) }}">Tablet Apple iPad Pro
M2</a>
</h5>
<ul class="list-unstyled gap-1 fs-xs mb-0">
<li><span class="text-body-secondary">Color:</span> <span
class="text-dark-emphasis fw-medium">Black</span></li>
<li><span class="text-body-secondary">Model:</span> <span
class="text-dark-emphasis fw-medium">256GB</span></li>
<li class="d-xl-none"><span class="text-body-secondary">Price:</span>
<span class="text-dark-emphasis fw-medium">$989.00 <del
class="text-body-tertiary fw-normal">$1,099.00</del></span>
</li>
</ul>
<div class="count-input rounded-2 d-md-none mt-3">
<button type="button" class="btn btn-sm btn-icon" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control form-control-sm"
value="1" readonly>
<button type="button" class="btn btn-sm btn-icon" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
</div>
</div>
</div>
</td>
<td class="h6 py-3 d-none d-xl-table-cell">$989.00 <del
class="text-body-tertiary fs-xs fw-normal">$1,099.00</del></td>
<td class="py-3 d-none d-md-table-cell">
<div class="count-input">
<button type="button" class="btn btn-icon" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control" value="1" readonly>
<button type="button" class="btn btn-icon" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
</div>
</td>
<td class="h6 py-3 d-none d-md-table-cell">$989.00</td>
<td class="text-end py-3 px-0">
<button type="button" class="btn-close fs-sm" data-bs-toggle="tooltip"
data-bs-custom-class="tooltip-sm" data-bs-title="Remove"
aria-label="Remove from cart"></button>
</td>
</tr>
<!-- Item -->
<tr>
<td class="py-3 ps-0">
<div class="d-flex align-items-center">
<a class="flex-shrink-0" href="{{ route('second', ['shop', 'product-general-electronics']) }}">
<img src="/img/shop/electronics/thumbs/01.png" width="110"
alt="Smart Watch">
</a>
<div class="w-100 min-w-0 ps-2 ps-xl-3">
<h5 class="d-flex animate-underline mb-2">
<a class="d-block fs-sm fw-medium text-truncate animate-target"
href="{{ route('second', ['shop', 'product-general-electronics']) }}">Smart Watch Series
7</a>
</h5>
<ul class="list-unstyled gap-1 fs-xs mb-0">
<li><span class="text-body-secondary">Color:</span> <span
class="text-dark-emphasis fw-medium">White</span></li>
<li><span class="text-body-secondary">Model:</span> <span
class="text-dark-emphasis fw-medium">44 mm</span></li>
<li class="d-xl-none"><span class="text-body-secondary">Price:</span>
<span class="text-dark-emphasis fw-medium">$429.00</span></li>
</ul>
<div class="count-input rounded-2 d-md-none mt-3">
<button type="button" class="btn btn-sm btn-icon" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control form-control-sm"
value="1" readonly>
<button type="button" class="btn btn-sm btn-icon" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
</div>
</div>
</div>
</td>
<td class="h6 py-3 d-none d-xl-table-cell">$429.00</td>
<td class="py-3 d-none d-md-table-cell">
<div class="count-input">
<button type="button" class="btn btn-icon" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control" value="1" readonly>
<button type="button" class="btn btn-icon" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
</div>
</td>
<td class="h6 py-3 d-none d-md-table-cell">$429.00</td>
<td class="text-end py-3 px-0">
<button type="button" class="btn-close fs-sm" data-bs-toggle="tooltip"
data-bs-custom-class="tooltip-sm" data-bs-title="Remove"
aria-label="Remove from cart"></button>
</td>
</tr>
</tbody>
</table>
<div class="nav position-relative z-2 mb-4 mb-lg-0">
<a class="nav-link animate-underline px-0" href="{{ route('second', ['shop', 'catalog-electronics']) }}'">
<a class="nav-link animate-underline px-0"
href="{{ route('second', ['shop', 'catalog-electronics']) }}'">
<i class="ci-chevron-left fs-lg me-1"></i>
<span class="animate-target">Continue shopping</span>
</a>
@ -429,7 +175,8 @@
<span class="fs-sm">Estimated total:</span>
<span class="h5 mb-0">$2,390.40</span>
</div>
<a class="btn btn-lg btn-primary w-100" href="{{ route('second', ['checkout', 'v1-delivery-1']) }}">
<a class="btn btn-lg btn-primary w-100"
href="{{ route('second', ['checkout', 'v1-delivery-1']) }}">
Proceed to checkout
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
</a>
@ -985,4 +732,149 @@
@endsection
@section('scripts')
<script>
// JavaScript number formatting function
function number_format(number, decimals, dec_point, thousands_sep) {
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
var dec = (typeof dec_point === 'undefined') ? '.' : dec_point;
var s = '';
var toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return '' + Math.round(n * k) / k;
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (thousands_sep) {
var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])) {
s[0] = s[0].replace(re, '$1' + sep + '$2');
}
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
async function updateCartItem(cartId, action, button) {
const container = button.closest('.count-input');
const input = container.querySelector('input[type="number"]');
let currentQty = parseInt(input.value);
if (action === 'increment') {
currentQty++;
} else if (action === 'decrement' && currentQty > 1) {
currentQty--;
}
// input.value = currentQty;
try {
const response = await fetch(`cart/${cartId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json'
},
body: JSON.stringify({
qty: currentQty
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
// Update total price display
const row = button.closest('tr');
const totalCell = row.querySelector('.total-row');
const priceInput = document.getElementById(`price_${cartId}`);
const price = parseFloat(priceInput.value);
totalCell.innerHTML = `Rp ${number_format(price * currentQty, 0, ',', '.')}`;
// Update mobile quantity input
const mobileInput = row.querySelector('td:first-child .count-input input');
if (mobileInput) {
mobileInput.value = currentQty;
}
// Update cart count in header
updateCartCount();
}
} else {
console.error('Error updating cart:', await response.json());
}
} catch (error) {
console.error('Network error:', error);
}
}
async function deleteCartItem(event, form, cartId) {
event.preventDefault();
if (!confirm('Are you sure you want to remove this item from cart?')) {
return;
}
try {
const response = await fetch(`cart/${cartId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
// Remove the row from the table
const row = form.closest('tr');
row.remove();
// Update cart count in header
updateCartCount();
// Optionally reload the page to update cart summary
// window.location.reload();
}
} else {
console.error('Error deleting cart item:', await response.json());
}
} catch (error) {
console.error('Network error:', error);
}
}
// Function to update cart count in header
async function updateCartCount() {
try {
const response = await fetch('cart/count', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
const cartCountElements = document.querySelectorAll('.cart-count');
cartCountElements.forEach(element => {
element.textContent = result.count;
});
}
} catch (error) {
console.error('Error updating cart count:', error);
}
}
</script>
@endsection

View File

@ -0,0 +1,151 @@
<div>
<div class="offcanvas offcanvas-end pb-sm-2 px-sm-2" id="authForm" tabindex="-1" aria-labelledby="authFormLabel"
style="width: 500px">
<div class="offcanvas-header flex-column align-items-start py-3 pt-lg-4">
<div class="d-flex align-items-center justify-content-between w-100 mb-3 mb-lg-4">
<h4 class="offcanvas-title" id="authFormLabel">Login to continue</h4>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<ul class="nav nav-underline" role="tablist">
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" id="login-tab" data-bs-toggle="tab"
data-bs-target="#login-tab-pane" role="tab" aria-controls="login-tab-pane"
aria-selected="false">
Login
</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link active" id="register-tab" data-bs-toggle="tab"
data-bs-target="#register-tab-pane" role="tab" aria-controls="register-tab-pane"
aria-selected="true">
Register
</button>
</li>
</ul>
</div>
<div class="offcanvas-body tab-content pt-2">
<!-- Login form -->
<div class="tab-pane fade" id="login-tab-pane" role="tabpanel" aria-labelledby="login-tab">
<form class="needs-validation" novalidate>
<div class="position-relative mb-4">
<label for="login-email" class="form-label">Email</label>
<input type="email" class="form-control form-control-lg" id="login-email" required>
<div class="invalid-tooltip bg-transparent py-0">Enter a valid email address!</div>
</div>
<div class="mb-4">
<label for="login-password" class="form-label">Password</label>
<div class="password-toggle">
<input type="password" class="form-control form-control-lg" id="login-password" required>
<div class="invalid-tooltip bg-transparent py-0">Password is incorrect!</div>
<label class="password-toggle-button fs-lg" aria-label="Show/hide password">
<input type="checkbox" class="btn-check">
</label>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mb-4">
<div class="form-check me-2">
<input type="checkbox" class="form-check-input" id="remember-30">
<label for="remember-30" class="form-check-label">Remember for 30 days</label>
</div>
<div class="nav">
<a class="nav-link animate-underline p-0"
href="{{ route('second', ['account', 'password-recovery']) }}">
<span class="animate-target">Forgot password?</span>
</a>
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary w-100">
Login to your account
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
</button>
</form>
</div>
<!-- Register form -->
<div class="tab-pane fade show active" id="register-tab-pane" role="tabpanel"
aria-labelledby="register-tab">
<form class="needs-validation" novalidate>
<div class="position-relative mb-4">
<label for="register-email" class="form-label">Email</label>
<input type="email" class="form-control form-control-lg" id="register-email" required>
<div class="invalid-tooltip bg-transparent py-0">Enter a valid email address!</div>
</div>
<div class="mb-4">
<label for="register-password" class="form-label">Password</label>
<div class="password-toggle">
<input type="password" class="form-control form-control-lg" id="register-password"
minlength="8" placeholder="Minimum 8 characters" required>
<div class="invalid-tooltip bg-transparent py-0">Password does not meet the required
criteria!
</div>
<label class="password-toggle-button fs-lg" aria-label="Show/hide password">
<input type="checkbox" class="btn-check">
</label>
</div>
</div>
<div class="d-flex flex-column gap-2 mb-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="save-pass">
<label for="save-pass" class="form-check-label">Save the password</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="privacy" required>
<label for="privacy" class="form-check-label">I have read and accept the <a
class="text-dark-emphasis" href="#!">Privacy Policy</a></label>
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary w-100">
Create an account
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
</button>
<div class="pt-5">
<h6>AsiaGolf account benefits</h6>
<ul class="list-unstyled d-flex flex-column gap-2 fs-sm mb-0">
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-mail fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Subscribe to your favorite products</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-settings fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">View and manage your orders and withlist</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-gift fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Earn rewards for future purchases</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-percent fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Receive exclusive offers and discounts</div>
</li>
<li class="d-flex align-items-center pb-1">
<div
class="d-flex align-items-center justify-content-center bg-body-tertiary rounded-circle p-2">
<i class="ci-heart fs-base text-dark-emphasis m-1"></i>
</div>
<div class="ps-2 ms-1">Create multiple wishlists</div>
</li>
</ul>
</div>
</form>
</div>
</div>
</div>
@include('layouts.partials/offcanvas')
@include('layouts.partials/navbar', ['wishlist' => true])
</div>

View File

@ -124,7 +124,7 @@
<span class="h6 mb-0">$197.00</span>
</div>
<div class="d-flex w-100 gap-3">
<a class="btn btn-lg btn-secondary w-100" href="#!">View cart</a>
<a class="btn btn-lg btn-secondary w-100" href="{{ route('cart.index') }}">View cart</a>
<a class="btn btn-lg btn-dark w-100" href="#!">Checkout</a>
</div>
</div>
@ -253,8 +253,8 @@
data-bs-toggle="offcanvas" data-bs-target="#shoppingCart" aria-controls="shoppingCart"
aria-label="Shopping cart">
<span
class="position-absolute top-0 start-100 badge fs-xs text-bg-primary rounded-pill mt-1 ms-n4 z-2"
style="--cz-badge-padding-y: .25em; --cz-badge-padding-x: .42em">3</span>
class="position-absolute top-0 start-100 badge fs-xs text-bg-primary rounded-pill mt-1 ms-n4 z-2 cart-count"
style="--cz-badge-padding-y: .25em; --cz-badge-padding-x: .42em">{{ auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0 }}</span>
<i class="ci-shopping-bag animate-target me-1"></i>
</button>
</div>

View File

@ -0,0 +1,139 @@
@props([
'quantity' => 1,
'item_reference_id' => null,
'locationId' => session('location_id', 22),
'buttonText' => 'Add to cart',
'buttonClass' => 'btn-dark',
])
<div class="d-flex gap-3 pb-3 pb-lg-4 mb-3">
<div class="count-input flex-shrink-0">
<button type="button" class="btn btn-icon btn-lg" data-decrement aria-label="Decrement quantity"
onclick="decrementQuantity(this)">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control form-control-lg" min="1" value="{{ $quantity }}" readonly>
<button type="button" class="btn btn-icon btn-lg" data-increment aria-label="Increment quantity"
onclick="incrementQuantity(this)">
<i class="ci-plus"></i>
</button>
</div>
<button type="button" class="btn btn-lg {{ $buttonClass }} w-100" onclick="addToCartItem(this)"
data-item-reference-id="{{ $item_reference_id }}" data-location-id="{{ $locationId }}"
data-quantity="{{ $quantity }}">
<span class="button-text">{{ $buttonText }}</span>
<span class="loading-text" style="display: none;">
<i class="ci-loading animate-spin"></i> Adding...
</span>
</button>
</div>
<script>
function incrementQuantity(button) {
const input = button.parentElement.querySelector('input[type="number"]');
input.value = parseInt(input.value);
updateAddToCartButton(button);
}
function decrementQuantity(button) {
const input = button.parentElement.querySelector('input[type="number"]');
const currentValue = parseInt(input.value);
if (currentValue > 1) {
input.value = currentValue;
}
updateAddToCartButton(button);
}
function updateAddToCartButton(button) {
const container = button.closest('.d-flex');
const addToCartBtn = container.querySelector('button[onclick="addToCartItem(this)"]');
const input = container.querySelector('input[type="number"]');
if (addToCartBtn && input) {
addToCartBtn.dataset.quantity = input.value;
}
}
async function addToCartItem(button) {
const itemReferenceId = button.dataset.itemReferenceId;
const locationId = button.dataset.locationId;
const quantity = button.dataset.quantity || button.parentElement.querySelector('input[type="number"]')
.value;
if (!itemReferenceId) {
console.error('Item Reference ID is required');
return;
}
const buttonText = button.querySelector('.button-text');
const loadingText = button.querySelector('.loading-text');
// Show loading state
button.disabled = true;
if (buttonText) buttonText.style.display = 'none';
if (loadingText) loadingText.style.display = 'inline';
try {
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
const response = await fetch('{{ route('cart.add') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': token,
'Accept': 'application/json'
},
body: JSON.stringify({
item_reference_id: itemReferenceId,
location_id: locationId,
qty: quantity
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
// Update cart count in header
updateCartCount();
// Redirect to cart page on success
window.location.href = '{{ route('cart.index') }}';
}
} else {
const error = await response.json();
console.error('Error adding to cart:', error);
// You could show an error message here
}
} catch (error) {
console.error('Network error:', error);
// You could show an error message here
} finally {
// Restore button state
button.disabled = false;
if (buttonText) buttonText.style.display = 'inline';
if (loadingText) loadingText.style.display = 'none';
}
}
// Function to update cart count in header
async function updateCartCount() {
try {
const response = await fetch('{{ route('cart.count') }}', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
const cartCountElements = document.querySelectorAll('.cart-count');
cartCountElements.forEach(element => {
element.textContent = result.count;
});
}
} catch (error) {
console.error('Error updating cart count:', error);
}
}
</script>

View File

@ -429,10 +429,10 @@
<div class="position-relative">
@if ($product->display_discount_price > 0)
<span
class="badge text-bg-danger position-absolute top-0 start-0 z-2 mt-3 mt-sm-4 ms-3 ms-sm-4">Sale</span>
@endif
<button type="button"
<span
class="badge text-bg-danger position-absolute top-0 start-0 z-2 mt-3 mt-sm-4 ms-3 ms-sm-4">Sale</span>
@endif
<button type="button"
class="btn btn-icon btn-secondary animate-pulse fs-lg bg-transparent border-0 position-absolute top-0 end-0 z-2 mt-2 mt-sm-3 me-2 me-sm-3"
data-bs-toggle="tooltip" data-bs-placement="top" data-bs-custom-class="tooltip-sm"
data-bs-title="Add to Wishlist" aria-label="Add to Wishlist">
@ -453,24 +453,22 @@
<div class="row row-cols-2 g-3 g-sm-4 g-md-3 g-lg-4 pb-3 pb-sm-4 pb-md-0">
@foreach ($product->image_urls as $key => $url)
@if ($key == 0)
@continue
@endif
<div class="col">
<a class="hover-effect-scale hover-effect-opacity position-relative d-flex rounded overflow-hidden"
href="{{ $url }}" data-glightbox
data-gallery="product-gallery">
<i
class="ci-zoom-in hover-effect-target fs-3 text-white position-absolute top-50 start-50 translate-middle opacity-0 z-2"></i>
<div class="ratio hover-effect-target bg-body-tertiary rounded"
style="--cz-aspect-ratio: calc(342 / 306 * 100%)">
<img src="{{ $url }}" alt="Image">
</div>
</a>
</div>
@endforeach
@foreach ($product->image_urls as $key => $url)
@if ($key == 0)
@continue
@endif
<div class="col">
<a class="hover-effect-scale hover-effect-opacity position-relative d-flex rounded overflow-hidden"
href="{{ $url }}" data-glightbox data-gallery="product-gallery">
<i
class="ci-zoom-in hover-effect-target fs-3 text-white position-absolute top-50 start-50 translate-middle opacity-0 z-2"></i>
<div class="ratio hover-effect-target bg-body-tertiary rounded"
style="--cz-aspect-ratio: calc(342 / 306 * 100%)">
<img src="{{ $url }}" alt="Image">
</div>
</a>
</div>
@endforeach
</div>
@ -507,25 +505,26 @@
<p class="fs-sm mb-0">{!! nl2br(Str::limit($product->description, 500)) !!}</p>
<div class="collapse" id="moreDescription">
<div class="fs-sm pt-3">
@if(strlen($product->description) > 500)
<p>{!! nl2br(substr($product->description, 500)) !!}</p>
@if (strlen($product->description) > 500)
<p>{!! nl2br(substr($product->description, 500)) !!}</p>
@endif
</div>
</div>
<!-- Price -->
@if(strlen($product->description) > 500)
<a class="d-inline-block fs-sm fw-medium text-dark-emphasis collapsed mt-1"
href="#moreDescription" data-bs-toggle="collapse" aria-expanded="false"
aria-controls="moreDescription" data-label-collapsed="Read more"
data-label-expanded="Show less" aria-label="Show / hide description"></a>
@if (strlen($product->description) > 500)
<a class="d-inline-block fs-sm fw-medium text-dark-emphasis collapsed mt-1"
href="#moreDescription" data-bs-toggle="collapse" aria-expanded="false"
aria-controls="moreDescription" data-label-collapsed="Read more"
data-label-expanded="Show less" aria-label="Show / hide description"></a>
@endif
<div class="h4 d-flex align-items-center my-4">
<span class="display_price">Rp {{ number_format($product->display_price, 0, ',', '.') }}</span>
<del class="display_discount_price fs-sm fw-normal text-body-tertiary ms-2" style="display: {{ $product->display_discount_price ? 'inline' : 'none' }};">{{ $product->display_discount_price ? ' Rp ' . number_format($product->display_discount_price, 0, ',', '.') : '' }}</del>
<del class="display_discount_price fs-sm fw-normal text-body-tertiary ms-2"
style="display: {{ $product->display_discount_price ? 'inline' : 'none' }};">{{ $product->display_discount_price ? ' Rp ' . number_format($product->display_discount_price, 0, ',', '.') : '' }}</del>
</div>
<!-- Color options -->
@ -535,12 +534,14 @@
<div class="d-flex flex-wrap gap-2" data-binded-label="#variantOption">
@foreach ($product->variants as $key => $variant)
<input type="radio" class="btn-check" name="colors" id="variant-id-{{ $variant->id }}" {{ $key == 0 ? 'checked' : '' }}>
<label for="variant-id-{{ $variant->id }}" class="btn btn-image p-0" data-label="{{ $variant->description }}"
data-price="Rp {{ number_format($product->display_price, 0, ",",".") }}"
<input type="radio" class="btn-check" name="colors"
value="{{ $variant->reference->id }}"
id="variant-id-{{ $variant->id }}" {{ $key == 0 ? 'checked' : '' }}>
<label for="variant-id-{{ $variant->id }}" class="btn btn-image p-0"
data-label="{{ $variant->description }}"
data-price="Rp {{ number_format($product->display_price, 0, ',', '.') }}"
data-discount-price="{{ $product->display_discount_price ? 'Rp ' . number_format($product->display_discount_price, 0, ',', '.') : '' }}"
data-image="{{ $variant->image_url ?? $product->image_url }}"
>
data-image="{{ $variant->image_url ?? $product->image_url }}">
<img src="{{ $variant->image_url ?? $product->image_url }}" width="56"
alt="{{ $variant->name }}">
<span class="visually-hidden">{{ $variant->name }}</span>
@ -580,21 +581,7 @@
</div> --}}
<!-- Count input + Add to cart button -->
<div class="d-flex gap-3 pb-3 pb-lg-4 mb-3">
<div class="count-input flex-shrink-0">
<button type="button" class="btn btn-icon btn-lg" data-decrement
aria-label="Decrement quantity">
<i class="ci-minus"></i>
</button>
<input type="number" class="form-control form-control-lg" min="1"
value="1" readonly>
<button type="button" class="btn btn-icon btn-lg" data-increment
aria-label="Increment quantity">
<i class="ci-plus"></i>
</button>
</div>
<button type="button" class="btn btn-lg btn-dark w-100">Add to cart</button>
</div>
<x-shop.add-to-cart :item_reference_id="$product->variants->first()->id" />
<!-- Info list -->
{{-- <ul class="list-unstyled gap-3 pb-3 pb-lg-4 mb-3">
@ -636,7 +623,7 @@
<div class="navbar container flex-nowrap align-items-center bg-body pt-4 pt-lg-5 mt-lg-n2">
<div class="d-flex align-items-center min-w-0 ms-lg-2 me-3">
<div class="ratio ratio-1x1 flex-shrink-0" style="width: 50px">
<img src="{{ $product->image_url ?? '' }}" class="product-main-image" alt="Image">
<img src="{{ $product->image_url ?? '' }}" class="product-main-image" alt="Image">
</div>
<h4 class="h6 fw-medium d-none d-lg-block ps-3 mb-0 variantOption">{{ $product->name }}</h4>
<div class="w-100 min-w-0 d-lg-none ps-2">
@ -645,8 +632,11 @@
</div>
</div>
</div>
<div class="h4 d-none d-lg-block mb-0 ms-auto me-4 display_price">Rp {{ number_format($product->display_price,0,",",".") }} <del
class="fs-sm fw-normal text-body-tertiary display_discount_price" style="display: {{ $product->display_discount_price ? 'inline' : 'none' }};">Rp {{ number_format($product->display_discount_price,0,",",".") }}</del></div>
<div class="h4 d-none d-lg-block mb-0 ms-auto me-4 display_price">Rp
{{ number_format($product->display_price, 0, ',', '.') }} <del
class="fs-sm fw-normal text-body-tertiary display_discount_price"
style="display: {{ $product->display_discount_price ? 'inline' : 'none' }};">Rp
{{ number_format($product->display_discount_price, 0, ',', '.') }}</del></div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-icon btn-secondary animate-pulse"
aria-label="Add to Wishlist">
@ -687,7 +677,8 @@
<button type="button" class="nav-link" id="delivery-tab" data-bs-toggle="tab"
data-bs-target="#delivery-tab-pane" role="tab" aria-controls="delivery-tab-pane"
aria-selected="false">
{{ __('product_fashion.delivery') }}<span class="d-none d-md-inline">&nbsp;{{ __('product_fashion.and_returns') }}</span>
{{ __('product_fashion.delivery') }}<span
class="d-none d-md-inline">&nbsp;{{ __('product_fashion.and_returns') }}</span>
</button>
</li>
<li class="nav-item" role="presentation">
@ -709,17 +700,22 @@
Pastikan apakah stock tersedia dengan terlebih dahulu KIRIM PESAN - TANYA kepada kami,
<br>
<br>
Segera lakukan pembayaran saat melakukan checkout produk agar system order bisa langusung terima dan proses kemas,
Segera lakukan pembayaran saat melakukan checkout produk agar system order bisa langusung terima
dan proses kemas,
<br>
<br>
- Layanan Pengiriman "Instant" tersedia (Prioritas) kami upayakan proses langsung,
<br>
- Layanan Pengiriman "Sameday" Maksimal order masuk pukul 15.00 Wib karna maksimal Request pick up yang di tentukan oleh tokopedia pukul 16.00 Wib,.
<br>
- Layanan pengiriman "Regullar - Yes - Cargo" Kami pastikan produk terkirim status berubah terkirim pada Sore menjelang petan karna kami hanya tersedia 1x pengiriman ke jasa pengiriman dalam 1 hari
<br>
<br>
Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan ikut pengiriman ke jasa pengiriman ke esokan harinya,.
- Layanan Pengiriman "Instant" tersedia (Prioritas) kami upayakan proses langsung,
<br>
- Layanan Pengiriman "Sameday" Maksimal order masuk pukul 15.00 Wib karna maksimal Request pick
up yang di tentukan oleh tokopedia pukul 16.00 Wib,.
<br>
- Layanan pengiriman "Regullar - Yes - Cargo" Kami pastikan produk terkirim status berubah
terkirim pada Sore menjelang petan karna kami hanya tersedia 1x pengiriman ke jasa pengiriman
dalam 1 hari
<br>
<br>
Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan ikut pengiriman ke
jasa pengiriman ke esokan harinya,.
</div>
{{-- <div class="col-lg-6 col-xl-5 offset-xl-1">
<div class="row row-cols-2 g-4 my-0 my-lg-n2">
@ -760,8 +756,7 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
</div>
<!-- Washing instructions tab -->
<div class="tab-pane fade fs-sm" id="washing-tab-pane" role="tabpanel"
aria-labelledby="washing-tab">
<div class="tab-pane fade fs-sm" id="washing-tab-pane" role="tabpanel" aria-labelledby="washing-tab">
<p>Following below washing instructions can help prolong the life of your denim skirt and keep it
looking its best for longer.</p>
<div class="row row-cols-1 row-cols-md-2">
@ -802,46 +797,61 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
</div>
<!-- Delivery and returns tab -->
<div class="tab-pane fade fs-sm" id="delivery-tab-pane" role="tabpanel"
aria-labelledby="delivery-tab">
<div class="tab-pane fade fs-sm" id="delivery-tab-pane" role="tabpanel" aria-labelledby="delivery-tab">
<div class="row row-cols-1 row-cols-md-2">
<div class="col mb-3 mb-md-0">
<div class="pe-lg-2 pe-xl-3">
<h6>Pengiriman</h6>
<p>Kami bekerja sama dengan berbagai penyedia jasa pengiriman terpercaya untuk memastikan proses pengiriman pesanan berjalan dengan aman dan efisien.
<p>Kami bekerja sama dengan berbagai penyedia jasa pengiriman terpercaya untuk memastikan
proses pengiriman pesanan berjalan dengan aman dan efisien.
<br>
<br>
Pelanggan diberikan kebebasan untuk memilih layanan pengiriman yang paling sesuai dengan kebutuhan, baik dari segi kecepatan maupun biaya.
Pelanggan diberikan kebebasan untuk memilih layanan pengiriman yang paling sesuai dengan
kebutuhan, baik dari segi kecepatan maupun biaya.
<br>
<br>
Estimasi waktu pengiriman dapat berbeda-beda tergantung pada jenis layanan yang dipilih, lokasi tujuan, serta kebijakan dan kondisi operasional dari masing-masing penyedia jasa pengiriman.
Estimasi waktu pengiriman dapat berbeda-beda tergantung pada jenis layanan yang dipilih,
lokasi tujuan, serta kebijakan dan kondisi operasional dari masing-masing penyedia jasa
pengiriman.
<br>
<br>
Perlu diperhatikan bahwa keterlambatan yang disebabkan oleh faktor di luar kendali kami merupakan tanggung jawab penyedia jasa terkait.
Perlu diperhatikan bahwa keterlambatan yang disebabkan oleh faktor di luar kendali kami
merupakan tanggung jawab penyedia jasa terkait.
<br>
<br>
Meskipun demikian, kami akan senantiasa membantu memantau status pengiriman guna memastikan pesanan sampai ke tangan pelanggan dengan baik.</p>
Meskipun demikian, kami akan senantiasa membantu memantau status pengiriman guna
memastikan pesanan sampai ke tangan pelanggan dengan baik.
</p>
</div>
</div>
<div class="col">
<div class="ps-lg-2 ps-xl-3">
<h6>Return Barang</h6>
<p>Kami memberikan kesempatan kepada pelanggan untuk mengajukan permohonan retur atas produk yang diterima apabila terdapat ketidaksesuaian atau kendala tertentu. Proses pengajuan retur dapat dilakukan dengan menghubungi customer service resmi kami melalui WhatsApp.
<p>Kami memberikan kesempatan kepada pelanggan untuk mengajukan permohonan retur atas produk
yang diterima apabila terdapat ketidaksesuaian atau kendala tertentu. Proses pengajuan
retur dapat dilakukan dengan menghubungi customer service resmi kami melalui WhatsApp.
<br>
<br>
Untuk keperluan verifikasi, pelanggan diwajibkan mengirimkan informasi detail transaksi pembelian, foto produk yang diterima, serta bukti video unboxing yang jelas dan tidak terputus.
Untuk keperluan verifikasi, pelanggan diwajibkan mengirimkan informasi detail transaksi
pembelian, foto produk yang diterima, serta bukti video unboxing yang jelas dan tidak
terputus.
<br>
<br>
Permohonan retur akan ditinjau sesuai dengan ketentuan dan kebijakan yang berlaku.
<br>
<br>
Setelah proses verifikasi selesai dan disetujui, kami akan memberikan informasi lebih lanjut terkait tahapan pengembalian barang maupun solusi yang dapat diberikan kepada pelanggan.
Setelah proses verifikasi selesai dan disetujui, kami akan memberikan informasi lebih
lanjut terkait tahapan pengembalian barang maupun solusi yang dapat diberikan kepada
pelanggan.
<br>
<br>
Kami berkomitmen untuk menyelesaikan setiap permohonan retur dengan cepat dan profesional sesuai dengan standar pelayanan kami.
Kami berkomitmen untuk menyelesaikan setiap permohonan retur dengan cepat dan
profesional sesuai dengan standar pelayanan kami.
<br>
<br>
Untuk informasi lebih lanjut, pelanggan dapat menghubungi customer service kami melalui WhatsApp.</p>
Untuk informasi lebih lanjut, pelanggan dapat menghubungi customer service kami melalui
WhatsApp.
</p>
</div>
</div>
</div>
@ -1103,8 +1113,8 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
<!-- Slider prev/next buttons -->
<div class="d-flex gap-2">
<button type="button"
class="btn btn-icon btn-outline-secondary animate-slide-start rounded-circle me-1"
id="lookPrev" aria-label="Prev">
class="btn btn-icon btn-outline-secondary animate-slide-start rounded-circle me-1" id="lookPrev"
aria-label="Prev">
<i class="ci-chevron-left fs-lg animate-target"></i>
</button>
<button type="button" class="btn btn-icon btn-outline-secondary animate-slide-end rounded-circle"
@ -1143,7 +1153,6 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
</div>
</div>
@endforeach
@ -1153,8 +1162,7 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
<!-- Product image -->
<div class="col-md-6 order-md-1">
<img src="{{ $product->image_url ?? '' }}" class="d-block bg-body-tertiary rounded"
alt="Image">
<img src="{{ $product->image_url ?? '' }}" class="d-block bg-body-tertiary rounded" alt="Image">
</div>
</div>
</section>
@ -1242,8 +1250,8 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
<h6 class="accordion-header" id="categoriesHeading">
<span class="text-dark-emphasis d-none d-sm-block">Categories</span>
<button type="button" class="accordion-button collapsed py-3 d-sm-none"
data-bs-toggle="collapse" data-bs-target="#categoriesLinks"
aria-expanded="false" aria-controls="categoriesLinks">Categories</button>
data-bs-toggle="collapse" data-bs-target="#categoriesLinks" aria-expanded="false"
aria-controls="categoriesLinks">Categories</button>
</h6>
<div class="accordion-collapse collapse d-sm-block" id="categoriesLinks"
aria-labelledby="categoriesHeading" data-bs-parent="#footerLinks">
@ -1314,8 +1322,8 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
<h6 class="accordion-header" id="customerHeading">
<span class="text-dark-emphasis d-none d-sm-block">Customer service</span>
<button type="button" class="accordion-button collapsed py-3 d-sm-none"
data-bs-toggle="collapse" data-bs-target="#customerLinks"
aria-expanded="false" aria-controls="customerLinks">Customer service</button>
data-bs-toggle="collapse" data-bs-target="#customerLinks" aria-expanded="false"
aria-controls="customerLinks">Customer service</button>
</h6>
<div class="accordion-collapse collapse d-sm-block" id="customerLinks"
aria-labelledby="customerHeading" data-bs-parent="#footerLinks">
@ -1420,15 +1428,13 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
</div>
<div>
<img src="/img/payment-methods/paypal-light-mode.svg" class="d-none-dark" alt="PayPal">
<img src="/img/payment-methods/paypal-dark-mode.svg" class="d-none d-block-dark"
alt="PayPal">
<img src="/img/payment-methods/paypal-dark-mode.svg" class="d-none d-block-dark" alt="PayPal">
</div>
<div>
<img src="/img/payment-methods/mastercard.svg" alt="Mastercard">
</div>
<div>
<img src="/img/payment-methods/google-pay-light-mode.svg" class="d-none-dark"
alt="Google Pay">
<img src="/img/payment-methods/google-pay-light-mode.svg" class="d-none-dark" alt="Google Pay">
<img src="/img/payment-methods/google-pay-dark-mode.svg" class="d-none d-block-dark"
alt="Google Pay">
</div>
@ -1461,39 +1467,45 @@ Pesanan masuk pukul 18.00 Wib ke atas kemungkinan akan kami proses kirim dan iku
</div>
</div>
</footer>
@endsection
@section('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const radios = document.querySelectorAll('input[name="colors"]');
radios.forEach(radio => {
radio.addEventListener('change', function() {
const label = document.querySelector(`label[for="${this.id}"]`);
const price = label.getAttribute('data-price');
const discountPrice = label.getAttribute('data-discount-price');
const image = label.getAttribute('data-image');
const labelText = label.getAttribute('data-label');
<script>
document.addEventListener('DOMContentLoaded', function() {
const radios = document.querySelectorAll('input[name="colors"]');
radios.forEach(radio => {
radio.addEventListener('change', function() {
const label = document.querySelector(`label[for="${this.id}"]`);
const price = label.getAttribute('data-price');
const discountPrice = label.getAttribute('data-discount-price');
const image = label.getAttribute('data-image');
const labelText = label.getAttribute('data-label');
document.querySelector('.display_price').textContent = price;
const discountSpan = document.querySelector('.display_discount_price');
if (discountPrice) {
discountSpan.textContent = ' ' + discountPrice;
discountSpan.style.display = 'inline';
} else {
discountSpan.style.display = 'none';
}
document.querySelector('.product-main-image').src = image;
document.querySelector('.variantOption').textContent = labelText;
document.querySelector('.display_price').textContent = price;
const discountSpan = document.querySelector('.display_discount_price');
if (discountPrice) {
discountSpan.textContent = ' ' + discountPrice;
discountSpan.style.display = 'inline';
} else {
discountSpan.style.display = 'none';
// Update AddToCart component with new variant ID
const addToCartBtn = document.querySelector('[data-item-reference-id]');
if (addToCartBtn) {
console.log(this.value);
var item_reference_id = this.value;
addToCartBtn.setAttribute('data-item-reference-id', item_reference_id);
}
});
});
// Trigger change for initially checked radio
const checkedRadio = document.querySelector('input[name="colors"]:checked');
if (checkedRadio) {
checkedRadio.dispatchEvent(new Event('change'));
}
document.querySelector('.product-main-image').src = image;
document.querySelector('.variantOption').textContent = labelText;
});
});
// Trigger change for the initially checked radio
const checkedRadio = document.querySelector('input[name="colors"]:checked');
if (checkedRadio) {
checkedRadio.dispatchEvent(new Event('change'));
}
});
</script>
</script>
@endsection

View File

@ -13,6 +13,7 @@ use App\Http\Controllers\ProductController;
use App\Http\Controllers\SearchController;
use App\Http\Controllers\ComponentController;
use App\Http\Controllers\Auth\ProfileController;
use App\Http\Controllers\CartController;
Route::group(['prefix' => '/dummy'], function () {
Route::get('', [RoutingController::class, 'index'])->name('root');
@ -74,11 +75,22 @@ Route::post('/profile', [ProfileController::class, 'update'])->name('profile.upd
Route::put('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.password.update');
Route::get('/addresses', [AddressController::class, 'index'])->name('addresses');
Route::post('/addresses', [AddressController::class, 'store'])->name('addresses.store');
Route::put('/addresses/{id}', [AddressController::class, 'update'])->name('addresses.update');
Route::delete('/addresses/{id}', [AddressController::class, 'destroy'])->name('addresses.destroy');
Route::get('/addresses/provinces', [AddressController::class, 'provinces'])->name('addresses.provinces');
Route::get('/addresses/cities/{provinceId}', [AddressController::class, 'cities'])->name('addresses.cities');
Route::get('/addresses/districts/{provinceId}', [AddressController::class, 'districts'])->name('addresses.districts');
Route::get('/addresses/villages/{districtId}', [AddressController::class, 'villages'])->name('addresses.villages');
Route::middleware(['auth'])->prefix('/addresses')->group(function () {
Route::get('/', [AddressController::class, 'index'])->name('addresses');
Route::post('/', [AddressController::class, 'store'])->name('addresses.store');
Route::put('/{id}', [AddressController::class, 'update'])->name('addresses.update');
Route::delete('/{id}', [AddressController::class, 'destroy'])->name('addresses.destroy');
Route::get('/provinces', [AddressController::class, 'provinces'])->name('addresses.provinces');
Route::get('/cities/{provinceId}', [AddressController::class, 'cities'])->name('addresses.cities');
Route::get('/districts/{provinceId}', [AddressController::class, 'districts'])->name('addresses.districts');
Route::get('/villages/{districtId}', [AddressController::class, 'villages'])->name('addresses.villages');
});
Route::middleware(['auth'])->prefix('/cart')->group(function () {
Route::get('/', [CartController::class, 'index'])->name('cart.index');
Route::post('/', [CartController::class, 'add'])->name('cart.add');
Route::put('/{id}', [CartController::class, 'update'])->name('cart.update');
Route::delete('/{id}', [CartController::class, 'delete'])->name('cart.delete');
Route::get('/count', [CartController::class, 'count'])->name('cart.count');
});