the statusphere demo reworked into a vite/react app in a monorepo
1import { AppBskyActorDefs, XyzStatusphereDefs } from '@statusphere/lexicon'
2
3// Use '/api' prefix consistently for all API calls
4const API_URL = '/api'
5
6// Helper function for logging API actions
7function logApiCall(
8 method: string,
9 endpoint: string,
10 status?: number,
11 error?: any,
12) {
13 const statusStr = status ? `[${status}]` : ''
14 const errorStr = error
15 ? ` - Error: ${error.message || JSON.stringify(error)}`
16 : ''
17 console.log(`🔄 API ${method} ${endpoint} ${statusStr}${errorStr}`)
18}
19
20export interface User {
21 did: string
22 profile: AppBskyActorDefs.ProfileView
23 status?: XyzStatusphereDefs.StatusView
24}
25
26// API service
27export const api = {
28 // Login
29 async login(handle: string) {
30 const url = `${API_URL}/login`
31 logApiCall('POST', url)
32
33 const response = await fetch(url, {
34 method: 'POST',
35 headers: {
36 'Content-Type': 'application/json',
37 },
38 credentials: 'include',
39 body: JSON.stringify({ handle }),
40 })
41
42 if (!response.ok) {
43 const error = await response.json()
44 throw new Error(error.error || 'Login failed')
45 }
46
47 return response.json()
48 },
49
50 // Logout
51 async logout() {
52 const url = `${API_URL}/logout`
53 logApiCall('POST', url)
54 const response = await fetch(url, {
55 method: 'POST',
56 credentials: 'include',
57 })
58
59 if (!response.ok) {
60 throw new Error('Logout failed')
61 }
62
63 return response.json()
64 },
65
66 // Get current user
67 async getCurrentUser() {
68 const url = `${API_URL}/user`
69 logApiCall('GET', url)
70 try {
71 const headers = {
72 Accept: 'application/json',
73 }
74
75 const response = await fetch(url, {
76 credentials: 'include', // This is crucial for sending cookies
77 headers,
78 cache: 'no-cache', // Don't cache this request
79 })
80
81 logApiCall('GET', '/user', response.status)
82
83 if (!response.ok) {
84 if (response.status === 401) {
85 return null
86 }
87
88 // Try to get error details
89 let errorText = ''
90 try {
91 const errorData = await response.text()
92 errorText = errorData
93 } catch (e) {
94 // Ignore error reading error
95 }
96
97 throw new Error(
98 `Failed to get user: ${response.status} ${response.statusText} ${errorText}`,
99 )
100 }
101
102 return response.json()
103 } catch (error) {
104 logApiCall('GET', '/user', undefined, error)
105 if (
106 error instanceof TypeError &&
107 error.message.includes('Failed to fetch')
108 ) {
109 console.error('Network error - Unable to connect to API server')
110 }
111 throw error
112 }
113 },
114
115 // Get statuses
116 async getStatuses() {
117 const url = `${API_URL}/statuses`
118 logApiCall('GET', url)
119 const response = await fetch(url, {
120 credentials: 'include',
121 })
122
123 if (!response.ok) {
124 throw new Error('Failed to get statuses')
125 }
126
127 return response.json() as Promise<{
128 statuses: XyzStatusphereDefs.StatusView[]
129 }>
130 },
131
132 // Create status
133 async createStatus(status: string) {
134 const url = `${API_URL}/status`
135 logApiCall('POST', url)
136 const response = await fetch(url, {
137 method: 'POST',
138 headers: {
139 'Content-Type': 'application/json',
140 },
141 credentials: 'include',
142 body: JSON.stringify({ status }),
143 })
144
145 if (!response.ok) {
146 const error = await response.json()
147 throw new Error(error.error || 'Failed to create status')
148 }
149
150 return response.json()
151 },
152}
153
154export default api