From 0ab158bed19cf154962a462224d58af41732e4f8 Mon Sep 17 00:00:00 2001 From: mitchellecm7 Date: Mon, 30 Mar 2026 15:06:14 -0700 Subject: [PATCH] feat(frontend): add Activity page and timeline component styles --- app/activity/page.tsx | 188 ++++++++++++++++++++ app/components/ActivityTimeline.tsx | 261 ++++++++++++++++++++++++++++ app/page.tsx | 22 --- package-lock.json | 23 +-- 4 files changed, 458 insertions(+), 36 deletions(-) create mode 100644 app/activity/page.tsx create mode 100644 app/components/ActivityTimeline.tsx delete mode 100644 app/page.tsx diff --git a/app/activity/page.tsx b/app/activity/page.tsx new file mode 100644 index 0000000..3a75991 --- /dev/null +++ b/app/activity/page.tsx @@ -0,0 +1,188 @@ +"use client"; + +import React, { useState } from "react"; +import { ActivityTimeline, StreamEvent } from "../components/ActivityTimeline"; + +const MOCK_EVENTS: StreamEvent[] = [ + { + id: "evt-001", + type: "created", + streamId: "0x3a4f...c82e", + streamName: "Payroll · Alice → Bob", + timestamp: "2025-03-28T09:15:00Z", + amount: "2,500.00", + token: "USDC", + }, + { + id: "evt-002", + type: "started", + streamId: "0x3a4f...c82e", + streamName: "Payroll · Alice → Bob", + timestamp: "2025-03-28T09:16:42Z", + amount: "2,500.00", + token: "USDC", + }, + { + id: "evt-003", + type: "created", + streamId: "0xf10b...77d1", + streamName: "Grant · DAO → Dev Fund", + timestamp: "2025-03-29T14:03:10Z", + amount: "10,000.00", + token: "STRM", + }, + { + id: "evt-004", + type: "settled", + streamId: "0x3a4f...c82e", + streamName: "Payroll · Alice → Bob", + timestamp: "2025-03-30T00:00:00Z", + amount: "2,500.00", + token: "USDC", + }, + { + id: "evt-005", + type: "stopped", + streamId: "0xf10b...77d1", + streamName: "Grant · DAO → Dev Fund", + timestamp: "2025-03-30T08:45:22Z", + }, +]; + +type FilterType = "all" | "created" | "started" | "settled" | "stopped"; + +const FILTER_OPTIONS: { value: FilterType; label: string }[] = [ + { value: "all", label: "All" }, + { value: "created", label: "Created" }, + { value: "started", label: "Started" }, + { value: "settled", label: "Settled" }, + { value: "stopped", label: "Stopped" }, +]; + +export default function ActivityPage() { + const [filter, setFilter] = useState("all"); + const [useMock, setUseMock] = useState(true); + + const source = useMock ? MOCK_EVENTS : []; + const filtered = + filter === "all" ? source : source.filter((e) => e.type === filter); + + return ( +
+
+ + {/* header */} +
+
+

+ Activity +

+

+ Stream lifecycle events +

+
+ + {/* demo toggle */} + +
+ + {/* filter tabs */} +
+ {FILTER_OPTIONS.map(({ value, label }) => { + const active = filter === value; + return ( + + ); + })} +
+ + {/* count badge */} + {filtered.length > 0 && ( +

+ {filtered.length} event{filtered.length !== 1 ? "s" : ""} +

+ )} + + {/* timeline */} + +
+
+ ); +} diff --git a/app/components/ActivityTimeline.tsx b/app/components/ActivityTimeline.tsx new file mode 100644 index 0000000..569064c --- /dev/null +++ b/app/components/ActivityTimeline.tsx @@ -0,0 +1,261 @@ +"use client"; + +import React from "react"; + +export type StreamEventType = "created" | "started" | "settled" | "stopped"; + +export interface StreamEvent { + id: string; + type: StreamEventType; + streamId: string; + streamName?: string; + timestamp: string; // ISO string + amount?: string; + token?: string; +} + +interface ActivityTimelineProps { + events: StreamEvent[]; +} + +const EVENT_META: Record< + StreamEventType, + { label: string; color: string; dot: string; bg: string } +> = { + created: { + label: "Stream Created", + color: "#60a5fa", + dot: "#3b82f6", + bg: "rgba(59,130,246,0.08)", + }, + started: { + label: "Stream Started", + color: "#34d399", + dot: "#10b981", + bg: "rgba(16,185,129,0.08)", + }, + settled: { + label: "Stream Settled", + color: "#a78bfa", + dot: "#8b5cf6", + bg: "rgba(139,92,246,0.08)", + }, + stopped: { + label: "Stream Stopped", + color: "#f87171", + dot: "#ef4444", + bg: "rgba(239,68,68,0.08)", + }, +}; + +function formatTimestamp(iso: string): string { + const date = new Date(iso); + return date.toLocaleString("en-US", { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} + +function EventIcon({ type }: { type: StreamEventType }) { + const icons: Record = { + created: ( + + + + + ), + started: ( + + + + ), + settled: ( + + + + ), + stopped: ( + + + + ), + }; + return icons[type]; +} + +export function ActivityTimeline({ events }: ActivityTimelineProps) { + if (events.length === 0) { + return ( +
+ + + + +

+ No activity yet +

+

+ Stream events will appear here +

+
+ ); + } + + return ( +
    + {/* vertical line */} +
  1. + {/* dot */} +
    + + + +
    + + {/* card */} +
    +
    + + {meta.label} + + +
    + +

    + {event.streamName ?? event.streamId} +

    + + {(event.amount || event.token) && ( +

    + {event.amount && ( + + {event.amount} + + )} + {event.amount && event.token && " "} + {event.token && ( + + {event.token} + + )} +

    + )} +
    +
  2. + ); + })} +
+ ); +} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index df5e12e..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -export default function Home() { - return ( -
-

StreamPay

-

- Payment streaming on Stellar -

-

- Connect your wallet to create and manage payment streams. -

-
- ); -} diff --git a/package-lock.json b/package-lock.json index 517eb7d..e015dd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2106,7 +2105,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2292,7 +2292,6 @@ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2304,7 +2303,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -2385,7 +2383,6 @@ "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", @@ -2906,7 +2903,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3461,7 +3457,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4034,7 +4029,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/domexception": { "version": "4.0.0", @@ -4344,7 +4340,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7285,6 +7280,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -8056,6 +8052,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -8071,6 +8068,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8184,7 +8182,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8197,7 +8194,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -8211,7 +8207,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redent": { "version": "3.0.0", @@ -9112,7 +9109,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9344,7 +9340,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver"