feat: implement RoleSelector and ChannelSelector in config forms#175
feat: implement RoleSelector and ChannelSelector in config forms#175BillChirico merged 38 commits intomainfrom
Conversation
…move unused theme var - FormFieldContext/FormItemContext: initialize to null, move null checks before context is accessed so useFormField() throws early if used outside its providers - PopoverTitle: render <h2> instead of <div> for semantic correctness - role-selector: clear stale roles (setRoles([])) before fetching new guild's role list to prevent stale IDs from being applied - theme-toggle: remove unused 'theme' destructure from useTheme()
…variant in command.tsx - DialogHeader (with sr-only title/description) must be a descendant of DialogContent for Radix Dialog accessibility (title/description bound to the dialog role via aria-labelledby/aria-describedby) - **:data-[slot=command-input-wrapper]:h-12 was an invalid Tailwind variant; corrected to *:data-[slot=command-input-wrapper]:h-12
Ensures state updates only apply when the current AbortController matches the one that initiated the fetch — consistent with the existing setLoading guard in the finally block. Addresses Greptile review comment.
next-themes is imported in theme-provider.tsx, providers.tsx, and theme-toggle.tsx but was missing from web/package.json dependencies, causing module resolution failures at build time. Fixes reviewer comments on missing next-themes dep.
form.tsx imports Controller, FormProvider, useFormContext, useFormState from react-hook-form but the package was missing from web/package.json, causing CI lockfile validation failures.
Switch to since LabelPrimitive is only referenced in type positions (React.ComponentProps<typeof LabelPrimitive.Root>). This avoids an unnecessary runtime import/bundle side effect with isolatedModules. Addresses copilot review suggestion.
…urable flag Addresses copilot review thread: Object.defineProperty unconditionally redefining window.matchMedia can throw if a future jsdom version provides a non-configurable implementation. Add typeof guard and configurable: true to make the setup more robust. Resolves: PRRT_kwDORICdSM5xZb6X
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRefactors dashboard config editing to use structured per-option role menus and RoleSelector/ChannelSelector components, adds a Zustand store for caching Discord entities, adds abort-guarding for async fetches, introduces small UI/doc updates, and adds next-themes, react-hook-form, and zustand dependencies. Changes
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 183-189: The normalization of data.welcome.roleMenu.options
assumes each option is an object and directly accesses opt.id, which can throw
on null/primitive items; update the map in the data.welcome.roleMenu.options
normalization to defensively handle non-object entries by coalescing each opt
into an object (e.g., if typeof opt !== 'object' || opt === null then replace
with a new object) and always assign id: opt.id || crypto.randomUUID(); also
preserve any known fields when opt is an object so existing properties aren’t
lost.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
web/package.jsonweb/src/components/dashboard/config-editor.tsxweb/src/stores/discord-entities.tsweb/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (2)
web/src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript with type safety. Share contracts between dashboard UI and API responses via
web/src/types/analytics.tsand similar type definition files
Files:
web/src/types/config.tsweb/src/stores/discord-entities.ts
web/src/components/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
Files:
web/src/components/dashboard/config-editor.tsx
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to web/src/components/**/*.tsx : Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
📚 Learning: 2026-03-01T06:03:34.399Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to web/src/components/**/*.tsx : Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
Applied to files:
web/src/stores/discord-entities.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-01T06:03:34.399Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to src/modules/*.js : Per-request modules (AI, spam, moderation) should call `getConfig(guildId)` on every invocation for automatic config changes. Stateful resources should use `onConfigChange` listeners for reactive updates
Applied to files:
web/src/components/dashboard/config-editor.tsx
🧬 Code graph analysis (1)
web/src/components/dashboard/config-editor.tsx (1)
web/src/components/ui/role-selector.tsx (1)
RoleSelector(52-292)
🔇 Additional comments (6)
web/src/types/config.ts (1)
29-33:WelcomeRoleOption.idis a good contract addition.This supports stable option identity for UI keying and option-level updates/removals.
web/package.json (1)
28-37: Dependency additions look coherent for this PR scope.
next-themes,react-hook-form, andzustandalign with the selector/theming/form updates.web/src/stores/discord-entities.ts (1)
40-69: Store implementation is clean and correctly immutable.The per-guild cache API (
get/set/clearfor channels and roles) is well-structured for selector reuse.web/src/components/dashboard/config-editor.tsx (3)
218-223: Validation guard for incomplete role options is correctly enforced.Blocking save on empty
label/roleIdcloses the malformed-role-menu path.
787-860: Role-menu option editing flow is a solid upgrade.Stable keys (
opt.id), ID-based deletion, and single-selectRoleSelectorintegration are correctly wired.
1392-1418: Permissions role fields are correctly migrated toRoleSelector.Single-selection mapping to nullable IDs (
selected[0] ?? null) is correct foradminRoleIdandmoderatorRoleId.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 20 changed files in this pull request and generated 7 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/components/providers.tsx (1)
9-34:⚠️ Potential issue | 🔴 CriticalDuplicate function declaration causes lint failures.
ThemedToasteris defined twice—once without JSDoc (lines 9–18) and again with JSDoc (lines 25–34). This triggersnoUnusedVariableson the first definition andnoRedeclareon the second. Remove the original definition and keep only the documented version.🐛 Proposed fix to remove duplicate definition
'use client'; import { SessionProvider } from 'next-auth/react'; import { useTheme } from 'next-themes'; import type { ReactNode } from 'react'; import { Toaster } from 'sonner'; import { ThemeProvider } from '@/components/theme-provider'; -function ThemedToaster() { - const { resolvedTheme } = useTheme(); - return ( - <Toaster - position="bottom-right" - theme={(resolvedTheme as 'light' | 'dark') ?? 'system'} - richColors - /> - ); -} - /** * Render a global Toaster whose visual theme follows the resolved app theme. * * `@returns` A React element mounting a Toaster at the bottom-right with its `theme` set to the resolved theme (`'light'` or `'dark'`, falling back to `'system'` if unresolved) and `richColors` enabled. */ function ThemedToaster() { const { resolvedTheme } = useTheme(); return ( <Toaster position="bottom-right" theme={(resolvedTheme as 'light' | 'dark') ?? 'system'} richColors /> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/providers.tsx` around lines 9 - 34, Remove the duplicate, keeping only the documented ThemedToaster implementation: delete the first ThemedToaster function (the one without JSDoc) and leave the second documented ThemedToaster that uses useTheme and returns the Toaster with theme {(resolvedTheme as 'light' | 'dark') ?? 'system'} and richColors; ensure any imports (useTheme, Toaster) remain referenced and that there are no other references to the removed duplicate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@web/src/components/providers.tsx`:
- Around line 9-34: Remove the duplicate, keeping only the documented
ThemedToaster implementation: delete the first ThemedToaster function (the one
without JSDoc) and leave the second documented ThemedToaster that uses useTheme
and returns the Toaster with theme {(resolvedTheme as 'light' | 'dark') ??
'system'} and richColors; ensure any imports (useTheme, Toaster) remain
referenced and that there are no other references to the removed duplicate.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
web/src/components/providers.tsxweb/src/components/ui/form.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (1)
web/src/components/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
Files:
web/src/components/ui/form.tsxweb/src/components/providers.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to web/src/components/**/*.tsx : Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
🪛 GitHub Actions: CI
web/src/components/providers.tsx
[error] 9-9: lint/correctness/noUnusedVariables: Unused function ThemedToaster. FIXABLE
[error] 25-25: lint/suspicious/noRedeclare: 'ThemedToaster' is defined here
🔇 Additional comments (2)
web/src/components/ui/form.tsx (2)
85-104: LGTM!The JSDoc documentation accurately describes the
FormLabelcomponent's behavior, including its prop forwarding, error-aware styling integration, and automatichtmlForbinding to the form control.
110-115: Preserve caller-providedaria-describedbyIDs inFormControl.The current implementation overwrites any
aria-describedbyvalue passed via props, which can drop additional assistive descriptions from consuming components. This should merge the internal IDs with any caller-provided value.
- Remove duplicate ThemedToaster function in providers.tsx - Add generateId() fallback for environments without crypto.randomUUID - Only validate role menu options when role menu is enabled - Allow removing last role menu option (fix stuck state) - Make WelcomeRoleOption.id optional (backend may not return it)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
web/src/components/dashboard/config-sections/ModerationSection.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
web/src/components/dashboard/config-editor.tsx (1)
201-207:⚠️ Potential issue | 🟠 MajorGuard role-menu normalization before reading
opt.id.Line 205 assumes every option is an object. A malformed entry (
null/primitive) will break load whenopt.idis read.🛡️ Proposed defensive normalization
- // Ensure role menu options have stable IDs - if (data.welcome?.roleMenu?.options) { - data.welcome.roleMenu.options = data.welcome.roleMenu.options.map((opt) => ({ - ...opt, - id: opt.id || generateId(), - })); - } + // Ensure role menu options have stable IDs and safe object shape + if (Array.isArray(data.welcome?.roleMenu?.options)) { + data.welcome.roleMenu.options = data.welcome.roleMenu.options.map((opt) => { + if (typeof opt !== 'object' || opt === null) { + return { id: generateId(), label: '', roleId: '' }; + } + const safe = opt as Record<string, unknown>; + return { + ...safe, + id: + typeof safe.id === 'string' && safe.id.trim().length > 0 + ? safe.id + : generateId(), + }; + }) as typeof data.welcome.roleMenu.options; + }#!/bin/bash # Verify the normalization block currently reads `opt.id` without a non-object/null guard. sed -n '198,208p' web/src/components/dashboard/config-editor.tsx🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-editor.tsx` around lines 201 - 207, The role-menu normalization assumes each element in data.welcome.roleMenu.options is an object and reads opt.id directly, which will throw for null/primitives; update the mapping in the block handling data.welcome.roleMenu.options to defensively handle non-object entries by checking typeof opt === 'object' && opt !== null, and for any non-object replace it with a normalized object (e.g., { id: generateId(), label: String(opt) } or minimal { id: generateId() }) while preserving existing object fields and using opt.id || generateId() for objects; reference the data.welcome.roleMenu.options mapping and generateId() when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 1407-1436: The wrapper elements currently using <label> around the
Admin and Moderator Role selectors cause a11y lint failures because RoleSelector
is a custom control; change those wrapper <label className="space-y-2"> elements
to non-label containers (e.g., <div className="space-y-2">) for both the Admin
and Moderator blocks so the static <span className="text-sm font-medium">
remains the visible label and RoleSelector (used with selected from
draftConfig.permissions?.adminRoleId / moderatorRoleId and onChange calling
updatePermissionsField) is not nested inside a <label>. Ensure no other label
semantics are lost (the span stays as the visual label) and preserve the
existing props like guildId, selected, onChange, placeholder, disabled, and
maxSelections.
In `@web/src/components/ui/form.tsx`:
- Around line 109-115: The new ariaDescribedBy block is misformatted; run the
project formatter (e.g., Prettier/format task) and reformat the block that
defines callerDescribedBy, fieldDescribedBy, and ariaDescribedBy so it matches
the repository style. Ensure the expressions using props['aria-describedby'],
formDescriptionId, formMessageId, and the conditional assembly of
ariaDescribedBy are preserved exactly but reformatted to the project's
spacing/line-break conventions.
---
Duplicate comments:
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 201-207: The role-menu normalization assumes each element in
data.welcome.roleMenu.options is an object and reads opt.id directly, which will
throw for null/primitives; update the mapping in the block handling
data.welcome.roleMenu.options to defensively handle non-object entries by
checking typeof opt === 'object' && opt !== null, and for any non-object replace
it with a normalized object (e.g., { id: generateId(), label: String(opt) } or
minimal { id: generateId() }) while preserving existing object fields and using
opt.id || generateId() for objects; reference the data.welcome.roleMenu.options
mapping and generateId() when making this change.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (5)
web/src/components/dashboard/config-editor.tsxweb/src/components/providers.tsxweb/src/components/ui/dialog.tsxweb/src/components/ui/form.tsxweb/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (2)
web/src/components/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
Files:
web/src/components/providers.tsxweb/src/components/dashboard/config-editor.tsxweb/src/components/ui/form.tsxweb/src/components/ui/dialog.tsx
web/src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript with type safety. Share contracts between dashboard UI and API responses via
web/src/types/analytics.tsand similar type definition files
Files:
web/src/types/config.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to web/src/components/**/*.tsx : Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
📚 Learning: 2026-03-01T06:03:34.399Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to web/src/components/**/*.tsx : Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-01T06:03:34.399Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-01T06:03:34.399Z
Learning: Applies to src/modules/*.js : Per-request modules (AI, spam, moderation) should call `getConfig(guildId)` on every invocation for automatic config changes. Stateful resources should use `onConfigChange` listeners for reactive updates
Applied to files:
web/src/components/dashboard/config-editor.tsx
🧬 Code graph analysis (1)
web/src/components/dashboard/config-editor.tsx (1)
web/src/components/ui/role-selector.tsx (1)
RoleSelector(52-292)
🪛 GitHub Actions: CI
web/src/components/dashboard/config-editor.tsx
[error] 1407-1419: lint/a11y/noLabelWithoutControl: A form label must be associated with an input. Consider adding a for/htmlFor attribute or nesting the input inside the label.
[error] 1420-1436: lint/a11y/noLabelWithoutControl: A form label must be associated with an input. Consider adding a for/htmlFor attribute or nesting the input inside the label.
web/src/components/ui/form.tsx
[error] 4-4: lint/style/useImportType: Use import type for Label from 'radix-ui' to avoid importing runtime code.
[error] 109-115: Formatter would have printed different content. Run the formatter (e.g., Prettier) to fix formatting changes.
🔇 Additional comments (12)
web/src/components/providers.tsx (1)
9-13: JSDoc is accurate and helpful.This documentation cleanly describes
ThemedToasterbehavior and aligns with the implementation.web/src/components/ui/dialog.tsx (5)
9-17: LGTM!Clean JSDoc documentation that accurately describes the component's behavior of forwarding props to the Radix Dialog Root.
19-27: LGTM!Documentation correctly describes the
data-slotattribute injection and prop forwarding behavior.
29-37: Previous feedback addressed.The
@paramtag has been added to the DialogPortal JSDoc, resolving the inconsistency noted in prior review.
39-47: LGTM!Consistent documentation style matching the other dialog primitive wrappers.
65-70: LGTM!Good documentation focusing on the custom
showCloseButtonprop. The standard React props (className, children) appropriately don't need explicit documentation since they follow common patterns.web/src/components/ui/form.tsx (3)
85-91: HelpfulFormLabelJSDoc addition.This docblock is concise and accurately describes behavior/props for consumers.
109-123:aria-describedbymerge now preserves consumer IDs and internal field metadata.This resolves the accessibility regression risk by combining caller-provided IDs with form description/error IDs.
4-4: The proposed type-only import change is incorrect and would break the code.
LabelPrimitiveis used in a runtime expression on line 92:React.ComponentProps<typeof LabelPrimitive.Root>. Thetypeofoperator requires a runtime value, not a type-only import. The current import statement is correct as-is.Likely an incorrect or invalid review comment.
web/src/types/config.ts (1)
29-33:WelcomeRoleOption.idis a solid backward-compatible contract extension.Making
idoptional supports stable option identity in the editor without breaking older config payloads.web/src/components/dashboard/config-editor.tsx (2)
236-243: Good save guard for incomplete role-menu options.This correctly blocks save when role menu is enabled and an option is missing
labelorroleId.
806-877: Stable keying and ID-based deletion look correct here.Using
key={opt.id}, removing by ID, and generating IDs for new options is a strong improvement for row stability.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
web/src/components/dashboard/config-editor.tsx:766
- PR description calls out replacing several channel-id fields in
config-editor.tsxwithChannelSelector, but this file still uses plain text inputs for channel IDs (e.g.welcome.rulesChannelhere). Either implement the selectors in this editor as described, or update the PR description to reflect what actually changed.
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<label htmlFor="rules-channel-id" className="space-y-2">
<span className="text-sm font-medium">Rules Channel ID</span>
<input
id="rules-channel-id"
type="text"
value={draftConfig.welcome?.rulesChannel ?? ''}
onChange={(e) => updateWelcomeField('rulesChannel', e.target.value.trim() || null)}
disabled={saving}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
web/src/components/dashboard/config-sections/ModerationSection.tsx
Outdated
Show resolved
Hide resolved
| "tailwind-merge": "^3.5.0", | ||
| "next-themes": "^0.4.6", | ||
| "react-hook-form": "^7.56.4" | ||
| "tailwind-merge": "^3.5.0" |
There was a problem hiding this comment.
Removed next-themes but it's still imported in providers.tsx, theme-provider.tsx, and theme-toggle.tsx - this will cause build failure
| "tailwind-merge": "^3.5.0" | |
| "tailwind-merge": "^3.5.0", | |
| "next-themes": "^0.4.6", | |
| "react-hook-form": "^7.56.4" |
Prompt To Fix With AI
This is a comment left during a code review.
Path: web/package.json
Line: 36
Comment:
Removed `next-themes` but it's still imported in `providers.tsx`, `theme-provider.tsx`, and `theme-toggle.tsx` - this will cause build failure
```suggestion
"tailwind-merge": "^3.5.0",
"next-themes": "^0.4.6",
"react-hook-form": "^7.56.4"
```
How can I resolve this? If you propose a fix, please make it concise.
Summary
Implement RoleSelector and ChannelSelector components across dashboard configuration forms for better UX when selecting Discord channels and roles.
Changes
Channel Selectors (8 locations)
config-editor.tsx:
welcome.rulesChannel— Rules channel selectionwelcome.introChannel— Introduction channel selectionstarboard.channelId— Starboard channel selectionchallenges.channelId— Daily challenges channelgithub.feed.channelId— GitHub feed channelreputation.announceChannelId— Reputation announcementsModerationSection.tsx:
moderation.alertChannelId— Moderation alerts channelTriageSection.tsx:
triage.moderationLogChannel— Triage moderation log channelRole Selectors (2 locations)
permissions.adminRoleId— Admin role selectionpermissions.moderatorRoleId— Moderator role selectionFeatures
maxSelections={1})Technical Details
useGuildSelection()hook for guild context