📘

【Convex】NextJs14 と Convex【#27 Convex Rename Card Modal】

2024/05/27に公開

【#27 Convex Rename Card Modal】

YouTube: https://youtu.be/JxmCBG1Z1e4
https://youtu.be/JxmCBG1Z1e4

今回から何回かに分けて
カードタイトルをリネームするモーダルを実装します。

今回はモーダルを開くところまでの実装となります。

store/use-rename-card-modal.ts
import { create } from "zustand";

const defaultValues = { id: "", title: "" };

interface RenameCardModalInput {
  isOpen: boolean;
  initialValues: typeof defaultValues;
  onOpen: (id: string, title: string) => void;
  onClose: () => void;
}

export const useRenameCardModal = create<RenameCardModalInput>((set) => ({
  isOpen: false,
  onOpen: (id, title) => set({ isOpen: true, initialValues: { id, title } }),
  onClose: () => set({ isOpen: false, initialValues: defaultValues }),
  initialValues: defaultValues,
}));
components/modals/rename-card-modal.tsx
"use client";

import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogDescription,
  DialogClose,
  DialogFooter,
  DialogTitle,
} from "@/components/ui/dialog";

import { useRenameCardModal } from "@/store/use-rename-card-modal";

export const RenameCardModal = () => {
  const { isOpen, onClose, initialValues } = useRenameCardModal();
  return (
    <Dialog open={isOpen} onOpenChange={onClose}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Rename card title</DialogTitle>
          <DialogDescription>Enter a new card title.</DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  );
};
components/providers/modal-provider.tsx
"use client";

import { useEffect, useState } from "react";
import { CreateCardModal } from "@/components/modals/create-card-modal";
import { RenameCardModal } from "../modals/rename-card-modal";

export const ModalProvider = () => {
  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  if (!isMounted) {
    return null;
  }

  return (
    <>
      <RenameCardModal />
      <CreateCardModal />
    </>
  );
};
app/(main)/_components/card/card-actions.tsx
"use client";

import { Pencil, Trash2 } from "lucide-react";

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useRenameCardModal } from "@/store/use-rename-card-modal";

interface Props {
  children: React.ReactNode;
  id: string;
  title: string;
}

export const CardActions = ({ children, id, title }: Props) => {
  const { onOpen } = useRenameCardModal();
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem
          className="p-3 cursor-pointer"
          onClick={() => onOpen(id, title)}
        >
          <Pencil className="h-4 w-4 mr-2" /> Rename
        </DropdownMenuItem>
        <DropdownMenuItem className="p-3 cursor-pointer">
          <Trash2 className="h-4 w-4 mr-2" /> Delete
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
};
app/(main)/_components/card/index.tsx
"use client";

import Image from "next/image";
import { useAuth } from "@clerk/nextjs";
import { formatDistanceToNow } from "date-fns";

import { Footer } from "./footer";
import { Overlay } from "./overlay";
import { CardActions } from "./card-actions";
import { MoreHorizontal } from "lucide-react";

interface Props {
  id: string;
  title: string;
  imageUrl: string;
  authorId: string;
  authorName: string;
  createdAt: number;
  orgId: string;
  cardType: string;
}

export const Card = ({
  id,
  title,
  imageUrl,
  authorId,
  authorName,
  createdAt,
  orgId,
  cardType,
}: Props) => {
  const { userId } = useAuth();
  const authorLabel = userId === authorId ? "You" : authorName;
  const createdAtLabel = formatDistanceToNow(createdAt, { addSuffix: true });

  return (
    <div className="group aspect-[100/128] border rounded-lg flex flex-col justify-between overflow-hidden">
      <div className="relative flex-1 bg-slate-300">
        <Image src={imageUrl} alt={title} fill className="object-cover" />
        <Overlay />
        <CardActions id={id} title={title}>
          <button className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity px-3 py-2 outline-none">
            <MoreHorizontal className="text-white opacity-75 hover:opacity-100 transition-opacity" />
          </button>
        </CardActions>
      </div>
      <Footer
        title={title}
        authorLabel={authorLabel}
        type={cardType}
        createdAtLabel={createdAtLabel}
      />
    </div>
  );
};

Discussion