@medialane/ui 0.163.0 → 0.165.0

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.
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCY;AAjCZ,mBAA4C;AAC5C,iBAAmB;AACnB,kBAAiB;AACjB,4BAA8B;AAC9B,yBAAgC;AAGhC,sBAAyB;AACzB,gCAAiC;AACjC,0BAAgD;AAChD,sBAAmD;AACnD,0BAA4B;AAC5B,gBAAmB;AAGnB,eAAe,qBAA8C;AAC3D,QAAM,MAAM,MAAM,MAAM,oBAAoB;AAC5C,MAAI,CAAC,IAAI,GAAI,QAAO,CAAC;AACrB,SAAO,IAAI,KAAK;AAClB;AAEA,SAAS,oBAAoB,EAAE,cAAc,GAAsC;AACjF,QAAM,SAAS,cAAc,OAAO,CAAC,MAAM,EAAE,MAAM;AACnD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SACE,4CAAC,SAAI,WAAU,aACZ,iBAAO,IAAI,CAAC,QACX;AAAA,IAAC,YAAAA;AAAA,IAAA;AAAA,MAEC,MAAM,IAAI;AAAA,MACV,WAAU;AAAA,MAEV;AAAA,oDAAC,SAAI,WAAU,0FACb,sDAAC,iCAAU,WAAU,iCAAgC,GACvD;AAAA,QACA,6CAAC,SAAI,WAAU,kBACb;AAAA,uDAAC,SAAI,WAAU,oCACb;AAAA,wDAAC,2BAAI,WAAU,oCAAmC;AAAA,YAClD,4CAAC,UAAK,WAAU,wEAAuE,0BAAY;AAAA,aACrG;AAAA,UACA,4CAAC,OAAE,WAAU,sCAAsC,cAAI,OAAM;AAAA,UAC7D,4CAAC,OAAE,WAAU,iDAAiD,cAAI,MAAK;AAAA,WACzE;AAAA,QACA,4CAAC,kCAAW,WAAU,6FAA4F;AAAA;AAAA;AAAA,IAf7G,IAAI;AAAA,EAgBX,CACD,GACH;AAEJ;AAEA,MAAM,YAAY;AAMX,SAAS,eAAe,EAAE,UAAU,GAAwB;AACjE,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAS,CAAC;AAClC,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAS,EAAE;AAC/C,QAAM,CAAC,eAAe,gBAAgB,QAAI,uBAAwB,CAAC,CAAC;AACpE,QAAM,EAAE,MAAM,gBAAgB,CAAC,EAAE,QAAI,WAAAC;AAAA,IACnC;AAAA,IACA;AAAA,IACA,EAAE,mBAAmB,MAAM;AAAA,EAC7B;AAEA,QAAM,eAAW,qBAAO,UAAU;AAClC,8BAAU,MAAM;AACd,QAAI,SAAS,YAAY,YAAY;AACnC,eAAS,UAAU;AACnB,cAAQ,CAAC;AACT,uBAAiB,CAAC,CAAC;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,EAAE,YAAY,MAAM,UAAU,QAAI,qCAAc,WAAW;AAAA,IAC/D,OAAO;AAAA,IACP;AAAA,IACA,MAAO,cAA6C;AAAA,EACtD,CAAC;AAED,8BAAU,MAAM;AACd,QAAI,UAAW;AACf,QAAI,SAAS,GAAG;AACd,uBAAiB,UAAU;AAAA,IAC7B,OAAO;AACL,uBAAiB,CAAC,SAAS;AACzB,cAAM,WAAW,IAAI;AAAA,UACnB,KAAK,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,IAAI,IAAI,EAAE,cAAc,EAAE,EAAE;AAAA,QAC/D;AACA,cAAM,WAAW,WAAW;AAAA,UAC1B,CAAC,MAAM,CAAC,SAAS,IAAI,GAAG,EAAE,MAAM,IAAI,EAAE,IAAI,IAAI,EAAE,cAAc,EAAE,EAAE;AAAA,QACpE;AACA,eAAO,SAAS,SAAS,IAAI,CAAC,GAAG,MAAM,GAAG,QAAQ,IAAI;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,YAAY,WAAW,IAAI,CAAC;AAEhC,QAAM,aAAa,cAChB,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,SAAS,EAAE,KAAK,EAAE,KAAK,EAC1E,OAAO,CAAC,MAAmB,QAAQ,CAAC,CAAC;AACxC,QAAM,EAAE,MAAM,YAAY,QAAI,oCAAgB,WAAW,UAAU;AAEnE,QAAM,mBAAmB,aAAa,cAAc,WAAW;AAC/D,QAAM,gBAAgB,aAAa,cAAc,SAAS;AAC1D,QAAM,UAAU,MAAM,SAAS,OAAO,cAAc,SAAS,KAAK,QAAQ;AAC1E,QAAM,QAAQ,MAAM,SAAS;AAE7B,SACE,6CAAC,SAAI,WAAU,aACb;AAAA,gDAAC,uBAAoB,eAA8B;AAAA,IAElD,SAAS,QACR,6CAAC,SAAI,WAAU,yDACb;AAAA,mDAAC,UAAK,WAAU,6BACd;AAAA,oDAAC,UAAK,WAAU,yDAAwD;AAAA,QACxE,4CAAC,UAAK,WAAU,8CACb,gBAAM,eAAe,GACxB;AAAA,QACC,aAAa,GAAG,UAAU,YAAY;AAAA,SACzC;AAAA,MACC,cAAc,cAAc,SAAS,KACpC,4EACE;AAAA,oDAAC,UAAK,WAAU,4BAA2B,kBAAC;AAAA,QAC5C,6CAAC,UAAM;AAAA,wBAAc,OAAO,eAAe;AAAA,UAAE;AAAA,WAAO;AAAA,SACtD;AAAA,OAEJ;AAAA,IAGF,4CAAC,SAAI,WAAU,wBACZ,uCAAa,IAAI,CAAC,MAAM;AACvB,YAAM,aAAa,EAAE,QAAQ,qCAAqB,EAAE,KAAK,IAAI;AAC7D,YAAM,OAAO,YAAY;AACzB,YAAM,WAAW,eAAe,EAAE;AAClC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,SAAS,MAAM,cAAc,EAAE,KAAK;AAAA,UACpC,eAAW;AAAA,YACT;AAAA,YACA,WACI,wDACA;AAAA,UACN;AAAA,UAEC;AAAA,oBACC;AAAA,cAAC;AAAA;AAAA,gBACC,eAAW;AAAA,kBACT;AAAA,kBACA,WAAW,YAAY,aAAa;AAAA,gBACtC;AAAA;AAAA,YACF;AAAA,YAED,EAAE;AAAA;AAAA;AAAA,QAjBE,EAAE;AAAA,MAkBT;AAAA,IAEJ,CAAC,GACH;AAAA,IAEC,mBACC,4CAAC,SAAI,WAAU,6EACZ,gBAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,MAClC,6CAAC,SAAY,WAAU,+CACrB;AAAA,kDAAC,4BAAS,WAAU,+BAA8B;AAAA,MAClD,4CAAC,4BAAS,WAAU,+BAA8B;AAAA,MAClD,6CAAC,SAAI,WAAU,oBACb;AAAA,oDAAC,4BAAS,WAAU,cAAa;AAAA,QACjC,4CAAC,4BAAS,WAAU,cAAa;AAAA,SACnC;AAAA,MACA,6CAAC,SAAI,WAAU,6BACb;AAAA,qDAAC,SAAI,WAAU,wBACb;AAAA,sDAAC,4BAAS,WAAU,YAAW;AAAA,UAC/B,4CAAC,4BAAS,WAAU,aAAY;AAAA,WAClC;AAAA,QACA,4CAAC,4BAAS,WAAU,8BAA6B;AAAA,SACnD;AAAA,SAbQ,CAcV,CACD,GACH,IACE,cAAc,WAAW,IAC3B,6CAAC,SAAI,WAAU,sDACb;AAAA,kDAAC,SAAI,WAAU,oEACb,sDAAC,2BAAI,WAAU,iCAAgC,GACjD;AAAA,MACA,4CAAC,OAAE,WAAU,yBACV,uBAAa,MAAM,UAAU,iBAAiB,oBACjD;AAAA,OACF,IAEA,6CAAC,SAAI,WAAU,aACb;AAAA,kDAAC,SAAI,WAAU,6EACZ,wBAAc,IAAI,CAAC,UAAU,MAAM;AAClC,cAAM,QAAQ,SAAS,WAAW,SAAS,cAAc,SAAS,SAAS,SAAS,SAAS,KAAK,SAAS;AAC3G,eACE;AAAA,UAAC;AAAA;AAAA,YAEC;AAAA,YACA,WAAS;AAAA,YACT,cAAY;AAAA,YACZ,YAAY,QAAQ,aAAa,IAAI,KAAK,IAAI;AAAA;AAAA,UAJzC,GAAG,SAAS,MAAM,IAAI,SAAS,IAAI,IAAI,SAAS,cAAc,CAAC;AAAA,QAKtE;AAAA,MAEJ,CAAC,GACH;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,WAAW;AAAA,UACX,YAAY,MAAM,QAAQ,CAAC,MAAM,IAAI,CAAC;AAAA;AAAA,MACxC;AAAA,OACF;AAAA,KAEJ;AAEJ;","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?: "ghost" | "link" | "default" | "destructive" | "outline" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
6
+ variant?: "link" | "default" | "destructive" | "outline" | "ghost" | "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?: "ghost" | "link" | "default" | "destructive" | "outline" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
6
+ variant?: "link" | "default" | "destructive" | "outline" | "ghost" | "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> {
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AI_POLICIES: () => import_ip.AI_POLICIES,
24
24
  ActionButton: () => import_action_button.ActionButton,
25
25
  ActionDialog: () => import_action_dialog.ActionDialog,
26
+ ActivitiesFeed: () => import_activities_feed.ActivitiesFeed,
26
27
  ActivityCard: () => import_activity_card.ActivityCard,
27
28
  ActivityCardSkeleton: () => import_activity_card.ActivityCardSkeleton,
28
29
  ActivityFeedShell: () => import_activity_feed_shell.ActivityFeedShell,
@@ -153,6 +154,7 @@ __export(index_exports, {
153
154
  FormMessage: () => import_form.FormMessage,
154
155
  GATED_CONTENT_TYPES: () => import_gated_content_types.GATED_CONTENT_TYPES,
155
156
  GATED_CONTENT_TYPE_FALLBACK: () => import_gated_content_types.GATED_CONTENT_TYPE_FALLBACK,
157
+ GENERIC: () => import_describe_error.GENERIC,
156
158
  GEOGRAPHIC_SCOPES: () => import_ip.GEOGRAPHIC_SCOPES,
157
159
  GradientButton: () => import_gradient_button.GradientButton,
158
160
  HelpIcon: () => import_help_icon.HelpIcon,
@@ -204,9 +206,11 @@ __export(index_exports, {
204
206
  MedialaneCollectionCard: () => import_medialane_collection_card.MedialaneCollectionCard,
205
207
  MedialaneLogoFull: () => import_brand_logo.MedialaneLogoFull,
206
208
  MotionCard: () => import_motion_primitives.MotionCard,
209
+ NETWORK_BUSY: () => import_describe_error.NETWORK_BUSY,
207
210
  NOTIFICATION_COLOR: () => import_notification_meta.NOTIFICATION_COLOR,
208
211
  NOTIFICATION_ICON: () => import_notification_meta.NOTIFICATION_ICON,
209
212
  NOTIFICATION_LABEL: () => import_notification_meta.NOTIFICATION_LABEL,
213
+ NOT_SUBMITTED: () => import_describe_error.NOT_SUBMITTED,
210
214
  NavAccountSheet: () => import_nav_shell.NavAccountSheet,
211
215
  NavBrandButton: () => import_nav_shell.NavBrandButton,
212
216
  NavCommandMenu: () => import_nav_command_menu.NavCommandMenu,
@@ -285,6 +289,7 @@ __export(index_exports, {
285
289
  TokenCard: () => import_token_card.TokenCard,
286
290
  TokenCardSkeleton: () => import_token_card.TokenCardSkeleton,
287
291
  TokenGlyph: () => import_token_glyph.TokenGlyph,
292
+ UserFacingError: () => import_describe_error.UserFacingError,
288
293
  WrongNetworkError: () => import_wallet_error.WrongNetworkError,
289
294
  XpEarned: () => import_xp_earned.XpEarned,
290
295
  XpProgress: () => import_xp_progress.XpProgress,
@@ -302,6 +307,7 @@ __export(index_exports, {
302
307
  coinSupply: () => import_coins.coinSupply,
303
308
  createRewardEarned: () => import_reward_earned.createRewardEarned,
304
309
  derivePortfolioCounts: () => import_portfolio_counts.derivePortfolioCounts,
310
+ describeError: () => import_describe_error.describeError,
305
311
  dropCreateSchema: () => import_drop_create_schema.dropCreateSchema,
306
312
  fdvUsd: () => import_coins.fdvUsd,
307
313
  feeLabelFor: () => import_self_fund_consent_dialog.feeLabelFor,
@@ -318,7 +324,6 @@ __export(index_exports, {
318
324
  getDefaultClaimWindow: () => import_launchpad_defaults.getDefaultClaimWindow,
319
325
  getDefaultDropSchedule: () => import_launchpad_defaults.getDefaultDropSchedule,
320
326
  getDropStatus: () => import_drop_status.getDropStatus,
321
- getFriendlyWalletError: () => import_wallet_error.getFriendlyWalletError,
322
327
  getReadIds: () => import_notification_storage.getReadIds,
323
328
  ipfsToHttp: () => import_ipfs.ipfsToHttp,
324
329
  isBareExecuteFailure: () => import_wallet_error.isBareExecuteFailure,
@@ -400,6 +405,7 @@ var import_use_intersection_active = require("./utils/use-intersection-active.js
400
405
  var import_notification_storage = require("./utils/notification-storage.js");
401
406
  var import_license_summary = require("./utils/license-summary.js");
402
407
  var import_wallet_error = require("./utils/wallet-error.js");
408
+ var import_describe_error = require("./utils/describe-error.js");
403
409
  var import_ip_types = require("./data/ip-types.js");
404
410
  var import_ip = require("./data/ip.js");
405
411
  var import_ip_templates = require("./data/ip-templates.js");
@@ -495,6 +501,7 @@ var import_badge_catalog = require("./components/rewards/badge-catalog.js");
495
501
  var import_xp_earned = require("./components/rewards/xp-earned.js");
496
502
  var import_reward_earned = require("./components/rewards/reward-earned.js");
497
503
  var import_load_more_sentinel = require("./components/load-more-sentinel.js");
504
+ var import_activities_feed = require("./components/activities-feed.js");
498
505
  var import_rewards_section = require("./components/rewards-section.js");
499
506
  var import_action_button = require("./components/action-button.js");
500
507
  var import_gradient_button = require("./components/gradient-button.js");
@@ -572,6 +579,7 @@ var import_export_key_section = require("./components/wallet/export-key-section.
572
579
  AI_POLICIES,
573
580
  ActionButton,
574
581
  ActionDialog,
582
+ ActivitiesFeed,
575
583
  ActivityCard,
576
584
  ActivityCardSkeleton,
577
585
  ActivityFeedShell,
@@ -702,6 +710,7 @@ var import_export_key_section = require("./components/wallet/export-key-section.
702
710
  FormMessage,
703
711
  GATED_CONTENT_TYPES,
704
712
  GATED_CONTENT_TYPE_FALLBACK,
713
+ GENERIC,
705
714
  GEOGRAPHIC_SCOPES,
706
715
  GradientButton,
707
716
  HelpIcon,
@@ -753,9 +762,11 @@ var import_export_key_section = require("./components/wallet/export-key-section.
753
762
  MedialaneCollectionCard,
754
763
  MedialaneLogoFull,
755
764
  MotionCard,
765
+ NETWORK_BUSY,
756
766
  NOTIFICATION_COLOR,
757
767
  NOTIFICATION_ICON,
758
768
  NOTIFICATION_LABEL,
769
+ NOT_SUBMITTED,
759
770
  NavAccountSheet,
760
771
  NavBrandButton,
761
772
  NavCommandMenu,
@@ -834,6 +845,7 @@ var import_export_key_section = require("./components/wallet/export-key-section.
834
845
  TokenCard,
835
846
  TokenCardSkeleton,
836
847
  TokenGlyph,
848
+ UserFacingError,
837
849
  WrongNetworkError,
838
850
  XpEarned,
839
851
  XpProgress,
@@ -851,6 +863,7 @@ var import_export_key_section = require("./components/wallet/export-key-section.
851
863
  coinSupply,
852
864
  createRewardEarned,
853
865
  derivePortfolioCounts,
866
+ describeError,
854
867
  dropCreateSchema,
855
868
  fdvUsd,
856
869
  feeLabelFor,
@@ -867,7 +880,6 @@ var import_export_key_section = require("./components/wallet/export-key-section.
867
880
  getDefaultClaimWindow,
868
881
  getDefaultDropSchedule,
869
882
  getDropStatus,
870
- getFriendlyWalletError,
871
883
  getReadIds,
872
884
  ipfsToHttp,
873
885
  isBareExecuteFailure,