added footer and navbar, still working on copyright text
This commit is contained in:
parent
67c2eabd1a
commit
7bc254ad03
Binary file not shown.
@ -16,15 +16,18 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
"framer-motion": "^11.11.10",
|
||||||
"lucide-react": "^0.427.0",
|
"lucide-react": "^0.427.0",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-icons": "^5.3.0",
|
||||||
"resend": "^3.5.0",
|
"resend": "^3.5.0",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"usehooks-ts": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.16.1",
|
"@types/node": "^20.16.1",
|
||||||
|
|||||||
BIN
frontend/public/white_logo.png
Normal file
BIN
frontend/public/white_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 43 KiB |
@ -1,12 +1,15 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import Navbar from "@/components/Navbar/Navbar";
|
||||||
|
import Footer from "@/components/Footer/Footer";
|
||||||
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Kevos Attire",
|
title: "Kevos Attire",
|
||||||
description: "Clothing for people with needs",
|
description: "Clothing for the next generation.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@ -16,7 +19,11 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={`${inter.className} bg-slate-700`}>{children}</body>
|
<body className={`${inter.className} bg-slate-700`}>
|
||||||
|
<Navbar />
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { sendEmails } from "@/lib/mail.utils";
|
|||||||
|
|
||||||
const Popp = Poppins({ weight: "400", subsets: ["latin"] });
|
const Popp = Poppins({ weight: "400", subsets: ["latin"] });
|
||||||
|
|
||||||
|
|
||||||
// improve to drag & drop side cart for more fun
|
// improve to drag & drop side cart for more fun
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@ -24,6 +25,10 @@ export default function Home() {
|
|||||||
orders: "",
|
orders: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function newItem(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async function newOrder(event: React.FormEvent<HTMLFormElement>) {
|
async function newOrder(event: React.FormEvent<HTMLFormElement>) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const formData = new FormData(event.currentTarget);
|
const formData = new FormData(event.currentTarget);
|
||||||
@ -73,9 +78,9 @@ export default function Home() {
|
|||||||
<main className="flex min-h-screen flex-col px-20">
|
<main className="flex min-h-screen flex-col px-20">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className="text-4xl w-full justify-center text-center items-center flex flex-col gap-9 py-12 font-medium text-orange-600">
|
<div className="text-4xl w-full justify-center text-center items-center flex flex-col gap-9 py-12 font-medium text-orange-600">
|
||||||
<h1 className={`${Popp.className} text-white text-5xl`}>
|
{/* <h1 className={`${Popp.className} text-white text-5xl`}>
|
||||||
Kevo's Attire
|
Kevo's Attire
|
||||||
</h1>
|
</h1> */}
|
||||||
<div
|
<div
|
||||||
className={`${Popp.className} text-orange-600 text-center flex flex-col gap-5`}
|
className={`${Popp.className} text-orange-600 text-center flex flex-col gap-5`}
|
||||||
>
|
>
|
||||||
|
|||||||
38
frontend/src/components/CartModal/CartModal.tsx
Normal file
38
frontend/src/components/CartModal/CartModal.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"use client";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
const CartModal = () => {
|
||||||
|
const cartItems = true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="absolute p-4 rounded-md shadow-[0_3px_10px_rgb(0,0,0,0,2)] bg-white top-12 right-0 flex flex-col gap-6 z-20">
|
||||||
|
{!cartItems ? <div>Cart is Empty</div> :
|
||||||
|
<div className="">
|
||||||
|
<Image
|
||||||
|
src="batch1/Mockup 134.png"
|
||||||
|
alt=""
|
||||||
|
width={72}
|
||||||
|
height={96}
|
||||||
|
className="object-contain rounded-md"
|
||||||
|
/>
|
||||||
|
<div className="">
|
||||||
|
{/* TOP */}
|
||||||
|
<div>
|
||||||
|
{/* TITLE */}
|
||||||
|
<div className="">
|
||||||
|
<h3>Product Name</h3>
|
||||||
|
<div className="">$49</div>
|
||||||
|
</div>
|
||||||
|
{/* DESCRIPTION */}
|
||||||
|
<div className="">
|
||||||
|
available
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* BOTTOM */}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
11
frontend/src/components/CartTab/cartTab.tsx
Normal file
11
frontend/src/components/CartTab/cartTab.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"use client";
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const CartTab = () => {
|
||||||
|
const cartItems = false;
|
||||||
|
return (
|
||||||
|
<div className='absolute p-4 rounded-md shadow-[0_3px_10px_rgb(0,0,0,0,2)] bg-white top-12 right-0 flex flex-col gap-6 z-20'>{!cartItems ? (<div>Cart is Empty</div>) : (<div></div>)}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CartTab
|
||||||
40
frontend/src/components/Footer/Footer.tsx
Normal file
40
frontend/src/components/Footer/Footer.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { FaFacebookF } from "react-icons/fa";
|
||||||
|
import { FaInstagram } from "react-icons/fa";
|
||||||
|
import { FaTiktok } from "react-icons/fa";
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='footer text-white flex flex-col gap-8 px-24 pb-2'>
|
||||||
|
<div className='top'>
|
||||||
|
<div className='item'>
|
||||||
|
<h1>Categories</h1>
|
||||||
|
<span>Women</span>
|
||||||
|
<span>Men</span>
|
||||||
|
<span>Shoes</span>
|
||||||
|
<span>Accessories</span>
|
||||||
|
<span>New Arrivals</span>
|
||||||
|
</div>
|
||||||
|
<div className='item'>
|
||||||
|
<h1>Links</h1>
|
||||||
|
<span>FAQ</span>
|
||||||
|
<span>Pages</span>
|
||||||
|
<span>Stores</span>
|
||||||
|
<span>Compare</span>
|
||||||
|
<span>Cookies</span>
|
||||||
|
</div>
|
||||||
|
<div className='item'></div>
|
||||||
|
<div className='item'></div>
|
||||||
|
</div>
|
||||||
|
<div className='Socials flex gap-4 text-xl'>
|
||||||
|
<FaFacebookF />
|
||||||
|
<FaInstagram />
|
||||||
|
<FaTiktok />
|
||||||
|
</div>
|
||||||
|
<div className='bottom text-xs'><span>© 2024 - Kevos Attire LLC. All rights reserved. All items are made with the assumption that they are 100% original ideas as we do not </span></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer
|
||||||
140
frontend/src/components/Navbar/Navbar.tsx
Normal file
140
frontend/src/components/Navbar/Navbar.tsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
"use client";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useMediaQuery } from "@/util/useMediaQuery";
|
||||||
|
import { FiShoppingCart } from "react-icons/fi";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
const navMotion = {
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
when: "beforeChildren",
|
||||||
|
staggerChildren: 0.15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemMotion = {
|
||||||
|
visible: { opacity: 1, x: 0 },
|
||||||
|
hidden: { opacity: 0, x: -100 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Nav() {
|
||||||
|
const [toggled, setToggled] = useState(false);
|
||||||
|
const matches = useMediaQuery("(min-width: 1280px)");
|
||||||
|
// console.log(matches);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="relative mx-8 mb-10 flex justify-between items-center pt-12 font-medium md:mx-16 lg:mx-32 text-white">
|
||||||
|
<svg
|
||||||
|
className="absolute bottom-0 left-1/2 -translate-x-1/2"
|
||||||
|
width="250"
|
||||||
|
height={4}
|
||||||
|
viewBox="0 0 250 4"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M2 2L428 2" stroke="#EEEEEE" strokeLinecap="round" />
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
src="/white_logo.png"
|
||||||
|
width={64}
|
||||||
|
height={64}
|
||||||
|
className="rounded-full md:hidden"
|
||||||
|
alt="Profile picture"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
src="/white_logo.png"
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className="rounded-full md:block hidden xl:hidden"
|
||||||
|
alt="Profile picture"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
src="/white_logo.png"
|
||||||
|
width={90}
|
||||||
|
height={90}
|
||||||
|
className="rounded-full hidden xl:block"
|
||||||
|
alt="Profile picture"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Title */}
|
||||||
|
<h1 className="text-lg font-bold absolute text-center w-full ">
|
||||||
|
<a href="/">Kevos Attire</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Check if mobile device */}
|
||||||
|
{matches && (
|
||||||
|
<div className="flex gap-8 relative">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/products">Shirts</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
{/* <a href="/contact">Contact</a> */}
|
||||||
|
<a className="flex relative items-center justify-center" href="/cart">
|
||||||
|
<FiShoppingCart className="text-lg lg:text-xl" />
|
||||||
|
<span className="absolute text-white text-xs w-5 h-5 rounded-[50%] bg-slate-400 -top-2.5 -right-2.5 flex items-center justify-center">0</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!matches && (
|
||||||
|
<div
|
||||||
|
onClick={() => setToggled(!toggled)}
|
||||||
|
className="space-y-1.5 cursor-pointer z-50"
|
||||||
|
>
|
||||||
|
<motion.span
|
||||||
|
animate={{ rotateZ: toggled ? 45 : 0, y: toggled ? 8 : 0 }}
|
||||||
|
className="block h-0.5 w-8 bg-[#EEEEEE]"
|
||||||
|
></motion.span>
|
||||||
|
<motion.span
|
||||||
|
animate={{ width: toggled ? 0 : 24 }}
|
||||||
|
className="block h-0.5 w-6 bg-[#EEEEEE]"
|
||||||
|
></motion.span>
|
||||||
|
<motion.span
|
||||||
|
animate={{
|
||||||
|
rotateZ: toggled ? -45 : 0,
|
||||||
|
y: toggled ? -8 : 0,
|
||||||
|
width: toggled ? 32 : 16,
|
||||||
|
}}
|
||||||
|
className="block h-0.5 w-4 bg-[#EEEEEE]"
|
||||||
|
></motion.span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{toggled && !matches && (
|
||||||
|
<motion.div
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
initial={{ opacity: 0, x: 25 }}
|
||||||
|
className="fixed flex bg-slate-700 bottom-0 right-0 w-full h-screen items-center justify-center z-40"
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
variants={navMotion}
|
||||||
|
animate="visible"
|
||||||
|
initial="hidden"
|
||||||
|
className="flex flex-col gap-24 text-lg"
|
||||||
|
>
|
||||||
|
<motion.a variants={itemMotion} href="/">
|
||||||
|
Home
|
||||||
|
</motion.a>
|
||||||
|
<motion.a variants={itemMotion} href="/products">
|
||||||
|
Products
|
||||||
|
</motion.a>
|
||||||
|
<motion.a variants={itemMotion} href="/about">
|
||||||
|
About
|
||||||
|
</motion.a>
|
||||||
|
<motion.a variants={itemMotion} href="/cart">
|
||||||
|
Cart
|
||||||
|
</motion.a>
|
||||||
|
{/* <motion.a variants={itemMotion} href="/contact">
|
||||||
|
Contact
|
||||||
|
</motion.a> */}
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
frontend/src/components/ProductCart/ProductCart.tsx
Normal file
11
frontend/src/components/ProductCart/ProductCart.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const ProductCart = (props: any) => {
|
||||||
|
const {id, name, price, image, size} = props.data;
|
||||||
|
return (
|
||||||
|
<div className='bg-[#EEEEEE] p-5 rounded-xl shadow-sm'><Link href=""></Link></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductCart
|
||||||
@ -26,7 +26,7 @@ const MenuCard = ({ Menu }: any) => {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={menu_item.id}
|
key={menu_item.id}
|
||||||
className="flex-1 flex-col max-w-[325px] h-[400px] relative overflow-hidden bg-white/10 border-none hover:scale-150 transition hover:z-50 backdrop-blur-xl"
|
className="flex-1 flex-col max-w-[325px] h-[400px] relative overflow-hidden bg-white/10 border-none transition hover:z-50 backdrop-blur-xl"
|
||||||
>
|
>
|
||||||
<div className="w-full h-[75%]">
|
<div className="w-full h-[75%]">
|
||||||
<div className="h-full relative w-full">
|
<div className="h-full relative w-full">
|
||||||
|
|||||||
61
frontend/src/util/useMediaQuery.tsx
Normal file
61
frontend/src/util/useMediaQuery.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import { useIsomorphicLayoutEffect } from 'usehooks-ts'
|
||||||
|
|
||||||
|
type UseMediaQueryOptions = {
|
||||||
|
defaultValue?: boolean
|
||||||
|
initializeWithValue?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const IS_SERVER = typeof window === 'undefined'
|
||||||
|
|
||||||
|
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<boolean>(() => {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (matchMedia.removeListener) {
|
||||||
|
matchMedia.removeListener(handleChange)
|
||||||
|
} else {
|
||||||
|
matchMedia.removeEventListener('change', handleChange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [query])
|
||||||
|
|
||||||
|
return matches
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user