Skip to content

Commit

Permalink
Fix/user feedback (#8)
Browse files Browse the repository at this point in the history
* Add fix for PostCode on Match form

- Add value prop

* Add SummaryTable component for useful stats

- For num results + capped results

* Add ChildRefTable component for child references

- Not using Redux, just using fetch() inside the component

* Add siccodes conversion JSON

- Add SIC code conversion to expand view

* Add ResultsTable component

* Add additional props into ResultsTable

- DefaultPageSize, pagination etc.

* Fix clear functionality for UBRN search

- Focus on UBRN input after clear

* Remove comments, add config for /business endpoint

* Add proper formatting of ExpandView JSON

* Add Business Index description on Home page

* Fix Logout logic, handle 500's

- If there is a server error whilst logging out, the user will still be logged out

* Clear sessionStorage rather than localStorage
  • Loading branch information
ONS-Tom authored Nov 13, 2017
1 parent a5dc1fd commit a8a5bfc
Show file tree
Hide file tree
Showing 14 changed files with 1,034 additions and 188 deletions.
14 changes: 14 additions & 0 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ app.post('/logout', (req, res) => {
res.sendStatus(200);
});

app.post('/logout', (req, res) => {
logger.info('Logging user out');
const token = req.body.token;
try {
// Remove user from storage
delete sessions[token];
logger.info('Successful logout');
res.sendStatus(200);
} catch (e) {
logger.error(`Unable to log user out: ${e}`);
res.sendStatus(500);
}
});

app.post('/api', (req, res) => {
// re route api requests with API key
const method = req.body.method;
Expand Down
7 changes: 5 additions & 2 deletions src/actions/LoginActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,20 @@ export function logout() {
return (dispatch) => {
dispatch(sendingRequest(true));
auth.logout(sessionStorage.accessToken, (success) => {
dispatch(sendingRequest(false));
if (success) {
dispatch(sendingRequest(false));
dispatch(setAuthState(false));
localStorage.clear();
sessionStorage.clear();
browserHistory.push('/');
// This needs to go at the end, or else if we logout whilst on a page
// that uses the redux store, an error will occur before the user
// is redirected to '/'.
dispatch(resetState(undefined));
} else {
dispatch(setErrorMessage(errorMessages.GENERAL_ERROR));
sessionStorage.clear();
browserHistory.push('/');
dispatch(resetState(undefined));
}
});
};
Expand Down
90 changes: 90 additions & 0 deletions src/components/ChildRefTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import ErrorModal from '../components/ErrorModal';
import industryCodeDescription from '../utils/siccode';
import config from '../config/api-urls';
import { formatData } from '../utils/helperMethods';

const { REROUTE_URL, API_VERSION, BUSINESS_ENDPOINT } = config;

class ChildRefTable extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: [],
error: false,
errorMessage: '',
};
this.closeModal = this.closeModal.bind(this);
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
this.fetchData(this.props.row);
}
fetchData(row) {
fetch(`${REROUTE_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': sessionStorage.getItem('accessToken'),
},
body: JSON.stringify({
method: 'GET',
endpoint: `${API_VERSION}/${BUSINESS_ENDPOINT}/${row.original.id}`,
}),
})
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error(`Error: ${response.status} ${response.statusText}`);
})
.then(data => this.setState({ data: formatData(data), isLoading: false }))
.catch(error => this.setState({ errorMessage: error.message, error: true, isLoading: false }));
}
closeModal() {
this.setState({ error: false, errorMessage: '' });
}
render() {
return (
<div style={{ padding: '20px' }}>
<ReactTable
data={this.state.data}
columns={[
{
Header: 'Company Number',
accessor: 'companyNo',
},
{
Header: 'VAT References',
accessor: 'vatRefs',
},
{
Header: 'PAYE References',
accessor: 'payeRefs',
},
]}
defaultPageSize={1}
loading={this.state.isLoading}
className="-striped -highlight"
showPaginationTop={false}
showPaginationBottom={false}
/>
<h3>Industry Code: {industryCodeDescription[this.state.data.industryCode]}</h3>
<ErrorModal
show={this.state.error}
message={this.state.errorMessage}
close={this.closeModal}
/>
</div>
);
}
}

ChildRefTable.propTypes = {
row: PropTypes.object.isRequired,
};

export default ChildRefTable;
2 changes: 1 addition & 1 deletion src/components/MatchForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class MatchForm extends React.Component {
<SelectInput value={this.props.initialValues.LegalStatus} label="Legal Status" id="LegalStatus" onChange={this.props.onChange} bands={legalStatusBands} /><br />
<SelectInput value={this.props.initialValues.Turnover} label="Turnover" id="Turnover" onChange={this.props.onChange} bands={turnoverBands} /><br />
<SelectInput value={this.props.initialValues.TradingStatus} label="Trading Status" id="TradingStatus" onChange={this.props.onChange} bands={tradingStatusBands} /><br />
<TextInput label="Post Code" id="PostCode" onChange={this.props.onChange} /><br />
<TextInput value={this.props.initialValues.PostCode} label="Post Code" id="PostCode" onChange={this.props.onChange} /><br />
<Button id="loginButton" size="wide" text="Search" onClick={!this.props.currentlySending ? this.props.onSubmit : null} ariaLabel="Login Button" type="submit" loading={this.props.currentlySending} />
&nbsp;
<Button id="clearButton" size="wide" text="Clear" onClick={this.props.onClear} ariaLabel="Clear Button" type="reset" />
Expand Down
88 changes: 88 additions & 0 deletions src/components/ResultsTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import SummaryTable from '../components/SummaryTable';
import ChildRefTable from '../components/ChildRefTable';

const ResultsTable = ({ results, showFilter, showPagination, defaultPageSize }) => {
return (
<div id="react-table">
<ReactTable
showPagination={showPagination}
data={results}
filterable={showFilter}
columns={[
{
Header: 'UBRN',
id: 'id',
accessor: d => d.id,
},
{
Header: 'Business Name',
id: 'businessName',
accessor: d => d.businessName,
},
{
Header: 'PostCode',
id: 'postCode',
accessor: d => d.postCode,
},
{
Header: 'Industry Code',
id: 'industryCode',
accessor: d => d.industryCode,
},
{
Header: 'Legal Status',
id: 'legalStatus',
accessor: d => d.legalStatus,
},
{
Header: 'Trading Status',
id: 'tradingStatus',
accessor: d => d.tradingStatus,
},
{
Header: 'Turnover',
id: 'turnover',
accessor: d => d.turnover,
},
{
Header: 'Employment Bands',
id: 'employmentBands',
accessor: d => d.employmentBands,
},
]}
defaultPageSize={defaultPageSize}
className="-striped -highlight"
SubComponent={row => {
return (
<ChildRefTable row={row} />
);
}}
/>
<br /><br />
<SummaryTable
title="Useful Information"
items={[
{ key: 'Number of results', value: results.length },
{ key: 'Results capped at 10,000', value: (results.length === 10000) ? 'true' : 'false' },
]}
/>
</div>
);
};

ResultsTable.defaultProps = {
defaultPageSize: 10,
};

ResultsTable.propTypes = {
results: PropTypes.array.isRequired,
showFilter: PropTypes.bool.isRequired,
showPagination: PropTypes.bool.isRequired,
defaultPageSize: PropTypes.number,
};

export default ResultsTable;
33 changes: 33 additions & 0 deletions src/components/SummaryTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';

const SummaryTable = ({ title, items }) => {
return (
<div className="sdc-isolation">
<div className="summary">
<h2 className="summary__title saturn" id="">{title}</h2>
{
items.map((item) => {
return (
<div className="summary__items">
<div className="summary__question" id="">
{item.key}
</div>
<div className="summary__answer">
<div className="summary__answer-text" id="">{item.value}</div>
</div>
</div>
);
})
}
</div>
</div>
);
};

SummaryTable.propTypes = {
title: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
};

export default SummaryTable;
20 changes: 9 additions & 11 deletions src/components/UBRNForm.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import Loader from 'halogen/PulseLoader';
import { Button } from 'registers-react-library';
import TextInput from './TextInput';

class UBRNForm extends React.Component {
render() {
const spinner = (<Loader color="#FFFFFF" size="10px" margin="0px" />);
const icon = (<span className="icon icon-search--light"></span>);
const buttonContent = (this.props.currentlySending) ? spinner : icon;
return (
<form className="col-wrap search__form" action="/search" method="get">
<label className="search__label col col--md-5 col--lg-6" htmlFor="nav-search">UBRN</label>
<input ref={ip => (this.myInput = ip)} placeholder="Enter UBRN to search..." autoFocus onChange={this.props.onChange} type="search" autoComplete="on" className="search__input col col--md-21 col--lg-32" id="nav-search" name="q" value={this.props.value} />
<button onClick={!this.props.currentlySending ? this.props.onSubmit : null} aria-label="Search UBRN button" type="submit" className={`search__button col--md-3 col--lg-3 ${this.props.valid}`} id="nav-search-submit">
{buttonContent}
</button>
<form>
<TextInput ref={ip => (this.childTextInput = ip)} value={this.props.value} label="UBRN" id="UBRN" autoFocus onChange={this.props.onChange} /><br />
<Button id="loginButton" size="wide" text="Search" onClick={!this.props.currentlySending ? this.props.onSubmit : null} ariaLabel="Login Button" type="submit" loading={this.props.currentlySending} />
&nbsp;
<Button id="clearButton" size="wide" text="Clear" onClick={this.props.onClear} ariaLabel="Clear Button" type="reset" />
<br /><br />
</form>
);
}
Expand All @@ -23,7 +21,7 @@ UBRNForm.propTypes = {
currentlySending: PropTypes.bool.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
valid: PropTypes.string.isRequired,
onClear: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
};

Expand Down
1 change: 1 addition & 0 deletions src/config/api-urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const apiUrls = {
AUTH_URL: 'http://localhost:3001',
REROUTE_URL: 'http://localhost:3001/api',
SEARCH_ENDPOINT: 'search/',
BUSINESS_ENDPOINT: 'business',
API_VERSION: 'v1',
};

Expand Down
25 changes: 25 additions & 0 deletions src/utils/helperMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,28 @@ export function getLegalStatusDescription(status: string) {
return 'Not Allocated';
}
}

export function maxSize(...args) {
return args.reduce((a, b) => (a > b ? a.length : b.length), 0);
}

export function formatData(business: {}) {
const largestRef = maxSize(business.vatRefs, business.payeRefs);
const formattedData = [];
for (let i = 0; i <= largestRef; i += 1) {
if (i === 0) {
formattedData.push({
companyNo: business.companyNo,
vatRefs: business.vatRefs[i],
payeRefs: business.payeRefs[i],
});
} else {
formattedData.push({
companyNo: '',
vatRefs: business.vatRefs[i],
payeRefs: business.payeRefs[i],
});
}
}
return formattedData;
}
Loading

0 comments on commit a8a5bfc

Please sign in to comment.