forked from
samuel.fm/statusphere-react
the statusphere demo reworked into a vite/react app in a monorepo
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