186 lines
5.2 KiB
Vue
186 lines
5.2 KiB
Vue
<template>
|
|
<section class="container-page py-10">
|
|
<RouterLink to="/marketplace" class="mb-6 inline-flex text-sm font-bold text-brand-700">
|
|
← Kembali ke Marketplace
|
|
</RouterLink>
|
|
|
|
<div v-if="loading" class="card p-10 text-center">
|
|
<h1 class="text-2xl font-extrabold">Memuat produk...</h1>
|
|
</div>
|
|
|
|
<div v-else-if="product" class="grid gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(380px,520px)]">
|
|
<div class="grid items-start gap-4 sm:grid-cols-[88px_minmax(0,1fr)]">
|
|
<div class="order-2 flex gap-3 overflow-x-auto sm:order-1 sm:grid sm:content-start sm:max-h-[78vh] sm:overflow-y-auto">
|
|
<button
|
|
v-for="(image, index) in productImages"
|
|
:key="`${image}-${index}`"
|
|
type="button"
|
|
class="h-20 w-20 shrink-0 overflow-hidden rounded-xl border-2 bg-white"
|
|
:class="selectedImage === image ? 'border-brand-600' : 'border-slate-200'"
|
|
@click="selectedImage = image"
|
|
>
|
|
<img
|
|
:src="getProductImageUrl(image)"
|
|
:alt="`${product.title} ${index + 1}`"
|
|
class="h-full w-full object-cover"
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="order-1 sm:order-2">
|
|
<img
|
|
:src="getProductImageUrl(selectedImage)"
|
|
:alt="product.title"
|
|
class="block h-auto max-h-[78vh] w-auto max-w-full rounded-2xl object-contain shadow-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="rounded-full bg-brand-50 px-3 py-1 text-sm font-bold text-brand-700">
|
|
{{ product.category }}
|
|
</span>
|
|
|
|
<h1 class="mt-4 text-3xl font-extrabold text-slate-950">
|
|
{{ product.title }}
|
|
</h1>
|
|
|
|
<p class="mt-3 text-3xl font-extrabold text-brand-700">
|
|
{{ formatRupiah(product.price) }}
|
|
</p>
|
|
|
|
<div class="mt-6 grid gap-3 sm:grid-cols-2">
|
|
<div class="card p-4">
|
|
<p class="text-xs font-bold uppercase text-slate-500">Kondisi</p>
|
|
<p class="mt-1 font-bold">{{ product.condition }}</p>
|
|
</div>
|
|
|
|
<div class="card p-4">
|
|
<p class="text-xs font-bold uppercase text-slate-500">Lokasi</p>
|
|
<p class="mt-1 font-bold">{{ product.location }}</p>
|
|
</div>
|
|
|
|
<div class="card p-4">
|
|
<p class="text-xs font-bold uppercase text-slate-500">Penjual</p>
|
|
<p class="mt-1 font-bold">{{ product.seller }}</p>
|
|
</div>
|
|
|
|
<div class="card p-4">
|
|
<p class="text-xs font-bold uppercase text-slate-500">WhatsApp</p>
|
|
<p class="mt-1 font-bold">{{ product.whatsapp }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6">
|
|
<h2 class="text-lg font-extrabold">Deskripsi</h2>
|
|
<p class="mt-2 leading-7 text-slate-600">
|
|
{{ product.description }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="mt-8 flex flex-wrap gap-3">
|
|
<a :href="waLink" target="_blank" class="btn-primary">
|
|
Hubungi via WhatsApp
|
|
</a>
|
|
|
|
<button class="btn-secondary" @click="toggleSave">
|
|
{{ saved ? "Hapus dari Tersimpan" : "Simpan Barang" }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="card p-10 text-center">
|
|
<h1 class="text-2xl font-extrabold">Produk tidak ditemukan</h1>
|
|
<RouterLink to="/marketplace" class="btn-primary mt-5">
|
|
Lihat Marketplace
|
|
</RouterLink>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, ref, watch } from "vue"
|
|
import { useRoute } from "vue-router"
|
|
import { products as defaultProducts } from "../data/products"
|
|
import {
|
|
fetchProduct,
|
|
formatRupiah,
|
|
getProductImageUrl,
|
|
getSavedProducts,
|
|
setSavedProducts
|
|
} from "../utils"
|
|
|
|
const route = useRoute()
|
|
const id = Number(route.params.id)
|
|
|
|
const product = ref(null)
|
|
const loading = ref(true)
|
|
const selectedImage = ref("")
|
|
|
|
const productImages = computed(() => {
|
|
if (!product.value) return []
|
|
|
|
const images = product.value.images?.length
|
|
? product.value.images
|
|
: [product.value.image]
|
|
|
|
return [...new Set(images.filter(Boolean))]
|
|
})
|
|
|
|
watch(
|
|
productImages,
|
|
(images) => {
|
|
if (!images.includes(selectedImage.value)) {
|
|
selectedImage.value = images[0] || ""
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const savedIds = ref(getSavedProducts())
|
|
|
|
const saved = computed(() => {
|
|
return savedIds.value.includes(id)
|
|
})
|
|
|
|
const waLink = computed(() => {
|
|
if (!product.value) return "#"
|
|
|
|
const text = encodeURIComponent(
|
|
`Halo, saya tertarik dengan ${product.value.title} di SecondTech Market.`
|
|
)
|
|
|
|
return `https://wa.me/${product.value.whatsapp}?text=${text}`
|
|
})
|
|
|
|
function toggleSave() {
|
|
if (saved.value) {
|
|
savedIds.value = savedIds.value.filter((item) => item !== id)
|
|
} else {
|
|
savedIds.value.push(id)
|
|
}
|
|
|
|
setSavedProducts(savedIds.value)
|
|
}
|
|
|
|
async function loadProduct() {
|
|
loading.value = true
|
|
|
|
try {
|
|
const data = await fetchProduct(id)
|
|
product.value = data.product
|
|
} catch {
|
|
const fallbackProduct = defaultProducts.find((item) => Number(item.id) === id) || null
|
|
|
|
product.value = fallbackProduct
|
|
? { ...fallbackProduct, images: [fallbackProduct.image] }
|
|
: null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(loadProduct)
|
|
</script>
|