Skip to content

Commit

Permalink
Merge pull request #278 from flickmatch/getEventById
Browse files Browse the repository at this point in the history
Get event by Id
abhimanyu-fm authored Aug 17, 2024
2 parents d09dfa6 + c18fb7f commit 8ebf56a
Showing 13 changed files with 487 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@
import static java.lang.String.format;

@Service
@Log4j2

public class EventBuilder {

private static final String CLIENT_REFERENCE_ID = "?client_reference_id=";
125 changes: 125 additions & 0 deletions react-fm/src/pages/eventPage/EventPage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
::-webkit-scrollbar {
display: none;
}

@font-face {
font-family: 'CustomFont';
src: url('../../../otf/MontserratAlt1-Medium.otf') format('opentype');
}

body {
font-family: 'Montserrat Alt 1', sans-serif;
font-weight: 600;
}
.title {
font-size: 36px;
color: #4ce95a;
font-weight: 600;
text-align: center;
font-family: 'CustomFont', Arial, sans-serif;
}
.loading {
position: relative;
height: 100vh;
margin: 4rem;
}

.parentContainer {
position: relative;
margin: 1rem;
}

.portraitContainer {
margin-left: 0.5rem;
margin-right: 0.5rem;
position: relative;
height: 100vh;
}

.parent,
.portraitParent {
margin: 100px auto;
width: 80%;
}

.portraitParent {
width: 90%;
}

.accordion {
background: #1a1919;
margin-bottom: 35px;
border-radius: 10px;
}

.mobileAccordion {
margin-bottom: 20px;
}

.linkTag {
color: #fff;
}

.flexbox {
flex-flow: column;
width: 100%;
}

.venue {
width: 100%;
align-items: center;
justify-content: space-between;
margin-bottom: 5px;
position: relative;
}

.venue Button {
background: #4ce95a !important;
color: black;
}

.sportsIcon {
margin-right: 5px;
color: #4ce95a;
}

.box {
margin-top: 20px;
margin-left: 25px;
}

.reservedPlayersContainer {
display: flex;
}

.editIcon {
margin-left: 10px;
cursor: pointer;
padding: 8px;
font-size: 40px;
margin-top: -7px;
}

.editIcon:hover {
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px,
rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px,
rgba(0, 0, 0, 0.09) 0px -3px 5px;
}

.reservedPlayers {
color: #4ce95a;
margin-bottom: 15px;
font-size: 20px;
font-weight: 600;
}

.waitListPlayers {
color: #4ce95a;
margin-bottom: 15px;
font-size: 20px;
font-weight: 600;
}

.blink {
background-color: #342e37;
}
299 changes: 299 additions & 0 deletions react-fm/src/pages/eventPage/EventPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

/* eslint-disable no-useless-catch */
// /* eslint-disable @typescript-eslint/no-unused-vars */
// /* eslint-disable no-useless-catch */
// // /* eslint-disable no-useless-catch */
import React, { useEffect, useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';

import BorderColorIcon from '@mui/icons-material/BorderColor';
import SportsSoccerIcon from '@mui/icons-material/SportsSoccer';
import { Accordion, AccordionDetails, AccordionSummary, Box, Typography } from '@mui/material';
import Grid from '@mui/material/Grid';

import Loading from '@/components/Loading';
import { FlexBox } from '@/components/styled';
import useOrientation from '@/hooks/useOrientation';

import { VenueName } from '../matchQueues/VenueName';
import { Cities } from '../matchQueues/eventsComponents/Cities';
import { EventsCard } from '../matchQueues/eventsComponents/Events';
import { JoinNow } from '../matchQueues/eventsComponents/JoinNow';
import { PlayerDetails } from '../matchQueues/eventsComponents/Players';
import mapCityData from '../matchQueues/map';
import NotFound from '../notFound';
import styles from './EventPage.module.scss';
import type { PlayerDetail } from './EventPage.types';
import type { Event } from './EventPage.types';
import { apiUrl } from './constants';

function validateUniqueEventId(id: string) {
// Regular expression for the pattern: Digit-Date-Digit
const regex = /^[0-9]-\d{4}-\d{2}-\d{2}-[0-9]$/;
return regex.test(id);
}

const getEventById = async (uniqueEventId: string): Promise<Event | null> => {
// Validate the uniqueEventId
if (!validateUniqueEventId(uniqueEventId)) {
throw new Error('Invalid uniqueEventId');
}
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `query event {
event(uniqueEventId: "${uniqueEventId}") {
currency
startTime
endTime
eventId
uniqueEventId
displayTitle
venueLocationLink
charges
date
time
venueName
reservedPlayersCount
waitListPlayersCount
stripePaymentUrl
reservedPlayersList {
displayName
}
waitListPlayers{
displayName
}
}
}
`,
}),
});

const result = await response.json();

// console.log(result);
if (result.errors) {
throw new Error(result.errors[0].message);
}
return result.data.event;
} catch (error) {
throw error;
}
};

const EventPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const isPortrait = useOrientation();
const location = useLocation();
const highLighted = false;
const [isAdminMode, setIsAdminMode] = useState(false);
const [event, setEvent] = useState<Event | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!id) {
setError('Event ID is missing.');
setLoading(false);
return;
}

const fetchEvent = async () => {
try {
setLoading(true);
const eventData = await getEventById(id);
setEvent(eventData);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchEvent();
}, [id]);

useEffect(() => {
const adminMailId = localStorage.getItem('adminIds');
const storedData = localStorage.getItem('userData');

if (storedData) {
const parseUserData = JSON.parse(storedData);

if (adminMailId) {
const parseAdminData = JSON.parse(adminMailId);
const check = parseAdminData.data
.map((mailId: { EmailId: string }) => mailId.EmailId)
.includes(parseUserData.email);

setIsAdminMode(check);
}
}
}, []);
function evetnIdtoString(event: Event | null) {
const eventId =
event?.eventId !== undefined && event?.eventId !== null ? String(event.eventId) : 'undefined';
return eventId;
}
function reservedPlayersCount(event: Event | null) {
const reservedPlayersCount = event?.reservedPlayersCount || 0;
return reservedPlayersCount;
}
const expanded = true;

function cityId(event: Event | null) {
const dateString = event?.uniqueEventId || '';
const parts = dateString != '' ? dateString.split('-') : '';
let cityId = '99999';
cityId = parts.length > 0 ? parts[0] : '3';
return cityId;
}

function cityName(event: Event | null) {
const cityIdd = cityId(event);
const cityData = mapCityData.find((city) => city.cityId.toString() === cityIdd);

// Return the city name if found, otherwise return undefined
return cityData?.city;
}

const renderPlayer = (player: PlayerDetail | null | undefined, i: number) => (
<PlayerDetails displayName={player ? player.displayName : '(Empty)'} index={i} key={i} />
);

if (loading) {
return (
<div className={styles.loading}>
<Loading />
</div>
);
}

if (error) {
return <div>Error: {error}</div>;
}

if (!event) {
return (
<div className={isPortrait ? styles.portraitContainer : styles.parentContainer}>
<div className={styles.parent}>
<NotFound />
</div>
</div>
);
}
const EventsMapFunc = () => (
<>
<Typography className={styles.title}>Flickmatch Soccer</Typography>
<Cities
cityId={cityId(event)}
cityName={cityName(event) || ''}
countryCode="IN"
dummyData={false}
events={[]}
/>
<Accordion
className={isPortrait ? styles.mobileAccordion : styles.accordion}
sx={{
'&:before': {
display: 'none',
},
}}
expanded={expanded}
>
<AccordionSummary aria-controls="panel1a-content" id="panel1a-header">
<FlexBox className={styles.flexbox}>
<FlexBox className={styles.venue}>
<VenueName venueName={event?.venueName} dummyData={false} date={event?.date} />

{isPortrait ? null : (
<JoinNow
stripePaymentUrl={event?.stripePaymentUrl || ''}
charges={event?.charges || 0}
date={event?.date || ''}
uniqueEventId={event?.uniqueEventId || ''}
eventId={evetnIdtoString(event)}
reservedPlayersCount={event?.reservedPlayersCount || 0}
reservedPlayersList={event?.reservedPlayersList || []}
time={event?.endTime || ''}
venueLocationLink={event?.venueLocationLink || ''}
venueName={event?.venueName || ''}
waitListPlayers={[]}
waitListPlayersCount={event?.waitListPlayersCount || 0}
team_division={false}
team1_color={''}
team2_color={''}
dummyData={false}
singleEvent={true}
/>
)}
</FlexBox>
<EventsCard
uniqueEventId={event?.uniqueEventId || ''}
charges={event?.charges || 0}
date={event?.date || ''}
time={event?.time || ''}
venueLocationLink={event?.venueLocationLink || ''}
reservedPlayersCount={event?.reservedPlayersCount || 0}
waitListPlayersCount={event?.waitListPlayersCount || 0}
eventId={cityName(event) ?? ''}
reservedPlayersList={event?.reservedPlayersList || []}
venueName={event?.venueName || ''}
waitListPlayers={[]}
stripePaymentUrl={event?.stripePaymentUrl || ''}
team_division={false}
team1_color={''}
team2_color={''}
dummyData={false}
/>
</FlexBox>
</AccordionSummary>
<AccordionDetails
className={
highLighted && location.hash.substring(1) === event?.index.toString()
? styles.blink
: ''
}
>
<Box className={styles.box} sx={{ flexGrow: 1 }}>
<Box className={styles.reservedPlayersContainer}>
<Typography className={styles.reservedPlayers}>Reserved Players</Typography>
{isAdminMode ? <BorderColorIcon className={styles.editIcon} /> : null}
</Box>
<Box>
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 4, sm: 8, md: 12 }}>
{Array.from({ length: event?.reservedPlayersCount || 0 }, (_, i) => {
const player =
i < reservedPlayersCount(event) || 0 ? event?.reservedPlayersList[i] : null;
return renderPlayer(player, i);
})}
</Grid>
</Box>
</Box>
{event.waitListPlayersCount > 0 ? (
<Box className={styles.box} sx={{ flexGrow: 1 }}>
<Typography className={styles.waitListPlayers}>Waitlist</Typography>
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 4, sm: 8, md: 12 }}>
{Array.from({ length: event?.waitListPlayersCount }, (_, index) => {
const player = event?.waitListPlayers[index] ?? null;
return renderPlayer(player, index);
})}
</Grid>
</Box>
) : null}
</AccordionDetails>
</Accordion>
</>
);

return (
<div className={isPortrait ? styles.portraitParent : styles.parent}>{EventsMapFunc()}</div>
);
};

export default EventPage;
33 changes: 33 additions & 0 deletions react-fm/src/pages/eventPage/EventPage.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
type PlayerDetail = {
displayName: string;
};

type Event = {
reservedPlayersList: ReservedPlayerDetails[];
index: number;
startTime: string;
endTime: string;
charges: number;
reservedPlayersCount: number;
waitListPlayersCount: number;
sportName: string;
venueName: string;
venueLocationLink: string;
stripePaymentUrl: string;
currency: string;
eventId: string;
uniqueEventId: string;
date: string;
time: string;
waitListPlayers: WaitListPlayerList[];
};

type WaitListPlayerList = {
displayName: string;
};

type ReservedPlayerDetails = {
displayName: string;
};

export type { Event, PlayerDetail, ReservedPlayerDetails };
2 changes: 2 additions & 0 deletions react-fm/src/pages/eventPage/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const apiUrl = `${import.meta.env.VITE_API_URL}`;
export { apiUrl };
3 changes: 3 additions & 0 deletions react-fm/src/pages/eventPage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import EventPage from './EventPage';

export default EventPage;
23 changes: 12 additions & 11 deletions react-fm/src/pages/matchQueues/eventsComponents/Cities.tsx
Original file line number Diff line number Diff line change
@@ -9,17 +9,17 @@ import type { CityDetails, Weather, WeatherIcon } from '../types/Events.types';
import styles from './Cities.module.scss';

const weatherIconMap: WeatherIcon = {
'01': 'weather-icons/clear.png',
'02': 'weather-icons/fewclouds.png',
'03': 'weather-icons/scatteredclouds.png',
'04': 'weather-icons/brokenclouds.png',
'05': 'weather-icons/showerrain.png',
'06': 'weather-icons/showerrain.png',
'09': 'weather-icons/showerrain.png',
'10': 'weather-icons/rain.png',
'11': 'weather-icons/thunderstorm.png',
'13': 'weather-icons/snow.png',
'50': 'weather-icons/mist.png',
'01': `${window.location.origin}/weather-icons/clear.png`,
'02': `${window.location.origin}/weather-icons/fewclouds.png`,
'03': `${window.location.origin}/weather-icons/scatteredclouds.png`,
'04': `${window.location.origin}/weather-icons/brokenclouds.png`,
'05': `${window.location.origin}/weather-icons/showerrain.png`,
'06': `${window.location.origin}/weather-icons/showerrain.png`,
'09': `${window.location.origin}/weather-icons/showerrain.png`,
'10': `${window.location.origin}/weather-icons/rain.png`,
'11': `${window.location.origin}/weather-icons/thunderstorm.png`,
'13': `${window.location.origin}/weather-icons/snow.png`,
'50': `${window.location.origin}/weather-icons/mist.png`,
};

export const Cities: FC<CityDetails> = ({ cityName, cityId, countryCode }) => {
@@ -67,6 +67,7 @@ export const Cities: FC<CityDetails> = ({ cityName, cityId, countryCode }) => {
<div className={isPortrait ? styles.mobileWeather : styles.weather}>
<span>{weather?.temp ? `${Math.round(Number(weather?.temp))} °C` : ''} </span>
<span>{description || ''}</span>

{image && (
<img
className={isPortrait ? styles.weatherImgMobile : styles.weatherImg}
2 changes: 1 addition & 1 deletion react-fm/src/pages/matchQueues/eventsComponents/Events.tsx
Original file line number Diff line number Diff line change
@@ -54,7 +54,6 @@ export const EventsCard: FC<EventDetails> = ({
const dateToString = tomorrow.toString();
const index = dateToString.indexOf('2024');
const futureDate = dateToString.substring(0, index);

const startTime = time.split('-')[0]; //8:00PM
const endTime = time.split('-')[1].split(' ')[0];
const usTime = (dummyData ? futureDate : date) + ' ' + startTime + ' - ' + endTime;
@@ -111,6 +110,7 @@ export const EventsCard: FC<EventDetails> = ({
timeZone = <span>{usTime}</span>;
}
});

return timeZone;
};

3 changes: 2 additions & 1 deletion react-fm/src/pages/matchQueues/eventsComponents/JoinNow.tsx
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@ export const JoinNow: FC<EventDetails> = ({
venueName,
uniqueEventId,
eventId,
singleEvent,
}) => {
const [, notificationsActions] = useNotifications();
const isPortrait = useOrientation();
@@ -111,7 +112,7 @@ export const JoinNow: FC<EventDetails> = ({
const paymentOptions = (event: { stopPropagation: () => void }) => {
if (userData.name) {
event.stopPropagation();
history.replaceState(null, '', `#${uniqueEventId}`);
if (!singleEvent) history.replaceState(null, '', `#${uniqueEventId}`);
setShowPaymentOptions(true);
} else {
navigate('/login');
1 change: 1 addition & 0 deletions react-fm/src/pages/matchQueues/types/Events.types.tsx
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ type EventDetails = {
waitListPlayersCount: number;
stripePaymentUrl: string;
dummyData: boolean;
singleEvent?: boolean;
};

type CityDetails = {
4 changes: 4 additions & 0 deletions react-fm/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -100,6 +100,10 @@ const routes: Routes = {
component: asyncComponentLoader(() => import('@/pages/aboutUs')),
path: '/about-2',
},
[Pages.Event]: {
component: asyncComponentLoader(() => import('@/pages/eventPage')),
path: '/event/:id',
},
};

export default routes;
1 change: 1 addition & 0 deletions react-fm/src/routes/types.ts
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ enum Pages {
Profile,
About2,
Rewards,
Event,
}

type PathRouteCustomProps = {
10 changes: 3 additions & 7 deletions react-fm/src/sections/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -24,6 +24,8 @@ import styles from './Header.module.scss';

//import { appLogo } from './constants';

const appLogo = `${window.location.origin}/fm_rainbow.webp`;

interface UserDetails {
email: string;
family_name: string;
@@ -154,13 +156,7 @@ const Header = () => {
<FlexBox sx={{ alignItems: 'center' }}>
{navIcon()}
<Typography component={Link} to="/home">
<img
src="./fm_rainbow.webp"
alt="logo"
className={styles.logo}
height="52px"
width="54.23px"
/>
<img src={appLogo} alt="logo" className={styles.logo} height="52px" width="54.23px" />
</Typography>
{userState.login.isAdmin ? (
<Chip

0 comments on commit 8ebf56a

Please sign in to comment.