From 8d280b868c7aebffcf157cc0a53c6359eeb3af36 Mon Sep 17 00:00:00 2001 From: Nikita <39565703+nicitaacom@users.noreply.github.com> Date: Tue, 7 Nov 2023 13:47:50 +0100 Subject: [PATCH 1/7] ModalContainer renamed to ModalQueryContainer --- .../ui/{ModalContainer.tsx => ModalQueryContainer.tsx} | 4 ++-- app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx | 6 +++--- app/components/ui/Modals/AuthModal/AuthModal.tsx | 6 +++--- app/components/ui/Modals/CartModal/CartModal.tsx | 6 +++--- app/components/ui/Modals/ChangeLanguageModal.tsx | 6 +++--- app/components/ui/index.ts | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) rename app/components/ui/{ModalContainer.tsx => ModalQueryContainer.tsx} (95%) diff --git a/app/components/ui/ModalContainer.tsx b/app/components/ui/ModalQueryContainer.tsx similarity index 95% rename from app/components/ui/ModalContainer.tsx rename to app/components/ui/ModalQueryContainer.tsx index ac810852..8166326d 100644 --- a/app/components/ui/ModalContainer.tsx +++ b/app/components/ui/ModalQueryContainer.tsx @@ -7,14 +7,14 @@ import { useSwipeable } from "react-swipeable" import { twMerge } from "tailwind-merge" import { AnimatePresence, motion } from "framer-motion" -interface ModalContainerProps { +interface ModalQueryContainerProps { children: React.ReactNode modalQuery: string className?: string isLoading?: boolean } -export function ModalContainer({ children, modalQuery, className, isLoading }: ModalContainerProps) { +export function ModalQueryContainer({ children, modalQuery, className, isLoading }: ModalQueryContainerProps) { const pathname = usePathname() const router = useRouter() const queryParams = useSearchParams() diff --git a/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx b/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx index 1ef257e5..66026177 100644 --- a/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx +++ b/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx @@ -10,7 +10,7 @@ import { RadioButton } from "@/components/ui" import useUserStore from "@/store/user/userStore" import { IDBProduct } from "@/interfaces/IDBProduct" -import { ModalContainer } from "../../ModalContainer" +import { ModalQueryContainer } from "../../ModalQueryContainer" import { EditProductForm } from "./components/EditProductForm" import { AddProductForm } from "./components/AddProductForm" import { DeleteProductForm } from "./components/DeleteProductForm" @@ -35,7 +35,7 @@ export function AdminPanelModal({ label, ownerProducts }: AdminPanelModalProps) }, []) return ( - } - + ) } diff --git a/app/components/ui/Modals/AuthModal/AuthModal.tsx b/app/components/ui/Modals/AuthModal/AuthModal.tsx index 79303073..88d33b81 100644 --- a/app/components/ui/Modals/AuthModal/AuthModal.tsx +++ b/app/components/ui/Modals/AuthModal/AuthModal.tsx @@ -11,7 +11,7 @@ import { AuthError } from "@supabase/supabase-js" import { FormInput } from "../../Inputs/Validation/FormInput" import ContinueWithButton from "@/(auth)/components/ContinueWithButton" -import { Button, Checkbox, ModalContainer } from "../.." +import { Button, Checkbox, ModalQueryContainer } from "../.." import { Timer } from "@/(auth)/components" import useDarkMode from "@/store/ui/darkModeStore" import useUserStore from "@/store/user/userStore" @@ -280,7 +280,7 @@ export function AuthModal({ label }: AdminModalProps) { } return ( - Now change query params back to &variant=login :) )} - + ) } diff --git a/app/components/ui/Modals/CartModal/CartModal.tsx b/app/components/ui/Modals/CartModal/CartModal.tsx index 0b4da52f..a9dde1f2 100644 --- a/app/components/ui/Modals/CartModal/CartModal.tsx +++ b/app/components/ui/Modals/CartModal/CartModal.tsx @@ -15,11 +15,11 @@ import useToast from "@/store/ui/useToast" import useCartStore from "@/store/user/cartStore" import EmptyCart from "./EmptyCart" import { formatCurrency } from "@/utils/currencyFormatter" -import { ModalContainer } from "@/components/ui/ModalContainer" //Are you sure in what - please use clear naming import { Button, Slider } from "../.." import { useAreYouSureClearCartModal } from "@/store/ui/areYouSureClearCartModal" +import { ModalQueryContainer } from "../../ModalQueryContainer" interface CartModalProps { label: string @@ -241,7 +241,7 @@ export function CartModal({ label }: CartModalProps) { } return ( -
@@ -399,6 +399,6 @@ export function CartModal({ label }: CartModalProps) { )}
-
+ ) } diff --git a/app/components/ui/Modals/ChangeLanguageModal.tsx b/app/components/ui/Modals/ChangeLanguageModal.tsx index 66663fdf..d3dbf60c 100644 --- a/app/components/ui/Modals/ChangeLanguageModal.tsx +++ b/app/components/ui/Modals/ChangeLanguageModal.tsx @@ -1,4 +1,4 @@ -import { ModalContainer } from "../ModalContainer" +import { ModalQueryContainer } from "../ModalQueryContainer" import { Button } from ".." import { languages } from "@/constant/languages" import Image from "next/image" @@ -9,7 +9,7 @@ interface NameModalProps { export function ChangeLanguageModal({ label }: NameModalProps) { return ( - + {/* ANY CONTENT (to keep consistent keep {label})*/}

{label}

@@ -22,6 +22,6 @@ export function ChangeLanguageModal({ label }: NameModalProps) { ))}
-
+ ) } diff --git a/app/components/ui/index.ts b/app/components/ui/index.ts index 029746d6..285ceb29 100644 --- a/app/components/ui/index.ts +++ b/app/components/ui/index.ts @@ -1,9 +1,9 @@ import { Button } from "./Button" import { DropdownContainer } from "./DropdownContainer" import { DropdownItem } from "./DropdownItem" -import { ModalContainer } from "./ModalContainer" +import { ModalQueryContainer } from "./ModalQueryContainer" import { Checkbox } from "./Checkbox" import { Slider } from "./Slider" import { RadioButton } from "./RadioButton" -export { Button, DropdownContainer, DropdownItem, ModalContainer, Checkbox, Slider, RadioButton } +export { Button, DropdownContainer, DropdownItem, ModalQueryContainer, Checkbox, Slider, RadioButton } From 2a0085c6efe839a65ba920c9e2262775e2055863 Mon Sep 17 00:00:00 2001 From: Nikita <39565703+nicitaacom@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:25:50 +0100 Subject: [PATCH 2/7] overflow-hidden when modal isOpen improved I know that I renamed and moved some file for some reasons and it makes hard for me to review PR - in future I will consider that and use 'stash changes' - then rename file - then 'restore' --- app/(site)/page.tsx | 4 +- app/(site)/search/page.tsx | 8 +- app/components/Layout.tsx | 5 +- app/components/Navbar/Navbar.tsx | 5 +- .../Navbar/components/CtrlKBadge.tsx | 22 ++++ .../Navbar/components/NavbarSearch.tsx | 3 +- .../Navbar/components/NavbarWrapper.tsx | 5 +- app/components/ui/Inputs/SearchInput.tsx | 3 + .../ui/Modals/AdminPanel/AdminPanelModal.tsx | 2 +- .../ui/Modals/AreYouSureClearCartModal.tsx | 6 +- .../Modals/AreYouSureDeleteProductModal.tsx | 16 ++- .../ui/Modals/CartModal/CartModal.tsx | 4 +- .../ui/Modals/ChangeLanguageModal.tsx | 6 +- app/components/ui/Modals/CtrlKModal.tsx | 24 +++++ ...ainer.tsx => AdminPanelModalContainer.tsx} | 0 .../AreYouSureModalContainer.tsx} | 19 ++-- .../ModalContainers/CartModalContainer.tsx | 6 +- .../ChangeLanguageModalContainer.tsx | 3 +- .../Modals/ModalContainers/ModalContainer.tsx | 96 +++++++++++++++++ .../ModalContainers/ModalQueryContainer.tsx | 101 ++++++++++++++++++ .../ui/Modals/ModalContainers/index.ts | 7 +- app/components/ui/index.ts | 2 +- app/globals.css | 7 +- app/layout.tsx | 10 +- app/providers/ModalsProvider.tsx | 5 +- app/store/ui/ctrlKModal.ts | 15 +++ 26 files changed, 342 insertions(+), 42 deletions(-) create mode 100644 app/components/Navbar/components/CtrlKBadge.tsx create mode 100644 app/components/ui/Modals/CtrlKModal.tsx rename app/components/ui/Modals/ModalContainers/{AdminPanelContainer.tsx => AdminPanelModalContainer.tsx} (100%) rename app/components/ui/{AreYouSureModal.tsx => Modals/ModalContainers/AreYouSureModalContainer.tsx} (91%) create mode 100644 app/components/ui/Modals/ModalContainers/ModalContainer.tsx create mode 100644 app/components/ui/Modals/ModalContainers/ModalQueryContainer.tsx create mode 100644 app/store/ui/ctrlKModal.ts diff --git a/app/(site)/page.tsx b/app/(site)/page.tsx index 1248158e..0d0d594f 100644 --- a/app/(site)/page.tsx +++ b/app/(site)/page.tsx @@ -26,7 +26,9 @@ export default async function Home({ searchParams }: SearchProps) { const entries = products.slice(start, end) return ( -
+
diff --git a/app/(site)/search/page.tsx b/app/(site)/search/page.tsx index 5f8544c0..57c004e5 100644 --- a/app/(site)/search/page.tsx +++ b/app/(site)/search/page.tsx @@ -9,8 +9,14 @@ interface SearchPageProps { } export function generateMetadata({ searchParams: { query } }: SearchPageProps): Metadata { + if (query === undefined) { + return { + title: `Search - 23_store`, + } + } + return { - title: `Search ${query} - Flowmazon`, + title: `Search ${query} - 23_store`, } } diff --git a/app/components/Layout.tsx b/app/components/Layout.tsx index 240cab8b..b1fd9edf 100644 --- a/app/components/Layout.tsx +++ b/app/components/Layout.tsx @@ -32,8 +32,9 @@ export default function Layout({ children }: { children: React.ReactNode }) { return (
+ className="flex flex-col w-full overflow-hidden min-h-screen + bg-background text-title + transition-colors duration-300"> {children} {toast.isOpen && }
diff --git a/app/components/Navbar/Navbar.tsx b/app/components/Navbar/Navbar.tsx index d8b579a6..bb793389 100644 --- a/app/components/Navbar/Navbar.tsx +++ b/app/components/Navbar/Navbar.tsx @@ -16,6 +16,7 @@ import { OpenCartModalButton, OpenUserMenuButton, } from "./components" +import { CtrlKBadge } from "./components/CtrlKBadge" export default async function Navbar() { const { @@ -31,7 +32,9 @@ export default async function Navbar() {
{/* SEARCH + LANGUAGE */}
- + + +
diff --git a/app/components/Navbar/components/CtrlKBadge.tsx b/app/components/Navbar/components/CtrlKBadge.tsx new file mode 100644 index 00000000..0ab3a768 --- /dev/null +++ b/app/components/Navbar/components/CtrlKBadge.tsx @@ -0,0 +1,22 @@ +"use client" + +import { useCtrlKModal } from "@/store/ui/ctrlKModal" +import { useEffect } from "react" + +export function CtrlKBadge() { + const ctrlKModal = useCtrlKModal() + + useEffect(() => { + const handleKeydown = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + //to prevent focus state on browser searchbar + e.preventDefault() + ctrlKModal.toggle() + } + } + document.addEventListener("keydown", handleKeydown) + return () => document.removeEventListener("keydown", handleKeydown) + }, [ctrlKModal, ctrlKModal.toggle]) + + return
⌘+K
+} diff --git a/app/components/Navbar/components/NavbarSearch.tsx b/app/components/Navbar/components/NavbarSearch.tsx index f24a4631..944744cb 100644 --- a/app/components/Navbar/components/NavbarSearch.tsx +++ b/app/components/Navbar/components/NavbarSearch.tsx @@ -17,12 +17,13 @@ async function searchProducts(formData: FormData) { } } -export function NavbarSearch() { +export function NavbarSearch({ children: Children }: { children: React.ReactNode }) { return (
} + endIcon={Children} name="searchQuery" placeholder="Search..." /> diff --git a/app/components/Navbar/components/NavbarWrapper.tsx b/app/components/Navbar/components/NavbarWrapper.tsx index a6f70813..73e84394 100644 --- a/app/components/Navbar/components/NavbarWrapper.tsx +++ b/app/components/Navbar/components/NavbarWrapper.tsx @@ -18,8 +18,9 @@ export function NavbarWrapper({ children }: { children: React.ReactNode }) { }, []) return ( diff --git a/app/components/ui/Inputs/SearchInput.tsx b/app/components/ui/Inputs/SearchInput.tsx index e905fe85..743d7858 100644 --- a/app/components/ui/Inputs/SearchInput.tsx +++ b/app/components/ui/Inputs/SearchInput.tsx @@ -5,6 +5,7 @@ interface InputProps extends React.HTMLAttributes { type?: string className?: string startIcon?: React.ReactElement + endIcon?: React.ReactNode pattern?: string name?: string required?: boolean @@ -14,6 +15,7 @@ export function SearchInput({ type = "text", className = "", startIcon, + endIcon, pattern, required = false, name, @@ -32,6 +34,7 @@ export function SearchInput({ required={required} {...props} /> +
{endIcon}
) } diff --git a/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx b/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx index 66026177..fecaabf4 100644 --- a/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx +++ b/app/components/ui/Modals/AdminPanel/AdminPanelModal.tsx @@ -10,7 +10,7 @@ import { RadioButton } from "@/components/ui" import useUserStore from "@/store/user/userStore" import { IDBProduct } from "@/interfaces/IDBProduct" -import { ModalQueryContainer } from "../../ModalQueryContainer" +import { ModalQueryContainer } from "../ModalContainers/ModalQueryContainer" import { EditProductForm } from "./components/EditProductForm" import { AddProductForm } from "./components/AddProductForm" import { DeleteProductForm } from "./components/DeleteProductForm" diff --git a/app/components/ui/Modals/AreYouSureClearCartModal.tsx b/app/components/ui/Modals/AreYouSureClearCartModal.tsx index 4df801b2..f2cc987e 100644 --- a/app/components/ui/Modals/AreYouSureClearCartModal.tsx +++ b/app/components/ui/Modals/AreYouSureClearCartModal.tsx @@ -1,7 +1,7 @@ "use client" import { useAreYouSureClearCartModal } from "@/store/ui/areYouSureClearCartModal" -import { AreYouSureModal } from "../AreYouSureModal" +import { AreYouSureModalContainer } from "./ModalContainers/AreYouSureModalContainer" import useCartStore from "@/store/user/cartStore" export function AreYouSureClearCartButton() { @@ -10,13 +10,13 @@ export function AreYouSureClearCartButton() { const cartStore = useCartStore() return ( - + secondaryButtonLabel="Back"> ) } diff --git a/app/components/ui/Modals/AreYouSureDeleteProductModal.tsx b/app/components/ui/Modals/AreYouSureDeleteProductModal.tsx index c6c77667..4c769e4d 100644 --- a/app/components/ui/Modals/AreYouSureDeleteProductModal.tsx +++ b/app/components/ui/Modals/AreYouSureDeleteProductModal.tsx @@ -1,12 +1,12 @@ "use client" -import { useState } from "react" +import { useEffect, useState } from "react" import { useRouter } from "next/navigation" import { BiTrash } from "react-icons/bi" import axios from "axios" import { useAreYouSureDeleteProductModal } from "@/store/ui/areYouSureDeleteProductModal" -import { AreYouSureModal } from "../AreYouSureModal" +import { AreYouSureModalContainer } from "./ModalContainers/AreYouSureModalContainer" import useCartStore from "@/store/user/cartStore" export function AreYouSureDeleteProductModal() { @@ -15,6 +15,16 @@ export function AreYouSureDeleteProductModal() { const cartStore = useCartStore() const { title, id } = useAreYouSureDeleteProductModal() const [isLoading, setIsLoading] = useState(false) + const [isMounted, setIsMounted] = useState(false) + + useEffect(() => { + setIsLoading(true) + }, []) + // to prevent SSR because if I render this modal I trigger useEffect + // that trigger document.addEventListener on esc (I need close ctrlK modal on esc press) + if (!isMounted) { + return null + } async function deleteProduct() { setIsLoading(true) @@ -29,7 +39,7 @@ export function AreYouSureDeleteProductModal() { } return ( - { router.push(`${location.origin}/payment?status=success`) - console.log("txHash - ", txHash) + console.log(169, "You may use txHash as check QR code or payment identifier - ", txHash) }) .catch((error: Error) => { error.message.includes("MetaMask Tx Signature: User denied transaction signature.") @@ -175,7 +175,7 @@ export function CartModal({ label }: CartModalProps) { }) .finally(() => setIsConnecting(false)) } catch (error) { - console.log("Error -", error) + console.log(178, "Payment with metamask error -", error) } } diff --git a/app/components/ui/Modals/ChangeLanguageModal.tsx b/app/components/ui/Modals/ChangeLanguageModal.tsx index d3dbf60c..196bd048 100644 --- a/app/components/ui/Modals/ChangeLanguageModal.tsx +++ b/app/components/ui/Modals/ChangeLanguageModal.tsx @@ -1,13 +1,13 @@ -import { ModalQueryContainer } from "../ModalQueryContainer" +import { ModalQueryContainer } from "./ModalContainers/ModalQueryContainer" import { Button } from ".." import { languages } from "@/constant/languages" import Image from "next/image" -interface NameModalProps { +interface ChangeLanguageModalProps { label: string } -export function ChangeLanguageModal({ label }: NameModalProps) { +export function ChangeLanguageModal({ label }: ChangeLanguageModalProps) { return ( {/* ANY CONTENT (to keep consistent keep {label})*/} diff --git a/app/components/ui/Modals/CtrlKModal.tsx b/app/components/ui/Modals/CtrlKModal.tsx new file mode 100644 index 00000000..2774e194 --- /dev/null +++ b/app/components/ui/Modals/CtrlKModal.tsx @@ -0,0 +1,24 @@ +"use client" + +import { BiSearchAlt } from "react-icons/bi" +import { SearchInput } from "../Inputs/SearchInput" +import { ModalContainer } from "./ModalContainers/" +import { useCtrlKModal } from "@/store/ui/ctrlKModal" + +export function CtrlKModal() { + const ctrlKModal = useCtrlKModal() + + return ( + +
+

Search for products

+ } + name="searchQuery" + placeholder="Search..." + /> +
+
+ ) +} diff --git a/app/components/ui/Modals/ModalContainers/AdminPanelContainer.tsx b/app/components/ui/Modals/ModalContainers/AdminPanelModalContainer.tsx similarity index 100% rename from app/components/ui/Modals/ModalContainers/AdminPanelContainer.tsx rename to app/components/ui/Modals/ModalContainers/AdminPanelModalContainer.tsx diff --git a/app/components/ui/AreYouSureModal.tsx b/app/components/ui/Modals/ModalContainers/AreYouSureModalContainer.tsx similarity index 91% rename from app/components/ui/AreYouSureModal.tsx rename to app/components/ui/Modals/ModalContainers/AreYouSureModalContainer.tsx index 5fc0713c..3423d299 100644 --- a/app/components/ui/AreYouSureModal.tsx +++ b/app/components/ui/Modals/ModalContainers/AreYouSureModalContainer.tsx @@ -1,3 +1,5 @@ +"use client" + import { useEffect, useState } from "react" import { IoMdClose } from "react-icons/io" import { IconType } from "react-icons" @@ -5,9 +7,9 @@ import { useSwipeable } from "react-swipeable" import { AnimatePresence, motion } from "framer-motion" import { twMerge } from "tailwind-merge" -import { Button } from "." +import { Button } from "../.." -interface ModalContainerProps { +interface AreYouSureModalContainerProps { isOpen: boolean isLoading?: boolean label: string | React.ReactNode @@ -52,9 +54,9 @@ interface ModalContainerProps { className?: string } -export function AreYouSureModal({ - isOpen, - isLoading, +export function AreYouSureModalContainer({ + isOpen = false, + isLoading = false, label, primaryButtonVariant, primaryButtonIcon: PrimaryButtonIcon, @@ -65,15 +67,17 @@ export function AreYouSureModal({ secondaryButtonAction, secondaryButtonLabel, className, -}: ModalContainerProps) { +}: AreYouSureModalContainerProps) { const [showModal, setShowModal] = useState(isOpen) + console.log(72, "showModal - ", showModal) /* onOpen - show modal - disable scroll and scrollbar */ useEffect(() => { setShowModal(isOpen) if (isOpen) { document.body.style.overflow = "hidden" - document.body.style.width = "calc(100% - 17px)" + document.body.style.width = "calc(100% - 16px)" + document.getElementById("nav")!.style.width = "calc(100% - 16px)" } }, [isOpen]) @@ -88,6 +92,7 @@ export function AreYouSureModal({ function closeModal() { secondaryButtonAction() document.body.removeAttribute("style") + document.getElementById("nav")!.removeAttribute("style") } //Close modal on esc diff --git a/app/components/ui/Modals/ModalContainers/CartModalContainer.tsx b/app/components/ui/Modals/ModalContainers/CartModalContainer.tsx index 473b1d1a..4576f1fa 100644 --- a/app/components/ui/Modals/ModalContainers/CartModalContainer.tsx +++ b/app/components/ui/Modals/ModalContainers/CartModalContainer.tsx @@ -1,12 +1,10 @@ "use client" -import { useSearchParams } from "next/navigation" + import React from "react" -import { CartModal } from ".." -import useCartStore from "@/store/user/cartStore" +import { useSearchParams } from "next/navigation" export function CartModalContainer({ children }: { children: React.ReactNode }) { const searchParams = useSearchParams() - const cartStore = useCartStore() return <>{searchParams.getAll("modal").includes("CartModal") && children} } diff --git a/app/components/ui/Modals/ModalContainers/ChangeLanguageModalContainer.tsx b/app/components/ui/Modals/ModalContainers/ChangeLanguageModalContainer.tsx index 0352ffe3..7af14eba 100644 --- a/app/components/ui/Modals/ModalContainers/ChangeLanguageModalContainer.tsx +++ b/app/components/ui/Modals/ModalContainers/ChangeLanguageModalContainer.tsx @@ -1,6 +1,7 @@ "use client" -import { useSearchParams } from "next/navigation" + import React from "react" +import { useSearchParams } from "next/navigation" export function ChangeLanguageModalContainer({ children }: { children: React.ReactNode }) { const searchParams = useSearchParams() diff --git a/app/components/ui/Modals/ModalContainers/ModalContainer.tsx b/app/components/ui/Modals/ModalContainers/ModalContainer.tsx new file mode 100644 index 00000000..0465609b --- /dev/null +++ b/app/components/ui/Modals/ModalContainers/ModalContainer.tsx @@ -0,0 +1,96 @@ +"use client" + +import { useEffect, useState } from "react" +import { IoMdClose } from "react-icons/io" +import { useSwipeable } from "react-swipeable" +import { AnimatePresence, motion } from "framer-motion" +import { twMerge } from "tailwind-merge" + +interface ModalContainerProps { + isOpen: boolean + isLoading?: boolean + onClose: () => void + className?: string + label?: string | React.ReactNode + children: React.ReactNode +} + +export function ModalContainer({ isOpen, isLoading, onClose, className, label, children }: ModalContainerProps) { + const [showModal, setShowModal] = useState(isOpen) + + /* onOpen - show modal - disable scroll and scrollbar */ + useEffect(() => { + setShowModal(isOpen) + }, [isOpen]) + + //correct way to add event listener to listen keydown + useEffect(() => { + document.addEventListener("keydown", handleKeyDown) + return () => document.removeEventListener("keydown", handleKeyDown) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading]) + + /* onClose - close modal - show scrollbar */ + function closeModal() { + onClose() + } + + //Close modal on esc + const handleKeyDown = (event: KeyboardEvent) => { + //TODO - block esc key if isLoading (in ModalContainer.tsx) + if (event.key === "Escape" && !isLoading) { + closeModal() + } + } + + /* for e.stopPropagation when mousedown on modal and mouseup on modalBg */ + const modalBgHandler = useSwipeable({ + onTouchStartOrOnMouseDown: () => { + closeModal() + }, + trackMouse: true, + }) + + const modalHandler = useSwipeable({ + onTouchStartOrOnMouseDown: e => { + e.event.stopPropagation() + }, + trackMouse: true, + }) + + return ( + + {showModal && ( + + + +
+
{label}
+ {children} +
+
+
+ )} +
+ ) +} diff --git a/app/components/ui/Modals/ModalContainers/ModalQueryContainer.tsx b/app/components/ui/Modals/ModalContainers/ModalQueryContainer.tsx new file mode 100644 index 00000000..defbd044 --- /dev/null +++ b/app/components/ui/Modals/ModalContainers/ModalQueryContainer.tsx @@ -0,0 +1,101 @@ +"use client" + +import { useCallback, useEffect, useState } from "react" +import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { IoMdClose } from "react-icons/io" +import { useSwipeable } from "react-swipeable" +import { twMerge } from "tailwind-merge" +import { AnimatePresence, motion } from "framer-motion" + +interface ModalQueryContainerProps { + children: React.ReactNode + modalQuery: string + className?: string + isLoading?: boolean +} + +export function ModalQueryContainer({ children, modalQuery, className, isLoading = false }: ModalQueryContainerProps) { + const pathname = usePathname() + const router = useRouter() + const queryParams = useSearchParams() + + const showModal = queryParams.getAll("modal").includes(modalQuery) + const [shouldClose, setShouldClose] = useState(false) + + //correct way to add event listener to listen keydown + useEffect(() => { + document.addEventListener("keydown", handleKeyDown) + return () => document.removeEventListener("keydown", handleKeyDown) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading]) + + // Close modal and redirect on close + const closeModal = useCallback(() => { + document.body.removeAttribute("style") + document.getElementById("nav")!.removeAttribute("style") + setShouldClose(true) + setTimeout(() => { + router.push(pathname) + }, 500) + }, [router, pathname]) + + //Close modal on esc + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape" && !isLoading) { + closeModal() + } + } + + /* for e.stopPropagation when mousedown on modal and mouseup on modalBg */ + const modalBgHandler = useSwipeable({ + onTouchStartOrOnMouseDown: () => { + closeModal() + }, + trackMouse: true, + }) + + const modalHandler = useSwipeable({ + onTouchStartOrOnMouseDown: e => { + e.event.stopPropagation() + }, + trackMouse: true, + }) + + if (!showModal) { + return null + } + + return ( + + {shouldClose || + (showModal && ( + + + + {children} + + + ))} + + ) +} diff --git a/app/components/ui/Modals/ModalContainers/index.ts b/app/components/ui/Modals/ModalContainers/index.ts index 476e697e..852c5e4d 100644 --- a/app/components/ui/Modals/ModalContainers/index.ts +++ b/app/components/ui/Modals/ModalContainers/index.ts @@ -1,4 +1,7 @@ -export { CartModalContainer } from "./CartModalContainer" +export { AdminPanelModalContainer } from "./AdminPanelModalContainer" +export { AreYouSureModalContainer } from "./AreYouSureModalContainer" export { AuthModalContainer } from "./AuthModalContainer" +export { CartModalContainer } from "./CartModalContainer" export { ChangeLanguageModalContainer } from "./ChangeLanguageModalContainer" -export { AdminPanelModalContainer } from "./AdminPanelContainer" +export { ModalContainer } from "./ModalContainer" +export { ModalQueryContainer } from "./ModalQueryContainer" diff --git a/app/components/ui/index.ts b/app/components/ui/index.ts index 285ceb29..ae748824 100644 --- a/app/components/ui/index.ts +++ b/app/components/ui/index.ts @@ -1,7 +1,7 @@ import { Button } from "./Button" import { DropdownContainer } from "./DropdownContainer" import { DropdownItem } from "./DropdownItem" -import { ModalQueryContainer } from "./ModalQueryContainer" +import { ModalQueryContainer } from "./Modals/ModalContainers/ModalQueryContainer" import { Checkbox } from "./Checkbox" import { Slider } from "./Slider" import { RadioButton } from "./RadioButton" diff --git a/app/globals.css b/app/globals.css index 556bc51f..d69d5875 100644 --- a/app/globals.css +++ b/app/globals.css @@ -51,7 +51,12 @@ html, body, :root { height: 100%; - background-color: #202020; + width: 100%; + overflow: hidden; + /* This is strongly recommended when modal isOpen */ + /* overflow-y: auto; */ + /* Red color because user should not see this */ + background-color: red; } * { diff --git a/app/layout.tsx b/app/layout.tsx index f52d2e6f..706d0049 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,7 +3,6 @@ import "./globals.css" import type { Metadata } from "next" import { Layout } from "./components" -import Navbar from "./components/Navbar/Navbar" import { AdminPanelModalContainer, AuthModalContainer, @@ -14,8 +13,7 @@ import { AdminPanelModal, AuthModal, CartModal, ChangeLanguageModal } from "./co import ClientOnly from "./components/ClientOnly" import getOwnerProducts from "./actions/getOwnerProducts" import { ModalsProvider } from "./providers/ModalsProvider" -import { InitialPageLoadingSkeleton } from "./components/Skeletons/InitialPageLoadingSkeleton" -import { Dispatch, SetStateAction } from "react" +import Navbar from "./components/Navbar/Navbar" export const metadata: Metadata = { title: "23_store", @@ -42,8 +40,10 @@ export default async function RootLayout({ children }: { children: React.ReactNo - - {children} + + + {children} + diff --git a/app/providers/ModalsProvider.tsx b/app/providers/ModalsProvider.tsx index 5d15e6a3..274af8fd 100644 --- a/app/providers/ModalsProvider.tsx +++ b/app/providers/ModalsProvider.tsx @@ -1,8 +1,10 @@ "use client" -import { AreYouSureDeleteProductModal } from "@/components/ui/Modals/AreYouSureDeleteProductModal" import { useEffect, useState } from "react" +import { AreYouSureDeleteProductModal } from "@/components/ui/Modals/AreYouSureDeleteProductModal" +import { CtrlKModal } from "@/components/ui/Modals/CtrlKModal" + export function ModalsProvider() { const [isMounted, setIsMounted] = useState(false) @@ -18,6 +20,7 @@ export function ModalsProvider() { <> {/* */} + ) } diff --git a/app/store/ui/ctrlKModal.ts b/app/store/ui/ctrlKModal.ts new file mode 100644 index 00000000..654e9d35 --- /dev/null +++ b/app/store/ui/ctrlKModal.ts @@ -0,0 +1,15 @@ +import { create } from "zustand" + +type CtrlKModalStore = { + isOpen: boolean + openModal: () => void + closeModal: () => void + toggle: () => void +} + +export const useCtrlKModal = create((set, get) => ({ + isOpen: false, + openModal: () => set({ isOpen: true }), + closeModal: () => set({ isOpen: false }), + toggle: () => set({ isOpen: !get().isOpen }), +})) From 2223c508d1ba07400d61d438f859bed5f7331da2 Mon Sep 17 00:00:00 2001 From: Nikita <39565703+nicitaacom@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:37:33 +0100 Subject: [PATCH 3/7] SupportButton incapsulated --- app/components/Navbar/Navbar.tsx | 23 ++-------------- .../Navbar/components/SupportButton.tsx | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 app/components/Navbar/components/SupportButton.tsx diff --git a/app/components/Navbar/Navbar.tsx b/app/components/Navbar/Navbar.tsx index bb793389..8dbd3076 100644 --- a/app/components/Navbar/Navbar.tsx +++ b/app/components/Navbar/Navbar.tsx @@ -1,11 +1,7 @@ -import Link from "next/link" -import { FiPhoneCall } from "react-icons/fi" import { BiSearchAlt } from "react-icons/bi" import supabaseServer from "@/libs/supabaseServer" -import { contact } from "@/constant/contacts" import { Language } from "../Language" -import { DropdownContainer } from "../ui/DropdownContainer" import { SwitchDarkMode } from ".." import { NavbarWrapper } from "./components/NavbarWrapper" import { @@ -17,6 +13,7 @@ import { OpenUserMenuButton, } from "./components" import { CtrlKBadge } from "./components/CtrlKBadge" +import { SupportButton } from "./components/SupportButton" export default async function Navbar() { const { @@ -43,23 +40,7 @@ export default async function Navbar() { - }> -
-
- - Telegram - -

(response 8s)

-
-
-
+ {user ? : } diff --git a/app/components/Navbar/components/SupportButton.tsx b/app/components/Navbar/components/SupportButton.tsx new file mode 100644 index 00000000..ad150208 --- /dev/null +++ b/app/components/Navbar/components/SupportButton.tsx @@ -0,0 +1,27 @@ +import Link from "next/link" +import { FiPhoneCall } from "react-icons/fi" + +import { contact } from "@/constant/contacts" +import { DropdownContainer } from "@/components/ui" + +export function SupportButton() { + return ( + }> +
+
+ + Telegram + +

(response 8s)

+
+
+
+ ) +} From 6b6a0095f99766a84f2721741f00e636db0d5db9 Mon Sep 17 00:00:00 2001 From: Nikita <39565703+nicitaacom@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:39:00 +0100 Subject: [PATCH 4/7] store for avatarDropdown created --- app/store/ui/avatarDropdown.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/store/ui/avatarDropdown.ts diff --git a/app/store/ui/avatarDropdown.ts b/app/store/ui/avatarDropdown.ts new file mode 100644 index 00000000..a597aefd --- /dev/null +++ b/app/store/ui/avatarDropdown.ts @@ -0,0 +1,13 @@ +import { create } from "zustand" + +type AvatarDropdownStore = { + isOpen: boolean + openModal: () => void + closeModal: () => void +} + +export const useAvatarDropdown = create()(set => ({ + isOpen: false, + openModal: () => set({ isOpen: true }), + closeModal: () => set({ isOpen: false }), +})) From b515098b05d910a46b4c8e2a89891b105a08836c Mon Sep 17 00:00:00 2001 From: Nikita <39565703+nicitaacom@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:44:08 +0100 Subject: [PATCH 5/7] profile_picture_url changed to avatar_url to be consistent with AvatarDropdown --- app/(auth)/auth/callback/route.ts | 2 +- app/(auth)/auth/client/page.tsx | 4 ++-- .../Navbar/components/OpenUserMenuButton.tsx | 4 +++- app/store/user/userStore.ts | 12 ++++++------ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/(auth)/auth/callback/route.ts b/app/(auth)/auth/callback/route.ts index 255f41b7..6d40ae12 100644 --- a/app/(auth)/auth/callback/route.ts +++ b/app/(auth)/auth/callback/route.ts @@ -26,6 +26,6 @@ export async function GET(request: Request) { return NextResponse.redirect( `${requestUrl.origin}/auth/client?userId=${user?.id}&username=${user?.user_metadata.name} - &email=${user?.user_metadata.email}&profile_picture_url=${user?.user_metadata.picture}`, + &email=${user?.user_metadata.email}&avatar_url=${user?.user_metadata.picture}`, ) } diff --git a/app/(auth)/auth/client/page.tsx b/app/(auth)/auth/client/page.tsx index 47dc259b..d70e544f 100644 --- a/app/(auth)/auth/client/page.tsx +++ b/app/(auth)/auth/client/page.tsx @@ -11,9 +11,9 @@ export default function Page() { const userId = useSearchParams().get("userId") const username = useSearchParams().get("username") const email = useSearchParams().get("email") - const profile_picture_url = useSearchParams().get("profile_picture_url") + const avatar_url = useSearchParams().get("avatar_url") useEffect(() => { - userStore.setUser(userId ?? "", username ?? "", email ?? "", profile_picture_url ?? "") + userStore.setUser(userId ?? "", username ?? "", email ?? "", avatar_url ?? "") router.push("/") //to prevent error about too many re-renders // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/app/components/Navbar/components/OpenUserMenuButton.tsx b/app/components/Navbar/components/OpenUserMenuButton.tsx index 7dfd1829..31842c5f 100644 --- a/app/components/Navbar/components/OpenUserMenuButton.tsx +++ b/app/components/Navbar/components/OpenUserMenuButton.tsx @@ -11,8 +11,10 @@ import LogoutDropdownItem from "./LogoutDropdownItem" import { SwitchDarkMode } from "@/components" import { contact } from "@/constant/contacts" import { DropdownContainer, DropdownItem } from "@/components/ui" +import { useAvatarDropdown } from "@/store/ui/avatarDropdown" export function OpenUserMenuButton() { + const avatarDropdown = useAvatarDropdown() const userStore = useUserStore() const mode = useDarkMode() @@ -25,7 +27,7 @@ export function OpenUserMenuButton() { <> user logo void + avatarUrl: string + setUser: (userId: string, username: string, email: string, avatarUrl: string) => void logoutUser: () => void } @@ -19,15 +19,15 @@ export const userStore = (set: SetState): UserStore => ({ isAuthenticated: false, username: "", email: "", - profilePictureUrl: "", - setUser(userId: string, username: string, email: string, profilePictureUrl: string) { + avatarUrl: "", + setUser(userId: string, username: string, email: string, avatarUrl: string) { set((state: UserStore) => ({ ...state, userId: userId, isAuthenticated: true, username: username, email: email, - profilePictureUrl: profilePictureUrl, + avatarUrl: avatarUrl, })) }, logoutUser() { @@ -37,7 +37,7 @@ export const userStore = (set: SetState): UserStore => ({ isAuthenticated: false, username: "", email: "", - profilePictureUrl: "", + avatarUrl: "", })) }, }) From 17bc966fc6f1e98c59af954ba3b72751c46d0543 Mon Sep 17 00:00:00 2001 From: Nikita <39565703+nicitaacom@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:04:55 +0100 Subject: [PATCH 6/7] close avatarDropdown on DropdownItem click Also I created store to get access to dropdown open / close functions Also I improved avatarDropdown hook --- .../Navbar/components/OpenUserMenuButton.tsx | 16 +++++++-- app/components/ui/DropdownContainer.tsx | 8 ++--- app/hooks/ui/useAvatarDropdownClose.ts | 34 +++++++++++++++++++ app/hooks/ui/useCloseOnClickOutside.ts | 32 ----------------- app/store/ui/avatarDropdown.ts | 16 +++++---- 5 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 app/hooks/ui/useAvatarDropdownClose.ts delete mode 100644 app/hooks/ui/useCloseOnClickOutside.ts diff --git a/app/components/Navbar/components/OpenUserMenuButton.tsx b/app/components/Navbar/components/OpenUserMenuButton.tsx index 31842c5f..14d61ac1 100644 --- a/app/components/Navbar/components/OpenUserMenuButton.tsx +++ b/app/components/Navbar/components/OpenUserMenuButton.tsx @@ -12,12 +12,24 @@ import { SwitchDarkMode } from "@/components" import { contact } from "@/constant/contacts" import { DropdownContainer, DropdownItem } from "@/components/ui" import { useAvatarDropdown } from "@/store/ui/avatarDropdown" +import { useRouter } from "next/navigation" export function OpenUserMenuButton() { + const router = useRouter() const avatarDropdown = useAvatarDropdown() const userStore = useUserStore() const mode = useDarkMode() + function openAdminPanel() { + router.push("?modal=AdminPanel") + avatarDropdown.closeDropdown() + } + + function openChangeLanguageModal() { + router.push("?modal=ChangeLanguage") + avatarDropdown.closeDropdown() + } + return ( }> - + - + -
setIsDropdown(!isDropdown)}> +
+
{icon}
diff --git a/app/hooks/ui/useAvatarDropdownClose.ts b/app/hooks/ui/useAvatarDropdownClose.ts new file mode 100644 index 00000000..6bf7a3b6 --- /dev/null +++ b/app/hooks/ui/useAvatarDropdownClose.ts @@ -0,0 +1,34 @@ +import { useAvatarDropdown } from "@/store/ui/avatarDropdown" +import { useEffect, useRef } from "react" + +const useAvatarDropdownClose = () => { + const avatarDropdownRef = useRef(null) + const { isDropdown, openDropdown, closeDropdown, toggle } = useAvatarDropdown() + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (avatarDropdownRef.current && !avatarDropdownRef.current.contains(event.target as Node)) { + closeDropdown() + } + } + + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key === "Escape") { + closeDropdown() + } + } + + document.addEventListener("mousedown", handleClickOutside) + document.addEventListener("keydown", handleKeyPress) + + return () => { + document.removeEventListener("mousedown", handleClickOutside) + document.removeEventListener("keydown", handleKeyPress) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return { isDropdown, openDropdown, closeDropdown, toggle, avatarDropdownRef } +} + +export default useAvatarDropdownClose diff --git a/app/hooks/ui/useCloseOnClickOutside.ts b/app/hooks/ui/useCloseOnClickOutside.ts deleted file mode 100644 index fa15d658..00000000 --- a/app/hooks/ui/useCloseOnClickOutside.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect, useRef, useState } from "react" - -const useCloseOnClickOutside = () => { - const [isDropdown, setIsDropdown] = useState(false) - const dropdownContainerRef = useRef(null) - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownContainerRef.current && !dropdownContainerRef.current.contains(event.target as Node)) { - setIsDropdown(false) - } - } - - const handleKeyPress = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setIsDropdown(false) - } - } - - document.addEventListener("mousedown", handleClickOutside) - document.addEventListener("keydown", handleKeyPress) - - return () => { - document.removeEventListener("mousedown", handleClickOutside) - document.removeEventListener("keydown", handleKeyPress) - } - }, []) - - return { isDropdown, dropdownContainerRef, setIsDropdown } -} - -export default useCloseOnClickOutside diff --git a/app/store/ui/avatarDropdown.ts b/app/store/ui/avatarDropdown.ts index a597aefd..574d11ea 100644 --- a/app/store/ui/avatarDropdown.ts +++ b/app/store/ui/avatarDropdown.ts @@ -1,13 +1,15 @@ import { create } from "zustand" type AvatarDropdownStore = { - isOpen: boolean - openModal: () => void - closeModal: () => void + isDropdown: boolean + openDropdown: () => void + closeDropdown: () => void + toggle: () => void } -export const useAvatarDropdown = create()(set => ({ - isOpen: false, - openModal: () => set({ isOpen: true }), - closeModal: () => set({ isOpen: false }), +export const useAvatarDropdown = create()((set, get) => ({ + isDropdown: false, + openDropdown: () => set({ isDropdown: true }), + closeDropdown: () => set({ isDropdown: false }), + toggle: () => set({ isDropdown: !get().isDropdown }), })) From 4c05c443ff91025b89cc0c27855f5207c51289d8 Mon Sep 17 00:00:00 2001 From: Nikita <39565703+nicitaacom@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:38:08 +0100 Subject: [PATCH 7/7] areYouSureClearCartModal with lowercase --- app/components/ui/Modals/CartModal/CartModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/ui/Modals/CartModal/CartModal.tsx b/app/components/ui/Modals/CartModal/CartModal.tsx index dea91fe9..433f0a74 100644 --- a/app/components/ui/Modals/CartModal/CartModal.tsx +++ b/app/components/ui/Modals/CartModal/CartModal.tsx @@ -28,7 +28,7 @@ interface CartModalProps { export function CartModal({ label }: CartModalProps) { const router = useRouter() const cartStore = useCartStore() - const AreYouSureClearCartModal = useAreYouSureClearCartModal() + const areYouSureClearCartModal = useAreYouSureClearCartModal() const toast = useToast() const { isAuthenticated } = useUserStore() @@ -360,7 +360,7 @@ export function CartModal({ label }: CartModalProps) { Total:  {formatCurrency(cartStore.getProductsPrice())} -