diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..d5499356 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["next/babel"], + "plugins": ["@emotion"] +} \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..9738fb83 --- /dev/null +++ b/.env.example @@ -0,0 +1,40 @@ +NEXT_PUBLIC_IS_MAINT_MODE= +NEXT_PUBLIC_SHORT_TITLE= +NEXT_PUBLIC_ENTITY_NAME= +NEXT_PUBLIC_SITE_TITLE= +NEXT_PUBLIC_SITE_URL= +NEXT_PUBLIC_SITE_SUBTITLE= +NEXT_PUBLIC_PAGE_TITLE= +NEXT_PUBLIC_PAGE_DESC= +NEXT_PUBLIC_LOGO_PATH= +NEXT_PUBLIC_COMPANY_EMAIL= +NEXT_PUBLIC_COMPANY_PHONE= +NEXT_PUBLIC_COMPANY_LEGAL_URL= +NEXT_PUBLIC_COMING_SOON_COPY= +NEXT_PUBLIC_VIDEO_URL= +NEXT_PUBLIC_APP_URL= +NEXT_PUBLIC_SPREE_API_URL= +NEXT_PUBLIC_SPREE_ACCESS_TOKEN= +NEXT_PUBLIC_MUX_ACCESS_TOKEN_ID= +NEXT_PUBLIC_MUX_SECRET_KEY= +NEXT_PUBLIC_SC_CLIENT_ID= +NEXT_PUBLIC_SC_CLIENT_SECRET= +NEXT_PUBLIC_TRACKING= +NEXT_PUBLIC_TRACKING_VERBOSE= +NEXT_PUBLIC_TRACKING_GA_ON= +NEXT_PUBLIC_TRACKING_PROVIDER_GA= +NEXT_PUBLIC_GA_TRACKING_CODE= +NEXT_PUBLIC_GA_DEBUG_MODE= +NEXT_PUBLIC_TRACKING_KM_ON= +NEXT_PUBLIC_TRACKING_PROVIDER_KM= +NEXT_PUBLIC_TRACKING_VERBOSE= +NEXT_PUBLIC_MAILCHIMP_URL= +NEXT_PUBLIC_MAILCHIMP_CLIENT_KEY= +NEXT_PUBLIC_MAILCHIMP_API_KEY= +NEXT_PUBLIC_MAILCHIMP_USERNAME= +NEXT_PUBLIC_MAILCHIMP_U= +NEXT_PUBLIC_MAILCHIMP_ID= +NEXT_PUBLIC_MAILCHIMP_AUDIENCE_ID= +NEXT_PUBLIC_INSTAGRAM_URL= +NEXT_PUBLIC_FACEBOOK_URL= +NEXT_PUBLIC_TWITTER_URL= \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..5a946d46 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +.next +next.config.js \ No newline at end of file diff --git a/.gitignore b/.gitignore index 20fccdd4..2224bc17 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# IDE +.vscode +.idea # dependencies /node_modules @@ -24,7 +26,21 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env.local +.env.dev +.env.development .env.development.local +.env.staging +.env.staging.local +.env.test .env.test.local +.env.prod +.env.production .env.production.local +envvars.txt + +.env.DNA.development +.env.DNA.production +.env.DNA.staging +.env.DNA.test diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 00000000..31354ec1 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..025779ed --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn pre-commit diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..1380c2e7 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules +.next \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..9d7c0a41 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "tabWidth": 2, + "printWidth": 100, + "singleQuote": false, + "trailingComma": "none", + "jsxBracketSameLine": true +} diff --git a/README.md b/README.md index 4b5c883b..79a41f02 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,14 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next ## Getting Started -First, run the development server: +### Running Locally: -```bash -npm run dev -# or -yarn dev -``` +- `cp .env.example .env.development` +- Replace `.env.development` with variables from Aaron +- `yarn dev` +- Open [http://localhost:3000](http://localhost:3000) in your browser. -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. - -## Learn More +## NextJS To learn more about Next.js, take a look at the following resources: @@ -23,8 +18,95 @@ To learn more about Next.js, take a look at the following resources: You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +## Styles + +Always use `@emotion/styled` wherever possible. If regular CSS is required, use one of the following style files. +We manage our global styles in several files: + +- `./styles/global-styles.tsx` (global stylesheet) +- `./styles/fonts.css` (global @font-face rules) +- `./styles/all.css` (global styles injected in at app root) +- `./styles/theme.tsx` (global theme variables) +- `./emotion.d.ts` (theme typings) + +## Gotchas: + +- The app has a "Maintenance Mode" (branded fullscreen takeover), simply set `IS_MAINT_MODE=true`, and the `
` will disappear and `` gets taken over by ``. It's fun, try it! +- All the data for the app comes in from our staging server on Heroku, but you can also run the dna-admin CMS+API locally (hint: login only works with a localhost API) +- To run against the local API, set the `SPREE_API_URL` environment + variable to the local API host/port +- Complains about missing `.next/build-manifest.json` are usually indications + of a `next` build error. Try running `$(npm bin)/next build` to see the + exact error. + +## Deploy URLS: + +POL Admin Interface & API +http://dna-admin-dev.instinct.is/ +http://dna-admin-staging.instinct.is/ + +POL Frontend Interface +https://dna-frontend-dev.instinct.is/ +https://dna-frontend-staging.instinct.is/ + +## Keeping Your Code Updated: + +When there are lots of active changes occuring on this repo, make sure to regularly: + +1. Commit (or stash) your local changes on your branch +1. `git fetch origin` +1. `git checkout main` +1. `git pull origin main` +1. `git checkout ` +1. `git merge main` +1. Fix merge conflicts (if any) +1. `git add .` +1. `git commit -m 'merge in latest main'` + +Done! +…now you will be up-to-date with latest code. Do this before you submit your PR, and you can be sure it will be a clean merge. + +## Testing API Endpoints + +https://localhost:8080/apidocs/swagger_ui#/ + +## Updating a fork: + +- `git remote add upstream git@github.com:1instinct/dna-frontend.git` +- `git fetch upstream` +- `git checkout main` +- `git pull upstream main` + ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. + +# TODO: + +- Flow / Type Checking +- ~~React~~ +- ~~SSR~~ (NextJS) +- ~~State Mgmt~~ (hooks/useContext... no Redux, yet) +- ~~Request Mgmt~~ (React Query) +- ~~Search~~ (Fuse.js) +- Pusher / API Integration +- ~~Styled Components~~ (@emotion/styled) +- Moving Letters +- UI Sounds (proprietary: "npm install beeper") +- Maps +- File upload (ReactDropzone) +- ~~Form validation (Formik)~~ +- Animations / Transitions (ReactSpring, GSAP) +- Gestures +- UI Alerts +- Uptime Monitoring +- Twilio +- Unit Testing +- Chat widget +- Chatbot (Rasa) +- Browser Feature Detection +- Speed/Performance Benchmarking (GTMetrix.com API?) +- Header tags customization (NextJS: `next/header`) +- ~~Secrets management / Environment variables~~ (`dot-env`) diff --git a/components/404/FourOhFour.styles.tsx b/components/404/FourOhFour.styles.tsx new file mode 100644 index 00000000..0841bbfc --- /dev/null +++ b/components/404/FourOhFour.styles.tsx @@ -0,0 +1,21 @@ +import styled from "@emotion/styled"; + +export const NotFoundContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: space-around; + min-height: 80vh; + font-family: ${(p) => p.theme.typography.titleSM.fontFamily}; +`; + +export const NotFoundTitle = styled.h1` + width: 100%; + + text-align: center; +`; + +export const NotFoundSubtitle = styled.h2` + width: 100%; + text-align: center; +`; diff --git a/components/404/FourOhFour.tsx b/components/404/FourOhFour.tsx new file mode 100644 index 00000000..376a7fad --- /dev/null +++ b/components/404/FourOhFour.tsx @@ -0,0 +1,30 @@ +import Lottie from "react-lottie"; +import { Layout } from "../components"; +import girlAnimation from "../../data/girl.json"; +import { NotFoundContainer, NotFoundTitle, NotFoundSubtitle } from "./FourOhFour.styles"; + +const animationOptions = { + loop: true, + autoplay: true, + animationData: girlAnimation, + rendererSettings: { + preserveAspectRatio: "xMidYMid slice" + } +}; + +export const FourOhFour = () => { + return ( + + + + 404 + Whoops, keep looking... + + + ); +}; diff --git a/components/404/index.ts b/components/404/index.ts new file mode 100644 index 00000000..dad7ab38 --- /dev/null +++ b/components/404/index.ts @@ -0,0 +1 @@ +export { FourOhFour } from "./FourOhFour"; diff --git a/components/Alerts/Alert.tsx b/components/Alerts/Alert.tsx new file mode 100644 index 00000000..de447b77 --- /dev/null +++ b/components/Alerts/Alert.tsx @@ -0,0 +1,20 @@ +import Swal from "sweetalert2"; + +export const Alert = Swal.mixin({ + customClass: { + // container: 'container-class', + // popup: 'popup-class', + // header: 'header-class', + // title: 'title-class', + // closeButton: 'close-button-class', + icon: "alert-icon", + // image: 'image-class', + // content: 'content-class', + // input: 'input-class', + // actions: 'actions-class', + confirmButton: "alert-confirm" + // cancelButton: 'cancel-button-class', + // footer: 'footer-class' + }, + buttonsStyling: false +}); diff --git a/components/Alerts/index.ts b/components/Alerts/index.ts new file mode 100644 index 00000000..1ae3a8ab --- /dev/null +++ b/components/Alerts/index.ts @@ -0,0 +1 @@ +export { Alert } from "./Alert"; diff --git a/components/Animations/Animations.tsx b/components/Animations/Animations.tsx new file mode 100644 index 00000000..d580e86a --- /dev/null +++ b/components/Animations/Animations.tsx @@ -0,0 +1,45 @@ +import { fadeIn, slideInRight, slideOutLeft } from "react-animations"; +import { useSpring, animated } from "react-spring"; +import { keyframes } from "@emotion/react"; +import styled from "@emotion/styled"; + +export const fadeInKeyframes = keyframes` + 0% { opacity: 0; } + 100% { opacity: 1; } +`; + +export const fadeInOutKeyframes = keyframes` + 0% { opacity: 1; } + 50% { opacity: 0.25; } + 100% { opacity: 1; } +`; + +export const SlideInLeftKeyframes = keyframes` + 0% { transform: translateX(-5rem); opacity: 0; } + 100% { transform: translateX(0); opacity: 1; } +`; + +export const SlideOutLeftKeyframes = keyframes` + 0% { transform: translateX(0rem); opacity: 1; } + 100% { transform: translateX(-5rem); opacity: 0; } +`; + +// export const slideInLeftKeyframes = keyframes`${slideInLeft}`; + +// export const slideOutLeftKeyframes = keyframes`${slideOutLeft}`; + +export const FadeIn = styled.div` + animation: 0.33s ${fadeInKeyframes} ease-in-out infinite; +`; + +export const FadeInOut = styled.div` + animation: 0.33s ${fadeInOutKeyframes} linear infinite; +`; + +export const SlideOutLeft = styled.div` + animation: 0.33s ${SlideOutLeftKeyframes} cubic-bezier(0.215, 0.61, 0.355, 1); +`; + +export const SlideInLeft = styled.div` + animation: 0.33s ${SlideInLeftKeyframes} cubic-bezier(0.215, 0.61, 0.355, 1); +`; diff --git a/components/Animations/index.ts b/components/Animations/index.ts new file mode 100644 index 00000000..6f2f7676 --- /dev/null +++ b/components/Animations/index.ts @@ -0,0 +1 @@ +export * from "./Animations"; diff --git a/components/AuthForm/AuthForm.tsx b/components/AuthForm/AuthForm.tsx new file mode 100644 index 00000000..5d8e2566 --- /dev/null +++ b/components/AuthForm/AuthForm.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { Formik, Form, Field, ErrorMessage } from "formik"; +import styled from "@emotion/styled"; +import { SignupForm } from "../SignupForm"; +import { Login } from "../Login"; +import { ResetPassword } from "../ResetPassword"; + +import { AuthFormType } from "./constants"; +import { useAuth } from "../../config/auth"; + +const FieldContainer = styled.div` + margin: 15px 0px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; +`; + +interface Props { + formType: AuthFormType; +} + +export const AuthForm = ({ formType }: Props) => { + switch (formType) { + case AuthFormType.login: + return ; + case AuthFormType.signup: + return ; + case AuthFormType.reset_password: + return ; + default: + return null; + } +}; diff --git a/components/AuthForm/constants.ts b/components/AuthForm/constants.ts new file mode 100644 index 00000000..61340155 --- /dev/null +++ b/components/AuthForm/constants.ts @@ -0,0 +1,76 @@ +import * as Yup from "yup"; + +export enum AuthFormType { + login = "login", + signup = "signup", + reset_password = "reset_password", + update_password = "update_password", // not implemented + update_email = "update_email" // not implemented +} + +export const SignupSchema = Yup.object().shape({ + email: Yup.string().email("Invalid email").required("Required"), + password: Yup.string().min(6, "Too Short").required("Required"), + password_confirmation: Yup.string().test( + "passwords-match", + "Passwords must match", + function (value: string | undefined) { + return this.parent.password === value; + } + ) +}); + +export const LoginSchema = Yup.object().shape({ + username: Yup.string().email("Invalid email").required("Required"), + password: Yup.string().min(6, "Too Short").required("Required") +}); + +export const ForgotPasswordSchema = Yup.object().shape({ + email: Yup.string().email("Invalid email").required("Required") +}); + +export const loginForm = { + title: "LOGIN", + fields: { + username: "", + password: "" + }, + validate: LoginSchema +}; + +export const signupForm = { + title: "SIGN UP", + fields: { + email: "", + password: "", + password_confirmation: "" + }, + validate: SignupSchema +}; + +export const resetPasswordForm = { + title: "RESET PASSWORD", + fields: { + email: "" + }, + validate: ForgotPasswordSchema, + onSubmit: async (values: { email: string }) => {} +}; + +export const updatePasswordForm = { + title: "RESET PASSWORD", + fields: { + email: "" + }, + validate: ForgotPasswordSchema, + onSubmit: async () => {} +}; + +export const updateEmailForm = { + title: "RESET PASSWORD", + fields: { + email: "" + }, + validate: ForgotPasswordSchema, + onSubmit: async () => {} +}; diff --git a/components/AuthForm/index.tsx b/components/AuthForm/index.tsx new file mode 100644 index 00000000..7c429815 --- /dev/null +++ b/components/AuthForm/index.tsx @@ -0,0 +1 @@ +export { AuthForm } from "./AuthForm"; diff --git a/components/CartSidebar/CartSidebar.styles.tsx b/components/CartSidebar/CartSidebar.styles.tsx new file mode 100644 index 00000000..6c30cf89 --- /dev/null +++ b/components/CartSidebar/CartSidebar.styles.tsx @@ -0,0 +1,34 @@ +import styled from "@emotion/styled"; + +export const CartWrapper = styled.div` + font-family: ${(p) => p.theme.typography.titleSM.fontFamily}; + padding: 10px 5px 0 0; + + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + padding: 5px 10px 0 0; + } +`; + +export const CartTitle = styled.h2` + outline: none; +`; + +export const CartButton = styled.div` + cursor: pointer; + background: none; + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + margin: 0 -5px 0 0; + } + + > i { + font-size: 1em; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + } + + &:hover { + > i { + color: ${(p) => p.theme.colors.brand.primary}; + } + } +`; diff --git a/components/CartSidebar/CartSidebar.tsx b/components/CartSidebar/CartSidebar.tsx new file mode 100644 index 00000000..be3126f0 --- /dev/null +++ b/components/CartSidebar/CartSidebar.tsx @@ -0,0 +1,109 @@ +import { slide as BurgerMenu } from "react-burger-menu"; +import { Loading, LoadingWrapper } from ".."; +import { useCart } from "../../hooks/useCart"; +import { cartStyles } from "./cartStyles"; + +import { CartWrapper, CartTitle, CartButton } from "./CartSidebar.styles"; + +interface Props { + isVisible: boolean; + toggle: () => void; +} + +export const CartSidebar = ({ isVisible, toggle }: Props) => { + const { data: cartData, isLoading: cartIsLoading, isError: cartHasError } = useCart(); + + const renderCartItems = () => { + if (Array.isArray(cartData?.data?.relationships?.variants?.data)) { + return cartData?.data?.relationships?.variants?.data?.map((item, index): any => { + return
  • item: {item.id} | qty:
  • ; + }); + } + return null; + }; + + if (cartIsLoading) { + return ( + + + + + + + + + + + + ); + } + + if (cartHasError) { + return ( + + + + + + Cart +

    Cart Error

    +
    + +
    + ); + } + + const { + item_count = 0, + display_item_total, + included_tax_total, + display_total + } = cartData?.data?.attributes || {}; + + if (cartData !== undefined) { + return ( + + + + + + Cart +
    {item_count} items in your cart
    +
    {renderCartItems()}
    +
    Subtotal: {display_item_total}
    +
    Tax: {included_tax_total}
    +
    Total: {display_total}
    +
    + +
    + ); + } + return null; +}; diff --git a/components/CartSidebar/cartStyles.ts b/components/CartSidebar/cartStyles.ts new file mode 100644 index 00000000..08786b48 --- /dev/null +++ b/components/CartSidebar/cartStyles.ts @@ -0,0 +1,55 @@ +export const cartStyles: any = { + bmBurgerButton: { + position: "fixed", + width: "36px", + height: "30px", + left: "1.06vw", + top: "3.73vw" + }, + bmBurgerBars: { + display: "none", + background: "#000" + }, + bmBurgerBarsHover: { + background: "#a90000" + }, + bmCrossButton: { + height: "24px", + width: "24px" + }, + bmCross: { + background: "#000" + }, + bmMenuWrap: { + position: "fixed", + height: "100%", + top: "0" + }, + bmMenu: { + background: "#fff", + padding: "10%", + fontsize: "1.15em", + height: "100%" + /* width: '100vw' */ + }, + bmMorphShape: { + fill: "#fff" + }, + bmItemList: { + color: "#b8b7ad", + padding: "0 0em", + height: "100%" + }, + bmItem: { + display: "block", + padding: "0.8em", + color: "#000" + }, + bmOverlay: { + top: 0, + left: 0, + right: 0, + bottom: 0, + background: "rgba(0, 0, 0, 0.3)" + } +}; diff --git a/components/CartSidebar/index.tsx b/components/CartSidebar/index.tsx new file mode 100644 index 00000000..1d0a615f --- /dev/null +++ b/components/CartSidebar/index.tsx @@ -0,0 +1 @@ +export { CartSidebar } from "./CartSidebar"; diff --git a/components/Category/Category.styles.tsx b/components/Category/Category.styles.tsx new file mode 100644 index 00000000..a3686c38 --- /dev/null +++ b/components/Category/Category.styles.tsx @@ -0,0 +1,30 @@ +import styled from "@emotion/styled"; + +export const ProductContainer = styled.div` + display: flex; + flex-wrap: wrap; +`; + +export const ProductImageCarousel = styled.div` + width: 40%; + height: auto; + @media screen and (max-width: 800px) { + width: 100%; + } +`; + +export const ProductInfoBox = styled.div` + margin: 2%; +`; + +export const ProductDescription = styled.div` + text-align: center; + max-width: 400px; +`; + +export const Detail = styled.h3` + @media screen and (max-width: 800px) { + font-size: 16px; + text-align: center; + } +`; diff --git a/components/Category/Category.tsx b/components/Category/Category.tsx new file mode 100644 index 00000000..320a8e19 --- /dev/null +++ b/components/Category/Category.tsx @@ -0,0 +1,270 @@ +import * as React from "react"; +import { useRouter } from "next/router"; +import { QueryClient } from "react-query"; +import { dehydrate } from "react-query/hydration"; +import { fetchStreams, fetchProducts, useProducts, useStreams } from "../../hooks"; +import { Layout } from "../components"; +import { useProduct, fetchProduct } from "../../hooks/useProduct"; +import { useMutation, useQueryClient } from "react-query"; +import { addItemToCart } from "../../hooks/useCart"; +import { QueryKeys } from "../../hooks/queryKeys"; +import * as tracking from "../../config/tracking"; +import Featured from "../Home/Featured"; +import PolProductList from "../PolProductList"; +import { useMediaQuery } from "react-responsive"; +import homeData from "../Home/home.json"; +import { + CarouselProvider, + Slider, + Slide, + ButtonBack, + ButtonNext, + Image, + ImageWithZoom +} from "pure-react-carousel"; +import "pure-react-carousel/dist/react-carousel.es.css"; +// import ProductCard from "../components"; + +import { + ProductContainer, + ProductImageCarousel, + ProductInfoBox, + ProductDescription, + Detail +} from "./Category.styles"; + +const settings = { + speed: 500, + dots: false, + Infinite: false +}; + +export const Category = () => { + const router = useRouter(); + const isMobile = useMediaQuery({ maxWidth: 767 }); + const { id } = router.query; + const { data, isLoading, isSuccess } = useProduct(`${id}`); + const queryClient = useQueryClient(); + const addToCart = useMutation(addItemToCart, { + onSuccess: () => { + queryClient.invalidateQueries(QueryKeys.CART); + } + }); + + const { + error: productError, + status: productStatus, + data: productData, + isLoading: productsAreLoading, + isSuccess: productIsSuccess + }: { error: any; status: any; data: any; isLoading: boolean; isSuccess: boolean } = useProducts( + 1 + ); + + const polProductList = isMobile ? null : ( + + ); + const latestProducts = isMobile ? null : ; + + React.useEffect(() => { + if (isSuccess) { + // On page load, set focus on the product contaniner, because otherwise the arrow keys (left/right) won't work + const productContainer = Array.from( + document.getElementsByClassName("product-container") + ).shift(); + + if (productContainer) { + (productContainer as HTMLElement).focus(); + } + + tracking.trackEvent({ + action: tracking.Action.VIEW_PRODUCT, + category: tracking.Category.PRODUCT_DETAIL, + label: data?.data?.attributes?.name + }); + } + }, [`${id}`, isSuccess]); + + if (isLoading) { + return
    Loading Product...
    ; + } + + if (isSuccess) { + const variants = data?.data.relationships.variants.data; + + const handleAddToCart = () => + addToCart.mutate({ + variant_id: Array.isArray(variants) ? variants[0].id : "", + quantity: 1 + }); + + const handleKeyPress = (event: React.KeyboardEvent) => { + switch (event.key) { + case "ArrowLeft": + case "ArrowRight": + let productId: number = parseInt(`${id}`); + productId = event.key == "ArrowLeft" ? productId - 1 : productId + 1; + + fetchProduct(`${productId}`) + .then((product) => { + router.push(`/${product?.data?.attributes?.slug}`); + }) + .catch(() => { + /* product not found */ + }); + break; + } + }; + + const imageSource = + Array.isArray(data?.included) && data?.included[0]?.attributes?.styles?.[2].url; + const source = imageSource + ? `http://localhost:8080${imageSource}` + : "https://via.placeholder.com/400x600"; + // const source = "https://via.placeholder.com/400x600"; + + return ( + + + {/*
    + +
    + +
    +
    + +
    +
    +
    */} + + + + + + + + + + + + + + + + Back + Next + + + + + +

    {data?.data?.attributes?.name}

    +

    {data?.data?.attributes?.description}

    +

    ${data?.data?.attributes?.price}

    + + + +
    + + + + + +
    + + +
    + +
    + Product Details +

    FABRIC : 100% POLYESTER BUST : 29”LENGTH : 25 1/2”

    + Model Info +

    + Model info goes here: +
    + Height: 5'8'' +
    + Bust: 32 +
    + Waist: 24'' +
    + Hip: 34" +
    + Wearing Size: XS +

    +
    +
    +
    + +

    you may also like:

    + {polProductList ? polProductList : <>} + + +
    + ); + } + + return
    PRODUCT NOT FOUND
    ; +}; + +export async function getServerSideProps() { + const queryClient = new QueryClient(); + + // await queryClient.prefetchQuery(["posts", 1], () => fetchPosts(1)); + await queryClient.prefetchQuery(["streams", 1], () => fetchStreams(1)); + await queryClient.prefetchQuery(["products", 1], () => fetchProducts(1)); + + return { + props: { + dehydratedState: dehydrate(queryClient) + } + }; +} diff --git a/components/Category/index.ts b/components/Category/index.ts new file mode 100644 index 00000000..aa97d92b --- /dev/null +++ b/components/Category/index.ts @@ -0,0 +1 @@ +export { Category } from "./Category"; diff --git a/components/ComingSoon/ComingSoon.styles.tsx b/components/ComingSoon/ComingSoon.styles.tsx new file mode 100644 index 00000000..0b1cd94a --- /dev/null +++ b/components/ComingSoon/ComingSoon.styles.tsx @@ -0,0 +1,33 @@ +import styled from "@emotion/styled"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + position: relative; + justify-content: center; + align-items: center; + width: auto; + min-height: 100vh; +`; + +export const Logo = styled.img` + width: auto; + height: 240px; + + @media screen and (max-width: ${(p) => p.theme.breakpoints.values.sm}px) { + width: 90%; + height: auto; + } +`; + +export const Text = styled.div` + text-align: center; + width: 425px; + top: 400px; + font-style: normal; + font-weight: normal; + font-size: 21.4598px; + line-height: 28px; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; +`; diff --git a/components/ComingSoon/ComingSoon.tsx b/components/ComingSoon/ComingSoon.tsx new file mode 100644 index 00000000..4c332450 --- /dev/null +++ b/components/ComingSoon/ComingSoon.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { NotifyForm, SocialLinks } from "../../components"; +import { Container, Logo, Text } from "./ComingSoon.styles"; + +export const ComingSoon = () => { + const mailChimpUrl = `${process.env.MAILCHIMP_URL}?u=${process.env.MAILCHIMP_ID}&id=${process.env.MAILCHIMP_U}`; + return ( + <> + + + A NEW MOBILE MUSIC EXPERIENCE COMING TO A “WHERE EVER YOU ARE” NEAR YOU. + + + + + ); +}; diff --git a/components/ComingSoon/index.tsx b/components/ComingSoon/index.tsx new file mode 100644 index 00000000..8e1195f6 --- /dev/null +++ b/components/ComingSoon/index.tsx @@ -0,0 +1 @@ +export { ComingSoon } from "./ComingSoon"; diff --git a/components/Footer/Footer.styles.tsx b/components/Footer/Footer.styles.tsx new file mode 100644 index 00000000..1d699b5e --- /dev/null +++ b/components/Footer/Footer.styles.tsx @@ -0,0 +1,130 @@ +import styled from "@emotion/styled"; +export const Container = styled.div` + background: ${(p) => p.theme.colors.black.primary}; + padding-top: 41px; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + padding-bottom: 68px; + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + padding-top: 14px; + } +`; +export const LogoDiv = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; +export const Grid = styled.div` + display: grid; + padding: 20px 260px; + justify-content: space-between; + grid-template-columns: 111px 148px 67px 173px; + justify-items: center; + font-family: "Bebas Neue"; + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + display: flex; + flex-direction: column; + padding: 40px 20px; + justify-content: start; + grid-template-columns: [a]29.6vw 36.5vw; + justify-content: space-between; + row-gap: 11.2vw; + justify-items: start; + } +`; +export const IconLinksMo = styled.div` + justify-content: center; + align-items: center; + margin-top: 68px; + display: none; + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + display: flex; + } +`; +export const MobileIconLink = styled.a``; +export const Column = styled.div` + display: flex; + flex-direction: column; +`; +export const ColumnTitle = styled.div` + font-family: "Bebas Neue"; + font-size: 14px; + line-height: 17px; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + font-weight: 400; + margin-bottom: 22px; + white-space: nowrap; + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + margin-bottom: 9px; + font-size: 20px; + line-height: 24px; + color: ${(p) => p.theme.colors.gray.medium}; + } +`; +export const ColumnSubTitle = styled.div` + font-family: "Bebas Neue"; +`; +export const LinkItem = styled.a` + font-size: 14px; + line-height: 150%; + color: ${(p) => p.theme.colors.gray.medium}; + font-weight: 400; + font-family: "Bebas Neue"; +`; +export const Description = styled.div` + color: ${(p) => p.theme.colors.gray.medium}; + font-size: 14px; + line-height: 150%; + font-weight: 400; + margin-bottom: 5px; + font-family: "Bebas Neue"; +`; +export const IconLink = styled.a``; +export const IconLinkWrapper = styled.div` + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + display: none; + } + @media (max-width: 750px) { + display: none; + } +`; + +export const CameraIcon = styled.img` + width: 11px; + height: auto; + margin-right: 3.88px; + @media (max-width: 375px) { + width: 20.71px; + height: auto; + margin-right: 7.76px; + } +`; +export const FacebookIcon = styled.img` + width: 6.81px; + height: auto; + margin-right: 3.88px; + @media (max-width: 375px) { + width: 13.63px; + height: auto; + margin-right: 7.76px; + } +`; +export const PlayIcon = styled.img` + width: 12.29px; + height: auto; + margin-right: 3.88px; + @media (max-width: 375px) { + width: 24.59px; + height: auto; + margin-right: 7.76px; + } +`; +export const CircleIcon = styled.img` + width: 10.35px; + height: auto; + @media (max-width: 375px) { + width: 20.71px; + height: auto; + } +`; diff --git a/components/Footer/Footer.tsx b/components/Footer/Footer.tsx new file mode 100644 index 00000000..8efdc0f8 --- /dev/null +++ b/components/Footer/Footer.tsx @@ -0,0 +1,102 @@ +import React, { ReactNode } from "react"; +import classnames from "classnames"; +import { SocialLinks } from ".."; + +import { + Container, + Grid, + LogoDiv, + Column, + ColumnTitle, + ColumnSubTitle, + LinkItem, + Description, + IconLink, + IconLinkWrapper, + IconLinksMo, + MobileIconLink +} from "./Footer.styles"; + +export type CLASSESTYPE = { + root?: string; + grid?: string; + columnClassWrapper?: string; + columnTitle?: string; + subTitle?: string; + linkItem?: string; + description?: string; + iconWrapperClass?: string; +}; +export type Link = { + url: string; + text?: string; +}; +export type IconLink = { + icon: ReactNode; + url: string; +}; +export type Column = { + title?: string; + subTitle?: string; + links?: Link[]; + descriptions?: string[]; + iconLinks?: IconLink[]; +}; +export type FooterDataType = { + logo?: ReactNode; + columns: Column[]; + mobileIconLinks?: IconLink[]; +}; +export interface FootProps { + classes?: CLASSESTYPE; + footerData: FooterDataType; +} +export const Footer: React.FC = ({ classes, footerData }) => { + const Logo = footerData.logo as React.ComponentType; + const gridClass = classes?.grid || ""; + const columnClass = classes?.columnClassWrapper || ""; + const columnTitleClass = classes?.columnTitle || ""; + const subTitleClass = classes?.subTitle || ""; + const linkItemClass = classes?.linkItem || ""; + const descClass = classes?.description || ""; + const iconWrapperClass = classes?.iconWrapperClass || ""; + const columns = footerData.columns; + const mobileIconLinks = footerData.mobileIconLinks; + return ( + + {Logo && {Logo}} + + {columns.map((item, index) => ( + + {item.title && {item.title}} + {item.subTitle && ( + {item.subTitle} + )} + {item.links && + item.links.map((v, i) => ( + + {v.text} + + ))} + {item.descriptions && + item.descriptions.map((desc, idx) => ( + + {desc} + + ))} + {item.iconLinks && ( + + {item.iconLinks.map((icon, iconId) => ( + + {icon.icon} + + ))} + + )} + + ))} + + + + ); +}; diff --git a/components/Footer/footer.json b/components/Footer/footer.json new file mode 100644 index 00000000..c325852e --- /dev/null +++ b/components/Footer/footer.json @@ -0,0 +1,53 @@ +[ + { + "title": "Contact Info", + "descriptions": [ + "4920 S. Soto St.\n + Vernon, CA, 90058", + "+1 (310) 715-1370", + "ecom@polclothing.com" + ] + }, + { + "title": "Information", + "links": [ + { + "text": "accessibility statement", + "url": "" + }, + { + "text": "CA Privacy Right", + "url": "" + }, + { + "text": "Prop 65", + "url": "" + }, + { + "text": "Rewards", + "url": "" + }, + { "text": "Returns / exchanges / damages", "url": "/privacy" }, + { "text": "Terms of Use & Privacy Policy", "url": "/terms" }, + { "text": "contact us", "url": "/contact" } + ] + }, + { + "title": "My Account", + "links": [ + { + "text": "Customer Info", + "url": "" + }, + { "text": "Addresses", "url": "" }, + { "text": "Orders", "url": "" }, + { "text": "My Cart", "url": "" }, + { "text": "WishList", "url": "" } + ] + }, + { + "title": "About Us", + "descriptions": [ + "POL Clothing is a wholesale supplier to boutiques all over the world. POL focuses on coming together at the crossroads of fashion and business and creating styles inspired by the world around us." + ] + } +] diff --git a/components/Footer/index.tsx b/components/Footer/index.tsx new file mode 100644 index 00000000..5fca53f7 --- /dev/null +++ b/components/Footer/index.tsx @@ -0,0 +1 @@ +export * from "./Footer"; diff --git a/components/FormikWrappers/FormikAutocomplete.tsx b/components/FormikWrappers/FormikAutocomplete.tsx new file mode 100644 index 00000000..c57a6d62 --- /dev/null +++ b/components/FormikWrappers/FormikAutocomplete.tsx @@ -0,0 +1,113 @@ +import React, { useState, useEffect } from "react"; +// import { setFieldValue } from 'formik'; +import { useField } from "formik"; + +import { TextField } from "@material-ui/core"; + +import PlacesAutocomplete, { geocodeByAddress, getLatLng } from "react-places-autocomplete"; + +import { + Error, + HiddenInput, + SuggestionWrapper, + SuggestionLoader, + SuggestionItem +} from "./FormikInput.styles"; + +// interface FormikAutocompleteType { +// name: string; +// type: string; +// placeholder: string; +// props: any; +// } + +// Reference: https://github.com/mui-org/material-ui/issues/18331#issuecomment-569981389 +export const FormikAutocomplete = ({ + field, + fields: { ...fields }, + form: { setFieldValue, touched, errors }, + form, + addressType, + ...props +}: any) => { + const [homeAddress, setHomeAddress] = useState(""); + const [addressSelected, setAddressSelected] = useState(false); + + // const { setTouched, setFieldValue, name } = props; + + const handleChange = (input: any) => { + // console.log('changing'); + setAddressSelected(false); + setHomeAddress(input); + // setFieldValue(name, search, false); + }; + + const handleSelect = (address: any) => { + // geocodeByAddress(address) + // .then(results => getLatLng(results[0])) + // .then(latLng => console.log('Success', latLng)) + // .catch(error => console.error('Error', error)); + setAddressSelected(true); + setHomeAddress(address.address); + setFieldValue(address.name, address.address, false); + }; + + const addressTitle = addressType ? addressType + " " : null; + + return ( + <> + handleSelect({ name: field.name, address })} + > + {({ suggestions, getInputProps, getSuggestionItemProps, loading }) => ( + <> + + {!addressSelected && homeAddress !== "" && ( + + {loading && Loading...} + {suggestions.map((suggestion) => { + const className = suggestion.active + ? "suggestion-item active" + : "suggestion-item"; + return ( + + {suggestion.description} + + ); + })} + + )} + + )} + + {/* + + */} + {/* setHomeAddress(name, event.target.value) } + // onChange={ () => console.log('TYPE TYPE')} + onBlur={ () => setTouched({ [name]: true }) } + /> */} + {/* {touched[field.name] && errors[field.name] ? {errors[field.name]} : ""} */} + + ); +}; diff --git a/components/FormikWrappers/FormikCheckbox.tsx b/components/FormikWrappers/FormikCheckbox.tsx new file mode 100644 index 00000000..b4d66598 --- /dev/null +++ b/components/FormikWrappers/FormikCheckbox.tsx @@ -0,0 +1,35 @@ +import React from "react"; + +import { TermsCheckbox } from "./FormikInput.styles"; + +export const FormikCheckbox = ({ + field, + fields: { ...fields }, + form: { setFieldValue, touched, errors }, + accepted, + handleTermCheckbox, + nextTerm, + ...props +}: any) => { + const nextParent = () => { + console.log("TERM CHECKED: ", field.name, field.value); + handleTermCheckbox(); + setFieldValue(field.name, !accepted, false); + setTimeout(() => nextTerm(), 333); + }; + + return ( + <> + + + ); +}; diff --git a/components/FormikWrappers/FormikDateOfBirth.tsx b/components/FormikWrappers/FormikDateOfBirth.tsx new file mode 100644 index 00000000..4548560d --- /dev/null +++ b/components/FormikWrappers/FormikDateOfBirth.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { TextField } from "@material-ui/core"; + +import { Error } from "./FormikInput.styles"; + +export const FormikDateOfBirth = ({ + field: { ...fields }, + form: { touched, errors }, + ...props +}: any) => ( + <> + + {touched[fields.name] && errors[fields.name] ? {errors[fields.name]} : ""} + +); diff --git a/components/FormikWrappers/FormikEmail.tsx b/components/FormikWrappers/FormikEmail.tsx new file mode 100644 index 00000000..67f56d44 --- /dev/null +++ b/components/FormikWrappers/FormikEmail.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { TextField } from "@material-ui/core"; +// import { Field, useFormikContext } from 'formik'; + +import { Error } from "./FormikInput.styles"; + +const FormikInput = ({ + field: { ...fields }, + form: { touched, errors }, + styles, + ...props +}: any) => ( + <> + + {touched[fields.name] && errors[fields.name] ? {errors[fields.name]} : ""} + +); +export default FormikInput; diff --git a/components/FormikWrappers/FormikIncome.tsx b/components/FormikWrappers/FormikIncome.tsx new file mode 100644 index 00000000..b4b0fa99 --- /dev/null +++ b/components/FormikWrappers/FormikIncome.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { TextField } from "@material-ui/core"; + +import { Error } from "./FormikInput.styles"; + +export const FormikIncome = ({ + field, + field: { ...fields }, + form: { touched, errors, name }, + ...props +}: any) => ( + <> + {/* */} + + {touched[fields.name] && errors[fields.name] ? {errors[fields.name]} : ""} + +); diff --git a/components/FormikWrappers/FormikInput.styles.tsx b/components/FormikWrappers/FormikInput.styles.tsx new file mode 100644 index 00000000..fd5ca4cd --- /dev/null +++ b/components/FormikWrappers/FormikInput.styles.tsx @@ -0,0 +1,80 @@ +import styled from "@emotion/styled"; +import { TextField, Checkbox } from "@material-ui/core"; + +export const BasicField = styled(TextField)``; + +export const Error = styled.div` + color: ${(props: any) => props.theme.colors.red.primary}; + text-align: left; +`; + +export const HiddenInput = styled.div` + display: none; +`; + +export const SuggestionWrapper = styled.div` + background: ${(props: any) => props.theme.colors.white.primary}; + position: relative; + margin-top: 15px; + align-items: flex-start; + justify-content: center; + flex-flow: column nowrap; + min-width: 250px; + max-width: 500px; + max-height: 200px; + overflow: scroll; + z-index: 1; + display: flex; + box-shadow: 1px 3px 30px rgba(0, 0, 0, 0.23); + margin-top: 15px; + border-radius: 3px; + border: 1px solid ${(props: any) => props.theme.colors.gray.light}; + transition: all 0.3s ease-in-out; + + @media (max-width: ${(props: any) => props.theme.breakpoints.values.sm}px) { + padding: 20px 0 60px 0; + justify-content: top; + } +`; + +export const SuggestionLoader = styled.div` + margin: 0 auto; +`; + +export const SuggestionItem = styled.div` + align-self: flex-start; + padding: 5px 10px; + width: 100%; + text-align: left; + color: ${(props: any) => props.theme.colors.gray.medium}; + &.active { + cursor: pointer; + color: ${(props: any) => props.theme.colors.brand.primary}; + background: ${(props: any) => props.theme.colors.gray.background}; + } +`; + +interface TermsCheckboxType { + accepted: boolean; + theme: any; +} + +export const TermsCheckbox = styled(Checkbox)` + flex-basis: 5%; + + > .checkbox-label { + color: ${(props: any) => + props.accepted ? props.theme.colors.brand.dark : props.theme.colors.red.primary}; + } + + .checkbox-style { + border-color: ${(props: any) => + props.accepted ? props.theme.colors.brand.dark : props.theme.colors.red.primary}; + stroke: ${(props: any) => + props.accepted ? props.theme.colors.brand.dark : props.theme.colors.red.primary}; + } +`; + +export const Wrapper = styled.div` + padding: 15px; +`; diff --git a/components/FormikWrappers/FormikInput.tsx b/components/FormikWrappers/FormikInput.tsx new file mode 100644 index 00000000..d71dfcab --- /dev/null +++ b/components/FormikWrappers/FormikInput.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { TextField } from "@material-ui/core"; +// import { Field, useFormikContext } from 'formik'; + +import { BasicField, Error } from "./FormikInput.styles"; + +export const FormikInput = ({ + field: { ...fields }, + form: { touched, errors }, + styles, + ...props +}: any) => ( + <> + + {touched[fields.name] && errors[fields.name] ? {errors[fields.name]} : ""} + +); diff --git a/components/FormikWrappers/FormikPassword.tsx b/components/FormikWrappers/FormikPassword.tsx new file mode 100644 index 00000000..47a86b55 --- /dev/null +++ b/components/FormikWrappers/FormikPassword.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Input } from "@material-ui/core"; +// import { Field, useFormikContext } from 'formik'; + +import { Error } from "./FormikInput.styles"; + +const FormikInput = ({ + field: { ...fields }, + form: { touched, errors }, + styles, + ...props +}: any) => ( + <> + + {touched[fields.name] && errors[fields.name] ? {errors[fields.name]} : ""} + +); +export default FormikInput; diff --git a/components/FormikWrappers/FormikPhone.tsx b/components/FormikWrappers/FormikPhone.tsx new file mode 100644 index 00000000..59839d2d --- /dev/null +++ b/components/FormikWrappers/FormikPhone.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import InputMask from "react-input-mask"; +import { TextField } from "@material-ui/core"; +// import { Field, useFormikContext } from 'formik'; + +import { Error } from "./FormikInput.styles"; + +export const FormikPhone = ({ + field: { ...fields }, + form: { touched, errors }, + styles, + ...props +}: any) => ( + <> + {/* */} + + {(inputProps: any) => } + + {touched[fields.name] && errors[fields.name] ? {errors[fields.name]} : ""} + +); diff --git a/components/FormikWrappers/index.ts b/components/FormikWrappers/index.ts new file mode 100644 index 00000000..d3e84a48 --- /dev/null +++ b/components/FormikWrappers/index.ts @@ -0,0 +1,6 @@ +export { FormikInput } from "./FormikInput"; +export { FormikDateOfBirth } from "./FormikDateOfBirth"; +export { FormikIncome } from "./FormikIncome"; +export { FormikCheckbox } from "./FormikCheckbox"; +export { FormikAutocomplete } from "./FormikAutocomplete"; +export { FormikPhone } from "./FormikPhone"; diff --git a/components/GamePlayer/GamePlayer.styles.tsx b/components/GamePlayer/GamePlayer.styles.tsx new file mode 100644 index 00000000..9a60641d --- /dev/null +++ b/components/GamePlayer/GamePlayer.styles.tsx @@ -0,0 +1 @@ +import styled from "@emotion/styled"; diff --git a/components/GamePlayer/GamePlayer.tsx b/components/GamePlayer/GamePlayer.tsx new file mode 100644 index 00000000..982b280f --- /dev/null +++ b/components/GamePlayer/GamePlayer.tsx @@ -0,0 +1,42 @@ +import React, { useRef, useState, useEffect } from "react"; +import * as THREE from "three"; +import { Canvas, useFrame } from "@react-three/fiber"; + +const Box = (props: JSX.IntrinsicElements["mesh"]) => { + const ref = useRef(null!); + const [hovered, hover] = useState(false); + const [clicked, click] = useState(false); + useFrame((state, delta) => (ref.current.rotation.x += 0.01)); + return ( + click(!clicked)} + onPointerOver={(event) => hover(true)} + onPointerOut={(event) => hover(false)} + > + + + + ); +}; + +// export const GamePlayer = (props: any): => { +export const GamePlayer = (props: any) => { + const ref = useRef(null!); + const [hovered, hover] = useState(false); + const [clicked, click] = useState(false); + useFrame((state, delta) => (ref.current.rotation.x += 0.01)); + + // console.log("data: ", data); + + return ( + + + + + + + ); +}; diff --git a/components/GamePlayer/index.ts b/components/GamePlayer/index.ts new file mode 100644 index 00000000..7accdef7 --- /dev/null +++ b/components/GamePlayer/index.ts @@ -0,0 +1 @@ +export { GamePlayer } from "./GamePlayer"; diff --git a/components/Header/Header.styles.tsx b/components/Header/Header.styles.tsx new file mode 100644 index 00000000..a3b057e5 --- /dev/null +++ b/components/Header/Header.styles.tsx @@ -0,0 +1,266 @@ +import styled from "@emotion/styled"; +import { Popover } from "@material-ui/core"; +import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"; +import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder"; +import { pxIphone } from "../../utils"; + +export const TopHeader = styled.div` + padding: 10px 0 12px 0; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + @media (max-width: ${(p) => p.theme.breakpoints.values.sm}px) { + height: 30px; + } +`; + +export const LogoDiv = styled.div` + width: 355px; + padding: 15px 30px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +`; +export const HeaderDiv = styled.header` + z-index: 9; +`; + +export interface LinkDivProps { + isActive: boolean; +} + +export const LinkDiv = styled.a` + font-size: 14px; + text-decoration: none; + &:hover { + color: ${(p) => p.theme.colors.brand.primary}; + } +`; + +export const BottomHeader = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + flex-wrap: wrap; + padding: 0px; + & > :first-of-type { + padding-left: 0px; + } + & > :last-child { + padding-right: 0px; + } +`; + +export const Category = styled.a` + padding: 15px; +`; + +export const LeftSide = styled.div` + width: auto; + display: flex; + position: absolute; + top: 0; + left: 10px; + flex-direction: row; + align-items: center; + justify-content: space-between; + z-index: 2; + + @media (max-width: ${(p) => p.theme.breakpoints.values.sm}px) { + justify-content: flex-end; + } +`; + +export const RightSide = styled.div` + width: 33%; + display: flex; + position: absolute; + top: 0; + right: 10px; + flex-direction: row; + align-items: center; + justify-content: space-between; + z-index: 2; + + @media (max-width: ${(p) => p.theme.breakpoints.values.sm}px) { + justify-content: flex-end; + } +`; + +export const HeaderOptions = styled.div` + display: flex; + flex-direction: row; + justify-content: space-evenly; + margin: 0 20px; + justify-content: space-around; + width: 200px; + & > a { + font-family: ${(p) => p.theme.typography.titleMD.fontFamily}; + font-size: ${(p) => p.theme.typography.titleMD.fontSize}; + font-weight: ${(p) => p.theme.typography.titleMD.fontWeight}; + line-height: ${(p) => p.theme.typography.titleMD.lineHeight}; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + } + @media (max-width: 768px) { + display: none; + } +`; + +export const CartToggle = styled.div` + margin: -10px 3px 0 0; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + + @media (min-width: ${(p) => p.theme.breakpoints.values.xs}px) { + & span.MuiBadge-root span.MuiBadge-badge { + top: 5px !important; + } + } + + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + margin: 10px 10px 0 0; + } +`; + +export const HeaderAccount = styled.div` + display: flex; + flex-direction: row; + justify-content: space-evenly; + margin: 0 20px; + justify-content: space-around; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + @media (max-width: ${(p) => p.theme.breakpoints.values.sm}px) { + display: none; + } +`; + +export const AccountEmail = styled.div` + font-family: ${(p) => p.theme.typography.titleMD.fontFamily}; + display: flex; + justify-content: center; + align-items: center; + margin: 0 10px; + cursor: pointer; +`; + +export const AccountMenu = styled(Popover)` + font-family: ${(p) => p.theme.typography.titleMD.fontFamily}; + text-align: right; + & div.MuiPaper-root { + width: 160px; + padding: 20px; + top: 45px !important; + left: auto !important; + right: 105px; + background-color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.gray.dark : p.theme.colors.white.primary}; + } +`; + +export const AccountOption = styled.div` + cursor: pointer; + margin: 5px 0; + &:first-of-type { + margin: 0; + } + + & > a { + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + } +`; + +export const ArrowDown = styled(ArrowDropDownIcon)` + margin-left: 5px; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + @media (max-width: 768px) { + display: none !important; + } +`; + +export const ShoppingCart = styled.img` + width: 17px; + height: auto; + margin-left: 21px; + margin-right: 13px; + @media (max-width: 768px) { + display: none !important; + } +`; +export const FavoriteIcon = styled(FavoriteBorderIcon)` + font-size: 12px; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + margin-right: 13px; + @media (max-width: 768px) { + display: none !important; + } +`; +export const UserIconMo = styled.img` + display: none; + @media (max-width: 768px) { + display: block; + width: ${pxIphone(19)}; + height: auto; + margin-right: ${pxIphone(12)}; + } +`; +export const CartMo = styled.img` + display: none; + @media (max-width: 768px) { + display: block; + width: ${pxIphone(17)}; + height: auto; + margin-right: ${pxIphone(15)}; + } +`; +export const CustomIcon = styled.img` + width: ${pxIphone(37)}; + height: auto; +`; + +export const CameraIcon = styled.img` + width: 11px; + height: auto; + margin-right: 3.88px; + @media (max-width: 375px) { + width: 20.71px; + height: auto; + margin-right: 7.76px; + } +`; +export const FacebookIcon = styled.img` + width: 6.81px; + height: auto; + margin-right: 3.88px; + @media (max-width: 375px) { + width: 13.63px; + height: auto; + margin-right: 7.76px; + } +`; +export const PlayIcon = styled.img` + width: 12.29px; + height: auto; + margin-right: 3.88px; + @media (max-width: 375px) { + width: 24.59px; + height: auto; + margin-right: 7.76px; + } +`; +export const CircleIcon = styled.img` + width: 10.35px; + height: auto; + @media (max-width: 375px) { + width: 20.71px; + height: auto; + } +`; diff --git a/components/Header/Header.tsx b/components/Header/Header.tsx new file mode 100644 index 00000000..db1e2a79 --- /dev/null +++ b/components/Header/Header.tsx @@ -0,0 +1,147 @@ +import React, { useState, useEffect, useRef } from "react"; +import { useMediaQuery } from "react-responsive"; +import { useRouter } from "next/router"; +import Link from "next/link"; +import { Badge } from "@material-ui/core"; +import Sticky from "react-sticky-el"; +import { HeaderProps } from "./types"; +import { useAuth } from "../../config/auth"; +import { useCart } from "../../hooks/useCart"; +import { MyLogo } from "../Layout/Layout"; +import SearchBar from "../SearchBar"; +import { MainMenu } from "../MainMenu"; +import { menusData } from "../MainMenu/data/menusData"; +import { CartSidebar } from "../CartSidebar/CartSidebar"; +import { SocialLinks } from ".."; + +import { + TopHeader, + LeftSide, + RightSide, + LogoDiv, + HeaderDiv, + LinkDiv, + BottomHeader, + Category, + UserIconMo, + CartMo, + CartToggle, + HeaderAccount, + HeaderOptions, + ArrowDown, + ShoppingCart, + FavoriteIcon, + AccountEmail, + AccountMenu, + AccountOption +} from "./Header.styles"; + +const dummyCategories = ["Best Sellers", "Latest", "Seasonal", "Luxury", "On Sale", "Coming Soon"]; + +export const Header: React.FC = ({ darkMode }) => { + const { pathname } = useRouter(); + const { user, logout } = useAuth(); + const isMobile = useMediaQuery({ maxWidth: 767 }); + const [cartVisible, setCartVisible] = useState(false); + const [accountVisible, setAccountVisible] = useState(false); + const [accountElem, setAccountElem] = useState(null); + const accountRef = useRef(null); + const accountOpen = Boolean(accountElem); + const accountId = accountVisible ? "simple-popover" : undefined; + const toggleCart = () => setCartVisible((isVisible) => !isVisible); + const toggleAccount = () => setAccountVisible((isVisible) => !isVisible); + const isMaint = process.env.IS_MAINT_MODE; + + const { data: cartData, isLoading: cartIsLoading, isError: cartHasError } = useCart(); + + const handleAccount = (event: any) => { + setAccountElem(event.currentTarget); + }; + + const handleCloseAccount = () => { + setAccountElem(null); + }; + + if (isMaint && isMaint === "true") { + return null; + } + + useEffect(() => { + // console.log(user && user.data.attributes); + }, []); + + return ( + + + {!isMobile && ( + + + + )} + + + + + + + + + {isMobile ? null : } + {user ? ( + + + {user.data.attributes.email} + + + + +
    Account Settings
    +
    + +
    Need Help?
    +
    +
    + + + +
    + {/* */} + + + +
    + ) : ( + + + LOG IN + + + SIGN UP + + + )} + + + + + +
    +
    +
    + ); +}; diff --git a/components/Header/index.tsx b/components/Header/index.tsx index a8c7b094..c940126c 100644 --- a/components/Header/index.tsx +++ b/components/Header/index.tsx @@ -1,33 +1 @@ -import React from "react"; -import { useRouter } from "next/router"; -import styled from '@emotion/styled'; -import Link from "next/link"; -import { param } from "jquery"; -const HeaderMargin= styled.header` -margin-bottom: 25px; -` -const AnchorTag = styled.a( - { - fontSize: 20, - marginRight:15, - textDecoration:'none', - cursor:'pointer' - }, - props => ({ textDecoration: props.color }) -) -export const Header = () => { - const { pathname } = useRouter(); - - return ( - - - Home - - - - Client-Only - - - - ); -}; +export { Header } from "./Header"; diff --git a/components/Header/types/index.d.ts b/components/Header/types/index.d.ts new file mode 100644 index 00000000..1ae90795 --- /dev/null +++ b/components/Header/types/index.d.ts @@ -0,0 +1,3 @@ +export interface HeaderProps { + darkMode?: boolean; +} diff --git a/components/Home/Banner/Banner.styles.tsx b/components/Home/Banner/Banner.styles.tsx new file mode 100644 index 00000000..233899af --- /dev/null +++ b/components/Home/Banner/Banner.styles.tsx @@ -0,0 +1,29 @@ +import styled from "@emotion/styled"; +import { pxPC } from "../../../utils"; +import ButtonBase from "@material-ui/core/ButtonBase"; +import { XlargeTitle } from "../../../styles/BaseStyles"; +export const BannerContainer = styled.div` + position: relative; + margin: ${pxPC(36)} 0 -5px 0; +`; +export const BannerTitle = styled(XlargeTitle)``; +export const BannerImg = styled.img` + object-fit: cover; + min-width: 100%; + margin-top: ${pxPC(30)}; +`; +export const BannerBtn = styled(ButtonBase)` + width: ${pxPC(228)}; + height: ${pxPC(35)}; + position: absolute !important; + bottom: ${pxPC(43)}; + margin: auto !important; + background-color: #eb8b8b !important; + color: #fff !important; + left: 0; + right: 0; + font-family: "Bebas Neue"; + font-size: ${pxPC(18)}; + line-height: ${pxPC(22)}; + text-align: center; +`; diff --git a/components/Home/Banner/Banner.tsx b/components/Home/Banner/Banner.tsx new file mode 100644 index 00000000..23bb1f06 --- /dev/null +++ b/components/Home/Banner/Banner.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { BannerContainer, BannerTitle, BannerImg, BannerBtn } from "./Banner.styles"; +export type bannerData = { + img: string; +}; +export interface BannerProps { + data: bannerData; +} +const Banner: React.FC = (props) => { + const { data } = props; + return ( + + + BUTTON + + ); +}; + +export default Banner; diff --git a/components/Home/Banner/index.tsx b/components/Home/Banner/index.tsx new file mode 100644 index 00000000..907b6e02 --- /dev/null +++ b/components/Home/Banner/index.tsx @@ -0,0 +1 @@ +export { default } from "./Banner"; diff --git a/components/Home/Featured/Featured.styles.tsx b/components/Home/Featured/Featured.styles.tsx new file mode 100644 index 00000000..87595b19 --- /dev/null +++ b/components/Home/Featured/Featured.styles.tsx @@ -0,0 +1,49 @@ +import styled from "@emotion/styled"; +import { pxPC } from "../../../utils"; +import { XlargeTitle } from "../../../styles/BaseStyles"; +import { ButtonBase } from "@material-ui/core"; +export const FeaturedContainer = styled.div` + margin-top: ${pxPC(30)}; +`; +export const FeaturedTitle = styled(XlargeTitle)``; +export const FeaturedBox = styled.div` + margin-top: ${pxPC(30)}; + flex-wrap: nowrap; + display: flex; + justify-content: space-between; + align-items: center; + + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + margin-top: 0; + flex-wrap: wrap; + flex-direction: column; + } +`; +export const FeaturedItem = styled.div` + width: 100%; + position: relative; + margin-bottom: ${pxPC(20)}; + @media (max-width: ${(p) => p.theme.breakpoints.values.xs}px) { + } +`; +export const FeaturedImg = styled.img` + object-fit: cover; + width: 100%; +`; +export const FeaturedButton = styled(ButtonBase)` + background: ${(p) => p.theme.colors.brand.primary} !important; + /* width: ${pxPC(234)}; + height: ${pxPC(36)}; */ + width: 50%; + position: absolute !important; + bottom: ${pxPC(84)}; + left: 0; + right: 0; + margin: auto !important; + color: #fff !important; + font-family: "Bebas Neue"; + /* font-size: ${pxPC(18)}; + line-height: ${pxPC(22)}; */ + color: #fff; + text-align: center; +`; diff --git a/components/Home/Featured/Featured.tsx b/components/Home/Featured/Featured.tsx new file mode 100644 index 00000000..f43f95b1 --- /dev/null +++ b/components/Home/Featured/Featured.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { + FeaturedContainer, + FeaturedButton, + FeaturedImg, + FeaturedItem, + FeaturedBox, + FeaturedTitle +} from "./Featured.styles"; +export type LatestFeatured = { + img: string; +}; +export interface FeaturedProps { + data: LatestFeatured[]; + title: string; +} +const Featured: React.FC = (props) => { + const { data } = props; + return ( + + {props.title} + + {data.map((item, index) => ( + + + BUTTON + + ))} + + + ); +}; +export default Featured; diff --git a/components/Home/Featured/index.tsx b/components/Home/Featured/index.tsx new file mode 100644 index 00000000..cf0edeb0 --- /dev/null +++ b/components/Home/Featured/index.tsx @@ -0,0 +1 @@ +export { default } from "./Featured"; diff --git a/components/Home/Hero/Hero.styles.tsx b/components/Home/Hero/Hero.styles.tsx new file mode 100644 index 00000000..081d50a2 --- /dev/null +++ b/components/Home/Hero/Hero.styles.tsx @@ -0,0 +1,51 @@ +import styled from "@emotion/styled"; +import { LargeTitle, BtnTitle } from "../../../styles/BaseStyles"; +import ButtonBase from "@material-ui/core/ButtonBase"; +import { pxIphone, pxPC } from "../../../utils"; +export const Container = styled.div` + display: flex; + justify-content: left; + height: ${pxPC(719)}; + background-image: url(/pol-hero.jpg); + background-size: cover; + position: relative; + @media (max-width: 768px) { + height: calc(100vh - ${pxIphone(60)}); + background-image: url("/pol-hero-mo.jpg"); + } +`; +export const HeroTitle = styled(LargeTitle)` + display: flex; + flex-direction: column; + position: absolute; + text-align: left; + left: ${pxPC(100)}; + bottom: ${pxPC(200)}; + @media (max-width: 768px) { + left: auto; + margin: 0 20px; + bottom: ${pxIphone(100)}; + font-size: ${pxIphone(33)}; + line-height: ${pxIphone(41)}; + } +`; +export const HeroBtn = styled(ButtonBase)` + width: ${pxPC(237)}; + height: ${pxPC(37)}; + background: #eb8b8b !important; + margin-top: ${pxPC(22)}!important; + color: #fff !important; + font-family: "Bebas Neue"; + font-size: ${pxPC(18)}; + line-height: ${pxPC(22)}; + color: #fff; + text-align: center; + @media (max-width: 768px) { + width: 50vw; + height: ${pxIphone(31)}; + font-size: ${pxIphone(20)}; + line-height: ${pxIphone(24)}; + margin-top: ${pxIphone(10)}!important; + padding: ${pxIphone(6)} ${pxIphone(20)} !important; + } +`; diff --git a/components/Home/Hero/Hero.tsx b/components/Home/Hero/Hero.tsx new file mode 100644 index 00000000..e4187603 --- /dev/null +++ b/components/Home/Hero/Hero.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { HeroBtn, HeroTitle, Container } from "./Hero.styles"; +export interface HeroProps {} +const Hero: React.FC = (props) => { + return ( + + + BROWSE ALL THE NEW + SPRING/ SUMMER LOOKS + SHOW ME + + + ); +}; +export default Hero; diff --git a/components/Home/Hero/index.tsx b/components/Home/Hero/index.tsx new file mode 100644 index 00000000..eada8b79 --- /dev/null +++ b/components/Home/Hero/index.tsx @@ -0,0 +1 @@ +export { default } from "./Hero"; diff --git a/components/Home/Home.styles.tsx b/components/Home/Home.styles.tsx new file mode 100644 index 00000000..929dc74a --- /dev/null +++ b/components/Home/Home.styles.tsx @@ -0,0 +1,4 @@ +import styled from "@emotion/styled"; +import { pxIphone, pxPC } from "../../utils"; + +export const Content = styled.div``; diff --git a/components/Home/Home.tsx b/components/Home/Home.tsx new file mode 100644 index 00000000..b4f4a998 --- /dev/null +++ b/components/Home/Home.tsx @@ -0,0 +1,124 @@ +import React from "react"; +import { useRef, useEffect } from "react"; +import { useQuery } from "react-query"; +import { QueryClient } from "react-query"; +import { dehydrate } from "react-query/hydration"; +import { Layout, InfoBox, ProductList } from "../../components"; +import { fetchPosts, fetchProducts } from "../../hooks"; +import Banner from "./Banner"; +import { Content } from "./Home.styles"; +import Featured from "./Featured"; +import MemberList from "./MemberList"; +import Products from "./Products"; +import PolProductList from "../../components/POLProductList"; +import { useMediaQuery } from "react-responsive"; +import MobileLatest from "./MobileLatest"; +import { Loading } from "../Loading"; +import homeData from "./home.json"; +import { VideoJS } from ".."; + +export const Home = (props: any) => { + const isMobile = useMediaQuery({ maxWidth: 767 }); + + const playerRef = useRef(null); + + const videoJsOptions = { + autoplay: true, + playsInline: true, + controls: false, + responsive: true, + preload: "auto", + muted: true, + fluid: true, + sources: [ + { + src: "pol-fw-21.mp4", + type: "video/mp4" + } + ] + }; + + const handlePlayerReady = ({ player }: any) => { + playerRef.current = player; + + // you can handle player events here + // player.on('waiting', () => { + // console.log('player is waiting'); + // }); + + // player.on('dispose', () => { + // console.log('player will dispose'); + // }); + }; + + const { + error: productsError, + status: productsStatus, + data: productsData, + isLoading: productsAreLoading, + isSuccess: productsIsSuccess + }: { error: any; status: any; data: any; isLoading: boolean; isSuccess: boolean } = useProducts( + 1 + ); + + const { + error: streamsError, + status: streamsStatus, + data: streamsData, + isLoading: streamsAreLoading, + isSuccess: streamsAreSuccess + }: { error: any; status: any; data: any; isLoading: boolean; isSuccess: boolean } = useStreams(1); + + const memberList = isMobile ? null : ; + const mobileMemberList = !isMobile ? null : ; + const advertListMobile = isMobile ? ( + + ) : null; + const polProductList = isMobile ? null : ( + // + + ); + const banner = isMobile ? null : ; + + useEffect(() => { + // console.log(streamsData?.response_data, productsData); + }, []); + + if (productsAreLoading || streamsAreLoading) { + return ; + } + + if (productsError || streamsError) { + return ; + } + + return ( + + + + {/* {memberList} */} + + {!productsAreLoading && polProductList} + {/* {mobileMemberList} */} + ; + + {advertListMobile} + {!productsAreLoading && polProductList} + {banner} + + + ); +}; + +export async function getServerSideProps() { + const queryClient = new QueryClient(); + + await queryClient.prefetchQuery(["posts", 10], () => fetchPosts(10)); + await queryClient.prefetchQuery(["products", 1], () => fetchProducts(1)); + + return { + props: { + dehydratedState: dehydrate(queryClient) + } + }; +} diff --git a/components/Home/MemberList/MemberList.styles.tsx b/components/Home/MemberList/MemberList.styles.tsx new file mode 100644 index 00000000..0b418d1a --- /dev/null +++ b/components/Home/MemberList/MemberList.styles.tsx @@ -0,0 +1,45 @@ +import styled from "@emotion/styled"; +import { pxIphone, pxPC } from "../../../utils"; +import Avatar from "@material-ui/core/Avatar"; +export const Title = styled.div` + font-size: 33px; + line-height: 41px; + color: #000; + text-align: center; + font-family: "Bebas Neue"; + margin-top: 19px; +`; +export const MySwiperContainer = styled.div` + height: ${pxPC(143)}; + margin-top: ${pxPC(30)}; + @media (max-width: 375px) { + margin-top: ${pxIphone(19)}; + height: ${pxIphone(132)}; + } +`; +export const MyAvatar = styled(Avatar)` + width: ${pxPC(89)}!important; + height: ${pxPC(89)}!important; + @media (max-width: 375px) { + width: ${pxIphone(89)}!important; + height: ${pxIphone(89)}!important; + } +`; +export const MemberName = styled.div` + margin-top: ${pxPC(9)}; + white-space: nowrap; + font-size: ${pxPC(18)}; + color: #000; + line-height: ${pxPC(22)}; + text-align: center; + font-family: "Bebas Neue"; + @media (max-width: 375px) { + margin-top: ${pxIphone(9)}; + line-height: ${pxIphone(24)}; + } +`; +export const MySlideWrap = styled.div` + display: flex !important; + flex-direction: column !important; + align-items: center !important; +`; diff --git a/components/Home/MemberList/MemberList.tsx b/components/Home/MemberList/MemberList.tsx new file mode 100644 index 00000000..356fd738 --- /dev/null +++ b/components/Home/MemberList/MemberList.tsx @@ -0,0 +1,30 @@ +import React, { useCallback } from "react"; +import SwiperCore, { Navigation, Thumbs } from "swiper/core"; +import { Swiper, SwiperSlide } from "swiper/react"; +import { MemberName, MyAvatar, MySwiperContainer, MySlideWrap } from "./MemberList.styles"; +import { useMediaQuery } from "react-responsive"; +export type member = { name: string; avatar: string }; +export interface MemberListProps { + data: member[]; +} +SwiperCore.use([Navigation]); +const MemberList: React.FC = (props) => { + const { data } = props; + const isMobile = useMediaQuery({ maxWidth: 767 }); + const onSwipe = useCallback((swipe) => {}, []); + return ( + + + {data.map((item, index) => ( + + + + {item.name} + + + ))} + + + ); +}; +export default MemberList; diff --git a/components/Home/MemberList/index.tsx b/components/Home/MemberList/index.tsx new file mode 100644 index 00000000..746ed2e0 --- /dev/null +++ b/components/Home/MemberList/index.tsx @@ -0,0 +1,33 @@ +import React, { useCallback, Fragment } from "react"; +import SwiperCore, { Navigation, Thumbs } from "swiper/core"; +import { Swiper, SwiperSlide } from "swiper/react"; +import { MemberName, MyAvatar, MySwiperContainer, MySlideWrap, Title } from "./MemberList.styles"; +import { useMediaQuery } from "react-responsive"; +export type member = { name: string; avatar: string }; +export interface MemberListProps { + data: member[]; +} +SwiperCore.use([Navigation]); +const MemberList: React.FC = (props) => { + const { data } = props; + const isMobile = useMediaQuery({ maxWidth: 767 }); + const onSwipe = useCallback((swipe) => {}, []); + return ( + + POL INFLUENCERS + + + {data.map((item, index) => ( + + + + {item.name} + + + ))} + + + + ); +}; +export default MemberList; diff --git a/components/Home/MobileLatest/MobileLatest.styles.tsx b/components/Home/MobileLatest/MobileLatest.styles.tsx new file mode 100644 index 00000000..fd3f1367 --- /dev/null +++ b/components/Home/MobileLatest/MobileLatest.styles.tsx @@ -0,0 +1,86 @@ +import styled from "@emotion/styled"; +import { pxIphone, pxPC } from "../../../utils"; +import Rating from "@material-ui/lab/Rating"; +import { DescText, PriceText } from "../../../styles/BaseStyles"; + +export const Container = styled.div` + margin-bottom: ${pxIphone(28)}; +`; +export const LatestTitle = styled.div` + text-align: center; + color: ${(p) => + p.theme.isDarkMode ? p.theme.colors.white.primary : p.theme.colors.black.primary}; + font-size: 44px; + height: 67px; + font-family: "Bebas Neue"; + text-decoration: underline; + margin-top: 19px; + margin-bottom: 19px; +`; +export const Grid = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: ${pxIphone(16.23)}; + row-gap: ${pxIphone(15)}; + justify-items: center; + justify-content: space-between; +`; +export const ProductBox = styled.div` + width: ${pxIphone(161)}; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +`; +export const ProductImg = styled.img` + width: 100%; + height: auto; +`; +export const ProductTitleBox = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-top: ${pxIphone(9.26)}; + align-self: stretch; +`; +export const ProductTitle = styled.div` + font-size: ${pxIphone(14)}; + line-height: 150%; +`; +export const ProductRate = styled(Rating)``; +export const ProductDesc = styled(DescText)` + align-self: flex-start; + margin-top: ${pxIphone(5.56)}; +`; +export const ThreeDotWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-top: ${pxIphone(5.56)}; + align-self: stretch; +`; +export const ThreeDot = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: ${pxIphone(3)}; +`; +const Dot = styled.div` + width: ${pxIphone(6.53)}; + height: ${pxIphone(6.53)}; + border-radius: 50%; + border: 1px solid #969696; + margin-right: ${pxIphone(3.76)}; +`; +export const Dot1 = styled(Dot)` + background: #cbc8bf; +`; +export const Dot2 = styled(Dot)` + background: #979d93; +`; +export const Dot3 = styled(Dot)` + background: #979d93; +`; +export const Price = styled(PriceText)` + text-align: right; +`; diff --git a/components/Home/MobileLatest/MobileLatest.tsx b/components/Home/MobileLatest/MobileLatest.tsx new file mode 100644 index 00000000..96cf04b8 --- /dev/null +++ b/components/Home/MobileLatest/MobileLatest.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { fetchStreams, fetchProducts, useProducts, useStreams } from "../../../hooks"; +import { ProductCard } from "../.."; +import { Container, Grid, LatestTitle } from "./MobileLatest.styles"; +export interface MobileLatestProps { + products: any; + title: string; +} +const MobileLatest: React.FC = (props) => { + const { title, products } = props; + return ( + + {title} + + {products?.data.map((item: any, index: any) => { + const defaultImg = + "https://static-assets.strikinglycdn.com/images/ecommerce/ecommerce-default-image.png"; + const productImg = item.relationships?.images?.data[0]?.id; + const allImages = products && products?.included?.filter((e: any) => e.type == "image"); + const foundImg = allImages.filter((e: any) => e["id"] == productImg); + const imgUrl = foundImg[0]?.attributes?.styles[4]?.url; + const imgSrc = productImg ? `${process.env.SPREE_API_URL}${imgUrl}` : defaultImg; + + let optionTypes = item.relationships?.option_types?.data; + let productOptionIds = optionTypes.map((i: any) => i.id); + let allOptions = + products && products?.included?.filter((e: any) => e.type == "option_value"); + let productVariantColors = + allOptions && allOptions?.filter((e: any) => e.attributes.presentation.includes("#")); + let foundOptions = productVariantColors.filter((i: any) => { + console.log("found: ", i.relationships.option_type.data.id); + return productOptionIds.includes(i.relationships.option_type.data.id); + }); + console.log(item, productImg, foundImg, imgUrl); + return ; + })} + + + ); +}; +export default MobileLatest; diff --git a/components/Home/MobileLatest/index.tsx b/components/Home/MobileLatest/index.tsx new file mode 100644 index 00000000..eba84628 --- /dev/null +++ b/components/Home/MobileLatest/index.tsx @@ -0,0 +1 @@ +export { default } from "./MobileLatest"; diff --git a/components/Home/Products/Products.styles.tsx b/components/Home/Products/Products.styles.tsx new file mode 100644 index 00000000..65f6957f --- /dev/null +++ b/components/Home/Products/Products.styles.tsx @@ -0,0 +1,132 @@ +import styled from "@emotion/styled"; +import { pxPC, pxIphone } from "../../../utils"; +import { PriceText, ProductTitle, XlargeTitle, XsmallText } from "../../../styles/BaseStyles"; +export const Title = styled(XlargeTitle)` + font-family: "Bebas Neue"; + margin-bottom: ${pxPC(30)}; + font-size: 44px; + line-height: 54px; + @media (max-width: 375px) { + margin-bottom: ${pxIphone(19)}; + } +`; +export const MySwiperWrap = styled.div` + margin-top: ${pxPC(30)}; + overflow: hidden; + @media (max-width: 375px) { + margin-top: ${pxIphone(19)}; + } +`; +export const MySlideWrap = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + margin-right: ${pxPC(14.99)}; +`; +export const ProductImgOutterBox = styled.div` + position: relative; + width: 100%; +`; +export const ProductImg = styled.img` + object-fit: cover; + width: 100%; +`; +export const ProductMask = styled.div` + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.66); ; +`; +export const MaskTitle = styled.div` + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: 114px; + height: 90px; + margin: auto; + font-size: 24px; + line-height: 30px; + font-family: "Bebas Neue"; + text-align: center; + color: #707070; + @media (max-width: 375px) { + font-size: 19px; + line-height: 23px; + width: 88px; + } +`; +export const MaskTitleChecked = styled.div` + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: 137px; + height: 90px; + margin: auto; + font-size: 24px; + line-height: 30px; + font-family: "Bebas Neue"; + text-align: center; + color: #707070; + @media (max-width: 375px) { + font-size: 19px; + line-height: 23px; + width: 100px; + } +`; +export const InfluencerBox = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + position: absolute; + right: ${pxPC(7.74)}; + bottom: ${pxPC(-30)}; +`; +export const InfluencerAvatar = styled.img``; +export const InfluencerName = styled(XsmallText)` + margin-top: ${pxPC(5)}; +`; +export const MyProductTitle = styled(ProductTitle)` + font-family: Roboto Condensed; + font-size: 10.75px; + line-height: 13px; + margin-top: ${pxPC(5.56)}; + align-self: flex-start; +`; +export const ProductDescBox = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: ${pxPC(31)}; + align-self: stretch; +`; +export const MyProductSubTitle = styled.div` + font-family: Roboto Condensed; + font-size: 8px; + line-height: 9px; + text-decoration: underline; + color: #000; + text-align: center; +`; +export const MyProductSubText = styled.div` + margin-left: ${pxPC(4)}; + font-family: Roboto Condensed; + font-size: 8px; + line-height: 9px; + color: rgba(0, 0, 0, 0.33); +`; +export const ProductPrice = styled(PriceText)` + font-family: Roboto Condensed; + font-size: 7.9px; + line-height: 9px; + text-align: center; + color: #000; + margin-left: auto; +`; diff --git a/components/Home/Products/Products.tsx b/components/Home/Products/Products.tsx new file mode 100644 index 00000000..46c00142 --- /dev/null +++ b/components/Home/Products/Products.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import Rating from "@material-ui/lab/Rating"; +import { Swiper, SwiperSlide } from "swiper/react"; +import { ProductCard } from "../.."; +import { + InfluencerAvatar, + InfluencerBox, + InfluencerName, + MyProductTitle, + ProductDescBox, + ProductImg, + ProductImgOutterBox, + ProductPrice, + MySwiperWrap, + MySlideWrap, + Title, + ProductMask, + MaskTitle, + MyProductSubTitle, + MyProductSubText, + MaskTitleChecked +} from "./Products.styles"; +import SwiperCore, { Navigation, Thumbs } from "swiper/core"; +import { useMediaQuery } from "react-responsive"; +export type product = { + title: string; + subTitle: string; + subText: string; + influencer: string; + rate: number; + viewer: string; + img: string; + avatar: string; + status: number; +}; +export interface ProductsProps { + data: product[]; + title: string; +} +SwiperCore.use([Navigation, Thumbs]); +const Products: React.FC = (props) => { + const { data, title } = props; + const isMobile = useMediaQuery({ maxWidth: 767 }); + return ( + + {title} + + {data.map((item, index) => { + console.log(item); + return ( + + + + ); + })} + + + ); +}; +export default Products; diff --git a/components/Home/Products/index.tsx b/components/Home/Products/index.tsx new file mode 100644 index 00000000..62e3eeb8 --- /dev/null +++ b/components/Home/Products/index.tsx @@ -0,0 +1 @@ +export { default } from "./Products"; diff --git a/components/Home/home.json b/components/Home/home.json new file mode 100644 index 00000000..31769faa --- /dev/null +++ b/components/Home/home.json @@ -0,0 +1,287 @@ +{ + "memberList": [ + { + "name": "Jane Jetson", + "avatar": "/1.png" + }, + { + "name": "WILMA FLINTSTONE", + "avatar": "/2.png" + }, + { + "name": "LUCILE BALL", + "avatar": "/3.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "DIANA PRINCE", + "avatar": "./4.png" + }, + { + "name": "Jane Jetson", + "avatar": "/5.png" + }, + { + "name": "Jane Jetson", + "avatar": "/6.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/7.png" + }, + { + "name": "Jane Jetson", + "avatar": "/8.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/9.png" + }, + { + "name": "Jane Jetson", + "avatar": "/10.png" + }, + { + "name": "Jane Jetson", + "avatar": "/11.png" + } + ], + "productList": [ + { + "title": "Maeve Tuesday Tee", + "subTitle": "Sheer Top", + "img": "./p1.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "211k", + "avatar": "/avatar.png", + "status": 1, + "subText": "+ 7 more" + }, + { + "img": "/p2.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "302k", + "avatar": "/avatar.png", + "status": 2, + "title": "Whole Lotta Love", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "/p2.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "154k", + "avatar": "/avatar.png", + "status": 3, + "title": "Maeve Tuesday Tee", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "/p2.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "211k", + "avatar": "/avatar.png", + "status": 2, + "title": "Whole Lotta Love", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "/p2.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "154k", + "avatar": "/avatar.png", + "status": 3, + "title": "Maeve Tuesday Tee", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "/p2.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "302k", + "avatar": "/avatar.png", + "status": 3, + "title": "Maeve Tuesday Tee", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "./p3.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "211k", + "avatar": "/avatar.png", + "status": 2, + "title": "Whole Lotta Love", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "./p4.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "154k", + "avatar": "/avatar.png", + "status": 1, + "title": "Maeve Tuesday Tee", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "./p5.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "211k", + "avatar": "/avatar.png", + "status": 3, + "title": "Maeve Tuesday Tee", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + }, + { + "img": "./p6.png", + "influencer": "Influencer", + "rate": 5, + "viewer": "154k", + "avatar": "/avatar.png", + "status": 2, + "title": "Whole Lotta Love", + "subTitle": "Sheer Top", + "subText": "+ 7 more" + } + ], + "latestProducts": [ + { + "img": "/latest1.png" + }, + { + "img": "/latest2.png" + } + ], + "hotDigs": [ + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h1.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h2.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h3.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h4.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h4.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h4.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h4.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h4.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h4.png", + "rate": 5, + "price": "US$18.99" + }, + { + "title": "POLT130", + "desc": "ONLINE EXCLUSIVE", + "img": "/h1.png", + "rate": 5, + "price": "US$18.99" + } + ], + "bigHotDig": { + "img": "/bh.png" + } +} diff --git a/components/Home/index.ts b/components/Home/index.ts new file mode 100644 index 00000000..aa74a982 --- /dev/null +++ b/components/Home/index.ts @@ -0,0 +1 @@ +export { Home } from "./Home"; diff --git a/components/Home/index.tsx b/components/Home/index.tsx deleted file mode 100644 index a3f096a6..00000000 --- a/components/Home/index.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import React, { Component, createRef } from 'react' -import Popup from '../Modal/index' -import YouTubeIframe from '../video'; -import ReactAudioPlayer from 'react-audio-player'; -import Image from 'next/image' -const client_id = '465bfa9fa3bf3c824164deb07cb2761b' - -const styles = { - backgroundColor: '#1f1f1f', - paddingLeft: '17px', - paddingRight: '15px' - -} - -interface Props { -} - -export interface S { - songs: any; - index: any; - isPlaying: boolean; - url: any; - currentTime: any; - duration: any; - muted: boolean; - volume: number; - reverse: any; - seeking: boolean; - seekable: any; - audioInput: any; - seekbarvalue: any; - -} -export default class HomeComponent extends Component { - rap: any; - //seekbarFunction: (e: Event) => void; - constructor(props) { - super(props); - this.rap = React.createRef(); - this.state = { - songs: [], - index: 0, - isPlaying: false, - url: "", - currentTime: "", - duration: "", - muted: false, - volume: 1, - reverse: 100, - seeking: false, - seekable: "", - audioInput: "", - seekbarvalue: "", - }; - } - componentDidMount() { - this.ApiCall(); - const audio = document.getElementsByClassName("react-audio-player")[0]; - // console.log( this.rap.audioEl.current); - //audio.addEventListener('timeupdate', this.UpdateTheTime, false); - // audio.addEventListener('durationchange', this.SetSeekBar, false); - // const seekbar = document.getElementById('seekbar'); - // this.setState({ seekbarvalue: seekbar }) - this.setState({ audioInput: audio }); - this.rap.audioEl.current.onvolumechange = (e) => { - // console.log(`Events**********`, e) - }; - //this.setState({ audioInput: this.rap }); - // document.addEventListener("keypress", (e) => { - // console.log("KEYPRESSED*************", e) - // if (e.key === " ") { - // if (this.state.isPlaying) { - // this._pauseAudio(); - // } else { - // this._playCurrentSong(); - // } - // } - // }); - } - ApiCall() { - fetch( - "https://api.soundcloud.com/users/6319082/playlists?client_id=" + - `${client_id}` - ) - .then((res) => res.json()) - .then( - (result) => { - this.setState({ songs: result[0].tracks }, () => this.GetUrl()); - }, - (error) => {} - ); - } - GetCurrentTimeAndDuration = () => { - this.setState({ currentTime: this.rap.audioEl.current.currentTime }); - this.setState({ duration: this.rap.audioEl.current.duration }); - this.setState({ duration: this.rap.audioEl.current.seeking }); - this.setState({ duration: this.rap.audioEl.current.seekable }); - }; - - randomArrayShuffle(array) { - var currentIndex = array.length, - temporaryValue, - randomIndex; - while (0 !== currentIndex) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - return array; - } - - GetUrl = () => { - this.randomArrayShuffle(this.state.songs); - - const url = this.state.songs.map((song) => { - return song.stream_url + "?client_id=" + `${client_id}`; - }); - this.setState({ url: url }); - //this.GetCurrentTimeAndDuration() - }; - componentWillReceiveProps(nextProps) { - console.debug("Will received props", nextProps); - /* Update state songs while data is passed from parent component while ready and formated */ - this.setState({ - songs: nextProps.songs, - }); - } - - // UpdateTheTime = () => { - // var sec = this.state.audioInput.currentTime; - // var h = Math.floor(sec / 3600); - // sec = sec % 3600; - // var min = Math.floor(sec / 60); - // sec = Math.floor(sec % 60); - // if (sec.toString().length < 2) sec = "0" + sec; - // if (min.toString().length < 2) min = 0 + min; - // document.getElementById('lblTime').innerHTML = h + ":" + min + ":" + sec; - // this.state.seekbarvalue.min = this.state.audioInput.startTime; - // this.state.seekbarvalue.max = this.state.audioInput.duration; - // this.state.seekbarvalue.value = this.state.audioInput.currentTime; - // } - - // SetSeekBar = () => { - // this.state.seekbarvalue.min = 0; - // this.state.seekbarvalue.max = this.state.audioInput.duration; - // } - - handleChangeReverse = (value) => { - const finalvalue = (value * 1) / 100; - if (finalvalue) { - this.setState({ - volume: finalvalue, - }); - } - }; - - mutedPlayer = () => { - if (this.state.muted == true) { - this.setState({ muted: false }); - } else { - this.setState({ muted: true }); - } - }; - _playCurrentSong = () => { - this.rap.audioEl.current.play(); - this.setState({ isPlaying: true }); - }; - - _pauseAudio = () => { - this.rap.audioEl.current.pause(); - this.setState({ isPlaying: false }); - }; - - getRandomIndex = () => { - return Math.floor(Math.random() * this.state.songs.length); - }; - - _playNextSong = () => { - if (this.state.songs && this.state.songs.length != undefined && this.state.songs.length != 0) { - let randomIndex = this.getRandomIndex(); - - if (randomIndex != this.state.index) { - this.setState({ index: randomIndex }); - } else { - this._playNextSong(); - } - } - }; - - _ErrorNextSong = (e) => { - const { songs, index } = this.state; - console.log( - `${ - songs[index] && songs[index].title - } can't be loaded \nAutomatically loading next song...` - ); - this._playNextSong(); - }; - - ChangeTheTime = () => { - this.state.audioInput.currentTime = this.state.seekbarvalue.value; - }; - -// _playPreviousSong = () => { -// if (this.state.index - 1 >= 0) { -// this.setState({ index: this.state.index + -1 }, () => { -// console.log( -// this.state.songs.length + " IF... Previous song index is ", -// this.state.index -// ); -// }); -// } else { -// this.setState({ index: this.state.songs.length - 1 }, () => { -// console.log( -// this.state.songs.length + " ELSE... Previous song index is ", -// this.state.index -// ); -// }); -// } -// }; - - render() { - return ( -
    - - - {/*
    - - -
    */} - -

    - {this.state.songs[this.state.index] && - this.state.songs[this.state.index].title} -

    -
    -
    -
    - Picture of the author -
    - {this.state.isPlaying ? ( -
    - Picture of the author -
    - ) : ( -
    - Picture of the author -
    - )} - { - this.rap = element; - }} - autoPlay={this.state.isPlaying} - onEnded={this._playNextSong} - onError={this._ErrorNextSong} - onPlay={(e) => this._playCurrentSong} - onPause={(e) => this._pauseAudio} - volume={this.state.volume} - onVolumeChanged={this.handleChangeReverse} - // onSeeked={this.seekbarFunction} - muted={this.state.muted} - style={styles} - /> -
    - Picture of the author -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - ); - } -} diff --git a/components/InfoBox/InfoBox.tsx b/components/InfoBox/InfoBox.tsx new file mode 100644 index 00000000..f0eaa81f --- /dev/null +++ b/components/InfoBox/InfoBox.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { InfoBoxProps } from "./types"; + +const Info = styled.div` + margin-top: 20px; + margin-bottom: 20px; + padding-top: 20px; + padding-bottom: 20px; + border-top: 1px solid #ececec; + border-bottom: 1px solid #ececec; +`; + +export const InfoBox: React.FC = ({ children }: { children: string }) => ( + {children} +); diff --git a/components/InfoBox/index.ts b/components/InfoBox/index.ts new file mode 100644 index 00000000..2dfff09b --- /dev/null +++ b/components/InfoBox/index.ts @@ -0,0 +1 @@ +export { InfoBox } from "./InfoBox"; diff --git a/components/InfoBox/index.tsx b/components/InfoBox/index.tsx deleted file mode 100644 index 87d572c5..00000000 --- a/components/InfoBox/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styled from '@emotion/styled'; -import React from "react"; -const Div = styled.div` -margin-top: 20px; -margin-bottom: 20px; -padding-top: 20px; -padding-bottom: 20px; -border-top: 1px solid #ececec; -border-bottom: 1px solid #ececec; -` -const InfoBox = ({ children }) => ( -
    - {children} -
    -); - -export { InfoBox }; diff --git a/components/InfoBox/types/index.d.ts b/components/InfoBox/types/index.d.ts new file mode 100644 index 00000000..c3a74a0d --- /dev/null +++ b/components/InfoBox/types/index.d.ts @@ -0,0 +1,3 @@ +export interface InfoBoxProps { + children: string; +} diff --git a/components/Layout/Layout.styles.tsx b/components/Layout/Layout.styles.tsx new file mode 100644 index 00000000..e90b5bfa --- /dev/null +++ b/components/Layout/Layout.styles.tsx @@ -0,0 +1,53 @@ +import styled from "@emotion/styled"; +import { pxIphone } from "../../utils"; + +// export const LayoutContainer = styled.main` +// width: 100%; +// min-height: 100vh; +// flex: 1; +// display: flex; +// flex-direction: column; +// max-height: calc(100% - 235px); +// `; +// export const Content = styled.div` +// width: 100%; +// flex: 1; +// display: flex; +// flex-direction: column; +// overflow: hidden; +// `; + +type LogoType = { + src: string; + darkMode: boolean; +}; + +export const Logo = styled.img` + width: 181px; + height: auto; + margin-bottom: 20px; + ${(p) => (p.darkMode ? "filter: invert(1);" : null)}; + box-shadow: 0 10px 22px rgba(255, 255, 255, 0.1); + @media (max-width: 375px) { + width: ${pxIphone(80)}; + margin-bottom: 14.68vw; + height: auto; + } + @media (max-width: 750px) { + margin-bottom: 0; + margin-top: 6px; + } +`; + +export const Container = styled.main` + flex: 1; + overflow: scroll; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +`; +export const Content = styled.div` + flex: 1; + overflow: scroll; +`; diff --git a/components/Layout/Layout.tsx b/components/Layout/Layout.tsx new file mode 100644 index 00000000..84035f04 --- /dev/null +++ b/components/Layout/Layout.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { ClassNames } from "@emotion/react"; +import { LayoutProps } from "./types"; +import { Footer } from "../Footer/Footer"; +import columns from "../Footer/footer.json"; + +import { Container, Content, Logo } from "./Layout.styles"; + +type LogoTypeFC = { + imageFile: string; + darkMode?: boolean; +}; + +export const MyLogo = ({ imageFile, darkMode }: LogoTypeFC) => ( + +); + +export const Layout: React.FC = ({ + children +}: { + children: JSX.Element[] | JSX.Element; +}) => { + return ( + + + {children} + + {({ css, cx }) => ( +