dashboard/frontend/src/components/manage/SettingsCard.svelte
Scott Brown 0b493f41dc
Removing "required" from line 13 CUSTOMEMBED.go (#22)
* Removing "required" from line 13 CUSTOMEMBED.go

* updating label: archive->trasncripts

Co-authored-by: Ryan <29165304+rxdn@users.noreply.github.com>
2023-01-02 21:44:00 +00:00

497 lines
17 KiB
Svelte

{#if data}
<Card footer="{false}" fill="{false}">
<span slot="title">
Settings
</span>
<div slot="body" class="body-wrapper">
<form class="settings-form" on:submit|preventDefault={updateSettings}>
<Collapsible defaultOpen>
<span slot="header">General</span>
<div slot="content" class="col-1">
<div class="row">
<Input label="prefix (max len. 8)" placeholder="t!" col4 bind:value={data.prefix}/>
<Number label="per user simultaneous ticket limit" min=1 max=10 bind:value={data.ticket_limit}/>
<Dropdown label="Language" bind:value={data.language}>
<option value=null selected="selected">Server Default</option>
{#if data.languages}
{#each data.languages as language}
<option value={language}>{data.language_names[language]}</option>
{/each}
{/if}
</Dropdown>
<Checkbox label="allow users to close tickets" bind:value={data.users_can_close}/>
<Checkbox label="ticket close confirmation" bind:value={data.close_confirmation}/>
<Checkbox label="Enable User Feedback" bind:value={data.feedback_enabled}/>
</div>
</div>
</Collapsible>
<Collapsible defaultOpen tooltip="Click here to find out more about thread mode" tooltipUrl="https://docs.ticketsbot.net/features/thread-mode">
<span slot="header" class="header">
Thread Mode
<IconBadge icon="fas fa-flask">Beta</IconBadge>
</span>
<div slot="content" class="col-1">
<div class="row">
<Checkbox label="Enabled" bind:value={data.use_threads}/>
<ChannelDropdown label="Ticket Notification Channel" col4 {channels} disabled={!data.use_threads}
bind:value={data.ticket_notification_channel}/>
</div>
</div>
</Collapsible>
<Collapsible defaultOpen>
<span slot="header">Tickets</span>
<div slot="content" class="col-1">
<div class="row">
<ChannelDropdown label="Transcripts Channel" col4 channels={channels} withNull={true}
bind:value={data.archive_channel}/>
<Dropdown label="Overflow Category" col4 bind:value={data.overflow_category_id}>
<option value=-1>Disabled</option>
<option value=-2>Uncategorised (Appears at top of channel list)</option>
{#each channels as channel}
{#if channel.type === 4}
<option value={channel.id}>
{channel.name}
</option>
{/if}
{/each}
</Dropdown>
<Checkbox label="Store Ticket Transcripts" bind:value={data.store_transcripts}/>
<Checkbox label="Hide Claim Button" bind:value={data.hide_claim_button}/>
</div>
<div class="row">
</div>
</div>
</Collapsible>
<Collapsible>
<span slot="header">/Open Command</span>
<div slot="content" class="col-1">
<div class="row">
<Checkbox label="Disable /open Command" bind:value={data.disable_open_command}/>
<CategoryDropdown label="Channel Category" col3 channels={channels} bind:value={data.category}/>
<NamingScheme bind:value={data.naming_scheme}/>
</div>
<div class="row">
<div class="col-1-flex">
<Textarea label="welcome message" placeholder="Thanks for opening a ticket!" col1
bind:value={data.welcome_message}/>
</div>
</div>
</div>
</Collapsible>
<Collapsible>
<span slot="header">Context Menu (Start Ticket Dropdown)</span>
<div slot="content" class="col-1">
<div class="row">
<Dropdown col3 label="Required Permission Level" bind:value={data.context_menu_permission_level}>
<option value="0">Everyone</option>
<option value="1">Support Representative</option>
<option value="2">Administrator</option>
</Dropdown>
<Checkbox label="Add Message Sender To Ticket" bind:value={data.context_menu_add_sender}/>
<SimplePanelDropdown label="Use Settings From Panel" col3 allowNone={true} bind:panels
bind:value={data.context_menu_panel}/>
</div>
</div>
</Collapsible>
<Collapsible>
<span slot="header">Claiming</span>
<div slot="content" class="col-1">
<div class="row">
<Checkbox label="SUPPORT REPS CAN VIEW CLAIMED TICKETS" bind:value={data.claim_settings.support_can_view}
on:change={validateView}/>
<Checkbox label="SUPPORT REPS CAN TYPE IN CLAIMED TICKETS"
bind:value={data.claim_settings.support_can_type}
on:change={validateType}/>
</div>
</div>
</Collapsible>
<Collapsible>
<span slot="header">Auto Close</span>
<div slot="content" class="col-1">
<div class="row">
<Checkbox label="Enabled" bind:value={data.auto_close.enabled}/>
<Checkbox label="Close On User Leave" disabled={!data.auto_close.enabled}
bind:value={data.auto_close.on_user_leave}/>
</div>
<div class="row" style="justify-content: space-between">
<div class="col-2" style="flex-direction: row">
<Duration disabled={!isPremium || !data.auto_close.enabled} bind:days={sinceOpenDays}
bind:hours={sinceOpenHours}
bind:minutes={sinceOpenMinutes}>
<div slot="header" class="header">
<label class="form-label" style="margin-bottom: unset">Since Open With No Response</label>
<PremiumBadge/>
</div>
</Duration>
</div>
<div class="col-2" style="flex-direction: row">
<Duration disabled={!isPremium || !data.auto_close.enabled} bind:days={sinceLastDays}
bind:hours={sinceLastHours}
bind:minutes={sinceLastMinutes}>
<div slot="header" class="header">
<label class="form-label" style="margin-bottom: unset">Since Last Message</label>
<PremiumBadge/>
</div>
</Duration>
</div>
</div>
</div>
</Collapsible>
<Collapsible tooltip="Define which permissions are given to users in ticket channels">
<span slot="header">Ticket Permissions</span>
<div slot="content" class="row">
<Toggle label="Attach Files" bind:value={data.ticket_permissions.attach_files}/>
<Toggle label="Embed Links" bind:value={data.ticket_permissions.embed_links}/>
<Toggle label="Add Reactions" bind:value={data.ticket_permissions.add_reactions}/>
</div>
</Collapsible>
<Collapsible>
<div slot="header" class="header">
<span>Colour Scheme</span>
<PremiumBadge/>
</div>
<div slot="content" class="col-1">
<div class="row">
<Colour col4 label="Success" bind:value={data.colours["0"]} disabled={!isPremium}/>
<Colour col4 label="Failure" bind:value={data.colours["1"]} disabled={!isPremium}/>
</div>
</div>
</Collapsible>
<div class="row">
<div class="col-1">
<Button icon="fas fa-paper-plane" fullWidth=true>Submit</Button>
</div>
</div>
</form>
</div>
</Card>
{/if}
<svelte:head>
<style>
body {
overflow-y: scroll;
}
</style>
</svelte:head>
<script>
import ChannelDropdown from "../ChannelDropdown.svelte";
import Card from "../Card.svelte";
import Input from "../form/Input.svelte";
import Number from "../form/Number.svelte";
import Checkbox from "../form/Checkbox.svelte";
import Textarea from "../form/Textarea.svelte";
import {setDefaultHeaders} from '../../includes/Auth.svelte'
import axios from "axios";
import {notify, notifyError, notifySuccess, withLoadingScreen} from "../../js/util";
import {API_URL} from "../../js/constants";
import CategoryDropdown from "../CategoryDropdown.svelte";
import Button from "../Button.svelte";
import NamingScheme from "../NamingScheme.svelte";
import Dropdown from "../form/Dropdown.svelte";
import SimplePanelDropdown from "../SimplePanelDropdown.svelte";
import Collapsible from "../Collapsible.svelte";
import Duration from "../form/Duration.svelte";
import Colour from "../form/Colour.svelte";
import PremiumBadge from "../PremiumBadge.svelte";
import {toDays, toHours, toMinutes} from "../../js/timeutil";
import Toggle from "../form/Toggle.svelte";
import IconBadge from "../IconBadge.svelte";
export let guildId;
setDefaultHeaders();
let channels = [];
let panels = [];
let isPremium = false;
let data;
let sinceOpenDays = 0, sinceOpenHours = 0, sinceOpenMinutes = 0;
let sinceLastDays = 0, sinceLastHours = 0, sinceLastMinutes = 0;
function validateView() {
if (!data.support_can_view && data.support_can_type) {
data.support_can_type = false;
}
}
function validateType() {
if (!data.support_can_view && data.support_can_type) {
data.support_can_view = true;
}
}
async function loadPanels() {
const res = await axios.get(`${API_URL}/api/${guildId}/panels`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
panels = res.data;
}
async function loadChannels() {
const res = await axios.get(`${API_URL}/api/${guildId}/channels`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
channels = res.data;
}
async function loadPremium() {
const res = await axios.get(`${API_URL}/api/${guildId}/premium`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
isPremium = res.data.premium;
}
async function updateSettings() {
// Svelte hack - I can't even remember what this does
let mapped = Object.fromEntries(Object.entries(data).map(([k, v]) => {
if (v === "null") {
return [k, null];
} else {
return [k, v];
}
}));
// "Normalise" data.overflow_category_id
// Svelte doesn't always keep its promise of using integers, so == instead of ===
if (mapped.overflow_category_id == -1) {
mapped.overflow_enabled = false;
mapped.overflow_category_id = null;
} else if (mapped.overflow_category_id == -2) {
mapped.overflow_enabled = true
mapped.overflow_category_id = null;
} else {
mapped.overflow_enabled = true;
}
// Normalise autoclose
data.auto_close.since_open_with_no_response = sinceOpenDays * 86400 + sinceOpenHours * 3600 + sinceOpenMinutes * 60;
data.auto_close.since_last_message = sinceLastDays * 86400 + sinceLastHours * 3600 + sinceLastMinutes * 60;
const res = await axios.post(`${API_URL}/api/${guildId}/settings`, mapped);
if (res.status === 200) {
if (showValidations(res.data)) {
notifySuccess('Your settings have been saved.');
} else {
// Load valid data
await loadData();
}
} else {
notifyError(res.data.error);
}
}
function showValidations(data) {
let success = true;
if (data.error !== null) {
success = false;
notify("Warning", data.error);
}
if (!data.prefix) {
success = false;
notify("Warning", "Your prefix has not been saved.\nPrefixes must be between 1 - 8 characters in length.")
}
if (!data.welcome_message) {
success = false;
notify("Warning", "Your welcome message has not been saved.\nWelcome messages must be between 1 - 1000 characters in length.")
}
if (!data.ticket_limit) {
success = false;
notify("Warning", "Your ticket limit has not been saved.\nTicket limits must be in the range 1 - 10.")
}
if (!data.archive_channel) {
success = false;
notify("Warning", "Your archive channel has not been saved.")
}
if (!data.category) {
success = false;
notify("Warning", "Your channel category has not been saved.")
}
if (!data.naming_scheme) {
success = false;
notify("Warning", "Your archive channel has not been saved.")
}
return success;
}
async function loadData() {
const res = await axios.get(`${API_URL}/api/${guildId}/settings`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
data = res.data;
// Overrides
if (data.archive_channel === "0") {
let first = channels.find((c) => c.type === 0);
if (first !== undefined) {
data.archive_channel = first.id;
}
}
if (data.category === "0") {
let first = channels.find((c) => c.type === 4);
if (first !== undefined) {
data.category = first.id;
}
}
if (data.overflow_enabled === false) {
data.overflow_category_id = "-1";
} else if (data.overflow_enabled === true) {
if (data.overflow_category_id === null) {
data.overflow_category_id = "-2";
}
if (!channels.some((c) => c.id === data.overflow_category_id)) {
data.overflow_category_id = "-2";
}
}
if (data.language === null) {
data.language = "null";
}
// Auto close overrides
if (data.auto_close.since_open_with_no_response) {
sinceOpenDays = toDays(data.auto_close.since_open_with_no_response);
sinceOpenHours = toHours(data.auto_close.since_open_with_no_response);
sinceOpenMinutes = toMinutes(data.auto_close.since_open_with_no_response);
}
if (data.auto_close.since_last_message) {
sinceLastDays = toDays(data.auto_close.since_last_message);
sinceLastHours = toHours(data.auto_close.since_last_message);
sinceLastMinutes = toMinutes(data.auto_close.since_last_message);
}
}
withLoadingScreen(async () => {
await Promise.all([
loadPanels(),
loadChannels(),
loadPremium()
]);
await loadData(); // Depends on channels
});
</script>
<style>
:global(.body-wrapper) {
display: flex;
width: 100%;
height: 100%;
}
.row {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
gap: 2%;
width: 100%;
height: 100%;
margin-top: 10px;
}
.settings-form {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.col-1-flex {
display: flex;
flex-direction: column;
align-items: flex-start;
flex: 0 0 100%;
}
@media only screen and (max-width: 950px) {
.row {
flex-direction: column;
justify-content: center;
}
:global(.col-4, .col-3, .col-2, .col-3-4) {
width: 100% !important;
}
}
:global(.col-1, .col-1-force) {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
height: 100%;
}
:global(.col-2, .col-2-force) {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 49%;
height: 100%;
}
:global(.col-3, .col-3-force) {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 31%;
height: 100%;
}
:global(.col-4, .col-4-force) {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 23%;
height: 100%;
}
.header {
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
}
</style>