added zustand and global dialog component
This commit is contained in:
parent
b2cd0fc560
commit
0e614f544a
@ -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",
|
||||
|
||||
27
pnpm-lock.yaml
generated
27
pnpm-lock.yaml
generated
@ -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: {}
|
||||
|
||||
@ -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 (
|
||||
<HydrateClient>
|
||||
<SessionProvider session={session}>
|
||||
<GlobalDialog />
|
||||
<SidebarProvider>
|
||||
<AppSidebar user={session?.user} />
|
||||
<div className="h-screen w-full bg-background">
|
||||
|
||||
@ -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<CommentNode | undefined>();
|
||||
const commentRefs = useRef<Map<string, HTMLDivElement | null>>(new Map());
|
||||
const { socket } = useSocket();
|
||||
@ -28,12 +31,25 @@ function CommentSection({ articleId }: { articleId: string }) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="sticky top-0 z-50 bg-background pt-4">
|
||||
<CommentForm
|
||||
articleId={articleId}
|
||||
parentComment={replyComment}
|
||||
removeReplyComment={() => setReplyComment(undefined)}
|
||||
commentRefs={commentRefs}
|
||||
/>
|
||||
{session ? (
|
||||
<CommentForm
|
||||
articleId={articleId}
|
||||
parentComment={replyComment}
|
||||
removeReplyComment={() => setReplyComment(undefined)}
|
||||
commentRefs={commentRefs}
|
||||
/>
|
||||
) : (
|
||||
<Alert className="flex items-center">
|
||||
<LogIn className="size-4" />
|
||||
<AlertTitle>
|
||||
Melde dich an um einen Kommentar zu schreiben
|
||||
</AlertTitle>
|
||||
|
||||
<Button size={"sm"} className="ml-auto">
|
||||
Jetzt Anmelden
|
||||
</Button>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
|
||||
@ -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 (
|
||||
<div
|
||||
className={cn(
|
||||
@ -62,11 +73,7 @@ const Comment = React.memo(
|
||||
<div className="flex w-full items-center gap-2 pb-6">
|
||||
<CommentVoteButton vote commentId={comment.id} />
|
||||
<CommentVoteButton vote={false} commentId={comment.id} />
|
||||
<Button
|
||||
size={"sm"}
|
||||
variant={"ghost"}
|
||||
onClick={() => setReplyComment(comment)}
|
||||
>
|
||||
<Button size={"sm"} variant={"ghost"} onClick={handleReplyClick}>
|
||||
Antworten
|
||||
</Button>
|
||||
<div className="ml-auto">
|
||||
|
||||
50
src/components/global-dialog.tsx
Normal file
50
src/components/global-dialog.tsx
Normal file
@ -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 = <span>{dialog.actionText}</span>;
|
||||
const actionContent =
|
||||
typeof dialog.action === "string" ? (
|
||||
<Link href={dialog.action!} className="link link-primary">
|
||||
{actionText}
|
||||
</Link>
|
||||
) : (
|
||||
actionText
|
||||
);
|
||||
return (
|
||||
<Dialog open={dialog.open} onOpenChange={closeDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{dialog.title}</DialogTitle>
|
||||
<DialogDescription>{dialog.message}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant={"outline"} onClick={closeDialog}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
className="btn btn-primary"
|
||||
asChild={typeof dialog.action === "string"}
|
||||
>
|
||||
{actionContent}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default GlobalDialog;
|
||||
37
src/lib/store/use-global-dialog.ts
Normal file
37
src/lib/store/use-global-dialog.ts
Normal file
@ -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<GlobalDialogType>;
|
||||
openDialog: (dialog: GlobalDialogType) => void;
|
||||
closeDialog: () => void;
|
||||
}
|
||||
|
||||
export const useGlobalDialogStore = create<GlobalDialogStoreState>()((set) => ({
|
||||
globalDialog: {
|
||||
open: false,
|
||||
},
|
||||
openDialog(dialog: GlobalDialogType) {
|
||||
set(() => ({
|
||||
globalDialog: {
|
||||
...dialog,
|
||||
open: true,
|
||||
},
|
||||
}));
|
||||
},
|
||||
closeDialog() {
|
||||
set(() => ({
|
||||
globalDialog: {
|
||||
open: false,
|
||||
},
|
||||
}));
|
||||
},
|
||||
}));
|
||||
Loading…
x
Reference in New Issue
Block a user