Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use async actions for token status page #526

Merged
merged 12 commits into from
Feb 14, 2021
2 changes: 1 addition & 1 deletion simplq/src/api/requestFactory/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ import * as TokenRequestFactory from './token';
export { QueueRequestFactory, TokenRequestFactory };

export { getUserQueues, deleteQueue, getQueueStatus, getQueueStatusByName } from './queue';
export { createToken } from './token';
export { createToken, getToken, deleteToken } from './token';
18 changes: 14 additions & 4 deletions simplq/src/api/requestFactory/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ export const createToken = (name, contactNumber, notifiable, queueId) => ({
},
});

// Get a token by ID
export const get = (tokenId) => ({ method: 'get', url: `/token/${tokenId}` });
/**
* Request creator to get a token by ID
*
* @param {string} tokenId
* @returns {Object} request - partial axios request without baseURL
*/
export const getToken = (tokenId) => ({ method: 'get', url: `/token/${tokenId}` });

// Notify a token This will result in the user being notified by SMS, which is an upcoming feature
export const notify = (tokenId) => ({ method: 'put', url: `/token/notify/${tokenId}` });

// Remove a token from the queue. Can be called only by the person who created the token, and the queue manager.
export const remove = (tokenId) => ({ method: 'delete', url: `/token/${tokenId}` });
/**
* Remove a token from the queue. Can be called only by the person who created the token, and the queue manager
*
* @param {string} tokenId
* @returns {Object} request - partial axios request without baseURL
*/
export const deleteToken = (tokenId) => ({ method: 'delete', url: `/token/${tokenId}` });
24 changes: 12 additions & 12 deletions simplq/src/components/pages/Status/LeaveQueue.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import CloseIcon from '@material-ui/icons/Close';
import { useHistory } from 'react-router-dom';
import SidePanelItem from 'components/common/SidePanel/SidePanelItem';
import { setInfoPopupMessage } from 'store/appSlice';
import { useDeleteToken } from 'store/asyncActions';
import { selectToken } from 'store/token';
import SidePanelItem from '../../common/SidePanel/SidePanelItem';

export default (props) => {
const history = useHistory();
export default () => {
const dispatch = useDispatch();
const handleClick = () => {
props.leaveQueueHandler().then(() => {
dispatch(setInfoPopupMessage('Successfully left queue'));
history.push(`/`);
});
const deleteToken = useDeleteToken();
const token = useSelector(selectToken);

const onDeleteClick = () => {
dispatch(deleteToken({ tokenId: token.tokenId, goHome: true }));
};

// TODO: The item should be disabled if token is already deteled
return (
<SidePanelItem
onClick={handleClick}
onClick={onDeleteClick}
Icon={CloseIcon}
title="Leave Queue"
description="Exit from the queue"
Expand Down
10 changes: 6 additions & 4 deletions simplq/src/components/pages/Status/QueueDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ import QueueStats from 'components/common/QueueStats';
import { useGetQueueStatus } from 'store/asyncActions';
import { selectQueueStatus } from 'store/queueStatus';
import { useDispatch, useSelector } from 'react-redux';
import { selectToken } from 'store/token';
import SidePanelItem from 'components/common/SidePanel/SidePanelItem';

export default ({ queueId }) => {
export default () => {
const queueStatus = useSelector(selectQueueStatus);
const token = useSelector(selectToken);
const dispatch = useDispatch();
const getQueueStatus = useCallback(useGetQueueStatus(), []);

useEffect(() => {
dispatch(getQueueStatus({ queueId }));
}, [queueId, dispatch, getQueueStatus]);
dispatch(getQueueStatus({ queueId: token.queueId }));
}, [token, dispatch, getQueueStatus]);

return (
<SidePanelItem
Icon={InfoIcon}
title="Queue Details"
description="Other information about the queue"
expandable
loading={!queueStatus}
loading={false} // TODO: Should be queueStatus's loading
>
<QueueStats queueStatus={queueStatus} />
</SidePanelItem>
Expand Down
39 changes: 25 additions & 14 deletions simplq/src/components/pages/Status/StatusContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
import React from 'react';
import LoadingIndicator from 'components/common/LoadingIndicator';
import Button from 'components/common/Button';
import { useDispatch, useSelector } from 'react-redux';
import { useGetToken } from 'store/asyncActions';
import { selectToken } from 'store/token';
import LoadingStatus from 'components/common/Loading/LoadingStatus';
import Button from '../../common/Button';
import styles from './status.module.scss';

export default (props) => {
export default () => {
const token = useSelector(selectToken);
const dispatch = useDispatch();
const getToken = useGetToken();

const onRefreshClick = () => {
dispatch(getToken({ tokenId: token.tokenId }));
};

let status = null;
if (props.updateInProgress) {
status = null;
} else if (props.tokenStatus === 'REMOVED') {
if (token.tokenStatus === 'REMOVED') {
status = <p>You have been removed from the queue, have a nice day</p>;
} else if (props.tokenStatus === 'NOTIFIED') {
} else if (token.tokenStatus === 'NOTIFIED') {
status = <p>Your turn is up, please proceed to the counter</p>;
} else if (props.aheadCount === 0) {
} else if (token.aheadCount === 0) {
status = <p>There is no one ahead of you. Please wait to be notified by the queue manager.</p>;
} else {
/* eslint-disable react/jsx-one-expression-per-line */
status = (
<>
<p>Hello {props.name}, </p>
<p>Hello {token.name}, </p>
<br />
<p>People waiting in front of you:</p>
<p className={styles['count']}>{props.aheadCount}</p>
<p className={styles['count']}>{token.aheadCount}</p>
<p>Please wait for your turn. You will be notified here.</p>
<div className={styles['refresh-button']}>
<Button onClick={props.update}>Refresh status</Button>
<Button onClick={onRefreshClick}>Refresh status</Button>
</div>
</>
);
}

if (!status) return <LoadingIndicator />;

return <div className={styles['status-box']}>{status}</div>;
return (
<div className={styles['status-box']}>
<LoadingStatus dependsOn="getToken">{status}</LoadingStatus>
</div>
);
};
6 changes: 3 additions & 3 deletions simplq/src/components/pages/Status/StatusSidePanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import ContactAdmin from './ContactAdmin';
import QueueDetails from './QueueDetails';
import NotificationContainer from './NotificationContainer';

export default (props) => (
export default () => (
<SidePanel>
<LeaveQueue leaveQueueHandler={props.leaveQueueHandler} />
<LeaveQueue />
<ContactAdmin />
<QueueDetails queueId={props.queueId} />
<QueueDetails />
<NotificationContainer />
</SidePanel>
);
31 changes: 20 additions & 11 deletions simplq/src/components/pages/Status/TokenNumber.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import LoadingStatus from 'components/common/Loading';
import React from 'react';
import { useSelector } from 'react-redux';
import { selectToken } from 'store/token';
import styles from './status.module.scss';

export default (props) => (
<div className={styles['token-number']}>
<div className={styles['hanging-threads']} />
<div className={styles['token-container']}>
<span className={styles['token']}>Token No</span>
</div>
<div className={styles['separator']} />
<div className={styles['count-container']}>
<span className={styles['count']}>{props.tokenNumber}</span>
export default () => {
const token = useSelector(selectToken);

return (
<div className={styles['token-number']}>
<div className={styles['hanging-threads']} />
<div className={styles['token-container']}>
<span className={styles['token']}>Token No</span>
</div>
<div className={styles['separator']} />
<div className={styles['count-container']}>
<span className={styles['count']}>
<LoadingStatus dependsOn="getToken">{token.tokenNumber}</LoadingStatus>
</span>
</div>
</div>
</div>
);
);
};
72 changes: 15 additions & 57 deletions simplq/src/components/pages/Status/index.jsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,34 @@
import React, { useState, useEffect, useCallback } from 'react';
import HeaderSection from 'components/common/HeaderSection';
import LoadingIndicator from 'components/common/LoadingIndicator';
import { TokenRequestFactory } from 'api/requestFactory';
import useRequest from 'api/useRequest';
import { notify } from 'services/notification';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useGetToken } from 'store/asyncActions';
import { selectToken } from 'store/token';
import styles from './status.module.scss';
import StatusContainer from './StatusContainer';
import StatusSidePanel from './StatusSidePanel';
import TokenNumber from './TokenNumber';

const TIMEOUT = 10000;
let timeoutId;

// TODO Rename component to token status, component and folder/page name
function QueueStatus(props) {
const tokenId = props.match.params.tokenId;
const [tokenStatusResponse, setTokenStatusResponse] = useState();
const [updateInProgress, setUpdateInProgress] = useState(false);
const { requestMaker } = useRequest();

const showNotification = useCallback(() => {
notify(`${tokenStatusResponse.queueName}: You've been notified by the queue manager.`);
}, [tokenStatusResponse]);

const oldTokenStatus = tokenStatusResponse ? tokenStatusResponse.tokenStatus : undefined;
const update = useCallback(() => {
clearTimeout(timeoutId);
requestMaker(TokenRequestFactory.get(tokenId)).then((response) => {
if (response) {
setTokenStatusResponse(response);
if (response.tokenStatus === 'NOTIFIED' && oldTokenStatus === 'WAITING') {
showNotification();
}
}
timeoutId = setTimeout(update, TIMEOUT);
});
// eslint-disable-next-line
}, [tokenId, oldTokenStatus]); // don't add showNotification, will result in infinite loop
const dispatch = useDispatch();
const token = useSelector(selectToken);
const getToken = useGetToken();
daltonfury42 marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
update();
return () => clearTimeout(timeoutId);
}, [update]);

const onDeleteClick = async () => {
setUpdateInProgress(true);
await requestMaker(TokenRequestFactory.remove(tokenId)).then((response) => {
if (response) {
setTokenStatusResponse({ ...tokenStatusResponse, tokenStatus: response.tokenStatus });
}
setUpdateInProgress(false);
});
};

if (!tokenStatusResponse) {
return <LoadingIndicator />;
}
dispatch(getToken({ tokenId, refresh: true }));
}, [tokenId, dispatch, getToken]);
Copy link
Contributor

@mradenovic mradenovic Feb 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getToken() here might cause fetch request on every render. Not 100% sure, please check, network tab, or just put some conosle.log() statement to see what's going on.

UPDATE: Just noticed it was don in action. I think it should be done here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think we should do here? I thought it's better to do it once inside the action than do it everytime the action than every time it is used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's merge this as is. It will make maintenance easier, and if we can polish it later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've standardised for now: bb8f29a

The PR will be merged on your approval.


return (
<>
<HeaderSection queueName={tokenStatusResponse.queueName} />
<HeaderSection queueName={token ? token.queueName : 'Loading...'} />
<div className={styles['main-body']}>
<TokenNumber tokenNumber={tokenStatusResponse.tokenNumber} />
<StatusContainer
name={tokenStatusResponse.name}
updateInProgress={updateInProgress}
tokenStatus={tokenStatusResponse.tokenStatus}
aheadCount={tokenStatusResponse.aheadCount}
update={update}
/>
<StatusSidePanel leaveQueueHandler={onDeleteClick} queueId={tokenStatusResponse.queueId} />
<TokenNumber />
<StatusContainer />
<StatusSidePanel />
</div>
</>
);
}

export default QueueStatus;
37 changes: 37 additions & 0 deletions simplq/src/store/asyncActions/deleteToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { useMakeAuthedRequest } from 'api/auth';
import * as RequestFactory from 'api/requestFactory';
import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router';
import { setInfoPopupMessage } from 'store/appSlice';

const typePrefix = 'deleteToken/action';

/**
* A hook to access the deleteToken async action creator.
*
* @returns — deleteToken async action creator
*/
const useDeleteToken = () => {
const makeAuthedRequest = useMakeAuthedRequest();
const history = useHistory();
const dispatch = useDispatch();

const deleteToken = createAsyncThunk(typePrefix, async (arg) => {
const { tokenId } = arg;
const authedRequest = makeAuthedRequest(RequestFactory.deleteToken(tokenId));
const response = await authedRequest;
dispatch(setInfoPopupMessage('Successfully left queue'));
if (arg.goHome) {
history.push('/');
}
return response;
});

return useCallback(deleteToken, []);
};

const deleteToken = createAsyncThunk(typePrefix);

export { deleteToken, useDeleteToken };
37 changes: 37 additions & 0 deletions simplq/src/store/asyncActions/getToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import useAuth, { makeAuthedRequest } from 'api/auth';
import * as RequestFactory from 'api/requestFactory';
import { useCallback } from 'react';

const typePrefix = 'getToken/action';
let timer = null;
const REFRESH_INTERVAL = 3000;

/**
* A hook to access the getToken async action creator.
*
* @returns — getToken async action creator
*/
const useGetToken = () => {
const auth = useAuth();

const getToken = createAsyncThunk(typePrefix, async ({ tokenId, refresh }, { dispatch }) => {
if (timer) {
clearTimeout(timer);
}
const authedRequest = makeAuthedRequest(auth, RequestFactory.getToken(tokenId));
const response = await authedRequest.then((resp) => {
if (refresh === true) {
timer = setTimeout(() => dispatch(getToken({ tokenId, refresh })), REFRESH_INTERVAL);
}
return resp;
});
return response;
});

return useCallback(getToken, []);
};

const getToken = createAsyncThunk(typePrefix);

export { getToken, useGetToken };
3 changes: 3 additions & 0 deletions simplq/src/store/asyncActions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ export { deleteQueue, useDeleteQueue } from './deleteQueue';
export { getQueueStatus, useGetQueueStatus } from './getQueueStatus';
export { getQueueStatusByName, useGetQueueStatusByName } from './getQueueStatusByName';
export { joinQueue, useJoinQueue } from './joinQueue';

export { getToken, useGetToken } from './getToken';
export { deleteToken, useDeleteToken } from './deleteToken';
Loading