From b1c3eea5bff3caa90e26628d963f47e7e530478a Mon Sep 17 00:00:00 2001 From: Enrico Bausenhart Date: Sat, 5 Jul 2025 13:06:58 +0200 Subject: [PATCH 1/2] feat: implement ui page for displaying matches --- client/src/App.tsx | 28 +- client/src/components/Chat.tsx | 18 - client/src/components/Invitations.tsx | 18 - client/src/components/Layout.tsx | 8 +- client/src/components/LunchMeetings.tsx | 39 -- client/src/components/Matches.tsx | 520 ++++++++++++++++++++++++ client/src/mocks/matches.json | 186 +++++++++ client/src/services/matchesService.ts | 243 +++++++++++ client/src/types/matches.ts | 32 ++ 9 files changed, 986 insertions(+), 106 deletions(-) delete mode 100644 client/src/components/Chat.tsx delete mode 100644 client/src/components/Invitations.tsx delete mode 100644 client/src/components/LunchMeetings.tsx create mode 100644 client/src/components/Matches.tsx create mode 100644 client/src/mocks/matches.json create mode 100644 client/src/services/matchesService.ts create mode 100644 client/src/types/matches.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 9ed47f06..29399d7a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,9 +4,7 @@ import Layout from './components/Layout'; import Login from './components/Login'; import Dashboard from './components/Dashboard'; import MatchRequests from './components/MatchRequests'; -import Invitations from './components/Invitations'; -import LunchMeetings from './components/LunchMeetings'; -import Chat from './components/Chat'; +import Matches from './components/Matches'; import { useAuth0 } from '@auth0/auth0-react'; import Profile from './components/Profile'; @@ -79,31 +77,11 @@ const App = ({ toggleColorMode, mode }: AppProps) => { } /> - - - - } - /> - - - - - - } - /> - - - + } diff --git a/client/src/components/Chat.tsx b/client/src/components/Chat.tsx deleted file mode 100644 index 6e18f6f5..00000000 --- a/client/src/components/Chat.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Typography, Paper, Box } from '@mui/material'; - -const Chat = () => { - return ( - - - Chat - - - - Coming Soon: Chat with your colleagues and meeting participants. - - - - ); -}; - -export default Chat; diff --git a/client/src/components/Invitations.tsx b/client/src/components/Invitations.tsx deleted file mode 100644 index 78d91673..00000000 --- a/client/src/components/Invitations.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Typography, Paper, Box } from '@mui/material'; - -const Invitations = () => { - return ( - - - Invitations - - - - Coming Soon: View and manage your meeting invitations. - - - - ); -}; - -export default Invitations; diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx index cb075a88..803d052c 100644 --- a/client/src/components/Layout.tsx +++ b/client/src/components/Layout.tsx @@ -15,9 +15,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; import AppBar from './AppBar'; import DashboardIcon from '@mui/icons-material/Dashboard'; import EventNoteIcon from '@mui/icons-material/EventNote'; -import MailIcon from '@mui/icons-material/Mail'; -import LunchDiningIcon from '@mui/icons-material/LunchDining'; -import ChatIcon from '@mui/icons-material/Chat'; +import RestaurantIcon from '@mui/icons-material/Restaurant'; import Brightness7Icon from '@mui/icons-material/Brightness7'; import Brightness2Icon from '@mui/icons-material/Brightness2'; @@ -32,9 +30,7 @@ interface LayoutProps { export const menuItems = [ { text: 'Dashboard', icon: , path: '/dashboard' }, { text: 'Match Requests', icon: , path: '/preferences' }, - { text: 'Invitations', icon: , path: '/invitations' }, - { text: 'Lunch Meetings', icon: , path: '/meetings' }, - { text: 'Chat', icon: , path: '/chat' }, + { text: 'Matches', icon: , path: '/matches' }, ]; const Layout = ({ children, toggleColorMode, mode }: LayoutProps) => { diff --git a/client/src/components/LunchMeetings.tsx b/client/src/components/LunchMeetings.tsx deleted file mode 100644 index 1dfbeb55..00000000 --- a/client/src/components/LunchMeetings.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Typography, Paper, Box } from '@mui/material'; - -const LunchMeetings = () => { - return ( - - - Lunch Meetings - - - - Coming Soon: Schedule and manage your lunch meetings. - - - - ); -}; - -export default LunchMeetings; diff --git a/client/src/components/Matches.tsx b/client/src/components/Matches.tsx new file mode 100644 index 00000000..141f1463 --- /dev/null +++ b/client/src/components/Matches.tsx @@ -0,0 +1,520 @@ +import React, { useState, useEffect } from 'react'; +import { + Typography, + Paper, + Box, + CircularProgress, + Alert, + Card, + CardContent, + Chip, + Grid, + Divider, + Button, + Avatar, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + List, + ListItem, + ListItemText, + Snackbar, +} from '@mui/material'; +import CheckIcon from '@mui/icons-material/Check'; +import HelpIcon from '@mui/icons-material/Help'; +import CloseIcon from '@mui/icons-material/Close'; +import { matchesService, MatchesServiceError } from '../services/matchesService'; +import { MatchesResponse, Match, MatchStatus, ConversationStarter } from '../types/matches'; + +const Matches = () => { + const [matches, setMatches] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + const [selectedStarters, setSelectedStarters] = useState([]); + const [rejectDialogOpen, setRejectDialogOpen] = useState(false); + const [selectedMatchId, setSelectedMatchId] = useState(null); + const [rejecting, setRejecting] = useState(false); + const [acceptDialogOpen, setAcceptDialogOpen] = useState(false); + const [accepting, setAccepting] = useState(false); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(''); + const [snackbarSeverity, setSnackbarSeverity] = useState<'success' | 'error'>('success'); + + useEffect(() => { + const fetchMatches = async () => { + try { + setLoading(true); + setError(null); + + // For development, use mock data + // In production, replace with: const data = await matchesService.getMatches(userId); + const data = await matchesService.getMockMatches(); + setMatches(data); + } catch (err) { + const serviceError = err as MatchesServiceError; + setError(serviceError.message || 'Failed to load matches'); + } finally { + setLoading(false); + } + }; + + fetchMatches(); + }, []); + + const getStatusColor = (status: Match['status']) => { + switch (status) { + case 'CONFIRMED': + return 'success'; + case 'SENT': + return 'info'; + case 'UNSENT': + return 'warning'; + case 'REJECTED': + return 'error'; + case 'EXPIRED': + return 'default'; + default: + return 'default'; + } + }; + + const getConfirmedCount = (userStatuses: MatchStatus[]) => { + return userStatuses.filter((user) => user.status === 'CONFIRMED').length; + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }); + }; + + const formatTime = (timeSlot: number) => { + // Assuming time slots are 1-16 representing different times + const times = [ + '8:00 AM', + '8:30 AM', + '9:00 AM', + '9:30 AM', + '10:00 AM', + '10:30 AM', + '11:00 AM', + '11:30 AM', + '12:00 PM', + '12:30 PM', + '1:00 PM', + '1:30 PM', + '2:00 PM', + '2:30 PM', + '3:00 PM', + '3:30 PM', + ]; + return times[timeSlot - 1] || 'Unknown time'; + }; + + const handleAccept = (matchId: string) => { + setSelectedMatchId(matchId); + setAcceptDialogOpen(true); + }; + + const handleConfirmAccept = async () => { + if (!selectedMatchId) return; + + try { + setAccepting(true); + + // For development, simulate API call + // In production, replace with: await matchesService.acceptMatch(selectedMatchId); + await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API delay + + // Update local state to reflect the acceptance + if (matches) { + const updatedMatches = { + ...matches, + matches: matches.matches.map((match) => + match.matchID === selectedMatchId ? { ...match, status: 'CONFIRMED' as const } : match + ), + }; + setMatches(updatedMatches); + } + + setSnackbarMessage('Match accepted successfully'); + setSnackbarSeverity('success'); + setSnackbarOpen(true); + } catch (err) { + const serviceError = err as MatchesServiceError; + setSnackbarMessage(serviceError.message || 'Failed to accept match'); + setSnackbarSeverity('error'); + setSnackbarOpen(true); + } finally { + setAccepting(false); + setAcceptDialogOpen(false); + setSelectedMatchId(null); + } + }; + + const handleCloseAcceptDialog = () => { + setAcceptDialogOpen(false); + setSelectedMatchId(null); + }; + + const handleReject = (matchId: string) => { + setSelectedMatchId(matchId); + setRejectDialogOpen(true); + }; + + const handleConfirmReject = async () => { + if (!selectedMatchId) return; + + try { + setRejecting(true); + + // For development, simulate API call + // In production, replace with: await matchesService.rejectMatch(selectedMatchId); + await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API delay + + // Update local state to reflect the rejection + if (matches) { + const updatedMatches = { + ...matches, + matches: matches.matches.map((match) => + match.matchID === selectedMatchId ? { ...match, status: 'REJECTED' as const } : match + ), + }; + setMatches(updatedMatches); + } + + setSnackbarMessage('Match rejected successfully'); + setSnackbarSeverity('success'); + setSnackbarOpen(true); + } catch (err) { + const serviceError = err as MatchesServiceError; + setSnackbarMessage(serviceError.message || 'Failed to reject match'); + setSnackbarSeverity('error'); + setSnackbarOpen(true); + } finally { + setRejecting(false); + setRejectDialogOpen(false); + setSelectedMatchId(null); + } + }; + + const handleCloseRejectDialog = () => { + setRejectDialogOpen(false); + setSelectedMatchId(null); + }; + + const handleViewAllStarters = (starters: ConversationStarter[]) => { + setSelectedStarters(starters); + setDialogOpen(true); + }; + + const handleCloseDialog = () => { + setDialogOpen(false); + setSelectedStarters([]); + }; + + const handleCloseSnackbar = () => { + setSnackbarOpen(false); + }; + + if (loading) { + return ( + + + + ); + } + + if (error) { + return ( + + + Matches + + + {error} + + + ); + } + + return ( + + + Matches + + + {matches?.matches.length === 0 ? ( + + + No matches found. Check back later for new lunch opportunities! + + + ) : ( + + {matches?.matches.map((match) => ( + + + + + + {formatDate(match.group.date)} + + + + + {/* Accept/Reject buttons for SENT status */} + {match.status === 'SENT' && ( + + + + + )} + + + + Time: {formatTime(match.group.time)} + + + Location: {match.group.location} + + + + Participants: + + + + + + + + + + Conversation Starters: + + + {match.group.conversationStarters.conversationsStarters + .slice(0, 2) + .map((starter, index) => ( + + "{starter.prompt}" + + ))} + {match.group.conversationStarters.conversationsStarters.length > 2 && ( + + )} + + + + + + ))} + + )} + + {/* Conversation Starters Dialog */} + + + Conversation Starters + + + + {selectedStarters.map((starter, index) => ( + + + "{starter.prompt}" + + } + /> + + ))} + + + + + + + + {/* Accept Confirmation Dialog */} + + Accept Match + + Are you sure you want to accept this lunch match? This action cannot be undone. + + + + + + + + {/* Reject Confirmation Dialog */} + + Reject Match + + Are you sure you want to reject this lunch match? This action cannot be undone. + + + + + + + + {/* Snackbar for notifications */} + + + {snackbarMessage} + + + + ); +}; + +export default Matches; diff --git a/client/src/mocks/matches.json b/client/src/mocks/matches.json new file mode 100644 index 00000000..bdaaaacf --- /dev/null +++ b/client/src/mocks/matches.json @@ -0,0 +1,186 @@ +{ + "matches": [ + { + "matchID": "6832a21f-4dc8-43fb-b3db-b17455998d91", + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "CONFIRMED", + "group": { + "groupID": "ec414289-a6cd-4a76-a6d7-c7f42c7f1517", + "date": "2024-12-20", + "time": 3, + "location": "GARCHING", + "userStatus": [ + { + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "CONFIRMED" + }, + { + "userID": "7a8b9c0d-1e2f-3g4h-5i6j-7k8l9m0n1o2p", + "status": "CONFIRMED" + }, + { + "userID": "3d4e5f6g-7h8i-9j0k-1l2m-3n4o5p6q7r8s", + "status": "CONFIRMED" + } + ], + "conversationStarters": { + "conversationsStarters": [ + { + "prompt": "What's your favorite study spot on campus?" + }, + { + "prompt": "Have you tried the new menu items at the mensa?" + }, + { + "prompt": "What's the most interesting course you're taking this semester?" + } + ] + } + } + }, + { + "matchID": "9a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p", + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "SENT", + "group": { + "groupID": "f1g2h3i4-j5k6-l7m8-n9o0-p1q2r3s4t5u6v", + "date": "2024-12-22", + "time": 5, + "location": "ARCISSTR", + "userStatus": [ + { + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "SENT" + }, + { + "userID": "w7x8y9z0-1a2b-3c4d-5e6f-7g8h9i0j1k2l", + "status": "CONFIRMED" + }, + { + "userID": "m3n4o5p6-7q8r-9s0t-1u2v-3w4x5y6z7a8b", + "status": "SENT" + } + ], + "conversationStarters": { + "conversationsStarters": [ + { + "prompt": "What's your major and why did you choose it?" + }, + { + "prompt": "Do you have any tips for surviving exam season?" + } + ] + } + } + }, + { + "matchID": "c9d0e1f2-3g4h-5i6j-7k8l-9m0n1o2p3q4r5", + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "UNSENT", + "group": { + "groupID": "s6t7u8v9-0w1x-2y3z-4a5b-6c7d8e9f0g1h2", + "date": "2024-12-25", + "time": 2, + "location": "GARCHING", + "userStatus": [ + { + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "UNSENT" + }, + { + "userID": "i3j4k5l6-7m8n-9o0p-1q2r-3s4t5u6v7w8x", + "status": "UNSENT" + }, + { + "userID": "y9z0a1b2-3c4d-5e6f-7g8h-9i0j1k2l3m4n", + "status": "UNSENT" + }, + { + "userID": "o5p6q7r8-9s0t-1u2v-3w4x-5y6z7a8b9c0d", + "status": "UNSENT" + } + ], + "conversationStarters": { + "conversationsStarters": [ + { + "prompt": "What are your plans for the upcoming holidays?" + }, + { + "prompt": "Have you discovered any hidden gems on campus?" + }, + { + "prompt": "What's the best advice you've received as a student?" + } + ] + } + } + }, + { + "matchID": "e1f2g3h4-5i6j-7k8l-9m0n-1o2p3q4r5s6t7", + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "REJECTED", + "group": { + "groupID": "u8v9w0x1-2y3z-4a5b-6c7d-8e9f0g1h2i3j4", + "date": "2024-12-18", + "time": 4, + "location": "ARCISSTR", + "userStatus": [ + { + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "REJECTED" + }, + { + "userID": "k5l6m7n8-9o0p-1q2r-3s4t-5u6v7w8x9y0z1", + "status": "CONFIRMED" + }, + { + "userID": "a2b3c4d5-6e7f-8g9h-0i1j-2k3l4m5n6o7p8", + "status": "CONFIRMED" + } + ], + "conversationStarters": { + "conversationsStarters": [ + { + "prompt": "What's your favorite way to relax after a long day of studying?" + } + ] + } + } + }, + { + "matchID": "q9r0s1t2-3u4v-5w6x-7y8z-9a0b1c2d3e4f5g6", + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "EXPIRED", + "group": { + "groupID": "h7i8j9k0-1l2m-3n4o-5p6q-7r8s9t0u1v2w3x", + "date": "2024-12-15", + "time": 1, + "location": "GARCHING", + "userStatus": [ + { + "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + "status": "EXPIRED" + }, + { + "userID": "y4z5a6b7-8c9d-0e1f-2g3h-4i5j6k7l8m9n0", + "status": "EXPIRED" + }, + { + "userID": "o1p2q3r4-5s6t-7u8v-9w0x-1y2z3a4b5c6d7", + "status": "EXPIRED" + } + ], + "conversationStarters": { + "conversationsStarters": [ + { + "prompt": "What was the highlight of your week?" + }, + { + "prompt": "Any interesting projects you're working on?" + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/client/src/services/matchesService.ts b/client/src/services/matchesService.ts new file mode 100644 index 00000000..37e55cd9 --- /dev/null +++ b/client/src/services/matchesService.ts @@ -0,0 +1,243 @@ +import { MatchesResponse, Match } from '../types/matches'; + +const API_BASE_URL = 'https://meetatmensa.com/api/v2'; + +export interface MatchesServiceError { + message: string; + status?: number; +} + +export const matchesService = { + /** + * Fetch all matches for a specific user + * @param userId - The unique ID of the user + * @returns Promise - The matches data + * @throws MatchesServiceError - If the request fails + */ + async getMatches(userId: string): Promise { + try { + const response = await fetch(`${API_BASE_URL}/matching/matches/${userId}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + // Add authorization header if needed + // 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: MatchesResponse = await response.json(); + return data; + } catch (error) { + if (error instanceof Error) { + throw { + message: `Failed to fetch matches: ${error.message}`, + status: error instanceof Response ? error.status : undefined, + } as MatchesServiceError; + } + throw { + message: 'An unexpected error occurred while fetching matches', + } as MatchesServiceError; + } + }, + + /** + * Reject a match + * @param matchId - The unique ID of the match to reject + * @returns Promise - Success response + * @throws MatchesServiceError - If the request fails + */ + async rejectMatch(matchId: string): Promise { + try { + const response = await fetch(`${API_BASE_URL}/matching/rsvp/${matchId}/reject`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + // Add authorization header if needed + // 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error) { + if (error instanceof Error) { + throw { + message: `Failed to reject match: ${error.message}`, + status: error instanceof Response ? error.status : undefined, + } as MatchesServiceError; + } + throw { + message: 'An unexpected error occurred while rejecting the match', + } as MatchesServiceError; + } + }, + + /** + * Accept a match + * @param matchId - The unique ID of the match to accept + * @returns Promise - Success response + * @throws MatchesServiceError - If the request fails + */ + async acceptMatch(matchId: string): Promise { + try { + const response = await fetch(`${API_BASE_URL}/matching/rsvp/${matchId}/accept`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + // Add authorization header if needed + // 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + } catch (error) { + if (error instanceof Error) { + throw { + message: `Failed to accept match: ${error.message}`, + status: error instanceof Response ? error.status : undefined, + } as MatchesServiceError; + } + throw { + message: 'An unexpected error occurred while accepting the match', + } as MatchesServiceError; + } + }, + + /** + * Get mock matches data for development/testing + * @returns Promise - The mock matches data + */ + async getMockMatches(): Promise { + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 500)); + + const mockData: MatchesResponse = { + matches: [ + { + matchID: "6832a21f-4dc8-43fb-b3db-b17455998d91", + userID: "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + status: "CONFIRMED", + group: { + groupID: "ec414289-a6cd-4a76-a6d7-c7f42c7f1517", + date: "2024-12-20", + time: 3, + location: "GARCHING", + userStatus: [ + { + userID: "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + status: "CONFIRMED" + }, + { + userID: "7a8b9c0d-1e2f-3g4h-5i6j-7k8l9m0n1o2p", + status: "CONFIRMED" + }, + { + userID: "3d4e5f6g-7h8i-9j0k-1l2m-3n4o5p6q7r8s", + status: "CONFIRMED" + } + ], + conversationStarters: { + conversationsStarters: [ + { + prompt: "What's your favorite study spot on campus?" + }, + { + prompt: "Have you tried the new menu items at the mensa?" + }, + { + prompt: "What's the most interesting course you're taking this semester?" + } + ] + } + } + }, + { + matchID: "9a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p", + userID: "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + status: "SENT", + group: { + groupID: "f1g2h3i4-j5k6-l7m8-n9o0-p1q2r3s4t5u6v", + date: "2024-12-22", + time: 5, + location: "ARCISSTR", + userStatus: [ + { + userID: "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + status: "SENT" + }, + { + userID: "w7x8y9z0-1a2b-3c4d-5e6f-7g8h9i0j1k2l", + status: "CONFIRMED" + }, + { + userID: "m3n4o5p6-7q8r-9s0t-1u2v-3w4x5y6z7a8b", + status: "SENT" + } + ], + conversationStarters: { + conversationsStarters: [ + { + prompt: "What's your major and why did you choose it?" + }, + { + prompt: "Do you have any tips for surviving exam season?" + } + ] + } + } + }, + { + matchID: "c9d0e1f2-3g4h-5i6j-7k8l-9m0n1o2p3q4r5", + userID: "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + status: "UNSENT", + group: { + groupID: "s6t7u8v9-0w1x-2y3z-4a5b-6c7d8e9f0g1h2", + date: "2024-12-25", + time: 2, + location: "GARCHING", + userStatus: [ + { + userID: "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa", + status: "UNSENT" + }, + { + userID: "i3j4k5l6-7m8n-9o0p-1q2r-3s4t5u6v7w8x", + status: "UNSENT" + }, + { + userID: "y9z0a1b2-3c4d-5e6f-7g8h-9i0j1k2l3m4n", + status: "UNSENT" + }, + { + userID: "o5p6q7r8-9s0t-1u2v-3w4x-5y6z7a8b9c0d", + status: "UNSENT" + } + ], + conversationStarters: { + conversationsStarters: [ + { + prompt: "What are your plans for the upcoming holidays?" + }, + { + prompt: "Have you discovered any hidden gems on campus?" + }, + { + prompt: "What's the best advice you've received as a student?" + } + ] + } + } + } + ] + }; + + return mockData; + }, +}; \ No newline at end of file diff --git a/client/src/types/matches.ts b/client/src/types/matches.ts new file mode 100644 index 00000000..e01b3404 --- /dev/null +++ b/client/src/types/matches.ts @@ -0,0 +1,32 @@ +export interface ConversationStarter { + prompt: string; +} + +export interface ConversationStarterCollection { + conversationsStarters: ConversationStarter[]; +} + +export interface MatchStatus { + userID: string; + status: 'UNSENT' | 'SENT' | 'CONFIRMED' | 'REJECTED' | 'EXPIRED'; +} + +export interface Group { + groupID: string; + date: string; + time: number; + location: 'GARCHING' | 'ARCISSTR'; + userStatus: MatchStatus[]; + conversationStarters: ConversationStarterCollection; +} + +export interface Match { + matchID: string; + userID: string; + status: 'UNSENT' | 'SENT' | 'CONFIRMED' | 'REJECTED' | 'EXPIRED'; + group: Group; +} + +export interface MatchesResponse { + matches: Match[]; +} \ No newline at end of file From 760c0c39bff35099b066a796873385334594eda5 Mon Sep 17 00:00:00 2001 From: Enrico Bausenhart Date: Sun, 6 Jul 2025 17:14:53 +0200 Subject: [PATCH 2/2] fix: typescript errors that blocked docker build --- client/src/components/Matches.tsx | 6 +----- client/src/services/matchesService.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/client/src/components/Matches.tsx b/client/src/components/Matches.tsx index 141f1463..e5a60ba4 100644 --- a/client/src/components/Matches.tsx +++ b/client/src/components/Matches.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { Typography, Paper, @@ -11,7 +11,6 @@ import { Grid, Divider, Button, - Avatar, Dialog, DialogTitle, DialogContent, @@ -21,9 +20,6 @@ import { ListItemText, Snackbar, } from '@mui/material'; -import CheckIcon from '@mui/icons-material/Check'; -import HelpIcon from '@mui/icons-material/Help'; -import CloseIcon from '@mui/icons-material/Close'; import { matchesService, MatchesServiceError } from '../services/matchesService'; import { MatchesResponse, Match, MatchStatus, ConversationStarter } from '../types/matches'; diff --git a/client/src/services/matchesService.ts b/client/src/services/matchesService.ts index 37e55cd9..48e78ec2 100644 --- a/client/src/services/matchesService.ts +++ b/client/src/services/matchesService.ts @@ -1,4 +1,4 @@ -import { MatchesResponse, Match } from '../types/matches'; +import { MatchesResponse } from '../types/matches'; const API_BASE_URL = 'https://meetatmensa.com/api/v2';