feat(web): redesign bot config settings workspace#253
Conversation
|
🚅 Deployed to the volvox-bot-pr-253 environment in volvox-bot
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughRedesign of the web dashboard config editor: adds a category-based workspace (navigation, categories metadata), metadata-driven search with cross-category quick-jump, reusable SettingsFeatureCard UI for features (basic/advanced + enable toggle), updates CommunitySettingsSection to use the new system, and removes runtime GUILD_ID env reads. Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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 |
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @BillChirico. The following files were modified: * `web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx` * `web/src/components/dashboard/config-workspace/category-navigation.tsx` * `web/src/components/dashboard/config-workspace/config-categories.ts` * `web/src/components/dashboard/config-workspace/config-search.tsx` * `web/src/components/dashboard/config-workspace/settings-feature-card.tsx` * `web/src/components/dashboard/toggle-switch.tsx` These files were ignored: * `web/tests/components/dashboard/config-editor-autosave.test.tsx` These file types are not supported: * `CLAUDE.md`
Docstrings generation was requested by @BillChirico. The following files were modified: * `web/src/components/dashboard/config-workspace/config-categories.ts` These files were kept as they were: * `web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx` * `web/src/components/dashboard/config-workspace/category-navigation.tsx` * `web/src/components/dashboard/config-workspace/config-search.tsx` * `web/src/components/dashboard/config-workspace/settings-feature-card.tsx` * `web/src/components/dashboard/toggle-switch.tsx` These files were ignored: * `web/tests/components/dashboard/config-editor-autosave.test.tsx` These file types are not supported: * `CLAUDE.md`
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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-sections/CommunitySettingsSection.tsx`:
- Around line 133-185: The activity badge row key is unstable (uses days/label)
causing remounts and the delete path filters an empty array when activityBadges
is undefined; update the map key to a stable identifier (e.g., use the loop
index with key={`badge-${index}`} or a persistent id on Badge) and change the
delete logic to operate on the same fallback array you render (use const badges
= [...(draftConfig.engagement?.activityBadges ??
defaultActivityBadges)].filter((_, idx) => idx !== index) before calling
updateDraftConfig) so removing seeded badges works correctly.
In `@web/src/components/dashboard/config-workspace/config-categories.ts`:
- Around line 364-366: getCategoryById silently falls back to
CONFIG_CATEGORIES[0] which can hide invalid IDs; update getCategoryById to
detect when find(...) returns undefined and, in non-production/dev builds (e.g.
check process.env.NODE_ENV !== 'production' or existing isDev flag), emit a
console.warn or use the app logger with a message that includes the missing
categoryId and optionally the list of CONFIG_CATEGORIES.map(c => c.id) for
debugging; still return CONFIG_CATEGORIES[0] to preserve UI resilience.
- Around line 71-81: Remove the unused "section" property from the
ConfigSearchItem type and from every item in CONFIG_SEARCH_ITEMS; update the
type/interface definition (ConfigSearchItem) to no longer include "section" and
delete the "section" keys from the array entries, then run the build/tests to
ensure nothing else references it—note that handleSearchSelect only uses
categoryId, featureId and id so no call sites need changes (see
CONFIG_SEARCH_ITEMS and handleSearchSelect).
In `@web/src/components/dashboard/config-workspace/config-search.tsx`:
- Around line 64-66: The UI is rendering a slug-derived label via
item.categoryId.replace(/-/g, ' ') which can drift; instead look up the category
metadata by id and render its canonical label. In config-search.tsx change the
span to display the category's label (e.g., find the category with id ===
item.categoryId from the categories/metadata array or via getCategoryById used
elsewhere) and fall back to item.categoryId.replace(/-/g, ' ') only if no
category object or label is found; keep item.description as-is.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: fe0d9609-7981-4d4d-a3df-662f021f6889
📒 Files selected for processing (10)
CLAUDE.mdweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/category-navigation.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-workspace/config-search.tsxweb/src/components/dashboard/config-workspace/settings-feature-card.tsxweb/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/toggle-switch.tsxweb/tests/components/dashboard/config-editor-autosave.test.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). (2)
- GitHub Check: Greptile Review
- GitHub Check: Docker Build Validation
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
web/src/components/dashboard/config-workspace/config-search.tsxweb/src/components/dashboard/config-workspace/category-navigation.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/toggle-switch.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-workspace/settings-feature-card.tsx
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/src/components/dashboard/config-workspace/config-search.tsxweb/src/components/dashboard/config-workspace/category-navigation.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/toggle-switch.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-workspace/settings-feature-card.tsx
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to web/**/*.{ts,tsx} : Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to web/**/*.{ts,tsx} : Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Applied to files:
web/src/components/dashboard/config-workspace/category-navigation.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxCLAUDE.mdweb/src/components/dashboard/config-workspace/settings-feature-card.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to tests/**/*.{js,mjs,jsx,ts,tsx} : Maintain 80% code coverage threshold minimum, never lower this threshold
Applied to files:
web/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Claude CLI runs in headless mode for AI chat, Claude SDK used for auto-moderation (toxicity/spam detection), feedback tracked via reactions
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Before starting any coding session, read `CLAUDE.md` for coding standards and persona; after infrastructure work, update `CLAUDE.md` with technical decisions and session notes
Applied to files:
CLAUDE.md
🔇 Additional comments (4)
web/src/components/dashboard/config-workspace/config-categories.ts (4)
1-49: LGTM!The category metadata is well-structured with clear groupings. Type imports are clean, and the data aligns with the
ConfigCategoryMetainterface defined intypes.ts.
51-69: LGTM!The default category and feature labels are complete. All feature IDs defined across categories have corresponding human-readable labels.
368-373: LGTM!The implementation correctly searches categories by feature ID. Same fallback pattern as
getCategoryById— the optional warning suggestion above would apply here as well.
375-388: LGTM!The search implementation is clean:
- Properly normalizes input with trim and lowercase
- Returns early for empty queries
- Searches across label, description, and keywords
- Uses
Setfor deduplication ingetMatchedFeatureIdsThe O(n×m) complexity is appropriate for the static dataset size.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx
Show resolved
Hide resolved
|
Using badge content (days/label) as the React key caused remounts while the user was typing. Switch to index-based key so the row identity is stable during edits. Also fix the delete handler to fall back to defaultActivityBadges instead of an empty array, matching the add handler's behavior.
The section field was defined in the ConfigSearchItem interface and populated in CONFIG_SEARCH_ITEMS but never read by any consuming code. Removing it as dead code to keep the type surface clean.
The silent fallback to CONFIG_CATEGORIES[0] could mask bugs where an invalid categoryId is passed. Add a console.warn so these cases surface during development instead of silently returning unexpected data.
item.categoryId.replace(/-/g, ' ') produced text like 'support integrations' which doesn't match the actual category names. Use getCategoryById to render the proper label (e.g. 'Support & Integrations').
Calling setFocusFeatureId(null) synchronously before the RAF fires triggers a re-render that cancels the scroll/focus effect before it can run. Moving it inside the RAF callback ensures the DOM operations complete before the state is cleared.
Remove ineffective JSX biome-ignore comment (JSX expression context requires the pragma directly on the prop line; the noArrayIndexKey warning is pre-existing and emitted only as a warning, not an error). Add biome-ignore line comment to console.warn in getCategoryById so biome treats it as intentional diagnostic output.
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-sections/CommunitySettingsSection.tsx`:
- Around line 584-602: Add an explicit id on the timezone input and a matching
htmlFor on the label to associate them for accessibility: give the input a
unique id (e.g. id="challenges-timezone" or similar) and change the surrounding
<label> to <label htmlFor="challenges-timezone" ...>. Ensure you keep the
existing props and handlers (value={draftConfig.challenges?.timezone ??
'America/New_York'}, onChange calling updateDraftConfig, disabled={saving},
className={inputClasses}, placeholder) and only add the id attribute to the
input and htmlFor to the label so the Timezone field
(draftConfig.challenges.timezone) matches other inputs' accessibility patterns.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2a23ca3d-ff6f-48a1-bc7f-452c47451fd5
📒 Files selected for processing (8)
web/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/category-navigation.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-workspace/config-search.tsxweb/src/components/dashboard/config-workspace/settings-feature-card.tsxweb/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/toggle-switch.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 (2)
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
web/src/components/dashboard/config-workspace/category-navigation.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/config-search.tsxweb/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/settings-feature-card.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/toggle-switch.tsx
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/src/components/dashboard/config-workspace/category-navigation.tsxweb/src/components/dashboard/config-sections/CommunitySettingsSection.tsxweb/src/components/dashboard/config-workspace/config-search.tsxweb/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/settings-feature-card.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/toggle-switch.tsx
🧠 Learnings (2)
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to web/**/*.{ts,tsx} : Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Applied to files:
web/src/components/dashboard/config-workspace/category-navigation.tsxweb/src/components/dashboard/config-workspace/settings-feature-card.tsxweb/src/components/dashboard/toggle-switch.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/modules/**/*.{js,mjs} : Create new modules for features with corresponding config sections in `config.json` and entries in `SAFE_CONFIG_KEYS`
Applied to files:
web/src/components/dashboard/config-workspace/config-categories.ts
🔇 Additional comments (12)
web/src/components/dashboard/toggle-switch.tsx (1)
1-29: LGTM!Clean refactor replacing the custom button-based toggle with the shared
Switchprimitive. The component correctly mapsonChangetoonCheckedChange, maintains accessibility viaaria-label, and the props interface is well-documented.web/src/components/dashboard/config-workspace/types.ts (1)
1-57: LGTM!Well-structured type definitions that establish clear contracts for the config workspace. The
ConfigSectionKeyunion with'aiAutoMod'properly extends the baseConfigSectiontype, and all interfaces are appropriately scoped with necessary fields.web/src/components/dashboard/config-workspace/config-search.tsx (1)
1-87: LGTM!The search component is well-implemented with proper accessibility attributes, clear result limiting (8 items), and correct category label resolution using
getCategoryById(item.categoryId).label. The past review concern about slug-derived labels has been addressed.web/src/components/dashboard/config-workspace/category-navigation.tsx (1)
1-93: LGTM!Well-structured responsive navigation with proper accessibility attributes (
aria-current,aria-hiddenon icons). The icon mapping is complete, and the dirty count badges provide clear visual feedback. The type assertion on line 47 is safe since options are generated fromCONFIG_CATEGORIES.web/src/components/dashboard/config-workspace/settings-feature-card.tsx (2)
60-126: LGTM!The card structure is well-organized with proper accessibility attributes (
aria-expanded,aria-controls,aria-label). The conditional rendering of the enabled switch when bothonEnabledChangeandenabledare provided is correct. The visually hidden label complements the switch'saria-label.
52-56: This behavior is intentional and matches the documented contract.The JSDoc states "When true, ensures the Advanced section is opened"—a one-directional guarantee, not bidirectional synchronization. This design aligns with the search workflow: when a user finds an advanced setting via search, the panel opens for inspection. When search clears, the panel remains open (not locked), allowing continued exploration while preserving user agency through the manual toggle button.
The implementation correctly reflects the intended contract.
web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx (2)
151-213: Past review concerns have been addressed.The activity badge implementation now uses a stable index-based key (
key={\badge-${index}`}) and the delete logic correctly uses the same fallback array as the render path (draftConfig.engagement?.activityBadges ?? defaultActivityBadges`).
1-70: LGTM!The component refactor is well-structured with clear prop types, proper docstrings, and a clean
showFeaturehelper for visibility gating. The derived TL;DR values are sensibly extracted for reuse.web/src/components/dashboard/config-workspace/config-categories.ts (4)
341-349: Past review concern has been addressed.The
getCategoryByIdfunction now includes a development warning viaconsole.warnwhen falling back to the default category, with an appropriatebiome-ignorecomment to suppress the lint rule.
71-333: Past review concern has been addressed.The
sectionfield has been removed fromCONFIG_SEARCH_ITEMSentries. The search items now only include the necessary fields:id,featureId,categoryId,label,description,keywords, andisAdvanced.
372-391: LGTM!The search helper functions
getMatchingSearchItemsandgetMatchedFeatureIdsare clean implementations with proper normalization and case-insensitive matching.
41-48: No changes needed.'tickets'is a validConfigSectionKeyand is properly included in theConfigSectionunion type at line 357 ofweb/src/types/config.ts. No type error occurs here.> Likely an incorrect or invalid review comment.
| <label className="space-y-2 md:col-span-2"> | ||
| <span className="text-sm font-medium">Timezone</span> | ||
| <input | ||
| type="text" | ||
| value={draftConfig.challenges?.timezone ?? 'America/New_York'} | ||
| onChange={(event) => | ||
| updateDraftConfig((prev) => ({ | ||
| ...prev, | ||
| reputation: { ...prev.reputation, xpCooldownSeconds: num }, | ||
| })); | ||
| }} | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| /> | ||
| </label> | ||
| <label htmlFor="announce-channel-id" className="space-y-2"> | ||
| <span className="text-sm font-medium">Announce Channel ID</span> | ||
| <ChannelSelector | ||
| id="announce-channel-id" | ||
| guildId={guildId} | ||
| selected={ | ||
| draftConfig.reputation?.announceChannelId | ||
| ? [draftConfig.reputation.announceChannelId] | ||
| : [] | ||
| } | ||
| onChange={(selected) => | ||
| updateDraftConfig((prev) => ({ | ||
| ...prev, | ||
| reputation: { | ||
| ...prev.reputation, | ||
| announceChannelId: selected[0] ?? null, | ||
| }, | ||
| })) | ||
| } | ||
| disabled={saving} | ||
| placeholder="Select announcement channel" | ||
| maxSelections={1} | ||
| filter="text" | ||
| /> | ||
| </label> | ||
| </div> | ||
| <label htmlFor="level-thresholds-comma-separated" className="space-y-2"> | ||
| <span className="text-sm font-medium"> | ||
| Level Thresholds (comma-separated XP values) | ||
| </span> | ||
| <input | ||
| id="level-thresholds-comma-separated" | ||
| type="text" | ||
| value={( | ||
| draftConfig.reputation?.levelThresholds ?? [ | ||
| 100, 300, 600, 1000, 1500, 2500, 4000, 6000, 8500, 12000, | ||
| ] | ||
| ).join(', ')} | ||
| onChange={(e) => { | ||
| const nums = e.target.value | ||
| .split(',') | ||
| .map((s) => Number(s.trim())) | ||
| .filter((n) => Number.isFinite(n) && n > 0); | ||
| if (nums.length > 0) { | ||
| const sorted = [...nums].sort((a, b) => a - b); | ||
| updateDraftConfig((prev) => ({ | ||
| ...prev, | ||
| reputation: { ...prev.reputation, levelThresholds: sorted }, | ||
| })); | ||
| } | ||
| }} | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| placeholder="100, 300, 600, 1000, ..." | ||
| /> | ||
| challenges: { ...prev.challenges, timezone: event.target.value }, | ||
| })) | ||
| } | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| placeholder="America/New_York" | ||
| /> | ||
| <p className="text-xs text-muted-foreground"> | ||
| IANA timezone (e.g. America/Chicago, Europe/London) | ||
| </p> | ||
| </label> |
There was a problem hiding this comment.
Missing id and htmlFor attributes on the Timezone input.
The timezone label and input are not properly associated for accessibility. Other inputs in this file have proper id and htmlFor pairings.
♿ Proposed fix
- <label className="space-y-2 md:col-span-2">
+ <label htmlFor="challenges-timezone" className="space-y-2 md:col-span-2">
<span className="text-sm font-medium">Timezone</span>
<input
+ id="challenges-timezone"
type="text"
value={draftConfig.challenges?.timezone ?? 'America/New_York'}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <label className="space-y-2 md:col-span-2"> | |
| <span className="text-sm font-medium">Timezone</span> | |
| <input | |
| type="text" | |
| value={draftConfig.challenges?.timezone ?? 'America/New_York'} | |
| onChange={(event) => | |
| updateDraftConfig((prev) => ({ | |
| ...prev, | |
| reputation: { ...prev.reputation, xpCooldownSeconds: num }, | |
| })); | |
| }} | |
| disabled={saving} | |
| className={inputClasses} | |
| /> | |
| </label> | |
| <label htmlFor="announce-channel-id" className="space-y-2"> | |
| <span className="text-sm font-medium">Announce Channel ID</span> | |
| <ChannelSelector | |
| id="announce-channel-id" | |
| guildId={guildId} | |
| selected={ | |
| draftConfig.reputation?.announceChannelId | |
| ? [draftConfig.reputation.announceChannelId] | |
| : [] | |
| } | |
| onChange={(selected) => | |
| updateDraftConfig((prev) => ({ | |
| ...prev, | |
| reputation: { | |
| ...prev.reputation, | |
| announceChannelId: selected[0] ?? null, | |
| }, | |
| })) | |
| } | |
| disabled={saving} | |
| placeholder="Select announcement channel" | |
| maxSelections={1} | |
| filter="text" | |
| /> | |
| </label> | |
| </div> | |
| <label htmlFor="level-thresholds-comma-separated" className="space-y-2"> | |
| <span className="text-sm font-medium"> | |
| Level Thresholds (comma-separated XP values) | |
| </span> | |
| <input | |
| id="level-thresholds-comma-separated" | |
| type="text" | |
| value={( | |
| draftConfig.reputation?.levelThresholds ?? [ | |
| 100, 300, 600, 1000, 1500, 2500, 4000, 6000, 8500, 12000, | |
| ] | |
| ).join(', ')} | |
| onChange={(e) => { | |
| const nums = e.target.value | |
| .split(',') | |
| .map((s) => Number(s.trim())) | |
| .filter((n) => Number.isFinite(n) && n > 0); | |
| if (nums.length > 0) { | |
| const sorted = [...nums].sort((a, b) => a - b); | |
| updateDraftConfig((prev) => ({ | |
| ...prev, | |
| reputation: { ...prev.reputation, levelThresholds: sorted }, | |
| })); | |
| } | |
| }} | |
| disabled={saving} | |
| className={inputClasses} | |
| placeholder="100, 300, 600, 1000, ..." | |
| /> | |
| challenges: { ...prev.challenges, timezone: event.target.value }, | |
| })) | |
| } | |
| disabled={saving} | |
| className={inputClasses} | |
| placeholder="America/New_York" | |
| /> | |
| <p className="text-xs text-muted-foreground"> | |
| IANA timezone (e.g. America/Chicago, Europe/London) | |
| </p> | |
| </label> | |
| <label htmlFor="challenges-timezone" className="space-y-2 md:col-span-2"> | |
| <span className="text-sm font-medium">Timezone</span> | |
| <input | |
| id="challenges-timezone" | |
| type="text" | |
| value={draftConfig.challenges?.timezone ?? 'America/New_York'} | |
| onChange={(event) => | |
| updateDraftConfig((prev) => ({ | |
| ...prev, | |
| challenges: { ...prev.challenges, timezone: event.target.value }, | |
| })) | |
| } | |
| disabled={saving} | |
| className={inputClasses} | |
| placeholder="America/New_York" | |
| /> | |
| <p className="text-xs text-muted-foreground"> | |
| IANA timezone (e.g. America/Chicago, Europe/London) | |
| </p> | |
| </label> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/dashboard/config-sections/CommunitySettingsSection.tsx`
around lines 584 - 602, Add an explicit id on the timezone input and a matching
htmlFor on the label to associate them for accessibility: give the input a
unique id (e.g. id="challenges-timezone" or similar) and change the surrounding
<label> to <label htmlFor="challenges-timezone" ...>. Ensure you keep the
existing props and handlers (value={draftConfig.challenges?.timezone ??
'America/New_York'}, onChange calling updateDraftConfig, disabled={saving},
className={inputClasses}, placeholder) and only add the id attribute to the
input and htmlFor to the label so the Timezone field
(draftConfig.challenges.timezone) matches other inputs' accessibility patterns.
🧹 Preview Environment Cleaned UpThe Railway preview environment for this PR has been removed. Environment: |
Summary
This redesign replaces the monolithic config form with a category-first workspace so related toggles and controls stay together. It preserves the existing backend contract and manual save + diff confirmation behavior while making dirty state and section ownership clearer.
Key Changes
web/src/components/dashboard/config-workspace/for category metadata, desktop/mobile navigation, search, and reusable feature cards.web/src/components/dashboard/config-editor.tsxto use category-scoped rendering, metadata-driven search, advanced auto-open on search hits, and category-level dirty indicators while keeping existing save/patch semantics.web/src/components/dashboard/config-sections/CommunitySettingsSection.tsxinto category-aligned feature cards and standardized toggle behavior using the UISwitchprimitive.web/src/lib/config-utils.tshelpers.Impact
This impacts only the dashboard config UI layer in
web/. API routes and PATCH payload semantics remain unchanged.Testing
cd web && pnpm testcd web && pnpm typecheckcd web && pnpm lint