Follow Modes (neutral / pocket_veto / pocket_boost):
- Alembic migration 0013 adds follow_mode column to follows table
- FollowButton rewritten as mode-aware dropdown for bills; simple toggle for members/topics
- PATCH /api/follows/{id}/mode endpoint with validation
- Dispatcher filters pocket_veto follows (suppress new_document/new_amendment events)
- Dispatcher adds ntfy Actions header for pocket_boost follows
Change-driven (milestone) Alerts:
- New notification_utils.py with shared emit helpers and 30-min dedup
- congress_poller emits bill_updated events on milestone action text
- llm_processor replaced with shared emit util (also notifies member/topic followers)
Notification Enhancements:
- ntfy priority levels (high for bill_updated, default for others)
- Quiet hours (UTC): dispatcher holds events outside allowed window
- Digest mode (daily/weekly): send_notification_digest Celery beat task
- Notification history endpoint + Recent Alerts UI section
- Enriched following page (bill titles, member photos/details via sub-components)
- Follow mode test buttons in admin settings panel
Infrastructure:
- nginx: switch upstream blocks to set $variable proxy_pass so Docker DNS
re-resolves upstream IPs after container rebuilds (valid=10s)
- TROUBLESHOOTING.md documenting common Docker/nginx/postgres gotchas
Authored-By: Jack Levy
42 lines
1.2 KiB
TypeScript
42 lines
1.2 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { followsAPI } from "../api";
|
|
|
|
export function useFollows() {
|
|
return useQuery({
|
|
queryKey: ["follows"],
|
|
queryFn: () => followsAPI.list(),
|
|
staleTime: 30 * 1000,
|
|
});
|
|
}
|
|
|
|
export function useAddFollow() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ type, value }: { type: string; value: string }) =>
|
|
followsAPI.add(type, value),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ["follows"] }),
|
|
});
|
|
}
|
|
|
|
export function useRemoveFollow() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (id: number) => followsAPI.remove(id),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ["follows"] }),
|
|
});
|
|
}
|
|
|
|
export function useIsFollowing(type: string, value: string) {
|
|
const { data: follows = [] } = useFollows();
|
|
return follows.find((f) => f.follow_type === type && f.follow_value === value);
|
|
}
|
|
|
|
export function useUpdateFollowMode() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, mode }: { id: number; mode: string }) =>
|
|
followsAPI.updateMode(id, mode),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: ["follows"] }),
|
|
});
|
|
}
|