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",