├── .eslintignore
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .npmrc
├── .pnpm-debug.log
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── __.eslintrc.cjs
├── eslint.config.js
├── jsconfig.json
├── openapi.json
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── src
├── app.css
├── app.d.ts
├── app.html
├── components
│ ├── CategoryQuestions
│ │ ├── MenuQuestion.svelte
│ │ ├── OptionsModal.svelte
│ │ ├── Questions.svelte
│ │ └── TextQuestion.svelte
│ ├── DataModal.svelte
│ ├── ErrorBox.svelte
│ ├── ErrorPage.svelte
│ ├── ImportModal.svelte
│ ├── Required.svelte
│ ├── ResetModal.svelte
│ ├── Spinner.svelte
│ ├── TagInputs.svelte
│ ├── TopBar.svelte
│ ├── Tree.svelte
│ ├── WelcomeModal.svelte
│ └── state.svelte.js
├── hooks.client.js
├── hooks.server.js
├── lib
│ ├── i18n.js
│ ├── locales
│ │ └── en-GB
│ │ │ ├── _common.json
│ │ │ └── misc.json
│ ├── timezones.json
│ └── util
│ │ └── data.js
└── routes
│ ├── (default)
│ ├── +error.svelte
│ ├── +layout.svelte
│ ├── +page.js
│ ├── +page.svelte
│ ├── [guild]
│ │ ├── +layout.js
│ │ ├── +layout.svelte
│ │ ├── +page.js
│ │ ├── +page.svelte
│ │ ├── feedback
│ │ │ └── +page.svelte
│ │ ├── staff
│ │ │ └── +page.svelte
│ │ └── tickets
│ │ │ └── +page.svelte
│ ├── invite
│ │ └── +page.js
│ ├── login
│ │ ├── +page.js
│ │ └── +page.svelte
│ └── view
│ │ └── [ticket]
│ │ └── +page.svelte
│ ├── +layout.server.js
│ ├── +layout.svelte
│ └── settings
│ ├── +error.svelte
│ ├── +layout.svelte
│ ├── +page.js
│ ├── +page.svelte
│ └── [guild]
│ ├── +layout.js
│ ├── +layout.svelte
│ ├── +page.js
│ ├── +page@settings.svelte
│ ├── categories
│ ├── +page.js
│ ├── +page.svelte
│ └── [category]
│ │ ├── +page.js
│ │ └── +page.svelte
│ ├── feedback
│ └── +page.svelte
│ ├── general
│ ├── +page.js
│ └── +page.svelte
│ ├── panels
│ ├── +page.js
│ └── +page.svelte
│ └── tags
│ ├── +page.js
│ └── +page.svelte
├── static
├── assets
│ ├── topgg-dark.webp
│ ├── topgg-light.webp
│ ├── undraw_reviews.svg
│ ├── wordmark-dark.png
│ └── wordmark-light.png
└── favicon.png
├── svelte.config.js
├── swagger.json
├── tailwind.config.js
├── tsconfig.json
└── vite.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Lint, test, and build
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - main
9 | env:
10 | PUBLIC_HOST: http://localhost
11 | jobs:
12 | lint:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-node@v2
17 | with:
18 | node-version: '18'
19 | - name: Cache pnpm modules
20 | uses: actions/cache@v2
21 | env:
22 | cache-name: cache-pnpm-modules
23 | with:
24 | path: ~/.pnpm-store
25 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-${{ hashFiles('**/package.json') }}
26 | restore-keys: |
27 | ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-
28 | - uses: pnpm/action-setup@v2.0.1
29 | with:
30 | version: 7.9.0
31 | run_install: false
32 | - run: pnpm install
33 | - run: pnpm run lint
34 | - run: pnpm run build
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.pnpm-debug.log:
--------------------------------------------------------------------------------
1 | {
2 | "0 debug pnpm:scope": {
3 | "selected": 1
4 | },
5 | "1 error pnpm": {
6 | "code": "ERR_PNPM_INIT_ARG",
7 | "hint": "Maybe you wanted to run \"pnpm create svelte\"",
8 | "err": {
9 | "name": "pnpm",
10 | "message": "init command does not accept any arguments",
11 | "code": "ERR_PNPM_INIT_ARG",
12 | "stack": "pnpm: init command does not accept any arguments\n at Object.handler [as init] (/home/isaac/.nvm/versions/node/v17.6.0/pnpm-global/5/node_modules/.pnpm/pnpm@7.1.2/node_modules/pnpm/dist/pnpm.cjs:173945:15)\n at /home/isaac/.nvm/versions/node/v17.6.0/pnpm-global/5/node_modules/.pnpm/pnpm@7.1.2/node_modules/pnpm/dist/pnpm.cjs:176459:51\n at async run (/home/isaac/.nvm/versions/node/v17.6.0/pnpm-global/5/node_modules/.pnpm/pnpm@7.1.2/node_modules/pnpm/dist/pnpm.cjs:176435:34)\n at async runPnpm (/home/isaac/.nvm/versions/node/v17.6.0/pnpm-global/5/node_modules/.pnpm/pnpm@7.1.2/node_modules/pnpm/dist/pnpm.cjs:176653:5)\n at async /home/isaac/.nvm/versions/node/v17.6.0/pnpm-global/5/node_modules/.pnpm/pnpm@7.1.2/node_modules/pnpm/dist/pnpm.cjs:176645:7"
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Package Managers
2 | package-lock.json
3 | pnpm-lock.yaml
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
7 | "overrides": [
8 | {
9 | "files": "*.svelte",
10 | "options": {
11 | "parser": "svelte"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": ["cooldown", "skyra", "uuidv"]
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Discord Tickets Portal
2 |
3 | A web app for interacting with the [Discord Tickets](https://discordtickets.app) bot via the API.
4 |
5 | > [!NOTE]
6 | >
7 | > This is bundled with the bot; you don't need to download and install it separately.
8 |
9 | ## Screenshot
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/__.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
4 | parserOptions: {
5 | sourceType: 'module',
6 | ecmaVersion: 2020,
7 | extraFileExtensions: ['.svelte'],
8 | tsconfigRootDir: __dirname
9 | },
10 | env: {
11 | browser: true,
12 | es2017: true,
13 | node: true
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import prettier from 'eslint-config-prettier';
2 | import js from '@eslint/js';
3 | import svelte from 'eslint-plugin-svelte';
4 | import globals from 'globals';
5 |
6 | /** @type {import('eslint').Linter.Config[]} */
7 | export default [
8 | js.configs.recommended,
9 | ...svelte.configs['flat/recommended'],
10 | prettier,
11 | ...svelte.configs['flat/prettier'],
12 | {
13 | languageOptions: {
14 | globals: {
15 | ...globals.browser,
16 | ...globals.node
17 | }
18 | }
19 | },
20 | {
21 | ignores: ['build/', '.svelte-kit/', 'dist/']
22 | }
23 | ];
24 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
15 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
16 | //
17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18 | // from the referenced tsconfig.json - TypeScript does not merge them in
19 | }
20 |
--------------------------------------------------------------------------------
/openapi.json:
--------------------------------------------------------------------------------
1 | {
2 | "openapi": "3.0.0",
3 | "info": {
4 | "title": "Discord Tickets API",
5 | "description": "This is the schema for the API that you can use to interact with your ticket bot. It is used by the Archives Portal and the Settings Panel websites.\nIf you are using a managed ticket bot your API will be available at `https://hosting.discordtickets.app:{port}`. Create a ticket if you don't know what port your bot is on.\n# Error codes\n- `0x191`: Unauthorised\n- `0x193`: Forbidden",
6 | "version": "4.0.0"
7 | },
8 | "servers": [
9 | {
10 | "url": "https://virtserver.swaggerhub.com/eartharoid/discord-tickets/4.0.0",
11 | "description": "[TEST] SwaggerHub API Auto Mocking"
12 | },
13 | {
14 | "url": "http://localhost:{port}/api",
15 | "description": "[DEV] Local development API server",
16 | "variables": {
17 | "port": {
18 | "default": "8080"
19 | }
20 | }
21 | },
22 | {
23 | "url": "{host}/api",
24 | "description": "[PROD] Production API server",
25 | "variables": {
26 | "host": {
27 | "default": "http://localhost:8080"
28 | }
29 | }
30 | }
31 | ],
32 | "security": [
33 | {
34 | "api_key": []
35 | }
36 | ],
37 | "paths": {
38 | "/admin/guilds": {
39 | "get": {
40 | "tags": ["admin"],
41 | "summary": "List the guilds that the user has permission to manage",
42 | "description": "This route returns an array of guilds that the user can manage.",
43 | "responses": {
44 | "200": {
45 | "description": "OK",
46 | "content": {
47 | "application/json": {
48 | "schema": {
49 | "type": "array",
50 | "items": {
51 | "$ref": "#/components/schemas/Guild"
52 | }
53 | }
54 | }
55 | }
56 | }
57 | }
58 | }
59 | },
60 | "/admin/guilds/{guild}": {
61 | "get": {
62 | "tags": ["admin"],
63 | "summary": "Get a guild's settings",
64 | "description": "This route returns the guild's current settings.",
65 | "parameters": [
66 | {
67 | "name": "guild",
68 | "in": "path",
69 | "description": "The guild to get the settings of",
70 | "required": true,
71 | "style": "simple",
72 | "explode": false,
73 | "schema": {
74 | "type": "string"
75 | }
76 | }
77 | ],
78 | "responses": {
79 | "200": {
80 | "description": "OK",
81 | "content": {
82 | "application/json": {
83 | "schema": {
84 | "$ref": "#/components/schemas/GuildSettings"
85 | }
86 | }
87 | }
88 | }
89 | }
90 | },
91 | "put": {
92 | "tags": ["admin"],
93 | "summary": "Set a guild's settings",
94 | "description": "This route sets the guild's settings.",
95 | "parameters": [
96 | {
97 | "name": "guild",
98 | "in": "path",
99 | "description": "The guild to update the settings of",
100 | "required": true,
101 | "style": "simple",
102 | "explode": false,
103 | "schema": {
104 | "type": "string"
105 | }
106 | }
107 | ],
108 | "responses": {
109 | "200": {
110 | "description": "OK",
111 | "content": {
112 | "application/json": {
113 | "schema": {
114 | "$ref": "#/components/schemas/GuildSettings"
115 | }
116 | }
117 | }
118 | },
119 | "401": {
120 | "description": "Unauthorised",
121 | "content": {
122 | "application/json": {
123 | "schema": {
124 | "$ref": "#/components/schemas/Error"
125 | }
126 | }
127 | }
128 | },
129 | "403": {
130 | "description": "Forbidden",
131 | "content": {
132 | "application/json": {
133 | "schema": {
134 | "$ref": "#/components/schemas/Error"
135 | }
136 | }
137 | }
138 | }
139 | }
140 | }
141 | },
142 | "/archives/guilds": {
143 | "get": {
144 | "tags": ["user"],
145 | "summary": "List the guilds that the client and the user have in common",
146 | "description": "This route returns an array of guilds that the client and user have in common and it is therefore possible for the user to have a ticket in.",
147 | "responses": {
148 | "200": {
149 | "description": "OK",
150 | "content": {
151 | "application/json": {
152 | "schema": {
153 | "type": "array",
154 | "items": {
155 | "$ref": "#/components/schemas/Guild"
156 | }
157 | }
158 | }
159 | }
160 | }
161 | }
162 | }
163 | }
164 | },
165 | "components": {
166 | "schemas": {
167 | "Error": {
168 | "type": "object",
169 | "properties": {
170 | "code": {
171 | "type": "number"
172 | },
173 | "message": {
174 | "type": "string"
175 | }
176 | },
177 | "example": {
178 | "code": 1,
179 | "message": "Unauthorised"
180 | }
181 | },
182 | "Guild": {
183 | "type": "object",
184 | "properties": {
185 | "id": {
186 | "type": "string"
187 | },
188 | "logo": {
189 | "type": "string"
190 | },
191 | "name": {
192 | "type": "string"
193 | }
194 | },
195 | "example": {
196 | "id": "451745464480432129",
197 | "logo": "https://cdn.discordapp.com/icons/451745464480432129/c340066806e27569c1c6b2bbd8ab28f1.png",
198 | "name": "Planet Earth"
199 | }
200 | },
201 | "GuildSettings": {
202 | "type": "object",
203 | "properties": {
204 | "archive": {
205 | "type": "boolean"
206 | },
207 | "errorColour": {
208 | "type": "string"
209 | },
210 | "primaryColour": {
211 | "type": "string"
212 | },
213 | "successColour": {
214 | "type": "string"
215 | }
216 | },
217 | "example": {
218 | "archive": true,
219 | "errorColour": "RED",
220 | "primaryColour": "#009999",
221 | "successColour": "GREEN"
222 | }
223 | }
224 | },
225 | "securitySchemes": {
226 | "api_key": {
227 | "type": "apiKey",
228 | "name": "Authorization",
229 | "in": "header"
230 | }
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@discord-tickets/settings",
3 | "private": false,
4 | "version": "2.5.4",
5 | "main": "build/index.js",
6 | "type": "module",
7 | "files": [
8 | "build/"
9 | ],
10 | "scripts": {
11 | "dev": "vite dev",
12 | "build": "vite build",
13 | "prepack": "npm run build",
14 | "preview": "vite preview",
15 | "lint": "prettier --check --plugin-search-dir=. . && eslint .",
16 | "format": "prettier --write --plugin-search-dir=. ."
17 | },
18 | "devDependencies": {
19 | "@eartharoid/i18n": "2.0.0-alpha.1",
20 | "@eartharoid/vite-plugin-i18n": "1.0.0-alpha.1",
21 | "@fortawesome/fontawesome-free": "^6.6.0",
22 | "@skyra/discord-components-core": "^3.6.1",
23 | "@sveltejs/adapter-node": "^5.2.9",
24 | "@sveltejs/kit": "^2.17.1",
25 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
26 | "@tailwindcss/forms": "^0.5.9",
27 | "@tailwindcss/typography": "^0.5.15",
28 | "autoprefixer": "^10.4.20",
29 | "big-integer": "^1.6.52",
30 | "clsx": "^2.1.1",
31 | "cookie": "^0.5.0",
32 | "emoji-name-map": "^1.2.9",
33 | "eslint": "^8.57.1",
34 | "eslint-config-prettier": "^8.10.0",
35 | "eslint-plugin-svelte": "^2.46.0",
36 | "jszip": "^3.10.1",
37 | "marked": "^4.3.0",
38 | "ms": "^2.1.3",
39 | "negotiator": "^0.6.4",
40 | "postcss": "^8.4.49",
41 | "prettier": "^3.4.2",
42 | "prettier-plugin-svelte": "^3.3.3",
43 | "prettier-plugin-tailwindcss": "^0.6.11",
44 | "sortablejs": "^1.15.3",
45 | "svelte": "^5.19.9",
46 | "svelte-modals": "^2.0.0",
47 | "svelte-multiselect": "11.0.0-rc.1",
48 | "svelte-preprocess": "^6.0.3",
49 | "svelte-toasts": "^1.1.2",
50 | "tailwind-merge": "^2.5.4",
51 | "tailwind-variants": "^0.2.1",
52 | "tailwindcss": "^3.4.15",
53 | "typescript": "^5.6.3",
54 | "uuid": "^9.0.1",
55 | "vite": "^6.1.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | body {
7 | background: #2f3136;
8 | scroll-behavior: smooth;
9 | }
10 | }
11 |
12 | @layer components {
13 | .link {
14 | @apply transition duration-300 hover:bg-blurple hover:text-white dark:hover:bg-blurple dark:hover:text-white;
15 | }
16 |
17 | .input {
18 | /* focus: bg-white */
19 | @apply my-1 block w-full rounded-md border-transparent bg-gray-100 font-normal shadow-sm transition-colors placeholder:text-gray-500 focus:ring-2 focus:ring-blurple disabled:cursor-not-allowed dark:bg-slate-800 placeholder:dark:text-slate-400;
20 | }
21 |
22 | .form-checkbox {
23 | @apply m-2 cursor-pointer rounded bg-gray-100 p-3 text-blurple checked:bg-blurple focus:ring-blurple disabled:cursor-not-allowed dark:bg-slate-800 dark:checked:bg-blurple;
24 | }
25 |
26 | select option:checked,
27 | select option:checked i {
28 | @apply bg-blurple text-white;
29 | }
30 |
31 | .dragged {
32 | @apply bg-blurple/10 dark:bg-blurple/10;
33 | }
34 |
35 | .marked {
36 | @apply inline-block;
37 | }
38 | }
39 |
40 | /*
41 | ? MODALS
42 | */
43 |
44 | .backdrop {
45 | position: fixed;
46 | top: 0;
47 | bottom: 0;
48 | right: 0;
49 | left: 0;
50 | background: rgba(0, 0, 0, 0.5);
51 | }
52 |
53 | .modal {
54 | position: fixed;
55 | top: 0;
56 | bottom: 0;
57 | right: 0;
58 | left: 0;
59 | display: flex;
60 | justify-content: center;
61 | align-items: center;
62 | /* allow click-through to backdrop */
63 | pointer-events: none;
64 | }
65 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://svelte.dev/docs/kit/types#app.d.ts
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 | }
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sveltekit.head%
9 |
10 |
11 | %sveltekit.body%
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/CategoryQuestions/MenuQuestion.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
36 |
37 |
38 |
53 |
54 |
55 |
71 |
72 |
73 |
74 | Options ({question.options.length}/25)
75 |
76 |
80 |
88 |
89 |
90 |
91 | {#each question.options as option}
92 | - {option.label}
93 | {/each}
94 |
95 |
96 |
97 |
98 |
99 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/src/components/CategoryQuestions/OptionsModal.svelte:
--------------------------------------------------------------------------------
1 |
223 |
--------------------------------------------------------------------------------
/src/components/CategoryQuestions/Questions.svelte:
--------------------------------------------------------------------------------
1 |
58 |
59 |
60 | {#each qS.questions as q, i}
61 |
62 |
63 |
64 |
66 |
67 |
68 | {q.label}
69 |
82 |
94 |
95 |
96 | {#if expanded === q.id}
97 |
98 |
99 |
100 |
128 |
129 |
130 | {#if q.type === 'TEXT'}
131 |
132 | {:else if q.type === 'MENU'}
133 |
134 | {/if}
135 |
136 |
137 | {/if}
138 |
139 |
140 | {/each}
141 |
142 |
--------------------------------------------------------------------------------
/src/components/CategoryQuestions/TextQuestion.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
22 |
23 |
24 |
39 |
40 |
41 |
56 |
57 |
58 |
71 |
72 |
73 |
87 |
88 |
89 |
106 |
107 |
108 |
117 |
118 |
--------------------------------------------------------------------------------
/src/components/DataModal.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 | {#if isOpen}
14 |
19 |
22 |
23 |
24 |
Server data
25 |
26 | Manage your server data
27 |
28 |
29 |
30 |
75 |
76 |
77 |
83 |
84 |
85 |
86 |
87 | {/if}
88 |
--------------------------------------------------------------------------------
/src/components/ErrorBox.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | Sorry, something went wrong.
14 |
15 |
16 |
17 | Error
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/ErrorPage.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 | Sorry, something went wrong.
16 |
17 |
18 | Your request failed with HTTP status
19 | {$page.status}.
20 |
21 |
22 |
23 |
24 | URL:
25 | {$page.url}
26 |
27 |
28 | Route:
29 | {$page.route.id}
30 |
31 |
32 |
33 |
34 | Error
35 |
36 |
37 |
38 | {#if $page.params && Object.keys($page.params).length > 0}
39 |
40 |
41 | Parameters
42 |
43 |
44 |
45 | {/if}
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/ImportModal.svelte:
--------------------------------------------------------------------------------
1 |
82 |
83 | {#snippet warning(message)}
84 |
87 |
88 |
89 |
90 | {message}
91 |
92 |
93 |
94 | {/snippet}
95 |
96 | {#if isOpen}
97 |
277 | {/if}
278 |
--------------------------------------------------------------------------------
/src/components/Required.svelte:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/ResetModal.svelte:
--------------------------------------------------------------------------------
1 |
23 |
24 | {#snippet warning(message)}
25 |
28 |
29 |
30 |
31 | {message}
32 |
33 |
34 |
35 | {/snippet}
36 |
37 | {#if isOpen}
38 |
95 | {/if}
96 |
--------------------------------------------------------------------------------
/src/components/Spinner.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
81 |
--------------------------------------------------------------------------------
/src/components/TagInputs.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
19 |
20 |
21 |
29 |
30 |
31 |
41 | {#if tag.content}
42 |
Preview
43 |
46 | {@html marked.parse(
47 | tag.content
48 | .replace(/\n/g, '\n\n')
49 | .replace(/{+\s?(user)?name\s?}+/gi, '@' + getContext('user').username)
50 | )}
51 |
52 | {/if}
53 |
54 |
--------------------------------------------------------------------------------
/src/components/TopBar.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
26 |
27 |
30 |
35 |
40 | {user.username}
41 |
42 |
43 | {#if theme === 'dark'}
44 | toggle()}
48 | >
49 | {:else}
50 | toggle()}
54 | >
55 | {/if}
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/components/Tree.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {#if entry instanceof Array}
11 | {#each entry as child}
12 |
0} class="font-mono" style="padding-left: {indent}px;">
13 |
14 |
17 |
{child[0]}
18 |
19 |
20 |
21 | {/each}
22 | {:else}
23 |
24 |
25 | {@html marked.parse(entry)}
26 |
27 | {/if}
28 |
29 |
--------------------------------------------------------------------------------
/src/components/WelcomeModal.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 | {#if isOpen}
16 |
167 | {/if}
168 |
--------------------------------------------------------------------------------
/src/components/state.svelte.js:
--------------------------------------------------------------------------------
1 | export const questionsState = $state({
2 | questions: []
3 | });
4 |
5 | export const tagsState = $state({
6 | tags: []
7 | });
8 |
--------------------------------------------------------------------------------
/src/hooks.client.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@sveltejs/kit').Handle} */
2 | export async function handle({ event, resolve }) {
3 | const response = await resolve(event, {
4 | filterSerializedResponseHeaders: () => true
5 | });
6 | return response;
7 | }
8 |
--------------------------------------------------------------------------------
/src/hooks.server.js:
--------------------------------------------------------------------------------
1 | import { dev } from '$app/environment';
2 |
3 | /** @type {import('@sveltejs/kit').Handle} */
4 | export async function handle({ event, resolve }) {
5 | const response = await resolve(event, {
6 | filterSerializedResponseHeaders: () => true
7 | });
8 | return response;
9 | }
10 |
11 | /** @type {import('@sveltejs/kit').HandleServerError} */
12 | export function handleError({ error, event }) {
13 | const errorId = Date.now().toString(16);
14 | if (dev || process?.env.NODE_ENV === 'development') console.error(error);
15 | process?.emit('sveltekit:error', { error, errorId, event });
16 | return {
17 | name: 'Internal Server Error',
18 | message: error.message,
19 | errorId
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/i18n.js:
--------------------------------------------------------------------------------
1 | export const importJSON = (...modules) => [
2 | modules[0].locale_id,
3 | [].concat(...modules.map((mod) => mod.json))
4 | ];
5 |
6 | export const getSupportedLocales = () => {
7 | const files = Object.keys(import.meta.glob('$lib/locales/**'));
8 | return Array.from(
9 | new Set(
10 | files.map((file) => {
11 | const parts = file.split('/');
12 | return parts[parts.length - 2];
13 | })
14 | )
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/lib/locales/en-GB/_common.json:
--------------------------------------------------------------------------------
1 | {
2 | "language": "Language",
3 | "logout": "Log out",
4 | "settings_panel": "Settings Panel",
5 | "staff_dashboard": "Staff Dashboard",
6 | "theme": "Theme",
7 | "title": "{guild} - {client} Portal"
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/locales/en-GB/misc.json:
--------------------------------------------------------------------------------
1 | {
2 | "login_title": "Log in - {username} Portal",
3 | "please_login": "Log in to the Portal to view this page",
4 | "select_server": "Select a server",
5 | "select_server_title": "{username} Portal",
6 | "continue_with_discord": "Continue with Discord"
7 | }
8 |
--------------------------------------------------------------------------------
/src/lib/timezones.json:
--------------------------------------------------------------------------------
1 | [
2 | "Africa/Abidjan",
3 | "Africa/Accra",
4 | "Africa/Addis_Ababa",
5 | "Africa/Algiers",
6 | "Africa/Asmara",
7 | "Africa/Asmera",
8 | "Africa/Bamako",
9 | "Africa/Bangui",
10 | "Africa/Banjul",
11 | "Africa/Bissau",
12 | "Africa/Blantyre",
13 | "Africa/Brazzaville",
14 | "Africa/Bujumbura",
15 | "Africa/Cairo",
16 | "Africa/Casablanca",
17 | "Africa/Ceuta",
18 | "Africa/Conakry",
19 | "Africa/Dakar",
20 | "Africa/Dar_es_Salaam",
21 | "Africa/Djibouti",
22 | "Africa/Douala",
23 | "Africa/El_Aaiun",
24 | "Africa/Freetown",
25 | "Africa/Gaborone",
26 | "Africa/Harare",
27 | "Africa/Johannesburg",
28 | "Africa/Juba",
29 | "Africa/Kampala",
30 | "Africa/Khartoum",
31 | "Africa/Kigali",
32 | "Africa/Kinshasa",
33 | "Africa/Lagos",
34 | "Africa/Libreville",
35 | "Africa/Lome",
36 | "Africa/Luanda",
37 | "Africa/Lubumbashi",
38 | "Africa/Lusaka",
39 | "Africa/Malabo",
40 | "Africa/Maputo",
41 | "Africa/Maseru",
42 | "Africa/Mbabane",
43 | "Africa/Mogadishu",
44 | "Africa/Monrovia",
45 | "Africa/Nairobi",
46 | "Africa/Ndjamena",
47 | "Africa/Niamey",
48 | "Africa/Nouakchott",
49 | "Africa/Ouagadougou",
50 | "Africa/Porto-Novo",
51 | "Africa/Sao_Tome",
52 | "Africa/Timbuktu",
53 | "Africa/Tripoli",
54 | "Africa/Tunis",
55 | "Africa/Windhoek",
56 | "America/Adak",
57 | "America/Anchorage",
58 | "America/Anguilla",
59 | "America/Antigua",
60 | "America/Araguaina",
61 | "America/Argentina/Buenos_Aires",
62 | "America/Argentina/Catamarca",
63 | "America/Argentina/ComodRivadavia",
64 | "America/Argentina/Cordoba",
65 | "America/Argentina/Jujuy",
66 | "America/Argentina/La_Rioja",
67 | "America/Argentina/Mendoza",
68 | "America/Argentina/Rio_Gallegos",
69 | "America/Argentina/Salta",
70 | "America/Argentina/San_Juan",
71 | "America/Argentina/San_Luis",
72 | "America/Argentina/Tucuman",
73 | "America/Argentina/Ushuaia",
74 | "America/Aruba",
75 | "America/Asuncion",
76 | "America/Atikokan",
77 | "America/Atka",
78 | "America/Bahia",
79 | "America/Bahia_Banderas",
80 | "America/Barbados",
81 | "America/Belem",
82 | "America/Belize",
83 | "America/Blanc-Sablon",
84 | "America/Boa_Vista",
85 | "America/Bogota",
86 | "America/Boise",
87 | "America/Buenos_Aires",
88 | "America/Cambridge_Bay",
89 | "America/Campo_Grande",
90 | "America/Cancun",
91 | "America/Caracas",
92 | "America/Catamarca",
93 | "America/Cayenne",
94 | "America/Cayman",
95 | "America/Chicago",
96 | "America/Chihuahua",
97 | "America/Coral_Harbour",
98 | "America/Cordoba",
99 | "America/Costa_Rica",
100 | "America/Creston",
101 | "America/Cuiaba",
102 | "America/Curacao",
103 | "America/Danmarkshavn",
104 | "America/Dawson",
105 | "America/Dawson_Creek",
106 | "America/Denver",
107 | "America/Detroit",
108 | "America/Dominica",
109 | "America/Edmonton",
110 | "America/Eirunepe",
111 | "America/El_Salvador",
112 | "America/Ensenada",
113 | "America/Fort_Nelson",
114 | "America/Fort_Wayne",
115 | "America/Fortaleza",
116 | "America/Glace_Bay",
117 | "America/Godthab",
118 | "America/Goose_Bay",
119 | "America/Grand_Turk",
120 | "America/Grenada",
121 | "America/Guadeloupe",
122 | "America/Guatemala",
123 | "America/Guayaquil",
124 | "America/Guyana",
125 | "America/Halifax",
126 | "America/Havana",
127 | "America/Hermosillo",
128 | "America/Indiana/Indianapolis",
129 | "America/Indiana/Knox",
130 | "America/Indiana/Marengo",
131 | "America/Indiana/Petersburg",
132 | "America/Indiana/Tell_City",
133 | "America/Indiana/Vevay",
134 | "America/Indiana/Vincennes",
135 | "America/Indiana/Winamac",
136 | "America/Indianapolis",
137 | "America/Inuvik",
138 | "America/Iqaluit",
139 | "America/Jamaica",
140 | "America/Jujuy",
141 | "America/Juneau",
142 | "America/Kentucky/Louisville",
143 | "America/Kentucky/Monticello",
144 | "America/Knox_IN",
145 | "America/Kralendijk",
146 | "America/La_Paz",
147 | "America/Lima",
148 | "America/Los_Angeles",
149 | "America/Louisville",
150 | "America/Lower_Princes",
151 | "America/Maceio",
152 | "America/Managua",
153 | "America/Manaus",
154 | "America/Marigot",
155 | "America/Martinique",
156 | "America/Matamoros",
157 | "America/Mazatlan",
158 | "America/Mendoza",
159 | "America/Menominee",
160 | "America/Merida",
161 | "America/Metlakatla",
162 | "America/Mexico_City",
163 | "America/Miquelon",
164 | "America/Moncton",
165 | "America/Monterrey",
166 | "America/Montevideo",
167 | "America/Montreal",
168 | "America/Montserrat",
169 | "America/Nassau",
170 | "America/New_York",
171 | "America/Nipigon",
172 | "America/Nome",
173 | "America/Noronha",
174 | "America/North_Dakota/Beulah",
175 | "America/North_Dakota/Center",
176 | "America/North_Dakota/New_Salem",
177 | "America/Nuuk",
178 | "America/Ojinaga",
179 | "America/Panama",
180 | "America/Pangnirtung",
181 | "America/Paramaribo",
182 | "America/Phoenix",
183 | "America/Port-au-Prince",
184 | "America/Port_of_Spain",
185 | "America/Porto_Acre",
186 | "America/Porto_Velho",
187 | "America/Puerto_Rico",
188 | "America/Punta_Arenas",
189 | "America/Rainy_River",
190 | "America/Rankin_Inlet",
191 | "America/Recife",
192 | "America/Regina",
193 | "America/Resolute",
194 | "America/Rio_Branco",
195 | "America/Rosario",
196 | "America/Santa_Isabel",
197 | "America/Santarem",
198 | "America/Santiago",
199 | "America/Santo_Domingo",
200 | "America/Sao_Paulo",
201 | "America/Scoresbysund",
202 | "America/Shiprock",
203 | "America/Sitka",
204 | "America/St_Barthelemy",
205 | "America/St_Johns",
206 | "America/St_Kitts",
207 | "America/St_Lucia",
208 | "America/St_Thomas",
209 | "America/St_Vincent",
210 | "America/Swift_Current",
211 | "America/Tegucigalpa",
212 | "America/Thule",
213 | "America/Thunder_Bay",
214 | "America/Tijuana",
215 | "America/Toronto",
216 | "America/Tortola",
217 | "America/Vancouver",
218 | "America/Virgin",
219 | "America/Whitehorse",
220 | "America/Winnipeg",
221 | "America/Yakutat",
222 | "America/Yellowknife",
223 | "Antarctica/Casey",
224 | "Antarctica/Davis",
225 | "Antarctica/DumontDUrville",
226 | "Antarctica/Macquarie",
227 | "Antarctica/Mawson",
228 | "Antarctica/McMurdo",
229 | "Antarctica/Palmer",
230 | "Antarctica/Rothera",
231 | "Antarctica/South_Pole",
232 | "Antarctica/Syowa",
233 | "Antarctica/Troll",
234 | "Antarctica/Vostok",
235 | "Arctic/Longyearbyen",
236 | "Asia/Aden",
237 | "Asia/Almaty",
238 | "Asia/Amman",
239 | "Asia/Anadyr",
240 | "Asia/Aqtau",
241 | "Asia/Aqtobe",
242 | "Asia/Ashgabat",
243 | "Asia/Ashkhabad",
244 | "Asia/Atyrau",
245 | "Asia/Baghdad",
246 | "Asia/Bahrain",
247 | "Asia/Baku",
248 | "Asia/Bangkok",
249 | "Asia/Barnaul",
250 | "Asia/Beirut",
251 | "Asia/Bishkek",
252 | "Asia/Brunei",
253 | "Asia/Calcutta",
254 | "Asia/Chita",
255 | "Asia/Choibalsan",
256 | "Asia/Chongqing",
257 | "Asia/Chungking",
258 | "Asia/Colombo",
259 | "Asia/Dacca",
260 | "Asia/Damascus",
261 | "Asia/Dhaka",
262 | "Asia/Dili",
263 | "Asia/Dubai",
264 | "Asia/Dushanbe",
265 | "Asia/Famagusta",
266 | "Asia/Gaza",
267 | "Asia/Harbin",
268 | "Asia/Hebron",
269 | "Asia/Ho_Chi_Minh",
270 | "Asia/Hong_Kong",
271 | "Asia/Hovd",
272 | "Asia/Irkutsk",
273 | "Asia/Istanbul",
274 | "Asia/Jakarta",
275 | "Asia/Jayapura",
276 | "Asia/Jerusalem",
277 | "Asia/Kabul",
278 | "Asia/Kamchatka",
279 | "Asia/Karachi",
280 | "Asia/Kashgar",
281 | "Asia/Kathmandu",
282 | "Asia/Katmandu",
283 | "Asia/Khandyga",
284 | "Asia/Kolkata",
285 | "Asia/Krasnoyarsk",
286 | "Asia/Kuala_Lumpur",
287 | "Asia/Kuching",
288 | "Asia/Kuwait",
289 | "Asia/Macao",
290 | "Asia/Macau",
291 | "Asia/Magadan",
292 | "Asia/Makassar",
293 | "Asia/Manila",
294 | "Asia/Muscat",
295 | "Asia/Nicosia",
296 | "Asia/Novokuznetsk",
297 | "Asia/Novosibirsk",
298 | "Asia/Omsk",
299 | "Asia/Oral",
300 | "Asia/Phnom_Penh",
301 | "Asia/Pontianak",
302 | "Asia/Pyongyang",
303 | "Asia/Qatar",
304 | "Asia/Qostanay",
305 | "Asia/Qyzylorda",
306 | "Asia/Rangoon",
307 | "Asia/Riyadh",
308 | "Asia/Saigon",
309 | "Asia/Sakhalin",
310 | "Asia/Samarkand",
311 | "Asia/Seoul",
312 | "Asia/Shanghai",
313 | "Asia/Singapore",
314 | "Asia/Srednekolymsk",
315 | "Asia/Taipei",
316 | "Asia/Tashkent",
317 | "Asia/Tbilisi",
318 | "Asia/Tehran",
319 | "Asia/Tel_Aviv",
320 | "Asia/Thimbu",
321 | "Asia/Thimphu",
322 | "Asia/Tokyo",
323 | "Asia/Tomsk",
324 | "Asia/Ujung_Pandang",
325 | "Asia/Ulaanbaatar",
326 | "Asia/Ulan_Bator",
327 | "Asia/Urumqi",
328 | "Asia/Ust-Nera",
329 | "Asia/Vientiane",
330 | "Asia/Vladivostok",
331 | "Asia/Yakutsk",
332 | "Asia/Yangon",
333 | "Asia/Yekaterinburg",
334 | "Asia/Yerevan",
335 | "Atlantic/Azores",
336 | "Atlantic/Bermuda",
337 | "Atlantic/Canary",
338 | "Atlantic/Cape_Verde",
339 | "Atlantic/Faeroe",
340 | "Atlantic/Faroe",
341 | "Atlantic/Jan_Mayen",
342 | "Atlantic/Madeira",
343 | "Atlantic/Reykjavik",
344 | "Atlantic/South_Georgia",
345 | "Atlantic/St_Helena",
346 | "Atlantic/Stanley",
347 | "Australia/ACT",
348 | "Australia/Adelaide",
349 | "Australia/Brisbane",
350 | "Australia/Broken_Hill",
351 | "Australia/Canberra",
352 | "Australia/Currie",
353 | "Australia/Darwin",
354 | "Australia/Eucla",
355 | "Australia/Hobart",
356 | "Australia/LHI",
357 | "Australia/Lindeman",
358 | "Australia/Lord_Howe",
359 | "Australia/Melbourne",
360 | "Australia/NSW",
361 | "Australia/North",
362 | "Australia/Perth",
363 | "Australia/Queensland",
364 | "Australia/South",
365 | "Australia/Sydney",
366 | "Australia/Tasmania",
367 | "Australia/Victoria",
368 | "Australia/West",
369 | "Australia/Yancowinna",
370 | "Brazil/Acre",
371 | "Brazil/DeNoronha",
372 | "Brazil/East",
373 | "Brazil/West",
374 | "CEST",
375 | "CET",
376 | "CST6CDT",
377 | "Canada/Atlantic",
378 | "Canada/Central",
379 | "Canada/Eastern",
380 | "Canada/Mountain",
381 | "Canada/Newfoundland",
382 | "Canada/Pacific",
383 | "Canada/Saskatchewan",
384 | "Canada/Yukon",
385 | "Chile/Continental",
386 | "Chile/EasterIsland",
387 | "Cuba",
388 | "EDT",
389 | "EET",
390 | "EST",
391 | "EST5EDT",
392 | "Egypt",
393 | "Eire",
394 | "Etc/GMT",
395 | "Etc/GMT+0",
396 | "Etc/GMT+1",
397 | "Etc/GMT+10",
398 | "Etc/GMT+11",
399 | "Etc/GMT+12",
400 | "Etc/GMT+2",
401 | "Etc/GMT+3",
402 | "Etc/GMT+4",
403 | "Etc/GMT+5",
404 | "Etc/GMT+6",
405 | "Etc/GMT+7",
406 | "Etc/GMT+8",
407 | "Etc/GMT+9",
408 | "Etc/GMT-0",
409 | "Etc/GMT-1",
410 | "Etc/GMT-10",
411 | "Etc/GMT-11",
412 | "Etc/GMT-12",
413 | "Etc/GMT-13",
414 | "Etc/GMT-14",
415 | "Etc/GMT-2",
416 | "Etc/GMT-3",
417 | "Etc/GMT-4",
418 | "Etc/GMT-5",
419 | "Etc/GMT-6",
420 | "Etc/GMT-7",
421 | "Etc/GMT-8",
422 | "Etc/GMT-9",
423 | "Etc/GMT0",
424 | "Etc/Greenwich",
425 | "Etc/UCT",
426 | "Etc/UTC",
427 | "Etc/Universal",
428 | "Etc/Zulu",
429 | "Europe/Amsterdam",
430 | "Europe/Andorra",
431 | "Europe/Astrakhan",
432 | "Europe/Athens",
433 | "Europe/Belfast",
434 | "Europe/Belgrade",
435 | "Europe/Berlin",
436 | "Europe/Bratislava",
437 | "Europe/Brussels",
438 | "Europe/Bucharest",
439 | "Europe/Budapest",
440 | "Europe/Busingen",
441 | "Europe/Chisinau",
442 | "Europe/Copenhagen",
443 | "Europe/Dublin",
444 | "Europe/Gibraltar",
445 | "Europe/Guernsey",
446 | "Europe/Helsinki",
447 | "Europe/Isle_of_Man",
448 | "Europe/Istanbul",
449 | "Europe/Jersey",
450 | "Europe/Kaliningrad",
451 | "Europe/Kiev",
452 | "Europe/Kirov",
453 | "Europe/Lisbon",
454 | "Europe/Ljubljana",
455 | "Europe/London",
456 | "Europe/Luxembourg",
457 | "Europe/Madrid",
458 | "Europe/Malta",
459 | "Europe/Mariehamn",
460 | "Europe/Minsk",
461 | "Europe/Monaco",
462 | "Europe/Moscow",
463 | "Europe/Nicosia",
464 | "Europe/Oslo",
465 | "Europe/Paris",
466 | "Europe/Podgorica",
467 | "Europe/Prague",
468 | "Europe/Riga",
469 | "Europe/Rome",
470 | "Europe/Samara",
471 | "Europe/San_Marino",
472 | "Europe/Sarajevo",
473 | "Europe/Saratov",
474 | "Europe/Simferopol",
475 | "Europe/Skopje",
476 | "Europe/Sofia",
477 | "Europe/Stockholm",
478 | "Europe/Tallinn",
479 | "Europe/Tirane",
480 | "Europe/Tiraspol",
481 | "Europe/Ulyanovsk",
482 | "Europe/Uzhgorod",
483 | "Europe/Vaduz",
484 | "Europe/Vatican",
485 | "Europe/Vienna",
486 | "Europe/Vilnius",
487 | "Europe/Volgograd",
488 | "Europe/Warsaw",
489 | "Europe/Zagreb",
490 | "Europe/Zaporozhye",
491 | "Europe/Zurich",
492 | "GB",
493 | "GB-Eire",
494 | "GMT",
495 | "GMT+0",
496 | "GMT-0",
497 | "GMT0",
498 | "Greenwich",
499 | "HST",
500 | "Hongkong",
501 | "Iceland",
502 | "Indian/Antananarivo",
503 | "Indian/Chagos",
504 | "Indian/Christmas",
505 | "Indian/Cocos",
506 | "Indian/Comoro",
507 | "Indian/Kerguelen",
508 | "Indian/Mahe",
509 | "Indian/Maldives",
510 | "Indian/Mauritius",
511 | "Indian/Mayotte",
512 | "Indian/Reunion",
513 | "Iran",
514 | "Israel",
515 | "Jamaica",
516 | "Japan",
517 | "Kwajalein",
518 | "Libya",
519 | "MET",
520 | "MST",
521 | "MST7MDT",
522 | "Mexico/BajaNorte",
523 | "Mexico/BajaSur",
524 | "Mexico/General",
525 | "NZ",
526 | "NZ-CHAT",
527 | "Navajo",
528 | "PRC",
529 | "PST",
530 | "PDT",
531 | "PST8PDT",
532 | "Pacific/Apia",
533 | "Pacific/Auckland",
534 | "Pacific/Bougainville",
535 | "Pacific/Chatham",
536 | "Pacific/Chuuk",
537 | "Pacific/Easter",
538 | "Pacific/Efate",
539 | "Pacific/Enderbury",
540 | "Pacific/Fakaofo",
541 | "Pacific/Fiji",
542 | "Pacific/Funafuti",
543 | "Pacific/Galapagos",
544 | "Pacific/Gambier",
545 | "Pacific/Guadalcanal",
546 | "Pacific/Guam",
547 | "Pacific/Honolulu",
548 | "Pacific/Johnston",
549 | "Pacific/Kiritimati",
550 | "Pacific/Kosrae",
551 | "Pacific/Kwajalein",
552 | "Pacific/Majuro",
553 | "Pacific/Marquesas",
554 | "Pacific/Midway",
555 | "Pacific/Nauru",
556 | "Pacific/Niue",
557 | "Pacific/Norfolk",
558 | "Pacific/Noumea",
559 | "Pacific/Pago_Pago",
560 | "Pacific/Palau",
561 | "Pacific/Pitcairn",
562 | "Pacific/Pohnpei",
563 | "Pacific/Ponape",
564 | "Pacific/Port_Moresby",
565 | "Pacific/Rarotonga",
566 | "Pacific/Saipan",
567 | "Pacific/Samoa",
568 | "Pacific/Tahiti",
569 | "Pacific/Tarawa",
570 | "Pacific/Tongatapu",
571 | "Pacific/Truk",
572 | "Pacific/Wake",
573 | "Pacific/Wallis",
574 | "Pacific/Yap",
575 | "Poland",
576 | "Portugal",
577 | "ROC",
578 | "ROK",
579 | "Singapore",
580 | "Turkey",
581 | "UCT",
582 | "US/Alaska",
583 | "US/Aleutian",
584 | "US/Arizona",
585 | "US/Central",
586 | "US/East-Indiana",
587 | "US/Eastern",
588 | "US/Hawaii",
589 | "US/Indiana-Starke",
590 | "US/Michigan",
591 | "US/Mountain",
592 | "US/Pacific",
593 | "US/Samoa",
594 | "UTC",
595 | "Universal",
596 | "W-SU",
597 | "WET",
598 | "Zulu"
599 | ]
600 |
--------------------------------------------------------------------------------
/src/lib/util/data.js:
--------------------------------------------------------------------------------
1 | export function flatten(object) {
2 | // specifically instance of Error, not API responses which may have other properties
3 | object = object instanceof Error ? { message: object.message } : object;
4 | const entries = [];
5 | for (let [k, v] of Object.entries(object)) {
6 | if (typeof v === 'string') {
7 | try {
8 | let j = JSON.parse(v);
9 | if (typeof j === 'object') v = flatten(j);
10 | else v = String(j);
11 | } catch {
12 | /* empty */
13 | }
14 | } else if (typeof v === 'object') {
15 | v = flatten(v);
16 | } else {
17 | v = v.toString();
18 | }
19 | entries.push([k, v]);
20 | }
21 | return entries;
22 | }
23 |
--------------------------------------------------------------------------------
/src/routes/(default)/+error.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/routes/(default)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
20 |
28 | {#if $navigating || !mounted}
29 |
30 |
31 |
32 | {:else}
33 | {@render children?.()}
34 | {/if}
35 |
36 |
--------------------------------------------------------------------------------
/src/routes/(default)/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | // import { importJSON } from '@eartharoid/vite-plugin-i18n'; // doesn't work?
3 | import { importJSON } from '$lib/i18n';
4 |
5 | /** @type {import('./$types').PageLoad} */
6 | export async function load({ parent, fetch }) {
7 | // TODO: remove this when the portal section is more complete
8 | redirect(302, '/settings');
9 |
10 | const { locale } = await parent();
11 | const guilds = await (await fetch(`/api/guilds`)).json();
12 | if (guilds.length === 0) {
13 | redirect(302, '/settings');
14 | } else if (guilds.length === 1) {
15 | redirect(302, `/${guilds[0].id}`);
16 | }
17 | return {
18 | translations: importJSON(
19 | await import(`../../lib/locales/${locale}/_common.json`),
20 | await import(`../../lib/locales/${locale}/misc.json`)
21 | ),
22 | guilds
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/routes/(default)/+page.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | {t('select_server_title', { username: client.username })}
12 |
13 |
14 |
15 |
18 |
19 |
20 | {t('select_server')}
21 |
22 |
41 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/routes/(default)/[guild]/+layout.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 | import Big from 'big-integer';
3 |
4 | /** @type {import('./$types').PageLoad} */
5 | export async function load({ fetch, params }) {
6 | if (params.guild.split('.')[0] === 'favicon') error(404, 'Not Found');
7 | const guildId = new Big(params.guild, 36);
8 | const response = await fetch(`/api/guilds/${guildId}`);
9 | const body = await response.json();
10 | if (!response.ok) error(response.status, JSON.stringify(body));
11 | return {
12 | guild: body
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/src/routes/(default)/[guild]/+layout.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {@render children?.()}
14 |
15 |
--------------------------------------------------------------------------------
/src/routes/(default)/[guild]/+page.js:
--------------------------------------------------------------------------------
1 | import { importJSON } from '$lib/i18n';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ parent }) {
5 | const { locale } = await parent();
6 | return {
7 | translations: importJSON(await import(`../../../lib/locales/${locale}/_common.json`))
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/routes/(default)/[guild]/+page.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | {t('common:title', { guild: guild.name, client: client.username })}
13 |
14 |
15 |
16 | {#if guild.privilegeLevel > 0}
17 |
18 |
47 | {/if}
48 |
todo
49 |
50 |
--------------------------------------------------------------------------------
/src/routes/(default)/[guild]/feedback/+page.svelte:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/src/routes/(default)/[guild]/feedback/+page.svelte
--------------------------------------------------------------------------------
/src/routes/(default)/[guild]/staff/+page.svelte:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/src/routes/(default)/[guild]/staff/+page.svelte
--------------------------------------------------------------------------------
/src/routes/(default)/[guild]/tickets/+page.svelte:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/src/routes/(default)/[guild]/tickets/+page.svelte
--------------------------------------------------------------------------------
/src/routes/(default)/invite/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ url }) {
5 | redirect(307, `/auth/login?invite&guild=${url.searchParams.get('guild') || ''}`);
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/src/routes/(default)/login/+page.js:
--------------------------------------------------------------------------------
1 | import { importJSON } from '$lib/i18n';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ parent, url }) {
5 | const { locale } = await parent();
6 | return {
7 | translations: importJSON(
8 | await import(`../../../lib/locales/${locale}/_common.json`),
9 | await import(`../../../lib/locales/${locale}/misc.json`)
10 | ),
11 | query: url.search
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/src/routes/(default)/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 | {t('login_title', { username: client.username })}
15 |
16 |
17 |
18 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |

31 |
{client.username}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
52 |
55 |
56 |
57 |
58 |
59 |
60 | {t('common:language')}
61 |
62 |
63 |
64 |
65 |
66 |
67 | {t('common:theme')}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/routes/(default)/view/[ticket]/+page.svelte:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/src/routes/(default)/view/[ticket]/+page.svelte
--------------------------------------------------------------------------------
/src/routes/+layout.server.js:
--------------------------------------------------------------------------------
1 | import { error, redirect } from '@sveltejs/kit';
2 | import Negotiator from 'negotiator';
3 | import { getSupportedLocales } from '$lib/i18n';
4 | import ms from 'ms';
5 |
6 | /** @type {import('./$types').LayoutServerLoad} */
7 | export async function load({ cookies, fetch, request, url }) {
8 | if (url.pathname === '/invite') {
9 | redirect(307, `/auth/login?invite&guild=${url.searchParams.get('guild') || ''}`)
10 | }
11 | const response = await fetch(`/api/users/@me`);
12 | const isJSON = response.headers.get('Content-Type')?.includes('json');
13 | const body = isJSON ? await response.json() : await response.text();
14 | if (url.pathname !== '/login') {
15 | if (response.status === 401) {
16 | let qs = `r=${encodeURIComponent(url.pathname + url.search)}`;
17 | if (url.pathname.startsWith('/settings')) {
18 | qs += '&role=admin';
19 | }
20 | redirect(307, `/login?${qs}`);
21 | } else if (!response.ok) {
22 | error(response.status, isJSON ? JSON.stringify(body) : body);
23 | }
24 | }
25 | let locale = cookies.get('locale');
26 | if (!locale) {
27 | const supportedLocales = getSupportedLocales();
28 | if (supportedLocales.includes(body.locale)) {
29 | locale = body.locale;
30 | } else {
31 | const negotiator = new Negotiator(request);
32 | locale = negotiator.language(supportedLocales);
33 | }
34 | cookies.set('locale', locale, {
35 | maxAge: ms('1y') / 1000,
36 | path: '/',
37 | sameSite: 'lax',
38 | secure: false,
39 | httpOnly: false
40 | });
41 | }
42 | return {
43 | client: await (await fetch(`/api/client`, { credentials: 'include' })).json(),
44 | locale,
45 | theme: cookies.get('theme'),
46 | user: body
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 | {@render children?.()}
33 |
34 |
--------------------------------------------------------------------------------
/src/routes/settings/+error.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/routes/settings/+layout.svelte:
--------------------------------------------------------------------------------
1 |
49 |
50 |
51 | Discord Tickets
52 |
53 |
54 |
55 |
56 |
57 | {#snippet backdrop({ close })}
58 | close()}
62 | onkeypress={() => close()}
63 | >
64 | {/snippet}
65 | {#snippet loading()}
66 |
67 |
68 |
69 | {/snippet}
70 |
71 | {#if mounted && client.public && !cookies.dismissedCookies}
72 |
75 |
Cookies are being used to store credentials and preferences.
76 |
77 |
83 |
84 |
85 | {/if}
86 |
87 | {#if $navigating || !mounted}
88 |
89 |
90 |
91 | {:else}
92 |
93 |
94 |
95 | {@render children?.()}
96 |
163 |
164 |
165 | {/if}
166 |
167 |
168 |
--------------------------------------------------------------------------------
/src/routes/settings/+page.js:
--------------------------------------------------------------------------------
1 | /** @type {import('./$types').PageLoad} */
2 | export async function load({ fetch }) {
3 | const fetchOptions = { credentials: 'include' };
4 | return {
5 | guilds: await (await fetch(`/api/admin/guilds`, fetchOptions)).json()
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/src/routes/settings/+page.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 | {#if good.length === 0}
27 |
28 |
Add your bot to a guild to get started
29 |
30 | {:else}
31 |
32 |
Manage your guilds
33 |
34 | {#each good as guild}
35 |
36 |
39 |

40 |
{guild.name}
41 |
42 |
43 | {/each}
44 | {#if bad.length > 0}
45 |
46 | {/if}
47 | {/if}
48 |
49 |
50 | {#if good.length > 0}
51 |
52 |
Add your bot to more guilds
53 |
54 | {/if}
55 |
56 |
77 |
78 |
79 |
80 |
81 |
84 |

85 |
86 | {client.username}#{client.discriminator}
89 |
90 |
91 |
92 |
93 |
Activated users
94 |
{formatter.format(client.stats.activatedUsers)}
95 |
96 |
97 |
Archived messages
98 |
{formatter.format(client.stats.archivedMessages)}
99 |
100 |
101 |
Resolution time
102 |
{client.stats.avgResolutionTime}
103 |
104 |
105 |
Response time
106 |
{client.stats.avgResponseTime}
107 |
108 |
109 |
Categories
110 |
{formatter.format(client.stats.categories)}
111 |
112 |
113 |
Guilds
114 |
{formatter.format(client.stats.guilds)}
115 |
116 |
126 |
127 |
Members (avg)
128 |
129 | {formatter.format(client.stats.members)}
130 | ({formatter.format(Math.floor(client.stats.members / client.stats.guilds))})
131 |
132 |
133 |
134 |
Tags
135 |
{formatter.format(client.stats.tags)}
136 |
137 |
138 |
Tickets
139 |
{formatter.format(client.stats.tickets)}
140 |
141 |
142 |
143 |
144 |
145 |
146 |
173 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/+layout.js:
--------------------------------------------------------------------------------
1 | import { error, redirect } from '@sveltejs/kit';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ fetch, params, url }) {
5 | const response = await fetch(`/api/admin/guilds/${params.guild}`);
6 | const isJSON = response.headers.get('Content-Type')?.includes('json');
7 | const body = isJSON ? await response.json() : await response.text();
8 | if (response.status === 401 && body.elevate) {
9 | redirect(307, `/auth/login?r=${encodeURIComponent(url.pathname + url.search)}&role=${body.elevate}`);
10 | } else if (!response.ok) {
11 | error(response.status, isJSON ? JSON.stringify(body) : body);
12 | } else {
13 | return { guild: body };
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/+layout.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
17 |
18 | {@render children?.()}
19 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/+page.js:
--------------------------------------------------------------------------------
1 | import { error, redirect } from '@sveltejs/kit';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ fetch, params, url }) {
5 | const fetchOptions = { credentials: 'include' };
6 | const response = await fetch(`/api/admin/guilds/${params.guild}`, fetchOptions);
7 | const isJSON = response.headers.get('Content-Type')?.includes('json');
8 | const body = isJSON ? await response.json() : await response.text();
9 | if (response.status === 401 && body.elevate) {
10 | redirect(307, `/auth/login?r=${encodeURIComponent(url.pathname + url.search)}&role=${body.elevate}`);
11 | } else if (!response.ok) {
12 | error(response.status, isJSON ? JSON.stringify(body) : body);
13 | } else {
14 | return {
15 | guild: body,
16 | problems: await (
17 | await fetch(`/api/admin/guilds/${params.guild}/problems`, fetchOptions)
18 | ).json()
19 | };
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/+page@settings.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 | {#snippet botPublic()}
24 | WARNING:
25 | This bot is public; anyone can add it to their servers. Is this a mistake? Learn more at
26 | https://lnk.earth/dt-warn-pub.
31 | {/snippet}
32 |
33 | {#snippet logChannelMissingPermission(p)}
34 | Please give the bot {p.permission} permission in the log channel.
35 | {/snippet}
36 |
37 |
38 |
39 | {#each problems as p}
40 |
41 |
44 |
45 |
46 |
47 | {@render problemSnippets[p.id]?.(p)}
48 |
49 |
50 |
51 |
52 | {/each}
53 | {#if guild.stats.categories.length === 0}
54 |
67 | {/if}
68 |
112 |
113 |
114 |
115 |
118 |

119 |
120 |
121 | {guild.name}
122 |
123 |
124 |
125 |
126 | Added on
127 | {createdAt}
128 |
129 |
130 |
131 |
132 |
133 |
Resolution time
134 |
{guild.stats.avgResolutionTime}
135 |
136 |
137 |
Response time
138 |
{guild.stats.avgResponseTime}
139 |
140 |
141 |
Categories
142 |
{guild.stats.categories.length}
143 |
144 |
145 |
Tags
146 |
{guild.stats.tags}
147 |
148 |
149 |
Tickets
150 |
{formatter.format(guild.stats.tickets)}
151 |
152 |
153 |
Most used category
154 |
155 | {guild.stats.categories.sort((a, b) => b.tickets - a.tickets)[0]?.name ?? 'None'}
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/categories/+page.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ fetch, params }) {
5 | const response = await fetch(`/api/admin/guilds/${params.guild}/categories`);
6 | const isJSON = response.headers.get('Content-Type')?.includes('json');
7 | const body = isJSON ? await response.json() : await response.text();
8 | if (!response.ok) {
9 | error(response.status, isJSON ? JSON.stringify(body) : body);
10 | } else {
11 | return { categories: body };
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/categories/+page.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | Categories
11 |
68 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/categories/[category]/+page.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ fetch, params }) {
5 | const fetchOptions = { credentials: 'include' };
6 | let body;
7 | if (params.category === 'new') {
8 | body = {
9 | channelName: '',
10 | claiming: false,
11 | description: '',
12 | discordCategory: 'new',
13 | enableFeedback: false,
14 | emoji: '',
15 | image: '',
16 | memberLimit: 1,
17 | name: '',
18 | openingMessage: '',
19 | pingRoles: [],
20 | questions: [],
21 | ratelimit: null,
22 | requiredRoles: [],
23 | requireTopic: false,
24 | staffRoles: [],
25 | totalLimit: 50
26 | };
27 | } else {
28 | const response = await fetch(
29 | `/api/admin/guilds/${params.guild}/categories/${params.category}`,
30 | fetchOptions
31 | );
32 | const isJSON = response.headers.get('Content-Type')?.includes('json');
33 | body = isJSON ? await response.json() : await response.text();
34 | if (!response.ok) {
35 | error(response.status, isJSON ? JSON.stringify(body) : body);
36 | }
37 | }
38 |
39 | let url = `/api/admin/guilds/${params.guild}/categories`;
40 | if (params.category !== 'new') url += `/${params.category}`;
41 |
42 | return {
43 | url,
44 | category: body,
45 | channels: await (
46 | await fetch(`/api/admin/guilds/${params.guild}/data?query=channels.cache`, fetchOptions)
47 | ).json(),
48 | roles: await (
49 | await fetch(`/api/admin/guilds/${params.guild}/data?query=roles.cache`, fetchOptions)
50 | ).json(),
51 | settings: await (await fetch(`/api/admin/guilds/${params.guild}/settings`, fetchOptions)).json()
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/categories/[category]/+page.svelte:
--------------------------------------------------------------------------------
1 |
171 |
172 |
173 |
174 |
175 | Read the documentation
179 | to avoid problems.
180 |
181 |
182 | Categories
183 |
184 | {emoji.get(category.emoji) ?? ''}
185 | {category.name || 'New category'}
186 |
187 |
188 | {#if error}
189 |
190 | {/if}
191 |
680 |
681 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/feedback/+page.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | Feedback
7 |
8 |
9 |

16 |
17 |
26 |
27 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/general/+page.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 | /** @type {import('./$types').PageLoad} */
3 | export async function load({ fetch, params }) {
4 | const response = await fetch(`/api/admin/guilds/${params.guild}/settings`);
5 | const isJSON = response.headers.get('Content-Type')?.includes('json');
6 | const body = isJSON ? await response.json() : await response.text();
7 | if (!response.ok) {
8 | error(response.status, isJSON ? JSON.stringify(body) : body);
9 | } else {
10 | return {
11 | settings: body,
12 | channels: await (
13 | await fetch(`/api/admin/guilds/${params.guild}/data?query=channels.cache`)
14 | ).json(),
15 | locales: await (await fetch(`/api/locales`)).json(),
16 | roles: await (await fetch(`/api/admin/guilds/${params.guild}/data?query=roles.cache`)).json()
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/general/+page.svelte:
--------------------------------------------------------------------------------
1 |
97 |
98 | General settings
99 |
100 | {#if error}
101 |
102 | {/if}
103 |
104 |
Warning
105 |
106 | This page is made to be "just about functional".
107 | Read the documentation
111 | to avoid breaking something.
112 |
113 |
114 |
401 |
402 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/panels/+page.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ fetch, params }) {
5 | const fetchOptions = { credentials: 'include' };
6 | const response = await fetch(`/api/admin/guilds/${params.guild}/categories`, fetchOptions);
7 | const isJSON = response.headers.get('Content-Type')?.includes('json');
8 | const body = isJSON ? await response.json() : await response.text();
9 | if (!response.ok) {
10 | error(response.status, isJSON ? JSON.stringify(body) : body);
11 | } else {
12 | return {
13 | categories: body,
14 | channels: await (
15 | await fetch(`/api/admin/guilds/${params.guild}/data?query=channels.cache`, fetchOptions)
16 | ).json()
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/panels/+page.svelte:
--------------------------------------------------------------------------------
1 |
79 |
80 | Create a panel
81 |
82 | {#if error}
83 |
84 | {/if}
85 |
86 |
87 |
88 | {#if panel.channel !== 'new' && panel.type === 'MESSAGE'}
89 |
90 |
91 | Make sure members can read and send messages in
92 | #{getChannelName(panel.channel)}.
93 |
94 | {:else if panel.channel !== 'new' && panel.type !== 'MESSAGE'}
95 |
96 |
97 | Make sure members can read but not send messages in
98 | #{getChannelName(panel.channel)}.
99 |
100 | {/if}
101 |
102 |
229 |
230 |
231 |
232 |
233 |
234 | Looking to edit or remove a panel? Just delete the message or channel in Discord.
235 |
236 |
237 |
238 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/tags/+page.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 |
3 | /** @type {import('./$types').PageLoad} */
4 | export async function load({ fetch, params }) {
5 | const response = await fetch(`/api/admin/guilds/${params.guild}/tags`);
6 | const isJSON = response.headers.get('Content-Type')?.includes('json');
7 | const body = isJSON ? await response.json() : await response.text();
8 | if (!response.ok) {
9 | error(response.status, isJSON ? JSON.stringify(body) : body);
10 | } else {
11 | return {
12 | tags: body
13 | };
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/routes/settings/[guild]/tags/+page.svelte:
--------------------------------------------------------------------------------
1 |
2 |
153 |
154 |
155 |
156 |
157 | Read the documentation
160 | to avoid problems.
161 |
162 |
163 | Tags
164 | {#if error}
165 |
166 | {/if}
167 |
168 |
169 |
170 |
171 | filter(event.target.value)}
177 | />
178 |
179 | {#each shown as tag, i}
180 |
181 |
{tag.name}
182 |
(expanded = expanded === tag.id ? null : tag.id)}
185 | >
186 |
191 | Click to {expanded === tag.id ? 'collapse' : 'expand'}
192 |
193 | {#if expanded === tag.id}
194 |
195 |
198 |
199 |
212 |
224 |
225 |
226 | {/if}
227 |
228 | {/each}
229 |
230 |
231 |
232 |
233 |
Create a tag
234 |
249 |
250 |
251 |
252 |
253 |
254 | {#snippet children({ data: toasted })}
255 |
256 | {/snippet}
257 |
258 |
--------------------------------------------------------------------------------
/static/assets/topgg-dark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/static/assets/topgg-dark.webp
--------------------------------------------------------------------------------
/static/assets/topgg-light.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/static/assets/topgg-light.webp
--------------------------------------------------------------------------------
/static/assets/undraw_reviews.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/assets/wordmark-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/static/assets/wordmark-dark.png
--------------------------------------------------------------------------------
/static/assets/wordmark-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/static/assets/wordmark-light.png
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/discord-tickets/portal/6f8ac9b91b53c56c11db88796a9b1a6ae948cebb/static/favicon.png
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-node';
2 | import { sveltePreprocess } from 'svelte-preprocess';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | kit: {
7 | adapter: adapter({ out: 'build' }),
8 | alias: {
9 | $components: './src/components'
10 | }
11 | },
12 | preprocess: [sveltePreprocess({ postcss: true })],
13 | trailingSlash: 'never'
14 | };
15 |
16 | export default config;
17 |
--------------------------------------------------------------------------------
/swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "openapi": "3.0.0",
3 | "info": {
4 | "version": "4.0.0",
5 | "title": "Discord Tickets API",
6 | "description": "This is the schema for the API that you can use to interact with your ticket bot. It is used by the Archives Portal and the Settings Panel websites.\nIf you are using a managed ticket bot your API will be available at `https://my.discordtickets.app:{port}`. Create a ticket if you don't know what port your bot is on.\n# Error codes\nCheck the error `message` for more specific details.\n- `0x001`: Invalid type\n- `0x002`: String is too short\n- `0x003`: String is too long\n- `0x191`: Unauthorised\n- `0x193`: Forbidden\n- `0x194`: Not found\n- `0x1F4`: Internal server error"
7 | },
8 | "servers": [
9 | {
10 | "description": "[TEST] SwaggerHub API Auto Mocking",
11 | "url": "https://virtserver.swaggerhub.com/eartharoid/discord-tickets/4.0.0"
12 | },
13 | {
14 | "url": "http://localhost:{port}/api",
15 | "description": "[DEV] Local development API server",
16 | "variables": {
17 | "port": {
18 | "default": "8080"
19 | }
20 | }
21 | },
22 | {
23 | "url": "{host}/api",
24 | "description": "[PROD] Production API server",
25 | "variables": {
26 | "host": {
27 | "default": "http://localhost:8080"
28 | }
29 | }
30 | }
31 | ],
32 | "security": [
33 | {
34 | "api_key": []
35 | }
36 | ],
37 | "paths": {
38 | "/admin/guilds": {
39 | "get": {
40 | "tags": ["admin"],
41 | "summary": "List the guilds that the user has permission to manage",
42 | "description": "This route returns an array of guilds that the user can manage.",
43 | "responses": {
44 | "200": {
45 | "description": "OK",
46 | "content": {
47 | "application/json": {
48 | "schema": {
49 | "type": "array",
50 | "items": {
51 | "$ref": "#/components/schemas/Guild"
52 | }
53 | }
54 | }
55 | }
56 | },
57 | "default": {
58 | "$ref": "#/components/responses/default"
59 | }
60 | }
61 | }
62 | },
63 | "/admin/guilds/{guild}": {
64 | "parameters": [
65 | {
66 | "name": "guild",
67 | "in": "path",
68 | "description": "The guild to get or update the settings of",
69 | "required": true,
70 | "style": "simple",
71 | "schema": {
72 | "type": "string"
73 | }
74 | }
75 | ],
76 | "delete": {
77 | "tags": ["admin"],
78 | "summary": "Reset a guild's settings",
79 | "description": "This route resets the guild's settings.",
80 | "responses": {
81 | "200": {
82 | "description": "OK",
83 | "content": {
84 | "application/json": {
85 | "schema": {
86 | "$ref": "#/components/schemas/GuildSettings"
87 | }
88 | }
89 | }
90 | },
91 | "default": {
92 | "$ref": "#/components/responses/default"
93 | }
94 | }
95 | },
96 | "get": {
97 | "tags": ["admin"],
98 | "summary": "Get a guild's settings",
99 | "description": "This route returns the guild's current settings.",
100 | "responses": {
101 | "200": {
102 | "description": "OK",
103 | "content": {
104 | "application/json": {
105 | "schema": {
106 | "$ref": "#/components/schemas/GuildSettings"
107 | }
108 | }
109 | }
110 | },
111 | "default": {
112 | "$ref": "#/components/responses/default"
113 | }
114 | }
115 | },
116 | "put": {
117 | "tags": ["admin"],
118 | "summary": "Set a guild's settings",
119 | "description": "This route sets the guild's settings.",
120 | "requestBody": {
121 | "description": "The full GuildSettings object",
122 | "required": true,
123 | "content": {
124 | "application/json": {
125 | "schema": {
126 | "$ref": "#/components/schemas/GuildSettings"
127 | }
128 | }
129 | }
130 | },
131 | "responses": {
132 | "200": {
133 | "description": "OK",
134 | "content": {
135 | "application/json": {
136 | "schema": {
137 | "$ref": "#/components/schemas/GuildSettings"
138 | }
139 | }
140 | }
141 | },
142 | "default": {
143 | "$ref": "#/components/responses/default"
144 | }
145 | }
146 | }
147 | },
148 | "/admin/guilds/{guild}/data": {
149 | "get": {
150 | "tags": ["admin"],
151 | "summary": "Get properties from the guild data",
152 | "description": "This route returns the requested property from the guild.",
153 | "parameters": [
154 | {
155 | "name": "guild",
156 | "in": "path",
157 | "description": "The guild to reset the settings of",
158 | "required": true,
159 | "style": "simple",
160 | "schema": {
161 | "type": "string"
162 | }
163 | },
164 | {
165 | "name": "query",
166 | "in": "query",
167 | "description": "The dot-notation property to get",
168 | "required": true,
169 | "style": "form",
170 | "schema": {
171 | "type": "string"
172 | }
173 | }
174 | ],
175 | "responses": {
176 | "200": {
177 | "description": "OK",
178 | "content": {
179 | "application/json": {
180 | "schema": {
181 | "type": "object"
182 | },
183 | "example": 5
184 | }
185 | }
186 | },
187 | "default": {
188 | "$ref": "#/components/responses/default"
189 | }
190 | }
191 | }
192 | },
193 | "/admin/guilds/{guild}/categories": {
194 | "get": {
195 | "tags": ["admin"],
196 | "summary": "List the categories of a guild",
197 | "description": "This route returns an array of categories within a guild.",
198 | "parameters": [
199 | {
200 | "name": "guild",
201 | "in": "path",
202 | "description": "The guild to list the categories of",
203 | "required": true,
204 | "style": "simple",
205 | "schema": {
206 | "type": "string"
207 | }
208 | }
209 | ],
210 | "responses": {
211 | "200": {
212 | "description": "OK",
213 | "content": {
214 | "application/json": {
215 | "schema": {
216 | "type": "array",
217 | "items": {
218 | "$ref": "#/components/schemas/Category"
219 | }
220 | }
221 | }
222 | }
223 | },
224 | "default": {
225 | "$ref": "#/components/responses/default"
226 | }
227 | }
228 | },
229 | "post": {
230 | "tags": ["admin"],
231 | "summary": "Create a new category",
232 | "description": "This route creates a new category within a guild.",
233 | "parameters": [
234 | {
235 | "name": "guild",
236 | "in": "path",
237 | "description": "The guild to create a category in",
238 | "required": true,
239 | "style": "simple",
240 | "schema": {
241 | "type": "string"
242 | }
243 | }
244 | ],
245 | "requestBody": {
246 | "description": "The full CategorySettings object",
247 | "required": true,
248 | "content": {
249 | "application/json": {
250 | "schema": {
251 | "type": "object",
252 | "required": ["name", "openingMessage", "staffRoles"],
253 | "properties": {
254 | "discordCategory": {
255 | "type": "string"
256 | },
257 | "name": {
258 | "type": "string"
259 | },
260 | "openingMessage": {
261 | "type": "string"
262 | },
263 | "staffRoles": {
264 | "type": "array",
265 | "items": {
266 | "type": "string"
267 | }
268 | }
269 | },
270 | "example": {
271 | "discordCategory": null,
272 | "name": "Support",
273 | "openingMessage": "Thank you for creating a ticket. Please be patient, a member of the support team will reply when they become available. Whilst you wait, please provide as much information about your support query as possible. ",
274 | "roles": [451745787974778908, 513828182697312267]
275 | }
276 | }
277 | }
278 | }
279 | },
280 | "responses": {
281 | "201": {
282 | "description": "Created",
283 | "content": {
284 | "application/json": {
285 | "schema": {
286 | "$ref": "#/components/schemas/CategorySettings"
287 | }
288 | }
289 | }
290 | },
291 | "default": {
292 | "$ref": "#/components/responses/default"
293 | }
294 | }
295 | }
296 | },
297 | "/admin/guilds/{guild}/categories/{category}": {
298 | "delete": {
299 | "tags": ["admin"],
300 | "summary": "Delete a category",
301 | "description": "This route deletes the category. Tickets within this category will **not** be deleted automatically.",
302 | "parameters": [
303 | {
304 | "name": "guild",
305 | "in": "path",
306 | "description": "The guild the category belongs to",
307 | "required": true,
308 | "style": "simple",
309 | "schema": {
310 | "type": "string"
311 | }
312 | },
313 | {
314 | "name": "category",
315 | "in": "path",
316 | "description": "The category to delete",
317 | "required": true,
318 | "style": "simple",
319 | "schema": {
320 | "type": "string"
321 | }
322 | }
323 | ],
324 | "responses": {
325 | "200": {
326 | "description": "OK",
327 | "content": {
328 | "application/json": {
329 | "schema": {
330 | "type": "object"
331 | }
332 | }
333 | }
334 | },
335 | "default": {
336 | "$ref": "#/components/responses/default"
337 | }
338 | }
339 | },
340 | "get": {
341 | "tags": ["admin"],
342 | "summary": "Get a category's settings",
343 | "description": "This route returns the category's current settings.",
344 | "parameters": [
345 | {
346 | "name": "guild",
347 | "in": "path",
348 | "description": "The guild the category belongs to",
349 | "required": true,
350 | "style": "simple",
351 | "schema": {
352 | "type": "string"
353 | }
354 | },
355 | {
356 | "name": "category",
357 | "in": "path",
358 | "description": "The category to get the settings of",
359 | "required": true,
360 | "style": "simple",
361 | "schema": {
362 | "type": "string"
363 | }
364 | }
365 | ],
366 | "responses": {
367 | "200": {
368 | "description": "OK",
369 | "content": {
370 | "application/json": {
371 | "schema": {
372 | "$ref": "#/components/schemas/CategorySettings"
373 | }
374 | }
375 | }
376 | },
377 | "default": {
378 | "$ref": "#/components/responses/default"
379 | }
380 | }
381 | },
382 | "put": {
383 | "tags": ["admin"],
384 | "summary": "Set a category's settings",
385 | "description": "This route sets the category's settings.",
386 | "parameters": [
387 | {
388 | "name": "guild",
389 | "in": "path",
390 | "description": "The guild the category belongs to",
391 | "required": true,
392 | "style": "simple",
393 | "schema": {
394 | "type": "string"
395 | }
396 | },
397 | {
398 | "name": "category",
399 | "in": "path",
400 | "description": "The category to set the settings of",
401 | "required": true,
402 | "style": "simple",
403 | "schema": {
404 | "type": "string"
405 | }
406 | }
407 | ],
408 | "requestBody": {
409 | "description": "The full CategorySettings object",
410 | "required": true,
411 | "content": {
412 | "application/json": {
413 | "schema": {
414 | "$ref": "#/components/schemas/CategorySettings"
415 | }
416 | }
417 | }
418 | },
419 | "responses": {
420 | "200": {
421 | "description": "OK",
422 | "content": {
423 | "application/json": {
424 | "schema": {
425 | "type": "array",
426 | "items": {
427 | "$ref": "#/components/schemas/CategorySettings"
428 | }
429 | }
430 | }
431 | }
432 | },
433 | "default": {
434 | "$ref": "#/components/responses/default"
435 | }
436 | }
437 | }
438 | },
439 | "/admin/guilds/{guild}/stats": {
440 | "get": {
441 | "tags": ["admin"],
442 | "summary": "Get statistics about this guild",
443 | "description": "This route returns the statistics of the guild.",
444 | "parameters": [
445 | {
446 | "name": "guild",
447 | "in": "path",
448 | "description": "The guild to get the stats of",
449 | "required": true,
450 | "style": "simple",
451 | "schema": {
452 | "type": "string"
453 | }
454 | }
455 | ],
456 | "responses": {
457 | "200": {
458 | "description": "OK",
459 | "content": {
460 | "application/json": {
461 | "schema": {
462 | "$ref": "#/components/schemas/GuildStats"
463 | }
464 | }
465 | }
466 | },
467 | "default": {
468 | "$ref": "#/components/responses/default"
469 | }
470 | }
471 | }
472 | },
473 | "/archives/guilds": {
474 | "get": {
475 | "tags": ["user"],
476 | "summary": "List the guilds that the client and the user have in common",
477 | "description": "This route returns an array of guilds that the client and user have in common and it is therefore possible for the user to have a ticket in.",
478 | "responses": {
479 | "200": {
480 | "description": "OK",
481 | "content": {
482 | "application/json": {
483 | "schema": {
484 | "type": "array",
485 | "items": {
486 | "$ref": "#/components/schemas/Guild"
487 | }
488 | }
489 | }
490 | }
491 | },
492 | "default": {
493 | "$ref": "#/components/responses/default"
494 | }
495 | }
496 | }
497 | },
498 | "/global/stats": {
499 | "get": {
500 | "tags": ["global"],
501 | "summary": "Get the statistics for this instance",
502 | "description": "This **public route** (no authentication required) returns the statistics of the bot instance.",
503 | "responses": {
504 | "200": {
505 | "description": "OK",
506 | "content": {
507 | "application/json": {
508 | "schema": {
509 | "$ref": "#/components/schemas/GlobalStats"
510 | }
511 | }
512 | }
513 | },
514 | "default": {
515 | "$ref": "#/components/responses/default"
516 | }
517 | },
518 | "security": []
519 | }
520 | }
521 | },
522 | "components": {
523 | "schemas": {
524 | "Error": {
525 | "type": "object",
526 | "properties": {
527 | "code": {
528 | "type": "integer"
529 | },
530 | "message": {
531 | "type": "string"
532 | }
533 | },
534 | "example": {
535 | "code": "0x190",
536 | "message": "Bad Request"
537 | }
538 | },
539 | "Category": {
540 | "type": "object",
541 | "properties": {
542 | "id": {
543 | "type": "string"
544 | },
545 | "name": {
546 | "type": "string"
547 | }
548 | },
549 | "example": {
550 | "id": "620272351988285480",
551 | "name": "Support"
552 | }
553 | },
554 | "CategorySettings": {
555 | "type": "object",
556 | "required": [
557 | "channelName",
558 | "claiming",
559 | "discordCategory",
560 | "memberLimit",
561 | "name",
562 | "openingMessage",
563 | "pingRoles",
564 | "requiredRoles",
565 | "requireTopic",
566 | "staffRoles",
567 | "totalLimit"
568 | ],
569 | "properties": {
570 | "channelName": {
571 | "type": "string"
572 | },
573 | "claiming": {
574 | "type": "boolean"
575 | },
576 | "createdAt": {
577 | "type": "string"
578 | },
579 | "description": {
580 | "type": "string"
581 | },
582 | "discordCategory": {
583 | "type": "string"
584 | },
585 | "emoji": {
586 | "type": "string"
587 | },
588 | "enableFeedback": {
589 | "type": "boolean"
590 | },
591 | "image": {
592 | "type": "string"
593 | },
594 | "memberLimit": {
595 | "type": "integer"
596 | },
597 | "name": {
598 | "type": "string"
599 | },
600 | "openingMessage": {
601 | "type": "string"
602 | },
603 | "pingRoles": {
604 | "type": "array",
605 | "items": {
606 | "type": "string"
607 | }
608 | },
609 | "requiredRoles": {
610 | "type": "array",
611 | "items": {
612 | "type": "string"
613 | }
614 | },
615 | "requireTopic": {
616 | "type": "boolean"
617 | },
618 | "staffRoles": {
619 | "type": "array",
620 | "items": {
621 | "type": "string"
622 | }
623 | },
624 | "surveyDescription": {
625 | "type": "string"
626 | },
627 | "surveyLink": {
628 | "type": "string"
629 | },
630 | "surveyTitle": {
631 | "type": "string"
632 | },
633 | "totalLimit": {
634 | "type": "integer"
635 | }
636 | },
637 | "example": {
638 | "channelName": "ticket-{num}",
639 | "claiming": false,
640 | "description": "Get help with anything",
641 | "discordCategory": "874366537727877181",
642 | "emoji": "❓",
643 | "image": "https://static.eartharoid.me/ticket-header.png",
644 | "memberLimit": 1,
645 | "name": "Support",
646 | "openingMessage": "Thank you for creating a ticket. Please be patient, a member of the support team will reply when they become available. Whilst you wait, please provide as much information about your support query as possible. ",
647 | "pingRoles": [513828182697312267],
648 | "requiredRoles": [],
649 | "requireTopic": true,
650 | "staffRoles": [451745787974778908, 513828182697312267],
651 | "surveyDescription": "Click on the button below to give us feedback.",
652 | "surveyLink": "https://forms.google.com",
653 | "surveyTitle": "How did we do?",
654 | "totalLimit": -1
655 | }
656 | },
657 | "GlobalStats": {
658 | "type": "object",
659 | "properties": {
660 | "activatedUsers": {
661 | "type": "integer"
662 | },
663 | "archviedMessages": {
664 | "type": "integer"
665 | },
666 | "avgResponseTime": {
667 | "type": "integer"
668 | },
669 | "guilds": {
670 | "type": "integer"
671 | },
672 | "guildsAvgMembers": {
673 | "type": "integer"
674 | },
675 | "guildsTotalMembers": {
676 | "type": "integer"
677 | },
678 | "tags": {
679 | "type": "integer"
680 | },
681 | "tickets": {
682 | "type": "integer"
683 | }
684 | }
685 | },
686 | "Guild": {
687 | "type": "object",
688 | "properties": {
689 | "id": {
690 | "type": "string"
691 | },
692 | "logo": {
693 | "type": "string"
694 | },
695 | "name": {
696 | "type": "string"
697 | }
698 | },
699 | "example": {
700 | "id": "451745464480432129",
701 | "logo": "https://cdn.discordapp.com/icons/451745464480432129/c340066806e27569c1c6b2bbd8ab28f1.png",
702 | "name": "Planet Earth"
703 | }
704 | },
705 | "GuildStats": {
706 | "type": "object",
707 | "properties": {
708 | "categories": {
709 | "type": "array",
710 | "items": {
711 | "allOf": [
712 | {
713 | "$ref": "#/components/schemas/Category"
714 | },
715 | {
716 | "type": "object",
717 | "properties": {
718 | "tickets": {
719 | "type": "integer"
720 | }
721 | }
722 | }
723 | ]
724 | }
725 | },
726 | "archviedMessages": {
727 | "type": "integer"
728 | },
729 | "avgResponseTime": {
730 | "type": "string"
731 | },
732 | "tags": {
733 | "type": "integer"
734 | },
735 | "tickets": {
736 | "type": "integer"
737 | }
738 | },
739 | "example": {
740 | "categories": [
741 | {
742 | "id": "620272351988285480",
743 | "name": "Support",
744 | "tickets": 47
745 | }
746 | ],
747 | "archivedMessages": 1768,
748 | "avgResponseTime": "1h 7m",
749 | "tags": 14,
750 | "tickets": 47
751 | }
752 | },
753 | "GuildSettings": {
754 | "type": "object",
755 | "required": ["archive", "blocklist", "errorColour", "primaryColour", "successColour"],
756 | "properties": {
757 | "archive": {
758 | "type": "boolean"
759 | },
760 | "blocklist": {
761 | "type": "array",
762 | "items": {
763 | "type": "string"
764 | }
765 | },
766 | "createdAt": {
767 | "type": "string"
768 | },
769 | "errorColour": {
770 | "type": "string"
771 | },
772 | "primaryColour": {
773 | "type": "string"
774 | },
775 | "successColour": {
776 | "type": "string"
777 | }
778 | },
779 | "example": {
780 | "archive": true,
781 | "blocklist": ["587112191950585856"],
782 | "errorColour": "RED",
783 | "primaryColour": "#009999",
784 | "successColour": "GREEN"
785 | }
786 | }
787 | },
788 | "securitySchemes": {
789 | "api_key": {
790 | "type": "apiKey",
791 | "name": "Authorization",
792 | "in": "header"
793 | }
794 | },
795 | "responses": {
796 | "default": {
797 | "description": "Unexpected error",
798 | "content": {
799 | "application/json": {
800 | "schema": {
801 | "$ref": "#/components/schemas/Error"
802 | }
803 | }
804 | }
805 | }
806 | }
807 | }
808 | }
809 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import forms from '@tailwindcss/forms';
2 | import typography from '@tailwindcss/typography';
3 |
4 | /** @type {import('tailwindcss').Config} */
5 | export default {
6 | content: ['./src/**/*.{html,js,svelte,ts}'],
7 |
8 | darkMode: 'class',
9 |
10 | theme: {
11 | extend: {
12 | colors: {
13 | blurple: '#5865F2',
14 | dgrey: {
15 | // brand "black" 23272A
16 | // very dark 1E1F22
17 | // darker 2B2D31
18 | // slightly dark 313338
19 | // not so dark 404249
20 | 950: '#1E1F22',
21 | 900: '#2B2D31',
22 | // 950: '#1E1F23',
23 | // 900: '#202225',
24 | 800: '#2f3136',
25 | 700: '#36393f',
26 | 600: '#4f545c',
27 | 400: '#d4d7dc',
28 | 300: '#e3e5e8',
29 | 200: '#ebedef',
30 | 100: '#f2f3f5'
31 | }
32 | }
33 | }
34 | },
35 |
36 | variants: {},
37 |
38 | plugins: [typography, forms]
39 | };
40 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "checkJs": false,
5 | "verbatimModuleSyntax": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { I18nPlugin } from '@eartharoid/vite-plugin-i18n';
3 |
4 | /** @type {import('vite').UserConfig} */
5 | const config = {
6 | plugins: [
7 | sveltekit(),
8 | I18nPlugin({
9 | id_regex: /((?[a-z0-9-_]+)\/)((_(?[a-z0-9-_]+))|[a-z0-9-_]+)\.[a-z]+/i,
10 | include: 'src/lib/locales/*/*.json'
11 | })
12 | ],
13 | server: {
14 | host: '127.0.0.1',
15 | proxy: {
16 | '/api': {
17 | target: 'http://127.0.0.1',
18 | changeOrigin: true
19 | },
20 | '/attachments': {
21 | target: 'http://127.0.0.1',
22 | changeOrigin: true
23 | },
24 | '/auth': {
25 | target: 'http://127.0.0.1',
26 | changeOrigin: true
27 | },
28 | '/avatars': {
29 | target: 'http://127.0.0.1',
30 | changeOrigin: true
31 | },
32 | '/invite': {
33 | target: 'http://127.0.0.1',
34 | changeOrigin: true
35 | }
36 | }
37 | }
38 | };
39 |
40 | export default config;
41 |
--------------------------------------------------------------------------------