@archiva/archiva-nextjs 0.1.8 → 0.1.9

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.
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
2
3
 
3
4
  type TimelineItem = {
4
5
  id: string | number;
@@ -8,6 +9,16 @@ type TimelineItem = {
8
9
  badge?: string | number;
9
10
  data?: unknown;
10
11
  className?: string;
12
+ userName?: string;
13
+ userHandle?: string;
14
+ entityType?: string;
15
+ actorDisplay?: string;
16
+ avatar?: string;
17
+ avatarFallback?: string;
18
+ icon?: React.ComponentType<{
19
+ className?: string;
20
+ }>;
21
+ statusIndicator?: boolean;
11
22
  };
12
23
  type TimelineProps = {
13
24
  entityId?: string;
@@ -16,8 +27,13 @@ type TimelineProps = {
16
27
  initialLimit?: number;
17
28
  className?: string;
18
29
  emptyMessage?: string;
30
+ showSearch?: boolean;
31
+ showFilters?: boolean;
32
+ getActorAvatar?: (actorId: string) => string | React.ComponentType<{
33
+ className?: string;
34
+ }> | undefined;
19
35
  };
20
- declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, }: TimelineProps): react_jsx_runtime.JSX.Element;
36
+ declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, showSearch, showFilters, getActorAvatar, }: TimelineProps): react_jsx_runtime.JSX.Element;
21
37
 
22
38
  type ArchivaContextValue = {
23
39
  apiBaseUrl: string;
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
2
3
 
3
4
  type TimelineItem = {
4
5
  id: string | number;
@@ -8,6 +9,16 @@ type TimelineItem = {
8
9
  badge?: string | number;
9
10
  data?: unknown;
10
11
  className?: string;
12
+ userName?: string;
13
+ userHandle?: string;
14
+ entityType?: string;
15
+ actorDisplay?: string;
16
+ avatar?: string;
17
+ avatarFallback?: string;
18
+ icon?: React.ComponentType<{
19
+ className?: string;
20
+ }>;
21
+ statusIndicator?: boolean;
11
22
  };
12
23
  type TimelineProps = {
13
24
  entityId?: string;
@@ -16,8 +27,13 @@ type TimelineProps = {
16
27
  initialLimit?: number;
17
28
  className?: string;
18
29
  emptyMessage?: string;
30
+ showSearch?: boolean;
31
+ showFilters?: boolean;
32
+ getActorAvatar?: (actorId: string) => string | React.ComponentType<{
33
+ className?: string;
34
+ }> | undefined;
19
35
  };
20
- declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, }: TimelineProps): react_jsx_runtime.JSX.Element;
36
+ declare function Timeline({ entityId, actorId, entityType, initialLimit, className, emptyMessage, showSearch, showFilters, getActorAvatar, }: TimelineProps): react_jsx_runtime.JSX.Element;
21
37
 
22
38
  type ArchivaContextValue = {
23
39
  apiBaseUrl: string;
@@ -58,8 +58,18 @@ function useArchiva() {
58
58
 
59
59
  // src/react/Timeline.tsx
60
60
  var import_jsx_runtime2 = require("react/jsx-runtime");
61
- function formatTimestamp(timestamp) {
61
+ function formatTimestamp(timestamp, format = "default") {
62
62
  const date = typeof timestamp === "string" ? new Date(timestamp) : timestamp;
63
+ if (format === "activity") {
64
+ const month = date.toLocaleDateString("en-US", { month: "short" });
65
+ const day = date.getDate();
66
+ const time = date.toLocaleTimeString("en-US", {
67
+ hour: "numeric",
68
+ minute: "2-digit",
69
+ hour12: true
70
+ });
71
+ return `${month} ${day} at ${time}`;
72
+ }
63
73
  return date.toLocaleDateString(void 0, {
64
74
  month: "short",
65
75
  day: "numeric",
@@ -68,13 +78,36 @@ function formatTimestamp(timestamp) {
68
78
  minute: "2-digit"
69
79
  });
70
80
  }
71
- function eventToTimelineItem(event) {
81
+ function eventToTimelineItem(event, getActorAvatar) {
82
+ const actorId = event.actorId || "unknown";
83
+ const userName = actorId.includes(":") ? actorId.split(":")[1] || actorId : actorId;
84
+ const userHandle = userName;
85
+ const initials = userName.charAt(0).toUpperCase();
86
+ const actorAvatarOrIcon = getActorAvatar ? getActorAvatar(actorId) : void 0;
87
+ let actorAvatar = void 0;
88
+ let actorIcon = void 0;
89
+ if (typeof actorAvatarOrIcon === "string") {
90
+ actorAvatar = actorAvatarOrIcon;
91
+ } else if (actorAvatarOrIcon) {
92
+ actorIcon = actorAvatarOrIcon;
93
+ }
94
+ if (!actorAvatar && !actorIcon) {
95
+ actorAvatar = "https://www.gravatar.com/avatar?d=mp";
96
+ }
97
+ const action = event.action.charAt(0).toUpperCase() + event.action.slice(1);
72
98
  return {
73
99
  id: event.id,
74
- title: event.action,
75
- description: `${event.entityType} ${event.entityId}`,
100
+ title: "",
101
+ // Not used in activity layout
102
+ description: action,
76
103
  timestamp: event.receivedAt,
77
- badge: event.actorId ?? void 0,
104
+ userName: userName.charAt(0).toUpperCase() + userName.slice(1),
105
+ userHandle,
106
+ entityType: event.entityType.toLowerCase(),
107
+ avatar: actorAvatar,
108
+ icon: actorIcon,
109
+ avatarFallback: initials,
110
+ statusIndicator: false,
78
111
  data: event
79
112
  };
80
113
  }
@@ -121,20 +154,129 @@ async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, par
121
154
  nextCursor: typeof payload.nextCursor === "string" ? payload.nextCursor : void 0
122
155
  };
123
156
  }
157
+ function SimpleSearchInput({
158
+ value,
159
+ onChange,
160
+ onClear,
161
+ placeholder = "Search..."
162
+ }) {
163
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", width: "100%" }, children: [
164
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
165
+ "input",
166
+ {
167
+ type: "text",
168
+ value,
169
+ onChange: (e) => onChange(e.target.value),
170
+ placeholder,
171
+ style: {
172
+ width: "100%",
173
+ padding: "0.5rem 2.5rem 0.5rem 0.75rem",
174
+ border: "1px solid #e5e7eb",
175
+ borderRadius: "0.375rem",
176
+ fontSize: "0.875rem"
177
+ }
178
+ }
179
+ ),
180
+ value && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
181
+ "button",
182
+ {
183
+ type: "button",
184
+ onClick: onClear,
185
+ style: {
186
+ position: "absolute",
187
+ right: "0.5rem",
188
+ top: "50%",
189
+ transform: "translateY(-50%)",
190
+ background: "none",
191
+ border: "none",
192
+ cursor: "pointer",
193
+ padding: "0.25rem",
194
+ display: "flex",
195
+ alignItems: "center"
196
+ },
197
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "1rem" }, children: "\xD7" })
198
+ }
199
+ )
200
+ ] });
201
+ }
202
+ function SimpleAvatar({
203
+ src,
204
+ fallback,
205
+ icon: Icon,
206
+ size = 40
207
+ }) {
208
+ if (Icon) {
209
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
210
+ "div",
211
+ {
212
+ style: {
213
+ width: `${size}px`,
214
+ height: `${size}px`,
215
+ borderRadius: "50%",
216
+ backgroundColor: "#f3f4f6",
217
+ border: "2px solid #fff",
218
+ display: "flex",
219
+ alignItems: "center",
220
+ justifyContent: "center",
221
+ flexShrink: 0
222
+ },
223
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { className: "" })
224
+ }
225
+ );
226
+ }
227
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
228
+ "div",
229
+ {
230
+ style: {
231
+ width: `${size}px`,
232
+ height: `${size}px`,
233
+ borderRadius: "50%",
234
+ backgroundColor: "#f3f4f6",
235
+ border: "2px solid #fff",
236
+ display: "flex",
237
+ alignItems: "center",
238
+ justifyContent: "center",
239
+ flexShrink: 0,
240
+ overflow: "hidden"
241
+ },
242
+ children: src ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
243
+ "img",
244
+ {
245
+ src,
246
+ alt: fallback || "",
247
+ style: { width: "100%", height: "100%", objectFit: "cover" }
248
+ }
249
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: `${size * 0.4}px`, fontWeight: 500 }, children: fallback || "?" })
250
+ }
251
+ );
252
+ }
253
+ function applyClientSideFilters(events, searchQuery) {
254
+ if (!searchQuery.trim()) {
255
+ return events;
256
+ }
257
+ const query = searchQuery.toLowerCase();
258
+ return events.filter((event) => {
259
+ return event.action.toLowerCase().includes(query) || event.entityType.toLowerCase().includes(query) || event.entityId.toLowerCase().includes(query) || event.actorId && event.actorId.toLowerCase().includes(query) || event.source && event.source.toLowerCase().includes(query);
260
+ });
261
+ }
124
262
  function Timeline({
125
263
  entityId,
126
264
  actorId,
127
265
  entityType,
128
- initialLimit = 25,
266
+ initialLimit = 100,
129
267
  className,
130
- emptyMessage = "No events yet."
268
+ emptyMessage = "No events yet.",
269
+ showSearch = false,
270
+ showFilters = false,
271
+ getActorAvatar
131
272
  }) {
132
273
  const { apiBaseUrl, getToken, forceRefreshToken } = useArchiva();
133
- const [items, setItems] = React2.useState([]);
274
+ const [allEvents, setAllEvents] = React2.useState([]);
134
275
  const [cursor, setCursor] = React2.useState(void 0);
135
276
  const [loading, setLoading] = React2.useState(false);
136
277
  const [error, setError] = React2.useState(null);
137
278
  const [hasMore, setHasMore] = React2.useState(false);
279
+ const [searchQuery, setSearchQuery] = React2.useState("");
138
280
  const load = React2.useCallback(
139
281
  async (options) => {
140
282
  setLoading(true);
@@ -153,8 +295,7 @@ function Timeline({
153
295
  forceRefreshToken,
154
296
  params
155
297
  );
156
- const newItems = response.items.map(eventToTimelineItem);
157
- setItems((prev) => options?.reset ? newItems : [...prev, ...newItems]);
298
+ setAllEvents((prev) => options?.reset ? response.items : [...prev, ...response.items]);
158
299
  setCursor(response.nextCursor);
159
300
  setHasMore(Boolean(response.nextCursor));
160
301
  } catch (err) {
@@ -167,98 +308,118 @@ function Timeline({
167
308
  );
168
309
  React2.useEffect(() => {
169
310
  setCursor(void 0);
170
- setItems([]);
311
+ setAllEvents([]);
171
312
  void load({ reset: true });
172
313
  }, [entityId, actorId, entityType]);
173
- if (loading && items.length === 0) {
174
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: "Loading events..." }) });
314
+ const filteredEvents = React2.useMemo(() => {
315
+ return applyClientSideFilters(allEvents, searchQuery);
316
+ }, [allEvents, searchQuery]);
317
+ const timelineItems = React2.useMemo(() => {
318
+ return filteredEvents.slice(0, 10).map(
319
+ (event) => eventToTimelineItem(event, getActorAvatar)
320
+ );
321
+ }, [filteredEvents, getActorAvatar]);
322
+ if (loading && allEvents.length === 0) {
323
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", style: { width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "3rem", textAlign: "center", color: "#6b7280" }, children: "Loading events..." }) });
175
324
  }
176
325
  if (error) {
177
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: "red" }, children: [
326
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", style: { width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "1rem", color: "#dc2626", backgroundColor: "#fef2f2", borderRadius: "0.375rem" }, children: [
178
327
  "Error: ",
179
328
  error
180
329
  ] }) });
181
330
  }
182
- if (items.length === 0) {
183
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: className || "", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: emptyMessage }) });
184
- }
185
331
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: className || "", style: { width: "100%" }, children: [
186
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { position: "relative" }, children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
187
- "div",
332
+ showSearch && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginBottom: "1rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
333
+ SimpleSearchInput,
188
334
  {
189
- style: {
190
- position: "relative",
191
- display: "flex",
192
- gap: "1rem",
193
- paddingBottom: index < items.length - 1 ? "2rem" : "0"
194
- },
195
- children: [
196
- index < items.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
197
- "div",
198
- {
199
- style: {
200
- position: "absolute",
201
- left: "1.25rem",
202
- top: "3rem",
203
- height: "calc(100% - 3rem)",
204
- width: "2px",
205
- backgroundColor: "#e5e7eb"
335
+ value: searchQuery,
336
+ onChange: setSearchQuery,
337
+ onClear: () => setSearchQuery(""),
338
+ placeholder: "Search actions, entities, IDs, actors, sources..."
339
+ }
340
+ ) }),
341
+ searchQuery && filteredEvents.length !== allEvents.length && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", color: "#6b7280" }, children: [
342
+ "Showing ",
343
+ filteredEvents.length,
344
+ " of ",
345
+ allEvents.length,
346
+ " events"
347
+ ] }),
348
+ timelineItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "3rem", textAlign: "center", color: "#6b7280" }, children: searchQuery ? "No events match your search." : emptyMessage }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { position: "relative", width: "100%" }, children: timelineItems.map((item, index) => {
349
+ const useActivityLayout = !!(item.actorDisplay || item.userName || item.userHandle);
350
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
351
+ "div",
352
+ {
353
+ style: {
354
+ position: "relative",
355
+ display: "flex",
356
+ alignItems: "start",
357
+ gap: "1rem",
358
+ paddingBottom: index < timelineItems.length - 1 ? "2rem" : "0"
359
+ },
360
+ children: [
361
+ index < timelineItems.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
362
+ "div",
363
+ {
364
+ style: {
365
+ position: "absolute",
366
+ left: "1.25rem",
367
+ top: useActivityLayout ? "2.5rem" : "3rem",
368
+ height: `calc(100% - ${useActivityLayout ? "2.5rem" : "3rem"})`,
369
+ width: "2px",
370
+ backgroundColor: "#e5e7eb"
371
+ }
206
372
  }
207
- }
208
- ),
209
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
210
- "div",
211
- {
212
- style: {
213
- position: "relative",
214
- zIndex: 10,
215
- width: "2.5rem",
216
- height: "2.5rem",
217
- borderRadius: "50%",
218
- backgroundColor: "#f3f4f6",
219
- border: "2px solid #fff",
220
- display: "flex",
221
- alignItems: "center",
222
- justifyContent: "center",
223
- flexShrink: 0
224
- },
225
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
226
- "div",
227
- {
228
- style: {
229
- width: "0.75rem",
230
- height: "0.75rem",
231
- borderRadius: "50%",
232
- backgroundColor: "#6b7280"
373
+ ),
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { position: "relative", zIndex: 10, flexShrink: 0, paddingTop: "0.125rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
375
+ SimpleAvatar,
376
+ {
377
+ src: item.avatar,
378
+ fallback: item.avatarFallback,
379
+ icon: item.icon,
380
+ size: 40
381
+ }
382
+ ) }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { flex: 1, minWidth: 0 }, children: useActivityLayout ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "1rem" }, children: [
384
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
385
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
386
+ item.actorDisplay && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontWeight: 600, fontSize: "0.875rem" }, children: item.actorDisplay }),
387
+ item.userHandle && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { fontSize: "0.75rem", color: "#6b7280" }, children: [
388
+ "@",
389
+ item.userHandle.toLowerCase()
390
+ ] })
391
+ ] }),
392
+ item.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: "#111827", margin: "0.5rem 0 0 0" }, children: item.description })
393
+ ] }),
394
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flexShrink: 0, display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "0.25rem" }, children: [
395
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "0.75rem", fontFamily: "monospace", color: "#6b7280", whiteSpace: "nowrap" }, children: formatTimestamp(item.timestamp, "activity") }),
396
+ item.entityType && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "0.75rem", color: "#6b7280", whiteSpace: "nowrap" }, children: item.entityType })
397
+ ] })
398
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "0.5rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
399
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { style: { fontWeight: 600, margin: 0, fontSize: "0.875rem" }, children: item.title || item.description }),
401
+ item.badge && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
402
+ "span",
403
+ {
404
+ style: {
405
+ fontSize: "0.75rem",
406
+ padding: "0.125rem 0.5rem",
407
+ borderRadius: "0.375rem",
408
+ backgroundColor: "#f3f4f6"
409
+ },
410
+ children: item.badge
233
411
  }
234
- }
235
- )
236
- }
237
- ),
238
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { flex: 1, paddingBottom: "2rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "0.5rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
239
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
240
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h4", { style: { fontWeight: 600, margin: 0 }, children: item.title }),
241
- item.badge && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
242
- "span",
243
- {
244
- style: {
245
- fontSize: "0.75rem",
246
- padding: "0.125rem 0.5rem",
247
- borderRadius: "0.375rem",
248
- backgroundColor: "#f3f4f6"
249
- },
250
- children: item.badge
251
- }
252
- )
253
- ] }),
254
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0 0" }, children: formatTimestamp(item.timestamp) }),
255
- item.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.5rem 0 0 0" }, children: item.description })
256
- ] }) }) })
257
- ]
258
- },
259
- item.id
260
- )) }),
261
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "1rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
412
+ )
413
+ ] }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0 0" }, children: formatTimestamp(item.timestamp) }),
415
+ item.description && item.title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.5rem 0 0 0" }, children: item.description })
416
+ ] }) }) })
417
+ ]
418
+ },
419
+ item.id
420
+ );
421
+ }) }),
422
+ hasMore && !searchQuery && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "1rem", textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
262
423
  "button",
263
424
  {
264
425
  type: "button",
@@ -270,7 +431,8 @@ function Timeline({
270
431
  color: "white",
271
432
  border: "none",
272
433
  borderRadius: "0.375rem",
273
- cursor: loading ? "not-allowed" : "pointer"
434
+ cursor: loading ? "not-allowed" : "pointer",
435
+ fontSize: "0.875rem"
274
436
  },
275
437
  children: loading ? "Loading..." : "Load more"
276
438
  }
@@ -13,8 +13,18 @@ function useArchiva() {
13
13
 
14
14
  // src/react/Timeline.tsx
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
- function formatTimestamp(timestamp) {
16
+ function formatTimestamp(timestamp, format = "default") {
17
17
  const date = typeof timestamp === "string" ? new Date(timestamp) : timestamp;
18
+ if (format === "activity") {
19
+ const month = date.toLocaleDateString("en-US", { month: "short" });
20
+ const day = date.getDate();
21
+ const time = date.toLocaleTimeString("en-US", {
22
+ hour: "numeric",
23
+ minute: "2-digit",
24
+ hour12: true
25
+ });
26
+ return `${month} ${day} at ${time}`;
27
+ }
18
28
  return date.toLocaleDateString(void 0, {
19
29
  month: "short",
20
30
  day: "numeric",
@@ -23,13 +33,36 @@ function formatTimestamp(timestamp) {
23
33
  minute: "2-digit"
24
34
  });
25
35
  }
26
- function eventToTimelineItem(event) {
36
+ function eventToTimelineItem(event, getActorAvatar) {
37
+ const actorId = event.actorId || "unknown";
38
+ const userName = actorId.includes(":") ? actorId.split(":")[1] || actorId : actorId;
39
+ const userHandle = userName;
40
+ const initials = userName.charAt(0).toUpperCase();
41
+ const actorAvatarOrIcon = getActorAvatar ? getActorAvatar(actorId) : void 0;
42
+ let actorAvatar = void 0;
43
+ let actorIcon = void 0;
44
+ if (typeof actorAvatarOrIcon === "string") {
45
+ actorAvatar = actorAvatarOrIcon;
46
+ } else if (actorAvatarOrIcon) {
47
+ actorIcon = actorAvatarOrIcon;
48
+ }
49
+ if (!actorAvatar && !actorIcon) {
50
+ actorAvatar = "https://www.gravatar.com/avatar?d=mp";
51
+ }
52
+ const action = event.action.charAt(0).toUpperCase() + event.action.slice(1);
27
53
  return {
28
54
  id: event.id,
29
- title: event.action,
30
- description: `${event.entityType} ${event.entityId}`,
55
+ title: "",
56
+ // Not used in activity layout
57
+ description: action,
31
58
  timestamp: event.receivedAt,
32
- badge: event.actorId ?? void 0,
59
+ userName: userName.charAt(0).toUpperCase() + userName.slice(1),
60
+ userHandle,
61
+ entityType: event.entityType.toLowerCase(),
62
+ avatar: actorAvatar,
63
+ icon: actorIcon,
64
+ avatarFallback: initials,
65
+ statusIndicator: false,
33
66
  data: event
34
67
  };
35
68
  }
@@ -76,20 +109,129 @@ async function fetchEventsWithRetry(apiBaseUrl, getToken, forceRefreshToken, par
76
109
  nextCursor: typeof payload.nextCursor === "string" ? payload.nextCursor : void 0
77
110
  };
78
111
  }
112
+ function SimpleSearchInput({
113
+ value,
114
+ onChange,
115
+ onClear,
116
+ placeholder = "Search..."
117
+ }) {
118
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%" }, children: [
119
+ /* @__PURE__ */ jsx(
120
+ "input",
121
+ {
122
+ type: "text",
123
+ value,
124
+ onChange: (e) => onChange(e.target.value),
125
+ placeholder,
126
+ style: {
127
+ width: "100%",
128
+ padding: "0.5rem 2.5rem 0.5rem 0.75rem",
129
+ border: "1px solid #e5e7eb",
130
+ borderRadius: "0.375rem",
131
+ fontSize: "0.875rem"
132
+ }
133
+ }
134
+ ),
135
+ value && /* @__PURE__ */ jsx(
136
+ "button",
137
+ {
138
+ type: "button",
139
+ onClick: onClear,
140
+ style: {
141
+ position: "absolute",
142
+ right: "0.5rem",
143
+ top: "50%",
144
+ transform: "translateY(-50%)",
145
+ background: "none",
146
+ border: "none",
147
+ cursor: "pointer",
148
+ padding: "0.25rem",
149
+ display: "flex",
150
+ alignItems: "center"
151
+ },
152
+ children: /* @__PURE__ */ jsx("span", { style: { fontSize: "1rem" }, children: "\xD7" })
153
+ }
154
+ )
155
+ ] });
156
+ }
157
+ function SimpleAvatar({
158
+ src,
159
+ fallback,
160
+ icon: Icon,
161
+ size = 40
162
+ }) {
163
+ if (Icon) {
164
+ return /* @__PURE__ */ jsx(
165
+ "div",
166
+ {
167
+ style: {
168
+ width: `${size}px`,
169
+ height: `${size}px`,
170
+ borderRadius: "50%",
171
+ backgroundColor: "#f3f4f6",
172
+ border: "2px solid #fff",
173
+ display: "flex",
174
+ alignItems: "center",
175
+ justifyContent: "center",
176
+ flexShrink: 0
177
+ },
178
+ children: /* @__PURE__ */ jsx(Icon, { className: "" })
179
+ }
180
+ );
181
+ }
182
+ return /* @__PURE__ */ jsx(
183
+ "div",
184
+ {
185
+ style: {
186
+ width: `${size}px`,
187
+ height: `${size}px`,
188
+ borderRadius: "50%",
189
+ backgroundColor: "#f3f4f6",
190
+ border: "2px solid #fff",
191
+ display: "flex",
192
+ alignItems: "center",
193
+ justifyContent: "center",
194
+ flexShrink: 0,
195
+ overflow: "hidden"
196
+ },
197
+ children: src ? /* @__PURE__ */ jsx(
198
+ "img",
199
+ {
200
+ src,
201
+ alt: fallback || "",
202
+ style: { width: "100%", height: "100%", objectFit: "cover" }
203
+ }
204
+ ) : /* @__PURE__ */ jsx("span", { style: { fontSize: `${size * 0.4}px`, fontWeight: 500 }, children: fallback || "?" })
205
+ }
206
+ );
207
+ }
208
+ function applyClientSideFilters(events, searchQuery) {
209
+ if (!searchQuery.trim()) {
210
+ return events;
211
+ }
212
+ const query = searchQuery.toLowerCase();
213
+ return events.filter((event) => {
214
+ return event.action.toLowerCase().includes(query) || event.entityType.toLowerCase().includes(query) || event.entityId.toLowerCase().includes(query) || event.actorId && event.actorId.toLowerCase().includes(query) || event.source && event.source.toLowerCase().includes(query);
215
+ });
216
+ }
79
217
  function Timeline({
80
218
  entityId,
81
219
  actorId,
82
220
  entityType,
83
- initialLimit = 25,
221
+ initialLimit = 100,
84
222
  className,
85
- emptyMessage = "No events yet."
223
+ emptyMessage = "No events yet.",
224
+ showSearch = false,
225
+ showFilters = false,
226
+ getActorAvatar
86
227
  }) {
87
228
  const { apiBaseUrl, getToken, forceRefreshToken } = useArchiva();
88
- const [items, setItems] = React.useState([]);
229
+ const [allEvents, setAllEvents] = React.useState([]);
89
230
  const [cursor, setCursor] = React.useState(void 0);
90
231
  const [loading, setLoading] = React.useState(false);
91
232
  const [error, setError] = React.useState(null);
92
233
  const [hasMore, setHasMore] = React.useState(false);
234
+ const [searchQuery, setSearchQuery] = React.useState("");
93
235
  const load = React.useCallback(
94
236
  async (options) => {
95
237
  setLoading(true);
@@ -108,8 +250,7 @@ function Timeline({
108
250
  forceRefreshToken,
109
251
  params
110
252
  );
111
- const newItems = response.items.map(eventToTimelineItem);
112
- setItems((prev) => options?.reset ? newItems : [...prev, ...newItems]);
253
+ setAllEvents((prev) => options?.reset ? response.items : [...prev, ...response.items]);
113
254
  setCursor(response.nextCursor);
114
255
  setHasMore(Boolean(response.nextCursor));
115
256
  } catch (err) {
@@ -122,98 +263,118 @@ function Timeline({
122
263
  );
123
264
  React.useEffect(() => {
124
265
  setCursor(void 0);
125
- setItems([]);
266
+ setAllEvents([]);
126
267
  void load({ reset: true });
127
268
  }, [entityId, actorId, entityType]);
128
- if (loading && items.length === 0) {
129
- return /* @__PURE__ */ jsx("div", { className: className || "", children: /* @__PURE__ */ jsx("div", { children: "Loading events..." }) });
269
+ const filteredEvents = React.useMemo(() => {
270
+ return applyClientSideFilters(allEvents, searchQuery);
271
+ }, [allEvents, searchQuery]);
272
+ const timelineItems = React.useMemo(() => {
273
+ return filteredEvents.slice(0, 10).map(
274
+ (event) => eventToTimelineItem(event, getActorAvatar)
275
+ );
276
+ }, [filteredEvents, getActorAvatar]);
277
+ if (loading && allEvents.length === 0) {
278
+ return /* @__PURE__ */ jsx("div", { className: className || "", style: { width: "100%" }, children: /* @__PURE__ */ jsx("div", { style: { padding: "3rem", textAlign: "center", color: "#6b7280" }, children: "Loading events..." }) });
130
279
  }
131
280
  if (error) {
132
- return /* @__PURE__ */ jsx("div", { className: className || "", children: /* @__PURE__ */ jsxs("div", { style: { color: "red" }, children: [
281
+ return /* @__PURE__ */ jsx("div", { className: className || "", style: { width: "100%" }, children: /* @__PURE__ */ jsxs("div", { style: { padding: "1rem", color: "#dc2626", backgroundColor: "#fef2f2", borderRadius: "0.375rem" }, children: [
133
282
  "Error: ",
134
283
  error
135
284
  ] }) });
136
285
  }
137
- if (items.length === 0) {
138
- return /* @__PURE__ */ jsx("div", { className: className || "", children: /* @__PURE__ */ jsx("div", { children: emptyMessage }) });
139
- }
140
286
  return /* @__PURE__ */ jsxs("div", { className: className || "", style: { width: "100%" }, children: [
141
- /* @__PURE__ */ jsx("div", { style: { position: "relative" }, children: items.map((item, index) => /* @__PURE__ */ jsxs(
142
- "div",
287
+ showSearch && /* @__PURE__ */ jsx("div", { style: { marginBottom: "1rem" }, children: /* @__PURE__ */ jsx(
288
+ SimpleSearchInput,
143
289
  {
144
- style: {
145
- position: "relative",
146
- display: "flex",
147
- gap: "1rem",
148
- paddingBottom: index < items.length - 1 ? "2rem" : "0"
149
- },
150
- children: [
151
- index < items.length - 1 && /* @__PURE__ */ jsx(
152
- "div",
153
- {
154
- style: {
155
- position: "absolute",
156
- left: "1.25rem",
157
- top: "3rem",
158
- height: "calc(100% - 3rem)",
159
- width: "2px",
160
- backgroundColor: "#e5e7eb"
290
+ value: searchQuery,
291
+ onChange: setSearchQuery,
292
+ onClear: () => setSearchQuery(""),
293
+ placeholder: "Search actions, entities, IDs, actors, sources..."
294
+ }
295
+ ) }),
296
+ searchQuery && filteredEvents.length !== allEvents.length && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", color: "#6b7280" }, children: [
297
+ "Showing ",
298
+ filteredEvents.length,
299
+ " of ",
300
+ allEvents.length,
301
+ " events"
302
+ ] }),
303
+ timelineItems.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "3rem", textAlign: "center", color: "#6b7280" }, children: searchQuery ? "No events match your search." : emptyMessage }) : /* @__PURE__ */ jsx("div", { style: { position: "relative", width: "100%" }, children: timelineItems.map((item, index) => {
304
+ const useActivityLayout = !!(item.actorDisplay || item.userName || item.userHandle);
305
+ return /* @__PURE__ */ jsxs(
306
+ "div",
307
+ {
308
+ style: {
309
+ position: "relative",
310
+ display: "flex",
311
+ alignItems: "start",
312
+ gap: "1rem",
313
+ paddingBottom: index < timelineItems.length - 1 ? "2rem" : "0"
314
+ },
315
+ children: [
316
+ index < timelineItems.length - 1 && /* @__PURE__ */ jsx(
317
+ "div",
318
+ {
319
+ style: {
320
+ position: "absolute",
321
+ left: "1.25rem",
322
+ top: useActivityLayout ? "2.5rem" : "3rem",
323
+ height: `calc(100% - ${useActivityLayout ? "2.5rem" : "3rem"})`,
324
+ width: "2px",
325
+ backgroundColor: "#e5e7eb"
326
+ }
161
327
  }
162
- }
163
- ),
164
- /* @__PURE__ */ jsx(
165
- "div",
166
- {
167
- style: {
168
- position: "relative",
169
- zIndex: 10,
170
- width: "2.5rem",
171
- height: "2.5rem",
172
- borderRadius: "50%",
173
- backgroundColor: "#f3f4f6",
174
- border: "2px solid #fff",
175
- display: "flex",
176
- alignItems: "center",
177
- justifyContent: "center",
178
- flexShrink: 0
179
- },
180
- children: /* @__PURE__ */ jsx(
181
- "div",
182
- {
183
- style: {
184
- width: "0.75rem",
185
- height: "0.75rem",
186
- borderRadius: "50%",
187
- backgroundColor: "#6b7280"
328
+ ),
329
+ /* @__PURE__ */ jsx("div", { style: { position: "relative", zIndex: 10, flexShrink: 0, paddingTop: "0.125rem" }, children: /* @__PURE__ */ jsx(
330
+ SimpleAvatar,
331
+ {
332
+ src: item.avatar,
333
+ fallback: item.avatarFallback,
334
+ icon: item.icon,
335
+ size: 40
336
+ }
337
+ ) }),
338
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0 }, children: useActivityLayout ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "1rem" }, children: [
339
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
340
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
341
+ item.actorDisplay && /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, fontSize: "0.875rem" }, children: item.actorDisplay }),
342
+ item.userHandle && /* @__PURE__ */ jsxs("span", { style: { fontSize: "0.75rem", color: "#6b7280" }, children: [
343
+ "@",
344
+ item.userHandle.toLowerCase()
345
+ ] })
346
+ ] }),
347
+ item.description && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#111827", margin: "0.5rem 0 0 0" }, children: item.description })
348
+ ] }),
349
+ /* @__PURE__ */ jsxs("div", { style: { flexShrink: 0, display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "0.25rem" }, children: [
350
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", fontFamily: "monospace", color: "#6b7280", whiteSpace: "nowrap" }, children: formatTimestamp(item.timestamp, "activity") }),
351
+ item.entityType && /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: "#6b7280", whiteSpace: "nowrap" }, children: item.entityType })
352
+ ] })
353
+ ] }) : /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "0.5rem" }, children: /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
354
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
355
+ /* @__PURE__ */ jsx("h4", { style: { fontWeight: 600, margin: 0, fontSize: "0.875rem" }, children: item.title || item.description }),
356
+ item.badge && /* @__PURE__ */ jsx(
357
+ "span",
358
+ {
359
+ style: {
360
+ fontSize: "0.75rem",
361
+ padding: "0.125rem 0.5rem",
362
+ borderRadius: "0.375rem",
363
+ backgroundColor: "#f3f4f6"
364
+ },
365
+ children: item.badge
188
366
  }
189
- }
190
- )
191
- }
192
- ),
193
- /* @__PURE__ */ jsx("div", { style: { flex: 1, paddingBottom: "2rem" }, children: /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "start", justifyContent: "space-between", gap: "0.5rem" }, children: /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
194
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
195
- /* @__PURE__ */ jsx("h4", { style: { fontWeight: 600, margin: 0 }, children: item.title }),
196
- item.badge && /* @__PURE__ */ jsx(
197
- "span",
198
- {
199
- style: {
200
- fontSize: "0.75rem",
201
- padding: "0.125rem 0.5rem",
202
- borderRadius: "0.375rem",
203
- backgroundColor: "#f3f4f6"
204
- },
205
- children: item.badge
206
- }
207
- )
208
- ] }),
209
- /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0 0" }, children: formatTimestamp(item.timestamp) }),
210
- item.description && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.5rem 0 0 0" }, children: item.description })
211
- ] }) }) })
212
- ]
213
- },
214
- item.id
215
- )) }),
216
- hasMore && /* @__PURE__ */ jsx("div", { style: { marginTop: "1rem" }, children: /* @__PURE__ */ jsx(
367
+ )
368
+ ] }),
369
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.25rem 0 0 0" }, children: formatTimestamp(item.timestamp) }),
370
+ item.description && item.title && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#6b7280", margin: "0.5rem 0 0 0" }, children: item.description })
371
+ ] }) }) })
372
+ ]
373
+ },
374
+ item.id
375
+ );
376
+ }) }),
377
+ hasMore && !searchQuery && /* @__PURE__ */ jsx("div", { style: { marginTop: "1rem", textAlign: "center" }, children: /* @__PURE__ */ jsx(
217
378
  "button",
218
379
  {
219
380
  type: "button",
@@ -225,7 +386,8 @@ function Timeline({
225
386
  color: "white",
226
387
  border: "none",
227
388
  borderRadius: "0.375rem",
228
- cursor: loading ? "not-allowed" : "pointer"
389
+ cursor: loading ? "not-allowed" : "pointer",
390
+ fontSize: "0.875rem"
229
391
  },
230
392
  children: loading ? "Loading..." : "Load more"
231
393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archiva/archiva-nextjs",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Archiva Next.js SDK - Server Actions and Timeline Component",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",