Added basic validation for requests

This commit is contained in:
Tom
2024-08-25 21:35:46 +00:00
parent 2d40d6fe09
commit 624b3fa63b
42 changed files with 608 additions and 492 deletions

View File

@ -1,10 +1,10 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "../ui/button";
import { Button } from "@/components/ui/button";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Label } from "../ui/label";
import { Label } from "@/components/ui/label";
import axios from "axios";
export interface ConnectionDefault {
@ -21,7 +21,10 @@ export const ConnectionDefaultElement = ({
const OnDefaultConnectionUpdate = function (con: { name: string, clientId: string, token: string, type: string, scope: string, expiresAt: Date }) {
if (connection && con.name == connection.name)
return;
return
if (connection && !connections.some(c => c.clientId == connection.clientId && c.name == connection.name && c.token == connection.token))
return
axios.put('/api/connection/default', { name: con.name, type: con.type })
.then(d => {
@ -33,7 +36,6 @@ export const ConnectionDefaultElement = ({
const con = connections.filter((c: any) => c.type == type && c.default)
if (con.length > 0)
OnDefaultConnectionUpdate(con[0])
console.log('default', type, connections.filter(c => c.type == type).length > 0)
}, [])
return (

View File

@ -2,12 +2,12 @@
import axios from "axios";
import { useState } from "react";
import { Button } from "../ui/button";
import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { v4 } from "uuid";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Input } from "../ui/input";
import { Input } from "@/components/ui/input";
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
import { env } from "process";

View File

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { FaExclamationTriangle } from "react-icons/fa";
import { Button } from "../ui/button";
import { Button } from "@/components/ui/button";
import { X } from "lucide-react";

View File

@ -1,32 +1,37 @@
import axios from "axios";
import { useEffect, useState } from "react";
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Label } from "../ui/label";
import { HelpCircleIcon, Trash2Icon } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip"
import { Checkbox } from "../ui/checkbox";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { z } from "zod";
import { Trash2Icon } from "lucide-react";
interface Permission {
id: string|undefined
id: string | undefined
path: string
allow: boolean|null
allow: boolean | null
groupId: string
edit: boolean
showEdit: boolean
isNew: boolean
permissionPaths: { path: string, description: string }[]
adder: (id: string, path: string, allow: boolean|null) => void
remover: (redemption: { id: string, path: string, allow: boolean|null }) => void
adder: (id: string, path: string, allow: boolean | null) => void
remover: (redemption: { id: string, path: string, allow: boolean | null }) => void,
contains: (path: string) => boolean
}
const permissionPathSchema = z.string({
required_error: "Permission path should be available.",
invalid_type_error: "Permission path must be a string"
}).regex(/^[\w\-\.]{1,64}$/, "Permission path must contain only letters, numbers, dashes, periods.")
const permissionAllowSchema = z.boolean({
required_error: "Permission should be allowed, revoked or inherited from parent.",
invalid_type_error: "Something went wrong."
}).nullable()
const GroupPermission = ({
id,
path,
@ -37,18 +42,43 @@ const GroupPermission = ({
isNew,
permissionPaths,
adder,
remover
remover,
contains
}: Permission) => {
const [pathOpen, setPathOpen] = useState(false)
const [isEditable, setIsEditable] = useState(edit)
const [oldData, setOldData] = useState<{ path: string, allow: boolean|null } | undefined>(undefined)
const [permission, setPermission] = useState<{ id: string|undefined, path: string, allow: boolean|null }>({ id, path, allow });
const [oldData, setOldData] = useState<{ path: string, allow: boolean | null } | undefined>(undefined)
const [permission, setPermission] = useState<{ id: string | undefined, path: string, allow: boolean | null }>({ id, path, allow });
const [error, setError] = useState<string | undefined>(undefined)
function Save() {
setError(undefined)
if (!permission || !permission.path)
return
if (!permissionPaths.some(p => p.path == permission.path)) {
setError('Permission does not exist.')
return
}
const permissionPathValidation = permissionPathSchema.safeParse(permission.path)
if (!permissionPathValidation.success) {
setError(JSON.parse(permissionPathValidation.error['message'])[0].message)
return
}
const permissionAllowValidation = permissionAllowSchema.safeParse(permission.allow)
if (!permissionAllowValidation.success) {
setError(JSON.parse(permissionAllowValidation.error['message'])[0].message)
return
}
if (isNew) {
if (contains(permission.path)) {
setError("Permission already exists.")
return;
}
axios.post("/api/settings/groups/permissions", {
path: permission.path,
allow: permission.allow,
@ -61,6 +91,11 @@ const GroupPermission = ({
setPermission({ id: undefined, path: "", allow: true })
})
} else {
if (!contains(permission.path)) {
setError("Permission does not exists.")
return;
}
axios.put("/api/settings/groups/permissions", {
id: permission.id,
path: permission.path,
@ -74,7 +109,8 @@ const GroupPermission = ({
function Cancel() {
if (!oldData)
return
setError(undefined)
setPermission({ ...oldData, id: permission.id })
setIsEditable(false)
setOldData(undefined)
@ -124,7 +160,7 @@ const GroupPermission = ({
value={p.path}
key={p.path}
onSelect={(value) => {
setPermission({ ...permission, path: permissionPaths.find(v => v.path.toLowerCase() == value.toLowerCase())?.path ?? value.toLowerCase()})
setPermission({ ...permission, path: permissionPaths.find(v => v.path.toLowerCase() == value.toLowerCase())?.path ?? value.toLowerCase() })
setPathOpen(false)
}}>
<div>
@ -135,7 +171,7 @@ const GroupPermission = ({
{p.description}
</div>
</div>
</CommandItem>
))}
</CommandGroup>
@ -171,6 +207,11 @@ const GroupPermission = ({
}}
disabled={!isEditable || permission === undefined} />
</div>
{error &&
<p className="w-[380px] text-red-600 text-wrap text-sm">
{error}
</p>
}
<div>
{isEditable &&
<Button

View File

@ -2,12 +2,18 @@ import axios from "axios";
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "../ui/label";
import { Label } from "@/components/ui/label";
import { Maximize2, Minimize2, Trash2Icon } from "lucide-react";
import GroupPermission from "./group-permission";
import { z } from "zod";
import UserList from "./user-list-group";
const groupNameSchema = z.string({
required_error: "Group name is required.",
invalid_type_error: "Group name must be a string"
}).regex(/^[\w\-\s]{1,20}$/, "Group name must contain only letters, numbers, spaces, dashes, and underscores.")
const prioritySchema = z.string().regex(/^-?\d{1,5}$/, "Priority must be a valid number.")
interface Group {
id: string | undefined
name: string
@ -40,11 +46,11 @@ const GroupElement = ({
const [isEditable, setIsEditable] = useState(edit)
const [isNew, setIsNew] = useState(isNewGroup)
const [isMinimized, setIsMinimized] = useState(true)
const [oldData, setOldData] = useState<{ id: string|undefined, name: string, priority: number } | undefined>(undefined)
const [group, setGroup] = useState<{ id: string|undefined, name: string, priority: number }>({ id, name, priority })
const [oldData, setOldData] = useState<{ id: string | undefined, name: string, priority: number } | undefined>(undefined)
const [group, setGroup] = useState<{ id: string | undefined, name: string, priority: number }>({ id, name, priority })
const [permissions, setPermissions] = useState<{ id: string, path: string, allow: boolean | null }[]>(permissionsLoaded);
const isSpecial = (isEditable || oldData === undefined) && !!group && specialGroups.includes(group?.name)
const [error, setError] = useState<string|undefined>(undefined)
const [error, setError] = useState<string | undefined>(undefined)
function addPermission(id: string, path: string, allow: boolean | null) {
@ -55,25 +61,25 @@ const GroupElement = ({
setPermissions(permissions.filter(p => p.id != permission.id))
}
const nameSchema = z.string({
required_error: "Name is required.",
invalid_type_error: "Name must be a string"
}).regex(/^[\w\-\s]{1,20}$/, "Name must contain only letters, numbers, dashes, and underscores.")
const prioritySchema = z.string().regex(/^-?\d{1,5}$/, "Priority must be a valid number.")
function containsPermission(path: string) {
return permissions.some(p => p.path == path)
}
function Save() {
setError(undefined)
if (!isNew && !id)
if (!isNew && !id) {
setError("Something went wrong.")
return
}
if (!group.name) {
setError("Set a value for the group name")
return
}
const nameValidation = nameSchema.safeParse(group.name)
if (!nameValidation.success) {
setError(JSON.parse(nameValidation.error['message'])[0].message)
const groupNameValidation = groupNameSchema.safeParse(group.name)
if (!groupNameValidation.success) {
setError(JSON.parse(groupNameValidation.error['message'])[0].message)
return
}
@ -83,12 +89,12 @@ const GroupElement = ({
return
}
if (contains(group.name)) {
setError("Group already exist. Use another name.")
return;
}
if (isNew || group.id?.startsWith('$')) {
if (contains(group.name) && !isSpecial) {
setError("Group already exists. Use another name.")
return;
}
axios.post("/api/settings/groups", {
name: group.name,
priority: group.priority
@ -97,7 +103,6 @@ const GroupElement = ({
setError("Something went wrong.")
return
}
console.log("DATA", d.data)
if (specialGroups.includes(group.name)) {
setIsNew(false)
@ -111,6 +116,11 @@ const GroupElement = ({
setError("Something went wrong.")
})
} else {
if (!contains(group.name)) {
setError("Group does not exist. Something went wrong.")
return;
}
axios.put("/api/settings/groups", {
id: group.id,
name: group.name,
@ -124,10 +134,10 @@ const GroupElement = ({
}
function Cancel() {
setError(undefined)
if (!oldData)
return
setError(undefined)
setGroup({ ...oldData, id: group.id })
setIsEditable(false)
setOldData(undefined)
@ -196,12 +206,10 @@ const GroupElement = ({
onChange={e => setGroup(d => {
let temp = { ...group }
const v = parseInt(e.target.value)
if (e.target.value.length == 0) {
if (e.target.value.length == 0 || Number.isNaN(v)) {
temp.priority = 0
} else if (!Number.isNaN(v) && Number.isSafeInteger(v)) {
temp.priority = v
} else if (Number.isNaN(v)) {
temp.priority = 0
}
return temp
})}
@ -273,7 +281,8 @@ const GroupElement = ({
isNew={false}
permissionPaths={permissionPaths}
adder={addPermission}
remover={removePermission} />
remover={removePermission}
contains={containsPermission} />
</div>
)}
<div
@ -288,7 +297,8 @@ const GroupElement = ({
isNew={true}
permissionPaths={permissionPaths}
adder={addPermission}
remover={removePermission} />
remover={removePermission}
contains={containsPermission} />
</div>
</div>
}

View File

@ -1,6 +1,6 @@
import { Info, X } from "lucide-react";
import React, { useState } from "react";
import { Button } from "../ui/button";
import { Button } from "@/components/ui/button";
interface NoticeProps {

View File

@ -4,10 +4,10 @@ import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Label } from "../ui/label";
import { Label } from "@/components/ui/label";
import { Maximize2, Minimize2, Trash2Icon } from "lucide-react";
import { ActionType } from "@prisma/client";
import { boolean } from "zod";
import { boolean, z } from "zod";
const actionTypes = [
@ -19,13 +19,15 @@ const actionTypes = [
"type": "text",
"label": "File path",
"key": "file_path",
"placeholder": "Enter local file path, relative or full."
"placeholder": "Enter local file path, relative or full.",
"required": true
},
{
"type": "text",
"label": "File content",
"key": "file_content",
"placeholder": "Enter the text to write to the file."
"placeholder": "Enter the text to write to the file.",
"required": true
}
]
},
@ -37,13 +39,15 @@ const actionTypes = [
"type": "text",
"label": "File path",
"key": "file_path",
"placeholder": "Enter local file path, relative or full."
"placeholder": "Enter local file path, relative or full.",
"required": true
},
{
"type": "text",
"label": "File content",
"key": "file_content",
"placeholder": "Enter the text to append to the file."
"placeholder": "Enter the text to append to the file.",
"required": true
}
]
},
@ -60,7 +64,8 @@ const actionTypes = [
"type": "text",
"label": "File path",
"key": "file_path",
"placeholder": "Enter local file path, relative or full."
"placeholder": "Enter local file path, relative or full.",
"required": true
}
]
},
@ -78,6 +83,7 @@ const actionTypes = [
"label": "TTS Voice",
"key": "tts_voice",
"placeholder": "Name of an enabled TTS voice",
"required": true
}
]
},
@ -89,13 +95,15 @@ const actionTypes = [
"type": "text",
"label": "Scene Name",
"key": "scene_name",
"placeholder": "Name of the OBS scene"
"placeholder": "Name of the OBS scene",
"required": true
},
{
"type": "text",
"label": "Scene Item Name",
"key": "scene_item_name",
"placeholder": "Name of the OBS scene item / source"
"placeholder": "Name of the OBS scene item / source",
"required": true
}
]
},
@ -107,20 +115,23 @@ const actionTypes = [
"type": "text",
"label": "Scene Name",
"key": "scene_name",
"placeholder": "Name of the OBS scene"
"placeholder": "Name of the OBS scene",
"required": true
},
{
"type": "text",
"label": "Scene Item Name",
"key": "scene_item_name",
"placeholder": "Name of the OBS scene item / source"
"placeholder": "Name of the OBS scene item / source",
"required": true
},
{
"type": "text-values",
"label": "Visible",
"key": "obs_visible",
"placeholder": "true for visible; false otherwise",
"values": ["true", "false"]
"values": ["true", "false"],
"required": true
}
]
},
@ -132,19 +143,22 @@ const actionTypes = [
"type": "text",
"label": "Scene Name",
"key": "scene_name",
"placeholder": "Name of the OBS scene"
"placeholder": "Name of the OBS scene",
"required": true
},
{
"type": "text",
"label": "Scene Item Name",
"key": "scene_item_name",
"placeholder": "Name of the OBS scene item / source"
"placeholder": "Name of the OBS scene item / source",
"required": true
},
{
"type": "number",
"label": "Index",
"key": "obs_index",
"placeholder": "index, starting from 0."
"placeholder": "index, starting from 0.",
"required": true
}
]
},
@ -157,6 +171,7 @@ const actionTypes = [
"label": "Sleep",
"key": "sleep",
"placeholder": "Time in milliseconds to do nothing",
"required": true
}
]
},
@ -169,6 +184,7 @@ const actionTypes = [
"label": "nightbot.play",
"key": "nightbot_play",
"placeholder": "",
"required": true
}
]
},
@ -181,6 +197,7 @@ const actionTypes = [
"label": "nightbot.pause",
"key": "nightbot_pause",
"placeholder": "",
"required": true
}
]
},
@ -193,6 +210,7 @@ const actionTypes = [
"label": "nightbot.skip",
"key": "nightbot_skip",
"placeholder": "",
"required": true
}
]
},
@ -205,6 +223,7 @@ const actionTypes = [
"label": "nightbot.clear_playlist",
"key": "nightbot_clear_playlist",
"placeholder": "",
"required": true
}
]
},
@ -217,11 +236,16 @@ const actionTypes = [
"label": "nightbot.clear_queue",
"key": "nightbot_clear_queue",
"placeholder": "",
"required": true
}
]
},
]
const nameSchema = z.string({
required_error: "Name is required.",
invalid_type_error: "Name must be a string"
}).regex(/^[\w\-\s]{1,32}$/, "Name must contain only letters, numbers, spaces, dashes, and underscores.")
interface RedeemableAction {
name: string
@ -256,44 +280,68 @@ const RedemptionAction = ({
const [isEditable, setIsEditable] = useState(edit)
const [isMinimized, setIsMinimized] = useState(!isNew)
const [oldData, setOldData] = useState<{ n: string, t: ActionType | undefined, d: { [k: string]: string } } | undefined>(undefined)
const [error, setError] = useState<string | undefined>(undefined)
function Save(name: string, type: ActionType | undefined, data: { [key: string]: string }, isNew: boolean) {
// TODO: validation
if (!name) {
function Save(isNew: boolean) {
setError(undefined)
if (!actionName) {
setError("Name is required.")
return
}
console.log('typeeee', type)
if (!type) {
const nameValidation = nameSchema.safeParse(actionName)
if (!nameValidation.success) {
setError(JSON.parse(nameValidation.error['message'])[0].message)
return
}
if (!data) {
if (!actionType) {
setError("Action type is required.")
return
}
if (!actionTypes.some(t => t.value == actionType.value)) {
setError("Invalid action type given.")
return
}
if (!actionData) {
setError("Something went wrong with the data.")
return
}
const inputs = actionTypes.find(a => a.value == actionType.value && a.name == actionType.name)!.inputs
const required = inputs.filter(i => i.required)
for (const input of required) {
if (!(input.key in actionData)) {
setError("The field '" + input.label + "' is required.")
return
}
}
let info: any = {
name,
type
name: actionName,
type: actionType.value,
}
info = { ...info, ...data }
info.data = actionData
if (isNew) {
axios.post("/api/settings/redemptions/actions", info)
.then(d => {
adder(name, type, data)
adder(actionName, actionType.value, actionData)
setActionName("")
setActionType(undefined)
setActionData({})
})
.catch(error => setError(error.response.data.message))
} else {
axios.put("/api/settings/redemptions/actions", info)
.then(d => {
setIsEditable(false)
})
.catch(error => setError(error.response.data.message))
}
}
function Cancel(data: { n: string, t: ActionType | undefined, d: { [k: string]: any } } | undefined) {
setError(undefined)
if (!data)
return
@ -305,7 +353,7 @@ const RedemptionAction = ({
}
function Delete() {
axios.delete("/api/settings/redemptions/actions?action_name=" + name)
axios.delete("/api/settings/redemptions/actions?action_name=" + actionName)
.then(d => {
remover(d.data)
})
@ -431,12 +479,10 @@ const RedemptionAction = ({
onChange={e => setActionData(d => {
let abc = { ...actionData }
const v = parseInt(e.target.value)
if (e.target.value.length == 0) {
if (e.target.value.length == 0 || Number.isNaN(v)) {
abc[i.key] = "0"
} else if (!Number.isNaN(v) && Number.isSafeInteger(v)) {
abc[i.key] = v.toString()
} else if (Number.isNaN(v)) {
abc[i.key] = "0"
}
return abc
})}
@ -497,7 +543,7 @@ const RedemptionAction = ({
if (!!connection) {
setActionData({
'oauth_name': connection.name,
'oauth_type' : connection.type
'oauth_type': connection.type
})
}
else
@ -517,7 +563,6 @@ const RedemptionAction = ({
</Popover>
</div>
}
return <div key={i.key}></div>
})}
</div>
}
@ -548,11 +593,16 @@ const RedemptionAction = ({
</div>
}
</div>
{error &&
<div className="text-red-600 font-bold">
{error}
</div>
}
<div>
{isEditable &&
<Button
className="m-3"
onClick={() => Save(actionName, actionType?.value, actionData, isNew)}>
onClick={() => Save(isNew)}>
{isNew ? "Add" : "Save"}
</Button>
}

View File

@ -4,14 +4,14 @@ import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Label } from "../ui/label";
import { Label } from "@/components/ui/label";
import { HelpCircleIcon, Trash2Icon } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip"
} from "@/components/ui/tooltip"
interface Redemption {
id: string | undefined
@ -47,17 +47,42 @@ const OBSRedemption = ({
const [order, setOrder] = useState<number>(numbering)
const [isEditable, setIsEditable] = useState(edit)
const [oldData, setOldData] = useState<{ r: { id: string, title: string } | undefined, a: string | undefined, o: number } | undefined>(undefined)
const [error, setError] = useState<string | undefined>(undefined)
useEffect(() => {
setTwitchRedemption(twitchRedemptions.find(r => r.id == redemptionId))
}, [])
function Save() {
// TODO: validation
if (!isNew && !id)
setError(undefined)
if (!isNew && !id) {
setError('Something went wrong. Refresh the page.')
return
if (!action || !twitchRedemption)
}
if (!action) {
setError('An action must be selected.')
return
}
if (!actions.some(a => a == action)) {
setError('Ensure the action selected still exists.')
return
}
if (!twitchRedemption) {
setError('A Twitch redemption must be selected.')
return
}
if (!twitchRedemptions.some(r => r.id == twitchRedemption.id)) {
setError('Ensure the action selected still exists.')
return
}
if (isNaN(order)) {
setError('The order cannot be NaN.')
setOrder(0)
return
}
if (isNew) {
axios.post("/api/settings/redemptions", {
@ -88,6 +113,7 @@ const OBSRedemption = ({
if (!oldData)
return
setError(undefined)
setAction(oldData.a)
setTwitchRedemption(oldData.r)
setOrder(oldData.o)
@ -143,7 +169,8 @@ const OBSRedemption = ({
value={redemption.title}
key={redemption.id}
onSelect={(value) => {
setTwitchRedemption(twitchRedemptions.find(v => v.title.toLowerCase() == value.toLowerCase()))
console.log('tr', value, redemption.id, redemption.title)
setTwitchRedemption(redemption)
setRedemptionOpen(false)
}}>
{redemption.title}
@ -218,23 +245,37 @@ const OBSRedemption = ({
className="inline-block w-[300px]"
id="name"
placeholder="Enter an order number for this action"
onChange={e => setOrder(e.target.value.length == 0 ? 0 : parseInt(e.target.value))}
onChange={e => setOrder(d => {
let temp = order
const v = parseInt(e.target.value)
if (e.target.value.length == 0 || Number.isNaN(v)) {
temp = 0
} else if (!Number.isNaN(v) && Number.isSafeInteger(v)) {
temp = v
}
return temp
})}
value={order}
readOnly={!isEditable} />
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircleIcon
className="inline-block ml-3"/>
className="inline-block ml-3" />
</TooltipTrigger>
<TooltipContent>
<p>This decides when this action will be done relative to other actions for this Twitch redemption.<br/>
Action start from lowest to highest order number.<br/>
Equal order numbers cannot be guaranteed proper order.</p>
<p>This decides when this action will be done relative to other actions for this Twitch redemption.<br />
Action start from lowest to highest order number.<br />
Equal order numbers cannot be guaranteed proper order.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
{error &&
<div className="text-red-600 font-bold">
{error}
</div>
}
<div>
{isEditable &&
<Button

View File

@ -28,17 +28,18 @@ import RoleGate from "@/components/auth/role-gate";
interface UsersGroup {
groupId: string
groupName: string
//userList: { id: number, username: string }[]
//knownUsers: { id: number, username: string }[]
}
const ITEMS_PER_PAGE: number = 10;
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,
groupName,
//userList,
//knownUsers
groupName
}: UsersGroup) => {
const [usersListOpen, setUsersListOpen] = useState(false)
const [users, setUsers] = useState<{ id: number, username: string }[]>([])
@ -60,7 +61,8 @@ const UserList = ({
}).then(d => {
setUsers(d.data)
setKnownUsers(d.data)
setMaxPages(Math.ceil(d.data.length / ITEMS_PER_PAGE))
var maxPages = Math.ceil(d.data.length / ITEMS_PER_PAGE)
setMaxPages(maxPages)
})
}, [groupId, page])
@ -69,17 +71,12 @@ const UserList = ({
setUsersListOpen(false)
}
const usernameSchema = z.string({
required_error: "Name is required.",
invalid_type_error: "Name must be a string"
}).regex(/^[\w\-]{4,25}$/, "Invalid Twitch username.")
function AddUsername() {
setError(undefined)
const nameValidation = usernameSchema.safeParse(newUser)
if (!nameValidation.success) {
setError(JSON.parse(nameValidation.error['message'])[0].message)
const usernameValidation = usernameSchema.safeParse(newUser)
if (!usernameValidation.success) {
setError(JSON.parse(usernameValidation.error['message'])[0].message)
return
}
@ -95,12 +92,16 @@ const UserList = ({
logins: newUser
}
}).then(d => {
if (!d.data)
if (!d.data) {
setError("That was not a known Twitch username.")
return
}
user = d.data[0]
if (!user)
if (!user) {
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))
@ -111,7 +112,7 @@ const UserList = ({
setNewUser("")
setMaxPages(Math.ceil((users.length + 1) / ITEMS_PER_PAGE))
}).catch(e => {
setError("Username does not exist.")
setError("That was not a known Twitch username.")
})
return
}

View File

@ -1,7 +1,7 @@
import { X } from "lucide-react";
import React, { useState } from "react";
import { FaExclamationCircle } from "react-icons/fa";
import { Button } from "../ui/button";
import { Button } from "@/components/ui/button";
interface NoticeProps {