Feature 19: Notifications - Web Push and Google Chat (#169)
CI / ci (push) Successful in 8m31s
CI / ci (push) Successful in 8m31s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #169.
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { Archive, Check, CloudUpload, MessageSquare } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Link, useSearchParams } from 'react-router'
|
||||
import { Unavailable } from '../plan/PlanPage.tsx'
|
||||
import { defaultWeek, localNow, weekdayKicker, weekLabel, weekStart as weekOf } from '../plan/week.ts'
|
||||
import { ApiError } from '../api/errors.ts'
|
||||
import { formatClockTime, useHouseholdSettings } from '../settings/api.ts'
|
||||
import { useBreakpoint, type Breakpoint } from '../shell/useBreakpoint.ts'
|
||||
import { Button } from '../ui/Button.tsx'
|
||||
import buttonStyles from '../ui/Button.module.css'
|
||||
import { Checkbox } from '../ui/Checkbox.tsx'
|
||||
import { EmptyState } from '../ui/EmptyState.tsx'
|
||||
@@ -14,6 +15,7 @@ import { Skeleton } from '../ui/Skeleton.tsx'
|
||||
import { Tag } from '../ui/Tag.tsx'
|
||||
import {
|
||||
useGenerateSupplyRun,
|
||||
useSendToChat,
|
||||
useSupplyRun,
|
||||
useToggles,
|
||||
WEEK_PARAM,
|
||||
@@ -27,10 +29,6 @@ import styles from './SupplyRunPage.module.css'
|
||||
// SUPPLY_ARCHIVE is the Supply archive screen.
|
||||
export const SUPPLY_ARCHIVE = '/plan/shopping/archive'
|
||||
|
||||
// CHAT_HINT is why Send to Google Chat is unavailable.
|
||||
// TODO(Feature 19): send the Supply run to Google Chat once it is built.
|
||||
const CHAT_HINT = 'Sending the shopping list to Google Chat arrives in a later update.'
|
||||
|
||||
// sentAt is when the Supply run was sent to Google Chat as its status line
|
||||
// says it, in the household's zone: "Sat 8:00 am".
|
||||
export function sentAt(at: Date, timezone: string): string {
|
||||
@@ -104,6 +102,29 @@ function SentStatus({ at, timezone }: { at: Date; timezone: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
// UnconfirmedStatus is the status line while a send to Google Chat whose
|
||||
// outcome is not known is recorded: it may or may not have been posted.
|
||||
function UnconfirmedStatus({ at, timezone }: { at: Date; timezone: string }) {
|
||||
return (
|
||||
<p className={`${styles.sent} ${styles.unconfirmed}`}>
|
||||
<Icon icon={MessageSquare} size="inline" />
|
||||
Send to Google Chat not confirmed · {sentAt(at, timezone)} — check the space before sending again
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
// SendToChat posts the Supply run to Google Chat: "Send to Google Chat"
|
||||
// until it is sent, or a send is unconfirmed, then "Send again", which
|
||||
// posts it once more.
|
||||
function SendToChat({ sent, pending, onSend }: { sent: boolean; pending: boolean; onSend: () => void }) {
|
||||
return (
|
||||
<Button className={styles.action} disabled={pending} onClick={onSend}>
|
||||
<Icon icon={MessageSquare} size="inline" />
|
||||
{pending ? 'Sending…' : sent ? 'Send again' : 'Send to Google Chat'}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
// ArchiveLink opens the Supply archive: an outline action from tablet up, a
|
||||
// grape link on mobile.
|
||||
function ArchiveLink({ layout }: { layout: Breakpoint }) {
|
||||
@@ -120,13 +141,22 @@ function ArchiveLink({ layout }: { layout: Breakpoint }) {
|
||||
// the week's dates, the title with the count still to buy of all items, and
|
||||
// one card per dinner, stacked on mobile and in three masonry columns from
|
||||
// tablet up, where Archive and Send to Google Chat are actions in the
|
||||
// header. On a current Supply run each item toggles at once (useToggles); a
|
||||
// header; a send that fails says why, and one whose outcome is not known
|
||||
// offers no retry and leaves the status line saying so. On a current Supply run each item toggles at once (useToggles); a
|
||||
// toggle refused puts the item back and says why. An archived one, or any
|
||||
// opened from the Supply archive (readOnly), is read-only and tagged
|
||||
// Archived, and a toggle it refused is not offered again.
|
||||
function SupplyRunList({ weekStart, timezone, layout, readOnly }: WeekProps) {
|
||||
const toggles = useToggles(weekStart)
|
||||
const send = useSendToChat(weekStart)
|
||||
const run = toggles.shown
|
||||
const sent = run.sent_to_chat_at !== null
|
||||
// A send whose outcome is not known is posted again only on request.
|
||||
const resend = sent || run.chat_send_unconfirmed_at !== null
|
||||
const unconfirmed = !send.isPending && run.chat_send_unconfirmed_at !== null && (
|
||||
<UnconfirmedStatus at={run.chat_send_unconfirmed_at} timezone={timezone} />
|
||||
)
|
||||
const sendUnconfirmed = send.error instanceof ApiError && send.error.code === 'chat_unconfirmed'
|
||||
const viewOnly = readOnly || run.status === 'archived'
|
||||
const wide = layout !== 'mobile'
|
||||
const failed = toggles.failed
|
||||
@@ -155,12 +185,7 @@ function SupplyRunList({ weekStart, timezone, layout, readOnly }: WeekProps) {
|
||||
{wide && (
|
||||
<div className={styles.actions}>
|
||||
<ArchiveLink layout={layout} />
|
||||
{!viewOnly && (
|
||||
<Unavailable hint={CHAT_HINT} below className={styles.action}>
|
||||
<Icon icon={MessageSquare} size="inline" />
|
||||
Send to Google Chat
|
||||
</Unavailable>
|
||||
)}
|
||||
{!viewOnly && <SendToChat sent={resend} pending={send.isPending} onSend={() => send.mutate(resend)} />}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
@@ -170,7 +195,13 @@ function SupplyRunList({ weekStart, timezone, layout, readOnly }: WeekProps) {
|
||||
{failed.error.message}
|
||||
</ErrorBanner>
|
||||
)}
|
||||
{send.isError && !viewOnly && (
|
||||
<ErrorBanner onRetry={sendUnconfirmed ? undefined : () => send.mutate(resend)}>
|
||||
Couldn't send the shopping list to Google Chat. {send.error.message}
|
||||
</ErrorBanner>
|
||||
)}
|
||||
{wide && run.sent_to_chat_at !== null && <SentStatus at={run.sent_to_chat_at} timezone={timezone} />}
|
||||
{wide && unconfirmed}
|
||||
{run.groups.length === 0 ? (
|
||||
<EmptyState title="Nothing to buy">None of this week's dinners has a recipe with ingredients yet.</EmptyState>
|
||||
) : (
|
||||
@@ -183,6 +214,7 @@ function SupplyRunList({ weekStart, timezone, layout, readOnly }: WeekProps) {
|
||||
{!wide && (
|
||||
<footer className={styles.footer}>
|
||||
{run.sent_to_chat_at !== null && <SentStatus at={run.sent_to_chat_at} timezone={timezone} />}
|
||||
{unconfirmed}
|
||||
<ArchiveLink layout={layout} />
|
||||
</footer>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user