diff --git a/.env.development b/.env.development index 39ff2f20ec946104653b9db4813a0ab6900a4e8a..b14f63948deab7c2adc753934740cfe264b39a85 100644 --- a/.env.development +++ b/.env.development @@ -2,6 +2,3 @@ NEXT_PUBLIC_CLIENT_URL="http://localhost:3000" NEXT_PUBLIC_API_BASE_URL="http://localhost:8080" NEXT_PUBLIC_API_VERSION="v1" - -# Services -NEXT_PUBLIC_MAPBOX_PUBLIC_TOKEN="pk.eyJ1IjoiYnloLWFzc29jaWF0aW9uIiwiYSI6ImNsN2oyY3NtZzAzeWgzdnF6OXBnNnlwOHkifQ.MiSLQ4V4iVQoL0h-9TprXw" diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000000000000000000000000000000000..1560b99ccace0ed2b8517d97efee6052a24c5109 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +auto-install-peers = true \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 3946dbc27943328d2b7de2d15187fcd6e048c30f..58aaf011393408ee9437fe9caece8faca825b608 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -11,7 +11,6 @@ const nextConfig = { return config; }, - images: { domains: [ "images.unsplash.com", diff --git a/package.json b/package.json index ef2e4c7eb9e09dd92aed04083719e16905fae662..6ea04383c611de7f9f539275c5b6ac44a867feba 100644 --- a/package.json +++ b/package.json @@ -16,56 +16,55 @@ "prepare": "husky install" }, "dependencies": { - "@chakra-ui/react": "^2.2.9", - "@emotion/react": "^11.10.0", - "@emotion/styled": "^11.10.0", - "@fingerprintjs/fingerprintjs": "^3.3.4", - "@reduxjs/toolkit": "^1.8.2", - "axios": "^0.27.2", + "@headlessui/react": "^1.7.4", + "@heroicons/react": "^2.0.13", + "@next/font": "^13.0.4", + "@tanstack/react-query": "^4.16.1", "bowser": "^2.11.0", "cookies": "^0.8.0", "date-fns": "^2.28.0", - "framer-motion": "^7.2.1", "http-proxy": "^1.18.1", - "lodash": "^4.17.21", "mapbox-gl": "^2.10.0", - "next": "^12.2.3", + "next": "^13.0.4", "next-i18next": "^11.0.0", - "next-redux-wrapper": "^7.0.5", "nextjs-progressbar": "^0.0.14", - "query-string": "^7.1.1", - "react": "18.1.0", - "react-detect-click-outside": "^1.1.7", - "react-dom": "18.1.0", + "query-string": "^7.1.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-dropzone": "^14.2.1", "react-hook-form": "^7.31.3", - "react-icons": "^4.4.0", "react-map-gl": "^7.0.19", - "react-redux": "^8.0.2", + "react-modern-drawer": "^1.1.2", + "react-ranger": "^2.1.0", "react-swipeable": "^7.0.0", "recharts": "2.1.12", "sharp": "^0.30.7", - "socket.io-client": "^4.5.2" + "socket.io-client": "^4.5.4" }, "devDependencies": { - "@chakra-ui/cli": "^2.1.2", - "@svgr/webpack": "^6.2.1", - "@testing-library/jest-dom": "^5.16.4", - "@testing-library/react": "^13.2.0", + "@chakra-ui/cli": "^2.2.1", + "@svgr/webpack": "^6.5.1", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", "@types/cookies": "^0.7.7", "@types/http-proxy": "^1.17.9", - "@types/mapbox-gl": "^2.7.5", + "@types/mapbox-gl": "^2.7.10", "@types/node": "17.0.35", "@types/react": "18.0.9", "@types/react-dom": "18.0.5", + "@types/react-ranger": "^2.0.1", "@typescript-eslint/eslint-plugin": "^5.26.0", + "autoprefixer": "^10.4.13", "eslint": "8.16.0", - "eslint-config-next": "12.1.6", + "eslint-config-next": "^13.0.7", "eslint-config-prettier": "^8.5.0", - "husky": "^8.0.1", - "jest": "^28.1.0", - "jest-environment-jsdom": "^28.1.0", - "prettier": "^2.6.2", + "husky": "^8.0.2", + "jest": "^28.1.3", + "jest-environment-jsdom": "^28.1.3", + "postcss": "^8.4.20", + "prettier": "^2.8.1", + "prettier-plugin-tailwindcss": "^0.1.13", + "tailwindcss": "^3.2.4", "typescript": "4.7.2" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..33ad091d26d8a9dc95ebdf616e217d985ec215b8 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/prettier.config.cjs b/prettier.config.cjs new file mode 100644 index 0000000000000000000000000000000000000000..58b0aee22dc48ef56a4f03ff7e68c7e21c520984 --- /dev/null +++ b/prettier.config.cjs @@ -0,0 +1,4 @@ +/** @type {import("prettier").Config} */ +module.exports = { + plugins: [require.resolve("prettier-plugin-tailwindcss")], +}; diff --git a/public/icons/services/apple.svg b/public/icons/services/apple.svg new file mode 100644 index 0000000000000000000000000000000000000000..b69b97cf17898ec3d13d49fbac6a4c44b7034f6d --- /dev/null +++ b/public/icons/services/apple.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/social-media/facebook.svg b/public/icons/services/facebook.svg similarity index 100% rename from src/assets/icons/social-media/facebook.svg rename to public/icons/services/facebook.svg diff --git a/public/icons/services/google.svg b/public/icons/services/google.svg new file mode 100644 index 0000000000000000000000000000000000000000..71579d2c302b4f5391b2e5663974c596f1a62b2a --- /dev/null +++ b/public/icons/services/google.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/icons/social-media/twitter.svg b/public/icons/services/twitter.svg similarity index 100% rename from src/assets/icons/social-media/twitter.svg rename to public/icons/services/twitter.svg diff --git a/public/icons/spinner.svg b/public/icons/spinner.svg new file mode 100644 index 0000000000000000000000000000000000000000..b1f014ea9f05c9e87dd452ccd582055b39b6c887 --- /dev/null +++ b/public/icons/spinner.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/illustrations/auth-success.svg b/public/illustrations/auth-success.svg new file mode 100644 index 0000000000000000000000000000000000000000..087b1efc0706d04904b728b78349a0faedbea19f --- /dev/null +++ b/public/illustrations/auth-success.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/static/assets/logo.svg b/public/logo.svg similarity index 73% rename from public/static/assets/logo.svg rename to public/logo.svg index 0eacd33c6df3c086b1269fbfed03363a78cdff8f..112cbc11e31c3f691d282eaf8717395a90637c2b 100644 --- a/public/static/assets/logo.svg +++ b/public/logo.svg @@ -1,15 +1,9 @@ - - + + - - + + - - - - - - diff --git a/public/static/assets/landing/bestSellingSectionBg.svg b/public/modules/landing/best-selling-section-bg.svg similarity index 100% rename from public/static/assets/landing/bestSellingSectionBg.svg rename to public/modules/landing/best-selling-section-bg.svg diff --git a/public/static/assets/landing/car-market-landing-hero-bg.png b/public/modules/landing/car-market-landing-hero-bg.png similarity index 100% rename from public/static/assets/landing/car-market-landing-hero-bg.png rename to public/modules/landing/car-market-landing-hero-bg.png diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index fbf0e25a651c28931b2fe8afa2947e124eebc74f..0000000000000000000000000000000000000000 --- a/public/vercel.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/public/videos/auth-footage.mp4 b/public/videos/auth-footage.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c0d30d95b32dcfee9b97b2c6a90017374461db81 Binary files /dev/null and b/public/videos/auth-footage.mp4 differ diff --git a/src/_middleware.ts b/src/_middleware.ts new file mode 100644 index 0000000000000000000000000000000000000000..455cf3e4a62dd645a16b4eb7589246f63bc6b179 --- /dev/null +++ b/src/_middleware.ts @@ -0,0 +1,34 @@ +export {}; + +// import { NextRequest, NextResponse } from "next/server"; + +// const LOGIN_REDIRECT_PAGES = ["/dashboard", "/vehicles/new-listing"]; + +// export function middleware(request: NextRequest) { +// // Current requested url href +// const url = request.url; + +// // If it is third party callback => do nothing +// if (url.includes("/api")) { +// return; +// } + +// const cookies = request.cookies as any; + +// // Check is user authentication token exists +// const authenticated = +// !!cookies.get(AuthTokens.REFRESH) && !!cookies.get(AuthTokens.EXPIRATION); + +// /** +// * Redirect to sign in +// */ +// if (!authenticated && LOGIN_REDIRECT_PAGES.some((item) => url.includes(item))) +// return NextResponse.redirect(new URL("/auth/sign-in", url)); + +// /** +// * Redirect to dashboard +// */ +// if (authenticated && url === new URL("/", url).href) { +// return NextResponse.redirect(new URL("/dashboard", url)); +// } +// } diff --git a/src/api/instance.ts b/src/api/_instance.ts similarity index 100% rename from src/api/instance.ts rename to src/api/_instance.ts diff --git a/src/api/api-routes.ts b/src/api/api-routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f68a47e0bdcdadd4d43a7fd5e40a44f85c61c41 --- /dev/null +++ b/src/api/api-routes.ts @@ -0,0 +1,16 @@ +type APIMethods = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + +export class ApiRoutes { + private namespace: string; + + constructor(namespace: string) { + this.namespace = namespace; + } + + createRoute = (urlPath: string, method: APIMethods = "GET") => { + return { + url: `${this.namespace}/${urlPath}`, + method, + }; + }; +} diff --git a/src/api/fetch-instance.ts b/src/api/fetch-instance.ts new file mode 100644 index 0000000000000000000000000000000000000000..394381e3673bd83f9575480b9f581bbea4b12d86 --- /dev/null +++ b/src/api/fetch-instance.ts @@ -0,0 +1,46 @@ +import config from "../../config"; +import { browserInfo } from "../helpers/system"; + +const BROWSER_NAME = "Browser-Name"; +const BROWSER_DEVICE_TYPE = "Browser-Device-Type"; +const BROWSER_OS_VERSION = "Browser-Os-Version"; +const BROWSER_OS_NAME = "Browser-Os-Name"; + +export const fullApiUrl = `${config.api.baseURL}/${config.api.version}`; + +const headersData = { + "Content-Type": "application/json", + [BROWSER_NAME]: browserInfo.name, + [BROWSER_DEVICE_TYPE]: browserInfo.deviceType, + [BROWSER_OS_VERSION]: browserInfo.osVersion, + [BROWSER_OS_NAME]: browserInfo.osName, +}; + +type RequestInitOptions = Omit & { + body?: object | string | number | boolean | null; +}; + +export async function fetchInstance( + url: RequestInfo | URL, + requestInit?: RequestInitOptions +) { + const body = requestInit?.body + ? JSON.stringify(requestInit?.body) + : undefined; + const headers = new Headers({ + ...headersData, + ...requestInit?.headers, + }); + const response = await fetch(`${fullApiUrl}/${url}`, { + ...requestInit, + credentials: "include", + headers, + body, + }); + if (!response.ok) { + const error = await response.json(); + return Promise.reject(error); + } + const data = await response.json(); + return data.data as T; +} diff --git a/src/assets/icons/social-media/google.svg b/src/assets/icons/social-media/google.svg deleted file mode 100644 index 57c77bfa3045ba7eec06585e006923578290bb8c..0000000000000000000000000000000000000000 --- a/src/assets/icons/social-media/google.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/components/accordion/index.tsx b/src/components/accordion/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..38a5e4f1534a80976f75db964553c62d50534310 --- /dev/null +++ b/src/components/accordion/index.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { FC, PropsWithChildren } from "react"; +import { Disclosure, Transition } from "@headlessui/react"; +import { ChevronUpDownIcon } from "@heroicons/react/20/solid"; + +interface Props { + label: string; + isOpen?: boolean; + isDisabled?: boolean; +} + +const Accordion: FC> & Props> = ({ + children, + label, + isOpen, + isDisabled, +}) => { + return ( + + {({ open }) => ( + <> + + {label} + + + + + + {children} + + + )} + + ); +}; + +export default Accordion; diff --git a/src/components/avatar/index.tsx b/src/components/avatar/index.tsx index 532ca88aa6e296c4e0d12bf32895db34997bc614..5c3da36a6a0a374314d654bd04acf32924625129 100644 --- a/src/components/avatar/index.tsx +++ b/src/components/avatar/index.tsx @@ -1,49 +1,71 @@ -import React, { FC } from "react"; -import { Avatar as ChakraAvatar } from "@chakra-ui/react"; -import { MdPerson } from "react-icons/md"; +import { UserIcon } from "@heroicons/react/20/solid"; +import Image from "next/image"; +import { FC } from "react"; interface Props { - avatarUrl?: string | null; + src?: string | null; height?: number; width?: number; - withHover?: boolean; - borderRadius?: number; - withoutMargins?: boolean; + alt?: string; + className?: string; } -const Avatar: FC = ({ - avatarUrl, - height = 40, - width = 40, - withHover = false, - borderRadius, - withoutMargins, -}) => { +const Avatar: FC = ({ src, className, alt = "Profile's avatar" }) => { + if (!src) { + return ( +
+ +
+ ); + } + return ( - } - _hover={ - withHover - ? { - shadow: "16px", - } - : undefined - } - /> +
+ {alt} +
); }; export default Avatar; + +// const Avatar: FC = ({ +// avatarUrl, +// height = 40, +// width = 40, +// withHover = false, +// borderRadius, +// withoutMargins, +// }) => { +// return ( +// } +// _hover={ +// withHover +// ? { +// shadow: "16px", +// } +// : undefined +// } +// /> +// ); +// }; diff --git a/src/components/button/index.tsx b/src/components/button/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d0c7e68d720696c27e6dd40f486213f82b3109c0 --- /dev/null +++ b/src/components/button/index.tsx @@ -0,0 +1,30 @@ +import React, { FC, PropsWithChildren } from "react"; +import Spinner from "../spinner"; + +interface Props extends React.ButtonHTMLAttributes { + isLoading?: boolean; + loadingMessage?: string; +} + +const Button: FC> = ({ + isLoading, + loadingMessage = "Loading...", + children, + className, + ...rest +}) => { + if (isLoading) + return ( + + ); + return ( + + ); +}; + +export default Button; diff --git a/src/components/checkbox-group/index.tsx b/src/components/checkbox-group/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b26de6cecacbfae6522dafd41519e48719041a13 --- /dev/null +++ b/src/components/checkbox-group/index.tsx @@ -0,0 +1,51 @@ +import { FC } from "react"; +import Checkbox from "../checkbox"; + +type StringOrNumber = string | number; + +export type CheckboxOption = { + name: string; + id: StringOrNumber; +}; + +type Props = { + options: CheckboxOption[]; + name: string; + values: StringOrNumber[]; + onChange: (selectedOptions: StringOrNumber[]) => void; +}; + +export type OnChangeCheckboxType = { + label: string; + isChecked: boolean; + value: StringOrNumber; +}; + +const CheckboxGroup: FC = ({ name, onChange, options, values }) => { + const handleChange = ({ isChecked, value }: OnChangeCheckboxType) => { + if (isChecked) { + onChange([...values, value]); + } else { + onChange(values.filter((option) => option !== value)); + } + }; + + return ( +
+ {options.map(({ id, name: optionName }) => { + return ( + + ); + })} +
+ ); +}; + +export default CheckboxGroup; diff --git a/src/components/checkbox/index.tsx b/src/components/checkbox/index.tsx index baecb885fcf86a68f54a1d938af94fd7c358675d..57841113e5a2fd226f5fbc0df3f9be5ffea720cc 100644 --- a/src/components/checkbox/index.tsx +++ b/src/components/checkbox/index.tsx @@ -1,49 +1,70 @@ -import { - chakra, - Checkbox as ChakraCheckbox, - CheckboxProps, - Flex, - Text, -} from "@chakra-ui/react"; -import React, { FC } from "react"; +import { FC } from "react"; +import { Switch, Transition } from "@headlessui/react"; +import { OnChangeCheckboxType } from "../checkbox-group"; +import Error from "../error"; -type CheckboxPropsWithoutValue = Omit; - -interface Props extends CheckboxPropsWithoutValue { - label: string; - value: boolean; +interface Props { + name?: string; + value?: string | number; + label: React.ReactNode; + isChecked: boolean; + onChange: (value: Omit) => void; + error?: string; } -const Checkbox: FC = ({ label, value, ...props }) => { +const Checkbox: FC = ({ + label, + isChecked, + onChange, + name, + value = "", + error, +}) => { return ( - - + onChange({ isChecked, value })} + as={"div"} + name={name} + value={value.toString()} > - - {label} - - - + {({ checked }) => ( + <> +
+ +
+ +
+ +

+ {label} +

+ + )} + + {error && {error}} +
); }; -const CheckboxComp = chakra(ChakraCheckbox, { - baseStyle: { - span: { - span: { - width: "100%", - }, - }, - }, -}); - export default Checkbox; diff --git a/src/components/container/index.tsx b/src/components/container/index.tsx deleted file mode 100644 index 8e73cbb2035a3d615e284c4a689659fd5bc12c4f..0000000000000000000000000000000000000000 --- a/src/components/container/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Box } from "@chakra-ui/react"; -import React, { FC, PropsWithChildren } from "react"; - -const AppLargeContainer: FC> = ({ children }) => { - return ( - - {children} - - ); -}; - -const SmallerAppContainer: FC> = ({ children }) => { - return ( - - {children} - - ); -}; - -export { SmallerAppContainer }; - -export default AppLargeContainer; diff --git a/src/components/containers/large-app-container.tsx b/src/components/containers/large-app-container.tsx new file mode 100644 index 0000000000000000000000000000000000000000..75c417c12b3f08db5f86812fd3fe1901f47a3605 --- /dev/null +++ b/src/components/containers/large-app-container.tsx @@ -0,0 +1,9 @@ +import type { FC, PropsWithChildren } from "react"; + +export const AppLargeContainer: FC> = ({ + children, +}) => { + return ( +
{children}
+ ); +}; diff --git a/src/components/containers/small-app-container.tsx b/src/components/containers/small-app-container.tsx new file mode 100644 index 0000000000000000000000000000000000000000..df0a3816c6c2034c28c36d4cdbc06774359f3951 --- /dev/null +++ b/src/components/containers/small-app-container.tsx @@ -0,0 +1,7 @@ +import type { FC, PropsWithChildren } from "react"; + +export const SmallAppContainer: FC> = ({ + children, +}) => { + return
{children}
; +}; diff --git a/src/components/divider/index.tsx b/src/components/divider/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6c08f60f0c70934ddfb790cb39f7cca4040cd5b0 --- /dev/null +++ b/src/components/divider/index.tsx @@ -0,0 +1,5 @@ +const Divider = () => { + return
; +}; + +export default Divider; diff --git a/src/components/dot-divider/index.tsx b/src/components/dot-divider/index.tsx index 0fc204b189f11dfce96bd09e893b1f0426b5ef29..4e19a2ab8520cdd7ab8a190a788f6d6bff35e306 100644 --- a/src/components/dot-divider/index.tsx +++ b/src/components/dot-divider/index.tsx @@ -1,11 +1,3 @@ -import { chakra } from "@chakra-ui/react"; - -export default chakra("div", { - baseStyle: { - height: "4px", - width: "4px", - mx: "6px", - borderRadius: "999px", - backgroundColor: "grays.500", - }, -}); +export const DotDivider = () => ( + +); diff --git a/src/components/drawer/index.tsx b/src/components/drawer/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b6b8c49206c5832d244d4eef9432cfabfd9d4857 --- /dev/null +++ b/src/components/drawer/index.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { PropsWithChildren, FC, useEffect } from "react"; +import { default as DrawerWrapper } from "react-modern-drawer"; + +import "react-modern-drawer/dist/index.css"; + +interface Props { + direction: "right" | "left" | "top" | "bottom"; + open: boolean; + onClose: () => void; + size?: string; + className?: string; +} + +const Drawer: FC> = ({ + direction, + onClose, + open, + size = "auto", + className, + children, +}) => { + useEffect(() => { + document.body.classList.toggle("overflow-hidden", open); + // Return a callback function that removes the "overflow-hidden" class + // when the component unmounts + return () => document.body.classList.remove("overflow-hidden"); + }, [open]); + + return ( + + {children} + + ); +}; + +export default Drawer; diff --git a/src/components/error/index.tsx b/src/components/error/index.tsx index f7042528e16d3603243b0efbf362d9d2c1b2c59a..b028ef1b27027891767e82b5476f7c64ff184324 100644 --- a/src/components/error/index.tsx +++ b/src/components/error/index.tsx @@ -1,16 +1,25 @@ import React, { FC, PropsWithChildren } from "react"; -import { Text, TextProps } from "@chakra-ui/react"; -const Error: FC> = ({ children, ...props }) => { +const Error: FC> = ({ children }) => { // TODO: Implement message parse logic // For example: You will receive error: "[AUTH-001] The email address or password is incorrect" // [ErrorType - ErrorCode] ErrorMessage (for debug) // Parse error type and error code to get translation for error message + const messageBody = (() => { + if (typeof children === "string") { + const array = children.split(":"); + if (array.length === 2) { + return array[1].trim(); + } + } + return children; + })(); + return ( - - {children} - +
+

{messageBody}

+
); }; diff --git a/src/components/footer/components/LargeFooter.tsx b/src/components/footer/components/LargeFooter.tsx deleted file mode 100644 index d1e5fcbc3807f414a21d7da8c2df1e148c9fa677..0000000000000000000000000000000000000000 --- a/src/components/footer/components/LargeFooter.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Grid, GridItem } from "@chakra-ui/react"; -import React from "react"; -import footerItems from "../data/footer-items"; -import FooterList from "./footerList"; -import FooterListItem from "./footerList/FooterListItem"; - -const LargeFooter = () => { - return ( - - {footerItems.map((item) => ( - - - {item.items.map((footerListItem) => ( - - {footerListItem.name} - - ))} - - - ))} - - ); -}; - -export default LargeFooter; diff --git a/src/components/footer/components/SmallFooter.tsx b/src/components/footer/components/SmallFooter.tsx deleted file mode 100644 index ae087882956573f3491fbc24364f05f2457e926a..0000000000000000000000000000000000000000 --- a/src/components/footer/components/SmallFooter.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { - Button, - Flex, - Link as ChakraLink, - Stack, - Text, -} from "@chakra-ui/react"; -import Link from "next/link"; -import React from "react"; -import { MdPublic, MdMoreHoriz } from "react-icons/md"; - -const SmallFooter = () => { - return ( - - {/* Left part */} - - - - Privacy Policy - - - - - Term of Use - - - - © {new Date().getFullYear()} All rights reserved - - - - {/* Right part */} - - - - - - ); -}; - -export default SmallFooter; diff --git a/src/components/footer/components/footerList/FooterListItem.tsx b/src/components/footer/components/footerList/FooterListItem.tsx deleted file mode 100644 index 52b95f3cc29c8de3d40e819ef660b7fa06fce607..0000000000000000000000000000000000000000 --- a/src/components/footer/components/footerList/FooterListItem.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Link as ChakraLink } from "@chakra-ui/react"; -import Link from "next/link"; -import React, { FC, PropsWithChildren } from "react"; - -interface Props { - href: string; -} - -const FooterListItem: FC> = ({ href, children }) => { - return ( - - - {children} - - - ); -}; - -export default FooterListItem; diff --git a/src/components/footer/components/footerList/index.tsx b/src/components/footer/components/footerList/index.tsx deleted file mode 100644 index 4f43d09b9514ba4de2674f6f4557bf910cba9ea2..0000000000000000000000000000000000000000 --- a/src/components/footer/components/footerList/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Flex, Stack, Text } from "@chakra-ui/react"; -import React, { FC, PropsWithChildren } from "react"; - -interface Props { - title: string; -} - -const FooterList: FC> = ({ title, children }) => { - return ( - - - {title} - - {children} - - ); -}; - -export default FooterList; diff --git a/src/components/header/aside-menu.tsx b/src/components/header/aside-menu.tsx deleted file mode 100644 index 8128ced31a7d8da3bd4d882563fdf5305504308e..0000000000000000000000000000000000000000 --- a/src/components/header/aside-menu.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { - Box, - chakra, - Drawer, - DrawerBody, - DrawerContent, - DrawerOverlay, - Flex, - Link as ChakraLink, -} from "@chakra-ui/react"; - -import NextLink from "next/link"; - -import React, { FC } from "react"; - -interface Props { - isOpen: boolean; - onClose: () => void; -} - -const AsideMenu: FC = ({ isOpen, onClose }) => { - return ( - - - - - - - - Used Cars - - - Auctions - - - New Cars - - - Sell cars - - - Local Dealers - - - Support - - - - - - - ); -}; - -const Link = chakra(ChakraLink, { - baseStyle: { - mb: "24px", - fontSize: "m", - fontWeight: "semibold", - }, -}); - -export default AsideMenu; diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx deleted file mode 100644 index 0bf505e499700b4d49491cc314a87ff528b2be05..0000000000000000000000000000000000000000 --- a/src/components/header/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { - Button, - ButtonGroup, - chakra, - Flex, - Link as ChakraLink, - useDisclosure, -} from "@chakra-ui/react"; -import { SmallerAppContainer } from "../container"; -import NextLink from "next/link"; - -import Logo from "../../../public/static/assets/logo.svg"; -import { MdMenu, MdPerson } from "react-icons/md"; -import AsideMenu from "./aside-menu"; -import { FC, memo } from "react"; - -const Header: FC = () => { - const { isOpen, onOpen, onClose } = useDisclosure(); - - return ( - - - - - -
- -
-
- - - - - Used Cars - - - Auctions - - - New Cars - - - Sell cars - - - Local Dealers - - - Support - - - - - - - - - - - - - - - ); -}; - -const Link = chakra(ChakraLink, { - baseStyle: { - marginRight: "42px", - fontSize: "m", - fontWeight: "semibold", - }, -}); - -export const Person = chakra(MdPerson, { - baseStyle: { - height: "18px", - width: "18px", - color: "grays.500", - }, -}); - -const Menu = chakra(MdMenu); - -export default memo(Header); diff --git a/src/components/image/index.tsx b/src/components/image/index.tsx deleted file mode 100644 index ae1200280421eebe0094e7dbe94ca654d97a2bc6..0000000000000000000000000000000000000000 --- a/src/components/image/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { FC } from "react"; -import NextImage, { ImageProps } from "next/image"; -import { Box } from "@chakra-ui/react"; - -interface Props extends ImageProps { - rounded?: boolean; - borderRadius?: string; -} - -const Image: FC = ({ - layout = "fill", - height = "100%", - width = "100%", - priority = false, - rounded, - borderRadius, - ...props -}) => { - const borderRadiusStyle = () => { - if (rounded) { - return "100%"; - } - - if (borderRadius) { - return borderRadius; - } - - return "8px"; - }; - - return ( - - - - ); -}; - -export default Image; diff --git a/src/components/input/index.tsx b/src/components/input/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..aef784e5c1a743923b5cd088545bb8603debbf12 --- /dev/null +++ b/src/components/input/index.tsx @@ -0,0 +1,83 @@ +"use client"; + +import React, { useRef } from "react"; +import { FC } from "react"; +import Error from "../error"; + +type HTMLInputAttributes = React.InputHTMLAttributes; + +interface Props extends Omit { + value: string; + placeholder: HTMLInputAttributes["placeholder"]; + onChange: NonNullable; + icon?: React.ReactNode; + error?: string; +} + +const Input: FC = ({ + value, + onChange, + placeholder, + icon, + error, + ...rest +}) => { + const inputRef = useRef(null); + const onInputFocus = () => { + if (rest.disabled) return; + inputRef.current?.focus(); + }; + + const onValueChange = (e: React.ChangeEvent) => { + if (rest.type === "number") { + const value = e.target.value; + if (isNaN(Number(value))) return; + } + onChange(e); + }; + + return ( +
+ {/* Input field */} +
+ + {/* Input label */} + {value?.length > 0 && ( + + )} + {/* Icon */} + {!!icon && ( +
+ {icon} +
+ )} +
+ {/* Error */} + {error && {error}} +
+ ); +}; + +export default Input; diff --git a/src/components/logo/index.tsx b/src/components/logo/index.tsx index d60190c16d28267b831dbf29621d5ae7e1d88882..436bd19eb87e2e4a448116de15247531232f6b11 100644 --- a/src/components/logo/index.tsx +++ b/src/components/logo/index.tsx @@ -1,27 +1,42 @@ -import { chakra } from "@chakra-ui/react"; -import React, { FC } from "react"; -import GreatCarLogo from "../../../public/static/assets/logo.svg"; +import Image from "next/image"; +import { FC } from "react"; interface Props { /** - * Height of the logo in pixels (px) - * - * @default 40 + * Size of logo + * small - 24x24 px + * medium - 40x40 px + * large - 64x64 px + * @default medium */ - height?: number; + size?: "small" | "medium" | "large"; + className?: string; } -const Wrapper = chakra("div", { - svg: { - height: "100%", - }, -}); +const Logo: FC = ({ size = "medium", className }) => { + const dimensions = (() => { + switch (size) { + case "small": { + return { className: `h-6 w-6 ${className}`, length: 24 }; + } + case "large": { + return { className: `h-16 w-16 ${className}`, length: 64 }; + } + default: { + return { className: `h-10 w-10 ${className}`, length: 40 }; + } + } + })(); -const Logo: FC = ({ height = 40 }) => { return ( - - - +
+ Great Car logo +
); }; diff --git a/src/components/or-divider/index.tsx b/src/components/or-divider/index.tsx index b9093641c548911fc27d631d9a8f4d6158e8de81..a4443a4027a54d482d178d8eb077bc0f161e7a17 100644 --- a/src/components/or-divider/index.tsx +++ b/src/components/or-divider/index.tsx @@ -1,15 +1,15 @@ -import { Box, Flex, Text } from "@chakra-ui/react"; import React from "react"; const OrDivider = () => { return ( - - - +
+
+ + {/* TODO: Translate */} Or - - - + +
+
); }; diff --git a/src/components/page-layouts/auth-layout.tsx b/src/components/page-layouts/auth-layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f0f9678cb8a4e40d30056679bfd73b0e81975c6d --- /dev/null +++ b/src/components/page-layouts/auth-layout.tsx @@ -0,0 +1,22 @@ +import { FC, PropsWithChildren } from "react"; +import AuthSideBadge from "../../modules/auth/components/auth-side-badge"; +import RootHead, { SeoDataProps } from "./root-head"; + +const AuthLayout: FC> = ({ + children, + ...rest +}) => { + return ( + <> + +
+ {/* Content */} +
{children}
+ {/* Auth badge */} + +
+ + ); +}; + +export default AuthLayout; diff --git a/src/components/page-layouts/dashboard-layout.tsx b/src/components/page-layouts/dashboard-layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..618ab3733bc5acfbae096dd6b769a9eb35fd4a60 --- /dev/null +++ b/src/components/page-layouts/dashboard-layout.tsx @@ -0,0 +1,37 @@ +import { FC, PropsWithChildren } from "react"; +import Footer, { + PageFooterProps, +} from "../../modules/navigation/components/footer"; +import DashboardSidebar from "../../modules/navigation/components/sidebar"; +import DashboardTopNavigationBar from "../../modules/navigation/components/sidebar/components/top-navigation-bar"; +import RootHead, { SeoDataProps } from "./root-head"; + +interface Props extends SeoDataProps { + footerType: PageFooterProps["type"]; +} + +const DashboardLayout: FC> = ({ + description, + tags, + title, + children, + footerType, +}) => { + return ( + <> + +
+
+ +
+
+ +
{children}
+
+
+
+ + ); +}; + +export default DashboardLayout; diff --git a/src/components/page-wrapper/information-section.tsx b/src/components/page-layouts/information-section.tsx similarity index 98% rename from src/components/page-wrapper/information-section.tsx rename to src/components/page-layouts/information-section.tsx index a22887db70f3b38a86412e5c6dc716a2527da5b4..8981d872f0d4f1a1c66110378ed76f02aaa2658c 100644 --- a/src/components/page-wrapper/information-section.tsx +++ b/src/components/page-layouts/information-section.tsx @@ -6,7 +6,7 @@ import { useDisclosure, useTheme, } from "@chakra-ui/react"; -import React, { memo, useMemo } from "react"; +import { memo, useMemo } from "react"; import { MdClose } from "react-icons/md"; import { emailShorter } from "../../helpers/email-shorter"; import { useGetMeQuery } from "../../modules/user/hooks/use-get-me-query"; diff --git a/src/components/page-layouts/landing-layout.tsx b/src/components/page-layouts/landing-layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6524c80982c0799d4acae41641afcc7019bd75ae --- /dev/null +++ b/src/components/page-layouts/landing-layout.tsx @@ -0,0 +1,33 @@ +import { FC, PropsWithChildren } from "react"; +import RootHead, { SeoDataProps } from "./root-head"; +import useSeoValidation from "../../seo/hooks/use-seo-validation"; +import Footer, { + PageFooterProps, +} from "../../modules/navigation/components/footer"; +import Header from "../../modules/navigation/components/header"; + +interface Props extends SeoDataProps { + footerType: PageFooterProps["type"]; +} + +const LandingLayout: FC> = ({ + footerType, + children, + description, + tags, + title, +}) => { + useSeoValidation({ description, tags, title }); + return ( + <> + + <> +
+
{children}
+