Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
at main 154 lines 4.7 kB view raw
1import { ComputerDesktopIcon, GlobeAltIcon } from "@heroicons/react/24/outline"; 2import { 3 type AuthenticatedSessionsRequest, 4 PageSize, 5 useAuthenticatedSessionsQuery, 6 useRevokeAuthenticationMutation 7} from "@hey/indexer"; 8import type { ApolloClientError } from "@hey/types/errors"; 9import dayjs from "dayjs"; 10import { memo, useCallback, useState } from "react"; 11import { toast } from "sonner"; 12import { WindowVirtualizer } from "virtua"; 13import Loader from "@/components/Shared/Loader"; 14import { Button, EmptyState, ErrorMessage } from "@/components/Shared/UI"; 15import errorToast from "@/helpers/errorToast"; 16import useLoadMoreOnIntersect from "@/hooks/useLoadMoreOnIntersect"; 17import { useAccountStore } from "@/store/persisted/useAccountStore"; 18 19const List = () => { 20 const { currentAccount } = useAccountStore(); 21 const [revoking, setRevoking] = useState(false); 22 const [revokeingSessionId, setRevokeingSessionId] = useState<null | string>( 23 null 24 ); 25 26 const onError = useCallback((error: ApolloClientError) => { 27 setRevoking(false); 28 setRevokeingSessionId(null); 29 errorToast(error); 30 }, []); 31 32 const onCompleted = () => { 33 setRevoking(false); 34 setRevokeingSessionId(null); 35 toast.success("Session revoked"); 36 }; 37 38 const [revokeAuthentication] = useRevokeAuthenticationMutation({ 39 onCompleted, 40 onError, 41 update: (cache) => { 42 cache.evict({ id: "ROOT_QUERY" }); 43 } 44 }); 45 46 const handleRevoke = async (authenticationId: string) => { 47 setRevoking(true); 48 setRevokeingSessionId(authenticationId); 49 50 return await revokeAuthentication({ 51 variables: { request: { authenticationId } } 52 }); 53 }; 54 55 const request: AuthenticatedSessionsRequest = { pageSize: PageSize.Fifty }; 56 const { data, error, fetchMore, loading } = useAuthenticatedSessionsQuery({ 57 skip: !currentAccount?.address, 58 variables: { request } 59 }); 60 61 const authenticatedSessions = data?.authenticatedSessions?.items; 62 const pageInfo = data?.authenticatedSessions?.pageInfo; 63 const hasMore = pageInfo?.next; 64 65 const handleEndReached = useCallback(async () => { 66 if (hasMore) { 67 await fetchMore({ 68 variables: { request: { ...request, cursor: pageInfo?.next } } 69 }); 70 } 71 }, [fetchMore, hasMore, pageInfo?.next, request]); 72 73 const loadMoreRef = useLoadMoreOnIntersect(handleEndReached); 74 75 if (loading) { 76 return <Loader className="my-10" />; 77 } 78 79 if (error) { 80 return ( 81 <ErrorMessage 82 className="m-5" 83 error={error} 84 title="Failed to load sessions" 85 /> 86 ); 87 } 88 89 if (!authenticatedSessions?.length) { 90 return ( 91 <EmptyState 92 hideCard 93 icon={<GlobeAltIcon className="size-8" />} 94 message="You are not logged in on any other devices!" 95 /> 96 ); 97 } 98 99 return ( 100 <div className="virtual-divider-list-window"> 101 <WindowVirtualizer> 102 {authenticatedSessions.map((session) => ( 103 <div 104 className="flex flex-wrap items-start justify-between p-5" 105 key={session.authenticationId} 106 > 107 <div> 108 <div className="mb-3 flex items-center space-x-2"> 109 <ComputerDesktopIcon className="size-8" /> 110 <div> 111 {session.browser ? <span>{session.browser}</span> : null} 112 {session.os ? <span> - {session.os}</span> : null} 113 </div> 114 </div> 115 <div className="space-y-1 text-gray-500 text-sm dark:text-gray-200"> 116 {session.origin ? ( 117 <div> 118 <b>Origin -</b> {session.origin} 119 </div> 120 ) : null} 121 <div> 122 <b>Registered -</b>{" "} 123 {dayjs(session.createdAt).format("MMM D, YYYY - h:mm:ss A")} 124 </div> 125 <div> 126 <b>Last accessed -</b>{" "} 127 {dayjs(session.updatedAt).format("MMM D, YYYY - h:mm:ss A")} 128 </div> 129 <div> 130 <b>Expires at -</b>{" "} 131 {dayjs(session.expiresAt).format("MMM D, YYYY - h:mm:ss A")} 132 </div> 133 </div> 134 </div> 135 <Button 136 disabled={ 137 revoking && revokeingSessionId === session.authenticationId 138 } 139 loading={ 140 revoking && revokeingSessionId === session.authenticationId 141 } 142 onClick={() => handleRevoke(session.authenticationId)} 143 > 144 Revoke 145 </Button> 146 </div> 147 ))} 148 {hasMore && <span ref={loadMoreRef} />} 149 </WindowVirtualizer> 150 </div> 151 ); 152}; 153 154export default memo(List);