Overhaul design

This commit is contained in:
rxdn 2024-08-30 00:09:42 +01:00
parent 3fd7539d7f
commit 5eb26c3b53
20 changed files with 627 additions and 407 deletions

View File

@ -0,0 +1,29 @@
package api
import (
"github.com/TicketsBot/GoPanel/botcontext"
"github.com/TicketsBot/GoPanel/utils"
"github.com/gin-gonic/gin"
)
func GuildHandler(ctx *gin.Context) {
guildId := ctx.Keys["guildid"].(uint64)
botContext, err := botcontext.ContextForGuild(guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
guild, err := botContext.GetGuild(ctx, guildId)
if err != nil {
ctx.JSON(500, utils.ErrorJson(err))
return
}
ctx.JSON(200, gin.H{
"id": guild.Id,
"name": guild.Name,
"icon": guild.Icon,
})
}

View File

@ -94,6 +94,7 @@ func StartServer(sm *livechat.SocketManager) {
guildAuthApiSupport := apiGroup.Group("/:id", middleware.AuthenticateGuild(permission.Support))
guildApiNoAuth := apiGroup.Group("/:id", middleware.ParseGuildId)
{
guildAuthApiSupport.GET("/guild", api.GuildHandler)
guildAuthApiSupport.GET("/channels", api.ChannelsHandler)
guildAuthApiSupport.GET("/premium", api.PremiumHandler)
guildAuthApiSupport.GET("/user/:user", api.UserHandler)

View File

@ -1,4 +1,10 @@
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
:root {
--primary: #995DF3;
--primary-gradient: linear-gradient(71.3deg, #873ef5 0%, #995DF3 100%);
--blue: #3472f7;
}
html, body {
position: relative;
@ -7,7 +13,7 @@ html, body {
}
body, h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6, p, .navbar, .brand, .btn-simple, .alert, a, .td-name, td, button.close {
font-family: 'Noto Sans', sans-serif !important;
font-family: 'Poppins', sans-serif !important;
font-weight: 400 !important;
}

View File

@ -3,7 +3,7 @@
</div>
<script>
export let colour = '#3472f7';
export let colour = '';
</script>
<style>
@ -11,7 +11,7 @@
display: flex;
align-items: center;
background-color: var(--badge-background-color, #3472f7);
background: var(--badge-background-color, var(--primary));
border-radius: 2px;
font-size: 14px;
padding: 0 4px;

View File

@ -28,9 +28,10 @@
text-align: center;
color: white;
background-color: #3472f7;
border-color: #3472f7;
border-width: 2px;
background: var(--primary-gradient);
border: none;
/*border-color: var(--primary);*/
/*border-width: 2px;*/
border-radius: .25rem;
margin: 0;
@ -39,14 +40,14 @@
box-shadow: 0 4px 4px rgb(0 0 0 / 25%);
}
button:active, button:hover:enabled {
background-color: #0062cc;
border-color: #0062cc;
}
/*button:active, button:hover:enabled {*/
/* background-color: #0062cc;*/
/* border-color: #0062cc;*/
/*}*/
button:disabled {
background-color: #6c757d;
border-color: #6c757d;
background: #6c757d;
border: #6c757d;
cursor: default;
}
@ -60,12 +61,12 @@
}
.danger {
background-color: #dc3545 !important;
background: #dc3545 !important;
border-color: #dc3545 !important;
}
.danger:hover:enabled, .danger:active {
background-color: #c32232 !important;
background: #c32232 !important;
border-color: #c32232 !important;
}

View File

@ -0,0 +1,136 @@
<section class="sidebar">
<header>
<img src="{getIconUrl()}" class="guild-icon" alt="Guild icon" width="50" height="50"/>
{guild.name}
</header>
<nav>
<ul class="nav-list">
<ManageSidebarLink {currentRoute} title="← Back to servers" href="/" />
{#if isAdmin}
<ManageSidebarLink {currentRoute} title="Settings" icon="fa-cogs" href="/manage/{guildId}/settings" />
{/if}
<ManageSidebarLink {currentRoute} title="Transcripts" icon="fa-copy" href="/manage/{guildId}/transcripts" />
{#if isAdmin}
<ManageSidebarLink {currentRoute} routePrefix="/manage/{guildId}/panels" title="Ticket Panels" icon="fa-mouse-pointer" href="/manage/{guildId}/panels" />
<ManageSidebarLink {currentRoute} title="Forms" icon="fa-poll-h" href="/manage/{guildId}/forms" />
<ManageSidebarLink {currentRoute} title="Staff Teams" icon="fa-users" href="/manage/{guildId}/teams" />
<ManageSidebarLink {currentRoute} title="Integrations" icon="fa-robot" href="/manage/{guildId}/integrations" />
{/if}
<ManageSidebarLink {currentRoute} title="Tickets" icon="fa-ticket-alt" href="/manage/{guildId}/tickets" />
<ManageSidebarLink {currentRoute} title="Blacklist" icon="fa-ban" href="/manage/{guildId}/blacklist" />
<ManageSidebarLink {currentRoute} title="Tags" icon="fa-tags" href="/manage/{guildId}/tags" />
</ul>
</nav>
<nav class="bottom">
<hr/>
<ul class="nav-list">
<ManageSidebarLink {currentRoute} title="Documentation" icon="fa-book" href="https://docs.ticketsbot.net" newWindow />
<ManageSidebarLink {currentRoute} title="Logout" icon="fa-sign-out-alt" href="/logout" />
</ul>
</nav>
</section>
<style>
.sidebar {
display: flex;
flex-direction: column;
align-self: flex-start;
background-color: #272727;
padding: 15px;
width: 275px;
border-radius: 6px;
user-select: none;
}
header {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
font-weight: bold;
padding: 6px 10px;
border-radius: 4px;
background: linear-gradient(33.3deg, #873ef5 0%, #995DF3 100%);
box-shadow: 0 6px 6px rgba(10, 10, 10, .1), 0 0 0 1px rgba(10, 10, 10, .1);
}
.guild-icon {
width: 48px;
height: 48px;
border-radius: 50%;
}
nav > ul {
list-style-type: none;
padding: 0;
margin: 0;
}
nav hr {
width: 40%;
padding-left: 20px;
}
</style>
<script>
import {onMount} from "svelte";
import axios from "axios";
import {API_URL} from "../js/constants";
import {notifyError, withLoadingScreen} from "../js/util";
import ManageSidebarLink from "./ManageSidebarLink.svelte";
import SubNavigation from "./SubNavigation.svelte";
import SubNavigationLink from "./SubNavigationLink.svelte";
export let currentRoute;
export let permissionLevel;
$: isAdmin = permissionLevel >= 2;
let guildId = currentRoute.namedParams.id;
let guild = {};
async function loadGuild() {
const res = await axios.get(`${API_URL}/api/${guildId}/guild`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
guild = res.data;
}
function isAnimated() {
if (guild.icon === undefined || guild.icon === "") {
return false;
} else {
return guild.icon.startsWith('a_')
}
}
function getIconUrl() {
if (!guild.icon) {
return `https://cdn.discordapp.com/embed/avatars/${Number((BigInt(guildId) >> BigInt(22)) % BigInt(6))}.png`
}
if (isAnimated()) {
return `https:\/\/cdn.discordapp.com/icons/${guild.id}/${guild.icon}.gif?size=256`
} else {
return `https:\/\/cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=256`
}
}
onMount(async () => {
await withLoadingScreen(async () => {
await loadGuild();
})
});
</script>

View File

@ -0,0 +1,52 @@
<li>
<div style="width: 100%">
<a href="{href}" class:active target="{newWindow === true ? '_blank' : '_self'}">
{#if icon}
<i class="fas {icon}"/>
{/if}
<span>{title}</span>
</a>
{#if active}
<slot/>
{/if}
</div>
</li>
<style>
a.active {
background: linear-gradient(71.3deg, #873ef5 0%, #995DF3 100%);
box-shadow: 0 6px 6px rgba(10, 10, 10, .1), 0 0 0 1px rgba(10, 10, 10, .1);
}
a, a:link, a:hover, a:visited, a:active {
display: block;
color: inherit;
text-decoration: none;
font-size: 16px;
padding: 5px 10px 5px 20px;
border-radius: 4px;
}
i {
width: 20px;
text-align: center;
}
</style>
<script>
export let currentRoute;
export let title;
export let icon;
export let href = "#";
export let routePrefix;
export let newWindow;
let active = href !== "/" && ((routePrefix || href)?.toLowerCase() === currentRoute.name.toLowerCase() ||
currentRoute.name.toLowerCase().startsWith((routePrefix || href).toLowerCase()));
$: active;
</script>

View File

@ -19,12 +19,7 @@
<NavElement icon="fas fa-mouse-pointer" link="/manage/{guildId}/panels" on:click={closeDropdown}>Ticket Panels</NavElement>
<NavElement icon="fas fa-poll-h" link="/manage/{guildId}/forms" on:click={closeDropdown}>Forms</NavElement>
<NavElement icon="fas fa-users" link="/manage/{guildId}/teams" on:click={closeDropdown}>Staff Teams</NavElement>
<NavElement icon="fas fa-robot" link="/manage/{guildId}/integrations" on:click={closeDropdown}>
<div style="display: flex; gap:4px">
Integrations
<Badge>New!</Badge>
</div>
</NavElement>
<NavElement icon="fas fa-robot" link="/manage/{guildId}/integrations" on:click={closeDropdown}>Integrations</NavElement>
{/if}
<NavElement icon="fas fa-ticket-alt" link="/manage/{guildId}/tickets" on:click={closeDropdown}>Tickets</NavElement>
@ -63,7 +58,7 @@
<style>
.navbar {
display: flex;
display: none;
justify-content: center;
width: 100%;
background-color: #272727;
@ -89,6 +84,10 @@
}
@media only screen and (max-width: 1154px) {
.navbar {
display: flex;
}
.nav-section {
display: none;
}

View File

@ -0,0 +1,19 @@
<div class="root">
<ul>
<slot/>
</ul>
</div>
<style>
.root {
display: flex;
flex-direction: row;
}
ul {
width: 100%;
list-style-type: none;
margin: 2px 0 0 55px;
padding: 0;
}
</style>

View File

@ -0,0 +1,53 @@
<li>
<div class="wrapper">
<a href="{href}" class:active>
{#if icon}
<i class="fas {icon}"/>
{/if}
<span>
<slot/>
</span>
</a>
</div>
</li>
<style>
li {
/*padding: 2px 0;*/
}
.wrapper {
width: 100%;
}
a.active {
font-weight: 600 !important;
}
a:not(.active) {
opacity: 0.75;
}
a, a:link, a:hover, a:visited, a:active {
color: inherit;
text-decoration: none;
}
i {
width: 20px;
text-align: center;
}
</style>
<script>
export let currentRoute;
export let icon;
export let href = "#";
export let routePrefix;
let active = href !== "/" && ((routePrefix || href).toLowerCase() === currentRoute.name.toLowerCase() ||
currentRoute.name.toLowerCase().startsWith((routePrefix || href).toLowerCase()));
$: active;
</script>

View File

@ -5,6 +5,7 @@
<div class="super-container" class:dropdown={$dropdown}>
<LoadingScreen/>
<div class="content-container" class:hide={$loadingScreen}>
<ManageSidebar {currentRoute} {permissionLevel} />
<Route {currentRoute} {params}/>
</div>
<NotifyModal/>
@ -23,8 +24,13 @@
height: 100%;
}
.super-container {
padding: 30px;
}
.content-container {
display: flex;
gap: 30px;
width: 100%;
height: 100%;
}
@ -48,6 +54,7 @@
import {setDefaultHeaders} from '../includes/Auth.svelte'
import {permissionLevelCache} from '../js/stores';
import {get} from 'svelte/store';
import ManageSidebar from "../includes/ManageSidebar.svelte";
export let currentRoute;
export let params = {};

View File

@ -1,141 +1,141 @@
{#if blacklistUserModal}
<div class="modal" transition:fade>
<div class="modal-wrapper">
<Card footer footerRight fill={false}>
<span slot="title">Blacklist User</span>
<div class="modal" transition:fade>
<div class="modal-wrapper">
<Card footer footerRight fill={false}>
<span slot="title">Blacklist User</span>
<div slot="body" class="modal-inner">
<div>
<label class="form-label" style="margin-bottom: 0 !important;">Use User ID</label>
<Toggle hideLabel
toggledColor="#66bb6a"
untoggledColor="#ccc"
bind:toggled={blacklistById}/>
</div>
<div slot="body" class="modal-inner">
<div>
<label class="form-label" style="margin-bottom: 0 !important;">Use User ID</label>
<Toggle hideLabel
toggledColor="#66bb6a"
untoggledColor="#ccc"
bind:toggled={blacklistById}/>
</div>
{#if blacklistById}
<Input label="User ID" placeholder="592348585904198711" bind:value={blacklistUserId}/>
{:else}
<div class="user-select-wrapper">
<UserSelect {guildId} label="User" bind:value={blacklistUser} />
</div>
{/if}
{#if blacklistById}
<Input label="User ID" placeholder="592348585904198711" bind:value={blacklistUserId}/>
{:else}
<div class="user-select-wrapper">
<UserSelect {guildId} label="User" bind:value={blacklistUser}/>
</div>
{/if}
</div>
<div slot="footer" style="gap: 12px">
<Button danger on:click={() => blacklistUserModal = false}>Cancel</Button>
<Button on:click={addUser}>Confirm</Button>
</div>
</Card>
</div>
<div slot="footer" style="gap: 12px">
<Button danger on:click={() => blacklistUserModal = false}>Cancel</Button>
<Button on:click={addUser}>Confirm</Button>
</div>
</Card>
</div>
</div>
<div class="modal-backdrop" transition:fade>
</div>
<div class="modal-backdrop" transition:fade>
</div>
{:else if blacklistRoleModal}
<div class="modal" transition:fade>
<div class="modal-wrapper">
<Card footer footerRight fill={false}>
<span slot="title">Blacklist Role</span>
<div class="modal" transition:fade>
<div class="modal-wrapper">
<Card footer footerRight fill={false}>
<span slot="title">Blacklist Role</span>
<div slot="body" class="modal-inner user-select-wrapper">
<RoleSelect {guildId} {roles} label="Role" bind:value={blacklistRole} />
</div>
<div slot="body" class="modal-inner user-select-wrapper">
<RoleSelect {guildId} {roles} label="Role" bind:value={blacklistRole}/>
</div>
<div slot="footer" style="gap: 12px">
<Button danger on:click={() => blacklistRoleModal = false}>Cancel</Button>
<Button on:click={addRole}>Confirm</Button>
<div slot="footer" style="gap: 12px">
<Button danger on:click={() => blacklistRoleModal = false}>Cancel</Button>
<Button on:click={addRole}>Confirm</Button>
</div>
</Card>
</div>
</Card>
</div>
</div>
<div class="modal-backdrop" transition:fade>
</div>
<div class="modal-backdrop" transition:fade>
</div>
{/if}
{#if data}
<div class="parent">
<div class="content">
<div class="main-col">
<Card footer={false}>
<span slot="title">Blacklist</span>
<div slot="body" class="body-wrapper">
<div class="row" style="gap: 10px">
<Button icon="fas fa-ban" on:click={() => blacklistUserModal = true}>Blacklist New User</Button>
<Button icon="fas fa-ban" on:click={() => blacklistRoleModal = true}>Blacklist New Role</Button>
</div>
<div class="main-col">
<Card footer={false}>
<span slot="title">Blacklist</span>
<div slot="body" class="body-wrapper">
<div class="row" style="gap: 10px">
<Button icon="fas fa-ban" on:click={() => blacklistUserModal = true}>Blacklist New User</Button>
<Button icon="fas fa-ban" on:click={() => blacklistRoleModal = true}>Blacklist New Role</Button>
</div>
<hr/>
<hr/>
<div class="tables">
<table class="nice">
<thead>
<tr>
<th class="full-width">Role</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{#each data.roles as roleId}
{@const role = roles.find(role => role.id === roleId)}
<tr>
{#if role === undefined}
<td class="full-width">Unknown ({roleId})</td>
{:else}
<td class="full-width">{role.name}</td>
{/if}
<div class="tables">
<table class="nice">
<thead>
<tr>
<th class="full-width">Role</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{#each data.roles as roleId}
{@const role = roles.find(role => role.id === roleId)}
<tr>
{#if role === undefined}
<td class="full-width">Unknown ({roleId})</td>
{:else}
<td class="full-width">{role.name}</td>
{/if}
<td>
<Button type="button" danger icon="fas fa-trash-can" on:click={() => removeRoleBlacklist(roleId, role)}>
Remove
</Button>
</td>
</tr>
{/each}
</tbody>
</table>
<td>
<Button type="button" danger icon="fas fa-trash-can"
on:click={() => removeRoleBlacklist(roleId, role)}>
Remove
</Button>
</td>
</tr>
{/each}
</tbody>
</table>
<table class="nice">
<thead>
<tr>
<th class="full-width">User</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{#each data.users as user}
<tr>
{#if user.username !== ''}
<td class="full-width">{user.username} ({user.id})</td>
{:else}
<td class="full-width">Unknown ({user.id})</td>
{/if}
<table class="nice">
<thead>
<tr>
<th class="full-width">User</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{#each data.users as user}
<tr>
{#if user.username !== ''}
<td class="full-width">{user.username} ({user.id})</td>
{:else}
<td class="full-width">Unknown ({user.id})</td>
{/if}
<td>
<Button type="button" danger icon="fas fa-trash-can" on:click={() => removeUserBlacklist(user)}>
Remove
</Button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<td>
<Button type="button" danger icon="fas fa-trash-can"
on:click={() => removeUserBlacklist(user)}>
Remove
</Button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<div class="row nav">
<i class="fas fa-chevron-left pagination-chevron" class:disabled-chevron={page <= 1}
on:click={loadPrevious}></i>
<span>Page {page}</span>
<i class="fas fa-chevron-right pagination-chevron"
class:disabled-chevron={data.users.length < data.page_limit && data.roles.length < data.page_limit}
on:click={loadNext}></i>
</div>
</div>
</Card>
</div>
<div class="row nav">
<i class="fas fa-chevron-left pagination-chevron" class:disabled-chevron={page <= 1}
on:click={loadPrevious}></i>
<span>Page {page}</span>
<i class="fas fa-chevron-right pagination-chevron"
class:disabled-chevron={data.users.length < data.page_limit && data.roles.length < data.page_limit}
on:click={loadNext}></i>
</div>
</div>
</Card>
</div>
</div>
</div>
{/if}
<script>
@ -296,21 +296,10 @@
</script>
<style>
.parent {
display: flex;
justify-content: flex-start;
padding-left: 2%;
width: 100%;
height: 100%;
}
.content {
display: flex;
justify-content: space-between;
width: 60%;
width: 100%;
height: 100%;
margin-top: 30px;
padding-bottom: 4%;
}
.main-col {
@ -359,6 +348,7 @@
justify-content: center;
align-items: center;
gap: 2px;
margin-top: 20px;
}
.pagination-chevron {

View File

@ -1,83 +1,84 @@
<div class="parent">
<div class="content">
<div class="content">
<Card footer footerRight>
<span slot="title">Forms</span>
<div slot="body" class="body-wrapper">
<div class="section">
<h2 class="section-title">Create New Form</h2>
<span slot="title">Forms</span>
<div slot="body" class="body-wrapper">
<div class="section">
<h2 class="section-title">Create New Form</h2>
<form on:submit|preventDefault={createForm}>
<div class="row" id="creation-row">
<Input placeholder="Form Title" col3={true} bind:value={newTitle}/>
<div id="create-button-wrapper">
<Button icon="fas fa-paper-plane" fullWidth={windowWidth <= 950}>Create</Button>
</div>
<form on:submit|preventDefault={createForm}>
<div class="row" id="creation-row">
<Input placeholder="Form Title" col3={true} bind:value={newTitle}/>
<div id="create-button-wrapper">
<Button icon="fas fa-paper-plane" fullWidth={windowWidth <= 950}>Create</Button>
</div>
</div>
</form>
</div>
</form>
</div>
<div class="section">
<h2 class="section-title">Manage Forms</h2>
<div class="section">
<h2 class="section-title">Manage Forms</h2>
{#if editingTitle && activeFormId !== null}
<div class="row form-name-edit-wrapper">
<Input col4 label="Form Title" placeholder="Form Title" bind:value={renamedTitle}/>
<div class="form-name-save-wrapper">
<Button icon="fas fa-floppy-disk" fullWidth={windowWidth <= 950} on:click={updateTitle}>Save</Button>
</div>
</div>
{:else}
<div class="row form-select-row">
<div class="multiselect-super">
<Dropdown col1 bind:value={activeFormId}>
<option value={null}>Select a form...</option>
{#each forms as form}
<option value="{form.form_id}">{form.title}</option>
{/each}
</Dropdown>
</div>
{#if editingTitle && activeFormId !== null}
<div class="row form-name-edit-wrapper">
<Input col4 label="Form Title" placeholder="Form Title" bind:value={renamedTitle}/>
<div class="form-name-save-wrapper">
<Button icon="fas fa-floppy-disk" fullWidth={windowWidth <= 950} on:click={updateTitle}>
Save
</Button>
</div>
</div>
{:else}
<div class="row form-select-row">
<div class="multiselect-super">
<Dropdown col1 bind:value={activeFormId}>
<option value={null}>Select a form...</option>
{#each forms as form}
<option value="{form.form_id}">{form.title}</option>
{/each}
</Dropdown>
</div>
{#if activeFormId !== null}
<Button on:click={() => editingTitle = true}>Rename Form</Button>
<Button danger type="button"
on:click={() => deleteForm(activeFormId)}>Delete {activeFormTitle}</Button>
{/if}
</div>
{/if}
{#if activeFormId !== null}
<Button on:click={() => editingTitle = true}>Rename Form</Button>
<Button danger type="button"
on:click={() => deleteForm(activeFormId)}>Delete {activeFormTitle}</Button>
{/if}
</div>
{/if}
<div class="manage">
{#if activeFormId !== null}
{#each forms.find(form => form.form_id === activeFormId).inputs as input, i (input)}
<div animate:flip="{{duration: 500}}">
<FormInputRow data={input} formId={activeFormId}
withSaveButton={true} withDeleteButton={true} withDirectionButtons={true}
index={i} {formLength}
on:delete={() => deleteInput(activeFormId, input)}
on:move={(e) => changePosition(activeFormId, input, e.detail.direction)}/>
<div class="manage">
{#if activeFormId !== null}
{#each forms.find(form => form.form_id === activeFormId).inputs as input, i (input)}
<div animate:flip="{{duration: 500}}">
<FormInputRow data={input} formId={activeFormId}
withSaveButton={true} withDeleteButton={true} withDirectionButtons={true}
index={i} {formLength}
on:delete={() => deleteInput(activeFormId, input)}
on:move={(e) => changePosition(activeFormId, input, e.detail.direction)}/>
</div>
{/each}
{/if}
{#if activeFormId !== null}
<div class="row"
style="justify-content: center; align-items: center; gap: 10px; margin-top: 10px">
<hr class="fill">
<div class="row add-input-container" class:add-input-disabled={formLength >= 5}>
<i class="fas fa-plus"></i>
<a on:click={addInput}>New Field</a>
</div>
<hr class="fill">
</div>
{/if}
</div>
{/each}
{/if}
{#if activeFormId !== null}
<div class="row" style="justify-content: center; align-items: center; gap: 10px; margin-top: 10px">
<hr class="fill">
<div class="row add-input-container" class:add-input-disabled={formLength >= 5}>
<i class="fas fa-plus"></i>
<a on:click={addInput}>New Field</a>
</div>
<hr class="fill">
</div>
{/if}
</div>
</div>
</div>
</div>
<div slot="footer">
<Button type="submit" icon="fas fa-floppy-disk" disabled={formLength === 0} on:click={saveInputs}>
Save
</Button>
</div>
<div slot="footer">
<Button type="submit" icon="fas fa-floppy-disk" disabled={formLength === 0} on:click={saveInputs}>
Save
</Button>
</div>
</Card>
</div>
</div>
<svelte:window bind:innerWidth={windowWidth}/>
@ -282,20 +283,10 @@
</script>
<style>
.parent {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
.content {
display: flex;
justify-content: space-between;
width: 96%;
width: 100%;
height: 100%;
margin-top: 30px;
margin-bottom: 50px;
}
.body-wrapper {
@ -383,7 +374,7 @@
cursor: pointer;
}
.add-input-disabled > *{
.add-input-disabled > * {
cursor: default !important;
color: #777 !important;
}

View File

@ -19,8 +19,6 @@
flex-direction: row;
height: 100%;
width: 100%;
padding: 0 45px;
justify-content: space-between;
}
.main-col {
@ -28,7 +26,6 @@
flex-direction: column;
height: 100%;
width: 100%;
margin-top: 30px;
}
.right-col {

View File

@ -1,41 +1,37 @@
{#if tagCreateModal}
<TagEditor {isPremium} on:cancel={() => tagCreateModal = false} on:confirm={createTag}/>
<TagEditor {isPremium} on:cancel={() => tagCreateModal = false} on:confirm={createTag}/>
{:else if tagEditModal}
<TagEditor {isPremium} bind:data={editData} on:cancel={cancelEdit} on:confirm={editTag}/>
<TagEditor {isPremium} bind:data={editData} on:cancel={cancelEdit} on:confirm={editTag}/>
{/if}
<div class="parent">
<div class="content">
<div class="main-col">
<Card footer footerRight>
<div class="content">
<Card footer footerRight>
<span slot="title">Tags</span>
<div slot="body" class="body-wrapper">
<table class="nice">
<thead>
<tr>
<th>Tag</th>
<th style="text-align: right">Actions</th>
</tr>
</thead>
<tbody>
{#each Object.entries(tags) as [id, tag]}
<tr>
<td>{id}</td>
<td class="actions">
<Button type="button" on:click={() => openEditModal(id)}>Edit</Button>
<Button type="button" danger={true} on:click={() => deleteTag(id)}>Delete</Button>
</td>
</tr>
{/each}
</tbody>
</table>
<table class="nice">
<thead>
<tr>
<th>Tag</th>
<th style="text-align: right">Actions</th>
</tr>
</thead>
<tbody>
{#each Object.entries(tags) as [id, tag]}
<tr>
<td>{id}</td>
<td class="actions">
<Button type="button" on:click={() => openEditModal(id)}>Edit</Button>
<Button type="button" danger={true} on:click={() => deleteTag(id)}>Delete</Button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<div slot="footer">
<Button icon="fas fa-plus" on:click={openCreateModal}>Create Tag</Button>
<Button icon="fas fa-plus" on:click={openCreateModal}>Create Tag</Button>
</div>
</Card>
</div>
</div>
</Card>
</div>
<script>
@ -45,7 +41,6 @@
import axios from "axios";
import {API_URL} from "../js/constants";
import {setDefaultHeaders} from '../includes/Auth.svelte'
import {fade} from "svelte/transition";
import TagEditor from "../components/manage/TagEditor.svelte";
export let currentRoute;
@ -61,7 +56,7 @@
function openCreateModal(id) {
tagCreateModal = true;
window.scrollTo({ top: 0, behavior: 'smooth' });
window.scrollTo({top: 0, behavior: 'smooth'});
}
function openEditModal(id) {
@ -69,7 +64,7 @@
editData = tags[id];
tagEditModal = true;
window.scrollTo({ top: 0, behavior: 'smooth' });
window.scrollTo({top: 0, behavior: 'smooth'});
}
function cancelEdit() {
@ -192,27 +187,10 @@
</script>
<style>
.parent {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
.content {
display: flex;
justify-content: space-between;
width: 96%;
width: 100%;
height: 100%;
margin-top: 30px;
}
.main-col {
display: flex;
flex-direction: column;
width: 64%;
height: 100%;
padding-bottom: 4%;
}
.body-wrapper {

View File

@ -1,4 +1,3 @@
<div class="parent">
<div class="content">
<Card footer={false}>
<span slot="title">Support Teams</span>
@ -76,7 +75,6 @@
</div>
</Card>
</div>
</div>
<script>
import Card from "../components/Card.svelte";
@ -226,19 +224,10 @@
</script>
<style>
.parent {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
.content {
display: flex;
justify-content: space-between;
width: 96%;
width: 100%;
height: 100%;
margin-top: 30px;
}
.body-wrapper {

View File

@ -1,44 +1,42 @@
<div class="parent">
<div class="content">
<div class="content">
<Card footer={false}>
<span slot="title">Open Tickets</span>
<div slot="body" class="body-wrapper">
<table class="nice">
<thead>
<tr>
<th>ID</th>
<th>Panel</th>
<th>User</th>
<th>View</th>
</tr>
</thead>
<tbody>
{#each tickets as ticket}
<tr>
<td>{ticket.id}</td>
<td>{ticket.panel_title}</td>
{#if ticket.user !== undefined}
<td>{ticket.user.username}</td>
{:else}
<td>Unknown</td>
{/if}
<td>
<Navigate to="/manage/{guildId}/tickets/view/{ticket.id}" styles="link">
<Button type="button">View</Button>
</Navigate>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<span slot="title">Open Tickets</span>
<div slot="body" class="body-wrapper">
<table class="nice">
<thead>
<tr>
<th>ID</th>
<th>Panel</th>
<th>User</th>
<th>View</th>
</tr>
</thead>
<tbody>
{#each tickets as ticket}
<tr>
<td>{ticket.id}</td>
<td>{ticket.panel_title}</td>
{#if ticket.user !== undefined}
<td>{ticket.user.username}</td>
{:else}
<td>Unknown</td>
{/if}
<td>
<Navigate to="/manage/{guildId}/tickets/view/{ticket.id}" styles="link">
<Button type="button">View</Button>
</Navigate>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</Card>
</div>
</div>
<script>
import Card from "../components/Card.svelte";
import {notifyError, notifySuccess, withLoadingScreen} from '../js/util'
import {notifyError, withLoadingScreen} from '../js/util'
import axios from "axios";
import {API_URL} from "../js/constants";
import {setDefaultHeaders} from '../includes/Auth.svelte'
@ -67,20 +65,10 @@
</script>
<style>
.parent {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
margin-top: 30px;
}
.content {
display: flex;
justify-content: space-between;
width: 96%;
width: 100%;
height: 100%;
margin-top: 30px;
}
.body-wrapper {

View File

@ -242,9 +242,8 @@
.col {
display: flex;
flex-direction: column;
width: 95%;
height: 100%;
margin-top: 30px;
width: 100%;
}
.main-col {

View File

@ -1,65 +1,65 @@
<div class="parent">
<div class="content">
<div class="content">
<div class="container">
<div class="spread">
<h4 class="title">My Integrations</h4>
<Button icon="fas fa-server" on:click={() => navigateTo(`/manage/${guildId}/integrations/create`)}>Create
Integration
</Button>
</div>
<div class="integrations my-integrations">
{#each ownedIntegrations as integration}
<div class="integration">
<Integration owned name={integration.name} {guildId} integrationId={integration.id}
imageUrl={generateProxyUrl(integration)} guildCount={integration.guild_count}>
<div class="spread">
<h4 class="title">My Integrations</h4>
<Button icon="fas fa-server" on:click={() => navigateTo(`/manage/${guildId}/integrations/create`)}>Create
Integration
</Button>
</div>
<div class="integrations my-integrations">
{#each ownedIntegrations as integration}
<div class="integration">
<Integration owned name={integration.name} {guildId} integrationId={integration.id}
imageUrl={generateProxyUrl(integration)} guildCount={integration.guild_count}>
<span slot="description">
{integration.description}
</span>
</Integration>
</div>
{/each}
</div>
</Integration>
</div>
{/each}
</div>
</div>
<div>
<h4 class="title">Available Integrations</h4>
<div class="integrations">
<!-- Built in -->
{#if page === 1}
<div class="integration">
<Integration builtIn name="Bloxlink"
imageUrl="https://dbl-static.b-cdn.net/9bbd1f9504ddefc89606b19b290e9a0f.png"
viewLink="https://docs.ticketsbot.net/dashboard/settings/placeholders#bloxlink">
<h4 class="title">Available Integrations</h4>
<div class="integrations">
<!-- Built in -->
{#if page === 1}
<div class="integration">
<Integration builtIn name="Bloxlink"
imageUrl="https://dbl-static.b-cdn.net/9bbd1f9504ddefc89606b19b290e9a0f.png"
viewLink="https://docs.ticketsbot.net/dashboard/settings/placeholders#bloxlink">
<span slot="description">
Our Bloxlink integration inserts the Roblox usernames, profile URLs and more of your users into
ticket welcome messages automatically! This integration is automatically enabled in all servers, press the
View button below to check out the full list of placeholders you can use!
</span>
</Integration>
</div>
{/if}
</Integration>
</div>
{/if}
{#each availableIntegrations as integration}
<div class="integration">
<Integration name={integration.name} {guildId} integrationId={integration.id}
imageUrl={generateProxyUrl(integration)} ownerId={integration.owner_id}
added={integration.added} guildCount={integration.guild_count} showAuthor
author={integration.author} on:remove={() => removeIntegration(integration.id)}>
{#each availableIntegrations as integration}
<div class="integration">
<Integration name={integration.name} {guildId} integrationId={integration.id}
imageUrl={generateProxyUrl(integration)} ownerId={integration.owner_id}
added={integration.added} guildCount={integration.guild_count} showAuthor
author={integration.author} on:remove={() => removeIntegration(integration.id)}>
<span slot="description">
{integration.description}
</span>
</Integration>
</div>
{/each}
</div>
</Integration>
</div>
{/each}
</div>
</div>
<div class="pagination">
<i class="fas fa-chevron-left pagination-chevron" class:disabled-chevron={page === 1} on:click={previousPage}></i>
<p>Page {page}</p>
<i class="fas fa-chevron-right pagination-chevron" class:disabled-chevron={!hasNextPage} on:click={nextPage}></i>
<i class="fas fa-chevron-left pagination-chevron" class:disabled-chevron={page === 1}
on:click={previousPage}></i>
<p>Page {page}</p>
<i class="fas fa-chevron-right pagination-chevron" class:disabled-chevron={!hasNextPage}
on:click={nextPage}></i>
</div>
</div>
</div>
<script>
@ -161,21 +161,12 @@
</script>
<style>
.parent {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
.content {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 96%;
width: 100%;
height: 100%;
margin-top: 30px;
padding-bottom: 5vh;
row-gap: 4vh;
}
@ -193,7 +184,7 @@
}
.integration {
flex: 0 0 23.5%;
flex: 0 0 32%;
}
.my-integrations {
@ -231,19 +222,13 @@
cursor: default !important;
}
@media only screen and (max-width: 1180px) {
.integration {
flex: 0 0 32%;
}
}
@media only screen and (max-width: 930px) {
@media only screen and (max-width: 1200px) {
.integration {
flex: 0 0 49%;
}
}
@media only screen and (max-width: 576px) {
@media only screen and (max-width: 850px) {
.integration {
flex: 0 0 100%;
}

View File

@ -207,7 +207,7 @@
flex-direction: row;
height: 100%;
width: 100%;
margin-top: 30px;
gap: 2%;
}
.col {
@ -219,7 +219,7 @@
.row {
display: flex;
width: 96%;
width: 100%;
margin-bottom: 2%;
}