Hide candidate URLs, report unavailable notifications, clear grounded links
CI / ci (push) Successful in 2m41s
CI / ci (push) Successful in 2m41s
Release corrections from the review of promotion pull request #98: - MQ-98-01: recipe-candidates returns an opaque candidate_id (an HMAC of the dinner and page under a per-process key) that the server resolves to the page it last offered that dinner. Candidate URLs, which may carry a token, no longer reach responses or the service-worker cache. - MQ-98-02: "Turn on notifications" no longer finishes onboarding silently when notifications are unavailable (or denied); the step says plainly that nothing was turned on and offers "Finish setting up". The placeholder stays until Feature 19 wires Web Push. - MQ-98-03: GroundPendingDinner clears the pasted recipe_url when background grounding succeeds, as manual grounding already did. Maestro task 6819ce33-3715-4bd4-9208-a54a8b5cee02. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,11 +14,21 @@ export type NotificationsResult = 'enabled' | 'denied' | 'unavailable'
|
||||
export type EnableNotifications = () => Promise<NotificationsResult>
|
||||
|
||||
// TODO(Feature 19): Web Push. Until it is wired, notifications cannot be
|
||||
// turned on, and nothing asks the browser for permission.
|
||||
// turned on, and nothing asks the browser for permission. This placeholder
|
||||
// is the interim state: "Turn on notifications" answers 'unavailable', and
|
||||
// the step tells the household plainly that nothing was turned on.
|
||||
export function notificationsUnavailable(): Promise<NotificationsResult> {
|
||||
return Promise.resolve('unavailable')
|
||||
}
|
||||
|
||||
// notOnReasons says why notifications are not on after asking, for each
|
||||
// result other than 'enabled'.
|
||||
const notOnReasons: Record<Exclude<NotificationsResult, 'enabled'>, string> = {
|
||||
unavailable:
|
||||
"Notifications aren't on yet: MealQuest can't send them until the notification feature ships, so nothing was turned on. You can finish setting up now.",
|
||||
denied: "Notifications aren't on: this browser blocked them. You can allow them in the browser's settings later.",
|
||||
}
|
||||
|
||||
const topics: { icon: LucideIcon; name: string; meaning: string }[] = [
|
||||
{
|
||||
icon: CalendarDays,
|
||||
@@ -49,16 +59,19 @@ export type NotificationsStepProps = {
|
||||
}
|
||||
|
||||
// NotificationsStep is onboarding step 3: what MealQuest notifies about, the
|
||||
// iOS install hint, and finishing onboarding. Either button records that
|
||||
// onboarding is finished and goes Home. When the server refuses because the
|
||||
// household has fewer than three Regulars, the reason shows with a way back
|
||||
// to step 2 to add them.
|
||||
// iOS install hint, and finishing onboarding. "Maybe later", and "Turn on
|
||||
// notifications" once they are enabled, record that onboarding is finished
|
||||
// and go Home. When they are not enabled, the step says so and offers only
|
||||
// "Finish setting up". When the server refuses because the household has
|
||||
// fewer than three Regulars, the reason shows with a way back to step 2 to
|
||||
// add them.
|
||||
export function NotificationsStep({ enableNotifications = notificationsUnavailable }: NotificationsStepProps) {
|
||||
const navigate = useNavigate()
|
||||
const complete = useCompleteOnboarding()
|
||||
const [enabling, setEnabling] = useState(false)
|
||||
const [problem, setProblem] = useState<string | null>(null)
|
||||
const [fewRegulars, setFewRegulars] = useState(false)
|
||||
const [notOn, setNotOn] = useState<string | null>(null)
|
||||
const busy = enabling || complete.isPending
|
||||
|
||||
async function finish() {
|
||||
@@ -80,14 +93,19 @@ export function NotificationsStep({ enableNotifications = notificationsUnavailab
|
||||
async function turnOn() {
|
||||
clearProblem()
|
||||
setEnabling(true)
|
||||
let result: NotificationsResult
|
||||
try {
|
||||
await enableNotifications()
|
||||
result = await enableNotifications()
|
||||
} catch (error) {
|
||||
setProblem(`Could not turn on notifications: ${(error as Error).message}`)
|
||||
return
|
||||
} finally {
|
||||
setEnabling(false)
|
||||
}
|
||||
if (result !== 'enabled') {
|
||||
setNotOn(notOnReasons[result])
|
||||
return
|
||||
}
|
||||
await finish()
|
||||
}
|
||||
|
||||
@@ -101,14 +119,22 @@ export function NotificationsStep({ enableNotifications = notificationsUnavailab
|
||||
step={3}
|
||||
title="Stay in the loop"
|
||||
action={
|
||||
<div className={styles.actions}>
|
||||
<Button className={styles.action} disabled={busy} onClick={() => void turnOn()}>
|
||||
Turn on notifications
|
||||
</Button>
|
||||
<Button variant="outline" className={styles.action} disabled={busy} onClick={later}>
|
||||
Maybe later
|
||||
</Button>
|
||||
</div>
|
||||
notOn ? (
|
||||
<div className={styles.actions}>
|
||||
<Button className={styles.action} disabled={busy} onClick={later}>
|
||||
Finish setting up
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.actions}>
|
||||
<Button className={styles.action} disabled={busy} onClick={() => void turnOn()}>
|
||||
Turn on notifications
|
||||
</Button>
|
||||
<Button variant="outline" className={styles.action} disabled={busy} onClick={later}>
|
||||
Maybe later
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<p className={styles.helper}>MealQuest can let the household know when there's something to look at:</p>
|
||||
@@ -128,6 +154,11 @@ export function NotificationsStep({ enableNotifications = notificationsUnavailab
|
||||
<div className={styles.hint}>
|
||||
<InstallHint />
|
||||
</div>
|
||||
{notOn && (
|
||||
<p role="status" className={styles.notOn}>
|
||||
{notOn}
|
||||
</p>
|
||||
)}
|
||||
{problem && (
|
||||
<p role="alert" className={styles.problem}>
|
||||
{problem}
|
||||
|
||||
Reference in New Issue
Block a user