From 7a28b8ef45606274e0bf43dfdfab0760fbf6f732 Mon Sep 17 00:00:00 2001 From: Andres2D Date: Sun, 19 Feb 2023 20:56:00 -0500 Subject: [PATCH 1/7] Add state slice to manage the matches list --- interfaces/Match.ts | 5 +++ store/matches-list.slice.ts | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 store/matches-list.slice.ts diff --git a/interfaces/Match.ts b/interfaces/Match.ts index 78dd709..c2b087d 100644 --- a/interfaces/Match.ts +++ b/interfaces/Match.ts @@ -61,3 +61,8 @@ export interface IMatchDetails extends IMatchDetailsResponse { playersSelected: ISelectPayload[], changePlayerModalActive: boolean }; + +export interface MatchState { + matches: FullMatch[], + selectedMatch?: FullMatch; +} diff --git a/store/matches-list.slice.ts b/store/matches-list.slice.ts new file mode 100644 index 0000000..ff77d16 --- /dev/null +++ b/store/matches-list.slice.ts @@ -0,0 +1,62 @@ +import { + createSlice, + PayloadAction, + CaseReducer +} from '@reduxjs/toolkit'; +import { FullMatch, MatchState } from '../interfaces/Match'; + +type DeleteMatchPayload = { + id: string; +}; + +const initialState: MatchState = { + matches: [], + selectedMatch: undefined +}; + +const setMatchesList: CaseReducer> = +(state: MatchState, action: PayloadAction) => { + state.matches = action.payload; +}; + +const deleteLeaveMatch: CaseReducer> = +(state: MatchState, action: PayloadAction) => { + state.matches = state.matches.filter(match => match._id !== action.payload.id) +}; + +const setSelectedMatch: CaseReducer> = +(state: MatchState, action: PayloadAction) => { + state.selectedMatch = action.payload; +}; + +const updateMatch: CaseReducer> = +(state: MatchState, action: PayloadAction) => { + state.matches = state.matches.map(match => { + if(match._id === action.payload._id) { + return { + ...match, + location: action.payload.location, + date: action.payload.date, + fullTime: action.payload.fullTime, + homeScore: action.payload.homeScore, + awayScore: action.payload.awayScore + }; + }else { + return match; + } + }) +}; + +const matchesListSlice = createSlice({ + name: 'matchesList', + initialState, + reducers: { + setMatchesList, + deleteLeaveMatch, + setSelectedMatch, + updateMatch + } +}); + +export const matchesListActions = matchesListSlice.actions; +export default matchesListSlice.reducer; From e3958be81e58eef6ab13cb962125ec9d85e5439c Mon Sep 17 00:00:00 2001 From: Andres2D Date: Sun, 19 Feb 2023 21:25:49 -0500 Subject: [PATCH 2/7] Replace local states with app state on macthes list --- components/matches/match-list.tsx | 114 ++++++++++++++---------------- interfaces/Match.ts | 2 +- interfaces/State.ts | 3 +- pages/matches/index.tsx | 15 ++-- store/index.ts | 4 +- store/matches-list.slice.ts | 21 +++--- 6 files changed, 80 insertions(+), 79 deletions(-) diff --git a/components/matches/match-list.tsx b/components/matches/match-list.tsx index ade5f1f..6dc25ea 100644 --- a/components/matches/match-list.tsx +++ b/components/matches/match-list.tsx @@ -1,7 +1,7 @@ import type { NextPage } from 'next'; import { FormControl, FormLabel, IconButton, Image, Input, useDisclosure, useToast } from '@chakra-ui/react'; import { DeleteIcon, SettingsIcon, CheckCircleIcon } from '@chakra-ui/icons'; -import { useState, useEffect, useRef, MutableRefObject } from 'react'; +import { useState, useRef, MutableRefObject } from 'react'; import { useMutation } from 'react-query'; import { useRouter } from 'next/router'; import styles from './match-list.module.scss'; @@ -14,14 +14,15 @@ import { import { FullMatch } from '../../interfaces/Match'; import ModalAlert from '../layout/modal-alert'; import LeaveIcon from '../../assets/svg/leave.svg'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../interfaces/State'; +import { matchesListActions } from '../../store/matches-list.slice'; -interface Props { - matches: FullMatch[]; -} +const MatchList: NextPage = () => { -const MatchList: NextPage = ({ matches }) => { - const [matchesList, setMatchesList] = useState([]); - const [selectedMatch, setSelectedMatch] = useState(); + const matchesState = useSelector((state: RootState) => state.matchesList ); + const dispatch = useDispatch(); + // const [selectedMatch, setSelectedMatch] = useState(); const [place, setPlace] = useState(); const [date, setDate] = useState(); const { @@ -49,21 +50,14 @@ const MatchList: NextPage = ({ matches }) => { const homeScoreRef = useRef() as MutableRefObject; const awayScoreRef = useRef() as MutableRefObject; - // const placeRef = useRef() as MutableRefObject; - // const dateRef = useRef() as MutableRefObject; - - useEffect(() => { - setMatchesList(matches); - }, [matches]); - const { mutate: mutateDeleteMatch } = useMutation(deleteMatchService, { onSuccess: async (response) => { if (response.ok) { - setMatchesList((matches) => - matches.filter((match) => match._id !== selectedMatch?._id) - ); + // setMatchesList((matches) => + // matches.filter((match) => match._id !== selectedMatch?._id) + // ); deleteModalOnClose(); - setSelectedMatch(undefined); + dispatch(matchesListActions.setSelectedMatch(undefined)); toast({ title: 'Delete match', description: 'Match deleted.', @@ -80,7 +74,7 @@ const MatchList: NextPage = ({ matches }) => { isClosable: true, }); deleteModalOnClose(); - setSelectedMatch(undefined); + dispatch(matchesListActions.setSelectedMatch(undefined)); } }, onError: () => { @@ -91,11 +85,11 @@ const MatchList: NextPage = ({ matches }) => { const { mutate: mutateLeaveMatch } = useMutation(leaveMatch, { onSuccess: async (response) => { if (response.ok) { - setMatchesList((matches) => - matches.filter((match) => match._id !== selectedMatch?._id) - ); + // setMatchesList((matches) => + // matches.filter((match) => match._id !== selectedMatch?._id) + // ); leaveMatchModalOnClose(); - setSelectedMatch(undefined); + dispatch(matchesListActions.setSelectedMatch(undefined)); toast({ title: 'Leave match', description: 'You left the match.', @@ -112,7 +106,7 @@ const MatchList: NextPage = ({ matches }) => { isClosable: true, }); leaveMatchModalOnClose(); - setSelectedMatch(undefined); + dispatch(matchesListActions.setSelectedMatch(undefined)); } }, onError: () => { @@ -123,26 +117,26 @@ const MatchList: NextPage = ({ matches }) => { const { mutate: mutateUpdateMatch } = useMutation(updateMatch, { onSuccess: async (response) => { if (response.ok) { - setMatchesList((matches) => - matches.map((match) => { - if(match._id === selectedMatch?._id) { - return { - ...match, - location: response.data.match.location, - date: response.data.match.date, - fullTime: response.data.match.fullTime, - homeScore: response.data.match.homeScore, - awayScore: response.data.match.awayScore - }; - }else { - return match; - } - }) - ); + // setMatchesList((matches) => + // matches.map((match) => { + // if(match._id === selectedMatch?._id) { + // return { + // ...match, + // location: response.data.match.location, + // date: response.data.match.date, + // fullTime: response.data.match.fullTime, + // homeScore: response.data.match.homeScore, + // awayScore: response.data.match.awayScore + // }; + // }else { + // return match; + // } + // }) + // ); fulltimeModalOnClose(); updateMatchModalOnClose(); - setSelectedMatch(undefined); + dispatch(matchesListActions.setSelectedMatch(undefined)); toast({ title: 'Match updated', description: 'You update the match.', @@ -160,7 +154,7 @@ const MatchList: NextPage = ({ matches }) => { }); fulltimeModalOnClose(); updateMatchModalOnClose(); - setSelectedMatch(undefined); + dispatch(matchesListActions.setSelectedMatch(undefined)); } }, onError: () => { @@ -175,17 +169,17 @@ const MatchList: NextPage = ({ matches }) => { }; const showDeleteMatchModal = (match: FullMatch) => { - setSelectedMatch(match); + dispatch(matchesListActions.setSelectedMatch(match)); deleteModalOnOpen(); }; const showLeaveMatchModal = (match: FullMatch) => { - setSelectedMatch(match); + dispatch(matchesListActions.setSelectedMatch(match)); leaveMatchModalOnOpen(); }; const showFullTimeModal = (match: FullMatch) => { - setSelectedMatch(match); + dispatch(matchesListActions.setSelectedMatch(match)); fulltimeModalOnOpen(); }; @@ -193,15 +187,15 @@ const MatchList: NextPage = ({ matches }) => { updateMatchModalOnOpen(); setPlace(match.location); setDate(match.date); - setSelectedMatch(match); + dispatch(matchesListActions.setSelectedMatch(match)); }; const handleDeleteMatch = () => { - mutateDeleteMatch(selectedMatch?._id!); + mutateDeleteMatch(matchesState.selectedMatch?._id!); }; const handleLeaveMatch = () => { - mutateLeaveMatch(selectedMatch?._id!); + mutateLeaveMatch(matchesState.selectedMatch?._id!); }; const handleFulltime = () => { @@ -210,7 +204,7 @@ const MatchList: NextPage = ({ matches }) => { } const request: FullMatch = { - ...selectedMatch!, + ...matchesState.selectedMatch!, fullTime: true, homeScore: +homeScoreRef.current.value, awayScore: +awayScoreRef.current.value @@ -225,15 +219,15 @@ const MatchList: NextPage = ({ matches }) => { } const request: FullMatch = { - ...selectedMatch!, - date: date || selectedMatch?.date || '', - location: place || selectedMatch?.location || '' + ...matchesState.selectedMatch!, + date: date || matchesState.selectedMatch?.date || '', + location: place || matchesState.selectedMatch?.location || '' }; mutateUpdateMatch(request); }; - const matchesListMap = matchesList.map( + const matchesListMap = matchesState.matches.map( ({ _id, date, @@ -409,8 +403,8 @@ const MatchList: NextPage = ({ matches }) => {
{selectedMatch?.home_team.name} @@ -418,14 +412,14 @@ const MatchList: NextPage = ({ matches }) => { textAlign={'center'} marginInlineEnd={0} > - {selectedMatch?.home_team.name} + {matchesState.selectedMatch?.home_team.name} {selectedMatch?.away_team.name} @@ -433,7 +427,7 @@ const MatchList: NextPage = ({ matches }) => { textAlign={'center'} marginInlineEnd={0} > - {selectedMatch?.away_team.name} + {matchesState.selectedMatch?.away_team.name} @@ -444,7 +438,7 @@ const MatchList: NextPage = ({ matches }) => { onClose={updateMatchModalOnClose} onContinue={handleUpdateMatch} actionColor='green' - title={`Update ${selectedMatch?.home_team.name} vs ${selectedMatch?.away_team.name}`} + title={`Update ${matchesState.selectedMatch?.home_team.name} vs ${matchesState.selectedMatch?.away_team.name}`} continueLabel="Update" > diff --git a/interfaces/Match.ts b/interfaces/Match.ts index c2b087d..2f22d4d 100644 --- a/interfaces/Match.ts +++ b/interfaces/Match.ts @@ -62,7 +62,7 @@ export interface IMatchDetails extends IMatchDetailsResponse { changePlayerModalActive: boolean }; -export interface MatchState { +export interface MatchesListState { matches: FullMatch[], selectedMatch?: FullMatch; } diff --git a/interfaces/State.ts b/interfaces/State.ts index 2d925f8..e18b1a2 100644 --- a/interfaces/State.ts +++ b/interfaces/State.ts @@ -1,8 +1,9 @@ -import { ICreateMatchState, IMatchDetails } from './Match'; +import { ICreateMatchState, IMatchDetails, MatchesListState } from './Match'; import { IProfileState } from './Profile'; export interface RootState { createMatch: ICreateMatchState; matchDetails: IMatchDetails; profile: IProfileState; + matchesList: MatchesListState } diff --git a/pages/matches/index.tsx b/pages/matches/index.tsx index 705d506..e3e4724 100644 --- a/pages/matches/index.tsx +++ b/pages/matches/index.tsx @@ -10,9 +10,9 @@ import { getMatches } from '../../server/matches'; import styles from './matches.module.scss'; import { FullMatch, IMatch } from '../../interfaces/Match'; import { profileActions } from '../../store/profile.slice'; +import { matchesListActions } from '../../store/matches-list.slice'; import { getProfile } from '../../server/player'; import HeaderSettings from '../../accessibility/header-setting'; - interface Props { matches: string; profile: string; @@ -23,13 +23,14 @@ const Matches: NextPage = ({matches, profile}) => { const matchesList: FullMatch[] = JSON.parse(matches); const router = useRouter(); const dispatch = useDispatch(); - + // Persisting store profile // TODO: Change way to persis state let parsedProfile = JSON.parse(profile || ''); const flagResponse = getFlagSvg(parsedProfile.nationality, true); - + useEffect(() => { + dispatch(profileActions.setProfile({ _id: parsedProfile._id, overall: 0, @@ -39,6 +40,10 @@ const Matches: NextPage = ({matches, profile}) => { image: parsedProfile.image, nationality: parsedProfile.nationality, })); + console.log(matchesList); + + dispatch(matchesListActions.setMatchesList(matchesList)); + }, []); return ( @@ -52,9 +57,7 @@ const Matches: NextPage = ({matches, profile}) => { > New Match - + ); diff --git a/store/index.ts b/store/index.ts index 30bfebf..588034f 100644 --- a/store/index.ts +++ b/store/index.ts @@ -2,12 +2,14 @@ import { configureStore } from '@reduxjs/toolkit'; import createMatchSlice from './create-match.slice'; import matchDetailsSlice from './match-details.slice'; import profileSlice from './profile.slice'; +import matchesListSlice from './matches-list.slice'; const store = configureStore({ reducer: { createMatch: createMatchSlice, matchDetails: matchDetailsSlice, - profile: profileSlice + profile: profileSlice, + matchesList: matchesListSlice } }); diff --git a/store/matches-list.slice.ts b/store/matches-list.slice.ts index ff77d16..8da15c3 100644 --- a/store/matches-list.slice.ts +++ b/store/matches-list.slice.ts @@ -3,34 +3,35 @@ import { PayloadAction, CaseReducer } from '@reduxjs/toolkit'; -import { FullMatch, MatchState } from '../interfaces/Match'; +import { FullMatch, MatchesListState } from '../interfaces/Match'; type DeleteMatchPayload = { id: string; }; -const initialState: MatchState = { +const initialState: MatchesListState = { matches: [], selectedMatch: undefined }; -const setMatchesList: CaseReducer> = -(state: MatchState, action: PayloadAction) => { +const setMatchesList: CaseReducer> = +(state: MatchesListState, action: PayloadAction) => { + console.log('action', action); state.matches = action.payload; }; -const deleteLeaveMatch: CaseReducer> = -(state: MatchState, action: PayloadAction) => { +const deleteLeaveMatch: CaseReducer> = +(state: MatchesListState, action: PayloadAction) => { state.matches = state.matches.filter(match => match._id !== action.payload.id) }; -const setSelectedMatch: CaseReducer> = -(state: MatchState, action: PayloadAction) => { +const setSelectedMatch: CaseReducer> = +(state: MatchesListState, action: PayloadAction) => { state.selectedMatch = action.payload; }; -const updateMatch: CaseReducer> = -(state: MatchState, action: PayloadAction) => { +const updateMatch: CaseReducer> = +(state: MatchesListState, action: PayloadAction) => { state.matches = state.matches.map(match => { if(match._id === action.payload._id) { return { From 84ef059e6117f106ee1c7fbc79e43af497c48b21 Mon Sep 17 00:00:00 2001 From: Andres2D Date: Sun, 19 Feb 2023 21:59:30 -0500 Subject: [PATCH 3/7] Decouple delete match modal --- components/layout/modal-alert.tsx | 2 +- components/matches/match-list.tsx | 56 ++------------- .../matches/modals/delete-match.modal.tsx | 72 +++++++++++++++++++ interfaces/Match.ts | 3 + store/matches-list.slice.ts | 18 ++++- 5 files changed, 97 insertions(+), 54 deletions(-) create mode 100644 components/matches/modals/delete-match.modal.tsx diff --git a/components/layout/modal-alert.tsx b/components/layout/modal-alert.tsx index 0186269..fe8a767 100644 --- a/components/layout/modal-alert.tsx +++ b/components/layout/modal-alert.tsx @@ -8,7 +8,7 @@ import { Button } from '@chakra-ui/react'; import type { NextPage } from 'next'; -import { Children, MutableRefObject, useRef } from 'react'; +import { MutableRefObject, useRef } from 'react'; interface Props { isOpen: boolean; diff --git a/components/matches/match-list.tsx b/components/matches/match-list.tsx index 6dc25ea..4658c84 100644 --- a/components/matches/match-list.tsx +++ b/components/matches/match-list.tsx @@ -7,7 +7,6 @@ import { useRouter } from 'next/router'; import styles from './match-list.module.scss'; import Team from './team'; import { - deleteMatch as deleteMatchService, leaveMatch, updateMatch } from '../../services/api-configuration'; @@ -17,19 +16,16 @@ import LeaveIcon from '../../assets/svg/leave.svg'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../../interfaces/State'; import { matchesListActions } from '../../store/matches-list.slice'; +import DeleteMatchModal from './modals/delete-match.modal'; const MatchList: NextPage = () => { const matchesState = useSelector((state: RootState) => state.matchesList ); const dispatch = useDispatch(); - // const [selectedMatch, setSelectedMatch] = useState(); + const [place, setPlace] = useState(); const [date, setDate] = useState(); - const { - isOpen: deleteModalIsOpen, - onOpen: deleteModalOnOpen, - onClose: deleteModalOnClose, - } = useDisclosure(); + const { isOpen: leaveMatchModalIsOpen, onOpen: leaveMatchModalOnOpen, @@ -50,38 +46,6 @@ const MatchList: NextPage = () => { const homeScoreRef = useRef() as MutableRefObject; const awayScoreRef = useRef() as MutableRefObject; - const { mutate: mutateDeleteMatch } = useMutation(deleteMatchService, { - onSuccess: async (response) => { - if (response.ok) { - // setMatchesList((matches) => - // matches.filter((match) => match._id !== selectedMatch?._id) - // ); - deleteModalOnClose(); - dispatch(matchesListActions.setSelectedMatch(undefined)); - toast({ - title: 'Delete match', - description: 'Match deleted.', - status: 'success', - duration: 9000, - isClosable: true, - }); - } else { - toast({ - title: 'Delete match', - description: 'Something went wrong, please try again later.', - status: 'error', - duration: 9000, - isClosable: true, - }); - deleteModalOnClose(); - dispatch(matchesListActions.setSelectedMatch(undefined)); - } - }, - onError: () => { - // TODO: handle error - }, - }); - const { mutate: mutateLeaveMatch } = useMutation(leaveMatch, { onSuccess: async (response) => { if (response.ok) { @@ -170,7 +134,7 @@ const MatchList: NextPage = () => { const showDeleteMatchModal = (match: FullMatch) => { dispatch(matchesListActions.setSelectedMatch(match)); - deleteModalOnOpen(); + dispatch(matchesListActions.setMatchModalAction({action: 'isDeleteMatch', value: true })); }; const showLeaveMatchModal = (match: FullMatch) => { @@ -190,10 +154,6 @@ const MatchList: NextPage = () => { dispatch(matchesListActions.setSelectedMatch(match)); }; - const handleDeleteMatch = () => { - mutateDeleteMatch(matchesState.selectedMatch?._id!); - }; - const handleLeaveMatch = () => { mutateLeaveMatch(matchesState.selectedMatch?._id!); }; @@ -376,13 +336,7 @@ const MatchList: NextPage = () => { return ( <> {matchesListMap} - + { + const { isOpen, onOpen, onClose } = useDisclosure(); + const matchesState = useSelector((state: RootState) => state.matchesList); + const dispatch = useDispatch(); + const toast = useToast(); + + useEffect(() => { + if(matchesState.isDeleteMatch) { + onOpen(); + } + }, [matchesState, onOpen]); + + const { mutate } = useMutation(deleteMatch, { + onSuccess: async (response) => { + if (response.ok) { + dispatch(matchesListActions.deleteLeaveMatch({id: matchesState.selectedMatch?._id!})); + dispatch(matchesListActions.setSelectedMatch(undefined)); + handleCloseModal(); + toast({ + title: 'Delete match', + description: 'Match deleted.', + status: 'success', + duration: 9000, + isClosable: true, + }); + } else { + toast({ + title: 'Delete match', + description: 'Something went wrong, please try again later.', + status: 'error', + duration: 9000, + isClosable: true, + }); + dispatch(matchesListActions.setSelectedMatch(undefined)); + } + }, + onError: () => { + // TODO: handle error + }, + }); + + const handleDeleteMatch = () => { + mutate(matchesState.selectedMatch?._id!); + }; + + const handleCloseModal = () => { + dispatch(matchesListActions.setMatchModalAction({action: 'isDeleteMatch', value: false})); + onClose(); + }; + + return ( + handleCloseModal()} + onContinue={handleDeleteMatch} + title="Delete Match" + description={`Are you sure? You can't undo this action afterwards`} + /> + ); +} + +export default DeleteMatchModal; diff --git a/interfaces/Match.ts b/interfaces/Match.ts index 2f22d4d..046bef3 100644 --- a/interfaces/Match.ts +++ b/interfaces/Match.ts @@ -65,4 +65,7 @@ export interface IMatchDetails extends IMatchDetailsResponse { export interface MatchesListState { matches: FullMatch[], selectedMatch?: FullMatch; + isDeleteMatch: boolean; + isLeaveMatch: boolean; + isUpdateMatch: boolean; } diff --git a/store/matches-list.slice.ts b/store/matches-list.slice.ts index 8da15c3..eda95d4 100644 --- a/store/matches-list.slice.ts +++ b/store/matches-list.slice.ts @@ -9,9 +9,17 @@ type DeleteMatchPayload = { id: string; }; +type ModalActionPayload = { + action: 'isDeleteMatch' | 'isLeaveMatch' | 'isUpdateMatch', + value: boolean +}; + const initialState: MatchesListState = { matches: [], - selectedMatch: undefined + selectedMatch: undefined, + isDeleteMatch: false, + isLeaveMatch: false, + isUpdateMatch: false }; const setMatchesList: CaseReducer> = @@ -30,6 +38,11 @@ const setSelectedMatch: CaseReducer> = +(state: MatchesListState, action: PayloadAction) => { + state[action.payload.action] = action.payload.value; +}; + const updateMatch: CaseReducer> = (state: MatchesListState, action: PayloadAction) => { state.matches = state.matches.map(match => { @@ -55,7 +68,8 @@ const matchesListSlice = createSlice({ setMatchesList, deleteLeaveMatch, setSelectedMatch, - updateMatch + updateMatch, + setMatchModalAction } }); From dfae0a4532b8bd867b6f2044e91d11ec7cbaf58b Mon Sep 17 00:00:00 2001 From: Andres2D Date: Sun, 19 Feb 2023 22:10:41 -0500 Subject: [PATCH 4/7] Decouple leave match modal --- components/matches/match-list.tsx | 53 +------------- .../matches/modals/delete-match.modal.tsx | 6 +- .../matches/modals/leave-match.modal.tsx | 73 +++++++++++++++++++ 3 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 components/matches/modals/leave-match.modal.tsx diff --git a/components/matches/match-list.tsx b/components/matches/match-list.tsx index 4658c84..f9abe62 100644 --- a/components/matches/match-list.tsx +++ b/components/matches/match-list.tsx @@ -7,7 +7,6 @@ import { useRouter } from 'next/router'; import styles from './match-list.module.scss'; import Team from './team'; import { - leaveMatch, updateMatch } from '../../services/api-configuration'; import { FullMatch } from '../../interfaces/Match'; @@ -17,6 +16,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../../interfaces/State'; import { matchesListActions } from '../../store/matches-list.slice'; import DeleteMatchModal from './modals/delete-match.modal'; +import LeaveMatchModal from './modals/leave-match.modal'; const MatchList: NextPage = () => { @@ -26,11 +26,6 @@ const MatchList: NextPage = () => { const [place, setPlace] = useState(); const [date, setDate] = useState(); - const { - isOpen: leaveMatchModalIsOpen, - onOpen: leaveMatchModalOnOpen, - onClose: leaveMatchModalOnClose, - } = useDisclosure(); const { isOpen: fulltimeModalIsOpen, onOpen: fulltimeModalOnOpen, @@ -46,38 +41,6 @@ const MatchList: NextPage = () => { const homeScoreRef = useRef() as MutableRefObject; const awayScoreRef = useRef() as MutableRefObject; - const { mutate: mutateLeaveMatch } = useMutation(leaveMatch, { - onSuccess: async (response) => { - if (response.ok) { - // setMatchesList((matches) => - // matches.filter((match) => match._id !== selectedMatch?._id) - // ); - leaveMatchModalOnClose(); - dispatch(matchesListActions.setSelectedMatch(undefined)); - toast({ - title: 'Leave match', - description: 'You left the match.', - status: 'success', - duration: 9000, - isClosable: true, - }); - } else { - toast({ - title: 'Leave match', - description: 'Something went wrong, please try again later.', - status: 'error', - duration: 9000, - isClosable: true, - }); - leaveMatchModalOnClose(); - dispatch(matchesListActions.setSelectedMatch(undefined)); - } - }, - onError: () => { - // TODO: handle error - }, - }); - const { mutate: mutateUpdateMatch } = useMutation(updateMatch, { onSuccess: async (response) => { if (response.ok) { @@ -139,7 +102,7 @@ const MatchList: NextPage = () => { const showLeaveMatchModal = (match: FullMatch) => { dispatch(matchesListActions.setSelectedMatch(match)); - leaveMatchModalOnOpen(); + dispatch(matchesListActions.setMatchModalAction({action: 'isLeaveMatch', value: true })); }; const showFullTimeModal = (match: FullMatch) => { @@ -154,9 +117,6 @@ const MatchList: NextPage = () => { dispatch(matchesListActions.setSelectedMatch(match)); }; - const handleLeaveMatch = () => { - mutateLeaveMatch(matchesState.selectedMatch?._id!); - }; const handleFulltime = () => { if(homeScoreRef.current.value.trim() === '' || awayScoreRef.current.value.trim() === '' ) { @@ -337,14 +297,7 @@ const MatchList: NextPage = () => { <> {matchesListMap} - + { diff --git a/components/matches/modals/leave-match.modal.tsx b/components/matches/modals/leave-match.modal.tsx new file mode 100644 index 0000000..1e155c5 --- /dev/null +++ b/components/matches/modals/leave-match.modal.tsx @@ -0,0 +1,73 @@ +import { useDisclosure, useToast } from '@chakra-ui/react'; +import type { NextPage } from 'next'; +import { useMutation } from 'react-query'; +import { useSelector, useDispatch } from 'react-redux'; +import { useEffect } from 'react'; +import ModalAlert from '../../layout/modal-alert'; +import { RootState } from '../../../interfaces/State'; +import { matchesListActions } from '../../../store/matches-list.slice'; +import { leaveMatch } from '../../../services/api-configuration'; + +const LeaveMatchModal: NextPage = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const matchesState = useSelector((state: RootState) => state.matchesList); + const dispatch = useDispatch(); + const toast = useToast(); + + useEffect(() => { + if(matchesState.isLeaveMatch) { + onOpen(); + } + }, [matchesState, onOpen]); + + const { mutate } = useMutation(leaveMatch, { + onSuccess: async (response) => { + if (response.ok) { + dispatch(matchesListActions.deleteLeaveMatch({id: matchesState.selectedMatch?._id!})); + dispatch(matchesListActions.setSelectedMatch(undefined)); + handleCloseModal(); + toast({ + title: 'Leave match', + description: 'You left the match.', + status: 'success', + duration: 9000, + isClosable: true, + }); + } else { + toast({ + title: 'Leave match', + description: 'Something went wrong, please try again later.', + status: 'error', + duration: 9000, + isClosable: true, + }); + dispatch(matchesListActions.setSelectedMatch(undefined)); + } + }, + onError: () => { + // TODO: handle error + }, + }); + + const handleLeaveMatch = () => { + mutate(matchesState.selectedMatch?._id!); + }; + + const handleCloseModal = () => { + dispatch(matchesListActions.setMatchModalAction({action: 'isLeaveMatch', value: false})); + onClose(); + }; + + return ( + handleCloseModal()} + onContinue={handleLeaveMatch} + title="Leave match" + description={`Are you sure? You would request to a team mate to add you again afterwards`} + continueLabel="Leave match" + /> + ); +} + +export default LeaveMatchModal; From a2a0c2f0c8e56d1b881bd2ab85d3e569ef0c19f1 Mon Sep 17 00:00:00 2001 From: Andres2D Date: Sun, 19 Feb 2023 22:30:03 -0500 Subject: [PATCH 5/7] Decouple update match modal --- components/matches/match-list.module.scss | 13 -- components/matches/match-list.tsx | 119 +----------------- .../matches/modals/update-match.modal.tsx | 117 +++++++++++++++++ .../matches/modals/update-match.module.scss | 11 ++ 4 files changed, 133 insertions(+), 127 deletions(-) create mode 100644 components/matches/modals/update-match.modal.tsx create mode 100644 components/matches/modals/update-match.module.scss diff --git a/components/matches/match-list.module.scss b/components/matches/match-list.module.scss index 599726b..ca77fde 100644 --- a/components/matches/match-list.module.scss +++ b/components/matches/match-list.module.scss @@ -80,19 +80,6 @@ } } -.updateMatch { - display: flex; - flex-direction: column; - - .formControl { - align-items: center; - display: flex; - flex-direction: column; - margin-bottom: 10px; - } -} - - @media (max-width: $medium-small-device) { .section { flex-direction: column; diff --git a/components/matches/match-list.tsx b/components/matches/match-list.tsx index f9abe62..7a125d9 100644 --- a/components/matches/match-list.tsx +++ b/components/matches/match-list.tsx @@ -1,14 +1,10 @@ import type { NextPage } from 'next'; import { FormControl, FormLabel, IconButton, Image, Input, useDisclosure, useToast } from '@chakra-ui/react'; import { DeleteIcon, SettingsIcon, CheckCircleIcon } from '@chakra-ui/icons'; -import { useState, useRef, MutableRefObject } from 'react'; -import { useMutation } from 'react-query'; +import { useRef, MutableRefObject } from 'react'; import { useRouter } from 'next/router'; import styles from './match-list.module.scss'; import Team from './team'; -import { - updateMatch -} from '../../services/api-configuration'; import { FullMatch } from '../../interfaces/Match'; import ModalAlert from '../layout/modal-alert'; import LeaveIcon from '../../assets/svg/leave.svg'; @@ -17,78 +13,23 @@ import { RootState } from '../../interfaces/State'; import { matchesListActions } from '../../store/matches-list.slice'; import DeleteMatchModal from './modals/delete-match.modal'; import LeaveMatchModal from './modals/leave-match.modal'; +import UpdateMatchModal from './modals/update-match.modal'; const MatchList: NextPage = () => { const matchesState = useSelector((state: RootState) => state.matchesList ); const dispatch = useDispatch(); - const [place, setPlace] = useState(); - const [date, setDate] = useState(); const { isOpen: fulltimeModalIsOpen, onOpen: fulltimeModalOnOpen, onClose: fulltimeModalOnClose, } = useDisclosure(); - const { - isOpen: updateMatchModalIsOpen, - onOpen: updateMatchModalOnOpen, - onClose: updateMatchModalOnClose - } = useDisclosure(); - const toast = useToast(); const homeScoreRef = useRef() as MutableRefObject; const awayScoreRef = useRef() as MutableRefObject; - const { mutate: mutateUpdateMatch } = useMutation(updateMatch, { - onSuccess: async (response) => { - if (response.ok) { - // setMatchesList((matches) => - // matches.map((match) => { - // if(match._id === selectedMatch?._id) { - // return { - // ...match, - // location: response.data.match.location, - // date: response.data.match.date, - // fullTime: response.data.match.fullTime, - // homeScore: response.data.match.homeScore, - // awayScore: response.data.match.awayScore - // }; - // }else { - // return match; - // } - // }) - // ); - - fulltimeModalOnClose(); - updateMatchModalOnClose(); - dispatch(matchesListActions.setSelectedMatch(undefined)); - toast({ - title: 'Match updated', - description: 'You update the match.', - status: 'success', - duration: 9000, - isClosable: true, - }); - } else { - toast({ - title: 'Match updated', - description: 'Something went wrong, please try again later.', - status: 'error', - duration: 9000, - isClosable: true, - }); - fulltimeModalOnClose(); - updateMatchModalOnClose(); - dispatch(matchesListActions.setSelectedMatch(undefined)); - } - }, - onError: () => { - // TODO: handle error - }, - }); - const router = useRouter(); const goToMatchDetails = (matchId: string) => { @@ -111,13 +52,10 @@ const MatchList: NextPage = () => { }; const showUpdateMatchModal = (match: FullMatch) => { - updateMatchModalOnOpen(); - setPlace(match.location); - setDate(match.date); + dispatch(matchesListActions.setMatchModalAction({action: 'isUpdateMatch', value: true })); dispatch(matchesListActions.setSelectedMatch(match)); }; - const handleFulltime = () => { if(homeScoreRef.current.value.trim() === '' || awayScoreRef.current.value.trim() === '' ) { return; @@ -130,21 +68,7 @@ const MatchList: NextPage = () => { awayScore: +awayScoreRef.current.value }; - mutateUpdateMatch(request); - }; - - const handleUpdateMatch = () => { - if(!date || !location || date?.trim() === '' || place?.trim() === '' ) { - return; - } - - const request: FullMatch = { - ...matchesState.selectedMatch!, - date: date || matchesState.selectedMatch?.date || '', - location: place || matchesState.selectedMatch?.location || '' - }; - - mutateUpdateMatch(request); + // mutateUpdateMatch(request); }; const matchesListMap = matchesState.matches.map( @@ -289,15 +213,12 @@ const MatchList: NextPage = () => { } ); - const inputHandler = (input: string, event: any) => { - input === 'place' ? setPlace(event.target.value) : setDate(event.target.value); - }; - return ( <> {matchesListMap} + { - - -
- - - Place - - inputHandler('place', event)} value={place} /> - - - - Date - - inputHandler('date', event)} value={date} /> - -
-
); }; diff --git a/components/matches/modals/update-match.modal.tsx b/components/matches/modals/update-match.modal.tsx new file mode 100644 index 0000000..19716a8 --- /dev/null +++ b/components/matches/modals/update-match.modal.tsx @@ -0,0 +1,117 @@ +import { FormControl, FormLabel, Input, useDisclosure, useToast } from '@chakra-ui/react'; +import type { NextPage } from 'next'; +import { useMutation } from 'react-query'; +import { useSelector, useDispatch } from 'react-redux'; +import { useEffect, useState } from 'react'; +import ModalAlert from '../../layout/modal-alert'; +import { RootState } from '../../../interfaces/State'; +import { matchesListActions } from '../../../store/matches-list.slice'; +import { updateMatch } from '../../../services/api-configuration'; +import { FullMatch } from '../../../interfaces/Match'; +import styles from './update-match.module.scss'; + +const UpdateMatchModal: NextPage = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const matchesState = useSelector((state: RootState) => state.matchesList); + const dispatch = useDispatch(); + const toast = useToast(); + const [place, setPlace] = useState(''); + const [date, setDate] = useState(''); + + useEffect(() => { + if(matchesState.isUpdateMatch) { + onOpen(); + } + }, [matchesState, onOpen]); + + useEffect(() => { + setPlace(matchesState.selectedMatch?.location!); + setDate(matchesState.selectedMatch?.date!); + }, [matchesState]); + + const { mutate } = useMutation(updateMatch, { + onSuccess: async (response) => { + if (response.ok) { + dispatch(matchesListActions.updateMatch(response.data.match)); + dispatch(matchesListActions.setSelectedMatch(undefined)); + handleCloseModal(); + toast({ + title: 'Match updated', + description: 'You update the match.', + status: 'success', + duration: 9000, + isClosable: true, + }); + } else { + toast({ + title: 'Match updated', + description: 'Something went wrong, please try again later.', + status: 'error', + duration: 9000, + isClosable: true, + }); + dispatch(matchesListActions.setSelectedMatch(undefined)); + } + }, + onError: () => { + // TODO: handle error + }, + }); + + const handleUpdateMatch = () => { + if(!date || !location || date?.trim() === '' || place?.trim() === '' ) { + return; + } + + const request: FullMatch = { + ...matchesState.selectedMatch!, + date: date || matchesState.selectedMatch?.date || '', + location: place || matchesState.selectedMatch?.location || '' + }; + + mutate(request); + }; + + const handleCloseModal = () => { + dispatch(matchesListActions.setMatchModalAction({action: 'isUpdateMatch', value: false})); + onClose(); + }; + + const inputHandler = (input: string, event: any) => { + input === 'place' ? setPlace(event.target.value) : setDate(event.target.value); + }; + + return ( + handleCloseModal()} + onContinue={handleUpdateMatch} + actionColor='green' + title={`Update ${matchesState.selectedMatch?.home_team.name} vs ${matchesState.selectedMatch?.away_team.name}`} + continueLabel="Update" + > +
+ + + Place + + inputHandler('place', event)} value={place} /> + + + + Date + + inputHandler('date', event)} value={date} /> + +
+
+ ); +} + +export default UpdateMatchModal; diff --git a/components/matches/modals/update-match.module.scss b/components/matches/modals/update-match.module.scss new file mode 100644 index 0000000..4cfe9e1 --- /dev/null +++ b/components/matches/modals/update-match.module.scss @@ -0,0 +1,11 @@ +.updateMatch { + display: flex; + flex-direction: column; + + .formControl { + align-items: center; + display: flex; + flex-direction: column; + margin-bottom: 10px; + } +} From 7bb41188195df789ec319c4bbf7c4ffded23b7c2 Mon Sep 17 00:00:00 2001 From: Andres2D Date: Sun, 19 Feb 2023 22:42:41 -0500 Subject: [PATCH 6/7] Decouple full time match modal --- components/matches/match-list.module.scss | 12 -- components/matches/match-list.tsx | 76 +---------- components/matches/modals/full-time.modal.tsx | 128 ++++++++++++++++++ .../matches/modals/full-time.module.scss | 11 ++ interfaces/Match.ts | 1 + store/matches-list.slice.ts | 5 +- 6 files changed, 147 insertions(+), 86 deletions(-) create mode 100644 components/matches/modals/full-time.modal.tsx create mode 100644 components/matches/modals/full-time.module.scss diff --git a/components/matches/match-list.module.scss b/components/matches/match-list.module.scss index ca77fde..c7f864f 100644 --- a/components/matches/match-list.module.scss +++ b/components/matches/match-list.module.scss @@ -68,18 +68,6 @@ } } -.fullTime { - display: flex; - justify-content: space-around; - - .formControl { - align-items: center; - display: flex; - flex-direction: column; - width: 30%; - } -} - @media (max-width: $medium-small-device) { .section { flex-direction: column; diff --git a/components/matches/match-list.tsx b/components/matches/match-list.tsx index 7a125d9..7edd5e8 100644 --- a/components/matches/match-list.tsx +++ b/components/matches/match-list.tsx @@ -1,12 +1,10 @@ import type { NextPage } from 'next'; -import { FormControl, FormLabel, IconButton, Image, Input, useDisclosure, useToast } from '@chakra-ui/react'; +import { IconButton, Image } from '@chakra-ui/react'; import { DeleteIcon, SettingsIcon, CheckCircleIcon } from '@chakra-ui/icons'; -import { useRef, MutableRefObject } from 'react'; import { useRouter } from 'next/router'; import styles from './match-list.module.scss'; import Team from './team'; import { FullMatch } from '../../interfaces/Match'; -import ModalAlert from '../layout/modal-alert'; import LeaveIcon from '../../assets/svg/leave.svg'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../../interfaces/State'; @@ -14,22 +12,12 @@ import { matchesListActions } from '../../store/matches-list.slice'; import DeleteMatchModal from './modals/delete-match.modal'; import LeaveMatchModal from './modals/leave-match.modal'; import UpdateMatchModal from './modals/update-match.modal'; +import FullTimeModal from './modals/full-time.modal'; const MatchList: NextPage = () => { const matchesState = useSelector((state: RootState) => state.matchesList ); const dispatch = useDispatch(); - - - const { - isOpen: fulltimeModalIsOpen, - onOpen: fulltimeModalOnOpen, - onClose: fulltimeModalOnClose, - } = useDisclosure(); - - const homeScoreRef = useRef() as MutableRefObject; - const awayScoreRef = useRef() as MutableRefObject; - const router = useRouter(); const goToMatchDetails = (matchId: string) => { @@ -48,7 +36,7 @@ const MatchList: NextPage = () => { const showFullTimeModal = (match: FullMatch) => { dispatch(matchesListActions.setSelectedMatch(match)); - fulltimeModalOnOpen(); + dispatch(matchesListActions.setMatchModalAction({action: 'isFullTime', value: true })); }; const showUpdateMatchModal = (match: FullMatch) => { @@ -56,21 +44,6 @@ const MatchList: NextPage = () => { dispatch(matchesListActions.setSelectedMatch(match)); }; - const handleFulltime = () => { - if(homeScoreRef.current.value.trim() === '' || awayScoreRef.current.value.trim() === '' ) { - return; - } - - const request: FullMatch = { - ...matchesState.selectedMatch!, - fullTime: true, - homeScore: +homeScoreRef.current.value, - awayScore: +awayScoreRef.current.value - }; - - // mutateUpdateMatch(request); - }; - const matchesListMap = matchesState.matches.map( ({ _id, @@ -219,48 +192,7 @@ const MatchList: NextPage = () => { - - -
- - {matchesState.selectedMatch?.home_team.name} - - {matchesState.selectedMatch?.home_team.name} - - - - - {matchesState.selectedMatch?.away_team.name} - - {matchesState.selectedMatch?.away_team.name} - - - -
-
+ ); }; diff --git a/components/matches/modals/full-time.modal.tsx b/components/matches/modals/full-time.modal.tsx new file mode 100644 index 0000000..3bb9f9b --- /dev/null +++ b/components/matches/modals/full-time.modal.tsx @@ -0,0 +1,128 @@ +import { + FormControl, + FormLabel, + Image, + Input, + useDisclosure, + useToast +} from '@chakra-ui/react'; +import type { NextPage } from 'next'; +import { useMutation } from 'react-query'; +import { useSelector, useDispatch } from 'react-redux'; +import { MutableRefObject, useEffect, useRef } from 'react'; +import ModalAlert from '../../layout/modal-alert'; +import { RootState } from '../../../interfaces/State'; +import { matchesListActions } from '../../../store/matches-list.slice'; +import { updateMatch } from '../../../services/api-configuration'; +import { FullMatch } from '../../../interfaces/Match'; +import styles from './full-time.module.scss'; + +const FullTimeModal: NextPage = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const matchesState = useSelector((state: RootState) => state.matchesList); + const dispatch = useDispatch(); + const toast = useToast(); + const homeScoreRef = useRef() as MutableRefObject; + const awayScoreRef = useRef() as MutableRefObject; + + useEffect(() => { + if(matchesState.isFullTime) { + onOpen(); + } + }, [matchesState, onOpen]); + + const { mutate } = useMutation(updateMatch, { + onSuccess: async (response) => { + if (response.ok) { + dispatch(matchesListActions.updateMatch(response.data.match)); + dispatch(matchesListActions.setSelectedMatch(undefined)); + handleCloseModal(); + toast({ + title: 'Match updated', + description: 'You update the match.', + status: 'success', + duration: 9000, + isClosable: true, + }); + } else { + toast({ + title: 'Match updated', + description: 'Something went wrong, please try again later.', + status: 'error', + duration: 9000, + isClosable: true, + }); + dispatch(matchesListActions.setSelectedMatch(undefined)); + } + }, + onError: () => { + // TODO: handle error + }, + }); + + const handleUpdateMatch = () => { + if(homeScoreRef.current.value.trim() === '' || awayScoreRef.current.value.trim() === '' ) { + return; + } + + const request: FullMatch = { + ...matchesState.selectedMatch!, + fullTime: true, + homeScore: +homeScoreRef.current.value, + awayScore: +awayScoreRef.current.value + }; + + mutate(request); + }; + + const handleCloseModal = () => { + dispatch(matchesListActions.setMatchModalAction({action: 'isFullTime', value: false})); + onClose(); + }; + + return ( + handleCloseModal()} + onContinue={handleUpdateMatch} + actionColor='green' + title="Full time - Scores" + continueLabel="Update" + > +
+ + {matchesState.selectedMatch?.home_team.name} + + {matchesState.selectedMatch?.home_team.name} + + + + + {matchesState.selectedMatch?.away_team.name} + + {matchesState.selectedMatch?.away_team.name} + + + +
+
+ ); +} + +export default FullTimeModal; diff --git a/components/matches/modals/full-time.module.scss b/components/matches/modals/full-time.module.scss new file mode 100644 index 0000000..dd4cbd3 --- /dev/null +++ b/components/matches/modals/full-time.module.scss @@ -0,0 +1,11 @@ +.fullTime { + display: flex; + justify-content: space-around; + + .formControl { + align-items: center; + display: flex; + flex-direction: column; + width: 30%; + } +} diff --git a/interfaces/Match.ts b/interfaces/Match.ts index 046bef3..11ea51b 100644 --- a/interfaces/Match.ts +++ b/interfaces/Match.ts @@ -68,4 +68,5 @@ export interface MatchesListState { isDeleteMatch: boolean; isLeaveMatch: boolean; isUpdateMatch: boolean; + isFullTime: boolean; } diff --git a/store/matches-list.slice.ts b/store/matches-list.slice.ts index eda95d4..1037386 100644 --- a/store/matches-list.slice.ts +++ b/store/matches-list.slice.ts @@ -10,7 +10,7 @@ type DeleteMatchPayload = { }; type ModalActionPayload = { - action: 'isDeleteMatch' | 'isLeaveMatch' | 'isUpdateMatch', + action: 'isDeleteMatch' | 'isLeaveMatch' | 'isUpdateMatch' | 'isFullTime', value: boolean }; @@ -19,7 +19,8 @@ const initialState: MatchesListState = { selectedMatch: undefined, isDeleteMatch: false, isLeaveMatch: false, - isUpdateMatch: false + isUpdateMatch: false, + isFullTime: false }; const setMatchesList: CaseReducer> = From ddeb6af554eea681a890eaff3e22782306af1365 Mon Sep 17 00:00:00 2001 From: Andres2D Date: Sun, 19 Feb 2023 22:43:17 -0500 Subject: [PATCH 7/7] Update version --- components/layout/navbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/layout/navbar.tsx b/components/layout/navbar.tsx index 2558161..b3eb7ed 100644 --- a/components/layout/navbar.tsx +++ b/components/layout/navbar.tsx @@ -99,7 +99,7 @@ const Navbar: NextPage = () => { > Log out -

Version 2.6.0

+

Version 2.6.1