hermes-web/components/elements/user-list-group.tsx

269 lines
9.5 KiB
TypeScript
Raw Permalink Normal View History

import axios from "axios";
import { useEffect, useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import { z } from "zod";
import { Trash2 } from "lucide-react";
import RoleGate from "@/components/auth/role-gate";
interface UsersGroup {
groupId: string
groupName: string
}
const ITEMS_PER_PAGE: number = 10;
2024-08-25 17:35:46 -04:00
const usernameSchema = z.string({
required_error: "Name is required.",
invalid_type_error: "Name must be a string"
}).regex(/^[\w\-]{4,25}$/, "Invalid Twitch username.")
const UserList = ({
groupId,
2024-08-25 17:35:46 -04:00
groupName
}: UsersGroup) => {
const [usersListOpen, setUsersListOpen] = useState(false)
const [users, setUsers] = useState<{ id: number, username: string }[]>([])
const [addedUsers, setAddedUsers] = useState<{ id: number, username: string }[]>([])
const [deletedUsers, setDeletedUsers] = useState<{ id: number, username: string }[]>([])
const [newUser, setNewUser] = useState<string>("")
const [knownUsers, setKnownUsers] = useState<{ id: number, username: string }[]>([])
const [error, setError] = useState<string | undefined>(undefined)
const [page, setPage] = useState<number>(0)
const [maxPages, setMaxPages] = useState<number>(1)
useEffect(() => {
axios.get('/api/settings/groups/chatters', {
params: {
groupId,
page
}
}).then(d => {
setUsers(d.data)
setKnownUsers(d.data)
2024-08-25 17:35:46 -04:00
var maxPages = Math.ceil(d.data.length / ITEMS_PER_PAGE)
setMaxPages(maxPages)
})
}, [groupId, page])
function close() {
setUsers([...users.filter(u => !addedUsers.find(a => a.id == u.id)), ...deletedUsers])
setUsersListOpen(false)
}
function AddUsername() {
setError(undefined)
2024-08-25 17:35:46 -04:00
const usernameValidation = usernameSchema.safeParse(newUser)
if (!usernameValidation.success) {
setError(JSON.parse(usernameValidation.error['message'])[0].message)
return
}
if (users.find(u => u.username == newUser.toLowerCase())) {
setError("Username is already in this group.")
return;
}
let user = knownUsers.find(u => u.username == newUser.toLowerCase())
if (!user) {
axios.get('/api/settings/groups/twitchchatters', {
params: {
logins: newUser
}
}).then(d => {
2024-08-25 17:35:46 -04:00
if (!d.data) {
setError("That was not a known Twitch username.")
return
2024-08-25 17:35:46 -04:00
}
user = d.data[0]
2024-08-25 17:35:46 -04:00
if (!user) {
setError("That was not a known Twitch username.")
return
2024-08-25 17:35:46 -04:00
}
if (deletedUsers.find(u => u.id == user!.id))
setDeletedUsers(deletedUsers.filter(u => u.id != user!.id))
else
setAddedUsers([...addedUsers, user])
setUsers([...users, user])
setKnownUsers([...users, user])
setNewUser("")
setMaxPages(Math.ceil((users.length + 1) / ITEMS_PER_PAGE))
}).catch(e => {
2024-08-25 17:35:46 -04:00
setError("That was not a known Twitch username.")
})
return
}
if (deletedUsers.find(u => u.id == user!.id))
setDeletedUsers(deletedUsers.filter(u => u.id != user!.id))
else
setAddedUsers([...addedUsers, user])
setUsers([...users, user])
setNewUser("")
setMaxPages(Math.ceil((users.length + 1) / ITEMS_PER_PAGE))
if (deletedUsers.find(u => u.id == user!.id)) {
setAddedUsers(addedUsers.filter(u => u.username != newUser.toLowerCase()))
}
}
function DeleteUser(user: { id: number, username: string }) {
if (addedUsers.find(u => u.id == user.id)) {
setAddedUsers(addedUsers.filter(u => u.id != user.id))
} else {
setDeletedUsers([...deletedUsers, user])
}
setUsers(users.filter(u => u.id != user.id))
}
function save() {
setError(undefined)
if (addedUsers.length > 0) {
axios.post("/api/settings/groups/chatters", {
groupId,
users: addedUsers
}).then(d => {
setAddedUsers([])
if (deletedUsers.length > 0)
axios.delete("/api/settings/groups/chatters", {
params: {
groupId,
ids: deletedUsers.map(i => i.id.toString()).reduce((a, b) => a + ',' + b)
}
}).then(d => {
setDeletedUsers([])
}).catch(() => {
setError("Something went wrong.")
})
}).catch(() => {
setError("Something went wrong.")
})
return
}
if (deletedUsers.length > 0)
axios.delete("/api/settings/groups/chatters", {
params: {
groupId,
ids: deletedUsers.map(i => i.id.toString()).reduce((a, b) => a + ',' + b)
}
}).then(d => {
setDeletedUsers([])
}).catch(() => {
setError("Something went wrong.")
})
}
return (
<Sheet>
<SheetTrigger asChild>
<Button
className="ml-3 mr-3 align-middle"
onClick={() => setUsersListOpen(true)}>
Users
</Button>
</SheetTrigger>
<SheetContent className="w-[700px]">
<SheetHeader>
<SheetTitle>Edit group - {groupName}</SheetTitle>
<SheetDescription>
Make changes to this group&#39;s list of users.
</SheetDescription>
</SheetHeader>
{!!error &&
<p className="text-red-500">{error}</p>
}
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Username
</Label>
<Input
id="name"
value={newUser}
type="text"
onChange={e => setNewUser(e.target.value)}
className="col-span-3" />
<Button
className="bg-white"
onClick={() => AddUsername()}>
Add
</Button>
</div>
</div>
<hr className="mt-4" />
<Table>
<TableHeader>
<TableRow>
<RoleGate roles={['ADMIN']}><TableHead>Id</TableHead></RoleGate>
<TableHead>Username</TableHead>
<TableHead>Delete</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.length ? (
users.slice(ITEMS_PER_PAGE * page, ITEMS_PER_PAGE * (page + 1)).map((user) => (
<TableRow
key={user.id}>
<RoleGate roles={['ADMIN']}><TableCell colSpan={1} className="text-xs">{user.id}</TableCell></RoleGate>
<TableCell colSpan={1}>{user.username}</TableCell>
<TableCell>
<Button
className="bg-red-500 h-9"
onClick={() => DeleteUser(user)}>
<Trash2 />
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={3}
className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<SheetFooter>
<SheetClose asChild>
<Button onClick={() => save()} type="submit">Save changes</Button>
</SheetClose>
<SheetClose asChild>
<Button onClick={() => close()} type="submit">Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
export default UserList;