diff --git a/components/bill/Summary.tsx b/components/bill/Summary.tsx
index 4b1696055..10288d97b 100644
--- a/components/bill/Summary.tsx
+++ b/components/bill/Summary.tsx
@@ -104,11 +104,14 @@ export const ViewMessage = styled.div`
}
`
+const BALLOT_SUMMARY_CHAR_LIMIT = 1000
+
export const Summary = ({
bill,
className
}: BillProps & { className?: string }) => {
const [showBillDetails, setShowBillDetails] = useState(false)
+ const [showFullSummary, setShowFullSummary] = useState(false)
const handleShowBillDetails = () => setShowBillDetails(true)
const handleHideBillDetails = () => setShowBillDetails(false)
const billText = bill?.content?.DocumentText
@@ -221,7 +224,36 @@ export const Summary = ({
)}
{bill.summary !== undefined && isBallotMeasure ? (
- {bill.summary}
+ {bill.summary.length > BALLOT_SUMMARY_CHAR_LIMIT ? (
+ <>
+ {bill.summary.slice(0, BALLOT_SUMMARY_CHAR_LIMIT)}…{" "}
+ setShowFullSummary(true)}
+ >
+ {t("bill.view_full_summary")}
+
+ setShowFullSummary(false)}
+ size="lg"
+ >
+ setShowFullSummary(false)}
+ >
+ {bill?.id}
+
+
+
+ {bill.summary}
+
+
+
+ >
+ ) : (
+ bill.summary
+ )}
) : (
{bill.summary}
diff --git a/components/db/index.ts b/components/db/index.ts
index 768d36da1..8a30079c1 100644
--- a/components/db/index.ts
+++ b/components/db/index.ts
@@ -1,6 +1,7 @@
export * from "./bills"
export * from "./createTableHook"
export * from "./members"
+export * from "./news"
export * from "./profile"
export * from "./testimony"
export * from "./useUpcomingBills"
diff --git a/components/db/news.ts b/components/db/news.ts
new file mode 100644
index 000000000..fb4dca211
--- /dev/null
+++ b/components/db/news.ts
@@ -0,0 +1,26 @@
+import { collection, getDocs, orderBy, Timestamp } from "firebase/firestore"
+import { useAsync } from "react-async-hook"
+import { firestore } from "../firebase"
+
+export type NewsType = "article" | "award" | "book"
+
+export type NewsItem = {
+ id: string
+ url: string
+ title: string
+ author: string
+ type: NewsType
+ description?: string
+ publishDate: string
+ createdAt: Timestamp
+}
+
+export async function listNews(): Promise {
+ const newsRef = collection(firestore, "news")
+ const result = await getDocs(newsRef)
+ return result.docs.map(d => ({ id: d.id, ...d.data() } as NewsItem))
+}
+
+export function useNews() {
+ return useAsync(listNews, [])
+}
diff --git a/components/moderation/News.tsx b/components/moderation/News.tsx
new file mode 100644
index 000000000..fdb63b92a
--- /dev/null
+++ b/components/moderation/News.tsx
@@ -0,0 +1,91 @@
+import React, { useEffect } from "react"
+import { collection, getFirestore, onSnapshot } from "firebase/firestore"
+import {
+ Create,
+ Datagrid,
+ DateField,
+ DateInput,
+ Edit,
+ EditButton,
+ FunctionField,
+ List,
+ SelectInput,
+ SimpleForm,
+ TextField,
+ TextInput,
+ useRefresh
+} from "react-admin"
+
+const typeChoices = [
+ { id: "article", name: "Article" },
+ { id: "award", name: "Award" },
+ { id: "book", name: "Book" }
+]
+
+export function ListNews() {
+ const firestore = getFirestore()
+ const refresh = useRefresh()
+
+ useEffect(() => {
+ const newsRef = collection(firestore, "news")
+ const unsubscribe = onSnapshot(
+ newsRef,
+ () => refresh(),
+ (e: Error) => console.log(e)
+ )
+
+ return () => unsubscribe()
+ }, [firestore, refresh])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export function EditNews() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export function CreateNews() {
+ return (
+ ) => ({
+ ...data,
+ createdAt: new Date()
+ })}
+ >
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default { ListNews, EditNews, CreateNews }
diff --git a/components/moderation/dataProviderDbCalls.ts b/components/moderation/dataProviderDbCalls.ts
index a4ccc5fc0..ed745a236 100644
--- a/components/moderation/dataProviderDbCalls.ts
+++ b/components/moderation/dataProviderDbCalls.ts
@@ -136,9 +136,13 @@ export async function createMyOne(
): Promise {
console.log("creating my one")
const { data, meta } = params
- const ref = doc(firestore, resource, data.id)
- await setDoc(ref, data)
- return { data: data }
+ const ref = data.id
+ ? doc(firestore, resource, data.id)
+ : doc(collection(firestore, resource))
+ const id = ref.id
+ const newData = { ...data, id }
+ await setDoc(ref, newData)
+ return { data: newData }
}
export const getMyListGroup = async (
diff --git a/components/moderation/index.ts b/components/moderation/index.ts
index f987db222..a4dd62883 100644
--- a/components/moderation/index.ts
+++ b/components/moderation/index.ts
@@ -1,5 +1,6 @@
export * from "./types"
export * from "./ListPublishedTestimony"
+export * from "./News"
export * from "./ListReports"
export * from "./EditReports"
export * from "./ListProfiles"
diff --git a/components/moderation/moderation.tsx b/components/moderation/moderation.tsx
index f0926fc84..2ddc27834 100644
--- a/components/moderation/moderation.tsx
+++ b/components/moderation/moderation.tsx
@@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from "react-query"
import { EditReports, ListReports } from "./"
import { ListProfiles } from "./ListProfiles"
import { ScrapeHearingList } from "./ScrapeHearing"
+import { ListNews, EditNews, CreateNews } from "./"
import {
createMyOne,
getMyListGroup,
@@ -54,6 +55,13 @@ const App = () => {
list={ScrapeHearingList}
options={{ label: "Scrape Hearing" }}
/>
+
)
diff --git a/firestore.rules b/firestore.rules
index e33d279e2..5cbca817b 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -69,6 +69,12 @@ service cloud.firestore {
// Only admins can do anything with it
allow read, write: if request.auth.token.get("role", "user") == "admin"
}
+
+ // Admin-managed news collection used by the admin UI and public news page
+ match /news/{nid} {
+ allow read: if true;
+ allow write: if request.auth.token.get("role", "user") == "admin";
+ }
match /users/{uid} {
allow read, write: if request.auth.token.get("role", "user") == "admin"
match /draftTestimony/{id} {
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 0fc4af4f0..419f56be0 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -65,7 +65,8 @@
"smart_tag": "AI Smart Tag",
"status_and_history": "Status & History",
"status_history": "Status History",
- "view_bill": "View Bill Text"
+ "view_bill": "View Bill Text",
+ "view_full_summary": "View Full Summary"
},
"bill_updates": "Bill Updates",
"browse_bills": "browse bills",