feat(recordings): only recorder can accept/reject suggestions in MVP
Backend changes: - Restrict suggestion status updates to recorder only - Dancers can now only view who is assigned to record them - Return 403 error if non-recorder tries to update status Frontend changes: - Remove Accept/Reject buttons from dancer view (TO_BE_RECORDED) - Add "Pending" status badge with clock icon for pending suggestions - Keep Accept/Reject buttons for recorder view (TO_RECORD) - Dancers see only status badge and optional chat button UX flow: - Dancer sees: "Recording you: @username [Pending]" - Recorder sees: "You record: @username [Accept] [Reject]" - Only recorder's action creates the Match
This commit is contained in:
@@ -1249,14 +1249,14 @@ router.put('/:slug/match-suggestions/:suggestionId/status', authenticate, async
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authorization: only dancer or recorder can update status
|
// Check authorization: only recorder can update status (MVP decision)
|
||||||
const isDancer = suggestion.heat.userId === userId;
|
// Dancer can only view who is assigned to record them
|
||||||
const isRecorder = suggestion.recorderId === userId;
|
const isRecorder = suggestion.recorderId === userId;
|
||||||
|
|
||||||
if (!isDancer && !isRecorder) {
|
if (!isRecorder) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'You are not authorized to update this suggestion',
|
error: 'Only the assigned recorder can accept or reject this suggestion',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -291,6 +291,13 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject, navigate }) => {
|
|||||||
// Status badge
|
// Status badge
|
||||||
const getStatusBadge = () => {
|
const getStatusBadge = () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
case SUGGESTION_STATUS.PENDING:
|
||||||
|
return (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 rounded">
|
||||||
|
<Clock className="w-3 h-3" />
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
);
|
||||||
case SUGGESTION_STATUS.ACCEPTED:
|
case SUGGESTION_STATUS.ACCEPTED:
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 rounded">
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 rounded">
|
||||||
@@ -359,7 +366,23 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject, navigate }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{status === SUGGESTION_STATUS.PENDING ? (
|
{/* Dancers (TO_BE_RECORDED) can only view status, not update it */}
|
||||||
|
{type === SUGGESTION_TYPE.TO_BE_RECORDED ? (
|
||||||
|
<>
|
||||||
|
{getStatusBadge()}
|
||||||
|
{status === SUGGESTION_STATUS.ACCEPTED && match?.slug && (
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(`/matches/${match.slug}/chat`)}
|
||||||
|
className="p-2 text-primary-600 hover:bg-primary-50 rounded-md transition-colors"
|
||||||
|
title="Open Chat"
|
||||||
|
>
|
||||||
|
<MessageCircle className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
/* Recorders (TO_RECORD) can accept/reject */
|
||||||
|
status === SUGGESTION_STATUS.PENDING ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={onAccept}
|
onClick={onAccept}
|
||||||
@@ -389,6 +412,7 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject, navigate }) => {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user