Added basic validation for requests
This commit is contained in:
@ -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 (
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user