155 lines
4.9 KiB
TypeScript
155 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
|
import { useState } from "react";
|
|
import { toast } from "sonner";
|
|
import type { GroupRole } from "@album/contracts";
|
|
import { api } from "@/trpc/react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
|
|
export function GroupPeople({
|
|
groupId,
|
|
canManage,
|
|
}: {
|
|
groupId: string;
|
|
canManage: boolean;
|
|
}) {
|
|
const utils = api.useUtils();
|
|
const members = api.group.members.useQuery({ groupId });
|
|
const [email, setEmail] = useState("");
|
|
const [role, setRole] = useState<GroupRole>("member");
|
|
const [code, setCode] = useState<string | null>(null);
|
|
const setMember = api.group.setMember.useMutation({
|
|
onSuccess: async () => {
|
|
toast.success("Member updated");
|
|
setEmail("");
|
|
await utils.group.members.invalidate({ groupId });
|
|
},
|
|
onError: (error) => toast.error(error.message),
|
|
});
|
|
const invite = api.group.inviteEmail.useMutation({
|
|
onSuccess: () => toast.success("Invite emailed"),
|
|
onError: (error) => toast.error(error.message),
|
|
});
|
|
const createCode = api.group.createCode.useMutation({
|
|
onSuccess: (result) => {
|
|
setCode(result.code);
|
|
toast.success("Invite code created");
|
|
},
|
|
onError: (error) => toast.error(error.message),
|
|
});
|
|
const remove = api.group.removeMember.useMutation({
|
|
onSuccess: async () => {
|
|
await utils.group.members.invalidate({ groupId });
|
|
},
|
|
onError: (error) => toast.error(error.message),
|
|
});
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Group members</CardTitle>
|
|
<CardDescription>
|
|
People who can be added to events in this group.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-4">
|
|
<ul className="flex flex-col gap-2">
|
|
{(members.data ?? []).map((member) => (
|
|
<li key={member.id} className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<p className="font-medium">{member.name}</p>
|
|
<p className="text-xs text-muted-foreground">{member.email}</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="secondary">{member.role}</Badge>
|
|
{canManage ? (
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() =>
|
|
remove.mutate({ groupId, userId: member.userId })
|
|
}
|
|
>
|
|
Remove
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
{canManage ? (
|
|
<>
|
|
<form
|
|
className="flex flex-wrap items-end gap-2"
|
|
onSubmit={(formEvent) => {
|
|
formEvent.preventDefault();
|
|
setMember.mutate({ groupId, email, role });
|
|
}}
|
|
>
|
|
<Input
|
|
type="email"
|
|
required
|
|
placeholder="email@example.com"
|
|
value={email}
|
|
onChange={(event) => setEmail(event.target.value)}
|
|
className="max-w-xs"
|
|
/>
|
|
<Select value={role} onValueChange={(value) => setRole(value as GroupRole)}>
|
|
<SelectTrigger aria-label="Group role"><SelectValue /></SelectTrigger>
|
|
<SelectContent><SelectGroup><SelectItem value="member">Member</SelectItem><SelectItem value="owner">Owner</SelectItem></SelectGroup></SelectContent>
|
|
</Select>
|
|
<Button type="submit" disabled={setMember.isPending}>
|
|
Add existing user
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
disabled={invite.isPending}
|
|
onClick={() =>
|
|
invite.mutate({
|
|
email,
|
|
groupId,
|
|
groupRole: role,
|
|
})
|
|
}
|
|
>
|
|
Email invite
|
|
</Button>
|
|
</form>
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
onClick={() =>
|
|
createCode.mutate({
|
|
groupId,
|
|
reusable: true,
|
|
maxUses: 25,
|
|
groupRole: "member",
|
|
})
|
|
}
|
|
>
|
|
Create reusable code
|
|
</Button>
|
|
{code ? (
|
|
<p className="text-sm">
|
|
Share this code once: <code>{code}</code>
|
|
</p>
|
|
) : null}
|
|
</>
|
|
) : null}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|