diff --git a/frontend/bun.lockb b/frontend/bun.lockb index fe5f77d..b8ba59a 100755 Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ diff --git a/frontend/package.json b/frontend/package.json index 5735c57..1a9c768 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,8 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dotenv": "^16.4.5", + "embla-carousel-autoplay": "^8.3.0", + "embla-carousel-react": "^8.3.0", "framer-motion": "^11.11.10", "lucide-react": "^0.427.0", "next": "14.1.4", diff --git a/frontend/public/batch1/Mockup 132.png b/frontend/public/batch1/Mockup 132.png new file mode 100644 index 0000000..7bb9d9e Binary files /dev/null and b/frontend/public/batch1/Mockup 132.png differ diff --git a/frontend/public/batch1/Mockup 133.png b/frontend/public/batch1/Mockup 133.png new file mode 100644 index 0000000..95a7789 Binary files /dev/null and b/frontend/public/batch1/Mockup 133.png differ diff --git a/frontend/public/batch1/files_list.json b/frontend/public/batch1/files_list.json index fe0ad18..5268289 100644 --- a/frontend/public/batch1/files_list.json +++ b/frontend/public/batch1/files_list.json @@ -188,5 +188,25 @@ "inStock": "yes", "favorite": "no", "description": "Bear-Relax" + }, + { + "id": 23, + "name": "Mockup 132", + "photo": "batch1/Mockup 132.png", + "price": 25, + "size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL", + "inStock": "yes", + "favorite": "no", + "description": "Running-man" + }, + { + "id": 24, + "name": "Mockup 133", + "photo": "batch1/Mockup 133.png", + "price": 25, + "size": "Youth-S/M/L/2XL | Adult-S/M/L/2XL", + "inStock": "yes", + "favorite": "no", + "description": "Running-woman" } ] \ No newline at end of file diff --git a/frontend/public/payment.png b/frontend/public/payment.png new file mode 100644 index 0000000..4454402 Binary files /dev/null and b/frontend/public/payment.png differ diff --git a/frontend/src/app/index.tsx b/frontend/src/app/index.tsx index 43c6728..757ae07 100644 --- a/frontend/src/app/index.tsx +++ b/frontend/src/app/index.tsx @@ -1,77 +1,77 @@ //pages/index.tsx -import { useState } from "react"; -import styles from "@/styles/Home.module.css"; -import ClientPortal from "../components/ClientPortal/ClientPortal"; -export default function Home() { - const [showPortal, setShowPortal] = useState(false); - const handleModal = () => { - setShowPortal(!showPortal); - }; +// import { useState } from "react"; +// import styles from "@/styles/Home.module.css"; +// import ClientPortal from "../components/ClientPortal/ClientPortal"; +// export default function Home() { +// const [showPortal, setShowPortal] = useState(false); +// const handleModal = () => { +// setShowPortal(!showPortal); +// }; - return ( - <> -
-

Implementation portal in next JS

- -
- -
-
-
-

Lorem Ipsum

- -
-
-

- Basically, React Portal is a powerful technique to create a - first-class way to render children into a DOM (Document object - modal) Node from outside of their parent component hierarchy. -

-
-

Industry's stand 1960s with the release

-

- has been the industry's stand 1960s with the release of - Letraset sheets containing Lorem Ipsum passages, and more - recently with desktop publishing software like Aldus PageMaker - including versions of Lorem Ipsum. -

-
-
-

- Lorem Ipsum is simply dummy text of the printing It has - survived not only five centuries, but also the leap into - electronic typesetting, remaining essentially unchanged. It - was popularised in the 1960s with the release of Letraset - sheets containing Lorem Ipsum passages, and more recently -

-
-
-

- Lorem Ipsum is simply dummy text of the printing and - typesetting industry. Lorem Ipsum has been the industry's - standard dummy text ever since the 1500s, when an unknown - printer took a galley of type and Lorem Ipsum. -

-
-
-
-
-
- - ); -} \ No newline at end of file +// return ( +// <> +//
+//

Implementation portal in next JS

+// +//
+// +//
+//
+//
+//

Lorem Ipsum

+// +//
+//
+//

+// Basically, React Portal is a powerful technique to create a +// first-class way to render children into a DOM (Document object +// modal) Node from outside of their parent component hierarchy. +//

+//
+//

Industry's stand 1960s with the release

+//

+// has been the industry's stand 1960s with the release of +// Letraset sheets containing Lorem Ipsum passages, and more +// recently with desktop publishing software like Aldus PageMaker +// including versions of Lorem Ipsum. +//

+//
+//
+//

+// Lorem Ipsum is simply dummy text of the printing It has +// survived not only five centuries, but also the leap into +// electronic typesetting, remaining essentially unchanged. It +// was popularised in the 1960s with the release of Letraset +// sheets containing Lorem Ipsum passages, and more recently +//

+//
+//
+//

+// Lorem Ipsum is simply dummy text of the printing and +// typesetting industry. Lorem Ipsum has been the industry's +// standard dummy text ever since the 1500s, when an unknown +// printer took a galley of type and Lorem Ipsum. +//

+//
+//
+//
+//
+//
+// +// ); +// } \ No newline at end of file diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 8872458..c0c8e93 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -10,6 +10,7 @@ import styles from "@/styles/Home.module.css"; import { Poppins } from "next/font/google"; import { sendEmail } from "./actions/sendEmail"; import { sendEmails } from "@/lib/mail.utils"; +import ProductCarousel from '@/components/ProductCarousel/ProductCarousel'; const Popp = Poppins({ weight: "400", subsets: ["latin"] }); @@ -75,44 +76,9 @@ export default function Home() { } return ( -
- {/* Title */} -
- {/*

- Kevo's Attire -

*/} -
-
-

- Youth:{" "} - - S | M{" "} - | L - -

-

- Adult:{" "} - - S | M{" "} - | L{" "} - | XL{" "} - | 2XL - -

-
-
-

- Small - Large: $25 -

-

- XL - 2XL: $27 -

-
-
+
+
+
{/* Body */} diff --git a/frontend/src/components/Footer/Footer.tsx b/frontend/src/components/Footer/Footer.tsx index 0b72e34..dca900f 100644 --- a/frontend/src/components/Footer/Footer.tsx +++ b/frontend/src/components/Footer/Footer.tsx @@ -2,21 +2,22 @@ import React from "react"; import { FaFacebookF } from "react-icons/fa"; import { FaInstagram } from "react-icons/fa"; import { FaTiktok } from "react-icons/fa"; +import Image from "next/image"; const Footer = () => { return ( -
-
-
-

Categories

+
+
+
+

Categories

Women Men Shoes Accessories New Arrivals
-
-

Links

+
+

Links

FAQ Pages Stores @@ -31,11 +32,23 @@ const Footer = () => {
-
- - All items are sold under the assumption that they are original works and not copyrighted under another company. - - © 2024 - Kevos Attire LLC. All rights reserved. +
+
+ + All items are sold under the beleif that they are original works and + not copyrighted under another company. + + © 2024 - Kevos Attire LLC. All rights reserved. +
+
+ payment systems +
); diff --git a/frontend/src/components/Navbar/Navbar.tsx b/frontend/src/components/Navbar/Navbar.tsx index 7d994fb..420474f 100644 --- a/frontend/src/components/Navbar/Navbar.tsx +++ b/frontend/src/components/Navbar/Navbar.tsx @@ -1,6 +1,6 @@ "use client"; import { motion } from "framer-motion"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useMediaQuery } from "@/util/useMediaQuery"; import { FiShoppingCart } from "react-icons/fi"; import Image from "next/image"; @@ -74,10 +74,11 @@ export default function Nav() { Home Shirts About - {/* Contact */} - - 0 + + + 0 +
)} @@ -129,9 +130,6 @@ export default function Nav() { Cart - {/* - Contact - */} )} diff --git a/frontend/src/components/ProductCarousel/ProductCarousel.tsx b/frontend/src/components/ProductCarousel/ProductCarousel.tsx new file mode 100644 index 0000000..e687338 --- /dev/null +++ b/frontend/src/components/ProductCarousel/ProductCarousel.tsx @@ -0,0 +1,98 @@ +import * as React from "react" +import Autoplay from "embla-carousel-autoplay" +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel" +import Image from 'next/image'; + + +const ProductCarousel = () => { + + const plugin = React.useRef( + Autoplay({ delay: 3000, stopOnInteraction: true }) + ) + return ( + + + + shirt 3 + + + shirt 6 + + + shirt 10 + + + shirt 3 + + + shirt 6 + + + shirt 10 + + + shirt 10 + + + shirt 10 + + + + + + + ) +} + +export default ProductCarousel \ No newline at end of file diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 87f5a1d..36496a2 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -1,10 +1,11 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "../../lib/utils" + +import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { diff --git a/frontend/src/components/ui/carousel.tsx b/frontend/src/components/ui/carousel.tsx new file mode 100644 index 0000000..ec505d0 --- /dev/null +++ b/frontend/src/components/ui/carousel.tsx @@ -0,0 +1,262 @@ +"use client" + +import * as React from "react" +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react" +import { ArrowLeft, ArrowRight } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin + orientation?: "horizontal" | "vertical" + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return + } + + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) { + return + } + + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) { + return + } + + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) + } +) +Carousel.displayName = "Carousel" + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +}) +CarouselContent.displayName = "CarouselContent" + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel() + + return ( +
+ ) +}) +CarouselItem.displayName = "CarouselItem" + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +}) +CarouselPrevious.displayName = "CarouselPrevious" + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +}) +CarouselNext.displayName = "CarouselNext" + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} diff --git a/frontend/src/util/useMediaQuery.tsx b/frontend/src/util/useMediaQuery.tsx index 5f22199..e0f41ad 100644 --- a/frontend/src/util/useMediaQuery.tsx +++ b/frontend/src/util/useMediaQuery.tsx @@ -1,61 +1,24 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react'; -import { useIsomorphicLayoutEffect } from 'usehooks-ts' +export function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(false); -type UseMediaQueryOptions = { - defaultValue?: boolean - initializeWithValue?: boolean -} + useEffect(() => { + if (typeof window === 'undefined') return; // Ensures no window access on the server -const IS_SERVER = typeof window === 'undefined' + const matchMedia = window.matchMedia(query); -export function useMediaQuery( - query: string, - { - defaultValue = false, - initializeWithValue = true, - }: UseMediaQueryOptions = {}, -): boolean { - const getMatches = (query: string): boolean => { - if (IS_SERVER) { - return defaultValue - } - return window.matchMedia(query).matches - } - - const [matches, setMatches] = useState(() => { - if (initializeWithValue) { - return getMatches(query) - } - return defaultValue - }) - - // Handles the change event of the media query. - function handleChange() { - setMatches(getMatches(query)) - } - - useIsomorphicLayoutEffect(() => { - const matchMedia = window.matchMedia(query) - - // Triggered at the first client-side load and if query changes - handleChange() - - // Use deprecated `addListener` and `removeListener` to support Safari < 14 (#135) - if (matchMedia.addListener) { - matchMedia.addListener(handleChange) - } else { - matchMedia.addEventListener('change', handleChange) - } + const handleChange = () => setMatches(matchMedia.matches); + + // Set initial value + handleChange(); + // Add and remove event listeners for changes + matchMedia.addEventListener('change', handleChange); return () => { - if (matchMedia.removeListener) { - matchMedia.removeListener(handleChange) - } else { - matchMedia.removeEventListener('change', handleChange) - } - } - }, [query]) + matchMedia.removeEventListener('change', handleChange); + }; + }, [query]); - return matches -} \ No newline at end of file + return matches; +}