activity.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. /**
  2. * Local mavi activity log (this browser only).
  3. * Not a full chain indexer — just what this wallet did here.
  4. */
  5. export type MaviActivityKind = "send" | "fund" | "receive_note";
  6. export type MaviActivityItem = {
  7. id: string;
  8. kind: MaviActivityKind;
  9. /** Wallet address that owns this history row */
  10. wallet: string;
  11. hash?: string;
  12. to?: string;
  13. from?: string;
  14. amountEth?: string;
  15. at: string;
  16. note?: string;
  17. };
  18. const KEY = "mavi_activity_v1";
  19. const MAX = 40;
  20. function loadAll(): MaviActivityItem[] {
  21. if (typeof window === "undefined") return [];
  22. try {
  23. const raw = window.localStorage.getItem(KEY);
  24. if (!raw) return [];
  25. const parsed: unknown = JSON.parse(raw);
  26. if (!Array.isArray(parsed)) return [];
  27. return parsed.filter(isItem);
  28. } catch {
  29. return [];
  30. }
  31. }
  32. function isItem(v: unknown): v is MaviActivityItem {
  33. if (!v || typeof v !== "object") return false;
  34. const o = v as Record<string, unknown>;
  35. return (
  36. typeof o.id === "string" &&
  37. (o.kind === "send" || o.kind === "fund" || o.kind === "receive_note") &&
  38. typeof o.wallet === "string" &&
  39. typeof o.at === "string"
  40. );
  41. }
  42. function saveAll(items: MaviActivityItem[]) {
  43. try {
  44. window.localStorage.setItem(KEY, JSON.stringify(items.slice(0, MAX)));
  45. } catch {
  46. /* quota */
  47. }
  48. }
  49. export function readActivityForWallet(
  50. walletAddress: string,
  51. limit = 20,
  52. ): MaviActivityItem[] {
  53. const w = walletAddress.toLowerCase();
  54. return loadAll()
  55. .filter((i) => i.wallet.toLowerCase() === w)
  56. .slice(0, limit);
  57. }
  58. export function addActivity(
  59. item: Omit<MaviActivityItem, "id" | "at"> & { at?: string },
  60. ): MaviActivityItem {
  61. const full: MaviActivityItem = {
  62. ...item,
  63. id: `act_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
  64. at: item.at ?? new Date().toISOString(),
  65. wallet: item.wallet.toLowerCase(),
  66. };
  67. const next = [full, ...loadAll()];
  68. saveAll(next);
  69. return full;
  70. }
  71. export function clearActivityForWallet(walletAddress: string): void {
  72. const w = walletAddress.toLowerCase();
  73. saveAll(loadAll().filter((i) => i.wallet.toLowerCase() !== w));
  74. }
  75. export function formatActivityTime(iso: string): string {
  76. try {
  77. const d = new Date(iso);
  78. if (Number.isNaN(d.getTime())) return iso;
  79. return d.toLocaleString(undefined, {
  80. month: "short",
  81. day: "numeric",
  82. hour: "2-digit",
  83. minute: "2-digit",
  84. });
  85. } catch {
  86. return iso;
  87. }
  88. }
  89. export function shortHash(hash: string): string {
  90. if (hash.length < 14) return hash;
  91. return `${hash.slice(0, 8)}…${hash.slice(-6)}`;
  92. }
  93. /** Call after writing activity so open tabs / home refresh. */
  94. export function notifyActivityChanged() {
  95. if (typeof window === "undefined") return;
  96. window.dispatchEvent(new Event("mavi-activity"));
  97. }