Skip to content

Commit

Permalink
#102 Updated modal state
Browse files Browse the repository at this point in the history
  • Loading branch information
mjamry committed Feb 17, 2022
1 parent 527fe42 commit dd5990e
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 152 deletions.
17 changes: 7 additions & 10 deletions src/TripPlanner.App/ClientApp/src/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { LocalizationProvider } from '@mui/lab';
import DateAdapter from '@mui/lab/AdapterDayjs';
import { RecoilRoot } from 'recoil';
import { NotificationStateProvider } from './State/NotificationState';
import { ModalStateProvider } from './State/ModalState';
import ToasterNotificationsComponent from './components/ToasterNotifications';
import { PlansStateProvider } from './State/PlansState';

Expand All @@ -28,15 +27,13 @@ function AppContext({ children }: Props) {
<ToasterNotificationsComponent />
<RecoilRoot>
<PlansStateProvider>
<ModalStateProvider>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={DateAdapter}>
{children}
</LocalizationProvider>
</ThemeProvider>
</StyledEngineProvider>
</ModalStateProvider>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={DateAdapter}>
{children}
</LocalizationProvider>
</ThemeProvider>
</StyledEngineProvider>
</PlansStateProvider>
</RecoilRoot>
</NotificationStateProvider>
Expand Down
96 changes: 29 additions & 67 deletions src/TripPlanner.App/ClientApp/src/State/ModalState.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import React, {
createContext, useContext, useMemo, useReducer,
} from 'react';
import { atom, selector } from 'recoil';

const enum ModalTypes {
addLocation,
Expand All @@ -13,77 +11,41 @@ const enum ModalTypes {
sharePlan,
}

const enum ModalStateAction {
show,
hide,
update,
}

type State = {
type ModalState = {
type?: ModalTypes;
isVisible: boolean;
data?: any
data?: any;
isVisible?: boolean;
}

type Action =
| { type: ModalStateAction.show, data: any, modalType: ModalTypes }
| { type: ModalStateAction.hide }
| { type: ModalStateAction.update, data: any }

type Dispatch = (action: Action) => void;

const initialState: State = {
type: undefined,
isVisible: false,
data: undefined,
};

const reducer: React.Reducer<State, Action> = (state: State, action: Action) => {
let newState: State = state;
const modalState = atom<ModalState>({
key: 'modalState',
default: { type: undefined, data: undefined },
});

switch (action.type) {
case ModalStateAction.show:
newState = {
...state, data: action.data, type: action.modalType, isVisible: true,
};
break;
case ModalStateAction.hide:
newState = { data: undefined, type: undefined, isVisible: false };
break;
case ModalStateAction.update:
newState = { ...state, data: action.data };
break;
default:
break;
}

return newState;
};

const ModalContext = createContext<{ state: State, dispatch: Dispatch }>(
{
state: initialState,
dispatch: () => undefined,
const showModalState = selector<ModalState>({
key: 'modalState.show',
get: ({ get }) => get(modalState),
set: ({ set }, value) => {
set(modalState, { ...value, isVisible: true });
},
);
const useModalState = () => useContext(ModalContext);

type Props = {
children: JSX.Element
}

function ModalStateProvider({ children }: Props) {
const [state, dispatch] = useReducer<React.Reducer<State, Action>>(reducer, initialState);
});

const value = useMemo<{state: State, dispatch: Dispatch}>(() => ({ state, dispatch }), [state]);
const hideModalState = selector<ModalState>({
key: 'modalState.hide',
get: ({ get }) => get(modalState),
set: ({ set }) => {
set(modalState, { type: undefined, data: undefined, isVisible: false });
},
});

return (
<ModalContext.Provider value={value}>
{children}
</ModalContext.Provider>
);
}
const updateModalState = selector<ModalState>({
key: 'modalState.update',
get: ({ get }) => get(modalState),
set: ({ set }, value) => {
set(modalState, { ...value, isVisible: true });
},
});

export {
ModalStateProvider, useModalState, ModalTypes, ModalStateAction,
modalState, ModalTypes, showModalState, hideModalState, updateModalState,
};
17 changes: 8 additions & 9 deletions src/TripPlanner.App/ClientApp/src/components/Search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import SearchIcon from '@mui/icons-material/Search';
import CircularProgress from '@mui/material/CircularProgress';
import { Button, Popover } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { ModalStateAction, useModalState, ModalTypes } from '../../State/ModalState';
import { useSetRecoilState } from 'recoil';
import useLoggerService from '../../Services/Diagnostics/LoggerService';
import useWikiSearch from '../../Common/WikiSearchService';
import SearchResult from './SearchResult';
import { LocationFormStateActions, useLocationFormState } from '../modals/LocationDetailsForm/LocationDetailsFormState';
import { ModalTypes, showModalState } from '../../State/ModalState';

const useStyles = makeStyles({
root: {
Expand Down Expand Up @@ -39,7 +40,7 @@ function Search(props: Props) {
const [searchValue, setSearchValue] = useState<string>(locationState.location.name || '');
const [searchResults, setSearchResults] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(false);
const { dispatch: dispatchModal } = useModalState();
const showModal = useSetRecoilState(showModalState);
const logger = useLoggerService('Search');
const [searchResultAnchor, setSearchResultAnchor] = React.useState<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -70,21 +71,19 @@ function Search(props: Props) {

updateLocationState(selectedResult);

dispatchModal({
type: ModalStateAction.show,
modalType: ModalTypes.loading,
showModal({
type: ModalTypes.loading,
data: undefined,
});

wikiSearch
.getDetails(selectedResult)
.then((location) => {
dispatchModal({
type: ModalStateAction.show,
showModal({
type: ModalTypes.addLocation,
data: location,
modalType:
ModalTypes.addLocation,
});

logger.debug(`[Search] Received data for ${selectedResult}`, location);
})
.catch((error) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import ModalWrapper from './ModalComponent';
import { useModalState, ModalTypes, ModalStateAction } from '../../State/ModalState';
import useLocationFormBuilder from './LocationDetailsForm/LocationDetailsForm';
import ModalHeader from './ModalHeader';
import Confirmation from './Confirmation';
Expand All @@ -15,6 +15,9 @@ import { PlanEmpty } from '../../Common/Dto/PlanDto';
import SharePlanComponent from './Share/SharePlan';
import ShareStateFooter from './Share/SharePlanFooter';
import { ShareStateProvider } from './Share/ShareState';
import {
hideModalState, modalState, ModalTypes,
} from '../../State/ModalState';

const emptyModal = {} as ModalDto;

Expand All @@ -23,7 +26,9 @@ type ModalModel = ModalDto | ModalDto<typeof LocationFormStateProvider>;
// TODO extract to separate file

const useModalContentFactory = () => {
const { state, dispatch } = useModalState();
const hideModal = useSetRecoilState(hideModalState);
const { data: modalData } = useRecoilValue(modalState);

const locationService = useLocationService();
const planService = usePlanService();
const logger = useLoggerService('ModalContentFactory');
Expand All @@ -37,11 +42,11 @@ const useModalContentFactory = () => {
return locationFormBuilder(
{
title: 'Add location',
location: state.data,
location: modalData,
onSubmit: (data) => {
// TODO: add correct planID
locationService.add(data, 0);
dispatch({ type: ModalStateAction.hide });
hideModal({});
},
},
);
Expand All @@ -50,26 +55,26 @@ const useModalContentFactory = () => {
return locationFormBuilder(
{
title: 'Edit location',
location: state.data,
location: modalData,
onSubmit: (data) => {
locationService.edit(data);
dispatch({ type: ModalStateAction.hide });
hideModal({});
},
},
);

case ModalTypes.removeLocation:
{
const submitAction = () => {
locationService.remove(state.data);
dispatch({ type: ModalStateAction.hide });
locationService.remove(modalData);
hideModal({});
};

return {
header: <ModalHeader title={`Do you want to remove\n\r "${state.data.name}"`} />,
header: <ModalHeader title={`Do you want to remove\n\r "${modalData.name}"`} />,
body: <Confirmation
onSubmit={() => submitAction()}
onCancel={() => dispatch({ type: ModalStateAction.hide })}
onCancel={() => hideModal({})}
/>,
}; }

Expand All @@ -86,7 +91,7 @@ const useModalContentFactory = () => {
plan: PlanEmpty,
onSubmit: (data) => {
planService.add(data);
dispatch({ type: ModalStateAction.hide });
hideModal({});
},
},
);
Expand All @@ -95,26 +100,26 @@ const useModalContentFactory = () => {
return planFormBuilder(
{
title: 'Edit plan',
plan: state.data,
plan: modalData,
onSubmit: (data) => {
planService.edit(data);
dispatch({ type: ModalStateAction.hide });
hideModal({});
},
},
);

case ModalTypes.removePlan:
{
const submitAction = () => {
planService.remove(state.data);
dispatch({ type: ModalStateAction.hide });
planService.remove(modalData);
hideModal({});
};

return {
header: <ModalHeader title={`Do you want to remove\n\r "${state.data.name}"`} />,
header: <ModalHeader title={`Do you want to remove\n\r "${modalData.name}"`} />,
body: <Confirmation
onSubmit={() => submitAction()}
onCancel={() => dispatch({ type: ModalStateAction.hide })}
onCancel={() => hideModal({})}
/>,
};
}
Expand All @@ -123,10 +128,10 @@ const useModalContentFactory = () => {
return {
header: <ModalHeader title="Share with" />,
body: <SharePlanComponent
usersToShare={state.data.usersToShare}
shares={state.data.shares}
usersToShare={modalData.usersToShare}
shares={modalData.shares}
/>,
footer: <ShareStateFooter planId={state.data.planId} />,
footer: <ShareStateFooter planId={modalData.planId} />,
state: ShareStateProvider,
};

Expand All @@ -141,37 +146,38 @@ const useModalContentFactory = () => {

const ModalContainer = () => {
const [modalContent, setModalContent] = useState<ModalModel>(emptyModal);
const { state, dispatch } = useModalState();
const factory = useModalContentFactory();
const { isVisible, type: modalType } = useRecoilValue(modalState);
const hideModal = useSetRecoilState(modalState);

const renderModal = () => {
if (modalContent.state) {
return (
<modalContent.state>
<ModalWrapper
isVisible={state.isVisible}
isVisible={isVisible!}
header={modalContent.header}
body={modalContent.body}
footer={modalContent.footer}
onClickAway={() => dispatch({ type: ModalStateAction.hide })}
onClickAway={() => hideModal({})}
/>
</modalContent.state>
);
}
return (
<ModalWrapper
isVisible={state.isVisible}
isVisible={isVisible!}
header={modalContent.header}
body={modalContent.body}
footer={modalContent.footer}
onClickAway={() => dispatch({ type: ModalStateAction.hide })}
onClickAway={() => hideModal({})}
/>
);
};

useEffect(() => {
setModalContent(factory(state.type!));
}, [state]);
setModalContent(factory(modalType!));
}, [modalType]);

return renderModal();
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import React from 'react';
import IconButton from '@mui/material/IconButton';
import CancelIcon from '@mui/icons-material/Cancel';
import { useModalState, ModalStateAction } from '../../State/ModalState';
import { useSetRecoilState } from 'recoil';
import { hideModalState } from '../../State/ModalState';

type Props = {
title: string;
}

function ModalHeader({ title }: Props) {
const { dispatch } = useModalState();
const hideModal = useSetRecoilState(hideModalState);

return (
<div className="modal-header-container">
<div className="modal-header-title">{title}</div>
<IconButton
onClick={() => { dispatch({ type: ModalStateAction.hide }); }}
onClick={() => hideModal({})}
title="close"
size="small"
>
Expand Down
Loading

0 comments on commit dd5990e

Please sign in to comment.