Skip to content

Commit 53ace97

Browse files
committed
refactor(auth, feedback): implement login and feedback modals with Zustand state management
1 parent a6da6d7 commit 53ace97

File tree

10 files changed

+100
-138
lines changed

10 files changed

+100
-138
lines changed

src/app/layout.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import type { Metadata, Viewport } from "next";
33
import { Outfit, Roboto_Mono } from "next/font/google";
44
import { Navbar, Sidebar } from "@/components/layout";
55
import { BackToTop } from "@/components/ui";
6-
// import { ResourceFilters } from "@/components/resources";
6+
import { getCategories } from "@/actions/categories";
7+
import { LoginModal } from "@/components/auth";
8+
import { FeedbackModal } from "@/components/feedback";
79
import { PROJECT_NAME, PROJECT_DESCRIPTION } from "@/config/constants";
810
import Providers from "./providers";
911

12+
import "prismjs/themes/prism-tomorrow.css";
1013
import "../styles/globals.css";
11-
import { getCategories } from "@/actions/categories";
1214

1315
const outfit = Outfit({
1416
variable: "--font-outfit",
@@ -49,10 +51,11 @@ export default async function RootLayout({ children }: Props) {
4951
<Sidebar categories={categories} />
5052
<div className="w-full">
5153
<Navbar />
52-
{/* <ResourceFilters /> */}
5354
{children}
5455
</div>
5556
<BackToTop />
57+
<LoginModal />
58+
<FeedbackModal />
5659
</div>
5760
</Providers>
5861
</body>

src/components/auth/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export { default as GitHubButton } from "./github-button";
22
export { default as GoogleButton } from "./google-button";
3-
export { default as ModalLoginContext } from "./login-modal";
3+
export { default as LoginModal } from "./login-modal";
44
export { default as UserProfile } from "./user-profile";

src/components/auth/login-modal.tsx

Lines changed: 25 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,34 @@
11
"use client";
22

3-
import {
4-
createContext,
5-
useCallback,
6-
useContext,
7-
useState,
8-
ReactNode,
9-
} from "react";
103
import { Modal, ModalContent, ModalHeader, ModalBody } from "@heroui/react";
114
import { GoogleButton, GitHubButton } from "@/components/auth";
5+
import { useLoginModal } from "@/stores/use-login-modal";
126

13-
type ModalContextType = {
14-
open: () => void;
15-
close: () => void;
16-
};
17-
18-
const ModalLoginContext = createContext<ModalContextType | undefined>(
19-
undefined,
20-
);
21-
22-
export const useLoginModal = () => {
23-
const context = useContext(ModalLoginContext);
24-
if (!context) {
25-
throw new Error("useLoginModal must be used within a ModalLoginProvider");
26-
}
27-
return context;
28-
};
29-
30-
type Props = {
31-
children: ReactNode;
32-
};
33-
34-
export default function ModalLoginProvider({ children }: Props) {
35-
const [isOpen, setIsOpen] = useState(false);
36-
37-
const open = useCallback(() => setIsOpen(true), []);
38-
const close = useCallback(() => setIsOpen(false), []);
7+
export default function ModalLoginProvider() {
8+
const { isOpen, onOpenChange } = useLoginModal();
399

4010
return (
41-
<ModalLoginContext.Provider value={{ open, close }}>
42-
{children}
43-
44-
<Modal isOpen={isOpen} onOpenChange={setIsOpen} backdrop="blur">
45-
<ModalContent>
46-
<>
47-
<ModalHeader>
48-
<div className="flex flex-col gap-2">
49-
<h2 className="text-2xl font-semibold tracking-tight">
50-
Welcome back
51-
</h2>
52-
<p className="text-sm text-neutral-500">
53-
Sign in to continue with your account
54-
</p>
55-
</div>
56-
</ModalHeader>
57-
<ModalBody>
58-
<section className="flex flex-col gap-2 pb-10">
59-
<GoogleButton />
60-
<GitHubButton />
61-
</section>
62-
</ModalBody>
63-
</>
64-
</ModalContent>
65-
</Modal>
66-
</ModalLoginContext.Provider>
11+
<Modal isOpen={isOpen} onOpenChange={onOpenChange} backdrop="blur">
12+
<ModalContent>
13+
<>
14+
<ModalHeader>
15+
<div className="flex flex-col gap-2">
16+
<h2 className="text-2xl font-semibold tracking-tight">
17+
Welcome back
18+
</h2>
19+
<p className="text-sm text-neutral-500">
20+
Sign in to continue with your account
21+
</p>
22+
</div>
23+
</ModalHeader>
24+
<ModalBody>
25+
<section className="flex flex-col gap-2 pb-10">
26+
<GoogleButton />
27+
<GitHubButton />
28+
</section>
29+
</ModalBody>
30+
</>
31+
</ModalContent>
32+
</Modal>
6733
);
6834
}

src/components/feedback/feedback-form.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import { useRouter } from "next/navigation";
77
import { Input, Textarea, Button, addToast } from "@heroui/react";
88
import { useForm } from "react-hook-form";
99
import { zodResolver } from "@hookform/resolvers/zod";
10+
import { useFeedbackModal } from "@/stores/use-feedback-modal";
1011
import { feedbackSchema } from "@/lib/zod";
1112
import { HOME_PATH } from "@/config/constants";
1213

1314
export default function FeedbackForm() {
1415
const router = useRouter();
1516
const [apiError, setApiError] = useState<string | null>(null);
17+
const { onOpenChange } = useFeedbackModal();
1618

1719
const {
1820
register,
@@ -36,10 +38,11 @@ export default function FeedbackForm() {
3638
addToast({
3739
title: "Thank you for your feedback!",
3840
description:
39-
"We appreciate your feedback and will use it to continuously improve. Your opinion is valuable to us.",
41+
"We appreciate your feedback and will use it to continuously improve.",
4042
});
4143

4244
reset();
45+
onOpenChange(false);
4346
router.push(HOME_PATH);
4447
} catch (error) {
4548
console.error(error);
Lines changed: 29 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,38 @@
11
"use client";
22

3-
import {
4-
createContext,
5-
useCallback,
6-
useContext,
7-
useState,
8-
ReactNode,
9-
} from "react";
103
import { Modal, ModalContent, ModalHeader, ModalBody } from "@heroui/react";
4+
import { useFeedbackModal } from "@/stores/use-feedback-modal";
115
import FeedbackForm from "./feedback-form";
126

13-
type ModalContextType = {
14-
open: () => void;
15-
close: () => void;
16-
};
17-
18-
const ModalFeedbackContext = createContext<ModalContextType | undefined>(
19-
undefined,
20-
);
21-
22-
export const useFeedbackModal = () => {
23-
const context = useContext(ModalFeedbackContext);
24-
if (!context) {
25-
throw new Error(
26-
"useFeedbackModal must be used within a ModalFeedbackProvider",
27-
);
28-
}
29-
return context;
30-
};
31-
32-
type Props = {
33-
children: ReactNode;
34-
};
35-
36-
export default function ModalFeedbackProvider({ children }: Props) {
37-
const [isOpen, setIsOpen] = useState<boolean>(false);
38-
39-
const open = useCallback(() => setIsOpen(true), []);
40-
const close = useCallback(() => setIsOpen(false), []);
7+
export default function FeedbackModal() {
8+
const { isOpen, onOpenChange } = useFeedbackModal();
419

4210
return (
43-
<ModalFeedbackContext.Provider value={{ open, close }}>
44-
{children}
45-
46-
<Modal
47-
isOpen={isOpen}
48-
onOpenChange={setIsOpen}
49-
backdrop="blur"
50-
classNames={{
51-
base: "bg-white dark:bg-neutral-950 border-2 border-neutral-200 dark:border-neutral-800",
52-
}}
53-
>
54-
<ModalContent>
55-
<>
56-
<ModalHeader>
57-
<div className="flex flex-col gap-2">
58-
<h2 className="text-2xl font-semibold tracking-tight">
59-
Feedback
60-
</h2>
61-
<p className="text-sm text-neutral-500">
62-
Let us know what you think about our project.
63-
</p>
64-
</div>
65-
</ModalHeader>
66-
<ModalBody>
67-
<FeedbackForm />
68-
</ModalBody>
69-
</>
70-
</ModalContent>
71-
</Modal>
72-
</ModalFeedbackContext.Provider>
11+
<Modal
12+
isOpen={isOpen}
13+
onOpenChange={onOpenChange}
14+
backdrop="blur"
15+
classNames={{
16+
base: "bg-white dark:bg-neutral-950 border-2 border-neutral-200 dark:border-neutral-800",
17+
}}
18+
>
19+
<ModalContent>
20+
<>
21+
<ModalHeader>
22+
<div className="flex flex-col gap-2">
23+
<h2 className="text-2xl font-semibold tracking-tight">
24+
Feedback
25+
</h2>
26+
<p className="text-sm text-neutral-500">
27+
Let us know what you think about our project.
28+
</p>
29+
</div>
30+
</ModalHeader>
31+
<ModalBody>
32+
<FeedbackForm />
33+
</ModalBody>
34+
</>
35+
</ModalContent>
36+
</Modal>
7337
);
7438
}

src/components/feedback/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { default as ModalFeedbackContext } from "./feedback-modal";
1+
export { default as FeedbackModal } from "./feedback-modal";

src/components/layout/navbar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import { Breadcrumbs, ThemeSwitcher } from "@/components/ui";
88
import { UserProfile } from "@/components/auth";
99
import { Discord } from "@/components/icons";
1010
import { SidebarMobile } from "@/components/layout";
11-
import { DISCORD_INVITATION_LINK } from "@/config/constants";
11+
import { useLoginModal } from "@/stores/use-login-modal";
1212
import { useAuthUser } from "@/hooks/use-auth-user";
13-
import { useLoginModal } from "../auth/login-modal";
13+
import { DISCORD_INVITATION_LINK } from "@/config/constants";
1414

1515
export default function Navbar() {
1616
const router = useRouter();
1717
const { user } = useAuthUser();
18-
const { open } = useLoginModal();
18+
const { onOpen: loginOnOpen } = useLoginModal();
1919

2020
const { isOpen, onOpen, onOpenChange } = useDisclosure();
2121

@@ -56,7 +56,7 @@ export default function Navbar() {
5656
) : (
5757
<Button
5858
size="sm"
59-
onPress={open}
59+
onPress={loginOnOpen}
6060
variant="solid"
6161
className="bg-neutral-950 dark:bg-white text-white dark:text-neutral-950 font-medium"
6262
>

src/components/layout/sidebar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import {
1414
} from "@heroui/react";
1515
import { Folder, Origami } from "lucide-react";
1616
import { Footer, SearchBar } from "@/components/layout";
17+
import { useFeedbackModal } from "@/stores/use-feedback-modal";
18+
import { useActiveLink } from "@/hooks/use-active-link";
1719
import {
1820
DASHBOARD_ITEMS,
1921
HOME_PATH,
2022
PROJECT_NAME,
2123
SIDEBAR_ITEMS,
2224
} from "@/config/constants";
23-
import { useFeedbackModal } from "../feedback/feedback-modal";
24-
import { useActiveLink } from "@/hooks/use-active-link";
2525

2626
type SidebarListProps = {
2727
items: Array<{
@@ -35,7 +35,7 @@ type SidebarListProps = {
3535

3636
function SidebarList({ items }: SidebarListProps) {
3737
const { linkClass } = useActiveLink();
38-
const { open } = useFeedbackModal();
38+
const { onOpen } = useFeedbackModal();
3939

4040
const base = "flex items-center gap-3 pr-3 text-sm transition-colors";
4141
const active = "text-neutral-950 dark:text-white font-medium";
@@ -49,7 +49,7 @@ function SidebarList({ items }: SidebarListProps) {
4949
<Link
5050
href={item.path}
5151
className={linkClass(item.path, base, active, inactive)}
52-
{...(item.onclick && { onClick: open })}
52+
{...(item.onclick && { onClick: onOpen })}
5353
>
5454
<item.icon size={16} />
5555
<span>{item.title}</span>

src/stores/use-feedback-modal.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { create } from "zustand";
2+
3+
type FeedbackModalState = {
4+
isOpen: boolean;
5+
onOpen: () => void;
6+
onOpenChange: (open: boolean) => void;
7+
};
8+
9+
export const useFeedbackModal = create<FeedbackModalState>((set) => ({
10+
isOpen: false,
11+
onOpen: () => set({ isOpen: true }),
12+
onOpenChange: (open: boolean) => set({ isOpen: open }),
13+
}));

src/stores/use-login-modal.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { create } from "zustand";
2+
3+
type LoginModalState = {
4+
isOpen: boolean;
5+
onOpen: () => void;
6+
onOpenChange: (open: boolean) => void;
7+
};
8+
9+
export const useLoginModal = create<LoginModalState>((set) => ({
10+
isOpen: false,
11+
onOpen: () => set({ isOpen: true }),
12+
onOpenChange: (open: boolean) => set({ isOpen: open }),
13+
}));

0 commit comments

Comments
 (0)