Skip to content
Draft
1 change: 1 addition & 0 deletions components/db/profile/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ export type Profile = {
location?: string
orgCategories?: OrgCategory[] | ""
phoneVerified?: boolean
memberId?: string
}
13 changes: 9 additions & 4 deletions components/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof FeatureFlags>
Expand All @@ -41,7 +43,8 @@ const defaults: Record<Env, FeatureFlags> = {
showLLMFeatures: true,
hearingsAndTranscriptions: true,
phoneVerificationUI: true,
ballotQuestions: true
ballotQuestions: true,
legislators: true
},
production: {
testimonyDiffing: false,
Expand All @@ -52,7 +55,8 @@ const defaults: Record<Env, FeatureFlags> = {
showLLMFeatures: true,
hearingsAndTranscriptions: true,
phoneVerificationUI: false,
ballotQuestions: false
ballotQuestions: false,
legislators: false
},
test: {
testimonyDiffing: false,
Expand All @@ -63,7 +67,8 @@ const defaults: Record<Env, FeatureFlags> = {
showLLMFeatures: true,
hearingsAndTranscriptions: true,
phoneVerificationUI: true,
ballotQuestions: false
ballotQuestions: false,
legislators: false
}
}

Expand Down
37 changes: 37 additions & 0 deletions components/legislator/LegislatorComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export function Bluesky() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
className="bi bi-bluesky"
viewBox="0 0 16 16"
>
<path d="M3.468 1.948C5.303 3.325 7.276 6.118 8 7.616c.725-1.498 2.698-4.29 4.532-5.668C13.855.955 16 .186 16 2.632c0 .489-.28 4.105-.444 4.692-.572 2.04-2.653 2.561-4.504 2.246 3.236.551 4.06 2.375 2.281 4.2-3.376 3.464-4.852-.87-5.23-1.98-.07-.204-.103-.3-.103-.218 0-.081-.033.014-.102.218-.379 1.11-1.855 5.444-5.231 1.98-1.778-1.825-.955-3.65 2.28-4.2-1.85.315-3.932-.205-4.503-2.246C.28 6.737 0 3.12 0 2.632 0 .186 2.145.955 3.468 1.948" />
</svg>
)
}

export function LinkedIn() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
className="bi bi-linkedin"
viewBox="0 0 16 16"
>
<path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
</svg>
)
}

export function Twitter() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="#1a3185">
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>
</svg>
)
}
243 changes: 243 additions & 0 deletions components/legislator/LegislatorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
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, LinkedIn, 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}`
}))`
font-size: 12px;
`

const HeaderBlock = styled.div`
background-color: white;
border: "1px #ced4da solid";
border-radius: 5px;
margin-top: 8px;
padding: 16px;
`

const HeaderName = styled.div`
font-size: 26px;
font-weight: 700;
color: #0b0a3e;
`

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()

// eventually this should be replaced with a profile prop array that
// contains a list of courts the legislator served on
const viableCourts = "194"

const [district, setDistrict] = useState<string>("")
const [party, setParty] = useState<string>("")
const [phoneNumber, setPhoneNumber] = useState<string>("")

const memberData = useCallback(async () => {
const member = await getDoc(
doc(
firestore,
`generalCourts/${viableCourts}/members/${profile?.memberId}`
)
)
const docData = member.data()

setDistrict(docData?.content.District)
setParty(docData?.content.Party)
setPhoneNumber(docData?.content.PhoneNumber)
}, [district, party, phoneNumber])

useEffect(() => {
profile ? memberData() : null
}, [memberData, profile])

if (loading) {
return (
<Row>
<Spinner animation="border" className="mx-auto" />
</Row>
)
}
if (!legislators) {
return <ErrorPage statusCode={404} withDarkMode={false} />
}
if (!profile) {
return <ErrorPage statusCode={404} withDarkMode={false} />
}

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)}
`
}

console.log("Pro: ", profile)
console.log("District: ", district)
console.log("Party: ", party)
console.log("Phone #: ", phoneNumber)

return (
<Container className="my-3">
<DirectoryPath>
<Internal className="text-decoration-none" href="/">
{t("home")}
</Internal>
<FontAwesomeIcon className="fa-2xs px-2 " icon={faChevronRight} />
<div style={{ color: "#6c757d" }}>{t("legislators")}</div>
<FontAwesomeIcon className="fa-2xs px-2 " icon={faChevronRight} />
<div style={{ color: "#6c757d" }}>{profile.fullName}</div>
</DirectoryPath>

<HeaderBlock className="d-flex flex-wrap justify-content-between">
<CircleImage className="me-2">
<img
src={`https://malegislature.gov/Legislators/Profile/170/${profile.memberId}.jpg`}
alt={""}
className={`image`}
/>
</CircleImage>
<Col>
<Col className="d-flex" xs="6" sm="12">
<links.External
href={`https://malegislature.gov/Legislators/Profile/${profile.memberId}`}
className="text-decoration-none"
>
<HeaderName>{profile.fullName}</HeaderName>
</links.External>
</Col>

<div>
State ?<span className="px-2">·</span>
{district}
</div>
<div>{party}</div>
<div>
{/** fix mailto: on live **/}
<a href="mailto:#">{profile.email}</a>
<span className="px-2">·</span>
{/** need profile prop for personal webpage **/}
<a href="#" target="_blank">
janedoe.com
</a>
<span className="px-2">·</span>
<span>{formatPhoneNumber(phoneNumber)}</span>
<span className="px-2">·</span>
<a
href={profile?.social?.twitter}
className="pe-2"
title="Twitter/X"
>
<Twitter />
</a>
<a
href={profile?.social?.linkedIn}
className="pe-2"
title="linkedIn"
>
<LinkedIn />
</a>
<a href={profile?.social?.blueSky} className="pe-2" title="Bluesky">
<Bluesky />
</a>
</div>
</Col>
<Col className="col-2">
<div className="">Buttons</div>
</Col>
</HeaderBlock>

<div className="d-flex flex-wrap gap-2 justify-content-between mt-2">
<StatBlock>
<Col className="flex-grow-0 mx-auto">
<StatNum>?</StatNum>
<StatLine>{t("termsServed")}</StatLine>
</Col>
</StatBlock>
<StatBlock>
<Col className="flex-grow-0 mx-auto">
<StatNum>?</StatNum>
<StatLine>{t("billsSponsored")}</StatLine>
</Col>
</StatBlock>
<StatBlock>
<Col className="flex-grow-0 mx-auto">
<StatNum>?</StatNum>
<StatLine>{t("cosponsored")}</StatLine>
</Col>
</StatBlock>
<StatBlock>
<Col className="flex-grow-0 mx-auto">
<StatNum>?</StatNum>
<StatLine>{t("fundsRaised")}</StatLine>
</Col>
</StatBlock>
</div>

<Row>
<Col className={`mt-4`} md="9">
<LegislatorTabs />
</Col>
<Col className={`mt-4`} md="3">
<LegislatorSidebar />
</Col>
</Row>
</Container>
)
}
3 changes: 3 additions & 0 deletions components/legislator/SidebarComponents/Biography.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Biography() {
return <div>- Biography</div>
}
14 changes: 14 additions & 0 deletions components/legislator/SidebarComponents/LegislatorSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Biography } from "./Biography"
import { OtherTestimony } from "./OtherTestimony"
import { UpcomingHearings } from "./UpcomingHearings"

export function LegislatorSidebar() {
return (
<>
Sidebar Components
<OtherTestimony />
<UpcomingHearings />
<Biography />
</>
)
}
3 changes: 3 additions & 0 deletions components/legislator/SidebarComponents/OtherTestimony.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function OtherTestimony() {
return <div>- Other Testimony</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function UpcomingHearings() {
return <div>- Upcoming Hearings</div>
}
3 changes: 3 additions & 0 deletions components/legislator/TabComponents/Bills.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Bills() {
return <div>- Bills</div>
}
3 changes: 3 additions & 0 deletions components/legislator/TabComponents/District.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function District() {
return <div>- District</div>
}
3 changes: 3 additions & 0 deletions components/legislator/TabComponents/Elections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Elections() {
return <div>- Elections</div>
}
3 changes: 3 additions & 0 deletions components/legislator/TabComponents/Finance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Finance() {
return <div>- Finance</div>
}
Loading
Loading