182 lines
No EOL
6.3 KiB
Svelte
182 lines
No EOL
6.3 KiB
Svelte
<script lang="ts">
|
|
import { cart } from '$lib/stores/cart';
|
|
import type { MedusaProduct, MedusaVariant, MedusaOptionValue } from '$lib/types/medusa';
|
|
import { region } from '$lib/stores/cart';
|
|
|
|
export let product: MedusaProduct;
|
|
let selectedVariant: MedusaVariant = product.variants[0];
|
|
let currentImageIndex = 0;
|
|
let cartButton: HTMLDivElement;
|
|
let isSticky = false;
|
|
let selectedOptions = new Map<string, string>();
|
|
|
|
// Initialize selected options from the first variant
|
|
$: if (product.variants.length > 0) {
|
|
selectedOptions = new Map(
|
|
product.variants[0].options.map(opt => [opt.option_id, opt.value])
|
|
);
|
|
}
|
|
|
|
function handleScroll() {
|
|
if (!cartButton) return;
|
|
isSticky = window.scrollY + window.innerHeight < cartButton.offsetTop;
|
|
}
|
|
|
|
function handleOptionChange(optionId: string, value: string) {
|
|
selectedOptions.set(optionId, value);
|
|
|
|
// Find the variant that matches all selected options
|
|
const matchingVariant = product.variants.find(variant =>
|
|
variant.options.every(opt =>
|
|
selectedOptions.get(opt.option_id) === opt.value
|
|
)
|
|
);
|
|
|
|
if (matchingVariant) {
|
|
selectedVariant = matchingVariant;
|
|
}
|
|
}
|
|
|
|
// Get available values for an option based on other selected options
|
|
function getAvailableValues(optionId: string) {
|
|
const otherSelectedOptions = new Map(selectedOptions);
|
|
otherSelectedOptions.delete(optionId);
|
|
|
|
return product.options
|
|
.find(opt => opt.id === optionId)
|
|
?.values.filter(value => {
|
|
// Check if this value is available in any variant with current selections
|
|
return product.variants.some(variant =>
|
|
variant.options.some(opt =>
|
|
opt.option_id === optionId &&
|
|
opt.value === value.value &&
|
|
// Check if other selected options match
|
|
variant.options.every(opt =>
|
|
opt.option_id === optionId ||
|
|
otherSelectedOptions.get(opt.option_id) === opt.value
|
|
)
|
|
)
|
|
);
|
|
}) || [];
|
|
}
|
|
|
|
function getCurrencySymbol(currency_code: string) {
|
|
switch (currency_code?.toLowerCase()) {
|
|
case 'eur':
|
|
return '€';
|
|
case 'usd':
|
|
return '$';
|
|
case 'kwd':
|
|
return 'KWD';
|
|
default:
|
|
return currency_code?.toUpperCase() || '';
|
|
}
|
|
}
|
|
|
|
let currencyCode = $region?.currency_code || 'KWD';
|
|
let currencySymbol = getCurrencySymbol(currencyCode);
|
|
</script>
|
|
|
|
<svelte:window on:scroll={handleScroll}/>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 py-8">
|
|
<!-- Product Images -->
|
|
<div class="relative">
|
|
<div class="aspect-square overflow-hidden rounded-lg bg-base-200">
|
|
<img
|
|
src={product.images[currentImageIndex]?.url || product.thumbnail}
|
|
alt={product.title}
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
{#if product.images.length > 1}
|
|
<div class="grid grid-cols-4 gap-2 mt-2">
|
|
{#each product.images as image, i}
|
|
<button
|
|
class="aspect-square rounded-lg overflow-hidden bg-base-200 hover:opacity-75 transition-opacity"
|
|
class:ring-2={currentImageIndex === i}
|
|
class:ring-primary={currentImageIndex === i}
|
|
on:click={() => currentImageIndex = i}
|
|
>
|
|
<img
|
|
src={image.url}
|
|
alt={`${product.title} - View ${i + 1}`}
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Product Info -->
|
|
<div>
|
|
<h1 class="text-3xl font-bold mb-4">{product.title}</h1>
|
|
<p class="text-xl font-semibold mb-6">{selectedVariant.calculated_price.calculated_amount} {currencySymbol}</p>
|
|
|
|
<div class="prose max-w-none mb-8">
|
|
<p>{product.description}</p>
|
|
</div>
|
|
|
|
<!-- Product Options -->
|
|
{#if product.options.length > 0}
|
|
<div class="space-y-4 mb-8">
|
|
{#each product.options as option}
|
|
<div>
|
|
<label class="block text-sm font-medium mb-2" for={option.id}>
|
|
{option.title}
|
|
</label>
|
|
<select
|
|
id={option.id}
|
|
class="select select-bordered w-full"
|
|
value={selectedOptions.get(option.id)}
|
|
on:change={(e) => handleOptionChange(option.id, (e.target as HTMLSelectElement).value)}
|
|
>
|
|
{#each getAvailableValues(option.id) as value}
|
|
<option value={value.value}>
|
|
{value.value}
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Add to Cart -->
|
|
<div
|
|
class="card bg-base-200 p-4 sticky bottom-2 transition-all duration-300"
|
|
class:sticky-cart={isSticky}
|
|
bind:this={cartButton}
|
|
>
|
|
<button
|
|
class="btn btn-primary w-full"
|
|
on:click={() => cart.add(selectedVariant.id)}
|
|
>
|
|
Add to Cart
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.sticky-cart {
|
|
position: fixed !important;
|
|
bottom: 0.5rem;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 90%;
|
|
padding: 0.5rem;
|
|
z-index: 50;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.sticky-cart {
|
|
position: static !important;
|
|
padding: 0;
|
|
width: 100%;
|
|
transform: none;
|
|
left: auto;
|
|
}
|
|
}
|
|
</style> |