11import React , { useEffect , useState } from 'react' ;
2- import { useActivityLogs } from '@/api/account/activity' ;
2+ import { ActivityLogFilters , useActivityLogs } from '@/api/account/activity' ;
33import { useFlashKey } from '@/plugins/useFlash' ;
44import PageContentBlock from '@/components/elements/PageContentBlock' ;
55import FlashMessageRender from '@/components/FlashMessageRender' ;
@@ -8,66 +8,114 @@ import { Link } from 'react-router-dom';
88import PaginationFooter from '@/components/elements/table/PaginationFooter' ;
99import { UserIcon } from '@heroicons/react/outline' ;
1010import Tooltip from '@/components/elements/tooltip/Tooltip' ;
11- import { DesktopComputerIcon } from '@heroicons/react/solid' ;
11+ import { DesktopComputerIcon , XCircleIcon } from '@heroicons/react/solid' ;
12+ import { useLocation } from 'react-router' ;
13+ import Spinner from '@/components/elements/Spinner' ;
14+ import { styles as btnStyles } from '@/components/elements/button/index' ;
15+ import classNames from 'classnames' ;
1216
1317export default ( ) => {
18+ const location = useLocation ( ) ;
1419 const { clearAndAddHttpError } = useFlashKey ( 'account' ) ;
15- const [ page , setPage ] = useState ( 1 ) ;
16- const { data, isValidating : _ , error } = useActivityLogs ( page , {
20+ const [ filters , setFilters ] = useState < ActivityLogFilters > ( { page : 1 , sorts : { timestamp : - 1 } } ) ;
21+ const { data, isValidating, error } = useActivityLogs ( filters , {
1722 revalidateOnMount : true ,
1823 revalidateOnFocus : false ,
1924 } ) ;
2025
26+ useEffect ( ( ) => {
27+ const parsed = new URLSearchParams ( location . search ) ;
28+
29+ setFilters ( value => ( { ...value , filters : { ip : parsed . get ( 'ip' ) , event : parsed . get ( 'event' ) } } ) ) ;
30+ } , [ location . search ] ) ;
31+
2132 useEffect ( ( ) => {
2233 clearAndAddHttpError ( error ) ;
2334 } , [ error ] ) ;
2435
36+ const queryTo = ( params : Record < string , string > ) : string => {
37+ const current = new URLSearchParams ( location . search ) ;
38+ Object . keys ( params ) . forEach ( key => {
39+ current . set ( key , params [ key ] ) ;
40+ } ) ;
41+
42+ return current . toString ( ) ;
43+ } ;
44+
2545 return (
2646 < PageContentBlock title = { 'Account Activity Log' } >
2747 < FlashMessageRender byKey = { 'account' } />
28- < div className = { 'bg-gray-700' } >
29- { data ?. items . map ( ( activity ) => (
30- < div
31- key = { `${ activity . event } |${ activity . timestamp . toString ( ) } ` }
32- className = { 'grid grid-cols-10 py-4 border-b-2 border-gray-800 last:rounded-b last:border-0' }
48+ { ( filters . filters ?. event || filters . filters ?. ip ) &&
49+ < div className = { 'flex justify-end mb-2' } >
50+ < Link
51+ to = { '#' }
52+ className = { classNames ( btnStyles . button , btnStyles . text ) }
53+ onClick = { ( ) => setFilters ( value => ( { ...value , filters : { } } ) ) }
3354 >
34- < div className = { 'col-span-1 flex items-center justify-center select-none' } >
35- < div className = { 'flex items-center w-8 h-8 rounded-full bg-gray-600 overflow-hidden' } >
36- { activity . relationships . actor ?
37- < img src = { activity . relationships . actor . image } alt = { 'User avatar' } />
38- :
39- < UserIcon className = { 'w-5 h-5 mx-auto' } />
40- }
55+ Clear Filters < XCircleIcon className = { 'w-4 h-4 ml-2' } />
56+ </ Link >
57+ </ div >
58+ }
59+ { ! data && isValidating ?
60+ < Spinner centered />
61+ :
62+ < div className = { 'bg-gray-700' } >
63+ { data ?. items . map ( ( activity ) => (
64+ < div
65+ key = { `${ activity . event } |${ activity . timestamp . toString ( ) } ` }
66+ className = { 'grid grid-cols-10 py-4 border-b-2 border-gray-800 last:rounded-b last:border-0' }
67+ >
68+ < div className = { 'col-span-1 flex items-center justify-center select-none' } >
69+ < div className = { 'flex items-center w-8 h-8 rounded-full bg-gray-600 overflow-hidden' } >
70+ { activity . relationships . actor ?
71+ < img src = { activity . relationships . actor . image } alt = { 'User avatar' } />
72+ :
73+ < UserIcon className = { 'w-5 h-5 mx-auto' } />
74+ }
75+ </ div >
4176 </ div >
42- </ div >
43- < div className = { 'col-span-9' } >
44- < div className = { 'flex items-center text-gray-50' } >
45- { activity . relationships . actor ?. username || 'system' }
46- < span className = { 'text-gray-400' } > — </ span >
47- < Link to = { `#event=${ activity . event } ` } >
48- { activity . event }
49- </ Link >
50- { typeof activity . properties . useragent === 'string' &&
51- < Tooltip content = { activity . properties . useragent } placement = { 'top' } >
52- < DesktopComputerIcon className = { 'ml-2 w-4 h-4 cursor-pointer' } />
77+ < div className = { 'col-span-9' } >
78+ < div className = { 'flex items-center text-gray-50' } >
79+ { activity . relationships . actor ?. username || 'system' }
80+ < span className = { 'text-gray-400' } > — </ span >
81+ < Link
82+ to = { `?${ queryTo ( { event : activity . event } ) } ` }
83+ className = { 'transition-colors duration-75 hover:text-cyan-400' }
84+ >
85+ { activity . event }
86+ </ Link >
87+ { typeof activity . properties . useragent === 'string' &&
88+ < Tooltip content = { activity . properties . useragent } placement = { 'top' } >
89+ < DesktopComputerIcon className = { 'ml-2 w-4 h-4 cursor-pointer' } />
90+ </ Tooltip >
91+ }
92+ </ div >
93+ < div className = { 'mt-1 flex items-center text-sm' } >
94+ < Link
95+ to = { `?${ queryTo ( { ip : activity . ip } ) } ` }
96+ className = { 'transition-colors duration-75 hover:text-cyan-400' }
97+ >
98+ { activity . ip }
99+ </ Link >
100+ < span className = { 'text-gray-400' } > | </ span >
101+ < Tooltip
102+ placement = { 'right' }
103+ content = { format ( activity . timestamp , 'MMM do, yyyy h:mma' ) }
104+ >
105+ < span >
106+ { formatDistanceToNowStrict ( activity . timestamp , { addSuffix : true } ) }
107+ </ span >
53108 </ Tooltip >
54- }
55- </ div >
56- { /* <p className={'mt-1'}>{activity.description || JSON.stringify(activity.properties)}</p> */ }
57- < div className = { 'mt-1 flex items-center text-sm' } >
58- < Link to = { `#ip=${ activity . ip } ` } > { activity . ip } </ Link >
59- < span className = { 'text-gray-400' } > | </ span >
60- < Tooltip placement = { 'right' } content = { format ( activity . timestamp , 'MMM do, yyyy h:mma' ) } >
61- < span >
62- { formatDistanceToNowStrict ( activity . timestamp , { addSuffix : true } ) }
63- </ span >
64- </ Tooltip >
109+ </ div >
65110 </ div >
66111 </ div >
67- </ div >
68- ) ) }
69- </ div >
70- { data && < PaginationFooter pagination = { data . pagination } onPageSelect = { setPage } /> }
112+ ) ) }
113+ </ div >
114+ }
115+ { data && < PaginationFooter
116+ pagination = { data . pagination }
117+ onPageSelect = { page => setFilters ( value => ( { ...value , page } ) ) }
118+ /> }
71119 </ PageContentBlock >
72120 ) ;
73121} ;
0 commit comments