From 0e614f544a68a0e1eef10b7b14842a78928b8378 Mon Sep 17 00:00:00 2001 From: mr-shortman Date: Sun, 23 Mar 2025 13:16:59 +0100 Subject: [PATCH] added zustand and global dialog component --- package.json | 3 +- pnpm-lock.yaml | 27 ++++++++++++ src/app/(PAGES)/layout.tsx | 2 + src/components/comment/comment-section.tsx | 34 +++++++++++---- src/components/comment/comment.tsx | 21 ++++++--- src/components/global-dialog.tsx | 50 ++++++++++++++++++++++ src/lib/store/use-global-dialog.ts | 37 ++++++++++++++++ 7 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 src/components/global-dialog.tsx create mode 100644 src/lib/store/use-global-dialog.ts diff --git a/package.json b/package.json index 28cf9e9..9fe0d00 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ "tiptap-extension-resize-image": "^1.2.1", "use-debounce": "^10.0.4", "winston": "^3.17.0", - "zod": "^3.24.2" + "zod": "^3.24.2", + "zustand": "^5.0.3" }, "devDependencies": { "@types/eslint": "^8.56.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a5d974..b138e6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,9 @@ importers: zod: specifier: ^3.24.2 version: 3.24.2 + zustand: + specifier: ^5.0.3 + version: 5.0.3(@types/react@18.3.18)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) devDependencies: '@types/eslint': specifier: ^8.56.10 @@ -4774,6 +4777,24 @@ packages: react: optional: true + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -9663,4 +9684,10 @@ snapshots: '@types/react': 18.3.18 react: 18.3.1 + zustand@5.0.3(@types/react@18.3.18)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): + optionalDependencies: + '@types/react': 18.3.18 + react: 18.3.1 + use-sync-external-store: 1.4.0(react@18.3.1) + zwitch@2.0.4: {} diff --git a/src/app/(PAGES)/layout.tsx b/src/app/(PAGES)/layout.tsx index 1b59749..c210004 100644 --- a/src/app/(PAGES)/layout.tsx +++ b/src/app/(PAGES)/layout.tsx @@ -1,3 +1,4 @@ +import GlobalDialog from "@/components/global-dialog"; import { AppSidebar } from "@/components/layout/app-sidebar"; import Navbar from "@/components/layout/navbar"; import { SessionProvider } from "@/components/session-provider"; @@ -13,6 +14,7 @@ async function Layout({ children }: { children: React.ReactNode }) { return ( +
diff --git a/src/components/comment/comment-section.tsx b/src/components/comment/comment-section.tsx index 0fcc535..749bb6b 100644 --- a/src/components/comment/comment-section.tsx +++ b/src/components/comment/comment-section.tsx @@ -2,16 +2,19 @@ import React, { useEffect, useRef, useState } from "react"; import CommentForm from "@/components/comment/comment-form"; import CommentList from "@/components/comment/comment-list"; -import { Comment, CommentNode } from "@/server/db/schema"; +import { CommentNode } from "@/server/db/schema"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; import { useComments } from "@/lib/hooks/use-comments-hook"; - -import { SocketProvider, useSocket } from "../socket-context"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { useSocket } from "../socket-context"; import { JOIN_ROOM_EVENT } from "@/server/socket/event-const"; +import { useSession } from "../session-provider"; +import { LogIn } from "lucide-react"; function CommentSection({ articleId }: { articleId: string }) { const { comments } = useComments(articleId); + const { session } = useSession(); const [replyComment, setReplyComment] = useState(); const commentRefs = useRef>(new Map()); const { socket } = useSocket(); @@ -28,12 +31,25 @@ function CommentSection({ articleId }: { articleId: string }) { return (
- setReplyComment(undefined)} - commentRefs={commentRefs} - /> + {session ? ( + setReplyComment(undefined)} + commentRefs={commentRefs} + /> + ) : ( + + + + Melde dich an um einen Kommentar zu schreiben + + + + + )}
diff --git a/src/components/comment/comment.tsx b/src/components/comment/comment.tsx index c84d66e..e5ebc05 100644 --- a/src/components/comment/comment.tsx +++ b/src/components/comment/comment.tsx @@ -9,6 +9,7 @@ import { Session } from "next-auth"; import { Badge } from "../ui/badge"; import CommentDeleteButton from "./comment-delete-button"; import { cn } from "@/lib/utils"; +import { useGlobalDialogStore } from "@/lib/store/use-global-dialog"; const Comment = React.memo( (props: { @@ -19,9 +20,19 @@ const Comment = React.memo( level?: number; }) => { const { comment, setReplyComment, setRef, session, level = 0 } = props; - const isAuthor = session?.user?.id === comment?.author?.id; - + const openGlobalDialog = useGlobalDialogStore((state) => state.openDialog); + const handleReplyClick = () => { + if (!session) + openGlobalDialog({ + title: "Du bist nicht angemeldet", + message: + "Um auf einen Kommentar zu antworten, musst du dich anmelden.", + action: "/api/auth/signin", + actionText: "Jetzt Anmelden", + }); + else setReplyComment(comment); + }; return (
-
diff --git a/src/components/global-dialog.tsx b/src/components/global-dialog.tsx new file mode 100644 index 0000000..3d5dff0 --- /dev/null +++ b/src/components/global-dialog.tsx @@ -0,0 +1,50 @@ +"use client"; +import React from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useGlobalDialogStore } from "@/lib/store/use-global-dialog"; +import { Button } from "./ui/button"; +import Link from "next/link"; + +function GlobalDialog() { + const dialog = useGlobalDialogStore((state) => state.globalDialog); + const closeDialog = useGlobalDialogStore((state) => state.closeDialog); + const actionText = {dialog.actionText}; + const actionContent = + typeof dialog.action === "string" ? ( + + {actionText} + + ) : ( + actionText + ); + return ( + + + + {dialog.title} + {dialog.message} + + + + + + + + ); +} + +export default GlobalDialog; diff --git a/src/lib/store/use-global-dialog.ts b/src/lib/store/use-global-dialog.ts new file mode 100644 index 0000000..c8d6f76 --- /dev/null +++ b/src/lib/store/use-global-dialog.ts @@ -0,0 +1,37 @@ +import { create } from "zustand"; + +type GlobalDialogType = { + title: string; + message: string; + action: (() => void) | string; + actionText: string; +}; + +interface GlobalDialogStoreState { + globalDialog: { + open: boolean; + } & Partial; + openDialog: (dialog: GlobalDialogType) => void; + closeDialog: () => void; +} + +export const useGlobalDialogStore = create()((set) => ({ + globalDialog: { + open: false, + }, + openDialog(dialog: GlobalDialogType) { + set(() => ({ + globalDialog: { + ...dialog, + open: true, + }, + })); + }, + closeDialog() { + set(() => ({ + globalDialog: { + open: false, + }, + })); + }, +}));