Skip to content

Commit 56b7c67

Browse files
Add History Filter Feature (Fixes Issue #511) (#513)
* feat: add history filter to user account * fix: apply linter and prettier * fix: add margin on popover from window * fix: add UI changes to filter * fix: remove auto scroll * fix: remove lint error --------- Co-authored-by: ECWireless <40322776+ECWireless@users.noreply.github.com>
1 parent 90ab4a1 commit 56b7c67

5 files changed

Lines changed: 408 additions & 1 deletion

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import FilterIcon from "@components/Icons/FilterIcon";
2+
import {
3+
Badge,
4+
Box,
5+
Button,
6+
Flex,
7+
Popover,
8+
PopoverContent,
9+
PopoverTrigger,
10+
Text,
11+
} from "@livepeer/design-system";
12+
13+
interface HistoryFilterProps {
14+
selectedEventTypes: string[];
15+
isOpen: boolean;
16+
onOpenChange: (open: boolean) => void;
17+
onToggleEventType: (eventType: string) => void;
18+
onClearFilters: () => void;
19+
allEventTypes: string[];
20+
eventTypeLabels: Record<string, string>;
21+
}
22+
23+
const HistoryFilter = ({
24+
selectedEventTypes,
25+
isOpen,
26+
onOpenChange,
27+
onToggleEventType,
28+
onClearFilters,
29+
allEventTypes,
30+
eventTypeLabels,
31+
}: HistoryFilterProps) => {
32+
return (
33+
<Popover open={isOpen} onOpenChange={onOpenChange}>
34+
<PopoverTrigger asChild>
35+
<Button
36+
role="button"
37+
css={{
38+
display: "flex",
39+
alignItems: "center",
40+
justifyContent: "center",
41+
backgroundColor: "$neutral4",
42+
color: "$hiContrast",
43+
minHeight: "44px",
44+
width: "120px",
45+
padding: "$2 $3",
46+
"&:hover": {
47+
backgroundColor: "$neutral5",
48+
},
49+
}}
50+
>
51+
<FilterIcon size={16} css={{ marginRight: "$1" }} />
52+
<Text css={{ marginRight: "$2" }}>Filter</Text>
53+
{selectedEventTypes.length > 0 && (
54+
<Badge
55+
css={{
56+
backgroundColor: "$primary9",
57+
color: "white",
58+
borderRadius: "50%",
59+
width: "20px",
60+
height: "20px",
61+
display: "flex",
62+
alignItems: "center",
63+
justifyContent: "center",
64+
fontSize: "$1",
65+
fontWeight: 600,
66+
padding: 0,
67+
}}
68+
>
69+
{selectedEventTypes.length}
70+
</Badge>
71+
)}
72+
</Button>
73+
</PopoverTrigger>
74+
<PopoverContent
75+
data-history-filter-popover
76+
css={{
77+
width: "280px",
78+
backgroundColor: "$neutral4",
79+
borderRadius: "$3",
80+
padding: 0,
81+
boxShadow:
82+
"0px 5px 14px rgba(0, 0, 0, 0.22), 0px 0px 2px rgba(0, 0, 0, 0.2)",
83+
border: "1px solid $neutral6",
84+
zIndex: 9,
85+
display: "flex",
86+
flexDirection: "column",
87+
maxHeight: "400px",
88+
marginRight: "$3",
89+
overflow: "hidden",
90+
}}
91+
onPointerEnterCapture={undefined}
92+
onPointerLeaveCapture={undefined}
93+
placeholder={undefined}
94+
>
95+
{/* Header - Sticky */}
96+
<Flex
97+
css={{
98+
padding: "$3",
99+
borderBottom: "1px solid $neutral6",
100+
borderTopLeftRadius: "$3",
101+
borderTopRightRadius: "$3",
102+
alignItems: "center",
103+
justifyContent: "space-between",
104+
flexShrink: 0,
105+
backgroundColor: "$neutral4",
106+
position: "sticky",
107+
top: 0,
108+
zIndex: 1,
109+
}}
110+
>
111+
<Button
112+
onClick={onClearFilters}
113+
css={{
114+
color: "$neutral11",
115+
fontSize: "$2",
116+
padding: "$1",
117+
backgroundColor: "transparent",
118+
"&:hover": {
119+
color: "$hiContrast",
120+
backgroundColor: "transparent",
121+
},
122+
}}
123+
>
124+
Clear
125+
</Button>
126+
<Text css={{ fontWeight: 600, fontSize: "$3" }}>Filters</Text>
127+
<Button
128+
onClick={() => onOpenChange(false)}
129+
css={{
130+
color: "$primary11",
131+
fontSize: "$2",
132+
padding: "$1",
133+
backgroundColor: "transparent",
134+
"&:hover": {
135+
backgroundColor: "transparent",
136+
},
137+
}}
138+
>
139+
Done
140+
</Button>
141+
</Flex>
142+
143+
{/* Event type section - Scrollable */}
144+
<Flex
145+
data-history-filter-scrollable
146+
css={{
147+
flexDirection: "column",
148+
overflowY: "auto",
149+
flex: 1,
150+
borderBottomLeftRadius: "$3",
151+
borderBottomRightRadius: "$3",
152+
}}
153+
>
154+
<Box css={{ padding: "$3" }}>
155+
<Text
156+
css={{
157+
fontWeight: 500,
158+
fontSize: "$2",
159+
marginBottom: "$2",
160+
color: "$hiContrast",
161+
}}
162+
>
163+
Event Type
164+
</Text>
165+
<Flex css={{ flexDirection: "column", gap: "$2" }}>
166+
{allEventTypes.map((eventType) => {
167+
const isChecked = selectedEventTypes.includes(eventType);
168+
return (
169+
<Flex
170+
key={eventType}
171+
css={{
172+
alignItems: "center",
173+
cursor: "pointer",
174+
padding: "$1",
175+
borderRadius: "$1",
176+
"&:hover": {
177+
backgroundColor: "$neutral5",
178+
},
179+
}}
180+
onClick={() => onToggleEventType(eventType)}
181+
>
182+
<Box
183+
css={{
184+
width: "18px",
185+
height: "18px",
186+
border: "2px solid",
187+
borderColor: isChecked ? "$primary11" : "$neutral8",
188+
backgroundColor: isChecked
189+
? "$primary11"
190+
: "transparent",
191+
borderRadius: "$1",
192+
marginRight: "$2",
193+
display: "flex",
194+
alignItems: "center",
195+
justifyContent: "center",
196+
transition: "all 0.2s",
197+
}}
198+
>
199+
{isChecked && (
200+
<Box
201+
as="svg"
202+
width="12"
203+
height="12"
204+
viewBox="0 0 12 12"
205+
fill="none"
206+
>
207+
<path
208+
d="M2 6L4.5 8.5L10 3"
209+
stroke="white"
210+
strokeWidth="2"
211+
strokeLinecap="round"
212+
strokeLinejoin="round"
213+
/>
214+
</Box>
215+
)}
216+
</Box>
217+
<Text
218+
css={{
219+
fontSize: "$2",
220+
color: "$hiContrast",
221+
userSelect: "none",
222+
}}
223+
>
224+
{eventTypeLabels[eventType]}
225+
</Text>
226+
</Flex>
227+
);
228+
})}
229+
</Flex>
230+
</Box>
231+
</Flex>
232+
</PopoverContent>
233+
</Popover>
234+
);
235+
};
236+
237+
export default HistoryFilter;

components/HistoryView/index.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import HistoryFilter from "@components/HistoryView/HistoryFilter";
12
import Spinner from "@components/Spinner";
23
import TransactionBadge from "@components/TransactionBadge";
34
import { Fm, parsePollIpfs } from "@lib/api/polls";
@@ -20,6 +21,7 @@ import {
2021
useTransactionsQuery,
2122
VoteEvent,
2223
} from "apollo";
24+
import { useHistoryFilter } from "hooks";
2325
import { CHAIN_INFO, DEFAULT_CHAIN_ID } from "lib/chains";
2426
import { useRouter } from "next/router";
2527
import numbro from "numbro";
@@ -188,6 +190,18 @@ const Index = () => {
188190
]
189191
);
190192

193+
// Filter events using history hook
194+
const {
195+
filteredEvents,
196+
selectedEventTypes,
197+
toggleEventType,
198+
clearFilters,
199+
isFilterOpen,
200+
setIsFilterOpen,
201+
allEventTypes,
202+
eventTypeLabels,
203+
} = useHistoryFilter(mergedEvents);
204+
191205
if (error) {
192206
console.error(error);
193207
}
@@ -268,8 +282,31 @@ const Index = () => {
268282
position: "relative",
269283
}}
270284
>
285+
<Flex
286+
css={{
287+
justifyContent: "flex-end",
288+
marginBottom: "$3",
289+
alignItems: "center",
290+
}}
291+
>
292+
<HistoryFilter
293+
selectedEventTypes={selectedEventTypes}
294+
isOpen={isFilterOpen}
295+
onOpenChange={setIsFilterOpen}
296+
onToggleEventType={toggleEventType}
297+
onClearFilters={clearFilters}
298+
allEventTypes={allEventTypes}
299+
eventTypeLabels={eventTypeLabels}
300+
/>
301+
</Flex>
271302
<Box css={{ paddingBottom: "$3" }}>
272-
{mergedEvents.map((event, i: number) => renderSwitch(event, i))}
303+
{filteredEvents.length > 0 ? (
304+
filteredEvents.map((event, i: number) => renderSwitch(event, i))
305+
) : (
306+
<Box css={{ paddingTop: "$3", color: "$neutral11" }}>
307+
No events match the selected filters
308+
</Box>
309+
)}
273310
</Box>
274311
{loading && totalLoaded >= 10 && (
275312
<Flex

components/Icons/FilterIcon.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Box } from "@livepeer/design-system";
2+
import { CSS } from "@stitches/react";
3+
4+
interface FilterIconProps {
5+
size?: number;
6+
css?: CSS;
7+
}
8+
9+
const FilterIcon = ({ size = 16, css }: FilterIconProps) => (
10+
<Box
11+
as="svg"
12+
width={size}
13+
height={size}
14+
viewBox="0 0 16 16"
15+
fill="none"
16+
css={css}
17+
aria-hidden="true"
18+
>
19+
<path
20+
d="M2 4h12M4 8h8M6 12h4"
21+
stroke="currentColor"
22+
strokeWidth="1.5"
23+
strokeLinecap="round"
24+
/>
25+
</Box>
26+
);
27+
28+
export default FilterIcon;

0 commit comments

Comments
 (0)