Skip to content

Commit db932c9

Browse files
committed
refactor: replace zustand with context for modals
1 parent 0d9620e commit db932c9

File tree

13 files changed

+189
-155
lines changed

13 files changed

+189
-155
lines changed

app/layout.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ThemeProvider } from "next-themes";
77
import { cn, constructMetadata } from "@/lib/utils";
88
import { Toaster } from "@/components/ui/toaster";
99
import { Analytics } from "@/components/analytics";
10-
import { ModalProvider } from "@/components/modal-provider";
10+
import ModalProvider from "@/components/modals/providers";
1111
import { TailwindIndicator } from "@/components/tailwind-indicator";
1212

1313
interface RootLayoutProps {
@@ -35,10 +35,9 @@ export default function RootLayout({ children }: RootLayoutProps) {
3535
enableSystem
3636
disableTransitionOnChange
3737
>
38-
{children}
38+
<ModalProvider>{children}</ModalProvider>
3939
<Analytics />
4040
<Toaster />
41-
<ModalProvider />
4241
<TailwindIndicator />
4342
</ThemeProvider>
4443
</SessionProvider>

components/layout/navbar.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
"use client";
22

3+
import { useSession } from "next-auth/react";
34
import Link from "next/link";
45
import { useSelectedLayoutSegment } from "next/navigation";
5-
import { useSession } from "next-auth/react";
6+
import { useContext } from "react";
67

8+
import { DocsSearch } from "@/components/docs/search";
9+
import { ModalContext } from "@/components/modals/providers";
10+
import { Icons } from "@/components/shared/icons";
11+
import MaxWidthWrapper from "@/components/shared/max-width-wrapper";
12+
import { Button } from "@/components/ui/button";
13+
import { Skeleton } from "@/components/ui/skeleton";
714
import { dashboardConfig } from "@/config/dashboard";
815
import { docsConfig } from "@/config/docs";
916
import { marketingConfig } from "@/config/marketing";
1017
import { siteConfig } from "@/config/site";
18+
import { useScroll } from "@/hooks/use-scroll";
1119
import { cn } from "@/lib/utils";
12-
import useScroll from "@/hooks/use-scroll";
13-
import { useSigninModal } from "@/hooks/use-signin-modal";
14-
import { Button } from "@/components/ui/button";
15-
import { Skeleton } from "@/components/ui/skeleton";
16-
import { DocsSearch } from "@/components/docs/search";
17-
import { Icons } from "@/components/shared/icons";
18-
import MaxWidthWrapper from "@/components/shared/max-width-wrapper";
1920

2021
import { UserAccountNav } from "./user-account-nav";
2122

@@ -26,8 +27,8 @@ interface NavBarProps {
2627

2728
export function NavBar({ scroll = false }: NavBarProps) {
2829
const scrolled = useScroll(50);
29-
const signInModal = useSigninModal();
3030
const { data: session, status } = useSession();
31+
const { setShowSignInModal } = useContext(ModalContext);
3132

3233
const selectedLayout = useSelectedLayoutSegment();
3334
const dashBoard = selectedLayout === "dashboard";
@@ -124,7 +125,7 @@ export function NavBar({ scroll = false }: NavBarProps) {
124125
variant="default"
125126
size="sm"
126127
rounded="full"
127-
onClick={signInModal.onOpen}
128+
onClick={() => setShowSignInModal(true)}
128129
>
129130
<span>Sign In</span>
130131
<Icons.arrowRight className="size-4" />

components/modal-provider.tsx

Lines changed: 0 additions & 19 deletions
This file was deleted.

components/modals/providers.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use client";
2+
3+
import { createContext, Dispatch, ReactNode, SetStateAction } from "react";
4+
5+
import { useSignInModal } from "@/components/modals//sign-in-modal";
6+
7+
export const ModalContext = createContext<{
8+
setShowSignInModal: Dispatch<SetStateAction<boolean>>;
9+
}>({
10+
setShowSignInModal: () => {},
11+
});
12+
13+
export default function ModalProvider({ children }: { children: ReactNode }) {
14+
const { SignInModal, setShowSignInModal } = useSignInModal();
15+
16+
return (
17+
<ModalContext.Provider
18+
value={{
19+
setShowSignInModal,
20+
}}
21+
>
22+
<SignInModal />
23+
{children}
24+
</ModalContext.Provider>
25+
);
26+
}

components/layout/sign-in-modal.tsx renamed to components/modals/sign-in-modal.tsx

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
"use client";
2-
3-
import { useState } from "react";
1+
import { signIn } from "next-auth/react";
2+
import {
3+
Dispatch,
4+
SetStateAction,
5+
useCallback,
6+
useMemo,
7+
useState,
8+
} from "react";
49

510
import { Icons } from "@/components/shared/icons";
6-
import { Modal } from "@/components/shared/modal";
711
import { Button } from "@/components/ui/button";
12+
import { Modal } from "@/components/ui/modal";
813
import { siteConfig } from "@/config/site";
9-
import { useSigninModal } from "@/hooks/use-signin-modal";
10-
import { signIn } from "next-auth/react";
1114

12-
export const SignInModal = () => {
13-
const signInModal = useSigninModal();
15+
function SignInModal({
16+
showSignInModal,
17+
setShowSignInModal,
18+
}: {
19+
showSignInModal: boolean;
20+
setShowSignInModal: Dispatch<SetStateAction<boolean>>;
21+
}) {
1422
const [signInClicked, setSignInClicked] = useState(false);
1523

1624
return (
17-
<Modal showModal={signInModal.isOpen} setShowModal={signInModal.onClose}>
25+
<Modal showModal={showSignInModal} setShowModal={setShowSignInModal}>
1826
<div className="w-full">
1927
<div className="flex flex-col items-center justify-center space-y-3 border-b bg-background px-4 py-6 pt-8 text-center md:px-16">
2028
<a href={siteConfig.url}>
@@ -34,10 +42,9 @@ export const SignInModal = () => {
3442
onClick={() => {
3543
setSignInClicked(true);
3644
signIn("google", { redirect: false }).then(() =>
37-
// TODO: fix this without setTimeOut(), modal closes too quickly. Idea: update value before redirect
3845
setTimeout(() => {
39-
signInModal.onClose();
40-
}, 1000)
46+
setShowSignInModal(false);
47+
}, 400),
4148
);
4249
}}
4350
>
@@ -52,4 +59,25 @@ export const SignInModal = () => {
5259
</div>
5360
</Modal>
5461
);
55-
};
62+
}
63+
64+
export function useSignInModal() {
65+
const [showSignInModal, setShowSignInModal] = useState(false);
66+
67+
const SignInModalCallback = useCallback(() => {
68+
return (
69+
<SignInModal
70+
showSignInModal={showSignInModal}
71+
setShowSignInModal={setShowSignInModal}
72+
/>
73+
);
74+
}, [showSignInModal, setShowSignInModal]);
75+
76+
return useMemo(
77+
() => ({
78+
setShowSignInModal,
79+
SignInModal: SignInModalCallback,
80+
}),
81+
[setShowSignInModal, SignInModalCallback],
82+
);
83+
}

components/pricing-cards.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useContext, useState } from "react";
44
import Link from "next/link";
55
import { UserSubscriptionPlan } from "@/types";
66

7+
import { SubscriptionPlan } from "@/types/index";
78
import { pricingData } from "@/config/subscriptions";
89
import { cn } from "@/lib/utils";
9-
import { useSigninModal } from "@/hooks/use-signin-modal";
1010
import { Button, buttonVariants } from "@/components/ui/button";
1111
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
1212
import { BillingFormButton } from "@/components/forms/billing-form-button";
13+
import { ModalContext } from "@/components/modals/providers";
1314
import { HeaderSection } from "@/components/shared/header-section";
1415
import { Icons } from "@/components/shared/icons";
15-
16-
import { SubscriptionPlan } from "../types/index";
17-
import MaxWidthWrapper from "./shared/max-width-wrapper";
16+
import MaxWidthWrapper from "@/components/shared/max-width-wrapper";
1817

1918
interface PricingCardsProps {
2019
userId?: string;
@@ -27,7 +26,7 @@ export function PricingCards({ userId, subscriptionPlan }: PricingCardsProps) {
2726
? true
2827
: false;
2928
const [isYearly, setIsYearly] = useState<boolean>(!!isYearlyDefault);
30-
const signInModal = useSigninModal();
29+
const { setShowSignInModal } = useContext(ModalContext);
3130

3231
const toggleBilling = () => {
3332
setIsYearly(!isYearly);
@@ -127,7 +126,7 @@ export function PricingCards({ userId, subscriptionPlan }: PricingCardsProps) {
127126
: "outline"
128127
}
129128
rounded="full"
130-
onClick={signInModal.onOpen}
129+
onClick={() => setShowSignInModal(true)}
131130
>
132131
Sign in
133132
</Button>

components/shared/modal.tsx

Lines changed: 0 additions & 51 deletions
This file was deleted.

components/ui/modal.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"use client";
2+
3+
import { Dispatch, SetStateAction } from "react";
4+
// import { useRouter } from "next/router";
5+
import { Drawer } from "vaul";
6+
7+
import { Dialog, DialogContent } from "@/components/ui/dialog";
8+
import { useMediaQuery } from "@/hooks/use-media-query";
9+
import { cn } from "@/lib/utils";
10+
11+
interface ModalProps {
12+
children: React.ReactNode;
13+
className?: string;
14+
showModal?: boolean;
15+
setShowModal?: Dispatch<SetStateAction<boolean>>;
16+
onClose?: () => void;
17+
desktopOnly?: boolean;
18+
preventDefaultClose?: boolean;
19+
}
20+
21+
export function Modal({
22+
children,
23+
className,
24+
showModal,
25+
setShowModal,
26+
onClose,
27+
desktopOnly,
28+
preventDefaultClose,
29+
}: ModalProps) {
30+
// const router = useRouter();
31+
32+
const closeModal = ({ dragged }: { dragged?: boolean } = {}) => {
33+
if (preventDefaultClose && !dragged) {
34+
return;
35+
}
36+
// fire onClose event if provided
37+
onClose && onClose();
38+
39+
// if setShowModal is defined, use it to close modal
40+
if (setShowModal) {
41+
setShowModal(false);
42+
}
43+
// else, this is intercepting route @modal
44+
// else {
45+
// router.back();
46+
// }
47+
};
48+
const { isMobile } = useMediaQuery();
49+
50+
if (isMobile && !desktopOnly) {
51+
return (
52+
<Drawer.Root
53+
open={setShowModal ? showModal : true}
54+
onOpenChange={(open) => {
55+
if (!open) {
56+
closeModal({ dragged: true });
57+
}
58+
}}
59+
>
60+
<Drawer.Overlay className="fixed inset-0 z-40 bg-background/80 backdrop-blur-sm" />
61+
<Drawer.Portal>
62+
<Drawer.Content
63+
className={cn(
64+
"fixed inset-x-0 bottom-0 z-50 mt-24 overflow-hidden rounded-t-[10px] border bg-background",
65+
className,
66+
)}
67+
>
68+
<div className="sticky top-0 z-20 flex w-full items-center justify-center bg-inherit">
69+
<div className="my-3 h-1.5 w-16 rounded-full bg-muted-foreground/20" />
70+
</div>
71+
{children}
72+
</Drawer.Content>
73+
<Drawer.Overlay />
74+
</Drawer.Portal>
75+
</Drawer.Root>
76+
);
77+
}
78+
return (
79+
<Dialog
80+
open={setShowModal ? showModal : true}
81+
onOpenChange={(open) => {
82+
if (!open) {
83+
closeModal();
84+
}
85+
}}
86+
>
87+
<DialogContent
88+
onOpenAutoFocus={(e) => e.preventDefault()}
89+
onCloseAutoFocus={(e) => e.preventDefault()}
90+
className={cn(
91+
"overflow-hidden p-0 md:max-w-md md:rounded-2xl md:border",
92+
className,
93+
)}
94+
>
95+
{children}
96+
</DialogContent>
97+
</Dialog>
98+
);
99+
}

0 commit comments

Comments
 (0)