diff --git a/components/db/profile/types.ts b/components/db/profile/types.ts index 898b84f2e..8f9106a7a 100644 --- a/components/db/profile/types.ts +++ b/components/db/profile/types.ts @@ -44,4 +44,6 @@ export type Profile = { location?: string orgCategories?: OrgCategory[] | "" phoneVerified?: boolean + memberId?: string + website?: string } diff --git a/components/legislator/LegislatorComponents.tsx b/components/legislator/LegislatorComponents.tsx new file mode 100644 index 000000000..92f6496b8 --- /dev/null +++ b/components/legislator/LegislatorComponents.tsx @@ -0,0 +1,115 @@ +import { useTranslation } from "next-i18next" +import styled from "styled-components" + +export const formatPhoneNumber = (value: string) => { + if (!value) return value + + const phoneNumber = value.replace(/[^\d]/g, "") + const phoneNumberLength = phoneNumber.length + + // Format as (XXX) XXX-XXXX + if (phoneNumberLength < 4) return phoneNumber + if (phoneNumberLength < 7) { + return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3)}` + } + return ` + (${phoneNumber.slice(0, 3)}) + ${phoneNumber.slice(3, 6)}- + ${phoneNumber.slice(6, 10)} + ` +} + +/** Party Labels **/ + +const DemocraticBubble = styled.div.attrs(props => ({ + className: `${props.className}` +}))` + background: #d1d6e7; + color: #1a3185; + font-size: 11px; + font-weight: 700; + padding: 1px 10px; + border-radius: 999px; + width: max-content; +` + +const IndependantBubble = styled.div.attrs(props => ({ + className: `${props.className}` +}))` + background: #fff3cd; + color: #856404; + font-size: 11px; + font-weight: 700; + padding: 1px 10px; + border-radius: 999px; + width: max-content; +` + +const RepublicanBubble = styled.div.attrs(props => ({ + className: `${props.className}` +}))` + background: #f29999; + color: #de0100; + font-size: 11px; + font-weight: 700; + padding: 1px 10px; + border-radius: 999px; + width: max-content; +` + +export function PartyLabel(props: { party: string }) { + const { t } = useTranslation("legislators") + + switch (props.party) { + case "Democrat": + return {t("party.democratic")} + case "Republican": + return {t("party.republican")} + default: + return ( + + {props.party} {t("party.party")} + + ) + } +} + +/** Social Media components **/ + +export function Bluesky() { + return ( + + + + ) +} + +export function LinkedIn() { + return ( + + + + ) +} + +export function Twitter() { + return ( + + + + ) +} diff --git a/components/legislator/LegislatorPage.tsx b/components/legislator/LegislatorPage.tsx index 35f2b5251..b45851830 100644 --- a/components/legislator/LegislatorPage.tsx +++ b/components/legislator/LegislatorPage.tsx @@ -1,17 +1,29 @@ +import { doc, getDoc } from "firebase/firestore" 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 { useCallback, useEffect, useState } from "react" import styled from "styled-components" import { Col, Container, Row, Spinner } from "../bootstrap" import { usePublicProfile } from "../db" +import { firestore } from "../firebase" +import * as links from "../links" +import { + Bluesky, + formatPhoneNumber, + LinkedIn, + PartyLabel, + Twitter +} from "./LegislatorComponents" import { LegislatorSidebar } from "./SidebarComponents/LegislatorSidebar" import { LegislatorTabs } from "./TabComponents/LegislatorTabs" import { useFlags } from "components/featureFlags" import { Internal } from "components/links" +import { CircleImage } from "components/shared/LabeledIcon" const DirectoryPath = styled.div.attrs(props => ({ className: `align-items-center d-flex flex-nowrap ${props.className}` @@ -27,6 +39,30 @@ const HeaderBlock = styled.div` padding: 16px; ` +const HeaderName = styled.div` + font-size: 26px; + font-weight: 700; + color: #0b0a3e; +` + +const RoleLine = styled.div.attrs(props => ({ + className: `mb-2 ${props.className}` +}))` + color: #6c757d; + font-size: 14px; +` + +const PhoneNum = styled.span` + color: #6c757d; +` + +const SocialLine = styled.div.attrs(props => ({ + className: `d-flex flex-wrap ${props.className}` +}))` + font-size: 12px; + text-decoration: none; +` + const StatBlock = styled(Col).attrs(props => ({ className: `d-flex col-4 flex-grow-1 ${props.className}`, md: `2` @@ -58,7 +94,39 @@ export function LegislatorPage(props: { id: string }) { const { result: profile, loading } = usePublicProfile(props.id) const { legislators } = useFlags() - console.log("Pro: ", profile) + // eventually this should be replaced with a profile prop array that + // contains a list of courts the legislator served on + const viableCourts = "194" + + const [branch, setBranch] = useState("") + const [cosponsoredBills, setCosponsoredBills] = useState>([""]) + const [district, setDistrict] = useState("") + const [email, setEmail] = useState("") + const [party, setParty] = useState("") + const [phoneNumber, setPhoneNumber] = useState("") + const [sponsoredBills, setSponsoredBills] = useState>([""]) + + const memberData = useCallback(async () => { + const member = await getDoc( + doc( + firestore, + `generalCourts/${viableCourts}/members/${profile?.memberId}` + ) + ) + const docData = member.data() + + setBranch(docData?.content.Branch) + setCosponsoredBills(docData?.content.CoSponsoredBills) + setDistrict(docData?.content.District) + setEmail(docData?.content.EmailAddress) + setParty(docData?.content.Party) + setPhoneNumber(docData?.content.PhoneNumber) + setSponsoredBills(docData?.content.SponsoredBills) + }, [district, party, phoneNumber]) + + useEffect(() => { + profile ? memberData() : null + }, [memberData, profile]) if (loading) { return ( @@ -75,7 +143,7 @@ export function LegislatorPage(props: { id: string }) { } return ( - + {t("home")} @@ -86,7 +154,132 @@ export function LegislatorPage(props: { id: string }) {
{profile.fullName}
- Header Info Goes Here + + + {""} + + + + + {profile.fullName} + + + + + {branch == "Senate" ? ( + {t("stateSenator")} + ) : ( + {t("stateRepresentative")} + )} + · + {district} + + +
+ + {/* Incumbent Label */} + {/* District Label */} +
+ + +
+ + {email} + +
+ + {profile.website ? ( +
+ · + + {profile.website} + +
+ ) : ( +
+ · + + {`malegislature.gov/Legislators/Profile/${profile.memberId}`} + +
+ )} + + {phoneNumber ? ( +
+ · + {formatPhoneNumber(phoneNumber)} +
+ ) : ( + <> + )} + +
+ {profile?.social?.twitter || + profile?.social?.linkedIn || + profile?.social?.blueSky ? ( + · + ) : ( + <> + )} + + {profile?.social?.twitter ? ( + + + + ) : ( + <> + )} + {profile?.social?.linkedIn ? ( + + + + ) : ( + <> + )} + {profile?.social?.blueSky ? ( + + + + ) : ( + <> + )} +
+
+ + +
Buttons
+ +
@@ -97,13 +290,21 @@ export function LegislatorPage(props: { id: string }) { - ? + + {sponsoredBills?.length ? <>{sponsoredBills.length} : <>?} + {t("billsSponsored")} - ? + + {cosponsoredBills?.length ? ( + <>{cosponsoredBills.length} + ) : ( + <>? + )} + {t("cosponsored")} diff --git a/public/locales/en/legislators.json b/public/locales/en/legislators.json index 4974cfc9d..c3046b8c0 100644 --- a/public/locales/en/legislators.json +++ b/public/locales/en/legislators.json @@ -4,6 +4,13 @@ "fundsRaised": "Funds Raised", "home": "Home", "legislators": "Legislators", + "party": { + "democratic": "Democratic Party", + "party": "Party", + "republican": "Republican Party" + }, + "stateRepresentative": "State Representative", + "stateSenator": "State Senator", "tabs": { "bills": "Bills", "district": "District",