Files
yaak-mountain-loop/apps/yaak-client/lib/toast.tsx
T
Gregory Schier b1f1363502 Add in-app micro-feedback prompts for new features
Show a one-time toast asking how a feature is working after its third
successful use, with an optional comment sent anonymously to the Yaak
API (feature key, text, app version, and OS only — nothing identifying,
and nothing is sent unless the user clicks Send).

- New cmd_send_feedback Tauri command posts fire-and-forget via the
  shared API client (localhost in dev)
- Feature keys registry (cookie-editor, response-history, sse-summary,
  git-sync) with per-feature use counting in the key-value store
- "Never ask for feedback" setting to disable prompts entirely
- Toast gains dynamicHeight and hideDismiss props for richer content
- Fix missing vertical padding on xs/2xs multiline inputs
- Fix unused-import and dead-code warnings in yaak-system-appearance
  on non-Linux builds

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 15:06:21 -07:00

74 lines
1.8 KiB
TypeScript

import { atom } from "jotai";
import type { ToastInstance } from "../components/Toasts";
import { generateId } from "./generateId";
import { jotaiStore } from "./jotai";
export const toastsAtom = atom<ToastInstance[]>([]);
export function showToast({
id,
timeout = 5000,
...props
}: Omit<ToastInstance, "id" | "timeout" | "uniqueKey"> & {
id?: ToastInstance["id"];
timeout?: ToastInstance["timeout"];
}) {
id = id ?? generateId();
const uniqueKey = generateId();
const toasts = jotaiStore.get(toastsAtom);
const toastWithSameId = toasts.find((t) => t.id === id);
let delay = 0;
if (toastWithSameId) {
hideToast(toastWithSameId);
// Allow enough time for old toast to animate out
delay = 200;
}
setTimeout(() => {
const newToast: ToastInstance = { id, uniqueKey, timeout, ...props };
if (timeout != null) {
setTimeout(() => hideToast(newToast), timeout);
}
jotaiStore.set(toastsAtom, (prev) => [...prev, newToast]);
}, delay);
return id;
}
export function hideToastById(id: string) {
const toast = jotaiStore.get(toastsAtom).find((t) => t.id === id);
if (toast) hideToast(toast);
}
export function hideToast(toHide: ToastInstance) {
jotaiStore.set(toastsAtom, (all) => {
const t = all.find((t) => t.uniqueKey === toHide.uniqueKey);
t?.onClose?.();
return all.filter((t) => t.uniqueKey !== toHide.uniqueKey);
});
}
export function showErrorToast<T>({
id,
title,
message,
}: {
id: string;
title: string;
message: T;
}) {
return showToast({
id,
color: "danger",
timeout: null,
message: (
<div className="w-full">
<h2 className="text-lg font-bold mb-2">{title}</h2>
<div className="whitespace-pre-wrap wrap-break-word">{String(message)}</div>
</div>
),
});
}