summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIT Fixcomart <it@fixcomart.co.id>2022-11-11 18:02:11 +0700
committerIT Fixcomart <it@fixcomart.co.id>2022-11-11 18:02:11 +0700
commit8ee5432961a5b73e8e5c42af2eda05621723c9e7 (patch)
tree12b7e0628328f6f10f03b464ad0717a729e2ede0
parentdf04a3504e61f3c1257b5a46310e39c51bf23bea (diff)
Connect to solr (search product), header component with title, fix product card layout, show product search result
-rw-r--r--next.config.js3
-rw-r--r--src/components/Header.js32
-rw-r--r--src/components/ProductCard.js18
-rw-r--r--src/helpers/apiOdoo.js4
-rw-r--r--src/helpers/slug.js2
-rw-r--r--src/images/logo.png (renamed from public/images/logo.png)bin49879 -> 49879 bytes
-rw-r--r--src/images/page-not-found.pngbin0 -> 42280 bytes
-rw-r--r--src/pages/404.js24
-rw-r--r--src/pages/api/shop/search.js71
-rw-r--r--src/pages/index.js2
-rw-r--r--src/pages/shop/product/[slug].js202
-rw-r--r--src/pages/shop/search.js46
-rw-r--r--src/styles/globals.css279
13 files changed, 431 insertions, 252 deletions
diff --git a/next.config.js b/next.config.js
index 00852504..8c6659b9 100644
--- a/next.config.js
+++ b/next.config.js
@@ -12,7 +12,8 @@ const nextConfig = {
return config
},
env: {
- DB_HOST: 'https://erp.indoteknik.com',
+ ODOO_HOST: 'https://erp.indoteknik.com',
+ SOLR_HOST: 'http://34.101.189.218:8983'
},
}
diff --git a/src/components/Header.js b/src/components/Header.js
index 3814ed20..f7607a18 100644
--- a/src/components/Header.js
+++ b/src/components/Header.js
@@ -6,18 +6,29 @@ import MenuIcon from "../icons/menu.svg";
import ChevronRightIcon from "../icons/chevron-right.svg";
import { useState } from "react";
import Head from "next/head";
+import Logo from "../images/logo.png";
+import { useRouter } from "next/router";
-export default function Header() {
+export default function Header({ title }) {
+ const router = useRouter();
+ const { q = '' } = router.query;
+ const [searchQuery, setSearchQuery] = useState(q);
const [isMenuActive, setIsMenuActive] = useState(false);
const openMenu = () => setIsMenuActive(true);
const closeMenu = () => setIsMenuActive(false);
+ const searchSubmit = (e) => {
+ e.preventDefault();
+ router.push(`/shop/search?q=${searchQuery}`);
+ }
+
return (
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+ <title>{title}</title>
</Head>
<div className={isMenuActive ? 'menu-wrapper active' : 'menu-wrapper'}>
<div className="flex gap-x-2 items-center">
@@ -50,7 +61,7 @@ export default function Header() {
<div className="sticky-header">
<div className="flex justify-between items-center">
<Link href="/">
- <Image src="/images/logo.png" alt="Logo Indoteknik" width={120} height={40} />
+ <Image src={Logo} alt="Logo Indoteknik" width={120} height={40} className="w-auto" />
</Link>
<div className="flex gap-4">
<Link href="/shop/cart">
@@ -61,9 +72,20 @@ export default function Header() {
</button>
</div>
</div>
- <form action="" method="GET" className="flex mt-2">
- <input type="text" name="product_name" className="form-input rounded-r-none border-r-0 focus:outline-none" placeholder="Ketikan nama, merek, part number"/>
- <button type="submit" aria-label="search" className="btn-light bg-transparent px-2 py-1 rounded-l-none border-l-0">
+ <form onSubmit={searchSubmit} className="flex mt-2">
+ <input
+ type="text"
+ name="q"
+ onChange={(e) => setSearchQuery(e.target.value)}
+ value={searchQuery}
+ className="form-input rounded-r-none border-r-0 focus:outline-none"
+ placeholder="Ketikan nama, merek, part number"
+ />
+ <button
+ type="submit"
+ aria-label="search"
+ className="btn-light bg-transparent px-2 py-1 rounded-l-none border-l-0"
+ >
<SearchIcon />
</button>
</form>
diff --git a/src/components/ProductCard.js b/src/components/ProductCard.js
index 360e11ff..49bba235 100644
--- a/src/components/ProductCard.js
+++ b/src/components/ProductCard.js
@@ -24,19 +24,25 @@ export default function ProductCard({ data }) {
{product.name}
</Link>
</div>
- <div>
+ <div className="flex-1 mt-2">
{product.lowest_price.discount_percentage > 0 ? (
- <div className="flex gap-x-1 items-center mt-2">
+ <div className="flex gap-x-1 items-center">
<span className="badge-yellow">{product.lowest_price.discount_percentage}%</span>
<p className="text-xs text-gray-800 line-through">{currencyFormat(product.lowest_price.price)}</p>
</div>
) : ''}
- <p className="text-sm text-gray-900 font-semibold">{product.lowest_price.price_discount > 0 ? currencyFormat(product.lowest_price.price_discount) : 'Tanya harga'}</p>
- {product.stock_total > 0 ? (
- <div className="badge-yellow bg-green-200 text-green-700 w-fit mt-2">Ready Stock</div>
+ {product.lowest_price.price_discount > 0 ? (
+ <p className="text-sm text-gray-900 font-semibold">
+ {currencyFormat(product.lowest_price.price_discount)}
+ </p>
) : (
- <div className="h-6"></div>
+ <a href="https://wa.me" className="py-2 rounded block text-sm border border-yellow-900 text-center">
+ Tanya Harga
+ </a>
)}
+ {product.stock_total > 0 ? (
+ <div className="badge-yellow bg-green-200 text-green-700 w-fit mt-2">Ready Stock</div>
+ ) : ''}
</div>
</div>
</div>
diff --git a/src/helpers/apiOdoo.js b/src/helpers/apiOdoo.js
index aff27bc3..b1e4ce6b 100644
--- a/src/helpers/apiOdoo.js
+++ b/src/helpers/apiOdoo.js
@@ -3,7 +3,7 @@ import { getCookie, setCookie } from 'cookies-next';
const axios = require('axios');
const renewToken = async () => {
- let res = await axios.get(process.env.DB_HOST + '/api/token');
+ let res = await axios.get(process.env.ODOO_HOST + '/api/token');
setCookie('token', res.data.result);
return res.data.result;
};
@@ -17,7 +17,7 @@ const getToken = async () => {
const getOdoo = async (url) => {
try {
let token = await getToken();
- let res = await axios.get(process.env.DB_HOST + url, {headers: {Authorization: token}});
+ let res = await axios.get(process.env.ODOO_HOST + url, {headers: {Authorization: token}});
if (res.data.status.code == 401) {
await renewToken();
return getOdoo(url);
diff --git a/src/helpers/slug.js b/src/helpers/slug.js
index 5a8db08f..ed1a5b6a 100644
--- a/src/helpers/slug.js
+++ b/src/helpers/slug.js
@@ -1,5 +1,5 @@
const createSlug = (name, id) => {
- return name.trim().replace(new RegExp(/[^A-Za-z0-9]/, 'g'), '-').toLowerCase() + '-' + id;
+ return name?.trim().replace(new RegExp(/[^A-Za-z0-9]/, 'g'), '-').toLowerCase() + '-' + id;
}
const getId = (slug) => {
diff --git a/public/images/logo.png b/src/images/logo.png
index 87c696aa..87c696aa 100644
--- a/public/images/logo.png
+++ b/src/images/logo.png
Binary files differ
diff --git a/src/images/page-not-found.png b/src/images/page-not-found.png
new file mode 100644
index 00000000..296c0443
--- /dev/null
+++ b/src/images/page-not-found.png
Binary files differ
diff --git a/src/pages/404.js b/src/pages/404.js
new file mode 100644
index 00000000..83993f61
--- /dev/null
+++ b/src/pages/404.js
@@ -0,0 +1,24 @@
+import Image from "next/image";
+import Link from "next/link";
+import Header from "../components/Header";
+import PageNotFoundImage from "../images/page-not-found.png";
+
+export default function PageNotFound() {
+ return (
+ <>
+ <Header title="Halaman Tidak Ditemukan - Indoteknik" />
+ <main>
+ <Image src={PageNotFoundImage} alt="Halaman Tidak Ditemukan - Indoteknik" className="w-full" />
+ <p className="mt-3 h1 text-center">Halaman tidak ditemukan</p>
+ <div className="mt-10 text-center">
+ <Link href="/" className="btn-primary">
+ Kembali ke beranda
+ </Link>
+ <a href="https://send.whatsapp.com" className="block mt-6">
+ Tanya admin
+ </a>
+ </div>
+ </main>
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js
new file mode 100644
index 00000000..3c3a1766
--- /dev/null
+++ b/src/pages/api/shop/search.js
@@ -0,0 +1,71 @@
+import axios from "axios";
+
+const productResponseMap = (products) => {
+ return products.map((product) => {
+ let productMapped = {
+ id: product.product_id ? product.product_id[0] : '',
+ image: product.image ? product.image[0] : '',
+ code: product.default_code ? product.default_code[0] : '',
+ name: product.product_name ? product.product_name[0] : '',
+ lowest_price: {
+ price: product.price ? product.price[0] : 0,
+ price_discount: product.price_discount ? product.price_discount[0] : 0,
+ discount_percentage: product.discount ? product.discount[0] : 0,
+ },
+ variant_total: product.variant_total ? product.variant_total[0] : 0,
+ stock_total: product.stock_total ? product.stock_total[0] : 0,
+ weight: product.weight ? product.weight[0] : 0,
+ manufacture: {},
+ categories: [],
+ };
+
+ if (product.manufacture_id && product.brand) {
+ productMapped.manufacture = {
+ id: product.manufacture_id ? product.manufacture_id[0] : '',
+ name: product.brand ? product.brand[0] : '',
+ };
+ }
+
+ productMapped.categories = [
+ {
+ id: product.category_id ? product.category_id[0] : '',
+ name: product.category_name ? product.category_name[0] : '',
+ }
+ ];
+
+ return productMapped;
+ });
+}
+
+export default async function handler(req, res) {
+ const {
+ q,
+ page = 1
+ } = req.query;
+
+ let limit = 30;
+ let offset = (page - 1) * limit;
+
+ let parameter = [
+ `facet.query=${q}`,
+ 'facet=true',
+ 'indent=true',
+ 'q.op=AND',
+ `q=${q}`,
+ 'facet.field=brand_str',
+ 'facet.field=category_name_str',
+ `start=${offset}`,
+ `rows=${limit}`,
+ ].join('&');
+
+ let result = await axios(process.env.SOLR_HOST + '/solr/products/select?' + parameter);
+ try {
+ result.data.response.products = productResponseMap(result.data.response.docs);
+ result.data.responseHeader.params.start = parseInt(result.data.responseHeader.params.start);
+ result.data.responseHeader.params.rows = parseInt(result.data.responseHeader.params.rows);
+ delete result.data.response.docs;
+ res.status(200).json(result.data);
+ } catch (error) {
+ res.status(400).json({ error: error.message });
+ }
+} \ No newline at end of file
diff --git a/src/pages/index.js b/src/pages/index.js
index 40b36dc2..9d26d8ee 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -44,7 +44,7 @@ export default function Home() {
return (
<>
- <Header />
+ <Header title="Home - Indoteknik" />
<Swiper slidesPerView={1} pagination={{dynamicBullets: true}} modules={[Pagination, Autoplay]}>
{
heroBanners?.map((banner, index) => (
diff --git a/src/pages/shop/product/[slug].js b/src/pages/shop/product/[slug].js
index f50d5037..e601d8c0 100644
--- a/src/pages/shop/product/[slug].js
+++ b/src/pages/shop/product/[slug].js
@@ -1,17 +1,13 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
-import ProductCard from "../../../components/ProductCard";
import Header from "../../../components/Header";
import { getOdoo } from "../../../helpers/apiOdoo";
import { createSlug, getId } from "../../../helpers/slug";
import currencyFormat from "../../../helpers/currencyFormat";
-import Head from "next/head";
-import { Swiper, SwiperSlide } from "swiper/react";
import { LazyLoadImage } from "react-lazy-load-image-component";
-import ImagePlaceholderIcon from "../../../icons/image-placeholder.svg";
-import "swiper/css";
import "react-lazy-load-image-component/src/effects/blur.css";
+import ProductSlider from "../../../components/product/ProductSlider";
export async function getServerSideProps(context) {
const { slug } = context.query;
@@ -83,126 +79,98 @@ export default function ProductDetail({product}) {
return (
<>
- <Head>
- <title>{product.name + '- Indoteknik'}</title>
- </Head>
- <Header />
- <div className="px-4 pt-5 pb-10">
- <LazyLoadImage effect="blur" src={product.image} alt={product.name} className="border border-gray-300 rounded-md mb-4 w-full h-[300px] object-contain object-center" />
- <Link href={'/shop/brands/' + createSlug(product.manufacture.name, product.manufacture.id)}>
- {product.manufacture.name || '-'}
- </Link>
- <h1 className="mb-3">{product.name}{activeVariant.attributes != '' ? ' - ' + activeVariant.attributes : ''}</h1>
-
- {product.variant_total > 1 && selectedVariant == "" ? (
- <p className="text-xs text-gray-800 mb-1">Harga mulai dari:</p>
- ) : ''}
-
- {product.lowest_price.discount_percentage > 0 ? (
- <div className="flex gap-x-1 items-center">
- <span className="badge-yellow">{activeVariant.price.discount_percentage}%</span>
- <p className="text-xs text-gray-800 line-through">{currencyFormat(activeVariant.price.price)}</p>
- </div>
- ) : ''}
-
- {product.lowest_price.price > 0 ? (
- <p className="text-lg text-gray-900 font-semibold">{currencyFormat(activeVariant.price.price_discount)}</p>
- ) : (
- <p className="text-gray-800">Dapatkan harga terbaik, <a href="">hubungi kami.</a></p>
- )}
-
- <div className="flex gap-x-2 mt-5">
- <div className="w-9/12">
- <label className="form-label">Pilih: <span className="text-gray-800">{product.variant_total} Varian</span></label>
- <select name="variant" className="form-input" value={selectedVariant} onChange={onchangeVariant} >
- <option value="" disabled={selectedVariant != "" ? true : false}>Pilih Varian...</option>
- {product.variants.length > 1 ? (
- product.variants.map((variant) => {
- return (
- <option key={variant.id} value={variant.id}>{variant.attributes.join(', ')}</option>
- );
- })
- ) : (
- <option key={product.variants[0].id} value={product.variants[0].id}>{product.variants[0].name}</option>
- )}
- </select>
- </div>
- <div className="w-3/12">
- <label htmlFor="quantity" className="form-label">Jumlah</label>
- <input type="number" name="quantity" id="quantity" className="form-input text-center is-invalid" value={quantity} onChange={(e) => setQuantity(e.target.value)} />
- </div>
- </div>
-
- <div className="flex gap-x-2 mt-2">
- <button className="btn-light w-full" >+ Quotation</button>
- <button className="btn-primary w-full" onClick={addToCart} disabled={(product.lowest_price.price == 0 ? true : false)}>+ Keranjang</button>
- </div>
+ <Header title={`${product.name} - Indoteknik`}/>
+ <main>
+
+ <LazyLoadImage effect="blur" src={product.image} alt={product.name} className="border-b border-gray-300 w-full h-[300px] object-contain object-center" />
+ <div className="p-4 pb-10">
+ <Link href={'/shop/brands/' + createSlug(product.manufacture.name, product.manufacture.id)}>
+ {product.manufacture.name ?? '-'}
+ </Link>
+ <h1 className="mb-3">{product.name}{activeVariant.attributes ? ' - ' + activeVariant.attributes : ''}</h1>
+
+ {product.variant_total > 1 && !selectedVariant ? (
+ <p className="text-xs text-gray-800 mb-1">Harga mulai dari:</p>
+ ) : ''}
- <div className="mt-10">
- <h2 className="h1 mb-2">Detail Produk</h2>
- <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
- <h3 className="text-gray-900">Jumlah Varian</h3>
- <p className="text-gray-800">{product.variant_total} Varian</p>
- </div>
- <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
- <h3 className="text-gray-900">Nomor SKU</h3>
- <p className="text-gray-800" id="sku_number">SKU-{activeVariant.id}</p>
+ {product.lowest_price.discount_percentage > 0 ? (
+ <div className="flex gap-x-1 items-center">
+ <span className="badge-yellow">{activeVariant.price.discount_percentage}%</span>
+ <p className="text-xs text-gray-800 line-through">{currencyFormat(activeVariant.price.price)}</p>
+ </div>
+ ) : ''}
+
+ {product.lowest_price.price > 0 ? (
+ <p className="text-lg text-gray-900 font-semibold">{currencyFormat(activeVariant.price.price_discount)}</p>
+ ) : (
+ <p className="text-gray-800">Dapatkan harga terbaik, <a href="">hubungi kami.</a></p>
+ )}
+
+ <div className="flex gap-x-2 mt-5">
+ <div className="w-9/12">
+ <label className="form-label">Pilih: <span className="text-gray-800">{product.variant_total} Varian</span></label>
+ <select name="variant" className="form-input" value={selectedVariant} onChange={onchangeVariant} >
+ <option value="" disabled={selectedVariant != "" ? true : false}>Pilih Varian...</option>
+ {product.variants.length > 1 ? (
+ product.variants.map((variant) => {
+ return (
+ <option key={variant.id} value={variant.id}>{variant.attributes.join(', ')}</option>
+ );
+ })
+ ) : (
+ <option key={product.variants[0].id} value={product.variants[0].id}>{product.variants[0].name}</option>
+ )}
+ </select>
+ </div>
+ <div className="w-3/12">
+ <label htmlFor="quantity" className="form-label">Jumlah</label>
+ <input type="number" name="quantity" id="quantity" className="form-input text-center is-invalid" value={quantity} onChange={(e) => setQuantity(e.target.value)} />
+ </div>
</div>
- <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
- <h3 className="text-gray-900">Part Number</h3>
- <p className="text-gray-800" id="part_number">{activeVariant.code}</p>
+
+ <div className="flex gap-x-2 mt-2">
+ <button className="btn-light w-full" >+ Quotation</button>
+ <button className="btn-primary w-full" onClick={addToCart} disabled={(product.lowest_price.price == 0 ? true : false)}>+ Keranjang</button>
</div>
- <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
- <h3 className="text-gray-900">Stok</h3>
- <p className="text-gray-800" id="stock">
- {activeVariant.stock > 0 ? (activeVariant.stock > 5 ? 'Lebih dari 5' : 'Kurang dari 5') : '0'}
- </p>
+
+ <div className="mt-10">
+ <h2 className="font-bold mb-2">Detail Produk</h2>
+ <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
+ <h3 className="text-gray-900">Jumlah Varian</h3>
+ <p className="text-gray-800">{product.variant_total} Varian</p>
+ </div>
+ <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
+ <h3 className="text-gray-900">Nomor SKU</h3>
+ <p className="text-gray-800" id="sku_number">SKU-{activeVariant.id}</p>
+ </div>
+ <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
+ <h3 className="text-gray-900">Part Number</h3>
+ <p className="text-gray-800" id="part_number">{activeVariant.code}</p>
+ </div>
+ <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
+ <h3 className="text-gray-900">Stok</h3>
+ <p className="text-gray-800" id="stock">
+ {activeVariant.stock > 0 ? (activeVariant.stock > 5 ? 'Lebih dari 5' : 'Kurang dari 5') : '0'}
+ </p>
+ </div>
+ <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
+ <h3 className="text-gray-900">Berat Barang</h3>
+ <p className="text-gray-800" id="weight">{activeVariant.weight > 0 ? activeVariant.weight : '1'} KG</p>
+ </div>
</div>
- <div className="flex py-2 justify-between items-center gap-x-1 border-b border-gray-300">
- <h3 className="text-gray-900">Berat Barang</h3>
- <p className="text-gray-800" id="weight">{activeVariant.weight > 0 ? activeVariant.weight : '1'} KG</p>
+
+ <div className="mt-10">
+ <h2 className="font-bold mb-4">Deskripsi Produk</h2>
+ <div className="text-gray-800 leading-7" dangerouslySetInnerHTML={{__html: (product.description.trim() != '' ? product.description.replaceAll(/<*b>/g, '') : 'Belum ada deskripsi produk.')}}></div>
</div>
- </div>
- <div className="mt-10">
- <h2 className="h1 mb-4">Deskripsi Produk</h2>
- <div className="text-gray-800 leading-7" dangerouslySetInnerHTML={{__html: (product.description.trim() != '' ? product.description.replaceAll(/<*b>/g, '') : 'Belum ada deskripsi produk.')}}></div>
- </div>
+ <div className="mt-10">
+ <h2 className="h1 mb-4">Produk Lainnya</h2>
+ <ProductSlider products={similarProducts}/>
+ </div>
- <div className="mt-10">
- <h2 className="h1 mb-4">Produk Lainnya</h2>
- <Swiper freeMode={true} slidesPerView={2.15} spaceBetween={8} loop={true}>
- {similarProducts?.products?.map((product, index) => (<SwiperSlide key={index}><ProductCard data={product} /></SwiperSlide>))}
- </Swiper>
- {similarProducts == null ? (
- <div className="grid grid-cols-2 gap-x-4">
- <div role="status" className="p-4 max-w-sm rounded border border-gray-300 shadow animate-pulse md:p-6">
- <div className="flex justify-center items-center mb-4 h-48 bg-gray-300 rounded">
- <ImagePlaceholderIcon className="w-12 h-12 text-gray-200" />
- </div>
- <div className="h-2 bg-gray-200 rounded-full w-10 mb-1"></div>
- <div className="h-2.5 bg-gray-200 rounded-full w-full mb-4"></div>
- <div className="h-2 bg-gray-200 rounded-full mb-2.5"></div>
- <div className="h-2 bg-gray-200 rounded-full mb-2.5"></div>
- <div className="h-2 bg-gray-200 rounded-full"></div>
- <span className="sr-only">Loading...</span>
- </div>
- <div role="status" className="p-4 max-w-sm rounded border border-gray-300 shadow animate-pulse md:p-6">
- <div className="flex justify-center items-center mb-4 h-48 bg-gray-300 rounded">
- <ImagePlaceholderIcon className="w-12 h-12 text-gray-200" />
- </div>
- <div className="h-2 bg-gray-200 rounded-full w-10 mb-1"></div>
- <div className="h-2.5 bg-gray-200 rounded-full w-full mb-4"></div>
- <div className="h-2 bg-gray-200 rounded-full mb-2.5"></div>
- <div className="h-2 bg-gray-200 rounded-full mb-2.5"></div>
- <div className="h-2 bg-gray-200 rounded-full"></div>
- <span className="sr-only">Loading...</span>
- </div>
- </div>
- ) : ''}
</div>
-
- </div>
+ </main>
</>
);
} \ No newline at end of file
diff --git a/src/pages/shop/search.js b/src/pages/shop/search.js
new file mode 100644
index 00000000..6e6839be
--- /dev/null
+++ b/src/pages/shop/search.js
@@ -0,0 +1,46 @@
+import axios from "axios";
+import Link from "next/link";
+import { useEffect, useState } from "react";
+import Header from "../../components/Header";
+import ProductCard from "../../components/ProductCard";
+
+export async function getServerSideProps(context) {
+ const { q, page = 1 } = context.query;
+ let searchResults = await axios(`http://${context.req.headers.host}/api/shop/search?q=${q}&page=${page}`);
+ searchResults = searchResults.data;
+ return { props: { searchResults, q } };
+}
+
+export default function ShopSearch({ searchResults, q, page = 1 }) {
+ const pageTotal = Math.ceil(searchResults.response.numFound / searchResults.responseHeader.params.rows);
+
+ return (
+ <>
+ <Header title={`Jual ${q} - Indoteknik`} />
+ <main>
+ <div className="p-4">
+ <h1 className="mb-1">Produk</h1>
+ <div className="text-xs mb-4">
+ Menampilkan&nbsp;
+ {searchResults.responseHeader.params.start + 1}-{searchResults.responseHeader.params.start + searchResults.responseHeader.params.rows}
+ &nbsp;dari&nbsp;
+ {searchResults.response.numFound}
+ &nbsp;produk untuk pencarian {q}
+ </div>
+ <div className="grid grid-cols-2 gap-3">
+ {searchResults.response.products.map((product) => (
+ <ProductCard key={product.id} data={product} />
+ ))}
+ </div>
+ <div className="flex justify-center gap-x-1 mt-4">
+ {[...Array(pageTotal)].map((v, i) => (
+ <Link href={`/shop/search?q=${q}&page=${i+1}`} key={i} className={"p-1 px-2 rounded ease-linear duration-150 " + (page == (i + 1) ? "bg-yellow-900 text-white" : "bg-gray-100 hover:bg-gray-300 text-gray-900")}>
+ {i + 1}
+ </Link>
+ ))}
+ </div>
+ </div>
+ </main>
+ </>
+ )
+} \ No newline at end of file
diff --git a/src/styles/globals.css b/src/styles/globals.css
index e88d14aa..d0080dda 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -5,115 +5,146 @@
@tailwind utilities;
html, body {
- @apply w-screen;
- @apply text-base;
- @apply text-gray-900;
+ @apply
+ w-screen
+ text-base
+ text-gray-900
+ ;
}
@layer base {
h1, .h1 {
- @apply text-lg;
- @apply font-semibold;
- @apply text-gray-900;
+ @apply
+ text-lg
+ font-semibold
+ text-gray-900
+ ;
}
a {
- @apply font-medium;
- @apply text-yellow-900;
+ @apply
+ font-medium
+ text-yellow-900
+ ;
}
}
@layer components {
.badge-red {
- @apply text-xs;
- @apply font-medium;
- @apply px-1;
- @apply py-0.5;
- @apply rounded;
- @apply bg-red-300;
- @apply text-red-900;
+ @apply
+ text-xs
+ font-medium
+ px-1
+ py-0.5
+ rounded
+ bg-red-300
+ text-red-900
+ ;
}
.badge-yellow {
- @apply text-xs;
- @apply font-medium;
- @apply px-1;
- @apply py-0.5;
- @apply rounded;
- @apply bg-yellow-300;
- @apply text-yellow-900;
+ @apply
+ text-xs
+ font-medium
+ px-1
+ py-0.5
+ rounded
+ bg-yellow-300
+ text-yellow-900
+ ;
}
.form-label {
- @apply text-sm;
- @apply mb-1;
- @apply block;
+ @apply
+ text-sm
+ mb-1
+ block
+ ;
}
.form-input {
- @apply p-3;
- @apply rounded;
- @apply border;
- @apply border-gray-300;
- @apply bg-transparent;
- @apply w-full;
- @apply leading-none;
+ @apply
+ p-3
+ rounded
+ border
+ border-gray-300
+ bg-transparent
+ w-full
+ leading-none
+ ;
}
.btn-primary {
- @apply p-3;
- @apply bg-yellow-900;
- @apply border-yellow-900;
- @apply rounded;
- @apply border;
- @apply text-white;
- @apply text-center;
- @apply disabled:bg-yellow-700;
- @apply disabled:border-yellow-700;
+ @apply
+ block
+ w-fit
+ p-3
+ bg-yellow-900
+ border-yellow-900
+ rounded
+ border
+ text-white
+ text-center
+ disabled:bg-yellow-700
+ disabled:border-yellow-700
+ ;
}
.btn-light {
- @apply p-3;
- @apply bg-gray-100;
- @apply border-gray-300;
- @apply rounded;
- @apply border;
- @apply text-gray-900;
- @apply text-center;
+ @apply
+ block
+ w-fit
+ p-3
+ bg-gray-100
+ border-gray-300
+ rounded
+ border
+ text-gray-900
+ text-center
+ ;
}
.product-card {
- @apply w-full;
- @apply h-full;
- @apply border;
- @apply border-gray-300;
- @apply rounded;
- @apply relative;
- @apply flex;
- @apply flex-col;
+ @apply
+ w-full
+ h-full
+ border
+ border-gray-300
+ rounded
+ relative
+ flex
+ flex-col
+ ;
}
.product-card__image {
- @apply w-full;
- @apply h-[160px];
- @apply object-contain;
- @apply object-center;
- @apply border-b;
- @apply border-gray-300;
+ @apply
+ w-full
+ h-[160px]
+ object-contain
+ object-center
+ border-b
+ border-gray-300
+ ;
}
.product-card__description {
- @apply p-2;
- @apply pb-3;
- @apply flex;
- @apply flex-col;
- @apply flex-1;
- @apply justify-between;
+ @apply
+ p-2
+ pb-3
+ flex
+ flex-col
+ flex-1
+ justify-between
+ ;
}
.product-card__title {
- @apply text-sm;
- @apply text-gray-900;
+ @apply
+ text-sm
+ text-gray-900
+ h-16
+ ;
}
.product-card__brand {
@@ -140,19 +171,21 @@ html, body {
}
.menu-wrapper {
- @apply fixed;
- @apply top-0;
- @apply left-0;
- @apply bg-white;
- @apply w-[85%];
- @apply h-full;
- @apply z-[60];
- @apply py-4;
- @apply px-4;
- @apply overflow-y-auto;
- @apply translate-x-[-100%];
- @apply ease-linear;
- @apply duration-150;
+ @apply
+ fixed
+ top-0
+ left-0
+ bg-white
+ w-[85%]
+ h-full
+ z-[60]
+ py-4
+ px-4
+ overflow-y-auto
+ translate-x-[-100%]
+ ease-linear
+ duration-150
+ ;
}
.menu-wrapper.active{
@@ -160,51 +193,59 @@ html, body {
}
.menu-overlay {
- @apply fixed;
- @apply top-0;
- @apply left-0;
- @apply w-full;
- @apply h-full;
- @apply z-[55];
- @apply bg-gray-900/60;
+ @apply
+ fixed
+ top-0
+ left-0
+ w-full
+ h-full
+ z-[55]
+ bg-gray-900/60
+ ;
}
.sticky-header {
- @apply px-4;
- @apply py-3;
- @apply bg-white;
- @apply border-b;
- @apply sticky;
- @apply top-0;
- @apply shadow;
- @apply z-50;
+ @apply
+ px-4
+ py-3
+ bg-white
+ border-b
+ sticky
+ top-0
+ shadow
+ z-50
+ ;
}
.content-container {
- @apply max-w-full;
- @apply overflow-x-hidden;
+ @apply
+ max-w-full
+ overflow-x-hidden
+ ;
}
#indoteknik_toast {
- @apply fixed;
- @apply bottom-4;
- @apply translate-y-[200%];
- @apply left-[50%];
- @apply translate-x-[-50%];
- @apply z-[100];
- @apply flex;
- @apply items-center;
- @apply p-4;
- @apply mb-4;
- @apply w-[90%];
- @apply text-gray-500;
- @apply bg-white;
- @apply border;
- @apply border-gray-300;
- @apply rounded-lg;
- @apply shadow;
- @apply ease-linear;
- @apply duration-300;
+ @apply
+ fixed
+ bottom-4
+ translate-y-[200%]
+ left-[50%]
+ translate-x-[-50%]
+ z-[100]
+ flex
+ items-center
+ p-4
+ mb-4
+ w-[90%]
+ text-gray-500
+ bg-white
+ border
+ border-gray-300
+ rounded-lg
+ shadow
+ ease-linear
+ duration-300
+ ;
}
#indoteknik_toast.active {