🐕

【NextJs14】NextJs14 と 便利なライブラリ【#38Clerk Organization List】

2024/03/03に公開

【#38Clerk Organization List】

YouTube: https://youtu.be/vvsuLXygTHA

https://youtu.be/vvsuLXygTHA

今回はサイドバーを作成して、Organizationの一覧を表示します。

/app/(main)/account/page.tsx
import { auth } from "@clerk/nextjs";
import { formatDistanceToNow } from "date-fns";

import { getTodosByOrgId } from "@/service/todo-get";

import { Separator } from "@/components/ui/separator";
import { TodoForm } from "./_components/todo-form";
import { TodoActions } from "./_components/todo-actions";
import { OrgNavbar } from "./_components/org-navbar";
import { Sidebar } from "./_components/sidebar";

const AccountPage = async () => {
  const { orgId } = auth();
  const todos = await getTodosByOrgId(orgId);

  return (
    <div className="p-6 w-full h-full flex gap-x-3">
      <Sidebar />
      <div className="w-full h-full flex flex-col gap-y-3">
        <OrgNavbar />
        <div className="flex flex-col md:flex-row items-center justify-between gap-2">
          <h2 className="text-center md:text-left text-3xl font-bold">Todos</h2>
          <TodoForm />
        </div>
        <Separator />
        <ul className="w-full space-y-3">
          {todos.map((todo, idx) => (
            <li
              key={todo.id}
              className="flex flex-col md:flex-row items-center justify-between gap-x-4 gap-y-2"
            >
              <div className="flex flex-col md:flex-row flex-1 items-center justify-between gap-x-3">
                <p className="text-xl font-semibold">
                  <span className="pr-2">{idx + 1}</span>
                  <span className={todo.isCompleted ? "line-through" : ""}>
                    {todo.title}
                  </span>
                </p>
                <p>
                  {formatDistanceToNow(todo.createdAt, { addSuffix: true })}
                </p>
              </div>
              <TodoActions todoId={todo.id} isCompleted={todo.isCompleted} />
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default AccountPage;
/app/(main)/account/_components/sidebar/index.tsx
import { List } from "./list";

export const Sidebar = () => {
  return (
    <div className="h-full w-[60px] flex flex-col px-3 py-1 gap-y-4 text-white border-r">
      <List />
    </div>
  );
};
/app/(main)/account/_components/sidebar/list.tsx
"use client";

import { useOrganizationList } from "@clerk/nextjs";

import { Item } from "./item";

export const List = () => {
  const { userMemberships } = useOrganizationList({
    userMemberships: {
      infinite: true,
    },
  });

  if (!userMemberships) return null;

  return (
    <ul className="space-y-4">
      {userMemberships.data?.map((org) => (
        <Item
          key={org.organization.id}
          id={org.organization.id}
          name={org.organization.name}
          imageUrl={org.organization.imageUrl}
        />
      ))}
    </ul>
  );
};

こちらの「li」タグですが動画では「div」を設定していますので、
次回の動画で修正します。

/app/(main)/account/_components/sidebar/item.tsx
import Image from "next/image";
import { useOrganization, useOrganizationList } from "@clerk/nextjs";

import { cn } from "@/lib/utils";

import { OwnTooltipWrapper } from "@/components/own-tooltip-wrapper";

interface ItemProps {
  id: string;
  name: string;
  imageUrl: string;
}

export const Item = ({ id, name, imageUrl }: ItemProps) => {
  const { organization } = useOrganization();
  const { setActive } = useOrganizationList();

  const isActive = organization?.id === id;

  const onClick = () => {
    if (!setActive) return;

    setActive({ organization: id });
  };

  return (
    <li className="aspect-square relative">
      <OwnTooltipWrapper label={name} side="right" sideOffset={10}>
        <Image
          src={imageUrl}
          alt={name}
          fill
          onClick={onClick}
          className={cn(
            "rounded-md cursor-pointer opacity-75 hover:opacity-100 transition",
            isActive && "opacity-100"
          )}
        />
      </OwnTooltipWrapper>
    </li>
  );
};

Discussion