feat: T4 wave 11 – bottling form with article values and confirmation, Telegram in the app
- Bottling form: jar size and best-before from the product with source, differs-hints before saving, prefill status, water-content confirmation naming the measured value, idempotency key renewed on body change; product form gets shelf life (months) and whole-gram jar weight. - Telegram frontend: link card with step-up dialog, t.me-only deep link, status polling, unlink; system Telegram card with receiver/sender state and confirmed retry; StepUpDialog shared with the backup import. - Reauth calls never refresh-and-retry on 401 (no double lockout count); German texts for expired session and 429; service worker never caches Telegram, notification settings and system responses. - Telegram backend round 2: shared link removal on every disconnect path, lease released on shutdown, connection errors not counted as poison. - ARCHITECTURE.md §2, §6 (Annahmen 56–58), §10, §11.12, §11.13; TASKS.md T4. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
fab7f7e12f
commit
9760ce154f
73 files changed
+3633
-269
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { apiFetch } from "@/lib/auth/api-client";
|
||||
import type { NotificationSettings } from "./types";
|
||||
import type { NotificationSettings, NotificationSettingsPatch } from "./types";
|
||||
|
||||
const PATH = "/profile/notification-settings";
|
||||
|
||||
@@ -9,7 +9,7 @@ export function fetchNotificationSettings(): Promise<NotificationSettings> {
|
||||
|
||||
/** Send only what changed; an omitted field stays as it is. */
|
||||
export function updateNotificationSettings(
|
||||
patch: Partial<NotificationSettings>,
|
||||
patch: NotificationSettingsPatch,
|
||||
): Promise<NotificationSettings> {
|
||||
return apiFetch<NotificationSettings>(PATH, {
|
||||
method: "PATCH",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** One cache entry for the settings card and the Telegram card. */
|
||||
export const NOTIFICATION_SETTINGS_QUERY_KEY = ["notification-settings"] as const;
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { NotificationCategory } from "@/lib/notifications/types";
|
||||
import type { TelegramLinkStatus } from "@/lib/telegram/types";
|
||||
|
||||
/** The signed-in user's own settings (GET/PATCH /profile/notification-settings). */
|
||||
export interface NotificationSettings {
|
||||
@@ -11,4 +12,9 @@ export interface NotificationSettings {
|
||||
quietUntil: string | null;
|
||||
telegramCategories: NotificationCategory[];
|
||||
telegramDetail: boolean;
|
||||
/** Read-only connection state; changed through /profile/telegram/*. */
|
||||
telegram: TelegramLinkStatus;
|
||||
}
|
||||
|
||||
/** What PATCH accepts: the editable fields. */
|
||||
export type NotificationSettingsPatch = Partial<Omit<NotificationSettings, "telegram">>;
|
||||
@@ -10,6 +10,14 @@ const base: NotificationSettings = {
|
||||
quietUntil: "06:30",
|
||||
telegramCategories: ["TASK_DUE", "DIGEST"],
|
||||
telegramDetail: true,
|
||||
telegram: {
|
||||
botNameSet: true,
|
||||
botUsername: "apicore_bot",
|
||||
linked: false,
|
||||
firstName: null,
|
||||
linkedAt: null,
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
describe("validateDigestTime", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NotificationSettings } from "./types";
|
||||
import type { NotificationSettings, NotificationSettingsPatch } from "./types";
|
||||
|
||||
export const DIGEST_TIME_MIN = "05:00";
|
||||
export const DIGEST_TIME_MAX = "12:00";
|
||||
@@ -24,8 +24,8 @@ export function validateQuietHours(from: string, until: string): string | null {
|
||||
export function settingsPatch(
|
||||
original: NotificationSettings,
|
||||
draft: NotificationSettings,
|
||||
): Partial<NotificationSettings> {
|
||||
const patch: Partial<NotificationSettings> = {};
|
||||
): NotificationSettingsPatch {
|
||||
const patch: NotificationSettingsPatch = {};
|
||||
if (draft.taskDueEnabled !== original.taskDueEnabled) patch.taskDueEnabled = draft.taskDueEnabled;
|
||||
if (draft.digestEnabled !== original.digestEnabled) patch.digestEnabled = draft.digestEnabled;
|
||||
if (draft.digestTime !== original.digestTime) patch.digestTime = draft.digestTime;
|
||||
|
||||
Reference in new issue
Block a user