the statusphere demo reworked into a vite/react app in a monorepo
at main 132 lines 4.0 kB view raw
1import { useEffect } from 'react' 2import { useQuery } from '@tanstack/react-query' 3 4import api from '#/services/api' 5import { STATUS_OPTIONS } from './StatusForm' 6 7const StatusList = () => { 8 // Use React Query to fetch and cache statuses 9 const { data, isPending, isError, error } = useQuery({ 10 queryKey: ['statuses'], 11 queryFn: async () => { 12 const { data } = await api.getStatuses({ limit: 30 }) 13 return data 14 }, 15 placeholderData: (previousData) => previousData, // Use previous data while refetching 16 refetchInterval: 30e3, // Refetch every 30 seconds 17 }) 18 19 useEffect(() => { 20 if (error) { 21 console.error(error) 22 } 23 }, [error]) 24 25 // Destructure data 26 const statuses = data?.statuses || [] 27 28 // Get a random emoji from the STATUS_OPTIONS array 29 const randomEmoji = 30 STATUS_OPTIONS[Math.floor(Math.random() * STATUS_OPTIONS.length)] 31 32 if (isPending && !data) { 33 return ( 34 <div className="py-8 text-center"> 35 <div className="text-5xl mb-2 animate-pulse inline-block"> 36 {randomEmoji} 37 </div> 38 <div className="text-gray-500 dark:text-gray-400"> 39 Loading statuses... 40 </div> 41 </div> 42 ) 43 } 44 45 if (isError) { 46 return ( 47 <div className="py-4 text-red-500"> 48 {(error as Error)?.message || 'Failed to load statuses'} 49 </div> 50 ) 51 } 52 53 if (statuses.length === 0) { 54 return ( 55 <div className="py-4 text-center text-gray-500 dark:text-gray-400"> 56 No statuses yet. 57 </div> 58 ) 59 } 60 61 // Helper to format dates 62 const formatDate = (dateString: string) => { 63 const date = new Date(dateString) 64 const today = new Date() 65 const isToday = 66 date.getDate() === today.getDate() && 67 date.getMonth() === today.getMonth() && 68 date.getFullYear() === today.getFullYear() 69 70 if (isToday) { 71 return 'today' 72 } else { 73 return date.toLocaleDateString(undefined, { 74 year: 'numeric', 75 month: 'long', 76 day: 'numeric', 77 }) 78 } 79 } 80 81 return ( 82 <div className="px-4"> 83 <div className="relative"> 84 <div className="absolute left-[20.5px] top-[22.5px] bottom-[22.5px] w-0.5 bg-gray-200 dark:bg-gray-700"></div> 85 {statuses.map((status) => { 86 const handle = 87 status.profile.handle || status.profile.did.substring(0, 15) + '...' 88 const formattedDate = formatDate(status.createdAt) 89 const isToday = formattedDate === 'today' 90 91 return ( 92 <div 93 key={status.uri} 94 className="relative flex items-center gap-5 py-4" 95 > 96 <div className="relative z-10 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 h-[45px] w-[45px] flex items-center justify-center shadow-sm"> 97 <div className="text-2xl">{status.status}</div> 98 </div> 99 <div className="flex-1"> 100 <div className="text-gray-600 dark:text-gray-300 text-base"> 101 <a 102 target="_blank" 103 rel="noopener noreferrer" 104 href={`https://bsky.app/profile/${handle}`} 105 className="font-medium text-gray-700 dark:text-gray-200 hover:underline" 106 > 107 @{handle} 108 </a>{' '} 109 {isToday ? ( 110 <span> 111 is feeling{' '} 112 <span className="font-semibold">{status.status}</span>{' '} 113 today 114 </span> 115 ) : ( 116 <span> 117 was feeling{' '} 118 <span className="font-semibold">{status.status}</span> on{' '} 119 {formattedDate} 120 </span> 121 )} 122 </div> 123 </div> 124 </div> 125 ) 126 })} 127 </div> 128 </div> 129 ) 130} 131 132export default StatusList