feat(reactions): add emoji reactions for comments and issue descriptions
Add Slack-style emoji reactions to comments and issue descriptions with full-stack support: database tables, REST API endpoints, real-time WebSocket sync, optimistic UI updates, and inbox notifications. - New `comment_reaction` and `issue_reaction` tables with migrations - POST/DELETE endpoints for adding/removing reactions on both comments and issue descriptions - Real-time WS events (reaction:added/removed, issue_reaction:added/removed) - Shared ReactionBar component with quick emoji picker and full emoji-mart picker (lazy-loaded) - Optimistic add/remove with rollback on failure - Inbox notifications for comment author and issue creator when reacted to - Reactions included in timeline, comment list, and issue detail responses
This commit is contained in:
parent
72e3ccfe33
commit
7c1aabbe3a
32 changed files with 1221 additions and 49 deletions
|
|
@ -473,6 +473,76 @@ func registerNotificationListeners(bus *events.Bus, queries *db.Queries) {
|
|||
}
|
||||
})
|
||||
|
||||
// issue_reaction:added — notify the issue creator
|
||||
bus.Subscribe(protocol.EventIssueReactionAdded, func(e events.Event) {
|
||||
payload, ok := e.Payload.(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
reaction, ok := payload["reaction"].(handler.IssueReactionResponse)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
creatorType, _ := payload["creator_type"].(string)
|
||||
creatorID, _ := payload["creator_id"].(string)
|
||||
issueID, _ := payload["issue_id"].(string)
|
||||
issueTitle, _ := payload["issue_title"].(string)
|
||||
issueStatus, _ := payload["issue_status"].(string)
|
||||
|
||||
if creatorType == "" || creatorID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
details, _ := json.Marshal(map[string]string{
|
||||
"emoji": reaction.Emoji,
|
||||
})
|
||||
|
||||
notifyDirect(ctx, queries, bus,
|
||||
creatorType, creatorID,
|
||||
e.WorkspaceID, e, issueID, issueStatus,
|
||||
"reaction_added", "info",
|
||||
issueTitle, "",
|
||||
details,
|
||||
)
|
||||
})
|
||||
|
||||
// reaction:added — notify the comment author
|
||||
bus.Subscribe(protocol.EventReactionAdded, func(e events.Event) {
|
||||
payload, ok := e.Payload.(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
reaction, ok := payload["reaction"].(handler.ReactionResponse)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
commentAuthorType, _ := payload["comment_author_type"].(string)
|
||||
commentAuthorID, _ := payload["comment_author_id"].(string)
|
||||
issueID, _ := payload["issue_id"].(string)
|
||||
issueTitle, _ := payload["issue_title"].(string)
|
||||
issueStatus, _ := payload["issue_status"].(string)
|
||||
|
||||
if commentAuthorType == "" || commentAuthorID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
details, _ := json.Marshal(map[string]string{
|
||||
"emoji": reaction.Emoji,
|
||||
})
|
||||
|
||||
notifyDirect(ctx, queries, bus,
|
||||
commentAuthorType, commentAuthorID,
|
||||
e.WorkspaceID, e, issueID, issueStatus,
|
||||
"reaction_added", "info",
|
||||
issueTitle, "",
|
||||
details,
|
||||
)
|
||||
})
|
||||
|
||||
// task:completed — notify all subscribers except the agent
|
||||
bus.Subscribe(protocol.EventTaskCompleted, func(e events.Event) {
|
||||
payload, ok := e.Payload.(map[string]any)
|
||||
|
|
|
|||
|
|
@ -164,6 +164,8 @@ func NewRouter(pool *pgxpool.Pool, hub *realtime.Hub, bus *events.Bus) chi.Route
|
|||
r.Get("/subscribers", h.ListIssueSubscribers)
|
||||
r.Post("/subscribe", h.SubscribeToIssue)
|
||||
r.Post("/unsubscribe", h.UnsubscribeFromIssue)
|
||||
r.Post("/reactions", h.AddIssueReaction)
|
||||
r.Delete("/reactions", h.RemoveIssueReaction)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -171,6 +173,8 @@ func NewRouter(pool *pgxpool.Pool, hub *realtime.Hub, bus *events.Bus) chi.Route
|
|||
r.Route("/api/comments/{commentId}", func(r chi.Router) {
|
||||
r.Put("/", h.UpdateComment)
|
||||
r.Delete("/", h.DeleteComment)
|
||||
r.Post("/reactions", h.AddReaction)
|
||||
r.Delete("/reactions", h.RemoveReaction)
|
||||
})
|
||||
|
||||
// Agents
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue