diff options
| -rw-r--r-- | next.config.js | 17 | ||||
| -rw-r--r-- | package.json | 5 | ||||
| -rw-r--r-- | pages/api/hello.js | 5 | ||||
| -rw-r--r-- | pages/index.js | 69 | ||||
| -rw-r--r-- | public/images/logo.png | bin | 0 -> 49879 bytes | |||
| -rw-r--r-- | src/components/header.js | 68 | ||||
| -rw-r--r-- | src/components/productCard.js | 35 | ||||
| -rw-r--r-- | src/helpers/apiOdoo.js | 21 | ||||
| -rw-r--r-- | src/helpers/currencyFormat.js | 8 | ||||
| -rw-r--r-- | src/helpers/slug.js | 13 | ||||
| -rw-r--r-- | src/icons/menu.svg | 5 | ||||
| -rw-r--r-- | src/icons/search.svg | 4 | ||||
| -rw-r--r-- | src/icons/shopping-cart.svg | 12 | ||||
| -rw-r--r-- | src/pages/_app.js (renamed from pages/_app.js) | 0 | ||||
| -rw-r--r-- | src/pages/index.js | 12 | ||||
| -rw-r--r-- | src/pages/shop/product/[slug].js | 172 | ||||
| -rw-r--r-- | src/styles/globals.css | 220 | ||||
| -rw-r--r-- | styles/Home.module.css | 129 | ||||
| -rw-r--r-- | styles/globals.css | 26 | ||||
| -rw-r--r-- | tailwind.config.js | 83 |
20 files changed, 672 insertions, 232 deletions
diff --git a/next.config.js b/next.config.js index ae887958..8a019dcc 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,23 @@ const nextConfig = { reactStrictMode: true, swcMinify: true, + webpack(config) { + config.module.rules.push({ + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }) + + return config + }, + env: { + DB_HOST: 'https://erp.indoteknik.com', + DB_NAME: 'erp_indoteknik', + // DB_HOST: 'http://192.168.27.253:8069', + // DB_NAME: 'indoteknik_odoo', + DB_USER: 'it@fixcomart.co.id', + DB_PASS: 'Fixcomart378', + }, } module.exports = nextConfig diff --git a/package.json b/package.json index e280d3e9..b64a34d7 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,14 @@ "lint": "next lint" }, "dependencies": { + "axios": "^1.1.3", "next": "13.0.0", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "swiper": "^8.4.4" }, "devDependencies": { + "@svgr/webpack": "^6.5.0", "autoprefixer": "^10.4.12", "eslint": "8.26.0", "eslint-config-next": "13.0.0", diff --git a/pages/api/hello.js b/pages/api/hello.js deleted file mode 100644 index df63de88..00000000 --- a/pages/api/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction - -export default function handler(req, res) { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/pages/index.js b/pages/index.js deleted file mode 100644 index dc4b6403..00000000 --- a/pages/index.js +++ /dev/null @@ -1,69 +0,0 @@ -import Head from 'next/head' -import Image from 'next/image' -import styles from '../styles/Home.module.css' - -export default function Home() { - return ( - <div className={styles.container}> - <Head> - <title>Create Next App</title> - <meta name="description" content="Generated by create next app" /> - <link rel="icon" href="/favicon.ico" /> - </Head> - - <main className={styles.main}> - <h1 className={styles.title}> - Welcome to <a href="https://nextjs.org">Next.js!</a> - </h1> - - <p className={styles.description}> - Get started by editing{' '} - <code className={styles.code}>pages/index.js</code> - </p> - - <div className={styles.grid}> - <a href="https://nextjs.org/docs" className={styles.card}> - <h2>Documentation →</h2> - <p>Find in-depth information about Next.js features and API.</p> - </a> - - <a href="https://nextjs.org/learn" className={styles.card}> - <h2>Learn →</h2> - <p>Learn about Next.js in an interactive course with quizzes!</p> - </a> - - <a - href="https://github.com/vercel/next.js/tree/canary/examples" - className={styles.card} - > - <h2>Examples →</h2> - <p>Discover and deploy boilerplate example Next.js projects.</p> - </a> - - <a - href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" - className={styles.card} - > - <h2>Deploy →</h2> - <p> - Instantly deploy your Next.js site to a public URL with Vercel. - </p> - </a> - </div> - </main> - - <footer className={styles.footer}> - <a - href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" - target="_blank" - rel="noopener noreferrer" - > - Powered by{' '} - <span className={styles.logo}> - <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} /> - </span> - </a> - </footer> - </div> - ) -} diff --git a/public/images/logo.png b/public/images/logo.png Binary files differnew file mode 100644 index 00000000..87c696aa --- /dev/null +++ b/public/images/logo.png diff --git a/src/components/header.js b/src/components/header.js new file mode 100644 index 00000000..baebbd3a --- /dev/null +++ b/src/components/header.js @@ -0,0 +1,68 @@ +import Image from "next/image"; +import Link from "next/link"; +import ShoppingCartIcon from "../icons/shopping-cart.svg"; +import SearchIcon from "../icons/search.svg"; +import MenuIcon from "../icons/menu.svg"; +import { useState } from "react"; + + +export default function Header() { + const [isMenuActive, setIsMenuActive] = useState(false); + + const openMenu = () => setIsMenuActive(true); + const closeMenu = () => setIsMenuActive(false); + + return ( + <> + <div className={isMenuActive ? 'menu-wrapper active' : 'menu-wrapper'}> + <div className="flex gap-x-2 items-center"> + <Link href="/login" className="w-full py-2 btn-light">Masuk</Link> + <Link href="/register" className="w-full py-2 btn-primary">Daftar</Link> + </div> + <div className="flex flex-col gap-y-4 mt-5"> + <Link className="flex w-full font-normal text-gray-800" href="/shop/brands"> + <span>Brand</span> + <div className="ml-auto"> + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" className="stroke-gray-700 feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg> + </div> + </Link> + <Link className="flex w-full font-normal text-gray-800" href="/blog"> + <span>Blog</span> + <div className="ml-auto"> + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" className="stroke-gray-700 feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg> + </div> + </Link> + <button className="flex w-full font-normal text-gray-800" id="open_category_parent_menu"> + <span>Kategori</span> + <div className="ml-auto"> + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" className="stroke-gray-700 feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg> + </div> + </button> + </div> + </div> + <div className={isMenuActive ? 'menu-overlay block opacity-100' : 'menu-overlay hidden opacity-0'} onClick={closeMenu}></div> + + <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} /> + </Link> + <div className="flex gap-4"> + <Link href="/shop/cart"> + <ShoppingCartIcon className="w-6" /> + </Link> + <button onClick={openMenu}> + <MenuIcon className="w-6" /> + </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"> + <SearchIcon /> + </button> + </form> + </div> + </> + ) +}
\ No newline at end of file diff --git a/src/components/productCard.js b/src/components/productCard.js new file mode 100644 index 00000000..dc292316 --- /dev/null +++ b/src/components/productCard.js @@ -0,0 +1,35 @@ +import Link from "next/link"; +import currencyFormat from "../helpers/currencyFormat"; +import { createSlug } from "../helpers/slug"; + +export default function productCard({ data }) { + let product = data; + return ( + <div className="product-card"> + <Link href={'/shop/product/' + createSlug(product.name, product.id)} className="block"> + <img src={product.image} alt={product.name} className="product-card__image" loading="lazy" /> + </Link> + <div className="product-card__description"> + <div> + {typeof product.manufacture.name !== undefined ? ( + <a href={'/shop/brands/' + createSlug(product.manufacture.name, product.manufacture.id)} className="product-card__brand">{product.manufacture.name}</a> + ) : ( + <span className="product-card__brand">-</span> + )} + <a href={'/shop/product/' + createSlug(product.name, product.id)} className="product-card__title wrap-line-ellipsis-3"> + {product.name} + </a> + </div> + <div> + {product.lowest_price.discount_percentage > 0 ? ( + <div className="flex gap-x-1 items-center mt-2"> + <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> + </div> + </div> + </div> + ) +}
\ No newline at end of file diff --git a/src/helpers/apiOdoo.js b/src/helpers/apiOdoo.js new file mode 100644 index 00000000..632edf61 --- /dev/null +++ b/src/helpers/apiOdoo.js @@ -0,0 +1,21 @@ +const axios = require('axios'); + +const getOdoo = async (url) => { + try { + let res = await axios(process.env.DB_HOST + url, { + headers: { + db: process.env.DB_NAME, + username: process.env.DB_USER, + password: process.env.DB_PASS, + } + }); + + return res.data.result || []; + } catch (error) { + console.log(error); + } +} + +export { + getOdoo, +};
\ No newline at end of file diff --git a/src/helpers/currencyFormat.js b/src/helpers/currencyFormat.js new file mode 100644 index 00000000..dadeaec6 --- /dev/null +++ b/src/helpers/currencyFormat.js @@ -0,0 +1,8 @@ +export default function currencyFormat(value) { + const currency = new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + maximumFractionDigits: 0 + }); + return currency.format(value); +}
\ No newline at end of file diff --git a/src/helpers/slug.js b/src/helpers/slug.js new file mode 100644 index 00000000..5a8db08f --- /dev/null +++ b/src/helpers/slug.js @@ -0,0 +1,13 @@ +const createSlug = (name, id) => { + return name.trim().replace(new RegExp(/[^A-Za-z0-9]/, 'g'), '-').toLowerCase() + '-' + id; +} + +const getId = (slug) => { + let id = slug.split('-'); + return id[id.length-1]; +} + +export { + createSlug, + getId +};
\ No newline at end of file diff --git a/src/icons/menu.svg b/src/icons/menu.svg new file mode 100644 index 00000000..f4ff45ad --- /dev/null +++ b/src/icons/menu.svg @@ -0,0 +1,5 @@ +<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M3.04395 10H18.0439" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M3.04395 5H18.0439" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M3.04395 15H18.0439" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/src/icons/search.svg b/src/icons/search.svg new file mode 100644 index 00000000..6de1cdfa --- /dev/null +++ b/src/icons/search.svg @@ -0,0 +1,4 @@ +<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M9.71061 15.8333C13.3925 15.8333 16.3773 12.8486 16.3773 9.16667C16.3773 5.48477 13.3925 2.5 9.71061 2.5C6.02871 2.5 3.04395 5.48477 3.04395 9.16667C3.04395 12.8486 6.02871 15.8333 9.71061 15.8333Z" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M18.0439 17.5L14.4189 13.875" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/src/icons/shopping-cart.svg b/src/icons/shopping-cart.svg new file mode 100644 index 00000000..5c899876 --- /dev/null +++ b/src/icons/shopping-cart.svg @@ -0,0 +1,12 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_10_13)"> +<path d="M7.50008 18.3334C7.96032 18.3334 8.33341 17.9603 8.33341 17.5C8.33341 17.0398 7.96032 16.6667 7.50008 16.6667C7.03984 16.6667 6.66675 17.0398 6.66675 17.5C6.66675 17.9603 7.03984 18.3334 7.50008 18.3334Z" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M16.6666 18.3334C17.1268 18.3334 17.4999 17.9603 17.4999 17.5C17.4999 17.0398 17.1268 16.6667 16.6666 16.6667C16.2063 16.6667 15.8333 17.0398 15.8333 17.5C15.8333 17.9603 16.2063 18.3334 16.6666 18.3334Z" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M0.833252 0.833313H4.16659L6.39992 11.9916C6.47612 12.3753 6.68484 12.7199 6.98954 12.9652C7.29424 13.2105 7.6755 13.3408 8.06659 13.3333H16.1666C16.5577 13.3408 16.9389 13.2105 17.2436 12.9652C17.5483 12.7199 17.757 12.3753 17.8333 11.9916L19.1666 4.99998H4.99992" stroke="#2B2B2B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</g> +<defs> +<clipPath id="clip0_10_13"> +<rect width="20" height="20" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/pages/_app.js b/src/pages/_app.js index 1e1cec92..1e1cec92 100644 --- a/pages/_app.js +++ b/src/pages/_app.js diff --git a/src/pages/index.js b/src/pages/index.js new file mode 100644 index 00000000..57f96ec9 --- /dev/null +++ b/src/pages/index.js @@ -0,0 +1,12 @@ +import { useEffect, useState } from "react"; +import Header from "../components/header"; + +export default function Home() { + const [product, setProduct] = useState({}); + + return ( + <> + <Header /> + </> + ) +} diff --git a/src/pages/shop/product/[slug].js b/src/pages/shop/product/[slug].js new file mode 100644 index 00000000..519f8160 --- /dev/null +++ b/src/pages/shop/product/[slug].js @@ -0,0 +1,172 @@ +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import Header from "../../../components/header"; +import ProductCard from "../../../components/productCard"; +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 'swiper/css'; + + +export async function getServerSideProps(context) { + const { slug } = context.query; + let product = await getOdoo('/api/v1/product/' + getId(slug)); + product = product[0]; + + const similarProducts = await getOdoo(`/api/v1/product/${getId(slug)}/similar?limit=20`); + + return {props: {product, similarProducts}}; +} + +export default function ProductDetail({product, similarProducts}) { + const router = useRouter(); + const { slug } = router.query; + const [selectedVariant, setSelectedVariant] = useState(""); + const [quantity, setQuantity] = useState("1"); + const [activeVariant, setActiveVariant] = useState({ + id: product.id, + code: product.code, + price: product.lowest_price, + stock: product.stock_total, + weight: product.weight, + attributes: '', + }); + + useEffect(() => { + if (product.variants.length == 1) { + setSelectedVariant(product.variants[0].id); + } + }, [product.variants]); + + useEffect(() => { + if (selectedVariant != '') { + let newActiveVariant = product.variants.filter((variant) => { + return variant.id == selectedVariant; + }); + + if (newActiveVariant.length == 1) { + newActiveVariant = newActiveVariant[0]; + setActiveVariant({ + id: newActiveVariant.id, + code: newActiveVariant.code, + price: newActiveVariant.price, + stock: newActiveVariant.stock, + weight: newActiveVariant.weight, + attributes: newActiveVariant.attributes.join(', '), + }); + } + } + }, [selectedVariant]) + + let onchangeVariant = (e) => { + setSelectedVariant(e.target.value); + setQuantity("1"); + } + + let addToCart = () => { + return true; + } + + return ( + <> + <Head> + <title>{product.name + '- Indoteknik'}</title> + </Head> + <Header /> + <div className="px-4 pt-5 pb-10"> + <img 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> + + <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> + </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="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> + <Swiper freeMode={true} slidesPerView={2.15} spaceBetween={8} loop={true}> + {similarProducts.products.map((product, index) => (<SwiperSlide key={index}><ProductCard data={product} /></SwiperSlide>))} + </Swiper> + </div> + + </div> + </> + ); +}
\ No newline at end of file diff --git a/src/styles/globals.css b/src/styles/globals.css new file mode 100644 index 00000000..763a6d39 --- /dev/null +++ b/src/styles/globals.css @@ -0,0 +1,220 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, body { + @apply max-w-full; + @apply text-base; + @apply text-gray-900; +} + +@layer base { + h1, .h1 { + @apply text-lg; + @apply font-semibold; + @apply text-gray-900; + } + + a { + @apply font-medium; + @apply 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; + } + + .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; + } + + .form-label { + @apply text-sm; + @apply mb-1; + @apply block; + } + + .form-input { + @apply p-3; + @apply rounded; + @apply border; + @apply border-gray-300; + @apply bg-transparent; + @apply w-full; + @apply 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; + } + + .btn-light { + @apply p-3; + @apply bg-gray-100; + @apply border-gray-300; + @apply rounded; + @apply border; + @apply text-gray-900; + @apply 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; + } + + .product-card__image { + @apply w-full; + @apply h-[160px]; + @apply object-contain; + @apply object-center; + @apply border-b; + @apply border-gray-300; + } + + .product-card__description { + @apply p-2; + @apply pb-3; + @apply flex; + @apply flex-col; + @apply flex-1; + @apply justify-between; + } + + .product-card__title { + @apply text-sm; + @apply text-gray-900; + } + + .product-card__brand { + @apply text-sm; + } +} + +@layer utilities { + .wrap-line-ellipsis-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + .wrap-line-ellipsis-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.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; +} + +.menu-wrapper.active{ + @apply translate-x-0; +} + +.menu-overlay { + @apply fixed; + @apply top-0; + @apply left-0; + @apply w-full; + @apply h-full; + @apply z-[55]; + @apply 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; +} + +.content-container { + @apply max-w-full; + @apply 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; +} + +#indoteknik_toast.active { + @apply translate-y-0; +} + +.category-menu { + @apply hidden; +} + +.swiper-slide { + @apply !h-auto; +}
\ No newline at end of file diff --git a/styles/Home.module.css b/styles/Home.module.css deleted file mode 100644 index bd50f42f..00000000 --- a/styles/Home.module.css +++ /dev/null @@ -1,129 +0,0 @@ -.container { - padding: 0 2rem; -} - -.main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; -} - -.footer a { - display: flex; - justify-content: center; - align-items: center; - flex-grow: 1; -} - -.title a { - color: #0070f3; - text-decoration: none; -} - -.title a:hover, -.title a:focus, -.title a:active { - text-decoration: underline; -} - -.title { - margin: 0; - line-height: 1.15; - font-size: 4rem; -} - -.title, -.description { - text-align: center; -} - -.description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; -} - -.code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; -} - -.grid { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - max-width: 800px; -} - -.card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; -} - -.card:hover, -.card:focus, -.card:active { - color: #0070f3; - border-color: #0070f3; -} - -.card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; -} - -.card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; -} - -.logo { - height: 1em; - margin-left: 0.5rem; -} - -@media (max-width: 600px) { - .grid { - width: 100%; - flex-direction: column; - } -} - -@media (prefers-color-scheme: dark) { - .card, - .footer { - border-color: #222; - } - .code { - background: #111; - } - .logo img { - filter: invert(1); - } -} diff --git a/styles/globals.css b/styles/globals.css deleted file mode 100644 index 4f184216..00000000 --- a/styles/globals.css +++ /dev/null @@ -1,26 +0,0 @@ -html, -body { - padding: 0; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; -} - -a { - color: inherit; - text-decoration: none; -} - -* { - box-sizing: border-box; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } - body { - color: white; - background: black; - } -} diff --git a/tailwind.config.js b/tailwind.config.js index 32e3abde..127739a6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,8 +1,87 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: [], + content: [ + "./src/pages/**/*.{js,ts,tsx}", + "./src/components/**/*.{js,ts,tsx}", + ], theme: { - extend: {}, + extend: { + fontFamily: { + 'sans': ['Inter', 'sans-serif'] + }, + fontSize: { + base: '15px', + lg: '18px', + }, + colors: { + gray: { + 900: '#2B2B2B', + 800: '#595959', + 700: '#767676', + 600: '#888888', + 500: '#ABABAB', + 400: '#CCCCCC', + 300: '#E4E4E4', + 200: '#F0F0F0', + 100: '#F6F6F6', + }, + red: { + 900: '#CE1D34', + 800: '#D94F61', + 700: '#DF6D7C', + 600: '#E3808D', + 500: '#ECA6AF', + 400: '#F3C9CE', + 300: '#F9E3E6', + 200: '#FBEFF0', + 100: '#FDF6F7', + }, + yellow: { + 900: '#D7A30A', + 800: '#DFB740', + 700: '#E5C361', + 600: '#E8CB75', + 500: '#EFDB9E', + 400: '#F5E8C2', + 300: '#FAF3E0', + 200: '#FCF8ED', + 100: '#FDFBF5', + }, + // yellow500: { + // 900: '#F8C20A', + // 800: '#FACF40', + // 700: '#FBD761', + // 600: '#FBDC75', + // 500: '#FCE79E', + // 400: '#FDF0C2', + // 300: '#FEF7E0', + // 200: '#FFFBED', + // 100: '#FFFCF5', + // } + // yellow600: { + // 900: '#D7A30A', + // 800: '#DFB740', + // 700: '#E5C361', + // 600: '#E8CB75', + // 500: '#EFDB9E', + // 400: '#F5E8C2', + // 300: '#FAF3E0', + // 200: '#FCF8ED', + // 100: '#FDFBF5', + // } + // yellow700: { + // 900: '#B5860A', + // 800: '#C5A040', + // 700: '#CFB161', + // 600: '#D5BB75', + // 500: '#E2CF9E', + // 400: '#EDE1C2', + // 300: '#F6F0E0', + // 200: '#FAF6ED', + // 100: '#FCFAF5', + // } + } + }, }, plugins: [], } |
