Skip to content

Commit

Permalink
fix: users search, attestation enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
shadrach-tayo committed Oct 19, 2024
1 parent 209eac5 commit 5aa83ba
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 219 deletions.
79 changes: 51 additions & 28 deletions src/components/molecules/CommunityAttestations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import {
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Check, X, Award, Plus, Settings, Lock, Pen } from "lucide-react";
import { cn } from "@/lib/utils";
import {
Expand All @@ -29,20 +37,21 @@ import {
removeEntryAttestation,
} from "@/lib/api";
import Link from "next/link";
import { buttonVariants } from "../custom/LoaderButton";
import { buttonVariants, LoaderButton } from "../custom/LoaderButton";
import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { DotsHorizontalIcon } from "@radix-ui/react-icons";
import { useRouter } from "next/navigation";

export default function CommunityAttestations({
community,
}: {
community: Community;
}) {
const router = useRouter();
const [attestationOpen, setAttestationOpen] = useState(false);

const {
data: attestations,
isLoading,
isError,
refetch: refetchCommunityAttestations,
} = useQuery(attestationQueryOptions(community.id));
const { data: allAttestations, refetch: refetchAttestations } =
Expand Down Expand Up @@ -90,10 +99,10 @@ export default function CommunityAttestations({
};

const isPending = addEntryMutation.isPending || removeEntryMutation.isPending;
const hasError = addEntryMutation.isError || removeEntryMutation.isError;
const error = addEntryMutation.error || removeEntryMutation.error;
// const hasError = addEntryMutation.isError || removeEntryMutation.isError;
// const error = addEntryMutation.error || removeEntryMutation.error;

console.log("states", { isPending, hasError, error });
// console.log("states", { isPending, hasError, error });

return (
<Card className="mb-6">
Expand Down Expand Up @@ -185,28 +194,42 @@ export default function CommunityAttestations({
</div>
</div>
<div className="flex items-center justify-end">

{attestation.isRequired && (
<Button
variant="ghost"
size="icon"
onClick={() =>
toggleEntryAttestation(attestation.attestationId)
}
>
<X className="h-4 w-4 text-error" />
<span className="sr-only">Remove attestation</span>
</Button>
)}
{!attestation.isExternal && (
<Link
className={cn(buttonVariants({ variant: "ghost" }))}
href={`${attestation.communityId}/attestations/${attestation.attestationId}/edit`}
>
<Pen className="h-4 w-4" />
<span className="sr-only">Edit attestation</span>
</Link>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<LoaderButton
variant="ghost"
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
>
<DotsHorizontalIcon className="h-4 w-4" />
<span className="sr-only">Open menu</span>
</LoaderButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[160px]">
<DropdownMenuItem
onClick={() =>
router.push(
`${attestation.communityId}/attestations/${attestation.attestationId}/edit`
)
}
>
<span>Edit</span>
<span className="sr-only">Edit</span>
</DropdownMenuItem>
{attestation.isRequired && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() =>
toggleEntryAttestation(attestation.attestationId)
}
>
Delete
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
))}
Expand Down
198 changes: 198 additions & 0 deletions src/components/molecules/CommunityMembers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"use client";

/* eslint-disable @next/next/no-img-element */
import { useEffect, useState } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Check, ChevronsUpDown, X, Settings } from "lucide-react";
import {
addMember,
Community,
removeMember,
searchUsers,
} from "@/lib/api";
import { useMutation, useQuery } from "@tanstack/react-query";
import { getQueryClient } from "@/lib/get-query-client";
import { tags } from "@/lib/tags";

type User = {
id: number;
name: string;
};

export default function CommunityMembers({
community,
}: {
community: Community;
}) {
const queryClient = getQueryClient();

const members = community.CommunityMember;
const [search, setSearch] = useState<string>("");
const [open, setOpen] = useState(false);
const [users, setUsers] = useState<User[]>([])

const addMemberMutation = useMutation(
{
mutationFn: addMember,
},
queryClient
);
const removeMemberMutation = useMutation(
{
mutationFn: removeMember,
},
queryClient
);

const toggleMember = async (user: User) => {
const existingMember = members.find((m) => m.userId === user.id);
if (existingMember) {
removeMemberMutation.mutate(
{
communityId: community.id,
memberId: existingMember.id,
},
{
onSuccess() {
queryClient.invalidateQueries({ queryKey: [tags.communities] });
removeMemberMutation.reset();
},
}
);
} else {
addMemberMutation.mutate(
{ communityId: community.id, userId: user.id, role: "MEMBER" },
{
onSuccess() {
queryClient.invalidateQueries({ queryKey: [tags.communities] });
},
}
);
}
};

const { mutate } = useMutation(
{
mutationKey: [tags.users],
mutationFn: searchUsers,
},
queryClient
);

useEffect(() => {
mutate({ name: search.trim()}, { onSuccess(data, variables, context) {
console.log('Results', { data, variables, context})
if (data?.length > 0) setUsers(data ?? [])
},})
}, [search, mutate])

// const isPending =
// addMemberMutation.isPending || removeMemberMutation.isPending;

console.log('Users', users)
return (
<Card className="mb-6">
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>Members</CardTitle>
<CardDescription>
{community.CommunityMember?.length ?? "No"} members
</CardDescription>
</div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<Settings className="h-4 w-4 mr-2" />
Manage
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Manage Members</DialogTitle>
</DialogHeader>
<Command className="rounded-lg border shadow-md">
<CommandInput
placeholder="Search users..."
onValueChange={(search) => setSearch(search)}
/>
<CommandList>
<CommandEmpty>No users found.</CommandEmpty>
<CommandGroup>
{users?.map((user) => (
<CommandItem
key={user.id}
onSelect={() => toggleMember(user)}
className="flex items-center justify-between"
>
<span>{user.name}</span>
{members.some((m) => m.userId === user.id) && (
<Check className="h-4 w-4 text-green-600" />
)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</DialogContent>
</Dialog>
</CardHeader>
<CardContent>
<ScrollArea className="h-[300px]">
{members.map((member) => (
<div
key={member.id}
className="flex items-center justify-between gap-4 mb-4"
>
<div className="flex items-center gap-4">
<Avatar>
<AvatarFallback>{member.user.name[0]}</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{member.user.name}</p>
<p className="text-sm text-muted-foreground">{member.role}</p>
</div>
</div>
<Button
variant="ghost"
size="icon"
onClick={() =>
toggleMember({
id: member.userId,
name: member.user.name,
})
}
>
<X className="h-4 w-4" />
<span className="sr-only">Remove member</span>
</Button>
</div>
))}
</ScrollArea>
</CardContent>
</Card>
);
}
Loading

0 comments on commit 5aa83ba

Please sign in to comment.