diff --git a/backend/app/http/endpoints/api/forms/updateinputs.go b/backend/app/http/endpoints/api/forms/updateinputs.go index 7b0ddc64..b1e34554 100644 --- a/backend/app/http/endpoints/api/forms/updateinputs.go +++ b/backend/app/http/endpoints/api/forms/updateinputs.go @@ -9,6 +9,7 @@ import ( "sort" "strconv" "strings" + "unicode/utf8" "github.com/TicketsBot-cloud/common/featureflags" "github.com/TicketsBot-cloud/database" @@ -30,10 +31,11 @@ type ( } inputCreateBody struct { - Label string `json:"label" validate:"required,min=1,max=45"` + Label string `json:"label" validate:"omitempty,max=45"` Description *string `json:"description,omitempty" validate:"omitempty,max=100"` Placeholder *string `json:"placeholder,omitempty" validate:"omitempty,min=1,max=100"` - Type int `json:"type" validate:"required,oneof=3 4 5 6 7 8 21 22"` + Content *string `json:"content,omitempty" validate:"omitempty,max=4000"` + Type int `json:"type" validate:"required,oneof=3 4 5 6 7 8 10 21 22"` Position int `json:"position" validate:"required,min=1,max=5"` Style component.TextStyleTypes `json:"style" validate:"omitempty,required,min=1,max=2"` Required bool `json:"required"` @@ -93,6 +95,14 @@ func UpdateInputs(c *gin.Context) { return } + for i := range data.Create { + data.Create[i] = normalizeTypeFields(data.Create[i]) + } + + for i := range data.Update { + data.Update[i].inputCreateBody = normalizeTypeFields(data.Update[i].inputCreateBody) + } + if err := validate.Struct(data); err != nil { var validationErrors validator.ValidationErrors if !errors.As(err, &validationErrors) { @@ -206,6 +216,11 @@ func UpdateInputs(c *gin.Context) { } } + if contentLength := totalTextDisplayLength(data); contentLength > 4000 { + c.JSON(400, utils.ErrorStr("Text display content must be at most 4000 characters in total (current: %d characters)", contentLength)) + return + } + if err := saveInputs(c, formId, data, existingInputs); err != nil { _ = c.AbortWithError(http.StatusInternalServerError, app.NewError(err, "Failed to save form inputs to database")) return @@ -256,6 +271,23 @@ func arePositionsCorrect(body updateInputsBody) bool { return true } +func totalTextDisplayLength(body updateInputsBody) int { + var total int + for _, input := range body.Create { + if input.Type == 10 && input.Content != nil { + total += utf8.RuneCountInString(*input.Content) + } + } + + for _, input := range body.Update { + if input.Type == 10 && input.Content != nil { + total += utf8.RuneCountInString(*input.Content) + } + } + + return total +} + func validateUniqueOptionValues(options []inputOption) error { if len(options) == 0 { return nil @@ -307,6 +339,17 @@ func validateApiBodyTemplate(config *inputApiConfigBody) error { } func validateInput(input inputCreateBody, optionTypes map[int]string) error { + if input.Type == 10 { + if input.Content == nil || strings.TrimSpace(*input.Content) == "" { + return fmt.Errorf("Text display content is required") + } + return nil + } + + if strings.TrimSpace(input.Label) == "" { + return fmt.Errorf("Field labels are required") + } + if input.Type == 4 && input.MaxLength < 1 { return fmt.Errorf("Text input max length must be at least 1") } @@ -358,7 +401,7 @@ func validateInput(input inputCreateBody, optionTypes map[int]string) error { } func normalizeLengths(input inputCreateBody) (*uint16, *uint16) { - if input.Type == 21 { + if input.Type == 10 || input.Type == 21 { return nil, nil } @@ -393,6 +436,19 @@ func normalizeLengths(input inputCreateBody) (*uint16, *uint16) { return &minLength, &maxLength } +func normalizeTypeFields(input inputCreateBody) inputCreateBody { + if input.Type == 10 { + input.Label = "" + input.Required = false + input.Placeholder = nil + input.Description = nil + } else { + input.Content = nil + } + + return input +} + func saveInputs(ctx context.Context, formId int, data updateInputsBody, existingInputs []database.FormInput) error { // We can now update in the database tx, err := dbclient.Client.BeginTx(ctx) @@ -426,6 +482,7 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing Label: input.Label, Description: input.Description, Placeholder: input.Placeholder, + Content: input.Content, Required: input.Required, MinLength: minLengthPtr, MaxLength: maxLengthPtr, @@ -435,19 +492,11 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing return err } - if wrapped.Type == 3 || wrapped.Type == 21 || wrapped.Type == 22 { // String Select, RadioGroup, CheckboxGroup - // Delete existing options - options, err := dbclient.Client.FormInputOption.GetOptions(ctx, wrapped.Id) - if err != nil { - return err - } - - for _, option := range options { - if err := dbclient.Client.FormInputOption.DeleteTx(ctx, tx, option.Id); err != nil { - return err - } - } + if err := dbclient.Client.FormInputOption.DeleteByFormInputTx(ctx, tx, wrapped.Id); err != nil { + return err + } + if wrapped.Type == 3 || wrapped.Type == 21 || wrapped.Type == 22 { // String Select, RadioGroup, CheckboxGroup // Add new options for i, opt := range input.Options { option := database.FormInputOption{ @@ -487,6 +536,7 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing input.Label, input.Description, input.Placeholder, + input.Content, input.Required, minLengthPtr, maxLengthPtr, @@ -522,7 +572,7 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing func saveApiConfig(ctx context.Context, tx pgx.Tx, formInputId int, input inputCreateBody) error { if input.Type != 3 { - return nil + return dbclient.Client.FormInputApiConfig.DeleteByFormInputTx(ctx, tx, formInputId) } existingConfig, hasExisting, err := dbclient.Client.FormInputApiConfig.GetTx(ctx, tx, formInputId) diff --git a/backend/app/http/endpoints/api/gallery/import_form.go b/backend/app/http/endpoints/api/gallery/import_form.go index b8685f13..f6f1bc2c 100644 --- a/backend/app/http/endpoints/api/gallery/import_form.go +++ b/backend/app/http/endpoints/api/gallery/import_form.go @@ -169,6 +169,7 @@ func createFormInputs(ctx *gin.Context, tx pgx.Tx, formId int, inputs []database input.Label, input.Description, input.Placeholder, + input.Content, input.Required, input.MinLength, input.MaxLength, diff --git a/backend/app/http/endpoints/api/gallery/resubmit.go b/backend/app/http/endpoints/api/gallery/resubmit.go index beec08dc..cd118483 100644 --- a/backend/app/http/endpoints/api/gallery/resubmit.go +++ b/backend/app/http/endpoints/api/gallery/resubmit.go @@ -244,7 +244,7 @@ func ResubmitHandler(ctx *gin.Context) { snapshotInputs[i] = database.GalleryFormInputSnapshot{ Type: input.Type, Position: input.Position, Style: input.Style, Label: input.Label, Description: input.Description, Placeholder: input.Placeholder, - Required: input.Required, MinLength: input.MinLength, MaxLength: input.MaxLength, + Content: input.Content, Required: input.Required, MinLength: input.MinLength, MaxLength: input.MaxLength, } if opts, exists := optionsByInput[input.Id]; exists { snapshotOpts := make([]database.GalleryFormInputOptionSnapshot, len(opts)) diff --git a/backend/app/http/endpoints/api/gallery/submit_form.go b/backend/app/http/endpoints/api/gallery/submit_form.go index a58d2a12..d54a257f 100644 --- a/backend/app/http/endpoints/api/gallery/submit_form.go +++ b/backend/app/http/endpoints/api/gallery/submit_form.go @@ -126,6 +126,7 @@ func SubmitFormHandler(ctx *gin.Context) { Label: input.Label, Description: input.Description, Placeholder: input.Placeholder, + Content: input.Content, Required: input.Required, MinLength: input.MinLength, MaxLength: input.MaxLength, diff --git a/backend/go.mod b/backend/go.mod index d85483f3..6220acbd 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -14,7 +14,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2 - github.com/TicketsBot-cloud/database v0.0.0-20260927071905-7a22c7be0e4a + github.com/TicketsBot-cloud/database v0.0.0-20261002063446-01dca52a987e github.com/TicketsBot-cloud/gdl v0.0.0-20260612070331-a3947b410d3e github.com/TicketsBot-cloud/logarchiver v0.0.0-20251018211319-7a7df5cacbdc github.com/TicketsBot-cloud/worker v0.0.0-20260827073646-455b39e53841 diff --git a/backend/go.sum b/backend/go.sum index 1b37bf57..cb917e0d 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -41,8 +41,8 @@ github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 h1 github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2 h1:qeebeGGdrsDXrngOVrKMDt9G3JUTJwT/IEPTB61d46s= github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2/go.mod h1:yL+VPSYNVK5gxUkA+fbb0WdTjGfWXnr7ScfL0rwk29g= -github.com/TicketsBot-cloud/database v0.0.0-20260927071905-7a22c7be0e4a h1:Y1MPuFHwMmkoGeqnUV8CuXx4yqChdvPpqy3vGWBYnZo= -github.com/TicketsBot-cloud/database v0.0.0-20260927071905-7a22c7be0e4a/go.mod h1:0fKn7nPX6KeljYNQtGQiuZ5aJ/TczgXvBhnbz+4GEc8= +github.com/TicketsBot-cloud/database v0.0.0-20261002063446-01dca52a987e h1:kZS42Mp+6wBhApK7zA6F1SX8ZYYG18cVRHB2ZDTvx94= +github.com/TicketsBot-cloud/database v0.0.0-20261002063446-01dca52a987e/go.mod h1:0fKn7nPX6KeljYNQtGQiuZ5aJ/TczgXvBhnbz+4GEc8= github.com/TicketsBot-cloud/gdl v0.0.0-20260612070331-a3947b410d3e h1:qCibZQmO2rrBFrvn6+oAchPEQ7k/0Izr3ruL5P9aVZU= github.com/TicketsBot-cloud/gdl v0.0.0-20260612070331-a3947b410d3e/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= github.com/TicketsBot-cloud/logarchiver v0.0.0-20251018211319-7a7df5cacbdc h1:qTLNpCvIqM7UwZ6MdWQ9EztcDsIJfHh+VJdG+ULLEaA= diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 130f42b1..b482fa35 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,7 +13,7 @@ "@fortawesome/react-fontawesome": "^3.2.0", "@radix-ui/react-slider": "^1.3.6", "@sentry/react": "^10.42.0", - "@tailwindcss/vite": "^4.2.1", + "@tailwindcss/vite": "^4.3.3", "@tanstack/react-query": "^5.90.21", "@tiptap/extension-link": "^3.22.2", "@tiptap/extension-placeholder": "^3.22.2", @@ -31,7 +31,7 @@ "recharts": "^3.8.1", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4.3.3", "timezones-list": "^3.1.0", "tiptap-markdown": "^0.9.0", "unicode-emoji": "^2.6.0", @@ -1679,9 +1679,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1695,9 +1692,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1711,9 +1705,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1727,9 +1718,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1743,9 +1731,6 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1759,9 +1744,6 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1775,9 +1757,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1791,9 +1770,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1807,9 +1783,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1823,9 +1796,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1839,9 +1809,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1855,9 +1822,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1871,9 +1835,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2062,47 +2023,47 @@ "license": "MIT" }, "node_modules/@tailwindcss/node": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", - "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.21.0", - "jiti": "^2.6.1", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.0" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", - "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-x64": "4.3.0", - "@tailwindcss/oxide-freebsd-x64": "4.3.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-x64-musl": "4.3.0", - "@tailwindcss/oxide-wasm32-wasi": "4.3.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", - "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -2116,9 +2077,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", - "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -2132,9 +2093,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", - "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -2148,9 +2109,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", - "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -2164,9 +2125,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", - "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -2180,15 +2141,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", - "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2199,15 +2157,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", - "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2218,15 +2173,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", - "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2237,15 +2189,12 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", - "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2256,9 +2205,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", - "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2273,81 +2222,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.10.0", - "@emnapi/runtime": "^1.10.0", - "@emnapi/wasi-threads": "^1.2.1", + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.1", + "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", - "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -2361,9 +2250,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", - "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -2377,14 +2266,14 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", - "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.0", - "@tailwindcss/oxide": "4.3.0", - "tailwindcss": "4.3.0" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" @@ -4397,9 +4286,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.22.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.1.tgz", - "integrity": "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==", + "version": "5.25.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz", + "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -6196,9 +6085,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6219,9 +6105,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6242,9 +6125,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6265,9 +6145,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8751,9 +8628,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", - "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "license": "MIT" }, "node_modules/tapable": { diff --git a/frontend/package.json b/frontend/package.json index ae10c02b..b97e870a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,7 @@ "@fortawesome/react-fontawesome": "^3.2.0", "@radix-ui/react-slider": "^1.3.6", "@sentry/react": "^10.42.0", - "@tailwindcss/vite": "^4.2.1", + "@tailwindcss/vite": "^4.3.3", "@tanstack/react-query": "^5.90.21", "@tiptap/extension-link": "^3.22.2", "@tiptap/extension-placeholder": "^3.22.2", @@ -36,7 +36,7 @@ "recharts": "^3.8.1", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4.3.3", "timezones-list": "^3.1.0", "tiptap-markdown": "^0.9.0", "unicode-emoji": "^2.6.0", diff --git a/frontend/src/components/FormInputRow.tsx b/frontend/src/components/FormInputRow.tsx index 24de1be9..f95e0ed8 100644 --- a/frontend/src/components/FormInputRow.tsx +++ b/frontend/src/components/FormInputRow.tsx @@ -6,6 +6,7 @@ import Select from "./Select"; import RangeSlider from "./RangeSlider"; import NumberInput from "./NumberInput"; import Textarea from "./Textarea"; +import DiscordContent from "./discord/DiscordContent"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronUp, faChevronDown, faTrash, faPlus } from "@fortawesome/free-solid-svg-icons"; import Slider from "./Slider"; @@ -30,6 +31,7 @@ interface FormInputRowProps { } const OPTION_TYPES = [3, 21, 22]; +const NO_RANGE_TYPES = [10, 21]; // Must match SecretHeaderMask on the API; sent back untouched to keep the stored secret. const SECRET_HEADER_MASK = "••••••••"; @@ -115,8 +117,12 @@ const FormInputRow: FC = ({ // Validation const hasInvalidLabel = - !input.label || input.label.trim().length === 0 || input.label.length > 45; - const hasInvalidDescription = !!input.description && input.description.length > 100; + input.type !== 10 && + (!input.label || input.label.trim().length === 0 || input.label.length > 45); + const contentLength = [...(input.content ?? "")].length; + const hasInvalidContent = input.type === 10 && (!input.content?.trim() || contentLength > 4000); + const hasInvalidDescription = + input.type !== 10 && !!input.description && input.description.length > 100; const minOptionsRequired = input.type === 21 ? 2 : 1; const maxOptionsAllowed = input.type === 21 || input.type === 22 ? 10 : 25; const hasNoOptions = @@ -175,6 +181,7 @@ const FormInputRow: FC = ({ const hasBlankOptions = blankOptions.length > 0; const hasValidationErrors = hasInvalidLabel || + hasInvalidContent || hasInvalidDescription || hasDuplicateValues || hasNoOptions || @@ -212,6 +219,7 @@ const FormInputRow: FC = ({ { label: "Channel Select", key: "8" }, { label: "Radio Group", key: "21" }, { label: "Checkbox Group", key: "22" }, + { label: "Text Display", key: "10" }, ]; const inputStyles = [ @@ -262,42 +270,46 @@ const FormInputRow: FC = ({ -
- handleChange("label", value)} - /> - handleChange("placeholder", value)} - /> -
- {!!input.label && input.label.length > 45 && ( - - )} + {input.type !== 10 && ( + <> +
+ handleChange("label", value)} + /> + handleChange("placeholder", value)} + /> +
+ {!!input.label && input.label.length > 45 && ( + + )} -
- handleChange("description", value)} - /> -
- {hasInvalidDescription && ( - +
+ handleChange("description", value)} + /> +
+ {hasInvalidDescription && ( + + )} + )} -
+