\d+)$/
/** Formats H123 as H.123 */
@@ -15,56 +10,5 @@ export const formatBillId = (id: string) => {
}
}
-const MISSING_TIMESTAMP = Timestamp.fromMillis(0)
-export const formatTimestamp = (t?: Timestamp) => {
- if (!t || t.toMillis() == MISSING_TIMESTAMP.toMillis()) {
- return undefined
- }
- return t.toDate().toLocaleDateString()
-}
-
-export const FormattedBillTitle = ({ bill }: { bill: Bill | BillContent }) => {
- const isMobile = useMediaQuery("(max-width: 768px)")
- const billInfo = "content" in bill ? bill.content : bill
-
- const { BillNumber, Title } = billInfo
-
- return (
-
- {formatBillId(BillNumber)}:{" "}
- {isMobile ? Title.substring(0, 45) + "..." : Title}
-
- )
-}
-
-export const FormattedTestimonyTitle = ({
- testimony
-}: {
- testimony: Testimony
-}) => {
- const { authorDisplayName, publishedAt, position } = testimony
-
- return (
-
-
- Author: {authorDisplayName || "anonymous"}
-
-
-
- Published on: {publishedAt.toDate().toLocaleDateString()}
-
-
-
- Position: {position}
-
-
- )
-}
-
-export const decodeHtmlCharCodes = (s: string) =>
- s.replace(/((\d+);)/g, (match, capture, charCode) =>
- String.fromCharCode(charCode)
- )
-
export const truncateText = (s: string | undefined, maxLength: number) =>
!!s && s.length > maxLength ? s.substring(0, maxLength) + "..." : s
diff --git a/components/search/bills/BillSearch.tsx b/components/search/bills/BillSearch.tsx
index 80cefa3ec..faadd018e 100644
--- a/components/search/bills/BillSearch.tsx
+++ b/components/search/bills/BillSearch.tsx
@@ -7,23 +7,21 @@ import {
SearchBox,
useInstantSearch
} from "react-instantsearch"
-import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs"
-import singletonRouter from "next/router"
+import { currentGeneralCourt } from "functions/src/shared"
import styled from "styled-components"
import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter"
-import { currentGeneralCourt } from "functions/src/shared"
import { Col, Container, Row, Spinner } from "../../bootstrap"
import { NoResults } from "../NoResults"
import { ResultCount } from "../ResultCount"
import { SearchContainer } from "../SearchContainer"
import { SearchErrorBoundary } from "../SearchErrorBoundary"
+import { useRouting } from "../useRouting"
import { BillHit } from "./BillHit"
import { useBillRefinements } from "./useBillRefinements"
import { SortBy, SortByWithConfigurationItem } from "../SortBy"
import { getServerConfig, VirtualFilters } from "../common"
import { useBillSort } from "./useBillSort"
import { FC, useState } from "react"
-import { pathToSearchState, searchStateToUrl } from "../routingHelpers"
const searchClient = new TypesenseInstantSearchAdapter({
server: getServerConfig(),
@@ -72,16 +70,8 @@ export const BillSearch = () => {
}
}}
searchClient={searchClient}
- routing={{
- router: createInstantSearchRouterNext({
- singletonRouter,
- routerOptions: {
- cleanUrlOnDispose: false,
- createURL: args => searchStateToUrl(args),
- parseURL: args => pathToSearchState(args)
- }
- })
- }}
+ routing={useRouting()}
+ future={{ preserveSharedStateOnUnmount: true }}
>
diff --git a/components/search/routingHelpers.ts b/components/search/routingHelpers.ts
deleted file mode 100644
index 42e139b30..000000000
--- a/components/search/routingHelpers.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { UiState } from "instantsearch.js"
-import QueryString from "qs"
-
-export const searchStateToUrl = (createUrlArgs: {
- location: Location
- qsModule: typeof QueryString
- routeState: UiState
-}) => {
- const { location, qsModule: qs, routeState: searchState } = createUrlArgs
- const base = location.origin + location.pathname
-
- const flagQueries = Object.fromEntries(
- Object.entries(qs.parse(window.location.search.slice(1))).filter(
- ([key]) => !Object.keys(searchState).includes(key)
- )
- )
-
- const query = qs.stringify({
- ...searchState,
- ...flagQueries
- })
-
- return query ? `${base}?${query}` : base
-}
-
-export const pathToSearchState = (parseUrlArgs: {
- location: Location
- qsModule: typeof QueryString
-}) => {
- const { location, qsModule: qs } = parseUrlArgs
- const path = location.href
-
- return (
- path.includes("?") ? qs.parse(path.substring(path.indexOf("?") + 1)) : {}
- ) as UiState
-}
diff --git a/components/search/testimony/TestimonySearch.tsx b/components/search/testimony/TestimonySearch.tsx
index f9fe19271..e77b42645 100644
--- a/components/search/testimony/TestimonySearch.tsx
+++ b/components/search/testimony/TestimonySearch.tsx
@@ -6,8 +6,6 @@ import {
SearchBox,
useInstantSearch
} from "react-instantsearch"
-import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs"
-import singletonRouter from "next/router"
import {
StyledTabContent,
StyledTabNav
@@ -25,10 +23,10 @@ import { SearchContainer } from "../SearchContainer"
import { SearchErrorBoundary } from "../SearchErrorBoundary"
import { SortBy } from "../SortBy"
import { getServerConfig, VirtualFilters } from "../common"
+import { useRouting } from "../useRouting"
import { TestimonyHit } from "./TestimonyHit"
import { useTestimonyRefinements } from "./useTestimonyRefinements"
import { FollowContext, OrgFollowStatus } from "components/shared/FollowContext"
-import { pathToSearchState, searchStateToUrl } from "../routingHelpers"
const searchClient = new TypesenseInstantSearchAdapter({
server: getServerConfig(),
@@ -65,16 +63,8 @@ export const TestimonySearch = () => (
}
}}
searchClient={searchClient}
- routing={{
- router: createInstantSearchRouterNext({
- singletonRouter,
- routerOptions: {
- cleanUrlOnDispose: false,
- createURL: args => searchStateToUrl(args),
- parseURL: args => pathToSearchState(args)
- }
- })
- }}
+ routing={useRouting()}
+ future={{ preserveSharedStateOnUnmount: true }}
>
diff --git a/components/search/useRouting.tsx b/components/search/useRouting.tsx
new file mode 100644
index 000000000..e4ef3b9fc
--- /dev/null
+++ b/components/search/useRouting.tsx
@@ -0,0 +1,55 @@
+import { UiState } from "instantsearch.js"
+import { RouterProps } from "instantsearch.js/es/middlewares"
+import Router from "next/router"
+import qs from "qs"
+import { useMemo } from "react"
+
+const pathToSearchState = (path: string) =>
+ (path.includes("?")
+ ? qs.parse(path.substring(path.indexOf("?") + 1))
+ : {}) as UiState
+
+const searchStateToUrl = (searchState: UiState) => {
+ const base = window.location.pathname
+
+ const flagQueries = Object.fromEntries(
+ Object.entries(qs.parse(window.location.search.slice(1))).filter(
+ ([key]) => !Object.keys(searchState).includes(key)
+ )
+ )
+
+ const query = qs.stringify({
+ ...searchState,
+ ...flagQueries
+ })
+
+ return query ? `${base}?${query}` : base
+}
+
+export function useRouting(): RouterProps {
+ return useMemo(() => {
+ let disposed = false
+ return {
+ router: {
+ createURL: searchStateToUrl,
+ dispose() {
+ disposed = true
+ // Clear back listener
+ Router.beforePopState(() => true)
+ },
+ onUpdate(callback) {
+ Router.beforePopState(({ url }) => {
+ callback(pathToSearchState(url))
+ return true
+ })
+ },
+ read: () => pathToSearchState(window.location.href),
+ write(route) {
+ if (disposed) return
+ const url = searchStateToUrl(route)
+ Router.push(url, url, { shallow: true })
+ }
+ }
+ }
+ }, [])
+}
diff --git a/firestore.rules b/firestore.rules
index 9ce36c3d7..e33d279e2 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -99,6 +99,12 @@ service cloud.firestore {
allow read: if true
allow write: if false
}
+
+ // public, read-only
+ match /paragraphs/{pid} {
+ allow read: if true
+ allow write: if false
+ }
}
}
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 69d09debe..1d006efd6 100644
--- a/package.json
+++ b/package.json
@@ -118,7 +118,6 @@
"react-i18next": "^13.2.2",
"react-inlinesvg": "^3.0.1",
"react-instantsearch": "^7.12.4",
- "react-instantsearch-router-nextjs": "^7.15.5",
"react-is": "^18.2.0",
"react-markdown": "^8.0.4",
"react-overlays": "^5.1.1",
diff --git a/scripts/stories.yml b/scripts/stories.yml
index 33c5c25a6..3db5579a0 100644
--- a/scripts/stories.yml
+++ b/scripts/stories.yml
@@ -156,11 +156,6 @@ folder: billDetail
grouping: Bill Detail
figmaUrl: https://www.figma.com/file/3ifz37EOwDfmnEG8320KlD/CS1---MAPLE?node-id=180%3A7916
---
-name: CurrentCommitteeCard
-folder: billDetail
-grouping: Bill Detail
-figmaUrl: https://www.figma.com/file/3ifz37EOwDfmnEG8320KlD/CS1---MAPLE?node-id=159%3A4939
----
name: BillStatus
folder: billDetail
grouping: Bill Detail
diff --git a/stories/organisms/billDetail/CurrentCommitteeCard.stories.tsx b/stories/organisms/billDetail/CurrentCommitteeCard.stories.tsx
deleted file mode 100644
index 479913200..000000000
--- a/stories/organisms/billDetail/CurrentCommitteeCard.stories.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Meta, StoryObj } from "@storybook/react"
-import { CurrentCommitteeCard } from "components/CurrentCommitteeCard/CurrentCommitteeCard"
-
-const meta: Meta = {
- title: "Organisms/Bill Detail/CurrentCommitteeCard",
- component: CurrentCommitteeCard
-}
-
-export default meta
-
-type Story = StoryObj
-export const Primary: Story = {
- args: { chamber: "House", committee: "Committee of Ways and Means" },
- name: "CurrentCommitteeCard"
-}
diff --git a/yarn.lock b/yarn.lock
index c3169cf13..ceef2062b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5291,13 +5291,6 @@ algoliasearch-helper@3.22.5:
dependencies:
"@algolia/events" "^4.0.1"
-algoliasearch-helper@3.24.3:
- version "3.24.3"
- resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.24.3.tgz#9a358c3110bcd912e79ef606a6e7bdd7725d22ee"
- integrity sha512-3QKg5lzSfUiPN8Hn1ViHEGv6PjK7i4SFEDLzwlSzPO/4mVOsyos7B7/AsEtFQW5KHHPiCq6DyJl+mzg7CYlEgw==
- dependencies:
- "@algolia/events" "^4.0.1"
-
all-contributors-cli@^6.20.5:
version "6.26.1"
resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz#9f3358c9b9d0a7e66c8f84ffebf5a6432a859cae"
@@ -10408,13 +10401,6 @@ install-artifact-from-github@^1.3.5:
resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.5.tgz#88c96fe40e5eb21d45586d564208c648a1dbf38d"
integrity sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==
-instantsearch-ui-components@0.11.1:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/instantsearch-ui-components/-/instantsearch-ui-components-0.11.1.tgz#664ca03f657079946e459af72fa8d2674799c466"
- integrity sha512-ZqUbJYYgObQ47J08ftXV1KNC1vdEoiD4/49qrkCdW46kRzLxLgYXJGuEuk48DQwK4aBtIoccgTyfbMGfcqNjxg==
- dependencies:
- "@babel/runtime" "^7.1.2"
-
instantsearch-ui-components@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/instantsearch-ui-components/-/instantsearch-ui-components-0.9.0.tgz#f7ae71fe623d18eff32b73071749f31826cb7b89"
@@ -10445,24 +10431,6 @@ instantsearch.js@4.74.2:
qs "^6.5.1 < 6.10"
search-insights "^2.15.0"
-instantsearch.js@4.78.1:
- version "4.78.1"
- resolved "https://registry.yarnpkg.com/instantsearch.js/-/instantsearch.js-4.78.1.tgz#cee799b920ba08c7c4e5af5ba591b86a1d80af1d"
- integrity sha512-nDTWQ6DUxYzBZfkSxb/QJsYMZPPU8SGlGurn9147ABvA5Eumtxmk3Qy55EBMl0VxKVltGy3axAYMRB/gKIIHkg==
- dependencies:
- "@algolia/events" "^4.0.1"
- "@types/dom-speech-recognition" "^0.0.1"
- "@types/google.maps" "^3.55.12"
- "@types/hogan.js" "^3.0.0"
- "@types/qs" "^6.5.3"
- algoliasearch-helper "3.24.3"
- hogan.js "^3.0.2"
- htm "^3.0.0"
- instantsearch-ui-components "0.11.1"
- preact "^10.10.0"
- qs "^6.5.1 < 6.10"
- search-insights "^2.17.2"
-
instantsearch.js@^4.43.0:
version "4.62.0"
resolved "https://registry.yarnpkg.com/instantsearch.js/-/instantsearch.js-4.62.0.tgz#68577f4f04866728f22441cbc7464c544678d342"
@@ -14837,24 +14805,6 @@ react-instantsearch-core@7.13.2:
instantsearch.js "4.74.2"
use-sync-external-store "^1.0.0"
-react-instantsearch-core@7.15.5:
- version "7.15.5"
- resolved "https://registry.yarnpkg.com/react-instantsearch-core/-/react-instantsearch-core-7.15.5.tgz#65d1edc440de8dc73d55230d13af8cbcf1724221"
- integrity sha512-SFxiwwMf0f5F/8U0Y4ullvQ7bZtbYE516UOJbxaHhjV8yY0i8c22K4lrBFrYbxVRT7QAcp2wLGHiB7r/lD7eRA==
- dependencies:
- "@babel/runtime" "^7.1.2"
- algoliasearch-helper "3.24.3"
- instantsearch.js "4.78.1"
- use-sync-external-store "^1.0.0"
-
-react-instantsearch-router-nextjs@^7.15.5:
- version "7.15.5"
- resolved "https://registry.yarnpkg.com/react-instantsearch-router-nextjs/-/react-instantsearch-router-nextjs-7.15.5.tgz#a8b13bc5ad9bd8c5a689d48f2714eab6bed2514f"
- integrity sha512-kn325Nl6QkZlkSuOXwKUOb56QkSsKes9XQdBLythKt2oZzzAfcaXSYzYsFEyj96cMYDLkRHhHvLhdyq4C8Xezg==
- dependencies:
- instantsearch.js "4.78.1"
- react-instantsearch-core "7.15.5"
-
react-instantsearch@^7.12.4:
version "7.13.2"
resolved "https://registry.yarnpkg.com/react-instantsearch/-/react-instantsearch-7.13.2.tgz#db84d04bd399596fb0078625bc75a6abc65e4bc6"
@@ -15657,11 +15607,6 @@ search-insights@^2.15.0:
resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.17.2.tgz#d13b2cabd44e15ade8f85f1c3b65c8c02138629a"
integrity sha512-zFNpOpUO+tY2D85KrxJ+aqwnIfdEGi06UH2+xEb+Bp9Mwznmauqc9djbnBibJO5mpfUPPa8st6Sx65+vbeO45g==
-search-insights@^2.17.2:
- version "2.17.3"
- resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.17.3.tgz#8faea5d20507bf348caba0724e5386862847b661"
- integrity sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==
-
search-insights@^2.6.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.11.0.tgz#0512ae3b801fed5ff3a2ae82840bf20ba29d82e5"