Compare commits
4 Commits
7aa57f6082
...
9430b4895e
| Author | SHA1 | Date |
|---|---|---|
|
|
9430b4895e | |
|
|
f78819c9b9 | |
|
|
f2c2cf11ed | |
|
|
8ef3a2783a |
|
|
@ -35,7 +35,7 @@ class CartController extends Controller
|
|||
|
||||
public function add(MemberCartRequest $request, MemberCartRepository $repository)
|
||||
{
|
||||
Log::info($request->all());
|
||||
// Log::info($request->all());
|
||||
$data = $request->validated();
|
||||
$item = $repository->create($data);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use App\Repositories\Member\ShippingRepository;
|
|||
use App\Repositories\Member\Transaction\TransactionRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CheckoutController extends Controller
|
||||
|
|
@ -24,14 +23,10 @@ class CheckoutController extends Controller
|
|||
|
||||
$address_list = Address::where('user_id', auth()->user()->id)->orderBy('is_primary', 'desc')->get();
|
||||
|
||||
|
||||
$total = $subtotal;
|
||||
|
||||
|
||||
|
||||
$carts = $memberCartRepository->getList($request);
|
||||
|
||||
|
||||
return view('checkout.v1-delivery-1', [
|
||||
'carts' => $carts,
|
||||
'subtotal' => $subtotal,
|
||||
|
|
@ -46,7 +41,6 @@ class CheckoutController extends Controller
|
|||
$delivery_method = $request->input('delivery_method') ?? 'shipping';
|
||||
$address_id = $request->input('address_id');
|
||||
|
||||
|
||||
if ($address_id == null) {
|
||||
$address_list = Address::where('user_id', $request->user()->id)->orderBy('is_primary', 'desc')->get();
|
||||
$address_id = $address_list->first()->id;
|
||||
|
|
@ -60,19 +54,20 @@ class CheckoutController extends Controller
|
|||
if ($delivery_method == 'shipping') {
|
||||
session(['checkout_delivery_method' => $delivery_method]);
|
||||
session(['checkout_address_id' => $address_id]);
|
||||
|
||||
return redirect()->route('checkout.shipping');
|
||||
}
|
||||
|
||||
if ($delivery_method == 'pickup') {
|
||||
session(['checkout_delivery_method' => $delivery_method]);
|
||||
session(['checkout_address_id' => null]);
|
||||
|
||||
return redirect()->route('checkout.payment');
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Delivery method is not valid');
|
||||
}
|
||||
|
||||
|
||||
public function chooseShipping(Request $request, MemberCartRepository $memberCartRepository, ShippingRepository $shippingRepository)
|
||||
{
|
||||
try {
|
||||
|
|
@ -81,7 +76,6 @@ class CheckoutController extends Controller
|
|||
|
||||
$location_id = session('location_id', 22);
|
||||
|
||||
|
||||
if ($delivery_method == null || $address_id == null || $location_id == null) {
|
||||
|
||||
return redirect()->route('checkout.delivery');
|
||||
|
|
@ -90,21 +84,19 @@ class CheckoutController extends Controller
|
|||
$subtotal = $memberCartRepository->getSubtotal($location_id);
|
||||
$total = $subtotal;
|
||||
|
||||
|
||||
$request->merge(['location_id' => $location_id]);
|
||||
$carts = $memberCartRepository->getList($request);
|
||||
|
||||
|
||||
try {
|
||||
$shipping_list = collect($shippingRepository->getList([
|
||||
"location_id" => $location_id,
|
||||
"address_id" => $address_id,
|
||||
"items" => $carts,
|
||||
'location_id' => $location_id,
|
||||
'address_id' => $address_id,
|
||||
'items' => $carts,
|
||||
])['pricing'] ?? [])->map(function ($row) {
|
||||
return [
|
||||
'courier' => $row['courier_code'],
|
||||
'service' => $row['courier_service_code'],
|
||||
'title' => $row['courier_name']." - ".$row['courier_service_name'],
|
||||
'title' => $row['courier_name'].' - '.$row['courier_service_name'],
|
||||
'description' => $row['duration'],
|
||||
'cost' => $row['shipping_fee'],
|
||||
];
|
||||
|
|
@ -112,12 +104,20 @@ class CheckoutController extends Controller
|
|||
|
||||
if (count($shipping_list) == 0) {
|
||||
throw ValidationException::withMessages([
|
||||
"message" => "Tidak dapat menghitung ongkir"
|
||||
'message' => 'Tidak dapat menghitung ongkir',
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
// if contain Failed due to invalid or missing postal code
|
||||
if (str_contains($message, 'Failed due to invalid or missing postal code')) {
|
||||
$message = 'Kode pos tidak valid atau tidak ditemukan';
|
||||
} else if (str_contains($message, 'support@biteship.com')) {
|
||||
$message = 'Layanan pengiriman tidak tersedia untuk alamat ini';
|
||||
}
|
||||
throw ValidationException::withMessages([
|
||||
"message" => $e->getMessage()
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +132,7 @@ class CheckoutController extends Controller
|
|||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::info($e);
|
||||
|
||||
return redirect()->route('checkout.delivery')->with('error', $e->getMessage() ?? 'Invalid checkout data');
|
||||
}
|
||||
}
|
||||
|
|
@ -164,6 +165,8 @@ class CheckoutController extends Controller
|
|||
|
||||
$location_id = session('location_id', 22);
|
||||
|
||||
$use_point = session('use_point') ?? 0;
|
||||
|
||||
$items = [];
|
||||
|
||||
$request->merge(['location_id' => $location_id]);
|
||||
|
|
@ -175,22 +178,24 @@ class CheckoutController extends Controller
|
|||
|
||||
foreach ($carts as $cart) {
|
||||
$items[] = [
|
||||
"item_reference_id" => $cart->item_reference_id,
|
||||
"qty" => $cart->qty
|
||||
'item_reference_id' => $cart->item_reference_id,
|
||||
'qty' => $cart->qty,
|
||||
];
|
||||
}
|
||||
|
||||
$data = [
|
||||
"address_id" => $address_id,
|
||||
"note" => "",
|
||||
"courier_company" => $courier,
|
||||
"courier_type" => $service,
|
||||
"location_id" => $location_id,
|
||||
"items" => $items,
|
||||
"vouchers" => [],
|
||||
"use_customer_points" => 0,
|
||||
'address_id' => $address_id,
|
||||
'note' => '',
|
||||
'courier_company' => $courier,
|
||||
'courier_type' => $service,
|
||||
'location_id' => $location_id,
|
||||
'items' => $items,
|
||||
'vouchers' => [],
|
||||
'use_customer_points' => $use_point ?? 0,
|
||||
];
|
||||
|
||||
dd($data);
|
||||
|
||||
$item = $repository->create($data);
|
||||
|
||||
$notification = new \App\Notifications\Member\Transaction\OrderWaitPayment($item);
|
||||
|
|
@ -201,20 +206,21 @@ class CheckoutController extends Controller
|
|||
|
||||
// proses payment
|
||||
|
||||
|
||||
$payment = $item->payments()->where("method_type",'App\Models\XenditLink')
|
||||
->where("status",'PENDING')
|
||||
$payment = $item->payments()->where('method_type', 'App\Models\XenditLink')
|
||||
->where('status', 'PENDING')
|
||||
->first();
|
||||
$invoice_url = $payment ? @$payment->method->invoice_url: "";
|
||||
$invoice_url = $payment ? @$payment->method->invoice_url : '';
|
||||
|
||||
|
||||
// reset state session
|
||||
session()->forget(['checkout_delivery_method', 'checkout_address_id', 'checkout_courier', 'checkout_service', 'use_point']);
|
||||
|
||||
return redirect()->to($invoice_url)->withHeaders([
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
'Expires' => '0',
|
||||
]);
|
||||
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
Log::info($e);
|
||||
|
|
@ -222,4 +228,92 @@ class CheckoutController extends Controller
|
|||
return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply points to session
|
||||
*/
|
||||
public function applyPoint(Request $request)
|
||||
{
|
||||
try {
|
||||
$usePoint = $request->input('use_point');
|
||||
|
||||
// Validate input
|
||||
if (!$usePoint || !is_numeric($usePoint) || $usePoint < 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Invalid points value'
|
||||
]);
|
||||
}
|
||||
|
||||
$usePoint = (int) $usePoint;
|
||||
|
||||
// Get user's available points
|
||||
$userPoints = auth()->user()->customer->point ?? 0;
|
||||
|
||||
// Check if user has enough points
|
||||
if ($usePoint > $userPoints) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'You cannot use more points than available'
|
||||
]);
|
||||
}
|
||||
|
||||
// Set points in session
|
||||
session(['use_point' => $usePoint]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Points applied successfully',
|
||||
'points_applied' => $usePoint,
|
||||
'remaining_points' => $userPoints - $usePoint
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error applying points: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'An error occurred while applying points'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove points from session
|
||||
*/
|
||||
public function removePoint(Request $request)
|
||||
{
|
||||
try {
|
||||
// Check if points are currently applied
|
||||
$currentPoints = session('use_point', 0);
|
||||
|
||||
if ($currentPoints <= 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No points are currently applied'
|
||||
]);
|
||||
}
|
||||
|
||||
// Remove points from session
|
||||
session()->forget('use_point');
|
||||
|
||||
// Get user's available points
|
||||
$userPoints = auth()->user()->customer->point ?? 0;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Points removed successfully',
|
||||
'points_removed' => $currentPoints,
|
||||
'available_points' => $userPoints
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error removing points: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'An error occurred while removing points'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class VoucherEventController extends Controller
|
|||
// Call the repository's redeem method
|
||||
$result = $this->voucherEventRepository->redeem($voucherEvent, $user);
|
||||
|
||||
if ($result['success']) {
|
||||
if ($result) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $result['message'] ?? 'Voucher redeemed successfully!',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Repositories\Member\WishlistRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class WishController extends Controller
|
||||
{
|
||||
protected $wishlistRepository;
|
||||
|
||||
public function __construct(WishlistRepository $wishlistRepository)
|
||||
{
|
||||
$this->wishlistRepository = $wishlistRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the wishlist page
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$wishlists = $this->wishlistRepository->getList($request);
|
||||
|
||||
return view('account.wishlist', [
|
||||
'wishlists' => $wishlists,
|
||||
'user' => Auth::user()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', 'Failed to load wishlist: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to wishlist
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'item_id' => 'required|exists:items,id'
|
||||
]);
|
||||
|
||||
try {
|
||||
$wishlist = $this->wishlistRepository->create([
|
||||
'item_id' => $request->item_id
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Item added to wishlist successfully',
|
||||
'wishlist' => $wishlist
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to add item to wishlist: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from wishlist
|
||||
*/
|
||||
public function destroy(Request $request, $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
|
||||
|
||||
$this->wishlistRepository->delete($id);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Item removed from wishlist successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to remove item from wishlist: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -199,4 +199,6 @@ class ItemReference extends Model
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,4 +355,10 @@ class Items extends Model
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function isWishlist() : bool
|
||||
{
|
||||
return $this->hasOne(Wishlist::class, 'item_id', 'id')->where('customer_id', auth()->user()->customer->id)->exists();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Wishlist extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'item_id',
|
||||
];
|
||||
|
||||
public function customer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function item(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Items::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repositories\Member;
|
||||
|
||||
use App\Models\Wishlist;
|
||||
use App\Models\Customer;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class WishlistRepository
|
||||
{
|
||||
public function getList($request)
|
||||
{
|
||||
$customer = Customer::where('user_id', auth()->user()->id)->first();
|
||||
|
||||
if (!$customer) {
|
||||
throw new \Exception('Customer not found');
|
||||
}
|
||||
$limit = 20;
|
||||
|
||||
$wishlist = Wishlist::where('customer_id', $customer->id)
|
||||
->with([
|
||||
'item.variants',
|
||||
|
||||
])
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate($limit);
|
||||
|
||||
return $wishlist;
|
||||
}
|
||||
|
||||
public function create($data)
|
||||
{
|
||||
$model = DB::transaction(function () use ($data) {
|
||||
$customer = Customer::where('user_id', auth()->user()->id)->first();
|
||||
|
||||
// Check if item already exists in wishlist
|
||||
$existing = Wishlist::where('customer_id', $customer->id)
|
||||
->where('item_id', $data['item_id'])
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return $existing; // Return existing item if already in wishlist
|
||||
}
|
||||
|
||||
$model = Wishlist::create([
|
||||
'customer_id' => $customer->id,
|
||||
'item_id' => $data['item_id'],
|
||||
]);
|
||||
|
||||
return $model;
|
||||
});
|
||||
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function delete($item_id)
|
||||
{
|
||||
$wishlist = DB::transaction(function () use ($item_id) {
|
||||
$wishlist = Wishlist::where('customer_id', auth()->user()->customer->id)
|
||||
->where('item_id', $item_id)
|
||||
->first();
|
||||
|
||||
if (!$wishlist) {
|
||||
throw new \Exception('Wishlist not found');
|
||||
}
|
||||
|
||||
$wishlist->delete();
|
||||
return $wishlist;
|
||||
});
|
||||
|
||||
|
||||
return $wishlist;
|
||||
}
|
||||
}
|
||||
|
|
@ -56,4 +56,16 @@ return [
|
|||
'terms_and_conditions' => 'Terms and Conditions',
|
||||
'close' => 'Close',
|
||||
'redeem' => 'Redeem',
|
||||
'use_point' => 'Use Points',
|
||||
'your_points' => 'Your Points',
|
||||
'available_points' => 'Available Points',
|
||||
'input_point' => 'Enter points to use',
|
||||
'apply_point' => 'Apply Points',
|
||||
'remove_point' => 'Remove Points',
|
||||
'points_applied' => 'Points Applied',
|
||||
'points_used' => 'points used',
|
||||
'saving' => 'Discount:',
|
||||
'shipping' => 'Shipping:',
|
||||
'estimated_total' => 'Estimated total:',
|
||||
'proceed_to_checkout' => 'Proceed to checkout',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -50,10 +50,22 @@ return [
|
|||
'enter_valid_promo_code' => 'Masukkan kode promo yang valid!',
|
||||
'apply' => 'Gunakan',
|
||||
'available_vouchers' => 'Voucher Tersedia',
|
||||
'available_voucher_events' => 'Event Voucher Tersedia',
|
||||
'available_voucher_events' => 'Tukar Poin',
|
||||
'points_required' => 'Poin Diperlukan',
|
||||
'valid_period' => 'Periode Berlaku',
|
||||
'terms_and_conditions' => 'Syarat dan Ketentuan',
|
||||
'close' => 'Tutup',
|
||||
'redeem' => 'Tukar',
|
||||
'use_point' => 'Gunakan Poin',
|
||||
'your_points' => 'Poin Anda',
|
||||
'available_points' => 'Poin Tersedia',
|
||||
'input_point' => 'Masukkan poin yang akan digunakan',
|
||||
'apply_point' => 'Gunakan Poin',
|
||||
'remove_point' => 'Hapus Poin',
|
||||
'points_applied' => 'Poin Diterapkan',
|
||||
'points_used' => 'poin digunakan',
|
||||
'saving' => 'Diskon:',
|
||||
'shipping' => 'Pengiriman:',
|
||||
'estimated_total' => 'Total estimasi:',
|
||||
'proceed_to_checkout' => 'Lanjut ke checkout',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@
|
|||
<!-- Page title + Add list button-->
|
||||
<div class="d-flex align-items-center justify-content-between pb-3 mb-1 mb-sm-2 mb-md-3">
|
||||
<h1 class="h2 me-3 mb-0">Wishlist</h1>
|
||||
<div class="nav">
|
||||
{{-- <div class="nav">
|
||||
<a class="nav-link animate-underline px-0 py-1 py-ms-2" data-bs-toggle="modal" href="#wishlistModal">
|
||||
<i class="ci-plus fs-base me-1"></i>
|
||||
<span class="animate-target">Add wishlist</span>
|
||||
</a>
|
||||
</div>
|
||||
</div> --}}
|
||||
</div>
|
||||
<!-- Wishlist selector -->
|
||||
<div class="border-bottom pb-4 mb-3">
|
||||
{{-- <div class="border-bottom pb-4 mb-3">
|
||||
<div class="row align-items-center justify-content-between">
|
||||
<div class="col-sm-7 col-md-8 col-xxl-9 d-flex align-items-center mb-3 mb-sm-0">
|
||||
<h5 class="me-2 mb-0">Interesting offers</h5>
|
||||
|
|
@ -69,9 +69,9 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> --}}
|
||||
<!-- Master checkbox + Action buttons -->
|
||||
<div class="nav align-items-center mb-4">
|
||||
{{-- <div class="nav align-items-center mb-4">
|
||||
<div class="form-checkl nav-link animate-underline fs-lg ps-0 pe-2 py-2 mt-n1 me-4"
|
||||
data-master-checkbox='{"container": "#wishlistSelection", "label": "Select all", "labelChecked": "Unselect all", "showOnCheck": "#action-buttons"}'>
|
||||
<input checked="" class="form-check-input" id="wishlist-master" type="checkbox" />
|
||||
|
|
@ -92,232 +92,14 @@
|
|||
<span class="animate-target d-none d-md-inline">Remove selected</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div> --}}
|
||||
<!-- Wishlist items (Grid) -->
|
||||
<div class="row row-cols-2 row-cols-md-3 g-4" id="wishlistSelection">
|
||||
<!-- Item -->
|
||||
<div class="col">
|
||||
<div class="product-card animate-underline hover-effect-opacity bg-body rounded">
|
||||
<div class="position-relative">
|
||||
<div class="position-absolute top-0 end-0 z-1 pt-1 pe-1 mt-2 me-2">
|
||||
<div class="form-check fs-lg">
|
||||
<input checked="" class="form-check-input select-card-check" type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
<a class="d-block rounded-top overflow-hidden p-3 p-sm-4"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<span
|
||||
class="badge bg-danger position-absolute top-0 start-0 mt-2 ms-2 mt-lg-3 ms-lg-3">-21%</span>
|
||||
<div class="ratio" style="--cz-aspect-ratio: calc(240 / 258 * 100%)">
|
||||
<img alt="VR Glasses" src="/img/shop/electronics/01.png" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="w-100 min-w-0 px-1 pb-2 px-sm-3 pb-sm-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<div class="d-flex gap-1 fs-xs">
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star text-body-tertiary opacity-75"></i>
|
||||
</div>
|
||||
<span class="text-body-tertiary fs-xs">(123)</span>
|
||||
</div>
|
||||
<h3 class="pb-1 mb-2">
|
||||
<a class="d-block fs-sm fw-medium text-truncate"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<span class="animate-target">VRB01 Virtual Reality Glasses</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="h5 lh-1 mb-0">$340.99 <del
|
||||
class="text-body-tertiary fs-sm fw-normal">$430.00</del></div>
|
||||
<button aria-label="Add to Cart"
|
||||
class="product-card-button btn btn-icon btn-secondary animate-slide-end ms-2"
|
||||
type="button">
|
||||
<i class="ci-shopping-cart fs-base animate-target"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Item -->
|
||||
<div class="col">
|
||||
<div class="product-card animate-underline hover-effect-opacity bg-body rounded">
|
||||
<div class="position-relative">
|
||||
<div class="position-absolute top-0 end-0 z-1 pt-1 pe-1 mt-2 me-2">
|
||||
<div class="form-check fs-lg">
|
||||
<input checked="" class="form-check-input select-card-check" type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
<a class="d-block rounded-top overflow-hidden p-3 p-sm-4"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<div class="ratio" style="--cz-aspect-ratio: calc(240 / 258 * 100%)">
|
||||
<img alt="iPhone 14" src="/img/shop/electronics/02.png" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="w-100 min-w-0 px-1 pb-2 px-sm-3 pb-sm-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<div class="d-flex gap-1 fs-xs">
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-half text-warning"></i>
|
||||
</div>
|
||||
<span class="text-body-tertiary fs-xs">(142)</span>
|
||||
</div>
|
||||
<h3 class="pb-1 mb-2">
|
||||
<a class="d-block fs-sm fw-medium text-truncate"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<span class="animate-target">Apple iPhone 14 128GB White</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="h5 lh-1 mb-0">$899.00</div>
|
||||
<button aria-label="Add to Cart"
|
||||
class="product-card-button btn btn-icon btn-secondary animate-slide-end ms-2"
|
||||
type="button">
|
||||
<i class="ci-shopping-cart fs-base animate-target"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Item -->
|
||||
<div class="col">
|
||||
<div class="product-card animate-underline hover-effect-opacity bg-body rounded">
|
||||
<div class="position-relative">
|
||||
<div class="position-absolute top-0 end-0 z-1 pt-1 pe-1 mt-2 me-2">
|
||||
<div class="form-check fs-lg">
|
||||
<input class="form-check-input select-card-check" type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
<a class="d-block rounded-top overflow-hidden p-3 p-sm-4"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<div class="ratio" style="--cz-aspect-ratio: calc(240 / 258 * 100%)">
|
||||
<img alt="Smart Watch" src="/img/shop/electronics/03.png" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="w-100 min-w-0 px-1 pb-2 px-sm-3 pb-sm-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<div class="d-flex gap-1 fs-xs">
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
</div>
|
||||
<span class="text-body-tertiary fs-xs">(67)</span>
|
||||
</div>
|
||||
<h3 class="pb-1 mb-2">
|
||||
<a class="d-block fs-sm fw-medium text-truncate"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<span class="animate-target">Smart Watch Series 7, White</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="h5 lh-1 mb-0">$429.00</div>
|
||||
<button aria-label="Add to Cart"
|
||||
class="product-card-button btn btn-icon btn-secondary animate-slide-end ms-2"
|
||||
type="button">
|
||||
<i class="ci-shopping-cart fs-base animate-target"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Item -->
|
||||
<div class="col">
|
||||
<div class="product-card animate-underline hover-effect-opacity bg-body rounded">
|
||||
<div class="posittion-relative">
|
||||
<div class="position-absolute top-0 end-0 z-1 pt-1 pe-1 mt-2 me-2">
|
||||
<div class="form-check fs-lg">
|
||||
<input class="form-check-input select-card-check" type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
<a class="d-block rounded-top overflow-hidden p-3 p-sm-4"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<div class="ratio" style="--cz-aspect-ratio: calc(240 / 258 * 100%)">
|
||||
<img alt="iPad Air" src="/img/shop/electronics/05.png" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="w-100 min-w-0 px-1 pb-2 px-sm-3 pb-sm-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<div class="d-flex gap-1 fs-xs">
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
</div>
|
||||
<span class="text-body-tertiary fs-xs">(12)</span>
|
||||
</div>
|
||||
<h3 class="pb-1 mb-2">
|
||||
<a class="d-block fs-sm fw-medium text-truncate"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<span class="animate-target">Tablet Apple iPad Air M1</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="h5 lh-1 mb-0">$540.00</div>
|
||||
<button aria-label="Add to Cart"
|
||||
class="product-card-button btn btn-icon btn-secondary animate-slide-end ms-2"
|
||||
type="button">
|
||||
<i class="ci-shopping-cart fs-base animate-target"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Item -->
|
||||
<div class="col">
|
||||
<div class="product-card animate-underline hover-effect-opacity bg-body rounded">
|
||||
<div class="position-relative">
|
||||
<div class="position-absolute top-0 end-0 z-1 pt-1 pe-1 mt-2 me-2">
|
||||
<div class="form-check fs-lg">
|
||||
<input class="form-check-input select-card-check" type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
<a class="d-block rounded-top overflow-hidden p-3 p-sm-4"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<div class="ratio" style="--cz-aspect-ratio: calc(240 / 258 * 100%)">
|
||||
<img alt="AirPods 2" src="/img/shop/electronics/06.png" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="w-100 min-w-0 px-1 pb-2 px-sm-3 pb-sm-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<div class="d-flex gap-1 fs-xs">
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star-filled text-warning"></i>
|
||||
<i class="ci-star text-body-tertiary opacity-75"></i>
|
||||
</div>
|
||||
<span class="text-body-tertiary fs-xs">(78)</span>
|
||||
</div>
|
||||
<h3 class="pb-1 mb-2">
|
||||
<a class="d-block fs-sm fw-medium text-truncate"
|
||||
href="{{ route('second', ['shop', 'product-general-electronics']) }}">
|
||||
<span class="animate-target">Headphones Apple AirPods 2 Pro</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="h5 lh-1 mb-0">$224.00</div>
|
||||
<button aria-label="Add to Cart"
|
||||
class="product-card-button btn btn-icon btn-secondary animate-slide-end ms-2"
|
||||
type="button">
|
||||
<i class="ci-shopping-cart fs-base animate-target"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach ($wishlists as $key => $value)
|
||||
@include('components.home.product-card', ['product' => $value->item])
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -186,26 +186,26 @@
|
|||
<span class="text-dark-emphasis fw-medium" id="cart-subtotal">Rp 0</span>
|
||||
</li>
|
||||
<li class="d-flex justify-content-between">
|
||||
Saving:
|
||||
<span class="text-danger fw-medium">Rp 0</span>
|
||||
{{ __('checkout.saving') }}
|
||||
<span class="text-danger fw-medium">Rp {{ number_format(session('use_point') ?? 0,0,",",".") }}</span>
|
||||
</li>
|
||||
{{-- <li class="d-flex justify-content-between" id="tax-row" style="display: none;">
|
||||
Tax collected:
|
||||
<span class="text-dark-emphasis fw-medium">Rp 0</span>
|
||||
</li> --}}
|
||||
<li class="d-flex justify-content-between">
|
||||
Shipping:
|
||||
{{ __('checkout.shipping') }}
|
||||
<span class="text-dark-emphasis fw-medium">Calculated at checkout</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="border-top pt-4 mt-4">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="fs-sm">Estimated total:</span>
|
||||
<span class="fs-sm">{{ __('checkout.estimated_total') }}</span>
|
||||
<span class="h5 mb-0" id="cart-estimated-total">$0.00</span>
|
||||
</div>
|
||||
<a class="btn btn-lg btn-primary w-100"
|
||||
href="{{ route('checkout.delivery') }}">
|
||||
Proceed to checkout
|
||||
{{ __('checkout.proceed_to_checkout') }}
|
||||
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
||||
</a>
|
||||
{{-- <div class="nav justify-content-center fs-sm mt-3">
|
||||
|
|
@ -217,7 +217,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<x-checkout.promo-code />
|
||||
{{-- <x-checkout.promo-code /> --}}
|
||||
<x-checkout.use-point />
|
||||
</div>
|
||||
</aside>
|
||||
@endif
|
||||
|
|
@ -1039,7 +1040,7 @@
|
|||
}
|
||||
|
||||
// Calculate estimated total (subtotal - savings + tax)
|
||||
const savings = 0; // Fixed savings amount
|
||||
const savings = {{ session('use_point') ?? 0 }}; // Fixed savings amount
|
||||
const tax = 0; // Fixed tax amount
|
||||
const estimatedTotal = subtotal - savings + tax;
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
<!-- Order summary (sticky sidebar) -->
|
||||
<aside class="col-lg-4 offset-xl-1" style="margin-top: -100px">
|
||||
<div class="position-sticky top-0" style="padding-top: 100px">
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total" :savings="0" :tax="0"
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total"
|
||||
:showEdit="true" :editUrl="route('cart.index')" />
|
||||
</div>
|
||||
</aside>
|
||||
|
|
@ -113,6 +113,7 @@
|
|||
});
|
||||
|
||||
function updateOrderSummaryWithShippingCost(shippingValue) {
|
||||
const saving = {{ session('use_point') ?? 0 }};
|
||||
console.log('updateOrderSummaryWithShippingCost called with:', shippingValue);
|
||||
|
||||
// Parse the shipping value: courier|service|cost
|
||||
|
|
@ -138,7 +139,7 @@
|
|||
|
||||
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||
console.log('Current subtotal:', currentSubtotal);
|
||||
const newTotal = currentSubtotal + cost;
|
||||
const newTotal = currentSubtotal + cost - saving;
|
||||
console.log('New total:', newTotal);
|
||||
|
||||
// Store original total
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
<!-- Order summary (sticky sidebar) -->
|
||||
<aside class="col-lg-4 offset-xl-1" style="margin-top: -100px">
|
||||
<div class="position-sticky top-0" style="padding-top: 100px">
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total" :savings="0" :tax="0"
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total"
|
||||
:showEdit="true" :editUrl="route('cart.index')" />
|
||||
</div>
|
||||
</aside>
|
||||
|
|
@ -428,11 +428,12 @@
|
|||
function updateOrderSummaryWithShipping() {
|
||||
// Simulate shipping cost calculation based on postcode
|
||||
const shippingCost = calculateShippingCost(document.getElementById('zip').value);
|
||||
const saving = {{ session('use_point') ?? 0 }};
|
||||
|
||||
// Update order summary
|
||||
const subtotalElement = document.getElementById('cart-subtotal');
|
||||
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||
const newTotal = currentSubtotal + shippingCost;
|
||||
const newTotal = currentSubtotal + 0 - saving;
|
||||
|
||||
// Store original total
|
||||
if (!window.originalTotal) {
|
||||
|
|
@ -481,40 +482,15 @@
|
|||
}
|
||||
|
||||
function showShippingCost(cost, isFree = false) {
|
||||
// Find or create shipping cost element in order summary
|
||||
let shippingElement = document.querySelector('[data-shipping-cost]');
|
||||
|
||||
if (!shippingElement) {
|
||||
const orderSummary = document.querySelector('.list-unstyled');
|
||||
const shippingLi = document.createElement('li');
|
||||
shippingLi.className = 'd-flex justify-content-between';
|
||||
shippingLi.setAttribute('data-shipping-cost', '');
|
||||
shippingLi.innerHTML = `
|
||||
<span>{{ __('checkout.shipping') }}:</span>
|
||||
<span class="text-dark-emphasis fw-medium" id="shipping-cost">Rp 0</span>
|
||||
`;
|
||||
orderSummary.appendChild(shippingLi);
|
||||
shippingElement = shippingLi;
|
||||
}
|
||||
|
||||
const costElement = document.getElementById('shipping-cost');
|
||||
if (isFree) {
|
||||
costElement.textContent = '{{ __('checkout.free') }}';
|
||||
costElement.className = 'text-success fw-medium';
|
||||
} else {
|
||||
costElement.textContent = `Rp ${number_format(cost, 0, ',', '.')}`;
|
||||
costElement.className = 'text-dark-emphasis fw-medium';
|
||||
}
|
||||
|
||||
shippingElement.style.display = 'flex';
|
||||
}
|
||||
|
||||
function validateDeliveryForm() {
|
||||
const postcode = document.getElementById('zip').value;
|
||||
const firstName = document.getElementById('firstName').value;
|
||||
const address = document.getElementById('address').value;
|
||||
const city = document.getElementById('city').value;
|
||||
const phone = document.getElementById('phone').value;
|
||||
const postcode = document.getElementById('zip').textContent;
|
||||
const firstName = document.getElementById('firstName').textContent;
|
||||
const address = document.getElementById('address').textContent;
|
||||
const city = document.getElementById('city').textContent;
|
||||
const phone = document.getElementById('phone').textContent;
|
||||
|
||||
if (!postcode) {
|
||||
alert('{{ __('checkout.enter_postcode') }}');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
@props([
|
||||
'subtotal' => 0,
|
||||
'total' => 0,
|
||||
'savings' => 0,
|
||||
'savings' => session('use_point'),
|
||||
'tax' => 0,
|
||||
'showEdit' => false,
|
||||
'editUrl' => null,
|
||||
|
|
@ -36,9 +36,7 @@
|
|||
</div>
|
||||
<ul class="list-unstyled fs-sm gap-3 mb-0">
|
||||
<li class="d-flex justify-content-between">
|
||||
<div>{{ __('cart_summary.subtotal') }} (
|
||||
{{ __('cart_summary.items_count', ['count' => auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0]) }}):
|
||||
</div>
|
||||
<div>{{ __('cart_summary.subtotal') }} ({{ __('cart_summary.items_count', ['count' => auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0]) }})</div>
|
||||
<span class="text-dark-emphasis fw-medium" id="cart-subtotal">Rp
|
||||
{{ number_format($subtotal, 0, ',', '.') }}</span>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -48,8 +48,13 @@
|
|||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@foreach($vouchers as $voucher)
|
||||
<span class="badge bg-light text-dark px-3 py-2">
|
||||
{{ $voucher->code }}
|
||||
<span class="badge bg-light text-dark px-3 py-2"
|
||||
style="cursor: pointer; transition: all 0.2s ease;"
|
||||
onclick="applyVoucher('{{ $voucher->code }}')"
|
||||
onmouseover="this.style.backgroundColor='#e9ecef'; this.style.transform='scale(1.05)';"
|
||||
onmouseout="this.style.backgroundColor='#f8f9fa'; this.style.transform='scale(1)';"
|
||||
title="Click to apply {{ $voucher->code }}">
|
||||
{{ $voucher->description }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
|
|
@ -187,6 +192,84 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
|
||||
});
|
||||
|
||||
// Handle promo code form submission
|
||||
document.addEventListener('submit', function(e) {
|
||||
const form = e.target;
|
||||
|
||||
// Check if this is the promo code form
|
||||
if (!form || !form.classList.contains('needs-validation')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const promoInput = form.querySelector('input[placeholder="{{ __('checkout.enter_promo_code') }}"]');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.textContent;
|
||||
|
||||
if (!promoInput || !promoInput.value.trim()) {
|
||||
alert('Please enter a promo code');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Applying...';
|
||||
|
||||
// Get CSRF token
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
// Submit voucher code to server
|
||||
fetch('/apply-voucher', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
'promo_code': promoInput.value.trim()
|
||||
}).toString()
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Voucher applied successfully!');
|
||||
|
||||
// Clear input
|
||||
promoInput.value = '';
|
||||
|
||||
// Add visual feedback
|
||||
promoInput.style.backgroundColor = '#d4edda';
|
||||
promoInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
promoInput.style.backgroundColor = '';
|
||||
promoInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to apply voucher');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error applying voucher:', error);
|
||||
alert('An error occurred while applying the voucher');
|
||||
})
|
||||
.finally(() => {
|
||||
// Restore button state
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle redeem button click
|
||||
document.getElementById('redeemVoucherBtn').addEventListener('click', function() {
|
||||
const voucherEventId = this.getAttribute('data-voucher-event-id');
|
||||
|
|
@ -215,14 +298,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
// Show success message
|
||||
alert('Voucher redeemed successfully!');
|
||||
|
||||
// Close modal
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('voucherEventModal'));
|
||||
modal.hide();
|
||||
|
||||
// Reload page to update cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to redeem voucher');
|
||||
|
|
@ -259,6 +335,34 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
});
|
||||
});
|
||||
|
||||
// Function to apply voucher code when badge is clicked
|
||||
function applyVoucher(voucherCode) {
|
||||
// Find the promo code input
|
||||
const promoInput = document.querySelector('input[placeholder="{{ __('checkout.enter_promo_code') }}"]');
|
||||
|
||||
if (promoInput) {
|
||||
// Set the voucher code
|
||||
promoInput.value = voucherCode;
|
||||
|
||||
// Add visual feedback
|
||||
promoInput.style.backgroundColor = '#d4edda';
|
||||
promoInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
promoInput.style.backgroundColor = '';
|
||||
promoInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Focus on the input
|
||||
promoInput.focus();
|
||||
|
||||
// Optionally submit the form automatically
|
||||
// Uncomment the next line if you want auto-submission
|
||||
// promoInput.closest('form').requestSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
<div class="accordion bg-body-tertiary rounded-5 p-4">
|
||||
<div class="accordion-item border-0">
|
||||
<h3 class="accordion-header" id="promoCodeHeading">
|
||||
<button type="button"
|
||||
class="accordion-button animate-underline collapsed py-0 ps-sm-2 ps-lg-0 ps-xl-2"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#promoCode"
|
||||
aria-expanded="false"
|
||||
aria-controls="promoCode">
|
||||
<i class="ci-percent fs-xl me-2"></i>
|
||||
<span class="animate-target me-2">
|
||||
{{ __('checkout.use_point') }}
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<div class="accordion-collapse collapse"
|
||||
id="promoCode"
|
||||
aria-labelledby="promoCodeHeading">
|
||||
|
||||
<div class="accordion-body pt-3 pb-2 ps-sm-2 px-lg-0 px-xl-2">
|
||||
|
||||
{{-- Your Points --}}
|
||||
<div class="alert bg-white dark:bg-gray-800 text-white mb-3">
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div>
|
||||
<h4 class="mb-0 fw-bold text-dark dark:text-white" id="userPoints">{{ number_format(auth()->user()->customer->point, 0, ",",".") }}</h4>
|
||||
<small class="text-black dark:text-white">{{ __('checkout.available_points') }}</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Points Form --}}
|
||||
@if(session('use_point'))
|
||||
{{-- Points Applied Display --}}
|
||||
<div class="alert alert-success d-flex align-items-center gap-3">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">{{ __('checkout.points_applied') }}</h6>
|
||||
<p class="mb-0">
|
||||
<strong>{{ number_format(session('use_point'), 0, ",", ".") }}</strong> {{ __('checkout.points_used') }}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm" id="remove-points-btn">
|
||||
{{ __('checkout.remove_point') }}
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
{{-- Apply Points Form --}}
|
||||
<form class="needs-validation d-flex gap-2" novalidate>
|
||||
<div class="position-relative w-100">
|
||||
<input type="number"
|
||||
class="form-control"
|
||||
id="input-point"
|
||||
min="1"
|
||||
max="{{ auth()->user()->customer->point }}"
|
||||
placeholder="{{ __('checkout.input_point') }}"
|
||||
required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-dark">
|
||||
{{ __('checkout.apply_point') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{{-- SINGLE REUSABLE MODAL --}}
|
||||
<div class="modal fade" id="voucherEventModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content rounded-4">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title fw-semibold">
|
||||
Voucher Event
|
||||
</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body pt-0">
|
||||
<div id="voucherEventContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal">
|
||||
{{ __('checkout.close') }}
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
id="redeemVoucherBtn"
|
||||
data-voucher-event-id="">
|
||||
{{ __('checkout.redeem') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const modal = document.getElementById('voucherEventModal');
|
||||
|
||||
modal.addEventListener('show.bs.modal', function (event) {
|
||||
|
||||
const button = event.relatedTarget;
|
||||
|
||||
const name = button.getAttribute('data-name');
|
||||
const description = button.getAttribute('data-description');
|
||||
const point = button.getAttribute('data-point');
|
||||
const start = button.getAttribute('data-start');
|
||||
const end = button.getAttribute('data-end');
|
||||
const terms = button.getAttribute('data-terms');
|
||||
|
||||
let content = '';
|
||||
|
||||
if (description) {
|
||||
content += `<p>${description}</p>`;
|
||||
}
|
||||
|
||||
if (point) {
|
||||
content += `
|
||||
<div class="mb-2">
|
||||
<strong>{{ __('checkout.points_required') }}:</strong>
|
||||
${point}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (start && end) {
|
||||
content += `
|
||||
<div class="mb-2">
|
||||
<strong>{{ __('checkout.valid_period') }}:</strong><br>
|
||||
${start} - ${end}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (terms) {
|
||||
content += `
|
||||
<div class="mt-3 small text-muted">
|
||||
<strong>{{ __('checkout.terms_and_conditions') }}:</strong>
|
||||
<p>${terms}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
modal.querySelector('.modal-title').textContent = name;
|
||||
document.getElementById('voucherEventContent').innerHTML = content;
|
||||
|
||||
// Set voucher event ID for redeem button
|
||||
const redeemBtn = document.getElementById('redeemVoucherBtn');
|
||||
redeemBtn.setAttribute('data-voucher-event-id', button.getAttribute('data-id'));
|
||||
|
||||
});
|
||||
|
||||
// Handle points form submission
|
||||
document.addEventListener('submit', function(e) {
|
||||
const form = e.target;
|
||||
|
||||
// Check if this is the points form
|
||||
if (!form || !form.classList.contains('needs-validation')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const pointInput = form.querySelector('input[placeholder="{{ __('checkout.input_point') }}"]');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.textContent;
|
||||
const availablePoints = parseInt(document.getElementById('userPoints').textContent.replace(/\./g, ''));
|
||||
|
||||
if (!pointInput || !pointInput.value.trim()) {
|
||||
alert('Please enter points to use');
|
||||
return;
|
||||
}
|
||||
|
||||
const pointsToUse = parseInt(pointInput.value.trim());
|
||||
|
||||
if (isNaN(pointsToUse) || pointsToUse <= 0) {
|
||||
alert('Please enter a valid number of points');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointsToUse > availablePoints) {
|
||||
alert('You cannot use more points than available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Applying...';
|
||||
|
||||
// Get CSRF token
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
// Submit points to server to set session
|
||||
fetch('/apply-points', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
'use_point': pointsToUse
|
||||
}).toString()
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Points applied successfully!');
|
||||
|
||||
// Clear input
|
||||
pointInput.value = '';
|
||||
|
||||
// Add visual feedback
|
||||
pointInput.style.backgroundColor = '#d4edda';
|
||||
pointInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
pointInput.style.backgroundColor = '';
|
||||
pointInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to apply points');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error applying points:', error);
|
||||
alert('An error occurred while applying the points');
|
||||
})
|
||||
.finally(() => {
|
||||
// Restore button state
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle remove points button click
|
||||
document.getElementById('remove-points-btn')?.addEventListener('click', function() {
|
||||
const removeBtn = this;
|
||||
const originalText = removeBtn.textContent;
|
||||
|
||||
// Show loading state
|
||||
removeBtn.disabled = true;
|
||||
removeBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Removing...';
|
||||
|
||||
// Get CSRF token
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
// Remove points via AJAX
|
||||
fetch('/remove-points', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Points removed successfully!');
|
||||
|
||||
// Clear input field
|
||||
const pointInput = document.querySelector('input#input-point');
|
||||
if (pointInput) {
|
||||
pointInput.value = '';
|
||||
pointInput.style.backgroundColor = '#f8d7da';
|
||||
pointInput.style.borderColor = '#dc3545';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
pointInput.style.backgroundColor = '';
|
||||
pointInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to remove points');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error removing points:', error);
|
||||
alert('An error occurred while removing the points');
|
||||
})
|
||||
.finally(() => {
|
||||
// Restore button state
|
||||
removeBtn.disabled = false;
|
||||
removeBtn.textContent = originalText;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle redeem button click
|
||||
document.getElementById('redeemVoucherBtn').addEventListener('click', function() {
|
||||
const voucherEventId = this.getAttribute('data-voucher-event-id');
|
||||
|
||||
if (!voucherEventId) {
|
||||
console.error('No voucher event ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button during processing
|
||||
this.disabled = true;
|
||||
this.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Processing...';
|
||||
|
||||
// Make AJAX call to redeem voucher
|
||||
fetch(`/voucher-events/${voucherEventId}/redeem`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Voucher redeemed successfully!');
|
||||
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to redeem voucher');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while redeeming the voucher');
|
||||
})
|
||||
.finally(() => {
|
||||
// Re-enable button
|
||||
this.disabled = false;
|
||||
this.innerHTML = '{{ __('checkout.redeem') }}';
|
||||
});
|
||||
});
|
||||
|
||||
// Custom modal handler to prevent backdrop issues
|
||||
const voucherBadges = document.querySelectorAll('[data-bs-toggle="modal"][data-bs-target="#voucherEventModal"]');
|
||||
voucherBadges.forEach(function(badge) {
|
||||
badge.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove any existing backdrop
|
||||
const existingBackdrop = document.querySelector('.modal-backdrop');
|
||||
if (existingBackdrop) {
|
||||
existingBackdrop.remove();
|
||||
}
|
||||
|
||||
// Show modal without backdrop
|
||||
const modalInstance = new bootstrap.Modal(modal, {
|
||||
backdrop: false
|
||||
});
|
||||
modalInstance.show();
|
||||
});
|
||||
});
|
||||
|
||||
// Function to apply voucher code when badge is clicked
|
||||
function applyVoucher(voucherCode) {
|
||||
// Find the point input
|
||||
const pointInput = document.querySelector('input#input-point');
|
||||
|
||||
if (pointInput) {
|
||||
|
||||
// set point session via AJAX
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
fetch('/apply-points', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
'use_point': voucherCode
|
||||
}).toString()
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Set the voucher code in input
|
||||
pointInput.value = voucherCode;
|
||||
|
||||
// Add visual feedback
|
||||
pointInput.style.backgroundColor = '#d4edda';
|
||||
pointInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
pointInput.style.backgroundColor = '';
|
||||
pointInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Show success message
|
||||
alert('Points applied successfully!');
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to apply points');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error applying points:', error);
|
||||
alert('An error occurred while applying the points');
|
||||
});
|
||||
|
||||
// Focus on the input
|
||||
pointInput.focus();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
@ -33,8 +33,8 @@
|
|||
@endif
|
||||
</div>
|
||||
<div class="position-relative">
|
||||
@if ($product->variants->count() > 1)
|
||||
<div class=" fs-xs text-body-secondary opacity-100">+{{ $product->variants->count() - 1 }} Varian</div>
|
||||
@if (($product->variants?->count() ?? 0) > 1)
|
||||
<div class=" fs-xs text-body-secondary opacity-100">+{{ ($product->variants->count() ?? 0) - 1 }} Varian</div>
|
||||
@endif
|
||||
{{-- <div class="hover-effect-target fs-xs text-body-secondary opacity-100">+1 color</div> --}}
|
||||
{{-- <div class="hover-effect-target d-flex gap-2 position-absolute top-0 start-0 opacity-0">
|
||||
|
|
|
|||
|
|
@ -27,13 +27,19 @@
|
|||
|
||||
<!-- Search bar visible on screens > 768px wide (md breakpoint) -->
|
||||
<div class="position-relative w-100 d-none d-md-block me-3 me-xl-4">
|
||||
<input type="search" class="form-control form-control-lg rounded-pill"
|
||||
placeholder="Search for products" aria-label="Search">
|
||||
<button type="button"
|
||||
<form action="{{ route('product.index') }}" method="GET" id="header-search-form">
|
||||
<input type="search"
|
||||
class="form-control form-control-lg rounded-pill"
|
||||
name="search"
|
||||
placeholder="Search for products"
|
||||
aria-label="Search"
|
||||
value="{{ request('search') }}">
|
||||
<button type="submit"
|
||||
class="btn btn-icon btn-ghost fs-lg btn-secondary text-bo border-0 position-absolute top-0 end-0 rounded-circle mt-1 me-1"
|
||||
aria-label="Search button">
|
||||
<i class="ci-search"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Delivery options toggle visible on screens > 1200px wide (xl breakpoint) -->
|
||||
|
|
@ -159,9 +165,15 @@
|
|||
<div class="collapse d-md-none" id="searchBar">
|
||||
<div class="container pt-2 pb-3">
|
||||
<div class="position-relative">
|
||||
<form action="{{ route('product.index') }}" method="GET" id="mobile-search-form">
|
||||
<i class="ci-search position-absolute top-50 translate-middle-y d-flex fs-lg ms-3"></i>
|
||||
<input type="search" class="form-control form-icon-start rounded-pill"
|
||||
placeholder="Search for products" data-autofocus="collapse">
|
||||
<input type="search"
|
||||
class="form-control form-icon-start rounded-pill"
|
||||
name="search"
|
||||
placeholder="Search for products"
|
||||
data-autofocus="collapse"
|
||||
value="{{ request('search') }}">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -169,3 +181,46 @@
|
|||
|
||||
<x-grocery.top-header />
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Handle desktop search form submission
|
||||
const desktopSearchForm = document.getElementById('header-search-form');
|
||||
if (desktopSearchForm) {
|
||||
desktopSearchForm.addEventListener('submit', function(e) {
|
||||
const searchInput = this.querySelector('input[name="search"]');
|
||||
if (!searchInput.value.trim()) {
|
||||
e.preventDefault();
|
||||
searchInput.focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle mobile search form submission
|
||||
const mobileSearchForm = document.getElementById('mobile-search-form');
|
||||
if (mobileSearchForm) {
|
||||
mobileSearchForm.addEventListener('submit', function(e) {
|
||||
const searchInput = this.querySelector('input[name="search"]');
|
||||
if (!searchInput.value.trim()) {
|
||||
e.preventDefault();
|
||||
searchInput.focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add search suggestions functionality (optional enhancement)
|
||||
const searchInputs = document.querySelectorAll('input[name="search"]');
|
||||
searchInputs.forEach(input => {
|
||||
input.addEventListener('input', function(e) {
|
||||
const searchTerm = e.target.value.trim();
|
||||
if (searchTerm.length >= 2) {
|
||||
// You can implement AJAX search suggestions here
|
||||
// For now, just log the search term
|
||||
console.log('Searching for:', searchTerm);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
showLoginDialog();
|
||||
} else {
|
||||
// You could show an error message here
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
{{-- <span class="badge bg-primary rounded-pill ms-auto">1</span> --}}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action d-flex align-items-center"
|
||||
href="{{ route('second', ['account', 'wishlist']) }}">
|
||||
href="{{ route('wishlist.index') }}">
|
||||
<i class="ci-heart fs-base opacity-75 me-2"></i>
|
||||
{{ __('account_sidebar.wishlist') }}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
@extends('layouts.landing', ['title' => $product->name ?? 'Detail'])
|
||||
|
||||
@section('content')
|
||||
|
||||
|
||||
|
||||
<x-layout.header-grocery />
|
||||
|
||||
<main class="content-wrapper">
|
||||
|
|
@ -73,9 +70,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-lg btn-outline-secondary w-100 collapsed d-md-none"
|
||||
data-bs-toggle="collapse" data-bs-target="#morePictures"
|
||||
data-label-collapsed="Show more pictures" data-label-expanded="Show less pictures"
|
||||
aria-expanded="false" aria-controls="morePictures" aria-label="Show / hide pictures">
|
||||
data-bs-toggle="collapse" data-bs-target="#morePictures" data-label-collapsed="Show more pictures"
|
||||
data-label-expanded="Show less pictures" aria-expanded="false" aria-controls="morePictures"
|
||||
aria-label="Show / hide pictures">
|
||||
<i class="collapse-toggle-icon ci-chevron-down fs-lg ms-2 me-n2"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -98,16 +95,34 @@
|
|||
</a> --}}
|
||||
|
||||
<!-- Title -->
|
||||
<div class="d-flex gap-3">
|
||||
<div class=" flex-1">
|
||||
<h1 class="h3">{{ $product->name }}</h1>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- wishlist/love button --}}
|
||||
<button type="button"
|
||||
class="btn btn-outline-danger gap-2"
|
||||
style="height: 50px;"
|
||||
id="wishlist-btn"
|
||||
data-item-id="{{ $product->id }}"
|
||||
data-is-wishlist="{{ $product->isWishlist() ? 'true' : 'false' }}">
|
||||
<i class="{{ $product->isWishlist() == true ? 'ci-heart-filled' : 'ci-heart' }}"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- add category --}}
|
||||
<div class="mb-4 gap-2">
|
||||
<a href="{{ route('product.index', ['filter[category_id]' => $product->category->id]) }}"
|
||||
class="text-decoration-none text-body-emphasis animate-underline text-xs border border-secondary rounded-pill px-2 py-1" style="font-size:10pt;">{{ $product->category->name }}</a>
|
||||
class="text-decoration-none text-body-emphasis animate-underline text-xs border border-secondary rounded-pill px-2 py-1"
|
||||
style="font-size:10pt;">{{ $product->category->name }}</a>
|
||||
|
||||
<a href="{{ route('product.index', ['filter[brand_id]' => $product->brand->id]) }}"
|
||||
class="text-decoration-none text-body-emphasis animate-underline text-xs border border-secondary rounded-pill px-2 py-1" style="font-size:10pt;">{{ $product->brand->name }}</a>
|
||||
class="text-decoration-none text-body-emphasis animate-underline text-xs border border-secondary rounded-pill px-2 py-1"
|
||||
style="font-size:10pt;">{{ $product->brand->name }}</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
|
|
@ -144,8 +159,8 @@
|
|||
|
||||
@foreach ($product->variants as $key => $variant)
|
||||
<input type="radio" class="btn-check" name="colors"
|
||||
value="{{ $variant->reference->id }}"
|
||||
id="variant-id-{{ $variant->id }}" {{ $key == 0 ? 'checked' : '' }}>
|
||||
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, ',', '.') }}"
|
||||
|
|
@ -161,11 +176,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- <button type="button" class="btn btn-outline-secondary gap-2">
|
||||
<i class="ci-heart"></i>
|
||||
Add to wishlist
|
||||
</button>
|
||||
|
||||
{{--
|
||||
<br>
|
||||
<br> --}}
|
||||
|
||||
|
|
@ -1053,11 +1064,11 @@
|
|||
document.querySelector('.variantOption').textContent = labelText;
|
||||
|
||||
// Update AddToCart component with new variant ID
|
||||
const addToCartBtn = document.querySelector('[data-item-reference-id]');
|
||||
const addToCartBtn = document.querySelector('[data-item-id]');
|
||||
if (addToCartBtn) {
|
||||
console.log(this.value);
|
||||
var item_reference_id = this.value;
|
||||
addToCartBtn.setAttribute('data-item-reference-id', item_reference_id);
|
||||
var item_id = this.value;
|
||||
addToCartBtn.setAttribute('data-item-id', item_id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1067,5 +1078,108 @@
|
|||
checkedRadio.dispatchEvent(new Event('change'));
|
||||
}
|
||||
});
|
||||
|
||||
// Wishlist button functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const wishlistBtn = document.getElementById('wishlist-btn');
|
||||
if (wishlistBtn) {
|
||||
wishlistBtn.addEventListener('click', function() {
|
||||
const itemId = this.getAttribute('data-item-id');
|
||||
const isWishlist = this.getAttribute('data-is-wishlist') === 'true';
|
||||
const icon = this.querySelector('i');
|
||||
const text = this.querySelector('span');
|
||||
|
||||
// Get CSRF token
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
if (isWishlist) {
|
||||
// Remove from wishlist
|
||||
fetch('/account/wishlist/' + itemId, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update button state
|
||||
this.setAttribute('data-is-wishlist', 'false');
|
||||
icon.className = 'ci-heart';
|
||||
text.textContent = 'Add to Wishlist';
|
||||
|
||||
// Show success message
|
||||
const successAlert = document.createElement('div');
|
||||
successAlert.className = 'alert alert-success alert-dismissible fade show position-fixed';
|
||||
successAlert.style.css = 'top: 20px; right: 20px; z-index: 9999; max-width: 300px;';
|
||||
successAlert.innerHTML = `
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert">×</button>
|
||||
Item removed from wishlist!
|
||||
`;
|
||||
document.body.appendChild(successAlert);
|
||||
|
||||
// Auto-hide after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (successAlert.parentNode) {
|
||||
successAlert.parentNode.removeChild(successAlert);
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
console.error('Failed to remove from wishlist:', data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error removing from wishlist:', error);
|
||||
});
|
||||
} else {
|
||||
// Add to wishlist
|
||||
fetch('/account/wishlist', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
item_id: itemId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update button state
|
||||
this.setAttribute('data-is-wishlist', 'true');
|
||||
icon.className = 'ci-heart-filled';
|
||||
text.textContent = 'In Wishlist';
|
||||
|
||||
// Show success message
|
||||
const successAlert = document.createElement('div');
|
||||
successAlert.className = 'alert alert-success alert-dismissible fade show position-fixed';
|
||||
successAlert.style.css = 'top: 20px; right: 20px; z-index: 9999; max-width: 300px;';
|
||||
successAlert.innerHTML = `
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert">×</button>
|
||||
Item added to wishlist!
|
||||
`;
|
||||
document.body.appendChild(successAlert);
|
||||
|
||||
// Auto-hide after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (successAlert.parentNode) {
|
||||
successAlert.parentNode.removeChild(successAlert);
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
console.error('Failed to add to wishlist:', data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error adding to wishlist:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use App\Http\Controllers\TncController;
|
|||
use App\Http\Controllers\HelpController;
|
||||
use App\Http\Controllers\VoucherEventController;
|
||||
use App\Http\Controllers\ContactController;
|
||||
use App\Http\Controllers\WishController;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -123,6 +124,10 @@ Route::middleware(['auth'])->prefix('/checkout')->group(function () {
|
|||
|
||||
});
|
||||
|
||||
// Apply points route (outside checkout prefix for easier access)
|
||||
Route::post('/apply-points', [CheckoutController::class, 'applyPoint'])->name('checkout.apply.points');
|
||||
Route::post('/remove-points', [CheckoutController::class, 'removePoint'])->name('checkout.remove.points');
|
||||
|
||||
Route::middleware(['auth'])->prefix('/voucher-events')->group(function () {
|
||||
Route::post('/{voucherEvent}/redeem', [VoucherEventController::class, 'redeem'])->name('voucher-events.redeem');
|
||||
});
|
||||
|
|
@ -132,6 +137,15 @@ Route::middleware(['auth'])->prefix('/orders')->group(function () {
|
|||
Route::get('/', [OrderController::class, 'index'])->name('orders');
|
||||
|
||||
});
|
||||
|
||||
Route::middleware(['auth'])->prefix('/account/wishlist')->group(function () {
|
||||
|
||||
Route::get('/', [WishController::class, 'index'])->name('wishlist.index');
|
||||
Route::post('/', [WishController::class, 'store'])->name('wishlist.store');
|
||||
Route::delete('/{id}', [WishController::class, 'destroy'])->name('wishlist.destroy');
|
||||
|
||||
});
|
||||
|
||||
Route::get('/terms-and-conditions', [TncController::class, 'index'])->name('terms-and-conditions');
|
||||
Route::get('/help', [HelpController::class, 'index'])->name('help');
|
||||
Route::get('/contact', [ContactController::class, 'index'])->name('contact');
|
||||
|
|
|
|||
Loading…
Reference in New Issue