diff options
| author | Rafi Zadanly <zadanlyr@gmail.com> | 2023-01-24 15:54:48 +0700 |
|---|---|---|
| committer | Rafi Zadanly <zadanlyr@gmail.com> | 2023-01-24 15:54:48 +0700 |
| commit | ee4297280c1305c7e03bedd4df63ccf136c28c6c (patch) | |
| tree | 62eb00777f42542a37c63687dd1536f8f56df894 /src/components/layouts | |
| parent | 23b31aa10302cc990f3fb083b8189233b2e9e08d (diff) | |
Merapihkan struktur folder
Diffstat (limited to 'src/components/layouts')
| -rw-r--r-- | src/components/layouts/AppBar.js | 59 | ||||
| -rw-r--r-- | src/components/layouts/Footer.js | 69 | ||||
| -rw-r--r-- | src/components/layouts/Header.js | 164 | ||||
| -rw-r--r-- | src/components/layouts/Layout.js | 20 |
4 files changed, 312 insertions, 0 deletions
diff --git a/src/components/layouts/AppBar.js b/src/components/layouts/AppBar.js new file mode 100644 index 00000000..f9dddf9d --- /dev/null +++ b/src/components/layouts/AppBar.js @@ -0,0 +1,59 @@ +import { ChevronLeftIcon, HeartIcon, HomeIcon } from "@heroicons/react/24/outline"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import Link from "../elements/Link"; + +const AppBar = ({ title }) => { + const router = useRouter(); + + const [scrollPosition, setScrollPosition] = useState(0); + const handleScrollPosition = () => { + const position = window.pageYOffset; + setScrollPosition(position); + } + + useEffect(() => { + window.addEventListener('scroll', handleScrollPosition, { passive: true }); + + return () => { + window.addEventListener('scroll', handleScrollPosition); + }; + }, []); + + const handleBackButtonClick = (event) => { + event.currentTarget.disabled = true; + router.back(); + } + + return ( + <> + <Head> + <title>{ title } - Indoteknik</title> + </Head> + <div className={"sticky-header flex justify-between !p-0 !pr-4 idt-transition " + (scrollPosition > 0 && "shadow border-b-transparent" ) }> + {/* --- Start Title */} + <div className="flex items-center"> + <button type="button" onClick={handleBackButtonClick} className="text-gray_r-12 px-4 py-5"> + <ChevronLeftIcon className="w-6 stroke-2"/> + </button> + <h1 className="text-h-md">{ title }</h1> + </div> + {/* --- End Title */} + + {/* --- Start Icons */} + <div className="flex gap-x-4 items-center"> + <Link href="/"> + <HeartIcon className="w-6 stroke-2 text-gray_r-12"/> + </Link> + <Link href="/"> + <HomeIcon className="w-6 stroke-2 text-gray_r-12"/> + </Link> + </div> + {/* --- End Icons */} + </div> + </> + ); +}; + +export default AppBar;
\ No newline at end of file diff --git a/src/components/layouts/Footer.js b/src/components/layouts/Footer.js new file mode 100644 index 00000000..e48daf7f --- /dev/null +++ b/src/components/layouts/Footer.js @@ -0,0 +1,69 @@ +import { + PhoneIcon, + DevicePhoneMobileIcon, + EnvelopeIcon +} from "@heroicons/react/24/outline"; +import Image from "next/image"; +import InstagramIcon from "@/icons/instagram.svg"; +import LinkedinIcon from "@/icons/linkedin.svg"; + +export default function Footer() { + return ( + <div className="p-4 bg-gray_r-2"> + <div className="grid grid-cols-2 gap-x-2 mb-4"> + <div> + <p className="font-medium mb-2">Kantor Pusat</p> + <p className="text-gray_r-11 leading-6 text-caption-2"> + Jl. Bandengan Utara 85A No. 8-9 RT.3/RW.16, Penjaringan, Kec. Penjaringan + </p> + + <p className="font-medium mb-2 mt-6">Layanan Informasi</p> + <div className="flex items-center gap-x-2 text-gray_r-11 text-caption-2 mb-2"> + <PhoneIcon className="w-5 h-5 stroke-2"/> + <p>(021) 2933-8828 / 29</p> + </div> + <div className="flex items-center gap-x-2 text-gray_r-11 text-caption-2 mb-2"> + <DevicePhoneMobileIcon className="w-5 h-5 stroke-2"/> + <p>0812-8080-622</p> + </div> + <div className="flex items-center gap-x-2 text-gray_r-11 text-caption-2"> + <EnvelopeIcon className="w-5 h-5 stroke-2"/> + <p>sales@indoteknik.com</p> + </div> + + {/* <p className="font-medium mb-2 mt-6">Panduan Pelanggan</p> */} + </div> + <div> + <p className="font-medium mb-2">Jam Operasional</p> + <p className="text-gray_r-11 leading-6 text-caption-2"> + <span className="font-medium">Senin - Jumat:</span> 08:30 - 17:00 + </p> + <p className="text-gray_r-11 leading-6 text-caption-2"> + <span className="font-medium">Sabtu:</span> 08:30 - 14:00 + </p> + + <p className="font-medium mb-2 mt-6">Temukan Kami</p> + <div className="flex gap-x-2"> + <InstagramIcon className="w-5 h-5 stroke-gray_r-11" /> + <LinkedinIcon className="w-5 h-5 stroke-gray_r-11" /> + </div> + + <p className="font-medium mb-2 mt-6">Pembayaran</p> + <div className="grid grid-cols-4 gap-2"> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + <Image src="/images/payments/bca.webp" alt="BCA Payment" width={48} height={48} className="w-full" /> + </div> + + {/* <p className="font-medium mb-2 mt-6">Pengiriman</p> */} + </div> + </div> + <h6 className="h2">PT. Indoteknik Dotcom Gemilang</h6> + </div> + ); +}
\ No newline at end of file diff --git a/src/components/layouts/Header.js b/src/components/layouts/Header.js new file mode 100644 index 00000000..5cd0a1bc --- /dev/null +++ b/src/components/layouts/Header.js @@ -0,0 +1,164 @@ +import Image from "next/image"; +import { useCallback, useEffect, useRef, useState } from "react"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import axios from "axios"; +import { + MagnifyingGlassIcon, + Bars3Icon, + ShoppingCartIcon, + ChevronRightIcon, + Cog6ToothIcon +} from "@heroicons/react/24/outline"; + +// Helpers +import { useAuth } from "@/core/utils/auth"; +// Components +import Link from "../elements/Link"; +// Images +import Logo from "@/images/logo.png"; +import greeting from "@/core/utils/greeting"; + +const menus = [ + { name: 'Semua Brand', href: '/shop/brands' }, + { name: 'Blog Indoteknik', href: '/' }, + { name: 'Kategori', href: '/' }, +]; + +export default function Header({ title }) { + const router = useRouter(); + const { q = '' } = router.query; + const [searchQuery, setSearchQuery] = useState(q); + const [suggestions, setSuggestions] = useState([]); + const searchQueryRef = useRef(); + const [isMenuActive, setIsMenuActive] = useState(false); + const [auth] = useAuth(); + + useEffect(() => { + if (q) { + searchQueryRef.current.blur(); + setSuggestions([]); + }; + }, [q]); + + const clickSuggestion = (value) => { + router.push(`/shop/search?q=${value}`, undefined, { scroll: false }); + }; + + const getSuggestion = useCallback(async () => { + if (searchQuery.trim().length > 0) { + let result = await axios(`${process.env.SELF_HOST}/api/shop/suggest?q=${searchQuery.trim()}`); + setSuggestions(result.data.suggest.mySuggester[searchQuery.trim()].suggestions); + } else { + setSuggestions([]); + } + }, [searchQuery]); + + useEffect(() => { + if (document.activeElement == searchQueryRef.current) getSuggestion(); + }, [getSuggestion]); + + const openMenu = () => setIsMenuActive(true); + const closeMenu = () => setIsMenuActive(false); + + const searchSubmit = (e) => { + e.preventDefault(); + if (searchQuery.length > 0) { + router.push(`/shop/search?q=${searchQuery}`, undefined, { scroll: false }); + } else { + searchQueryRef.current.focus(); + } + } + + 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={'menu-wrapper' + (isMenuActive ? ' active ' : '')}> + <div className="flex gap-x-2 items-center border-b border-gray_r-6 p-4"> + { auth && ( + <Link href="/my/menu" className="w-full flex items-center text-gray_r-12" onClick={closeMenu}> + <div> + <p className="text-gray_r-11 text-caption-2">{ greeting() },</p> + <h1>{auth.name}</h1> + </div> + <div className="ml-auto"> + <Cog6ToothIcon className="w-5" /> + </div> + </Link> + ) } + + { !auth && ( + <> + <Link href="/login" onClick={closeMenu} className="w-full py-2 btn-light text-gray_r-12">Masuk</Link> + <Link href="/register" onClick={closeMenu} className="w-full py-2 btn-yellow text-gray_r-12">Daftar</Link> + </> + ) } + </div> + <div className="flex flex-col"> + { menus.map((menu, index) => ( + <Link className="flex w-full font-normal text-gray_r-11 border-b border-gray_r-6 p-4" href={menu.href} key={index} onClick={closeMenu}> + <span>{ menu.name }</span> + <div className="ml-auto"> + <ChevronRightIcon className="text-gray_r-12 w-5" /> + </div> + </Link> + )) } + </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="/" scroll={false}> + <Image src={Logo} alt="Logo Indoteknik" width={120} height={40} /> + </Link> + <div className="flex gap-x-4"> + <Link href="/shop/cart"> + <ShoppingCartIcon className="w-6 text-gray_r-12" /> + </Link> + <button onClick={openMenu}> + <Bars3Icon className="w-6 text-gray_r-12" /> + </button> + </div> + </div> + <form onSubmit={searchSubmit} className="relative flex mt-2"> + <input + ref={searchQueryRef} + type="text" + name="q" + onChange={(e) => setSearchQuery(e.target.value)} + onFocus={getSuggestion} + value={searchQuery} + className="form-input rounded-r-none border-r-0 focus:border-gray_r-7" + placeholder="Ketikan nama, merek, part number" + autoComplete="off" + /> + + <button + type="submit" + aria-label="search" + className="btn-light bg-transparent px-2 py-1 rounded-l-none border-l-0" + > + <MagnifyingGlassIcon className="w-6" /> + </button> + + {suggestions.length > 1 && ( + <div className="absolute w-full top-[50px] rounded-b bg-gray_r-2 border border-gray_r-6"> + {suggestions.map((suggestion, index) => ( + <p onClick={() => clickSuggestion(suggestion.term)} className="w-full p-2" key={index}>{suggestion.term}</p> + ))} + </div> + )} + </form> + </div> + + {suggestions.length > 1 && ( + <div className="menu-overlay !z-40" onClick={() => setSuggestions([])}></div> + )} + </> + ) +}
\ No newline at end of file diff --git a/src/components/layouts/Layout.js b/src/components/layouts/Layout.js new file mode 100644 index 00000000..fd507963 --- /dev/null +++ b/src/components/layouts/Layout.js @@ -0,0 +1,20 @@ +import { motion } from 'framer-motion'; + +export default function Layout({ children, ...pageProps }) { + const transition = { + ease: 'easeOut', + duration: 0.3 + }; + + return children && ( + <motion.main + initial={{ opacity: 0, x: 30, y: 0 }} + animate={{ opacity: 1, x: 0, y: 0 }} + exit={{ opacity: 0, x: 30, y: 0 }} + transition={transition} + {...pageProps} + > + {children} + </motion.main> + ); +}
\ No newline at end of file |
