Medusa-svelte/src/lib/components/single-product.svelte
2025-06-13 16:35:00 +03:00

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>