1+ import type { ColumnSort } from "@tanstack/react-table" ;
12import { observer } from "mobx-react" ;
23import { PlusIcon , WebhooksIcon } from "outline-icons" ;
3- import * as React from "react" ;
4+ import { useCallback , useEffect , useMemo , useState } from "react" ;
45import { useTranslation , Trans } from "react-i18next" ;
5- import type WebhookSubscription from "~/models/WebhookSubscription" ;
6+ import { useHistory , useLocation } from "react-router-dom" ;
7+ import { toast } from "sonner" ;
68import { Action } from "~/components/Actions" ;
79import Button from "~/components/Button" ;
10+ import { ConditionalFade } from "~/components/Fade" ;
811import Heading from "~/components/Heading" ;
9- import Modal from "~/components/Modal" ;
10- import PaginatedList from "~/components/PaginatedList" ;
12+ import InputSearch from "~/components/InputSearch" ;
1113import Scene from "~/components/Scene" ;
1214import Text from "~/components/Text" ;
1315import env from "~/env" ;
14- import useBoolean from "~/hooks/useBoolean" ;
1516import useCurrentTeam from "~/hooks/useCurrentTeam" ;
1617import usePolicy from "~/hooks/usePolicy" ;
18+ import useQuery from "~/hooks/useQuery" ;
1719import useStores from "~/hooks/useStores" ;
18- import WebhookSubscriptionListItem from "./components/WebhookSubscriptionListItem" ;
19- import WebhookSubscriptionNew from "./components/WebhookSubscriptionNew" ;
20+ import { useTableRequest } from "~/hooks/useTableRequest" ;
21+ import { StickyFilters } from "~/scenes/Settings/components/StickyFilters" ;
22+ import { createWebhookSubscription } from "./actions" ;
23+ import { WebhookSubscriptionsTable } from "./components/WebhookSubscriptionsTable" ;
2024
2125function Webhooks ( ) {
2226 const team = useCurrentTeam ( ) ;
2327 const { t } = useTranslation ( ) ;
2428 const { webhookSubscriptions } = useStores ( ) ;
25- const [ newModalOpen , handleNewModalOpen , handleNewModalClose ] = useBoolean ( ) ;
2629 const can = usePolicy ( team ) ;
2730 const appName = env . APP_NAME ;
31+ const params = useQuery ( ) ;
32+ const history = useHistory ( ) ;
33+ const location = useLocation ( ) ;
34+ const [ query , setQuery ] = useState ( params . get ( "query" ) || "" ) ;
35+
36+ const reqParams = useMemo (
37+ ( ) => ( {
38+ query : params . get ( "query" ) || undefined ,
39+ sort : params . get ( "sort" ) || "createdAt" ,
40+ direction : ( params . get ( "direction" ) || "desc" ) . toUpperCase ( ) as
41+ | "ASC"
42+ | "DESC" ,
43+ } ) ,
44+ [ params ]
45+ ) ;
46+
47+ const sort : ColumnSort = useMemo (
48+ ( ) => ( {
49+ id : reqParams . sort ,
50+ desc : reqParams . direction === "DESC" ,
51+ } ) ,
52+ [ reqParams . sort , reqParams . direction ]
53+ ) ;
54+
55+ const orderedData = webhookSubscriptions . orderedData ;
56+ const filteredWebhooks = useMemo (
57+ ( ) =>
58+ reqParams . query
59+ ? webhookSubscriptions . findByQuery ( reqParams . query )
60+ : orderedData ,
61+ [ webhookSubscriptions , orderedData , reqParams . query ]
62+ ) ;
63+
64+ const { data, error, loading, next } = useTableRequest ( {
65+ data : filteredWebhooks ,
66+ sort,
67+ reqFn : webhookSubscriptions . fetchPage ,
68+ reqParams,
69+ } ) ;
70+
71+ const updateParams = useCallback (
72+ ( name : string , value : string ) => {
73+ if ( value ) {
74+ params . set ( name , value ) ;
75+ } else {
76+ params . delete ( name ) ;
77+ }
78+
79+ history . replace ( {
80+ pathname : location . pathname ,
81+ search : params . toString ( ) ,
82+ } ) ;
83+ } ,
84+ [ params , history , location . pathname ]
85+ ) ;
86+
87+ const handleSearch = useCallback (
88+ ( event : React . ChangeEvent < HTMLInputElement > ) => {
89+ setQuery ( event . target . value ) ;
90+ } ,
91+ [ ]
92+ ) ;
93+
94+ useEffect ( ( ) => {
95+ if ( error ) {
96+ toast . error ( t ( "Could not load webhooks" ) ) ;
97+ }
98+ } , [ t , error ] ) ;
99+
100+ useEffect ( ( ) => {
101+ const timeout = setTimeout ( ( ) => updateParams ( "query" , query ) , 250 ) ;
102+ return ( ) => clearTimeout ( timeout ) ;
103+ } , [ query , updateParams ] ) ;
28104
29105 return (
30106 < Scene
@@ -36,7 +112,7 @@ function Webhooks() {
36112 < Action >
37113 < Button
38114 type = "button"
39- onClick = { handleNewModalOpen }
115+ action = { createWebhookSubscription }
40116 icon = { < PlusIcon /> }
41117 >
42118 { `${ t ( "New webhook" ) } …` }
@@ -45,6 +121,7 @@ function Webhooks() {
45121 ) }
46122 </ >
47123 }
124+ wide
48125 >
49126 < Heading > { t ( "Webhooks" ) } </ Heading >
50127 < Text as = "p" type = "secondary" >
@@ -54,29 +131,25 @@ function Webhooks() {
54131 in near real-time.
55132 </ Trans >
56133 </ Text >
57- < PaginatedList < WebhookSubscription >
58- fetch = { webhookSubscriptions . fetchPage }
59- items = { webhookSubscriptions . enabled }
60- heading = { < h2 > { t ( "Active" ) } </ h2 > }
61- renderItem = { ( webhook ) => (
62- < WebhookSubscriptionListItem key = { webhook . id } webhook = { webhook } />
63- ) }
64- />
65- < PaginatedList < WebhookSubscription >
66- items = { webhookSubscriptions . disabled }
67- heading = { < h2 > { t ( "Inactive" ) } </ h2 > }
68- renderItem = { ( webhook ) => (
69- < WebhookSubscriptionListItem key = { webhook . id } webhook = { webhook } />
70- ) }
71- />
72- < Modal
73- title = { t ( "New webhook" ) }
74- onRequestClose = { handleNewModalClose }
75- isOpen = { newModalOpen }
76- width = "480px"
77- >
78- < WebhookSubscriptionNew onSubmit = { handleNewModalClose } />
79- </ Modal >
134+ < StickyFilters >
135+ < InputSearch
136+ short
137+ value = { query }
138+ placeholder = { `${ t ( "Filter" ) } …` }
139+ onChange = { handleSearch }
140+ />
141+ </ StickyFilters >
142+ < ConditionalFade animate = { ! data } >
143+ < WebhookSubscriptionsTable
144+ data = { data ?? [ ] }
145+ sort = { sort }
146+ loading = { loading }
147+ page = { {
148+ hasNext : ! ! next ,
149+ fetchNext : next ,
150+ } }
151+ />
152+ </ ConditionalFade >
80153 </ Scene >
81154 ) ;
82155}
0 commit comments