Skip to content

Commit 3d23acd

Browse files
committed
Remember FAQ page state (#1091)
* Store page state in route * Add logic to remember opened questions * Fix cypress tests for updated back end data
1 parent 571a7b7 commit 3d23acd

File tree

5 files changed

+99
-62
lines changed

5 files changed

+99
-62
lines changed

cypress/integration/pages/faq.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const SEARCH = 'civic';
22
// const Q1 = 'Can I add multiple projects';
3-
const Q2 = 'What is Civic Tech';
3+
const Q2 = 'Who are index contributors';
44
// const A1 = 'Yes, Please follow the link to add multiple projects.';
5-
const A2 = 'Civic technology, or civic tech, enhances the relationship between the people and government';
5+
const A2 = 'are our partners who have contributed to the Civic Tech Index';
66

77
describe('FAQ Page (using API)', () => {
88
before(() => {
@@ -17,7 +17,7 @@ describe('FAQ Page (using API)', () => {
1717
cy.intercept(`${Cypress.env('REACT_APP_API_URL')}/api/faqs/*`).as(
1818
'getFaqs'
1919
);
20-
cy.get('[data-cy=search-faq]').click({ force: true }).type(SEARCH);
20+
cy.get('[data-cy=search-faq]').type(SEARCH);
2121
cy.wait('@getFaqs');
2222
cy.get('[data-cy=faq-question]')
2323
.first()

cypress/integration/pages/projects.spec.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('Projects Page (Search Projects)', () => {
2323
cy.intercept(`${Cypress.env('GITHUB_API_URL')}/search/repositories*`).as(
2424
'getProjects'
2525
);
26-
cy.get('[data-cy=search-projects]').click().type(SEARCH).type('{enter}');
26+
cy.get('[data-cy=search-projects]').type(SEARCH).type('{enter}');
2727
cy.wait('@getProjects');
2828
cy.get('[data-cy=how-to-improve-your-search-results]').click();
2929
cy.get('[data-cy=search-tips').within(() => {
@@ -34,7 +34,12 @@ describe('Projects Page (Search Projects)', () => {
3434
});
3535

3636
it('loads search results', () => {
37-
cy.get('[data-cy=search-projects]').click().type(SEARCH).type('{enter}');
37+
cy.visit('/projects');
38+
cy.intercept(`${Cypress.env('GITHUB_API_URL')}/search/repositories*`).as(
39+
'getProjects'
40+
);
41+
cy.get('[data-cy=search-projects]').type(SEARCH).type('{enter}');
42+
cy.wait('@getProjects');
3843
cy.get('[data-cy=project-card] [data-cy=project-url]').each(($el, index, $list) => {
3944
const innerText = $el.text();
4045
expect(RESULTS.indexOf(innerText)).to.be.at.least(0);

src/components/FAQCard/accordionSection.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ const SanitizedAnswer = ({ answer }) => {
5757
const AccordionSection = (props) => {
5858
const [currentFaq, setCurrentFaq] = useState([]);
5959
const [sendRequest, setSendRequest] = useState(false);
60-
const faqs = props.faqs;
6160
const classes = useStyles();
6261

6362
useEffect(() => {
@@ -78,15 +77,22 @@ const AccordionSection = (props) => {
7877

7978
return (
8079
<Grid item xs={12} sm={9} lg={11}>
81-
{faqs.map((faq) => (
80+
{props.faqs.map((faq) => (
8281
<Box key={faq.id}>
83-
<Accordion className={classes.accordion}>
82+
<Accordion
83+
className={classes.accordion}
84+
expanded={props.expandedFaqs.indexOf(faq.id.toString()) > -1}
85+
onClick={() => props.onFaqClick(faq.id)}
86+
>
8487
<AccordionSummary
8588
className={classes.summary}
8689
data-cy='faq-question'
8790
disabled={sendRequest}
8891
expandIcon={<ExpandMoreRoundedIcon />}
89-
onClick={() => { setSendRequest(true); setCurrentFaq(faq) }}
92+
onClick={() => {
93+
setSendRequest(true);
94+
setCurrentFaq(faq);
95+
}}
9096
>
9197
<Typography variant='h6'>{faq.question}</Typography>
9298
</AccordionSummary>
@@ -100,7 +106,7 @@ const AccordionSection = (props) => {
100106
</Box>
101107
))}
102108
</Grid>
103-
)
109+
);
104110
}
105111

106112
export default AccordionSection;

src/components/FAQCard/index.js

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,40 @@ const FAQCard = (props) => {
3333
const classes = useStyles();
3434

3535
return (
36-
<Box className="containerGray" py={4} px={1}>
36+
<Box className='containerGray' py={4} px={1}>
3737
<Container>
38-
<Grid container justify="center">
38+
<Grid container justify='center'>
3939
<Grid item xs={12} sm={9} lg={11} className={classes.titleContainer}>
40-
<Typography variant="h6" className={classes.title}>{props.title}</Typography>
40+
<Typography variant='h6' className={classes.title}>
41+
{props.title}
42+
</Typography>
4143
</Grid>
42-
{
43-
props.faqs.length > 0 ? (
44-
<>
45-
<AccordionSection faqs={props.faqs}/>
46-
<Grid item xs={12}>
47-
<Box my={3} display="flex" justifyContent="center">
48-
<Pagination
49-
color="secondary"
50-
count={props.pages}
51-
defaultPage={1}
52-
disabled={props.pages <= 1}
53-
onChange={props.onPageChange}
54-
page={props.currentPageNum}
55-
/>
56-
</Box>
57-
</Grid>
58-
</>
59-
) : (
60-
<Typography variant='body1' className={classes.message}>
61-
<SearchRoundedIcon className={classes.messageIcon}/>
62-
<i>Sorry, no results found.</i>
63-
</Typography>
64-
)
65-
}
44+
{props.faqs.length > 0 ? (
45+
<>
46+
<AccordionSection
47+
expandedFaqs={props.expandedFaqs}
48+
faqs={props.faqs}
49+
onFaqClick={props.onFaqClick}
50+
/>
51+
<Grid item xs={12}>
52+
<Box my={3} display='flex' justifyContent='center'>
53+
<Pagination
54+
color='secondary'
55+
count={props.pages}
56+
defaultPage={1}
57+
disabled={props.pages <= 1}
58+
onChange={props.onPageChange}
59+
page={props.currentPageNum}
60+
/>
61+
</Box>
62+
</Grid>
63+
</>
64+
) : (
65+
<Typography variant='body1' className={classes.message}>
66+
<SearchRoundedIcon className={classes.messageIcon} />
67+
<i>Sorry, no results found.</i>
68+
</Typography>
69+
)}
6670
</Grid>
6771
</Container>
6872
</Box>

src/pages/Faq/index.js

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable sort-keys */
1+
/* eslint-disable max-lines-per-function */
22
/* eslint-disable react-hooks/exhaustive-deps */
33
import React, { useCallback, useEffect, useState } from 'react';
44
import Box from '@material-ui/core/Box';
@@ -8,7 +8,7 @@ import useMediaQuery from '@material-ui/core/useMediaQuery';
88
import { makeStyles, useTheme } from '@material-ui/core/styles';
99
import axios from 'axios';
1010
import _ from 'lodash';
11-
11+
import { ArrayParam, NumberParam, StringParam, useQueryParam, withDefault } from 'use-query-params';
1212
import { GetStartedCard, GenericHeaderSection } from '../../components';
1313
import SearchBar from '../SearchProjects/SearchBar';
1414
import FAQCard from '../../components/FAQCard';
@@ -20,51 +20,71 @@ const useStyles = makeStyles({
2020
});
2121

2222
const Faq = () => {
23-
const breadCrumbLinks = [
24-
{ name: 'Home', href: '/home' },
25-
{ name: 'FAQ', href: '/about/faq' },
26-
];
27-
const [data, setData] = useState([]);
28-
const [pageNum, setPageNum] = useState(1);
29-
const [query, setQuery] = useState('');
30-
const [status, setStatus] = useState('fetchedFaq');
31-
const [totalCount, setTotalCount] = useState(0);
3223
const classes = useStyles();
33-
const apiUrl = `${process.env.REACT_APP_API_URL}/api/faqs/`;
34-
3524
const theme = useTheme();
3625
const largeScreen = useMediaQuery(theme.breakpoints.up('sm'), {
3726
noSsr: true,
3827
});
28+
const apiUrl = `${process.env.REACT_APP_API_URL}/api/faqs/`;
29+
const [faqs, setFaqs] = useState([]);
30+
const [status, setStatus] = useState('fetchedFaq');
31+
const [totalCount, setTotalCount] = useState(0);
32+
const [expandedFaqs, setExpandedFaqs] = useQueryParam(
33+
'opened',
34+
withDefault(ArrayParam, [])
35+
);
36+
const [pageNum, setPageNum] = useQueryParam(
37+
'page',
38+
withDefault(NumberParam, 1)
39+
);
40+
const [query, setQuery] = useQueryParam(
41+
'query',
42+
withDefault(StringParam, '')
43+
);
3944

40-
const getFAQData = async (currentQuery, resetPageNum) => {
45+
const breadCrumbLinks = [
46+
{ name: 'Home', href: '/home' },
47+
{ name: 'FAQ', href: '/about/faq' },
48+
];
49+
50+
const getFaqData = async (currentQuery, resetState) => {
4151
const params = {
42-
page: resetPageNum ? 1 : pageNum,
52+
page: resetState ? 1 : pageNum,
4353
page_size: largeScreen ? 10 : 5,
4454
search: currentQuery,
4555
};
46-
// reset pagination current page to 1 in certain cases such as new search query input
47-
if (resetPageNum) {
56+
if (resetState) {
57+
setExpandedFaqs([]);
4858
setPageNum(1);
4959
}
5060
const res = await axios.get(apiUrl, { params: params });
51-
setData(res.data.results);
61+
setFaqs(res.data.results);
5262
setTotalCount(res.data.count);
5363
setStatus(params.search ? 'fetchedSearch' : 'fetchedFaq');
5464
};
5565

5666
const debounce = useCallback(
5767
_.debounce((value) => {
58-
getFAQData(value, true);
68+
getFaqData(value, true);
5969
}, 300),
6070
[]
6171
);
6272

6373
useEffect(() => {
64-
getFAQData(query, false);
65-
// eslint-disable-next-line react-hooks/exhaustive-deps
74+
getFaqData(query, largeScreen);
6675
}, [pageNum, largeScreen]);
6776

77+
const handleFaqClick = (id) => {
78+
const expanded = [...expandedFaqs];
79+
const idx = expanded.indexOf(id.toString());
80+
if (idx > -1) {
81+
expanded.splice(idx, 1);
82+
} else {
83+
expanded.push(id.toString());
84+
}
85+
setExpandedFaqs(expanded);
86+
};
87+
6888
const handleInput = (event) => {
6989
setQuery(event.target.value);
7090
debounce(event.target.value);
@@ -87,17 +107,20 @@ const Faq = () => {
87107
dataCy='search-faq'
88108
onInput={handleInput}
89109
placeholder='Search the Civic Tech Index'
110+
value={query}
90111
/>
91112
</Grid>
92113
</Grid>
93114
</GenericHeaderSection>
94115
</Container>
95116
<FAQCard
96-
title={ status === 'fetchedFaq' ? 'Top Asked Questions' : `Search results (${totalCount})`}
97-
faqs={data}
98-
pages={Math.ceil(totalCount / (largeScreen ? 10 : 5))}
99117
currentPageNum={pageNum}
118+
expandedFaqs={expandedFaqs}
119+
faqs={faqs}
120+
onFaqClick={handleFaqClick}
100121
onPageChange={handlePageNumChange}
122+
pages={Math.ceil(totalCount / (largeScreen ? 10 : 5))}
123+
title={ status === 'fetchedFaq' ? 'Top Asked Questions' : `Search results (${totalCount})`}
101124
/>
102125
<GetStartedCard
103126
headerTitle='Can’t find an answer?'
@@ -107,5 +130,4 @@ const Faq = () => {
107130
</Box>
108131
);
109132
};
110-
111133
export default Faq;

0 commit comments

Comments
 (0)