@developer.krd/discord-dashboard 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,101 +1,69 @@
1
1
  # @developer.krd/discord-dashboard
2
2
 
3
- Advanced plug-and-play Discord dashboard package for bot developers.
4
-
5
- - No frontend coding required.
6
- - Built-in Discord OAuth2 login flow.
7
- - Ready-made dashboard UI.
8
- - Guild access control.
9
- - Discord-like server rail with user/guild avatars.
10
- - Invite-on-click flow for guilds where bot is not installed.
11
- - Extensible plugin panels + server actions.
12
- - Flexible Home Builder with editable sections and save actions.
13
- - Fluent designer API for setup/user/guild dashboard separation.
14
- - Fluent `.setupDesign(...)` API for dashboard main colors.
15
- - Home section width presets: `100` (default), `50`, `33`, `20`.
16
- - Built-in Discord utility helpers in actions (`getChannel`, `getRole`, `getGuildRoles`, etc.).
17
- - Overview-first tabs with separate module categories (like pets, permissions, etc.).
18
- - Website autocomplete field types for role/channel selection with filtering.
19
-
20
- ## Install
3
+ An advanced, plug-and-play Discord dashboard package for bot developers.
21
4
 
22
- ```bash
23
- npm install @developer.krd/discord-dashboard
24
- ```
25
-
26
- ## Ways to Create a Dashboard
27
-
28
- You can build your dashboard in multiple styles depending on your project:
5
+ Build a fully functional, beautiful web dashboard for your bot without writing a single line of frontend code. Powered by Express, this package handles the OAuth2 login flow, session management, UI rendering, and API routing out of the box.
29
6
 
30
- 1. **Direct Config (`createDashboard`)**
31
- - Fastest way for simple dashboards.
32
- - Use `home`, `plugins`, and `getOverviewCards` directly.
7
+ ## Features
33
8
 
34
- 2. **Fluent Designer (`createDashboardDesigner`)**
35
- - Best for larger bots and modular structure.
36
- - Use `setupCategory`, `userCategory`, `guildCategory`, and `onHomeAction`.
9
+ - **No Frontend Coding:** Generates a beautiful React/Vue-like UI using pure server-side rendering.
10
+ - **Built-in Auth:** Complete Discord OAuth2 login flow with secure session management.
11
+ - **Guild Access Control:** Automatically filters guilds based on admin/manage server permissions.
12
+ - **Fluent Designer API:** Build your dashboard cleanly using a chainable builder pattern.
13
+ - **Custom CSS Injection:** Fully theme the dashboard to match your bot's branding.
14
+ - **Extensible Plugins:** Create separate plugin panels with actionable buttons and forms.
15
+ - **Rich Form Fields:** Support for text, selects, booleans, and drag-and-drop string lists.
16
+ - **Smart Discord Lookups:** Website autocomplete fields for finding Roles, Channels, and Members.
17
+ - **Discord-like UI:** Familiar server rail with avatars and invite-on-click for missing guilds.
37
18
 
38
- 3. **Discord-like Lifecycle Pages**
39
- - Use `setupPage + onLoad/onSave` (also `onload/onsave`).
40
- - Great when pages need dynamic loading and per-page save handlers.
19
+ ---
41
20
 
42
- 4. **Template-based UI/UX**
43
- - Choose built-in template (`default`, `compact`) with `uiTemplate`.
44
- - Register custom full HTML templates with `uiTemplates` or Designer `addTemplate`.
21
+ ## 📦 Installation
45
22
 
46
- ## Language Support
47
-
48
- This package supports TypeScript, JavaScript (`.js`), and ESM JavaScript (`.mjs`).
49
-
50
- TypeScript / ESM:
51
-
52
- ```ts
53
- import { createDashboard } from "@developer.krd/discord-dashboard";
23
+ ```bash
24
+ npm install @developer.krd/discord-dashboard
54
25
  ```
55
26
 
56
- JavaScript ESM (`.mjs`):
27
+ _(This package supports TypeScript, JavaScript, and ESM out of the box. Node.js >= 18 is required)._
57
28
 
58
- ```js
59
- import { createDashboard } from "@developer.krd/discord-dashboard";
60
- ```
29
+ ---
61
30
 
62
- JavaScript CommonJS (`.js` with `require`):
31
+ ## 🚀 Quick Start (Direct Configuration)
63
32
 
64
- ```js
65
- const { createDashboard } = require("@developer.krd/discord-dashboard");
66
- ```
67
-
68
- ## Quick Start
33
+ The fastest way to get your dashboard running is by instantiating the `DiscordDashboard` class directly.
69
34
 
70
35
  ```ts
71
36
  import express from "express";
72
- import { createDashboard } from "@developer.krd/discord-dashboard";
37
+ import { DiscordDashboard } from "@developer.krd/discord-dashboard";
73
38
 
74
39
  const app = express();
75
40
 
76
- const dashboard = createDashboard({
77
- app,
41
+ const dashboard = new DiscordDashboard({
42
+ app, // Attach to your existing Express app
78
43
  basePath: "/dashboard",
79
- dashboardName: "KRD Bot Control",
44
+ dashboardName: "My Bot Control",
80
45
  botToken: process.env.DISCORD_BOT_TOKEN!,
81
46
  clientId: process.env.DISCORD_CLIENT_ID!,
82
47
  clientSecret: process.env.DISCORD_CLIENT_SECRET!,
83
48
  redirectUri: "http://localhost:3000/dashboard/callback",
84
49
  sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
85
- ownerIds: ["123456789012345678"],
50
+ ownerIds: ["YOUR_DISCORD_USER_ID"],
51
+
86
52
  getOverviewCards: async (context) => [
87
53
  {
88
54
  id: "uptime",
89
55
  title: "Bot Uptime",
90
56
  value: `${Math.floor(process.uptime() / 60)} min`,
91
- subtitle: `Logged in as ${context.user.username}`
57
+ subtitle: `Logged in as ${context.user.username}`,
58
+ intent: "success",
92
59
  },
93
60
  {
94
61
  id: "guilds",
95
- title: "Guilds",
96
- value: context.guilds.length
97
- }
62
+ title: "Manageable Guilds",
63
+ value: context.guilds.length,
64
+ },
98
65
  ],
66
+
99
67
  home: {
100
68
  async getSections(context) {
101
69
  return [
@@ -105,569 +73,213 @@ const dashboard = createDashboard({
105
73
  description: context.selectedGuildId ? "Guild-specific setup" : "User-level setup",
106
74
  fields: [
107
75
  { id: "enabled", label: "Enable Welcome", type: "boolean", value: true },
108
- { id: "channel", label: "Channel ID", type: "text", value: "" },
109
- { id: "message", label: "Message", type: "textarea", value: "Welcome to the server!" }
76
+ { id: "channel", label: "Channel", type: "channel-search" },
77
+ { id: "message", label: "Message", type: "textarea", value: "Welcome to the server!" },
110
78
  ],
111
- actions: [{ id: "saveWelcome", label: "Save", variant: "primary" }]
112
- }
79
+ actions: [{ id: "saveWelcome", label: "Save", variant: "primary" }],
80
+ },
113
81
  ];
114
82
  },
115
83
  actions: {
116
84
  async saveWelcome(context, payload) {
117
- console.log("Saving", context.selectedGuildId, payload.values);
85
+ console.log("Saving data for", context.selectedGuildId, payload.values);
118
86
  return { ok: true, message: "Welcome settings saved", refresh: false };
119
- }
120
- }
121
- },
122
- plugins: [
123
- {
124
- id: "moderation",
125
- name: "Moderation",
126
- description: "Live moderation controls",
127
- async getPanels(context) {
128
- return [
129
- {
130
- id: "status",
131
- title: "Status",
132
- fields: [
133
- { label: "Selected Guild", value: context.selectedGuildId ?? "None" },
134
- { label: "Access", value: "Ready" }
135
- ],
136
- actions: [
137
- { id: "sync", label: "Sync Rules", variant: "primary" }
138
- ]
139
- }
140
- ];
141
87
  },
142
- actions: {
143
- async sync() {
144
- return { ok: true, message: "Moderation rules synced.", refresh: true };
145
- }
146
- }
147
- }
148
- ]
88
+ },
89
+ },
149
90
  });
150
91
 
92
+ // Start your Express server normally
151
93
  app.listen(3000, () => {
152
- console.log("Dashboard: http://localhost:3000/dashboard");
94
+ console.log("Dashboard live at: http://localhost:3000/dashboard");
153
95
  });
154
96
  ```
155
97
 
156
- ## Fluent Dashboard Designer (Recommended)
98
+ ---
99
+
100
+ ## 🎨 Fluent Dashboard Designer (Recommended)
157
101
 
158
- Use a clean modular structure instead of putting all configuration in one place:
102
+ For larger bots, putting all your configuration in one object gets messy. Use the `DashboardDesigner` class to build your dashboard modularly.
159
103
 
160
- - `setup(...)` for important defaults
161
- - `setupDesign(...)` for dashboard colors (`primary`, `bg`, `rail`, `panel`, etc.)
162
- - `setupCategory(...)` for one-time setup UI
163
- - `userCategory(...)` for user dashboard sections
164
- - `guildCategory(...)` for server dashboard sections
165
- - `onHomeAction(...)` for save handlers
104
+ This approach also allows you to easily inject **Custom CSS** and define dashboard colors.
166
105
 
167
106
  ```ts
168
- import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
107
+ import express from "express";
108
+ import { DashboardDesigner } from "@developer.krd/discord-dashboard";
109
+
110
+ const app = express();
169
111
 
170
- const designer = createDashboardDesigner({
112
+ const dashboard = new DashboardDesigner({
171
113
  app,
172
- botToken,
173
- clientId,
174
- clientSecret,
175
- redirectUri,
176
- sessionSecret
114
+ botToken: process.env.DISCORD_BOT_TOKEN!,
115
+ clientId: process.env.DISCORD_CLIENT_ID!,
116
+ clientSecret: process.env.DISCORD_CLIENT_SECRET!,
117
+ redirectUri: "http://localhost:3000/dashboard/callback",
118
+ sessionSecret: process.env.DASHBOARD_SESSION_SECRET!,
177
119
  })
178
- .setup({ ownerIds: ["123"], botInvitePermissions: "8" })
179
- .setupDesign({ primary: "#4f46e5", rail: "#181a20", panel: "#2f3136" })
180
- .userCategory("pets", "Pets", (category) => {
181
- category.section({
182
- id: "pets-user",
183
- title: "User Pets",
184
- width: 50,
185
- fields: [{ id: "petName", label: "Pet Name", type: "text", value: "Luna" }],
186
- actions: [{ id: "saveUserPets", label: "Save", variant: "primary" }]
187
- });
120
+ .setup({
121
+ ownerIds: ["1234567890"],
122
+ botInvitePermissions: "8",
188
123
  })
124
+ // Customize the default color palette
125
+ .setupDesign({
126
+ primary: "#4f46e5",
127
+ rail: "#181a20",
128
+ panel: "#2f3136",
129
+ })
130
+ // Inject your own CSS rules
131
+ .customCss(
132
+ `
133
+ .brand { font-size: 1.2rem; text-transform: uppercase; }
134
+ button.primary { box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4); }
135
+ `,
136
+ )
137
+ // Build a Guild-specific settings category
189
138
  .guildCategory("pets", "Pets", (category) => {
190
139
  category.section({
191
140
  id: "pets-guild",
192
141
  title: "Guild Pets",
193
- fields: [{ id: "petsChannelId", label: "Pets Channel", type: "text", value: "" }],
194
- actions: [{ id: "saveGuildPets", label: "Save", variant: "primary" }]
142
+ fields: [{ id: "petsChannelId", label: "Pets Channel", type: "channel-search" }],
143
+ actions: [{ id: "saveGuildPets", label: "Save", variant: "primary" }],
195
144
  });
196
145
  })
146
+ // Handle the save action
197
147
  .onHomeAction("saveGuildPets", async (context, payload) => {
198
148
  const channelId = String(payload.values.petsChannelId ?? "");
199
149
  const channel = await context.helpers.getChannel(channelId);
200
150
  if (!channel) return { ok: false, message: "Channel not found" };
201
- return { ok: true, message: "Saved", refresh: true };
202
- });
203
-
204
- createDashboard({ ...designer.build() });
205
- ```
206
-
207
- ## Discord-Style Flexible Flow (`onLoad` / `onSave`)
208
-
209
- You can also build pages in a Discord-client-like style:
210
-
211
- ```ts
212
- import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
213
-
214
- const dashboard = createDashboardDesigner({
215
- app,
216
- botToken,
217
- clientId,
218
- clientSecret,
219
- redirectUri,
220
- sessionSecret
221
- })
222
- .setupPage({
223
- id: "profile",
224
- title: "Profile",
225
- scope: "user",
226
- width: 50,
227
- fields: [
228
- { id: "bio", label: "Bio", type: "textarea", value: "" },
229
- { id: "notifications", label: "Notifications", type: "boolean", value: true }
230
- ]
151
+ return { ok: true, message: "Saved successfully!", refresh: true };
231
152
  })
232
- .onLoad("profile", async (ctx, section) => ({
233
- ...section,
234
- description: `Loaded for ${ctx.user.username}`
235
- }))
236
- .onSave("profile", async (ctx, payload) => {
237
- console.log("save profile", ctx.user.id, payload.values);
238
- return { ok: true, message: "Profile saved", refresh: true };
239
- });
240
-
241
- createDashboard({ ...dashboard.build() });
242
- ```
243
-
244
- Notes:
245
-
246
- - `setupPage(...)` creates a page/section with optional fields/actions.
247
- - `onLoad(pageId, handler)` (or lowercase `onload`) runs when that page is resolved.
248
- - `onSave(pageId, handler)` (or lowercase `onsave`) auto-adds a default **Save** action if missing.
153
+ // Automatically instantiates the DiscordDashboard class
154
+ .createDashboard();
249
155
 
250
- ## Designer API Reference
251
-
252
- `createDashboardDesigner(baseOptions)` supports:
253
-
254
- - `setup({...})`: core setup (`ownerIds`, invite permissions/scopes, name/path, `uiTemplate`).
255
- - `setupDesign({...})`: color/theme tokens for built-in templates.
256
- - `setupCategory(id, label, build)`
257
- - `userCategory(id, label, build)`
258
- - `guildCategory(id, label, build)`
259
- - `onHomeAction(actionId, handler)`
260
- - `setupPage({...})`: page-style section definition.
261
- - `onLoad(pageId, handler)` + alias `onload(...)`.
262
- - `onSave(pageId, handler)` + alias `onsave(...)`.
263
- - `addTemplate(templateId, renderer)` + `useTemplate(templateId)`.
264
- - `build()`: returns full `DashboardOptions` for `createDashboard(...)`.
265
-
266
- ## UI Template System (for package developers)
267
-
268
- You can ship multiple UI/UX templates and choose one by id.
269
-
270
- Built-in templates:
271
-
272
- - `default`
273
- - `compact`
274
-
275
- Use built-in compact mode:
276
-
277
- ```ts
278
- createDashboard({
279
- app,
280
- botToken,
281
- clientId,
282
- clientSecret,
283
- redirectUri,
284
- sessionSecret,
285
- uiTemplate: "compact"
286
- });
287
- ```
288
-
289
- Direct `createDashboard(...)` usage:
290
-
291
- ```ts
292
- import { createDashboard } from "@developer.krd/discord-dashboard";
293
-
294
- createDashboard({
295
- app,
296
- botToken,
297
- clientId,
298
- clientSecret,
299
- redirectUri,
300
- sessionSecret,
301
- uiTemplate: "glass",
302
- uiTemplates: {
303
- glass: ({ dashboardName, basePath }) => `
304
- <!doctype html>
305
- <html>
306
- <head><title>${dashboardName}</title></head>
307
- <body>
308
- <h1>${dashboardName}</h1>
309
- <p>Custom template active. Base path: ${basePath}</p>
310
- </body>
311
- </html>
312
- `
313
- }
314
- });
156
+ app.listen(3000, () => console.log("Dashboard ready!"));
315
157
  ```
316
158
 
317
- Designer usage:
318
-
319
- ```ts
320
- const designer = createDashboardDesigner({
321
- app,
322
- botToken,
323
- clientId,
324
- clientSecret,
325
- redirectUri,
326
- sessionSecret
327
- })
328
- .addTemplate("glass", ({ dashboardName }) => `<html><body><h1>${dashboardName}</h1></body></html>`)
329
- .useTemplate("glass");
330
-
331
- createDashboard({ ...designer.build() });
332
- ```
333
-
334
- Template renderer signature:
335
-
336
- - Input: `{ dashboardName, basePath, setupDesign }`
337
- - Output: full HTML string (complete document/template, not only CSS overrides)
338
-
339
- Template files in this package are organized under [src/templates](src/templates).
159
+ ---
340
160
 
341
- ## Built-in Helper Functions
161
+ ## 🧩 Built-in Helper Functions
342
162
 
343
- Available in `context.helpers` inside home/plugin actions:
163
+ Whenever you handle an action (`onHomeAction` or inside a Plugin), you get access to the `context.helpers` object. These automatically use the bot token to fetch data from the Discord API.
344
164
 
345
165
  - `getChannel(channelId)`
346
166
  - `getGuildChannels(guildId)`
347
167
  - `getRole(guildId, roleId)`
348
168
  - `getGuildRoles(guildId)`
349
169
  - `getGuildMember(guildId, userId)`
170
+ - `searchGuildRoles(guildId, query, options)`
171
+ - `searchGuildChannels(guildId, query, options)`
350
172
 
351
- ## Plugin Scopes and Form Actions
173
+ ---
352
174
 
353
- Plugins can now be separated by target dashboard scope:
175
+ ## 🛠️ Plugin Scopes & Forms
354
176
 
355
- - `scope: "user"` shown only on user dashboard
356
- - `scope: "guild"` → shown only on guild dashboard
357
- - `scope: "both"` (default) → shown on both
177
+ Plugins are modular features you can attach to the dashboard. They can be restricted to specific scopes.
358
178
 
359
- Plugin action forms:
179
+ - `scope: "user"` → Shows only on the User Dashboard (when no server is selected).
180
+ - `scope: "guild"` → Shows only on the Guild Dashboard.
181
+ - `scope: "both"` → (Default) Shows everywhere.
360
182
 
361
- - Mark a panel field with `editable: true` and set `type` (text, textarea, select, boolean, role-search, channel-search, member-search, url).
362
- - Use `type: "string-list"` for drag-and-drop ordered labels (great for poll buttons).
363
- - Set action `collectFields: true` to send panel values in the action payload.
364
- - Action body format:
365
- - `panelId`
366
- - `values` (contains selected lookup objects and typed field values)
367
-
368
- ## Lookup Field Types (Website UI)
369
-
370
- Use these in home sections to let users type and select Discord objects:
371
-
372
- - `role-search`: type role name, get matching roles, select one, submit role object data.
373
- - `channel-search`: type channel name, get matching channels with filters, select one, submit channel object data.
374
-
375
- Lookup filters:
376
-
377
- - `limit`
378
- - `minQueryLength`
379
- - `includeManaged` (roles)
380
- - `nsfw` (channels)
381
- - `channelTypes` (channels)
382
-
383
- ## Required Discord OAuth2 Setup
384
-
385
- 1. Open the Discord Developer Portal for your application.
386
- 2. In OAuth2 settings, add your redirect URI (example: `http://localhost:3000/dashboard/callback`).
387
- 3. Make sure scopes include at least `identify guilds`.
388
- 4. Use your app `CLIENT_ID` and `CLIENT_SECRET` in `createDashboard()`.
389
-
390
- ## API Reference
391
-
392
- ### `createDashboard(options)`
393
-
394
- Creates and mounts a complete dashboard with OAuth2 + UI.
395
-
396
- Key `options`:
397
-
398
- - `app?`: existing Express app instance.
399
- - `basePath?`: dashboard route prefix. Default: `/dashboard`.
400
- - `dashboardName?`: topbar/dashboard title.
401
- - `setupDesign?`: override CSS theme variables (`primary`, `bg`, `rail`, `panel`, etc.).
402
- - `uiTemplate?`: template id to render (`default`, `compact`, or your custom id).
403
- - `uiTemplates?`: custom full-HTML template renderers.
404
- - `botToken`: bot token (for your own plugin logic).
405
- - `clientId`, `clientSecret`, `redirectUri`: Discord OAuth2 credentials.
406
- - `sessionSecret`: secret for encrypted session cookies.
407
- - `sessionName?`, `sessionMaxAgeMs?`: cookie/session configuration.
408
- - `scopes?`: OAuth scopes for login (default includes `identify guilds`).
409
- - `botInvitePermissions?`, `botInviteScopes?`: controls invite link generation.
410
- - `ownerIds?`: optional allow-list of Discord user IDs.
411
- - `guildFilter?`: async filter for guild visibility.
412
- - `getOverviewCards?`: dynamic card resolver.
413
- - `home?`: configurable homepage sections + save handlers.
414
- - `home.getOverviewSections?`: add overview-scoped sections.
415
- - `home.getCategories?`: define category tabs (setup/user/guild).
416
- - `home.getSections?`: define sections for active scope.
417
- - `home.actions?`: action handlers for section actions.
418
- - `plugins?`: plugin definitions with panels/actions.
419
- - `trustProxy?`: proxy configuration when running behind reverse proxies.
420
- - `host?`, `port?`: only used when you call `dashboard.start()` without passing `app`.
421
-
422
- Return value:
423
-
424
- - `app`: Express app instance.
425
- - `start()`: starts internal server only if you didn’t pass `app`.
426
- - `stop()`: stops internal server.
427
-
428
- ## Security Notes
429
-
430
- - Uses secure HTTP-only session cookies.
431
- - Uses OAuth2 `state` validation for callback integrity.
432
- - Use HTTPS in production and strong `sessionSecret`.
433
- - Optionally set trusted proxy with `trustProxy`.
434
-
435
- ## Local Development
436
-
437
- ```bash
438
- npm install
439
- npm run typecheck
440
- npm run build
441
- ```
442
-
443
- Run the example app:
444
-
445
- ```bash
446
- cp .env.example .env
447
- # Fill .env with your Discord app credentials first
448
- npm run example
449
- ```
450
-
451
- Run the real-bot entry directly:
452
-
453
- ```bash
454
- npm run real-bot
455
- ```
456
-
457
- ## Real Bot Code Examples
458
-
459
- The real-bot sample is in:
460
-
461
- - [examples/real-bot/main.ts](examples/real-bot/main.ts)
462
- - [examples/real-bot/dashboard.ts](examples/real-bot/dashboard.ts)
463
- - [examples/real-bot/commands](examples/real-bot/commands)
464
- - [examples/real-bot/events](examples/real-bot/events)
465
-
466
- ### Slash command example (`/ping`)
183
+ ### Example: Ticket Panel Builder Plugin
467
184
 
468
185
  ```ts
469
- import { SlashCommandBuilder } from "discord.js";
470
- import type { BotCommand } from "../types";
471
-
472
- const command: BotCommand = {
473
- data: new SlashCommandBuilder().setName("ping").setDescription("Check latency"),
474
- async execute(context, interaction) {
475
- const wsPing = context.client.ws.ping;
476
- const start = interaction.createdTimestamp;
477
- const response = await interaction.reply({ content: "Pinging...", fetchReply: true });
478
- const apiPing = response.createdTimestamp - start;
479
- await interaction.editReply(`🏓 Pong! WS: ${wsPing}ms | API: ${apiPing}ms`);
480
- }
481
- };
186
+ import { DiscordDashboard } from "@developer.krd/discord-dashboard";
482
187
 
483
- export default command;
188
+ const dashboard = new DiscordDashboard({
189
+ // ... core credentials ...
190
+ plugins: [
191
+ {
192
+ id: "tickets",
193
+ name: "Tickets",
194
+ scope: "guild",
195
+ getPanels: async () => [
196
+ {
197
+ id: "ticket-panel",
198
+ title: "Ticket Panel Generator",
199
+ fields: [
200
+ { id: "targetChannel", label: "Target Channel", type: "channel-search", editable: true },
201
+ { id: "title", label: "Embed Title", type: "text", editable: true, value: "Need help?" },
202
+ { id: "description", label: "Embed Description", type: "textarea", editable: true },
203
+ { id: "buttonLabel", label: "Button Label", type: "text", editable: true, value: "Open Ticket" },
204
+ ],
205
+ actions: [{ id: "publishTicket", label: "Publish to Channel", variant: "primary", collectFields: true }],
206
+ },
207
+ ],
208
+ actions: {
209
+ publishTicket: async (context, body) => {
210
+ // body.values contains the form data because collectFields was true
211
+ const data = body as { values?: Record<string, unknown> };
212
+ const values = data.values ?? {};
213
+
214
+ console.log(`Creating ticket panel in ${values.targetChannel}`);
215
+ return { ok: true, message: `Ticket panel published!`, data: values };
216
+ },
217
+ },
218
+ },
219
+ ],
220
+ });
484
221
  ```
485
222
 
486
- ### Event example (`interactionCreate`)
487
-
488
- ```ts
489
- import type { BotEvent } from "../types";
223
+ ---
490
224
 
491
- const event: BotEvent<"interactionCreate"> = {
492
- name: "interactionCreate",
493
- async execute(context, interaction) {
494
- if (!interaction.isChatInputCommand()) return;
225
+ ## 🔍 Lookup Fields (Discord Entities)
495
226
 
496
- const command = context.commands.get(interaction.commandName);
497
- if (!command) {
498
- await interaction.reply({ content: "Command not found.", ephemeral: true });
499
- return;
500
- }
501
-
502
- await command.execute(context, interaction);
503
- }
504
- };
227
+ Instead of forcing users to copy/paste IDs, use lookup fields to provide a rich website autocomplete experience.
505
228
 
506
- export default event;
507
- ```
229
+ - `type: "role-search"`: Type a role name, select it, and the action receives the full Role object.
230
+ - `type: "channel-search"`: Type a channel name.
231
+ - `type: "member-search"`: Type a username or nickname.
508
232
 
509
- ### Dashboard creation example (classic + flexible pages)
233
+ You can configure lookups with filters:
510
234
 
511
235
  ```ts
512
- createDashboard({
513
- app,
514
- botToken,
515
- clientId,
516
- clientSecret,
517
- redirectUri,
518
- sessionSecret,
519
- uiTemplate: "compact",
520
- home: {
521
- async getSections(ctx) {
522
- return [{
523
- id: "classic",
524
- title: "Classic Home",
525
- scope: ctx.selectedGuildId ? "guild" : "user",
526
- fields: [{ id: "mode", label: "Mode", type: "text", value: ctx.selectedGuildId ? "Guild" : "User", readOnly: true }]
527
- }];
528
- }
236
+ {
237
+ id: "logChannel",
238
+ label: "Logs",
239
+ type: "channel-search",
240
+ lookup: {
241
+ limit: 5,
242
+ channelTypes: [0, 5] // 0 = GUILD_TEXT, 5 = GUILD_ANNOUNCEMENT
529
243
  }
530
- });
531
-
532
- // Flexible style:
533
- createDashboard({
534
- ...createDashboardDesigner({ app, botToken, clientId, clientSecret, redirectUri, sessionSecret })
535
- .setupPage({ id: "profile", title: "Profile", scope: "user", fields: [{ id: "bio", label: "Bio", type: "textarea" }] })
536
- .onLoad("profile", async (ctx, section) => ({ ...section, description: `Loaded for ${ctx.user.username}` }))
537
- .onSave("profile", async () => ({ ok: true, message: "Saved", refresh: true }))
538
- .build()
539
- });
244
+ }
540
245
  ```
541
246
 
542
- While using the real-bot example, live edits and bot activity are persisted to [examples/dashboard-demo-state.json](examples/dashboard-demo-state.json).
247
+ ---
543
248
 
544
- ## Common Recipes
249
+ ## 📚 API Reference
545
250
 
546
- ### Welcome System (Guild Home Section)
251
+ ### `DashboardDesigner` Methods
547
252
 
548
- ```ts
549
- import { createDashboard, createDashboardDesigner } from "@developer.krd/discord-dashboard";
253
+ - **`setup(options)`**: Set core info (`ownerIds`, `dashboardName`, `basePath`, `uiTemplate`).
254
+ - **`setupDesign(config)`**: Override theme colors (e.g., `primary`, `bg`, `panel`).
255
+ - **`customCss(cssString)`**: Inject raw CSS to completely customize the dashboard.
256
+ - **`setupCategory(id, label, build)`**: Add tabs to the one-time setup view.
257
+ - **`userCategory(id, label, build)`**: Add tabs to the user dashboard.
258
+ - **`guildCategory(id, label, build)`**: Add tabs to the server dashboard.
259
+ - **`onHomeAction(actionId, handler)`**: Define what happens when a button is clicked.
260
+ - **`createDashboard()`**: Terminal method. Builds the config and returns a `DiscordDashboard` instance.
550
261
 
551
- const designer = createDashboardDesigner({ app, botToken, clientId, clientSecret, redirectUri, sessionSecret })
552
- .guildCategory("welcome", "Welcome", (category) => {
553
- category.section({
554
- id: "welcome-settings",
555
- title: "Welcome Settings",
556
- fields: [
557
- { id: "enabled", label: "Enabled", type: "boolean", value: true },
558
- { id: "channel", label: "Welcome Channel", type: "channel-search", placeholder: "Search channel..." },
559
- { id: "message", label: "Message", type: "textarea", value: "Welcome to the server, {user}!" }
560
- ],
561
- actions: [{ id: "saveWelcome", label: "Save Welcome", variant: "primary" }]
562
- });
563
- })
564
- .onHomeAction("saveWelcome", async (context, payload) => {
565
- if (!context.selectedGuildId) return { ok: false, message: "Select a guild first" };
566
- const channel = payload.values.channel as { id?: string; name?: string } | null;
567
- return { ok: true, message: `Welcome config saved for ${context.selectedGuildId} in #${channel?.name ?? "unknown"}` };
568
- });
262
+ ### `DiscordDashboard` Methods
569
263
 
570
- createDashboard({ ...designer.build() });
571
- ```
264
+ - **`app`**: The Express instance (either the one you provided, or a newly created one).
265
+ - **`start()`**: Starts the internal HTTP server (only if you didn't pass your own Express app).
266
+ - **`stop()`**: Gracefully shuts down the internal server.
572
267
 
573
- ### Moderation Toggles + Limits
268
+ ---
574
269
 
575
- ```ts
576
- home: {
577
- getSections: async (context) => [
578
- {
579
- id: "moderation-core",
580
- title: "Moderation",
581
- scope: "guild",
582
- fields: [
583
- { id: "antiSpam", label: "Anti-Spam", type: "boolean", value: true },
584
- { id: "antiLinks", label: "Block Suspicious Links", type: "boolean", value: true },
585
- { id: "maxMentions", label: "Max Mentions", type: "number", value: 5 }
586
- ],
587
- actions: [{ id: "saveModeration", label: "Save Moderation", variant: "primary" }]
588
- }
589
- ],
590
- actions: {
591
- saveModeration: async (_context, payload) => ({
592
- ok: true,
593
- message: `Saved moderation: antiSpam=${Boolean(payload.values.antiSpam)}, maxMentions=${Number(payload.values.maxMentions ?? 0)}`,
594
- refresh: true
595
- })
596
- }
597
- }
598
- ```
270
+ ## 🔒 Required Discord OAuth2 Setup
599
271
 
600
- ### Ticket Panel Builder (Plugin)
272
+ To make login work:
601
273
 
602
- ```ts
603
- plugins: [
604
- {
605
- id: "tickets",
606
- name: "Tickets",
607
- scope: "guild",
608
- getPanels: async () => [
609
- {
610
- id: "ticket-panel",
611
- title: "Ticket Panel",
612
- fields: [
613
- { id: "targetChannel", label: "Target Channel", type: "channel-search", editable: true, value: "" },
614
- { id: "title", label: "Embed Title", type: "text", editable: true, value: "Need help?" },
615
- { id: "description", label: "Embed Description", type: "textarea", editable: true, value: "Click below to open a ticket." },
616
- { id: "buttonLabel", label: "Button Label", type: "text", editable: true, value: "Open Ticket" }
617
- ],
618
- actions: [{ id: "publishTicketPanel", label: "Publish", variant: "primary", collectFields: true }]
619
- }
620
- ],
621
- actions: {
622
- publishTicketPanel: async (_context, body) => {
623
- const data = body as { values?: Record<string, unknown> };
624
- const values = data.values ?? {};
625
- return { ok: true, message: `Ticket panel published as '${String(values.title ?? "Need help?")}'`, data: values };
626
- }
627
- }
628
- }
629
- ]
630
- ```
274
+ 1. Go to the [Discord Developer Portal](https://discord.com/developers/applications).
275
+ 2. Open your Application -> **OAuth2**.
276
+ 3. Add your Redirect URI (e.g., `http://localhost:3000/dashboard/callback`).
277
+ 4. Grab your `CLIENT_ID` and `CLIENT_SECRET` to use in your dashboard config.
631
278
 
632
- ### Announcement Composer (with Role Mention)
279
+ _Note: Ensure your `sessionSecret` is a long, random string in production, and run your bot behind HTTPS._
633
280
 
634
- ```ts
635
- plugins: [
636
- {
637
- id: "announcements",
638
- name: "Announcements",
639
- scope: "guild",
640
- getPanels: async () => [
641
- {
642
- id: "announce",
643
- title: "Announcement Composer",
644
- fields: [
645
- { id: "channel", label: "Channel", type: "channel-search", editable: true, value: "" },
646
- { id: "role", label: "Mention Role", type: "role-search", editable: true, value: "" },
647
- { id: "content", label: "Content", type: "textarea", editable: true, value: "Server update goes here..." }
648
- ],
649
- actions: [{ id: "sendAnnouncement", label: "Send", variant: "primary", collectFields: true }]
650
- }
651
- ],
652
- actions: {
653
- sendAnnouncement: async (_context, body) => {
654
- const payload = body as { values?: Record<string, unknown> };
655
- const channel = payload.values?.channel as { id?: string; name?: string } | null;
656
- const role = payload.values?.role as { id?: string; name?: string } | null;
657
- const mention = role?.id ? `<@&${role.id}> ` : "";
658
- const content = String(payload.values?.content ?? "");
659
-
660
- return {
661
- ok: true,
662
- message: `Announcement queued for #${channel?.name ?? "unknown"}`,
663
- data: { message: mention + content, channel }
664
- };
665
- }
666
- }
667
- }
668
- ]
669
- ```
281
+ ---
670
282
 
671
- ## License
283
+ ## 📄 License
672
284
 
673
285
  MIT