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:
Radosław Gierwiało
2025-11-30 14:54:09 +01:00
parent 560ff1edc1
commit 2e49fa5c62
2 changed files with 46 additions and 22 deletions

View File

@@ -1249,14 +1249,14 @@ router.put('/:slug/match-suggestions/:suggestionId/status', authenticate, async
});
}
// Check authorization: only dancer or recorder can update status
const isDancer = suggestion.heat.userId === userId;
// Check authorization: only recorder can update status (MVP decision)
// Dancer can only view who is assigned to record them
const isRecorder = suggestion.recorderId === userId;
if (!isDancer && !isRecorder) {
if (!isRecorder) {
return res.status(403).json({
success: false,
error: 'You are not authorized to update this suggestion',
error: 'Only the assigned recorder can accept or reject this suggestion',
});
}

View File

@@ -291,6 +291,13 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject, navigate }) => {
// Status badge
const getStatusBadge = () => {
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:
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">
@@ -359,24 +366,8 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject, navigate }) => {
</div>
<div className="flex items-center gap-2">
{status === SUGGESTION_STATUS.PENDING ? (
<>
<button
onClick={onAccept}
className="p-2 text-green-600 hover:bg-green-50 rounded-md transition-colors"
title="Accept"
>
<CheckCircle className="w-5 h-5" />
</button>
<button
onClick={onReject}
className="p-2 text-red-600 hover:bg-red-50 rounded-md transition-colors"
title="Reject"
>
<XCircle className="w-5 h-5" />
</button>
</>
) : (
{/* Dancers (TO_BE_RECORDED) can only view status, not update it */}
{type === SUGGESTION_TYPE.TO_BE_RECORDED ? (
<>
{getStatusBadge()}
{status === SUGGESTION_STATUS.ACCEPTED && match?.slug && (
@@ -389,6 +380,39 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject, navigate }) => {
</button>
)}
</>
) : (
/* Recorders (TO_RECORD) can accept/reject */
status === SUGGESTION_STATUS.PENDING ? (
<>
<button
onClick={onAccept}
className="p-2 text-green-600 hover:bg-green-50 rounded-md transition-colors"
title="Accept"
>
<CheckCircle className="w-5 h-5" />
</button>
<button
onClick={onReject}
className="p-2 text-red-600 hover:bg-red-50 rounded-md transition-colors"
title="Reject"
>
<XCircle className="w-5 h-5" />
</button>
</>
) : (
<>
{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>
)}
</>
)
)}
</div>
</div>