@medialane/ui 0.162.0 → 0.164.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/components/activities-feed.cjs +211 -0
  2. package/dist/components/activities-feed.cjs.map +1 -0
  3. package/dist/components/activities-feed.d.cts +9 -0
  4. package/dist/components/activities-feed.d.ts +9 -0
  5. package/dist/components/activities-feed.js +177 -0
  6. package/dist/components/activities-feed.js.map +1 -0
  7. package/dist/components/button.d.cts +1 -1
  8. package/dist/components/button.d.ts +1 -1
  9. package/dist/components/fast-mint/dropzone.d.cts +1 -0
  10. package/dist/components/fast-mint/dropzone.d.ts +1 -0
  11. package/dist/components/fast-mint/fast-mint.cjs +3 -1
  12. package/dist/components/fast-mint/fast-mint.cjs.map +1 -1
  13. package/dist/components/fast-mint/fast-mint.d.cts +2 -2
  14. package/dist/components/fast-mint/fast-mint.d.ts +2 -2
  15. package/dist/components/fast-mint/fast-mint.js +3 -1
  16. package/dist/components/fast-mint/fast-mint.js.map +1 -1
  17. package/dist/components/fast-mint/success-view.cjs +2 -0
  18. package/dist/components/fast-mint/success-view.cjs.map +1 -1
  19. package/dist/components/fast-mint/success-view.d.cts +3 -1
  20. package/dist/components/fast-mint/success-view.d.ts +3 -1
  21. package/dist/components/fast-mint/success-view.js +2 -0
  22. package/dist/components/fast-mint/success-view.js.map +1 -1
  23. package/dist/components/fast-mint/types.cjs.map +1 -1
  24. package/dist/components/fast-mint/types.d.cts +2 -0
  25. package/dist/components/fast-mint/types.d.ts +2 -0
  26. package/dist/components/sheet.d.cts +1 -1
  27. package/dist/components/sheet.d.ts +1 -1
  28. package/dist/index.cjs +3 -0
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +2 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils/notification-readiness.cjs +32 -0
  35. package/dist/utils/notification-readiness.cjs.map +1 -0
  36. package/dist/utils/notification-readiness.d.cts +9 -0
  37. package/dist/utils/notification-readiness.d.ts +9 -0
  38. package/dist/utils/notification-readiness.js +8 -0
  39. package/dist/utils/notification-readiness.js.map +1 -0
  40. package/dist/utils/use-notifications.cjs +15 -4
  41. package/dist/utils/use-notifications.cjs.map +1 -1
  42. package/dist/utils/use-notifications.d.cts +1 -0
  43. package/dist/utils/use-notifications.d.ts +1 -0
  44. package/dist/utils/use-notifications.js +16 -5
  45. package/dist/utils/use-notifications.js.map +1 -1
  46. package/package.json +1 -1
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var activities_feed_exports = {};
31
+ __export(activities_feed_exports, {
32
+ ActivitiesFeed: () => ActivitiesFeed
33
+ });
34
+ module.exports = __toCommonJS(activities_feed_exports);
35
+ var import_jsx_runtime = require("react/jsx-runtime");
36
+ var import_react = require("react");
37
+ var import_swr = __toESM(require("swr"), 1);
38
+ var import_link = __toESM(require("next/link"), 1);
39
+ var import_use_activities = require("../utils/use-activities.js");
40
+ var import_use_rewards = require("../utils/use-rewards.js");
41
+ var import_skeleton = require("./skeleton.js");
42
+ var import_load_more_sentinel = require("./load-more-sentinel.js");
43
+ var import_lucide_react = require("lucide-react");
44
+ var import_activity = require("../data/activity.js");
45
+ var import_activity_row = require("./activity-row.js");
46
+ var import_cn = require("../utils/cn.js");
47
+ async function fetchAnnouncements() {
48
+ const res = await fetch("/api/announcements");
49
+ if (!res.ok) return [];
50
+ return res.json();
51
+ }
52
+ function AnnouncementsBanner({ announcements }) {
53
+ const pinned = announcements.filter((a) => a.pinned);
54
+ if (pinned.length === 0) return null;
55
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "space-y-2", children: pinned.map((ann) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
56
+ import_link.default,
57
+ {
58
+ href: ann.href,
59
+ className: "flex items-start gap-3 rounded-xl border border-brand-purple/20 bg-brand-purple/5 px-4 py-3 hover:bg-brand-purple/10 transition-colors group",
60
+ children: [
61
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-8 w-8 rounded-lg bg-brand-purple/10 flex items-center justify-center shrink-0 mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Megaphone, { className: "h-3.5 w-3.5 text-brand-purple" }) }),
62
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 min-w-0", children: [
63
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5 mb-0.5", children: [
64
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Pin, { className: "h-2.5 w-2.5 text-brand-purple/60" }),
65
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] font-bold uppercase tracking-widest text-brand-purple/60", children: "Announcement" })
66
+ ] }),
67
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm font-semibold leading-snug", children: ann.title }),
68
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: ann.body })
69
+ ] }),
70
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { className: "h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors shrink-0 mt-1" })
71
+ ]
72
+ },
73
+ ann.id
74
+ )) });
75
+ }
76
+ const PAGE_SIZE = 30;
77
+ function ActivitiesFeed({ getClient }) {
78
+ const [page, setPage] = (0, import_react.useState)(1);
79
+ const [typeFilter, setTypeFilter] = (0, import_react.useState)("");
80
+ const [allActivities, setAllActivities] = (0, import_react.useState)([]);
81
+ const { data: announcements = [] } = (0, import_swr.default)(
82
+ "announcements",
83
+ fetchAnnouncements,
84
+ { revalidateOnFocus: false }
85
+ );
86
+ const prevType = (0, import_react.useRef)(typeFilter);
87
+ (0, import_react.useEffect)(() => {
88
+ if (prevType.current !== typeFilter) {
89
+ prevType.current = typeFilter;
90
+ setPage(1);
91
+ setAllActivities([]);
92
+ }
93
+ }, [typeFilter]);
94
+ const { activities, meta, isLoading } = (0, import_use_activities.useActivities)(getClient, {
95
+ limit: PAGE_SIZE,
96
+ page,
97
+ type: typeFilter || void 0
98
+ });
99
+ (0, import_react.useEffect)(() => {
100
+ if (isLoading) return;
101
+ if (page === 1) {
102
+ setAllActivities(activities);
103
+ } else {
104
+ setAllActivities((prev) => {
105
+ const existing = new Set(
106
+ prev.map((a) => `${a.txHash}-${a.type}-${a.nftTokenId ?? ""}`)
107
+ );
108
+ const newItems = activities.filter(
109
+ (a) => !existing.has(`${a.txHash}-${a.type}-${a.nftTokenId ?? ""}`)
110
+ );
111
+ return newItems.length > 0 ? [...prev, ...newItems] : prev;
112
+ });
113
+ }
114
+ }, [activities, isLoading, page]);
115
+ const pageActors = allActivities.map((a) => a.offerer ?? a.fulfiller ?? (a.type === "mint" ? a.to : a.from)).filter((a) => Boolean(a));
116
+ const { data: actorLevels } = (0, import_use_rewards.useRewardsBatch)(getClient, pageActors);
117
+ const isInitialLoading = isLoading && allActivities.length === 0;
118
+ const isLoadingMore = isLoading && allActivities.length > 0;
119
+ const hasMore = meta?.total != null ? allActivities.length < meta.total : false;
120
+ const total = meta?.total ?? null;
121
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-5", children: [
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnnouncementsBanner, { announcements }),
123
+ total != null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-1.5", children: [
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" }),
126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold text-foreground tabular-nums", children: total.toLocaleString() }),
127
+ typeFilter ? `${typeFilter} events` : "total events"
128
+ ] }),
129
+ typeFilter && allActivities.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground/40", children: "\xB7" }),
131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
132
+ allActivities.length.toLocaleString(),
133
+ " loaded"
134
+ ] })
135
+ ] })
136
+ ] }),
137
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-2", children: import_activity.TYPE_FILTERS.map((f) => {
138
+ const typeConfig = f.value ? import_activity.ACTIVITY_TYPE_CONFIG[f.value] : null;
139
+ const Icon = typeConfig?.icon;
140
+ const isActive = typeFilter === f.value;
141
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
142
+ "button",
143
+ {
144
+ onClick: () => setTypeFilter(f.value),
145
+ className: (0, import_cn.cn)(
146
+ "flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-all font-medium",
147
+ isActive ? "border-primary bg-primary/10 text-primary shadow-sm" : "border-border text-muted-foreground hover:border-primary/40 hover:text-foreground hover:bg-muted/50"
148
+ ),
149
+ children: [
150
+ Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
151
+ Icon,
152
+ {
153
+ className: (0, import_cn.cn)(
154
+ "h-3 w-3",
155
+ isActive ? typeConfig?.colorClass : ""
156
+ )
157
+ }
158
+ ),
159
+ f.label
160
+ ]
161
+ },
162
+ f.value
163
+ );
164
+ }) }),
165
+ isInitialLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "divide-y divide-border/50 rounded-xl border border-border overflow-hidden", children: Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-3 px-4 py-3.5 bg-card", children: [
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_skeleton.Skeleton, { className: "h-8 w-8 rounded-lg shrink-0" }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_skeleton.Skeleton, { className: "h-9 w-9 rounded-md shrink-0" }),
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 space-y-2", children: [
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_skeleton.Skeleton, { className: "h-3.5 w-40" }),
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_skeleton.Skeleton, { className: "h-2.5 w-24" })
171
+ ] }),
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2.5", children: [
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1 text-right", children: [
174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_skeleton.Skeleton, { className: "h-4 w-16" }),
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_skeleton.Skeleton, { className: "h-2.5 w-8" })
176
+ ] }),
177
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_skeleton.Skeleton, { className: "h-2.5 w-12 hidden sm:block" })
178
+ ] })
179
+ ] }, i)) }) : allActivities.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "py-20 flex flex-col items-center gap-3 text-center", children: [
180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-12 w-12 rounded-full bg-muted flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Zap, { className: "h-5 w-5 text-muted-foreground" }) }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-muted-foreground", children: typeFilter ? `No ${typeFilter} events yet.` : "No activity yet." })
182
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-4", children: [
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "divide-y divide-border/50 rounded-xl border border-border overflow-hidden", children: allActivities.map((activity, i) => {
184
+ const actor = activity.offerer ?? activity.fulfiller ?? (activity.type === "mint" ? activity.to : activity.from);
185
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
186
+ import_activity_row.ActivityRow,
187
+ {
188
+ activity,
189
+ showActor: true,
190
+ showExplorer: true,
191
+ actorLevel: actor ? actorLevels?.get(actor) : void 0
192
+ },
193
+ `${activity.txHash}-${activity.type}-${activity.nftTokenId ?? i}`
194
+ );
195
+ }) }),
196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
197
+ import_load_more_sentinel.LoadMoreSentinel,
198
+ {
199
+ hasMore,
200
+ isLoading: isLoadingMore,
201
+ onLoadMore: () => setPage((p) => p + 1)
202
+ }
203
+ )
204
+ ] })
205
+ ] });
206
+ }
207
+ // Annotate the CommonJS export names for ESM import in node:
208
+ 0 && (module.exports = {
209
+ ActivitiesFeed
210
+ });
211
+ //# sourceMappingURL=activities-feed.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/activities-feed.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useEffect, useRef } from \"react\";\nimport useSWR from \"swr\";\nimport Link from \"next/link\";\nimport { useActivities } from \"../utils/use-activities.js\";\nimport { useRewardsBatch } from \"../utils/use-rewards.js\";\nimport type { ApiActivitiesQuery, ApiActivity } from \"@medialane/sdk\";\nimport type { MedialaneClient } from \"@medialane/sdk/starknet\";\nimport { Skeleton } from \"./skeleton.js\";\nimport { LoadMoreSentinel } from \"./load-more-sentinel.js\";\nimport { Zap, Megaphone, ArrowRight, Pin } from \"lucide-react\";\nimport { ACTIVITY_TYPE_CONFIG, TYPE_FILTERS } from \"../data/activity.js\";\nimport { ActivityRow } from \"./activity-row.js\";\nimport { cn } from \"../utils/cn.js\";\nimport type { Announcement } from \"../data/notification.js\";\n\nasync function fetchAnnouncements(): Promise<Announcement[]> {\n const res = await fetch(\"/api/announcements\");\n if (!res.ok) return [];\n return res.json();\n}\n\nfunction AnnouncementsBanner({ announcements }: { announcements: Announcement[] }) {\n const pinned = announcements.filter((a) => a.pinned);\n if (pinned.length === 0) return null;\n return (\n <div className=\"space-y-2\">\n {pinned.map((ann) => (\n <Link\n key={ann.id}\n href={ann.href}\n className=\"flex items-start gap-3 rounded-xl border border-brand-purple/20 bg-brand-purple/5 px-4 py-3 hover:bg-brand-purple/10 transition-colors group\"\n >\n <div className=\"h-8 w-8 rounded-lg bg-brand-purple/10 flex items-center justify-center shrink-0 mt-0.5\">\n <Megaphone className=\"h-3.5 w-3.5 text-brand-purple\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-1.5 mb-0.5\">\n <Pin className=\"h-2.5 w-2.5 text-brand-purple/60\" />\n <span className=\"text-[10px] font-bold uppercase tracking-widest text-brand-purple/60\">Announcement</span>\n </div>\n <p className=\"text-sm font-semibold leading-snug\">{ann.title}</p>\n <p className=\"text-xs text-muted-foreground mt-0.5 truncate\">{ann.body}</p>\n </div>\n <ArrowRight className=\"h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors shrink-0 mt-1\" />\n </Link>\n ))}\n </div>\n );\n}\n\nconst PAGE_SIZE = 30;\n\nexport interface ActivitiesFeedProps {\n getClient: () => MedialaneClient;\n}\n\nexport function ActivitiesFeed({ getClient }: ActivitiesFeedProps) {\n const [page, setPage] = useState(1);\n const [typeFilter, setTypeFilter] = useState(\"\");\n const [allActivities, setAllActivities] = useState<ApiActivity[]>([]);\n const { data: announcements = [] } = useSWR<Announcement[]>(\n \"announcements\",\n fetchAnnouncements,\n { revalidateOnFocus: false }\n );\n\n const prevType = useRef(typeFilter);\n useEffect(() => {\n if (prevType.current !== typeFilter) {\n prevType.current = typeFilter;\n setPage(1);\n setAllActivities([]);\n }\n }, [typeFilter]);\n\n const { activities, meta, isLoading } = useActivities(getClient, {\n limit: PAGE_SIZE,\n page,\n type: (typeFilter as ApiActivitiesQuery[\"type\"]) || undefined,\n });\n\n useEffect(() => {\n if (isLoading) return;\n if (page === 1) {\n setAllActivities(activities);\n } else {\n setAllActivities((prev) => {\n const existing = new Set(\n prev.map((a) => `${a.txHash}-${a.type}-${a.nftTokenId ?? \"\"}`)\n );\n const newItems = activities.filter(\n (a) => !existing.has(`${a.txHash}-${a.type}-${a.nftTokenId ?? \"\"}`)\n );\n return newItems.length > 0 ? [...prev, ...newItems] : prev;\n });\n }\n }, [activities, isLoading, page]);\n\n const pageActors = allActivities\n .map((a) => a.offerer ?? a.fulfiller ?? (a.type === \"mint\" ? a.to : a.from))\n .filter((a): a is string => Boolean(a));\n const { data: actorLevels } = useRewardsBatch(getClient, pageActors);\n\n const isInitialLoading = isLoading && allActivities.length === 0;\n const isLoadingMore = isLoading && allActivities.length > 0;\n const hasMore = meta?.total != null ? allActivities.length < meta.total : false;\n const total = meta?.total ?? null;\n\n return (\n <div className=\"space-y-5\">\n <AnnouncementsBanner announcements={announcements} />\n\n {total != null && (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse\" />\n <span className=\"font-semibold text-foreground tabular-nums\">\n {total.toLocaleString()}\n </span>\n {typeFilter ? `${typeFilter} events` : \"total events\"}\n </span>\n {typeFilter && allActivities.length > 0 && (\n <>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{allActivities.length.toLocaleString()} loaded</span>\n </>\n )}\n </div>\n )}\n\n <div className=\"flex flex-wrap gap-2\">\n {TYPE_FILTERS.map((f) => {\n const typeConfig = f.value ? ACTIVITY_TYPE_CONFIG[f.value] : null;\n const Icon = typeConfig?.icon;\n const isActive = typeFilter === f.value;\n return (\n <button\n key={f.value}\n onClick={() => setTypeFilter(f.value)}\n className={cn(\n \"flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-all font-medium\",\n isActive\n ? \"border-primary bg-primary/10 text-primary shadow-sm\"\n : \"border-border text-muted-foreground hover:border-primary/40 hover:text-foreground hover:bg-muted/50\"\n )}\n >\n {Icon && (\n <Icon\n className={cn(\n \"h-3 w-3\",\n isActive ? typeConfig?.colorClass : \"\"\n )}\n />\n )}\n {f.label}\n </button>\n );\n })}\n </div>\n\n {isInitialLoading ? (\n <div className=\"divide-y divide-border/50 rounded-xl border border-border overflow-hidden\">\n {Array.from({ length: 10 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3 px-4 py-3.5 bg-card\">\n <Skeleton className=\"h-8 w-8 rounded-lg shrink-0\" />\n <Skeleton className=\"h-9 w-9 rounded-md shrink-0\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-3.5 w-40\" />\n <Skeleton className=\"h-2.5 w-24\" />\n </div>\n <div className=\"flex items-center gap-2.5\">\n <div className=\"space-y-1 text-right\">\n <Skeleton className=\"h-4 w-16\" />\n <Skeleton className=\"h-2.5 w-8\" />\n </div>\n <Skeleton className=\"h-2.5 w-12 hidden sm:block\" />\n </div>\n </div>\n ))}\n </div>\n ) : allActivities.length === 0 ? (\n <div className=\"py-20 flex flex-col items-center gap-3 text-center\">\n <div className=\"h-12 w-12 rounded-full bg-muted flex items-center justify-center\">\n <Zap className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n <p className=\"text-muted-foreground\">\n {typeFilter ? `No ${typeFilter} events yet.` : \"No activity yet.\"}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <div className=\"divide-y divide-border/50 rounded-xl border border-border overflow-hidden\">\n {allActivities.map((activity, i) => {\n const actor = activity.offerer ?? activity.fulfiller ?? (activity.type === \"mint\" ? activity.to : activity.from);\n return (\n <ActivityRow\n key={`${activity.txHash}-${activity.type}-${activity.nftTokenId ?? i}`}\n activity={activity}\n showActor\n showExplorer\n actorLevel={actor ? actorLevels?.get(actor) : undefined}\n />\n );\n })}\n </div>\n\n <LoadMoreSentinel\n hasMore={hasMore}\n isLoading={isLoadingMore}\n onLoadMore={() => setPage((p) => p + 1)}\n />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["Link","useSWR"]}
@@ -0,0 +1,9 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MedialaneClient } from '@medialane/sdk/starknet';
3
+
4
+ interface ActivitiesFeedProps {
5
+ getClient: () => MedialaneClient;
6
+ }
7
+ declare function ActivitiesFeed({ getClient }: ActivitiesFeedProps): react_jsx_runtime.JSX.Element;
8
+
9
+ export { ActivitiesFeed, type ActivitiesFeedProps };
@@ -0,0 +1,9 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MedialaneClient } from '@medialane/sdk/starknet';
3
+
4
+ interface ActivitiesFeedProps {
5
+ getClient: () => MedialaneClient;
6
+ }
7
+ declare function ActivitiesFeed({ getClient }: ActivitiesFeedProps): react_jsx_runtime.JSX.Element;
8
+
9
+ export { ActivitiesFeed, type ActivitiesFeedProps };
@@ -0,0 +1,177 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useRef } from "react";
4
+ import useSWR from "swr";
5
+ import Link from "next/link";
6
+ import { useActivities } from "../utils/use-activities.js";
7
+ import { useRewardsBatch } from "../utils/use-rewards.js";
8
+ import { Skeleton } from "./skeleton.js";
9
+ import { LoadMoreSentinel } from "./load-more-sentinel.js";
10
+ import { Zap, Megaphone, ArrowRight, Pin } from "lucide-react";
11
+ import { ACTIVITY_TYPE_CONFIG, TYPE_FILTERS } from "../data/activity.js";
12
+ import { ActivityRow } from "./activity-row.js";
13
+ import { cn } from "../utils/cn.js";
14
+ async function fetchAnnouncements() {
15
+ const res = await fetch("/api/announcements");
16
+ if (!res.ok) return [];
17
+ return res.json();
18
+ }
19
+ function AnnouncementsBanner({ announcements }) {
20
+ const pinned = announcements.filter((a) => a.pinned);
21
+ if (pinned.length === 0) return null;
22
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: pinned.map((ann) => /* @__PURE__ */ jsxs(
23
+ Link,
24
+ {
25
+ href: ann.href,
26
+ className: "flex items-start gap-3 rounded-xl border border-brand-purple/20 bg-brand-purple/5 px-4 py-3 hover:bg-brand-purple/10 transition-colors group",
27
+ children: [
28
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded-lg bg-brand-purple/10 flex items-center justify-center shrink-0 mt-0.5", children: /* @__PURE__ */ jsx(Megaphone, { className: "h-3.5 w-3.5 text-brand-purple" }) }),
29
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
30
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-0.5", children: [
31
+ /* @__PURE__ */ jsx(Pin, { className: "h-2.5 w-2.5 text-brand-purple/60" }),
32
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold uppercase tracking-widest text-brand-purple/60", children: "Announcement" })
33
+ ] }),
34
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold leading-snug", children: ann.title }),
35
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: ann.body })
36
+ ] }),
37
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors shrink-0 mt-1" })
38
+ ]
39
+ },
40
+ ann.id
41
+ )) });
42
+ }
43
+ const PAGE_SIZE = 30;
44
+ function ActivitiesFeed({ getClient }) {
45
+ const [page, setPage] = useState(1);
46
+ const [typeFilter, setTypeFilter] = useState("");
47
+ const [allActivities, setAllActivities] = useState([]);
48
+ const { data: announcements = [] } = useSWR(
49
+ "announcements",
50
+ fetchAnnouncements,
51
+ { revalidateOnFocus: false }
52
+ );
53
+ const prevType = useRef(typeFilter);
54
+ useEffect(() => {
55
+ if (prevType.current !== typeFilter) {
56
+ prevType.current = typeFilter;
57
+ setPage(1);
58
+ setAllActivities([]);
59
+ }
60
+ }, [typeFilter]);
61
+ const { activities, meta, isLoading } = useActivities(getClient, {
62
+ limit: PAGE_SIZE,
63
+ page,
64
+ type: typeFilter || void 0
65
+ });
66
+ useEffect(() => {
67
+ if (isLoading) return;
68
+ if (page === 1) {
69
+ setAllActivities(activities);
70
+ } else {
71
+ setAllActivities((prev) => {
72
+ const existing = new Set(
73
+ prev.map((a) => `${a.txHash}-${a.type}-${a.nftTokenId ?? ""}`)
74
+ );
75
+ const newItems = activities.filter(
76
+ (a) => !existing.has(`${a.txHash}-${a.type}-${a.nftTokenId ?? ""}`)
77
+ );
78
+ return newItems.length > 0 ? [...prev, ...newItems] : prev;
79
+ });
80
+ }
81
+ }, [activities, isLoading, page]);
82
+ const pageActors = allActivities.map((a) => a.offerer ?? a.fulfiller ?? (a.type === "mint" ? a.to : a.from)).filter((a) => Boolean(a));
83
+ const { data: actorLevels } = useRewardsBatch(getClient, pageActors);
84
+ const isInitialLoading = isLoading && allActivities.length === 0;
85
+ const isLoadingMore = isLoading && allActivities.length > 0;
86
+ const hasMore = meta?.total != null ? allActivities.length < meta.total : false;
87
+ const total = meta?.total ?? null;
88
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
89
+ /* @__PURE__ */ jsx(AnnouncementsBanner, { announcements }),
90
+ total != null && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
91
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
92
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" }),
93
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground tabular-nums", children: total.toLocaleString() }),
94
+ typeFilter ? `${typeFilter} events` : "total events"
95
+ ] }),
96
+ typeFilter && allActivities.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
97
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "\xB7" }),
98
+ /* @__PURE__ */ jsxs("span", { children: [
99
+ allActivities.length.toLocaleString(),
100
+ " loaded"
101
+ ] })
102
+ ] })
103
+ ] }),
104
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: TYPE_FILTERS.map((f) => {
105
+ const typeConfig = f.value ? ACTIVITY_TYPE_CONFIG[f.value] : null;
106
+ const Icon = typeConfig?.icon;
107
+ const isActive = typeFilter === f.value;
108
+ return /* @__PURE__ */ jsxs(
109
+ "button",
110
+ {
111
+ onClick: () => setTypeFilter(f.value),
112
+ className: cn(
113
+ "flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-all font-medium",
114
+ isActive ? "border-primary bg-primary/10 text-primary shadow-sm" : "border-border text-muted-foreground hover:border-primary/40 hover:text-foreground hover:bg-muted/50"
115
+ ),
116
+ children: [
117
+ Icon && /* @__PURE__ */ jsx(
118
+ Icon,
119
+ {
120
+ className: cn(
121
+ "h-3 w-3",
122
+ isActive ? typeConfig?.colorClass : ""
123
+ )
124
+ }
125
+ ),
126
+ f.label
127
+ ]
128
+ },
129
+ f.value
130
+ );
131
+ }) }),
132
+ isInitialLoading ? /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/50 rounded-xl border border-border overflow-hidden", children: Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3.5 bg-card", children: [
133
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 rounded-lg shrink-0" }),
134
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9 rounded-md shrink-0" }),
135
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
136
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-40" }),
137
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-24" })
138
+ ] }),
139
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
140
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-right", children: [
141
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16" }),
142
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-8" })
143
+ ] }),
144
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-12 hidden sm:block" })
145
+ ] })
146
+ ] }, i)) }) : allActivities.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "py-20 flex flex-col items-center gap-3 text-center", children: [
147
+ /* @__PURE__ */ jsx("div", { className: "h-12 w-12 rounded-full bg-muted flex items-center justify-center", children: /* @__PURE__ */ jsx(Zap, { className: "h-5 w-5 text-muted-foreground" }) }),
148
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: typeFilter ? `No ${typeFilter} events yet.` : "No activity yet." })
149
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
150
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/50 rounded-xl border border-border overflow-hidden", children: allActivities.map((activity, i) => {
151
+ const actor = activity.offerer ?? activity.fulfiller ?? (activity.type === "mint" ? activity.to : activity.from);
152
+ return /* @__PURE__ */ jsx(
153
+ ActivityRow,
154
+ {
155
+ activity,
156
+ showActor: true,
157
+ showExplorer: true,
158
+ actorLevel: actor ? actorLevels?.get(actor) : void 0
159
+ },
160
+ `${activity.txHash}-${activity.type}-${activity.nftTokenId ?? i}`
161
+ );
162
+ }) }),
163
+ /* @__PURE__ */ jsx(
164
+ LoadMoreSentinel,
165
+ {
166
+ hasMore,
167
+ isLoading: isLoadingMore,
168
+ onLoadMore: () => setPage((p) => p + 1)
169
+ }
170
+ )
171
+ ] })
172
+ ] });
173
+ }
174
+ export {
175
+ ActivitiesFeed
176
+ };
177
+ //# sourceMappingURL=activities-feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/activities-feed.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useEffect, useRef } from \"react\";\nimport useSWR from \"swr\";\nimport Link from \"next/link\";\nimport { useActivities } from \"../utils/use-activities.js\";\nimport { useRewardsBatch } from \"../utils/use-rewards.js\";\nimport type { ApiActivitiesQuery, ApiActivity } from \"@medialane/sdk\";\nimport type { MedialaneClient } from \"@medialane/sdk/starknet\";\nimport { Skeleton } from \"./skeleton.js\";\nimport { LoadMoreSentinel } from \"./load-more-sentinel.js\";\nimport { Zap, Megaphone, ArrowRight, Pin } from \"lucide-react\";\nimport { ACTIVITY_TYPE_CONFIG, TYPE_FILTERS } from \"../data/activity.js\";\nimport { ActivityRow } from \"./activity-row.js\";\nimport { cn } from \"../utils/cn.js\";\nimport type { Announcement } from \"../data/notification.js\";\n\nasync function fetchAnnouncements(): Promise<Announcement[]> {\n const res = await fetch(\"/api/announcements\");\n if (!res.ok) return [];\n return res.json();\n}\n\nfunction AnnouncementsBanner({ announcements }: { announcements: Announcement[] }) {\n const pinned = announcements.filter((a) => a.pinned);\n if (pinned.length === 0) return null;\n return (\n <div className=\"space-y-2\">\n {pinned.map((ann) => (\n <Link\n key={ann.id}\n href={ann.href}\n className=\"flex items-start gap-3 rounded-xl border border-brand-purple/20 bg-brand-purple/5 px-4 py-3 hover:bg-brand-purple/10 transition-colors group\"\n >\n <div className=\"h-8 w-8 rounded-lg bg-brand-purple/10 flex items-center justify-center shrink-0 mt-0.5\">\n <Megaphone className=\"h-3.5 w-3.5 text-brand-purple\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-1.5 mb-0.5\">\n <Pin className=\"h-2.5 w-2.5 text-brand-purple/60\" />\n <span className=\"text-[10px] font-bold uppercase tracking-widest text-brand-purple/60\">Announcement</span>\n </div>\n <p className=\"text-sm font-semibold leading-snug\">{ann.title}</p>\n <p className=\"text-xs text-muted-foreground mt-0.5 truncate\">{ann.body}</p>\n </div>\n <ArrowRight className=\"h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors shrink-0 mt-1\" />\n </Link>\n ))}\n </div>\n );\n}\n\nconst PAGE_SIZE = 30;\n\nexport interface ActivitiesFeedProps {\n getClient: () => MedialaneClient;\n}\n\nexport function ActivitiesFeed({ getClient }: ActivitiesFeedProps) {\n const [page, setPage] = useState(1);\n const [typeFilter, setTypeFilter] = useState(\"\");\n const [allActivities, setAllActivities] = useState<ApiActivity[]>([]);\n const { data: announcements = [] } = useSWR<Announcement[]>(\n \"announcements\",\n fetchAnnouncements,\n { revalidateOnFocus: false }\n );\n\n const prevType = useRef(typeFilter);\n useEffect(() => {\n if (prevType.current !== typeFilter) {\n prevType.current = typeFilter;\n setPage(1);\n setAllActivities([]);\n }\n }, [typeFilter]);\n\n const { activities, meta, isLoading } = useActivities(getClient, {\n limit: PAGE_SIZE,\n page,\n type: (typeFilter as ApiActivitiesQuery[\"type\"]) || undefined,\n });\n\n useEffect(() => {\n if (isLoading) return;\n if (page === 1) {\n setAllActivities(activities);\n } else {\n setAllActivities((prev) => {\n const existing = new Set(\n prev.map((a) => `${a.txHash}-${a.type}-${a.nftTokenId ?? \"\"}`)\n );\n const newItems = activities.filter(\n (a) => !existing.has(`${a.txHash}-${a.type}-${a.nftTokenId ?? \"\"}`)\n );\n return newItems.length > 0 ? [...prev, ...newItems] : prev;\n });\n }\n }, [activities, isLoading, page]);\n\n const pageActors = allActivities\n .map((a) => a.offerer ?? a.fulfiller ?? (a.type === \"mint\" ? a.to : a.from))\n .filter((a): a is string => Boolean(a));\n const { data: actorLevels } = useRewardsBatch(getClient, pageActors);\n\n const isInitialLoading = isLoading && allActivities.length === 0;\n const isLoadingMore = isLoading && allActivities.length > 0;\n const hasMore = meta?.total != null ? allActivities.length < meta.total : false;\n const total = meta?.total ?? null;\n\n return (\n <div className=\"space-y-5\">\n <AnnouncementsBanner announcements={announcements} />\n\n {total != null && (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse\" />\n <span className=\"font-semibold text-foreground tabular-nums\">\n {total.toLocaleString()}\n </span>\n {typeFilter ? `${typeFilter} events` : \"total events\"}\n </span>\n {typeFilter && allActivities.length > 0 && (\n <>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{allActivities.length.toLocaleString()} loaded</span>\n </>\n )}\n </div>\n )}\n\n <div className=\"flex flex-wrap gap-2\">\n {TYPE_FILTERS.map((f) => {\n const typeConfig = f.value ? ACTIVITY_TYPE_CONFIG[f.value] : null;\n const Icon = typeConfig?.icon;\n const isActive = typeFilter === f.value;\n return (\n <button\n key={f.value}\n onClick={() => setTypeFilter(f.value)}\n className={cn(\n \"flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-all font-medium\",\n isActive\n ? \"border-primary bg-primary/10 text-primary shadow-sm\"\n : \"border-border text-muted-foreground hover:border-primary/40 hover:text-foreground hover:bg-muted/50\"\n )}\n >\n {Icon && (\n <Icon\n className={cn(\n \"h-3 w-3\",\n isActive ? typeConfig?.colorClass : \"\"\n )}\n />\n )}\n {f.label}\n </button>\n );\n })}\n </div>\n\n {isInitialLoading ? (\n <div className=\"divide-y divide-border/50 rounded-xl border border-border overflow-hidden\">\n {Array.from({ length: 10 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3 px-4 py-3.5 bg-card\">\n <Skeleton className=\"h-8 w-8 rounded-lg shrink-0\" />\n <Skeleton className=\"h-9 w-9 rounded-md shrink-0\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-3.5 w-40\" />\n <Skeleton className=\"h-2.5 w-24\" />\n </div>\n <div className=\"flex items-center gap-2.5\">\n <div className=\"space-y-1 text-right\">\n <Skeleton className=\"h-4 w-16\" />\n <Skeleton className=\"h-2.5 w-8\" />\n </div>\n <Skeleton className=\"h-2.5 w-12 hidden sm:block\" />\n </div>\n </div>\n ))}\n </div>\n ) : allActivities.length === 0 ? (\n <div className=\"py-20 flex flex-col items-center gap-3 text-center\">\n <div className=\"h-12 w-12 rounded-full bg-muted flex items-center justify-center\">\n <Zap className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n <p className=\"text-muted-foreground\">\n {typeFilter ? `No ${typeFilter} events yet.` : \"No activity yet.\"}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <div className=\"divide-y divide-border/50 rounded-xl border border-border overflow-hidden\">\n {allActivities.map((activity, i) => {\n const actor = activity.offerer ?? activity.fulfiller ?? (activity.type === \"mint\" ? activity.to : activity.from);\n return (\n <ActivityRow\n key={`${activity.txHash}-${activity.type}-${activity.nftTokenId ?? i}`}\n activity={activity}\n showActor\n showExplorer\n actorLevel={actor ? actorLevels?.get(actor) : undefined}\n />\n );\n })}\n </div>\n\n <LoadMoreSentinel\n hasMore={hasMore}\n isLoading={isLoadingMore}\n onLoadMore={() => setPage((p) => p + 1)}\n />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "link" | "default" | "destructive" | "outline" | "ghost" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
6
+ variant?: "ghost" | "link" | "default" | "destructive" | "outline" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
7
7
  size?: "icon" | "default" | "sm" | "lg" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "link" | "default" | "destructive" | "outline" | "ghost" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
6
+ variant?: "ghost" | "link" | "default" | "destructive" | "outline" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
7
7
  size?: "icon" | "default" | "sm" | "lg" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { MediaKind } from './types.cjs';
3
+ import 'react';
3
4
  import 'starknet';
4
5
  import '@medialane/sdk';
5
6
  import '@medialane/sdk/starknet';
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { MediaKind } from './types.js';
3
+ import 'react';
3
4
  import 'starknet';
4
5
  import '@medialane/sdk';
5
6
  import '@medialane/sdk/starknet';
@@ -202,7 +202,8 @@ function FastMint(props) {
202
202
  getUploadToken,
203
203
  getSigner,
204
204
  client,
205
- provider
205
+ provider,
206
+ successFooter
206
207
  } = props;
207
208
  const [status, setStatus] = (0, import_react.useState)("idle");
208
209
  const [errorMsg, setErrorMsg] = (0, import_react.useState)(null);
@@ -516,6 +517,7 @@ function FastMint(props) {
516
517
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
517
518
  import_success_view.SuccessView,
518
519
  {
520
+ footer: successFooter,
519
521
  presentation,
520
522
  previewImage: mediaKind === "image" ? mediaPreview : featurePreview,
521
523
  name,