refactor(frontend): replace status string literals with constants

- Create constants/statuses.js with MATCH_STATUS, SUGGESTION_STATUS, MATCH_FILTER
- Update MatchCard, MatchesPage, HistoryPage, RatePartnerPage to use MATCH_STATUS
- Update RecordingTab to use SUGGESTION_STATUS
- Update Navbar to use MATCH_STATUS for API calls
This commit is contained in:
Radosław Gierwiało
2025-11-23 22:21:12 +01:00
parent 93ff331bfb
commit b3a6d39d7a
8 changed files with 64 additions and 29 deletions

View File

@@ -5,6 +5,7 @@ import Avatar from '../common/Avatar';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { matchesAPI } from '../../services/api'; import { matchesAPI } from '../../services/api';
import { connectSocket, disconnectSocket, getSocket } from '../../services/socket'; import { connectSocket, disconnectSocket, getSocket } from '../../services/socket';
import { MATCH_STATUS } from '../../constants';
const Navbar = () => { const Navbar = () => {
const { user, logout } = useAuth(); const { user, logout } = useAuth();
@@ -48,7 +49,7 @@ const Navbar = () => {
const loadPendingMatches = async () => { const loadPendingMatches = async () => {
try { try {
const result = await matchesAPI.getMatches(null, 'pending'); const result = await matchesAPI.getMatches(null, MATCH_STATUS.PENDING);
// Only count incoming requests (where user is not the initiator) // Only count incoming requests (where user is not the initiator)
const incomingCount = (result.data || []).filter(m => !m.isInitiator).length; const incomingCount = (result.data || []).filter(m => !m.isInitiator).length;
setPendingMatchesCount(incomingCount); setPendingMatchesCount(incomingCount);

View File

@@ -1,14 +1,15 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { MessageCircle, Check, X, Loader2, Calendar, MapPin } from 'lucide-react'; import { MessageCircle, Check, X, Loader2, Calendar, MapPin } from 'lucide-react';
import { MATCH_STATUS } from '../../constants';
/** /**
* Match card for matches list page * Match card for matches list page
* Shows match details with accept/reject/chat actions * Shows match details with accept/reject/chat actions
*/ */
const MatchCard = ({ match, onAccept, onReject, onOpenChat, processing }) => { const MatchCard = ({ match, onAccept, onReject, onOpenChat, processing }) => {
const isIncoming = !match.isInitiator && match.status === 'pending'; const isIncoming = !match.isInitiator && match.status === MATCH_STATUS.PENDING;
const isOutgoing = match.isInitiator && match.status === 'pending'; const isOutgoing = match.isInitiator && match.status === MATCH_STATUS.PENDING;
const isAccepted = match.status === 'accepted'; const isAccepted = match.status === MATCH_STATUS.ACCEPTED;
return ( return (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 hover:shadow-md transition-shadow"> <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 hover:shadow-md transition-shadow">

View File

@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { Video, VideoOff, Clock, CheckCircle, XCircle, AlertTriangle, RefreshCw } from 'lucide-react'; import { Video, VideoOff, Clock, CheckCircle, XCircle, AlertTriangle, RefreshCw } from 'lucide-react';
import { matchingAPI } from '../../services/api'; import { matchingAPI } from '../../services/api';
import Avatar from '../common/Avatar'; import Avatar from '../common/Avatar';
import { SUGGESTION_STATUS } from '../../constants';
/** /**
* RecordingTab - Main component for managing recording partnerships * RecordingTab - Main component for managing recording partnerships
@@ -199,8 +200,8 @@ const RecordingTab = ({ slug, event, myHeats }) => {
key={suggestion.id || suggestion.heat?.id} key={suggestion.id || suggestion.heat?.id}
suggestion={suggestion} suggestion={suggestion}
type="toBeRecorded" type="toBeRecorded"
onAccept={() => handleUpdateStatus(suggestion.id, 'accepted')} onAccept={() => handleUpdateStatus(suggestion.id, SUGGESTION_STATUS.ACCEPTED)}
onReject={() => handleUpdateStatus(suggestion.id, 'rejected')} onReject={() => handleUpdateStatus(suggestion.id, SUGGESTION_STATUS.REJECTED)}
/> />
))} ))}
</div> </div>
@@ -225,8 +226,8 @@ const RecordingTab = ({ slug, event, myHeats }) => {
key={suggestion.id || suggestion.heat?.id} key={suggestion.id || suggestion.heat?.id}
suggestion={suggestion} suggestion={suggestion}
type="toRecord" type="toRecord"
onAccept={() => handleUpdateStatus(suggestion.id, 'accepted')} onAccept={() => handleUpdateStatus(suggestion.id, SUGGESTION_STATUS.ACCEPTED)}
onReject={() => handleUpdateStatus(suggestion.id, 'rejected')} onReject={() => handleUpdateStatus(suggestion.id, SUGGESTION_STATUS.REJECTED)}
/> />
))} ))}
</div> </div>
@@ -278,21 +279,21 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject }) => {
// Status badge // Status badge
const getStatusBadge = () => { const getStatusBadge = () => {
switch (status) { switch (status) {
case '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">
<CheckCircle className="w-3 h-3" /> <CheckCircle className="w-3 h-3" />
Zaakceptowano Zaakceptowano
</span> </span>
); );
case 'rejected': case SUGGESTION_STATUS.REJECTED:
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-red-100 text-red-800 rounded"> <span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-red-100 text-red-800 rounded">
<XCircle className="w-3 h-3" /> <XCircle className="w-3 h-3" />
Odrzucono Odrzucono
</span> </span>
); );
case 'not_found': case SUGGESTION_STATUS.NOT_FOUND:
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-amber-100 text-amber-800 rounded"> <span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-amber-100 text-amber-800 rounded">
<AlertTriangle className="w-3 h-3" /> <AlertTriangle className="w-3 h-3" />
@@ -305,7 +306,7 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject }) => {
}; };
// No recorder found // No recorder found
if (status === 'not_found') { if (status === SUGGESTION_STATUS.NOT_FOUND) {
return ( return (
<div className="flex items-center justify-between p-3 bg-amber-50 border border-amber-200 rounded-lg"> <div className="flex items-center justify-between p-3 bg-amber-50 border border-amber-200 rounded-lg">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -346,7 +347,7 @@ const SuggestionCard = ({ suggestion, type, onAccept, onReject }) => {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{status === 'pending' ? ( {status === SUGGESTION_STATUS.PENDING ? (
<> <>
<button <button
onClick={onAccept} onClick={onAccept}

View File

@@ -0,0 +1 @@
export { MATCH_STATUS, SUGGESTION_STATUS, MATCH_FILTER } from './statuses';

View File

@@ -0,0 +1,28 @@
/**
* Match status constants
*/
export const MATCH_STATUS = {
PENDING: 'pending',
ACCEPTED: 'accepted',
REJECTED: 'rejected',
COMPLETED: 'completed',
};
/**
* Suggestion status constants (for auto-matching)
*/
export const SUGGESTION_STATUS = {
PENDING: 'pending',
ACCEPTED: 'accepted',
REJECTED: 'rejected',
NOT_FOUND: 'not_found',
};
/**
* Filter options for matches list
*/
export const MATCH_FILTER = {
ALL: 'all',
PENDING: 'pending',
ACCEPTED: 'accepted',
};

View File

@@ -1,6 +1,7 @@
import Layout from '../components/layout/Layout'; import Layout from '../components/layout/Layout';
import { mockMatches, mockRatings } from '../mocks/matches'; import { mockMatches, mockRatings } from '../mocks/matches';
import { Calendar, MapPin, Star, MessageCircle } from 'lucide-react'; import { Calendar, MapPin, Star, MessageCircle } from 'lucide-react';
import { MATCH_STATUS } from '../constants';
const HistoryPage = () => { const HistoryPage = () => {
return ( return (
@@ -42,12 +43,12 @@ const HistoryPage = () => {
<div> <div>
<span <span
className={`px-3 py-1 rounded-full text-sm font-medium ${ className={`px-3 py-1 rounded-full text-sm font-medium ${
match.status === 'completed' match.status === MATCH_STATUS.COMPLETED
? 'bg-green-100 text-green-800' ? 'bg-green-100 text-green-800'
: 'bg-blue-100 text-blue-800' : 'bg-blue-100 text-blue-800'
}`} }`}
> >
{match.status === 'completed' ? 'Completed' : 'Active'} {match.status === MATCH_STATUS.COMPLETED ? 'Completed' : 'Active'}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -6,13 +6,14 @@ import { matchesAPI } from '../services/api';
import { Loader2, Users } from 'lucide-react'; import { Loader2, Users } from 'lucide-react';
import { connectSocket, disconnectSocket, getSocket } from '../services/socket'; import { connectSocket, disconnectSocket, getSocket } from '../services/socket';
import { MatchCard } from '../components/matches'; import { MatchCard } from '../components/matches';
import { MATCH_STATUS, MATCH_FILTER } from '../constants';
const MatchesPage = () => { const MatchesPage = () => {
const { user } = useAuth(); const { user } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const [matches, setMatches] = useState([]); const [matches, setMatches] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('all'); // 'all', 'pending', 'accepted' const [filter, setFilter] = useState(MATCH_FILTER.ALL);
const [processingMatchId, setProcessingMatchId] = useState(null); const [processingMatchId, setProcessingMatchId] = useState(null);
useEffect(() => { useEffect(() => {
@@ -108,20 +109,20 @@ const MatchesPage = () => {
}; };
const handleOpenChat = (match) => { const handleOpenChat = (match) => {
if (match.status === 'accepted' && match.roomId) { if (match.status === MATCH_STATUS.ACCEPTED && match.roomId) {
navigate(`/matches/${match.slug}/chat`); navigate(`/matches/${match.slug}/chat`);
} }
}; };
// Filter matches based on selected filter // Filter matches based on selected filter
const filteredMatches = matches.filter(match => { const filteredMatches = matches.filter(match => {
if (filter === 'all') return true; if (filter === MATCH_FILTER.ALL) return true;
return match.status === filter; return match.status === filter;
}); });
// Separate pending incoming matches (where user is recipient) // Separate pending incoming matches (where user is recipient)
const pendingIncoming = filteredMatches.filter(m => m.status === 'pending' && !m.isInitiator); const pendingIncoming = filteredMatches.filter(m => m.status === MATCH_STATUS.PENDING && !m.isInitiator);
const otherMatches = filteredMatches.filter(m => !(m.status === 'pending' && !m.isInitiator)); const otherMatches = filteredMatches.filter(m => !(m.status === MATCH_STATUS.PENDING && !m.isInitiator));
return ( return (
<Layout> <Layout>
@@ -136,9 +137,9 @@ const MatchesPage = () => {
{/* Filter Tabs */} {/* Filter Tabs */}
<div className="bg-white rounded-lg shadow-sm p-1 mb-6 flex gap-1"> <div className="bg-white rounded-lg shadow-sm p-1 mb-6 flex gap-1">
<button <button
onClick={() => setFilter('all')} onClick={() => setFilter(MATCH_FILTER.ALL)}
className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${ className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
filter === 'all' filter === MATCH_FILTER.ALL
? 'bg-primary-600 text-white' ? 'bg-primary-600 text-white'
: 'text-gray-600 hover:bg-gray-100' : 'text-gray-600 hover:bg-gray-100'
}`} }`}
@@ -146,24 +147,24 @@ const MatchesPage = () => {
All ({matches.length}) All ({matches.length})
</button> </button>
<button <button
onClick={() => setFilter('pending')} onClick={() => setFilter(MATCH_FILTER.PENDING)}
className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${ className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
filter === 'pending' filter === MATCH_FILTER.PENDING
? 'bg-primary-600 text-white' ? 'bg-primary-600 text-white'
: 'text-gray-600 hover:bg-gray-100' : 'text-gray-600 hover:bg-gray-100'
}`} }`}
> >
Pending ({matches.filter(m => m.status === 'pending').length}) Pending ({matches.filter(m => m.status === MATCH_STATUS.PENDING).length})
</button> </button>
<button <button
onClick={() => setFilter('accepted')} onClick={() => setFilter(MATCH_FILTER.ACCEPTED)}
className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${ className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
filter === 'accepted' filter === MATCH_FILTER.ACCEPTED
? 'bg-primary-600 text-white' ? 'bg-primary-600 text-white'
: 'text-gray-600 hover:bg-gray-100' : 'text-gray-600 hover:bg-gray-100'
}`} }`}
> >
Active ({matches.filter(m => m.status === 'accepted').length}) Active ({matches.filter(m => m.status === MATCH_STATUS.ACCEPTED).length})
</button> </button>
</div> </div>

View File

@@ -3,6 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom';
import Layout from '../components/layout/Layout'; import Layout from '../components/layout/Layout';
import { matchesAPI } from '../services/api'; import { matchesAPI } from '../services/api';
import { Star, Loader2 } from 'lucide-react'; import { Star, Loader2 } from 'lucide-react';
import { MATCH_STATUS } from '../constants';
const RatePartnerPage = () => { const RatePartnerPage = () => {
const { slug } = useParams(); const { slug } = useParams();
@@ -24,7 +25,7 @@ const RatePartnerPage = () => {
setMatch(result.data); setMatch(result.data);
// Check if this match can be rated // Check if this match can be rated
if (result.data.status !== 'accepted' && result.data.status !== 'completed') { if (result.data.status !== MATCH_STATUS.ACCEPTED && result.data.status !== MATCH_STATUS.COMPLETED) {
alert('This match must be accepted before rating.'); alert('This match must be accepted before rating.');
navigate('/matches'); navigate('/matches');
return; return;