Building Five-Minute-Brief - Part 3: Static Popup UI
Welcome back everyone! Today the coding start (though nothing close to challenging yet). Today, we will implement the barebone static UI page, just React components with absolutely no styling yet. Let’s get started!
1. The Design
Sorry, I lied. As much as I wanted to jump straight into coding, we need an actual design. However, Five Minute Brief is supposed to be a minimalist popup, it should be pretty straightforward. As much as I hate to say it, AI was pretty helpful for this part since I was too lazy and unskilled to come up with a good UI myself. I used Canva AI to generate a “minimalist Chrome extension news digest accordion-style” for me and to my surprise, one prompt was good enough. The product is definitely nowhere near good enough for a professional setting, but for a personal one-person project, this is plenty for me (it also forgot to generate the headlines). The generated UI looks like this:
I also hopped on Color hunt to find a nice color palette just in case I want to play around more with the UI and decided on this one:
Now, we can hop into the coding part!
2. Mock Data
First item of the day is to have some mock data that we can use for the popup. I used an LLM for this since it is pretty handy and looks more realistic. Totally could have lorem ipsum-ed this, but I already paid for ChatGPT plus so might as well.
The mock data looks like this:
// FAKE MOCK DATA
export const mockBrief = {
date: 'August 7, 2026',
publishedAt: '8:00 a.m. ET',
stories: [
{
id: 'story-1',
headline: 'Global markets steady as investors assess new economic data',
summary:
'Global markets closed largely unchanged after fresh data pointed to cooling price pressures alongside uneven growth. Investors are now watching closely for the next signal from central banks.',
sources: [
{
publisher: 'Reuters',
url: 'https://www.reuters.com',
},
{
publisher: 'BBC',
url: 'https://www.bbc.com',
},
{
publisher: 'AP',
url: 'https://apnews.com',
},
],
},
{
id: 'story-2',
headline: 'Major international leaders meet for new round of talks',
summary:
'Officials met Thursday for negotiations focused on several unresolved international disputes.',
sources: [
{
publisher: 'Reuters',
url: 'https://www.reuters.com',
},
{
publisher: 'AP',
url: 'https://apnews.com',
},
],
},
...
],
}
Note: I have not decided on the specific shape for the data yet. I will leave this a problem for when I work on the news generation part. For now, we will just roll with this.
3. Header
The first component to create is the Header component, which would be this part:
This should be pretty straightforward:
const Header = ({ date, storyCount }) => (
<header>
<p>The essential read</p>
<h1>Five Minute Brief</h1>
<p>
{date}
<i className="bi bi-dot"></i>
{storyCount} stories
</p>
</header>
)
export default Header;
TIP
I will be using Bootstrap icons for this project as you can see above
4. News summarization
Next, the ‘body’ where the news headlines and summaries lie. That would be this part:
Here, you will see that I decided to handle the accordion list with React rather than Bootstrap. This is because I think this will be handier when we need to save which headlines are open. I might go with Bootstrap if this causes too many headaches for styling though.
For this part, we will need to implement 2 components: StoryList and StoryItem. StoryItem is the component for each piece of news, which means StoryList will just be a section with a bunch of StoryItems. We will start with StoryItem:
import { useState } from "react";
const StoryItem = ({
headline,
summary,
sources,
defaultOpen=false
}) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
const handleToggle = () => {
setIsOpen(!isOpen);
}
return (
<article>
{/* ----- Headline ----- */}
<button
onClick={handleToggle}
>
<span>{headline}</span>
<span>
{isOpen
? <i className="bi bi-chevron-up"></i>
: <i className="bi bi-chevron-down"></i>
}
</span>
</button>
{/* ----- Content ----- */}
{isOpen && (
<div>
<p>{summary}</p>
{sources.map(source => (
<a
key={source.publisher}
href={source.url}
target="_blank"
>
{source.publisher}|
</a>
))}
</div>
)}
</article>
)
}
export default StoryItem;
Next is StoryList, which should be significantly more straightforward:
import StoryItem from "./StoryItem";
const StoryList = ({ stories }) => {
return (
<section>
{stories.map((story, index) => (
<StoryItem
key={story.id}
headline={story.headline}
summary={story.summary}
sources={story.sources}
defaultOpen={index === 0}
/>
))}
</section>
)
}
export default StoryList;
Note: Right now I am using
story.idas the key, but I am thinking about changing it to something more dynamic like using the story index instead. This is really easy to do so we’ll worry about it later.
5. Footer
Last but not least, the footer:
There’s not much to talk about for this one, it’s just three lines of words:
const Footer = ({ publishedAt }) => {
return (
<footer>
<h2>That's the brief for today.</h2>
<p>Published at {publishedAt}</p>
<p>AI-generated headlines and summaries may contain error.</p>
</footer>
)
}
export default Footer;
6. App
And now, putting them all together:
import Header from "./components/Header";
import StoryList from "./components/StoryList";
import Footer from "./components/Footer";
import { mockBrief } from "./data/mockBrief";
const App = () => {
return (
<>
<Header
date={mockBrief.date}
storyCount={mockBrief.stories.length}
/>
<StoryList
stories={mockBrief.stories}
/>
<Footer publishedAt={mockBrief.publishedAt}/>
</>
)
}
export default App;
And now, the extension looks like this:
Looks rough, I know, but that’s why we’re going to beautify this bad boy next up!
Conclusion
That should be plenty for today. Next time, we will get started on styling and have it look like an actual product, which would be the part I dreaded the most. See you guys soon!
Enjoy Reading This Article?
Here are some more articles you might like to read next:
- Building CommitGate - Part 3: Git Hooks Installation
- Building Five-Minute-Brief - Part 1: Planning
- Building CommitGate - Part 2: Workflow & Repository Setup
- Building Five-Minute-Brief - Part 2: Initialize Chrome extension project
- Building CommitGate - Part 4: Gitleaks Integration & Git Hook Install Fix
- Predicting NBA Player Salary Using Linear Regression
- NBA Playoff Qualification Prediction Using Logistic Regression
- Building CommitGate - Part 5: CLI Integration
- Predict NBA Position Using K-nearest Neighbor
- Predict NBA Position Using K-means Clustering
- Building CommitGate - Part 1: Planning & Architecture