feat: feature requests board with voting
- [FEATURES] tab next to PINNED in channel header
- Create, vote/unvote, filter by status (open/planned/done/rejected)
- Sort by vote count (most voted first)
- Status selector for moderation
- DB: feature_requests + feature_request_votes tables
- Backend: full CRUD + vote endpoints under /servers/{id}/feature-requests
This commit is contained in:
@@ -18,6 +18,7 @@ import { ThreadListPanel } from "./ThreadListPanel.tsx";
|
||||
import { ThreadPanel } from "./ThreadPanel.tsx";
|
||||
import { useContextMenu } from "./ContextMenu.tsx";
|
||||
import { PinnedMessages } from "./PinnedMessages.tsx";
|
||||
import { FeatureRequestsPanel } from "./FeatureRequestsPanel.tsx";
|
||||
import { ReactionBar } from "./ReactionBar.tsx";
|
||||
import { EmojiPicker } from "./EmojiPicker.tsx";
|
||||
import { ReplyBar } from "./ReplyBar.tsx";
|
||||
@@ -308,6 +309,7 @@ export function ChatArea() {
|
||||
const markRead = useReadStatesStore((s) => s.markRead);
|
||||
|
||||
const [showPinned, setShowPinned] = useState(false);
|
||||
const [showFeatureRequests, setShowFeatureRequests] = useState(false);
|
||||
const [activeReactionMessageId, setActiveReactionMessageId] = useState<string | null>(null);
|
||||
const [replyToMessage, setReplyToMessage] = useState<any | null>(null);
|
||||
const pinMessage = useMessageStore((s) => s.pinMessage);
|
||||
@@ -662,6 +664,7 @@ export function ChatArea() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowPinned(false);
|
||||
setShowFeatureRequests(false);
|
||||
setShowSearch((prev) => !prev);
|
||||
}}
|
||||
className={`text-xs font-mono ${showSearch ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
|
||||
@@ -672,6 +675,7 @@ export function ChatArea() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowSearch(false);
|
||||
setShowFeatureRequests(false);
|
||||
setShowPinned((prev) => !prev);
|
||||
}}
|
||||
className={`text-xs font-mono ${showPinned ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
|
||||
@@ -679,6 +683,17 @@ export function ChatArea() {
|
||||
>
|
||||
[PINNED{pinnedCount > 0 ? `:${pinnedCount}` : ''}]
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowSearch(false);
|
||||
setShowPinned(false);
|
||||
setShowFeatureRequests((prev) => !prev);
|
||||
}}
|
||||
className={`text-xs font-mono ${showFeatureRequests ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
|
||||
title="Feature requests"
|
||||
>
|
||||
[FEATURES]
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -739,6 +754,13 @@ export function ChatArea() {
|
||||
onJump={handleJumpToMessage}
|
||||
/>
|
||||
)}
|
||||
{showFeatureRequests && activeServerId && (
|
||||
<FeatureRequestsPanel
|
||||
serverId={activeServerId}
|
||||
channelId={activeChannelId || undefined}
|
||||
onClose={() => setShowFeatureRequests(false)}
|
||||
/>
|
||||
)}
|
||||
{showThreads && activeChannelId && activeChannel?.type !== 'forum' && (
|
||||
<ThreadListPanel
|
||||
channelId={activeChannelId}
|
||||
|
||||
Reference in New Issue
Block a user