Skip to content

Commit 6b70152

Browse files
authored
MAPLE In The News Page (#2102)
* get basic page and translations * css changes to nav groups * add in main components * add in badges and tweak styling * remove placeholder testing news items * add loading state * prettier * remove unused import and add read more button to translation file * add sorting to news items * formatting
1 parent 797099d commit 6b70152

10 files changed

Lines changed: 364 additions & 2 deletions

File tree

components/InTheNews/InTheNews.tsx

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { useState } from "react"
2+
import { Col, Row, Container, Badge, Spinner } from "../bootstrap"
3+
import Tab from "react-bootstrap/Tab"
4+
import Nav from "react-bootstrap/Nav"
5+
import Dropdown from "react-bootstrap/Dropdown"
6+
import { useMediaQuery } from "usehooks-ts"
7+
import { useTranslation } from "next-i18next"
8+
import { NewsCard } from "./NewsCard"
9+
import { NewsType, NewsItem, useNews } from "components/db/news"
10+
11+
type NewsFeedProps = {
12+
type: NewsType
13+
newsItems: NewsItem[]
14+
}
15+
16+
type TabCounts = {
17+
media: number
18+
awards: number
19+
books: number
20+
}
21+
22+
const NewsFeed = ({ type, newsItems }: NewsFeedProps) => {
23+
return (
24+
<div className="d-flex flex-column align-items-left gap-1 w-100">
25+
{newsItems
26+
.filter(item => item.type === type)
27+
.map((item, index) => (
28+
<NewsCard key={index} newsItem={item} />
29+
))}
30+
</div>
31+
)
32+
}
33+
34+
export const InTheNews = () => {
35+
const { t } = useTranslation("inTheNews")
36+
const isMobile = useMediaQuery("(max-width: 768px)")
37+
const { result: newsItems, loading } = useNews()
38+
39+
const counts: TabCounts | null = newsItems
40+
? {
41+
media: newsItems.filter(item => item.type === "article").length,
42+
awards: newsItems.filter(item => item.type === "award").length,
43+
books: newsItems.filter(item => item.type === "book").length
44+
}
45+
: null
46+
47+
return (
48+
<Container className="ptx-4 pt-5 gap-4 min-vh-100">
49+
<h1 className="fw-bold m-3" style={{ fontSize: "5rem" }}>
50+
{t("title")}
51+
</h1>
52+
<div className="d-flex flex-column bg-white rounded-4 my-5 gap-4 p-4">
53+
<Tab.Container defaultActiveKey="media">
54+
{isMobile ? (
55+
<TabDropdown counts={counts} />
56+
) : (
57+
<TabGroup counts={counts} />
58+
)}
59+
<Row className="g-0">
60+
<Col>
61+
{loading ? (
62+
<div className="d-flex justify-content-center p-5">
63+
<Spinner animation="border" role="status">
64+
<span className="visually-hidden">Loading...</span>
65+
</Spinner>
66+
</div>
67+
) : (
68+
<Tab.Content>
69+
<Tab.Pane eventKey="media">
70+
<div className="d-flex flex-column align-items-center">
71+
<NewsFeed type="article" newsItems={newsItems ?? []} />
72+
</div>
73+
</Tab.Pane>
74+
<Tab.Pane eventKey="awards">
75+
<div className="d-flex flex-column align-items-center">
76+
<NewsFeed type="award" newsItems={newsItems ?? []} />
77+
</div>
78+
</Tab.Pane>
79+
<Tab.Pane eventKey="books">
80+
<div className="d-flex flex-column align-items-center">
81+
<NewsFeed type="book" newsItems={newsItems ?? []} />
82+
</div>
83+
</Tab.Pane>
84+
</Tab.Content>
85+
)}
86+
</Col>
87+
</Row>
88+
</Tab.Container>
89+
</div>
90+
</Container>
91+
)
92+
}
93+
94+
const TabGroup = ({ counts }: { counts: TabCounts | null }) => {
95+
const { t } = useTranslation("inTheNews")
96+
return (
97+
<Row className="g-0 fs-4 fw-semibold">
98+
<Col md={4} className="text-center">
99+
<Nav className="in-the-news flex-column">
100+
<Nav.Item>
101+
<Nav.Link eventKey="media">
102+
<div className="d-flex justify-content-center align-items-center gap-3 p-4">
103+
{t("media.title")}
104+
<Badge
105+
bg="secondary"
106+
className="rounded-pill px-4 fw-bold"
107+
style={{
108+
fontSize: "20px",
109+
visibility: counts ? "visible" : "hidden"
110+
}}
111+
>
112+
{counts ? counts.media : 0}
113+
</Badge>
114+
</div>
115+
</Nav.Link>
116+
</Nav.Item>
117+
</Nav>
118+
</Col>
119+
<Col md={4} className="text-center">
120+
<Nav className="in-the-news flex-column">
121+
<Nav.Item>
122+
<Nav.Link eventKey="awards">
123+
<div className="d-flex justify-content-center align-items-center gap-3 p-4">
124+
{t("awards.title")}
125+
<Badge
126+
bg="secondary"
127+
className="rounded-pill px-4 fw-bold"
128+
style={{
129+
fontSize: "20px",
130+
visibility: counts ? "visible" : "hidden"
131+
}}
132+
>
133+
{counts ? counts.awards : 0}
134+
</Badge>
135+
</div>
136+
</Nav.Link>
137+
</Nav.Item>
138+
</Nav>
139+
</Col>
140+
<Col md={4} className="text-center">
141+
<Nav className="in-the-news flex-column">
142+
<Nav.Item>
143+
<Nav.Link eventKey="books">
144+
<div className="d-flex justify-content-center align-items-center gap-3 p-4">
145+
{t("books.title")}
146+
<Badge
147+
bg="secondary"
148+
className="rounded-pill px-4 fw-bold"
149+
style={{
150+
fontSize: "20px",
151+
visibility: counts ? "visible" : "hidden"
152+
}}
153+
>
154+
{counts ? counts.books : 0}
155+
</Badge>
156+
</div>
157+
</Nav.Link>
158+
</Nav.Item>
159+
</Nav>
160+
</Col>
161+
</Row>
162+
)
163+
}
164+
165+
const TabDropdown = ({ counts }: { counts: TabCounts | null }) => {
166+
const { t } = useTranslation("inTheNews")
167+
const [selectedTab, setSelectedTab] = useState<string>("Media")
168+
169+
const handleTabClick = (tabTitle: string) => {
170+
setSelectedTab(tabTitle)
171+
}
172+
173+
return (
174+
<Row className="p-3 g-0">
175+
<Col md={12}>
176+
<Dropdown className="our-team-dropdown">
177+
<Dropdown.Toggle className="our-team-dropdown-button">
178+
<span style={{ float: "left" }}>{selectedTab}</span>
179+
</Dropdown.Toggle>
180+
<Dropdown.Menu className="p-2">
181+
<Dropdown.Item
182+
className="p-2"
183+
eventKey="media"
184+
onClick={() => handleTabClick("Media")}
185+
>
186+
{t("media.title")}
187+
</Dropdown.Item>
188+
<Dropdown.Divider />
189+
<Dropdown.Item
190+
className="p-2"
191+
eventKey="awards"
192+
onClick={() => handleTabClick("Awards")}
193+
>
194+
{t("awards.title")}
195+
</Dropdown.Item>
196+
<Dropdown.Divider />
197+
<Dropdown.Item
198+
className="p-2"
199+
eventKey="books"
200+
onClick={() => handleTabClick("Books")}
201+
>
202+
{t("books.title")}
203+
</Dropdown.Item>
204+
</Dropdown.Menu>
205+
</Dropdown>
206+
</Col>
207+
</Row>
208+
)
209+
}

components/InTheNews/NewsCard.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import ArrowForward from "@mui/icons-material/ArrowForward"
2+
import { useTranslation } from "next-i18next"
3+
import { NewsItem } from "components/db"
4+
5+
type NewsCardProps = {
6+
newsItem: NewsItem
7+
}
8+
9+
export const NewsCard = ({ newsItem }: NewsCardProps) => {
10+
const { t } = useTranslation("inTheNews")
11+
return (
12+
<div
13+
className="d-flex flex-row flex-fill gap-5 p-4"
14+
style={{
15+
backgroundColor: "#F6F7FF",
16+
borderTop: "6px solid var(--bs-secondary)"
17+
}}
18+
>
19+
<div
20+
className="d-flex flex-fill flex-column gap-2"
21+
style={{ minWidth: 0 }}
22+
>
23+
<div className="d-flex flex-row align-items-center gap-4 mb-1">
24+
<p className="m-0 fw-medium">
25+
{new Date(newsItem.publishDate).toLocaleDateString("en-US", {
26+
year: "numeric",
27+
month: "long",
28+
day: "numeric",
29+
timeZone: "UTC"
30+
})}
31+
</p>
32+
<div
33+
style={{
34+
width: "1px",
35+
height: "1em",
36+
backgroundColor: "black",
37+
alignSelf: "center"
38+
}}
39+
/>
40+
<p className="m-0">{newsItem.author}</p>
41+
</div>
42+
<div className="">
43+
<a
44+
className="fw-bold fs-4 lh-1 tracking-tight text-decoration-underline"
45+
href={newsItem.url}
46+
target="_blank"
47+
rel="noopener noreferrer"
48+
>
49+
{newsItem.title}
50+
</a>
51+
</div>
52+
<div className="text-truncate">{newsItem.description}</div>
53+
</div>
54+
<div className="align-self-end flex-shrink-0">
55+
<a
56+
className="d-flex gap-1 text-decoration-none hover-underline"
57+
href={newsItem.url}
58+
target="_blank"
59+
rel="noopener noreferrer"
60+
style={{ color: "#1587D3", fontWeight: 800 }}
61+
>
62+
{t("readMoreButton")}
63+
<ArrowForward />
64+
</a>
65+
</div>
66+
</div>
67+
)
68+
}

components/Navbar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
NavbarLinkEffective,
1717
NavbarLinkFAQ,
1818
NavbarLinkGoals,
19+
NavbarLinkInTheNews,
1920
NavbarLinkLogo,
2021
NavbarLinkNewsfeed,
2122
NavbarLinkProcess,
@@ -92,6 +93,7 @@ const MobileNav: React.FC<React.PropsWithChildren<unknown>> = () => {
9293
<NavbarLinkSupport handleClick={closeNav} />
9394
<NavbarLinkFAQ handleClick={closeNav} />
9495
<NavbarLinkAI handleClick={closeNav} />
96+
<NavbarLinkInTheNews handleClick={closeNav} />
9597
</NavDropdown>
9698

9799
<NavDropdown className={"navLink-primary"} title={t("learn")}>
@@ -228,6 +230,7 @@ const DesktopNav: React.FC<React.PropsWithChildren<unknown>> = () => {
228230
<NavbarLinkSupport />
229231
<NavbarLinkFAQ />
230232
<NavbarLinkAI />
233+
<NavbarLinkInTheNews />
231234
</Dropdown.Menu>
232235
</Dropdown>
233236
</div>

components/NavbarComponents.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,24 @@ export const NavbarLinkWhyUse: React.FC<
378378
</NavDropdown.Item>
379379
)
380380
}
381+
382+
export const NavbarLinkInTheNews: React.FC<
383+
React.PropsWithChildren<{
384+
handleClick?: any
385+
other?: any
386+
}>
387+
> = ({ handleClick, other }) => {
388+
const isMobile = useMediaQuery("(max-width: 768px)")
389+
const { t } = useTranslation(["common", "auth"])
390+
return (
391+
<NavDropdown.Item onClick={handleClick}>
392+
<NavLink
393+
className={isMobile ? "navLink-primary" : ""}
394+
href="/about/in-the-news"
395+
{...other}
396+
>
397+
{t("navigation.inTheNews")}
398+
</NavLink>
399+
</NavDropdown.Item>
400+
)
401+
}

components/db/news.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { collection, getDocs, orderBy, Timestamp } from "firebase/firestore"
1+
import {
2+
collection,
3+
getDocs,
4+
orderBy,
5+
query,
6+
Timestamp
7+
} from "firebase/firestore"
28
import { useAsync } from "react-async-hook"
39
import { firestore } from "../firebase"
410

@@ -17,7 +23,8 @@ export type NewsItem = {
1723

1824
export async function listNews(): Promise<NewsItem[]> {
1925
const newsRef = collection(firestore, "news")
20-
const result = await getDocs(newsRef)
26+
const q = query(newsRef, orderBy("publishDate", "desc"))
27+
const result = await getDocs(q)
2128
return result.docs.map(d => ({ id: d.id, ...d.data() } as NewsItem))
2229
}
2330

pages/about/in-the-news.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createPage } from "../../components/page"
2+
import { InTheNews } from "../../components/InTheNews/InTheNews"
3+
import { createGetStaticTranslationProps } from "components/translations"
4+
5+
export default createPage({
6+
titleI18nKey: "titles.in_the_news",
7+
Page: () => {
8+
return <InTheNews />
9+
}
10+
})
11+
12+
export const getStaticProps = createGetStaticTranslationProps([
13+
"auth",
14+
"common",
15+
"footer",
16+
"inTheNews"
17+
])

public/locales/en/common.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
"aboutTestimony": "About Testimony",
129129
"viewProfile": "View Profile",
130130
"whyUseMaple": "Why Use MAPLE",
131+
"inTheNews": "In The News",
131132
"login": "Login"
132133
},
133134
"new_feature": "*NEW*",
@@ -180,6 +181,7 @@
180181
"legislative_process": "How To Have Impact Through Legislative Testimony",
181182
"submit_testimony": "Submit Testimony",
182183
"support_maple": "How to Support MAPLE",
184+
"in_the_news": "In The News",
183185
"testimony": "Testimony",
184186
"policies": "Policies",
185187
"unsubscribe": "Unsubscribe",

public/locales/en/inTheNews.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"title": "Media, Articles & Insights",
3+
"readMoreButton": "READ MORE",
4+
"media": {
5+
"title": "Media"
6+
},
7+
"awards": {
8+
"title": "Awards"
9+
},
10+
"books": {
11+
"title": "Books"
12+
}
13+
}

0 commit comments

Comments
 (0)