diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2022-11-11 18:02:11 +0700 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2022-11-11 18:02:11 +0700 |
| commit | 8ee5432961a5b73e8e5c42af2eda05621723c9e7 (patch) | |
| tree | 12b7e0628328f6f10f03b464ad0717a729e2ede0 | |
| parent | df04a3504e61f3c1257b5a46310e39c51bf23bea (diff) | |
Connect to solr (search product), header component with title, fix product card layout, show product search result
| -rw-r--r-- | next.config.js | 3 | ||||
| -rw-r--r-- | src/components/Header.js | 32 | ||||
| -rw-r--r-- | src/components/ProductCard.js | 18 | ||||
| -rw-r--r-- | src/helpers/apiOdoo.js | 4 | ||||
| -rw-r--r-- | src/helpers/slug.js | 2 | ||||
| -rw-r--r-- | src/images/logo.png (renamed from public/images/logo.png) | bin | 49879 -> 49879 bytes | |||
| -rw-r--r-- | src/images/page-not-found.png | bin | 0 -> 42280 bytes | |||
| -rw-r--r-- | src/pages/404.js | 24 | ||||
| -rw-r--r-- | src/pages/api/shop/search.js | 71 | ||||
| -rw-r--r-- | src/pages/index.js | 2 | ||||
| -rw-r--r-- | src/pages/shop/product/[slug].js | 202 | ||||
| -rw-r--r-- | src/pages/shop/search.js | 46 | ||||
| -rw-r--r-- | src/styles/globals.css | 279 |
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 Binary files differindex 87c696aa..87c696aa 100644 --- a/public/images/logo.png +++ b/src/images/logo.png diff --git a/src/images/page-not-found.png b/src/images/page-not-found.png Binary files differnew file mode 100644 index 00000000..296c0443 --- /dev/null +++ b/src/images/page-not-found.png 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 + {searchResults.responseHeader.params.start + 1}-{searchResults.responseHeader.params.start + searchResults.responseHeader.params.rows} + dari + {searchResults.response.numFound} + 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 { |
