diff --git a/components/featureFlags.ts b/components/featureFlags.ts index 459d5efee..04214dc18 100644 --- a/components/featureFlags.ts +++ b/components/featureFlags.ts @@ -19,7 +19,9 @@ export const FeatureFlags = z.object({ /** Phone Verification UI changes **/ phoneVerificationUI: z.boolean().default(false), /** Ballot Questions feature */ - ballotQuestions: z.boolean().default(false) + ballotQuestions: z.boolean().default(false), + /** Legislators Page feature **/ + legislators: z.boolean().default(false) }) export type FeatureFlags = z.infer @@ -41,7 +43,8 @@ const defaults: Record = { showLLMFeatures: true, hearingsAndTranscriptions: true, phoneVerificationUI: true, - ballotQuestions: true + ballotQuestions: true, + legislators: true }, production: { testimonyDiffing: false, @@ -52,7 +55,8 @@ const defaults: Record = { showLLMFeatures: true, hearingsAndTranscriptions: true, phoneVerificationUI: false, - ballotQuestions: false + ballotQuestions: false, + legislators: false }, test: { testimonyDiffing: false, @@ -63,7 +67,8 @@ const defaults: Record = { showLLMFeatures: true, hearingsAndTranscriptions: true, phoneVerificationUI: true, - ballotQuestions: false + ballotQuestions: false, + legislators: false } } diff --git a/components/legislator/LegislatorPage.tsx b/components/legislator/LegislatorPage.tsx new file mode 100644 index 000000000..35f2b5251 --- /dev/null +++ b/components/legislator/LegislatorPage.tsx @@ -0,0 +1,128 @@ +import { faChevronRight } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { useTranslation } from "next-i18next" +import ErrorPage from "next/error" +import styled from "styled-components" + +import { Col, Container, Row, Spinner } from "../bootstrap" +import { usePublicProfile } from "../db" + +import { LegislatorSidebar } from "./SidebarComponents/LegislatorSidebar" +import { LegislatorTabs } from "./TabComponents/LegislatorTabs" + +import { useFlags } from "components/featureFlags" +import { Internal } from "components/links" + +const DirectoryPath = styled.div.attrs(props => ({ + className: `align-items-center d-flex flex-nowrap ${props.className}` +}))` + font-size: 12px; +` + +const HeaderBlock = styled.div` + background-color: white; + border: "1px #ced4da solid"; + border-radius: 5px; + margin-top: 8px; + padding: 16px; +` + +const StatBlock = styled(Col).attrs(props => ({ + className: `d-flex col-4 flex-grow-1 ${props.className}`, + md: `2` +}))` + background-color: white; + border: 1px #ced4da solid; + border-radius: 5px; + margin-top: 4px; + padding: 16px; +` + +const StatLine = styled(Row).attrs(props => ({ + className: `text-nowrap ${props.className}` +}))` + font-size: 12px; +` + +const StatNum = styled.div.attrs(props => ({ + className: `mx-auto ${props.className}` +}))` + color: #1a3185; + font-size: 22px; + font-weight: 700; + width: max-content; +` + +export function LegislatorPage(props: { id: string }) { + const { t } = useTranslation("legislators") + const { result: profile, loading } = usePublicProfile(props.id) + const { legislators } = useFlags() + + console.log("Pro: ", profile) + + if (loading) { + return ( + + + + ) + } + if (!legislators) { + return + } + if (!profile) { + return + } + + return ( + + + + {t("home")} + + +
{t("legislators")}
+ +
{profile.fullName}
+
+ + Header Info Goes Here + +
+ + + ? + {t("termsServed")} + + + + + ? + {t("billsSponsored")} + + + + + ? + {t("cosponsored")} + + + + + ? + {t("fundsRaised")} + + +
+ + + + + + + + + +
+ ) +} diff --git a/components/legislator/SidebarComponents/Biography.tsx b/components/legislator/SidebarComponents/Biography.tsx new file mode 100644 index 000000000..90cf3f5e0 --- /dev/null +++ b/components/legislator/SidebarComponents/Biography.tsx @@ -0,0 +1,3 @@ +export function Biography() { + return
- Biography
+} diff --git a/components/legislator/SidebarComponents/LegislatorSidebar.tsx b/components/legislator/SidebarComponents/LegislatorSidebar.tsx new file mode 100644 index 000000000..39f77168e --- /dev/null +++ b/components/legislator/SidebarComponents/LegislatorSidebar.tsx @@ -0,0 +1,14 @@ +import { Biography } from "./Biography" +import { OtherTestimony } from "./OtherTestimony" +import { UpcomingHearings } from "./UpcomingHearings" + +export function LegislatorSidebar() { + return ( + <> + Sidebar Components + + + + + ) +} diff --git a/components/legislator/SidebarComponents/OtherTestimony.tsx b/components/legislator/SidebarComponents/OtherTestimony.tsx new file mode 100644 index 000000000..8b3940014 --- /dev/null +++ b/components/legislator/SidebarComponents/OtherTestimony.tsx @@ -0,0 +1,3 @@ +export function OtherTestimony() { + return
- Other Testimony
+} diff --git a/components/legislator/SidebarComponents/UpcomingHearings.tsx b/components/legislator/SidebarComponents/UpcomingHearings.tsx new file mode 100644 index 000000000..69e68cb05 --- /dev/null +++ b/components/legislator/SidebarComponents/UpcomingHearings.tsx @@ -0,0 +1,3 @@ +export function UpcomingHearings() { + return
- Upcoming Hearings
+} diff --git a/components/legislator/TabComponents/Bills.tsx b/components/legislator/TabComponents/Bills.tsx new file mode 100644 index 000000000..61d6b5918 --- /dev/null +++ b/components/legislator/TabComponents/Bills.tsx @@ -0,0 +1,3 @@ +export function Bills() { + return
- Bills
+} diff --git a/components/legislator/TabComponents/District.tsx b/components/legislator/TabComponents/District.tsx new file mode 100644 index 000000000..06025fbff --- /dev/null +++ b/components/legislator/TabComponents/District.tsx @@ -0,0 +1,3 @@ +export function District() { + return
- District
+} diff --git a/components/legislator/TabComponents/Elections.tsx b/components/legislator/TabComponents/Elections.tsx new file mode 100644 index 000000000..c20356658 --- /dev/null +++ b/components/legislator/TabComponents/Elections.tsx @@ -0,0 +1,3 @@ +export function Elections() { + return
- Elections
+} diff --git a/components/legislator/TabComponents/Finance.tsx b/components/legislator/TabComponents/Finance.tsx new file mode 100644 index 000000000..f1431f05f --- /dev/null +++ b/components/legislator/TabComponents/Finance.tsx @@ -0,0 +1,3 @@ +export function Finance() { + return
- Finance
+} diff --git a/components/legislator/TabComponents/LegislatorTabs.tsx b/components/legislator/TabComponents/LegislatorTabs.tsx new file mode 100644 index 000000000..34cc14260 --- /dev/null +++ b/components/legislator/TabComponents/LegislatorTabs.tsx @@ -0,0 +1,128 @@ +import { useTranslation } from "next-i18next" +import { TabPane } from "react-bootstrap" +import TabContainer from "react-bootstrap/TabContainer" +import styled from "styled-components" + +import { Container, Nav } from "../../bootstrap" + +import { Bills } from "./Bills" +import { District } from "./District" +import { Elections } from "./Elections" +import { Finance } from "./Finance" +import { Priorities } from "./Priorities" +import { Testimony } from "./Testimony" +import { Votes } from "./Votes" + +import { + StyledTabContent, + TabNavWrapper, + TabType +} from "components/EditProfilePage/StyledEditProfileComponents" + +const tabCategory = [ + "priorities", + "bills", + "elections", + "finance", + "district", + "testimony", + "votes" +] +type TabCategories = (typeof tabCategory)[number] + +const TabNavLink = styled(Nav.Link).attrs(props => ({ + className: `rounded-top m-0 p-0 ${props.className}` +}))` + color: #6c757d; + + &.active { + color: #1a3185; + font-weight: bold; + } +` + +const TabNavItem = ({ + tab, + i: i, + className +}: { + tab: TabType + i: number + className?: string +}) => { + return ( + + +

+ {tab.title} +

+
+
+
+ ) +} + +export function LegislatorTabs({ + tabCategory +}: { + tabCategory?: TabCategories +}) { + const { t } = useTranslation("legislators") + + const tabs = [ + { + title: t("tabs.priorities"), + eventKey: "priorities", + content: + }, + { + title: t("tabs.bills"), + eventKey: "bills", + content: + }, + { + title: t("tabs.elections"), + eventKey: "elections", + content: + }, + { + title: t("tabs.finance"), + eventKey: "finance", + content: + }, + { + title: t("tabs.district"), + eventKey: "district", + content: + }, + { + title: t("tabs.testimony"), + eventKey: "testimony", + content: + }, + { + title: t("tabs.votes"), + eventKey: "votes", + content: + } + ] + + return ( + + + + {tabs.map((t, i) => ( + + ))} + + + {tabs.map(t => ( + + {t.content} + + ))} + + + + ) +} diff --git a/components/legislator/TabComponents/Priorities.tsx b/components/legislator/TabComponents/Priorities.tsx new file mode 100644 index 000000000..534f54540 --- /dev/null +++ b/components/legislator/TabComponents/Priorities.tsx @@ -0,0 +1,3 @@ +export function Priorities() { + return
- Priorities
+} diff --git a/components/legislator/TabComponents/Testimony.tsx b/components/legislator/TabComponents/Testimony.tsx new file mode 100644 index 000000000..d69dde353 --- /dev/null +++ b/components/legislator/TabComponents/Testimony.tsx @@ -0,0 +1,3 @@ +export function Testimony() { + return
- Testimony
+} diff --git a/components/legislator/TabComponents/Votes.tsx b/components/legislator/TabComponents/Votes.tsx new file mode 100644 index 000000000..ecdfb5f01 --- /dev/null +++ b/components/legislator/TabComponents/Votes.tsx @@ -0,0 +1,3 @@ +export function Votes() { + return
- Votes
+} diff --git a/components/legislator/index.ts b/components/legislator/index.ts new file mode 100644 index 000000000..13355a639 --- /dev/null +++ b/components/legislator/index.ts @@ -0,0 +1 @@ +export * from "./LegislatorPage" diff --git a/pages/hearing/[hearingId].tsx b/pages/hearing/[hearingId].tsx index 84263d4e1..46431a13e 100644 --- a/pages/hearing/[hearingId].tsx +++ b/pages/hearing/[hearingId].tsx @@ -9,7 +9,7 @@ import { fetchHearingData, HearingData } from "components/hearing/hearing" const Query = z.object({ hearingId: z.coerce.number() }) export default createPage<{ hearingData: HearingData }>({ - titleI18nKey: "Hearing", + titleI18nKey: "navigation.hearing", Page: ({ hearingData }) => { return } diff --git a/pages/legislators/profile.tsx b/pages/legislators/profile.tsx new file mode 100644 index 000000000..2e3b29d76 --- /dev/null +++ b/pages/legislators/profile.tsx @@ -0,0 +1,59 @@ +import { useTranslation } from "next-i18next" +import { useEffect, useState } from "react" +import { Spinner } from "react-bootstrap" + +import { LegislatorPage } from "components/legislator" +import { createPage } from "components/page" +import { createGetStaticTranslationProps } from "components/translations" + +export default createPage<{ court: string }>({ + titleI18nKey: "navigation.legislator", + Page: () => { + const { id, loading } = useProfileRouting() + const { t } = useTranslation("profile") + + return ( +
+ {loading ? ( +
+ +
+ ) : id ? ( + + ) : ( +
{t("noLegislator")}
+ )} +
+ ) + } +}) + +const useProfileRouting = () => { + const [id, setId] = useState("od") + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (window) { + const urlParams = new URLSearchParams(window.location?.search) + const urlid = urlParams.get("id") + + if (typeof urlid === "string") { + setId(urlid) + } + if (urlid !== undefined) { + setLoading(false) + } + } + }, [id]) + + return { id, loading } +} + +export const getStaticProps = createGetStaticTranslationProps([ + "auth", + "common", + "footer", + "legislators", + "profile", + "testimony" +]) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 209b85ac4..3dda1fe62 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -119,10 +119,12 @@ "editProfile": "Edit Profile", "faq": "FAQ", "followingTab": "Followed Content", + "hearing": "Hearing", "hearings": "Hearings", "home": "Home", "learnAboutTestimony": "Learn About Testimony", "legislativeProcess": "About the MA Legislative Process", + "legislator": "Legislator", "logo": "Massachusetts Platform for Legislative Engagement home", "main": "Main navigation", "missionAndGoals": "Mission & Goals", diff --git a/public/locales/en/legislators.json b/public/locales/en/legislators.json new file mode 100644 index 000000000..4974cfc9d --- /dev/null +++ b/public/locales/en/legislators.json @@ -0,0 +1,17 @@ +{ + "billsSponsored": "Bills Sponsored", + "cosponsored": "Cosponsored", + "fundsRaised": "Funds Raised", + "home": "Home", + "legislators": "Legislators", + "tabs": { + "bills": "Bills", + "district": "District", + "elections": "Elections", + "finance": "Finance", + "priorities": "Priorities", + "testimony": "Testimony", + "votes": "Votes" + }, + "termsServed": "Terms Served" +} \ No newline at end of file diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json index 339637c00..fca924cbb 100644 --- a/public/locales/en/profile.json +++ b/public/locales/en/profile.json @@ -1,30 +1,29 @@ { "aboutMe": "About {{firstName}}", "aboutUs": "About Us", - "user": "User", - "noUser": "no user", "anonymousUser": "Anonymous User", - "joinDate": "Joined in {{date}}", "button":{ "editProfile": "Edit\u00A0Profile", - "notficationFrequencyDropdown": "open envelope with letter, toggles update frequency options", "follow" : "Follow", "followedContent": "Followed Content", "following" : "Following", + "notficationFrequencyDropdown": "open envelope with letter, toggles update frequency options", "yourTestimonies": "Your Testimonies" }, "content": { - "viewingProfile": "Currently viewing your profile", - "publicProfile": "Your profile is currently public. You can change this in \"settings\"", + "noLegislatorInfo": "No legislator information given", "privateProfile": "Your profile is currently private. You can change this in \"settings\"", - "noLegislatorInfo": "No legislator information given" - }, - "verifyAccountSection":{ - "verifyAccount": "We sent a link to your email to verify your account, but you haven't\nclicked it yet. If you don't see it, be sure to check your spam\nfolder.", - "checkEmail": "Check your email!", - "sendAnotherLink": "Send Another Link" + "publicProfile": "Your profile is currently public. You can change this in \"settings\"", + "viewingProfile": "Currently viewing your profile" }, + "joinDate": "Joined in {{date}}", "legislators": "Legislators", + "noLegislator": "No legislator", + "noUser": "no user", + "orgIcon": { + "large": "Large Organization Icon", + "small": "Small Organization Icon" + }, "position": { "endorse":"The author of the submitted testimony endorses the bill.", "neutral":"The author of the submitted testimony is neutral towards the bill.", @@ -33,12 +32,14 @@ "profileMenu": "Open/Close Profile Menu", "representative": "Representative", "senator": "Senator", - "orgIcon": { - "large": "Large Organization Icon", - "small": "Small Organization Icon" - }, + "user": "User", "userIcon": { "large": "Large User Icon", "small": "Small User Icon" + }, + "verifyAccountSection": { + "checkEmail": "Check your email!", + "sendAnotherLink": "Send Another Link", + "verifyAccount": "We sent a link to your email to verify your account, but you haven't\nclicked it yet. If you don't see it, be sure to check your spam\nfolder." } }