PRD - review FAQ search bar and CMS settings
Overview
We need to review the FAQ search bar and CMS settings to ensure we understand what's being searched and that we know how to include metadata and heading structure.
Action Items
- [x] Confirm that this content is being pulled from the CMS (Contentful?)
- Answer: data in 311's Contentful dashboard matches our FAQ content
- see also: confirmed via network tab, https://github.com/hackforla/311-data/issues/2000#issuecomment-3199712995
- see also: confirmed via faqs.jsx, https://github.com/hackforla/311-data/issues/2000#issuecomment-3199738687
- [x] Find the CMS credentials
- [ ] Add the UXWriter to 1Password vault for Contentful access
- Status: permission requested: https://github.com/hackforla/311-data/issues/2000#issuecomment-3199843719
- [ ] Take tickets out of icebox:
- [ ] #1999
- [ ] https://github.com/hackforla/311-data/issues/2001
- [x] Review the search bar to see if/what data it can be connected to
- [ ] Investigate the content model and code to see how to add metadata and headings to the page
Resources
Content note: once we have a draft of the FAQ, we'll be able to determine whether we need a search bar or sections within the FAQ.
Notes for Product:
- please determine whether this should be one ticket or whether you'd rather split out the search bar portion into a separate task.
- please also select a complexity and sizing for this task. I don't feel qualified to do that.
This is ready for a PM to pick up 👍
I've picked this up....
ETA for pre-work: 30 min
Report: Does FAQ content comes from Contentful?
TLDR
Yes, we can verify this by observing our FAQ page's content arrive into our app from contentful servers
Dev Report
Had a look at the developer tools for the FAQ page, and in the network tab we can see an API call that goes out to contentful that returns the content that supplies our FAQ page:
Click to see: Contentful API call returns the contents of our FAQ
And this lines up with what we see by logging into our Contentful account, available in our P: 311-Data 1Password vault:
Click to see: Contentful dashboard contains the contents of our FAQ
Report: Does the code suggest we use Contentful?
TLDR
Yes, we are using Contentful libraries to populate the FAQ page. We do not store the contents of our FAQ page locally or via any other 3rd party.
Dev Report
Looks like in the code, we are using a react-hook to grab data from contentful. Once the hook performs the query (see const query =...), it returns the data in the format of data.faqCollection, which is an array... and this most likely takes the shape shown in the first screenshot in the previous comment, e.g. ( data > faqCollection > items > [ item-1, item-2, item-3, etc], where items have an ID, a Question string, and an Answer string).
Code for reference...
Components > main > Faqs.jsx
import useContentful from '../../hooks/useContentful';
//...
const query = `
query {
faqCollection(order: priority_ASC) {
items {
sys { id }
question
answer
}
}
}
`;
function Faqs() {
const { data, errors } = useContentful(query);
// ...
const handleExpandAll = () => {
setAllExpanded(prevAllExpanded => !prevAllExpanded);
setExpanded(
data.faqCollection.items.reduce(
(prevExpanded, item) => ({
...prevExpanded,
[item.sys.id]: !allExpanded,
}),
// ...
Report: How is the FAQ searchbar represented in the code? What does it do?
TLDR
The searchbar does nothing and it is NOT hooked up to Contentful. It is custom React code that was likely made to interact with the FAQ data, but this functionality was never implemented.
Dev Report
I found the HTML id by running the code locally and using the Components tab in the developer tools, which lets me hover over the searchbar to reveal details about the React dom element. This reveals the id is faq-search:
Click to see: faq-search revealed in developer tools
I did a CTRL+F in my IDE to reveal that this component can be also be found in FAQs.jsx (surprise surprise)
<form className={classes.searchBar} onSubmit={handleSubmit}>
<TextField
placeholder="Type your question here..."
id="faq-search"
name="search"
type="search"
variant="outlined"
required
className={classes.textField}
value={searchFormState}
onChange={handleSearchFieldOnChange}
InputProps={{
className: classes.textFieldInput,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Button
className={classes.searchButton}
variant="contained"
color="secondary"
type="search"
>
Search
</Button>
Posting this snippet a bit out of order wrt the code, let's follow where handleSubmit goes, since this is the function that is run when you actually click "Search"
const handleSubmit = e => {
e.preventDefault();
console.log(searchFormState);
if (searchFormState) {
alert(`Searching: ${searchFormState}`);
}
};
Having a look at the code, this explains getting the alert you get when searching something and clicking "Search"
Let's follow it further... what is searchFormState ? This is the value that gets alerted (in the pic above, the searchFormState="asdf")
const [searchFormState, setSearchFormState] = React.useState('');
// ...
<TextField
id="faq-search"
value={searchFormState}
onChange={handleSearchFieldOnChange}
/>
I've simplified the same TextField from earlier, but we see that searchFormState is literally whatever is inside the search bar at any given time. We also see a handleSearchFieldOnChange that fires off anytime the searchbar contents is changed by the user. So what does handleSearchFieldOnChange do?
const handleSearchFieldOnChange = e => {
setSearchFormState(e.target.value);
};
It... updates the state. So at this point searchFormState takes on the same value as #faq-search.value. In other words, we are not doing anything useful with this code.
I've kicked off the process for adding someone to 1Password on Slack:
- https://hackforla.slack.com/archives/C01Q24YF56J/p1755593430531689
Hi @jberzowska, can you help me understand what specifically you are looking for? I'm going to need an explicit definition on the metadata you want. Also, you want headings for the page? Like these? https://www.w3schools.com/html/html_headings.asp
update: ah, you need headings for SEO. See comment below on findings
RE:
Investigate the content model and code to see how to add metadata and headings to the page
I've grabbed the HTML in the developer tab and tossed it into chatGPT, I hope this is useful. This is what it found:
<h4> — “311DATA” (site logo in the AppBar)
<h6> — “FILTERS”
<h3> — wraps the hero/search block (contains another heading inside)
<h4> — “What can we help you with?” (nested inside the <h3>)
<h6> — “Frequently Asked Questions”
<h6> — “Expand All”
<h6> — “What are Neighborhood Councils?”
<h6> — “What do Neighborhood Councils do?”
<h6> — “Why do some Neighborhood Council boundaries overlap?”
<h6> — “How are Neighborhood Councils funded?”
<h6> — “How can I submit a service request?”
If we want to know what it had to say about this...
Click to see ChatGPT's Commentary
SEO / accessibility issues
- There is no <h1> on the page. (Search/assistive tech expect one primary page heading.)
- The first heading is an <h4> on the site logo; that should not be part of the content outline.
- There’s a heading nested inside another heading: an <h4> inside an <h3> wrapper. Headings must not contain other headings.
- Multiple <h6> used for section titles (“Frequently Asked Questions”, “FILTERS”, questions). Skipping levels and using the smallest level for important sections makes the outline confusing to screen readers and weakens SEO signals.
Recommended outline (quick win)
- AppBar logo: not a heading. Use <span> or <p> visually styled as h4, or MuiTypography variant="h4" component="span".
- Page title: <h1> “Frequently Asked Questions” (or “FAQ”).
- Hero/search prompt: <h2> “What can we help you with?”.
- Filters sidecard title: <h2> “Filters” (if it’s a main section) or <h3> if subordinate to the page content.
- Each FAQ question: <h3> (or <h2> if they’re peer to hero). If collapsible, the clickable control can be a <button aria-expanded> adjacent to the heading, or the heading can contain the button (but not another heading).
Checklist
- [ ] Exactly one <h1> per page (the page topic).
- [ ] Increase levels sequentially (h1 → h2 → h3…), don’t skip.
- [ ] Avoid putting headings inside headings.
- [ ] Keep nav/brand out of the heading outline.
Oh, those headings are messy. We should have an H1 and then the rest should be nested (an h1 can be followed by an h2 or another h1, etc.).
What I meant by metadata was the things that go into the page header - historically for SEO but now moreso so that you don't get penalties to your SEO and that you have default layouts in previews and search results. So, once we want to make these pages public, we need to make sure the html header has the following tags:
- title
- description
- keywords
- author (that's less for SEO but nice for us)
If we want to optimize any of the pages for social, we should also add open graph tags:
- og:title .... - The title of your object as it should appear within the graph
- og:description
- og:image - An image URL which should represent your object within the graph.
- og:url - The canonical URL of your object that will be used as its permanent ID in the graph, e.g., "https://www.imdb.com/title/tt0117500/".
(from https://ogp.me/ & https://ahrefs.com/blog/open-graph-meta-tags/)
@ryanfchase - there's an underlying dev/product decision that's needed... here is the metadata we have on the app and the two pages we're updating at the moment:
<head>
--
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="canonical" href="https://www.311-data.org/">
I assume we can have different meta tags on each page. But is there already a place where we capture this content? It would be nice if your content and marketing folks could access this directly in the cms, either as individual fields or in a json file maybe. But let me know what makes the most sense to product and devs.
I took a quick look at our content model for 'simple page', and it looks more like a blog post than a page. Do you think we could add a json field to this page model that will let us enter the metadata? Or would you rather handle it a different way?
Also, it very much looks to me like there isn't a wrapper for either the FAQ or contact pages in contentful - just FAQ items, and that only the introduction to the About page exists in the CMS. Can you let me know if that is what you see as well?
content listing, with my comments in light blue:
Just to note here what I've already mentioned elsewhere:
- let's investigate the url structure we're using and figure out if we want different pages indexable. It currently looks like pages are denoted by a hash - but that once we introduce search terms for the map, the hash is pushed out to the end of the url. As a result, we do not have persistent urls for the different pages on the 311 data site.
So, the new question is: do we want our other pages to be findable using search or is the product strategy to look like a one page site and always direct people to the map first?
As an addendum: the map does not have a lot of explanatory text and won't place super highly in search results by itself.