Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions app/http/endpoints/api/forms/getforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import (
"github.com/gin-gonic/gin"
)

type embeddedFormInput struct {
database.FormInput
Options []database.FormInputOption `json:"options"`
}

type embeddedForm struct {
database.Form
Inputs []database.FormInput `json:"inputs"`
Inputs []embeddedFormInput `json:"inputs"`
}

func GetForms(c *gin.Context) {
Expand All @@ -29,16 +34,30 @@ func GetForms(c *gin.Context) {
return
}

options, err := dbclient.Client.FormInputOption.GetAllOptionsByGuild(c, guildId)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, app.NewServerError(err))
return
}

data := make([]embeddedForm, len(forms))
for i, form := range forms {
formInputs, ok := inputs[form.Id]
if !ok {
formInputs = make([]database.FormInput, 0)
}

inputs := make([]embeddedFormInput, len(formInputs))
for j, input := range formInputs {
inputs[j] = embeddedFormInput{
FormInput: input,
Options: options[input.Id],
}
}

data[i] = embeddedForm{
Form: form,
Inputs: formInputs,
Inputs: inputs,
}
}

Expand Down
63 changes: 60 additions & 3 deletions app/http/endpoints/api/forms/updateinputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,19 @@ type (
Label string `json:"label" validate:"required,min=1,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,min=3,max=8"`
Position int `json:"position" validate:"required,min=1,max=5"`
Style component.TextStyleTypes `json:"style" validate:"required,min=1,max=2"`
Style component.TextStyleTypes `json:"style" validate:"omitempty,required,min=1,max=2"`
Required bool `json:"required"`
MinLength uint16 `json:"min_length" validate:"min=0,max=1024"` // validator interprets 0 as not set
MaxLength uint16 `json:"max_length" validate:"min=0,max=1024"`
Options []inputOption `json:"options,omitempty" validate:"omitempty,dive,required,min=1,max=25"`
}

inputOption struct {
Label string `json:"label" validate:"required,min=1,max=100"`
Description *string `json:"description,omitempty" validate:"omitempty,max=100"`
Value string `json:"value" validate:"required,min=1,max=100"`
}

inputUpdateBody struct {
Expand Down Expand Up @@ -213,6 +221,7 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing
wrapped := database.FormInput{
Id: input.Id,
FormId: formId,
Type: input.Type,
Position: input.Position,
CustomId: existing.CustomId,
Style: uint8(input.Style),
Expand All @@ -227,6 +236,35 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing
if err := dbclient.Client.FormInput.UpdateTx(ctx, tx, wrapped); err != nil {
return err
}

if wrapped.Type == 3 { // String Select
// 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
}
}

// Add new options
for i, opt := range input.Options {
option := database.FormInputOption{
FormInputId: wrapped.Id,
Position: i + 1,
Label: opt.Label,
Description: opt.Description,
Value: opt.Value,
}

if _, err := dbclient.Client.FormInputOption.CreateTx(ctx, tx, option); err != nil {
return err
}
}
}
}

for _, input := range data.Create {
Expand All @@ -235,9 +273,10 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing
return err
}

if _, err := dbclient.Client.FormInput.CreateTx(ctx,
formInputId, err := dbclient.Client.FormInput.CreateTx(ctx,
tx,
formId,
input.Type,
customId,
input.Position,
uint8(input.Style),
Expand All @@ -247,9 +286,27 @@ func saveInputs(ctx context.Context, formId int, data updateInputsBody, existing
input.Required,
&input.MinLength,
&input.MaxLength,
); err != nil {
)

if err != nil {
return err
}

if input.Type == 3 { // String Select
for i, opt := range input.Options {
option := database.FormInputOption{
FormInputId: formInputId,
Position: i + 1,
Label: opt.Label,
Description: opt.Description,
Value: opt.Value,
}

if _, err := dbclient.Client.FormInputOption.CreateTx(ctx, tx, option); err != nil {
return err
}
}
}
}

return tx.Commit(context.Background())
Expand Down
2 changes: 1 addition & 1 deletion envvars.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
---
- ADMINS
- FORCED_WHITELABEL
- SENTRY_DSN
- SENTRY_DSN
- SERVER_ADDR
- METRIC_SERVER_ADDR
- BASE_URL
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/_headers
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-eval' https://static.cloudflareinsights.com https://ticketsv21.statuspage.io; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://use.fontawesome.com; img-src 'self' https://rover.link https://cdn.discordapp.com https://media.discordapp.net https://image-cdn.tickets.bot; font-src https://fonts.googleapis.com https://fonts.gstatic.com https://use.fontawesome.com; connect-src https://9152d165aba0cefcea4f29140b8bde7f.r2.cloudflarestorage.com https://api.tickets.bot https://import-api.tickets.bot wss://api.tickets.bot https://cloudflareinsights.com/cdn-cgi/rum; media-src https://cdn.discordapp.com https://media.discordapp.net; frame-src 'self'
Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-eval' https://static.cloudflareinsights.com https://ticketsv21.statuspage.io; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://use.fontawesome.com; img-src 'self' https://rover.link https://cdn.discordapp.com https://media.discordapp.net https://image-cdn.tickets.bot; font-src https://fonts.googleapis.com https://fonts.gstatic.com https://use.fontawesome.com; connect-src https://9152d165aba0cefcea4f29140b8bde7f.r2.cloudflarestorage.com https://api.tickets.bot https://beta-api.tickets.bot https://import-api.tickets.bot wss://api.tickets.bot https://cloudflareinsights.com/cdn-cgi/rum; media-src https://cdn.discordapp.com https://media.discordapp.net; frame-src 'self'

/manage/*/transcripts/view/*
! Content-Security-Policy
Expand Down
157 changes: 157 additions & 0 deletions frontend/src/components/BetaAlert.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<script>
export let title = "Beta Feature";
export let message = "This feature is currently in beta and functionality may change in future updates.";
export let icon = "fas fa-flask";
export let color = "#7b61ff"; // Default purple color
export let animate = true;
export let compact = false;
</script>

<div class="beta-alert" class:compact class:animated={animate} style="--alert-color: {color}">
<div class="beta-alert-icon">
<i class={icon}></i>
</div>
<div class="beta-alert-content">
<strong>{title}</strong>
{#if !compact}
<p>{message}</p>
{/if}
</div>
{#if $$slots.default}
<div class="beta-alert-extra">
<slot />
</div>
{/if}
</div>

<style>
.beta-alert {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 16px;
margin: 10px 0;
background: linear-gradient(
135deg,
color-mix(in srgb, var(--alert-color) 10%, transparent) 0%,
color-mix(in srgb, var(--alert-color) 5%, transparent) 100%
);
border: 1px solid color-mix(in srgb, var(--alert-color) 30%, transparent);
border-radius: 8px;
transition: all 0.3s ease;
}

.beta-alert.compact {
padding: 8px 12px;
margin: 5px 0;
}

.beta-alert.animated {
animation: subtle-pulse 3s ease-in-out infinite;
}

@keyframes subtle-pulse {
0%, 100% {
border-color: color-mix(in srgb, var(--alert-color) 30%, transparent);
}
50% {
border-color: color-mix(in srgb, var(--alert-color) 50%, transparent);
}
}

.beta-alert-icon {
flex-shrink: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--alert-color) 20%, transparent);
border-radius: 50%;
color: var(--alert-color);
}

.compact .beta-alert-icon {
width: 20px;
height: 20px;
}

.beta-alert-icon i {
font-size: 12px;
}

.compact .beta-alert-icon i {
font-size: 10px;
}

.beta-alert-content {
flex: 1;
}

.beta-alert-content strong {
display: block;
color: var(--alert-color);
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}

.compact .beta-alert-content strong {
font-size: 13px;
margin-bottom: 0;
}

.beta-alert-content p {
margin: 0;
color: var(--text-secondary, #666);
font-size: 13px;
line-height: 1.4;
}

.beta-alert-extra {
flex-shrink: 0;
}

/* Hover effect */
.beta-alert:hover {
border-color: color-mix(in srgb, var(--alert-color) 40%, transparent);
background: linear-gradient(
135deg,
color-mix(in srgb, var(--alert-color) 12%, transparent) 0%,
color-mix(in srgb, var(--alert-color) 6%, transparent) 100%
);
}

/* Different color presets via CSS variables */
.beta-alert[style*="--alert-color: #ff6b6b"] .beta-alert-icon { /* Red/Danger */
background: color-mix(in srgb, #ff6b6b 20%, transparent);
}

.beta-alert[style*="--alert-color: #51cf66"] .beta-alert-icon { /* Green/Success */
background: color-mix(in srgb, #51cf66 20%, transparent);
}

.beta-alert[style*="--alert-color: #ffd43b"] .beta-alert-icon { /* Yellow/Warning */
background: color-mix(in srgb, #ffd43b 20%, transparent);
}

.beta-alert[style*="--alert-color: #339af0"] .beta-alert-icon { /* Blue/Info */
background: color-mix(in srgb, #339af0 20%, transparent);
}

/* Responsive design */
@media (max-width: 480px) {
.beta-alert {
padding: 10px 12px;
gap: 10px;
}

.beta-alert-content strong {
font-size: 13px;
}

.beta-alert-content p {
font-size: 12px;
}
}
</style>
Loading