2024-08-14 16:33:40 -04:00
|
|
|
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.")
|
|
|
|
|
2024-08-14 16:33:40 -04:00
|
|
|
const UserList = ({
|
|
|
|
groupId,
|
2024-08-25 17:35:46 -04:00
|
|
|
groupName
|
2024-08-14 16:33:40 -04:00
|
|
|
}: 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)
|
2024-08-14 16:33:40 -04:00
|
|
|
})
|
|
|
|
}, [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)
|
2024-08-14 16:33:40 -04:00
|
|
|
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.")
|
2024-08-14 16:33:40 -04:00
|
|
|
return
|
2024-08-25 17:35:46 -04:00
|
|
|
}
|
2024-08-14 16:33:40 -04:00
|
|
|
|
|
|
|
user = d.data[0]
|
2024-08-25 17:35:46 -04:00
|
|
|
if (!user) {
|
|
|
|
setError("That was not a known Twitch username.")
|
2024-08-14 16:33:40 -04:00
|
|
|
return
|
2024-08-25 17:35:46 -04:00
|
|
|
}
|
2024-08-14 16:33:40 -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.")
|
2024-08-14 16:33:40 -04:00
|
|
|
})
|
|
|
|
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'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;
|