A mobile app view of atmoquest
7.6 kB
249 lines
1import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
2import { apiFetch } from './client';
3
4// ─── Types ──────────────────────────────────────────────────────────
5
6export interface UserSession {
7 did: string;
8 handle: string;
9 isAdmin: boolean;
10}
11
12export interface Profile {
13 did: string;
14 handle: string;
15 displayName: string;
16 bio: string;
17 interests: string[];
18 links: { label: string; url: string }[];
19 location: string;
20 isHiring: boolean;
21 isLooking: boolean;
22}
23
24export interface Connection {
25 did: string;
26 handle: string;
27 displayName: string;
28 connectedAt: string;
29}
30
31export interface ConnectionNotes {
32 notes: string;
33 followUp: boolean;
34 updatedAt: string;
35}
36
37export interface Event {
38 token: string;
39 name: string;
40 description: string;
41 startDate: string;
42 endDate: string;
43 location: string;
44 lat?: number;
45 lng?: number;
46 radius?: number;
47 checkinCount: number;
48}
49
50export interface EventDetail extends Event {
51 attendees: { did: string; handle: string; displayName: string }[];
52 stats: {
53 uniqueConnectors: number;
54 totalCheckins: number;
55 attendeeCount: number;
56 };
57 isCheckedIn: boolean;
58}
59
60export interface Badge {
61 name: string;
62 earnedAt: string;
63 eventToken?: string;
64 svgUrl: string;
65}
66
67export interface Settings {
68 did: string;
69 handle: string;
70 showInAttendees: boolean;
71 showInLeaderboard: boolean;
72}
73
74// ─── Query Keys ─────────────────────────────────────────────────────
75
76export const queryKeys = {
77 session: ['session'] as const,
78 profile: ['profile'] as const,
79 userProfile: (did: string) => ['profile', did] as const,
80 connections: ['connections'] as const,
81 connectionNotes: (did: string) => ['connectionNotes', did] as const,
82 events: ['events'] as const,
83 eventDetail: (token: string) => ['event', token] as const,
84 eventStats: (token: string) => ['eventStats', token] as const,
85 badges: ['badges'] as const,
86 badgesRecent: ['badges', 'recent'] as const,
87 settings: ['settings'] as const,
88 settingsNotes: ['settings', 'notes'] as const,
89} as const;
90
91// ─── Auth ───────────────────────────────────────────────────────────
92
93export function useSession() {
94 return useQuery({
95 queryKey: queryKeys.session,
96 queryFn: () => apiFetch<UserSession>('/auth/session'),
97 retry: false,
98 staleTime: 5 * 60 * 1000,
99 });
100}
101
102// ─── Profile ────────────────────────────────────────────────────────
103
104export function useProfile() {
105 return useQuery({
106 queryKey: queryKeys.profile,
107 queryFn: () => apiFetch<Profile>('/profile'),
108 staleTime: 5 * 60 * 1000,
109 });
110}
111
112export function useUserProfile(did: string) {
113 return useQuery({
114 queryKey: queryKeys.userProfile(did),
115 queryFn: () => apiFetch<Profile>(`/users/${encodeURIComponent(did)}`),
116 staleTime: 5 * 60 * 1000,
117 });
118}
119
120export function useUpdateProfile() {
121 const queryClient = useQueryClient();
122 return useMutation({
123 mutationFn: (data: Partial<Profile>) =>
124 apiFetch<Profile>('/profile', { method: 'PUT', body: JSON.stringify(data) }),
125 onSuccess: () => {
126 queryClient.invalidateQueries({ queryKey: queryKeys.profile });
127 },
128 });
129}
130
131// ─── Connections ────────────────────────────────────────────────────
132
133export function useConnections() {
134 return useQuery({
135 queryKey: queryKeys.connections,
136 queryFn: () => apiFetch<Connection[]>('/connections'),
137 staleTime: 2 * 60 * 1000,
138 });
139}
140
141export function useCreateConnection() {
142 const queryClient = useQueryClient();
143 return useMutation({
144 mutationFn: (did: string) =>
145 apiFetch('/connections/' + encodeURIComponent(did), { method: 'POST' }),
146 onSuccess: () => {
147 queryClient.invalidateQueries({ queryKey: queryKeys.connections });
148 },
149 });
150}
151
152export function useConnectionNotes(did: string) {
153 return useQuery({
154 queryKey: queryKeys.connectionNotes(did),
155 queryFn: () => apiFetch<ConnectionNotes>(`/connections/${encodeURIComponent(did)}/notes`),
156 });
157}
158
159export function useSaveConnectionNotes() {
160 const queryClient = useQueryClient();
161 return useMutation({
162 mutationFn: ({ did, notes, followUp }: { did: string; notes: string; followUp: boolean }) =>
163 apiFetch(`/connections/${encodeURIComponent(did)}/notes`, {
164 method: 'PUT',
165 body: JSON.stringify({ notes, followUp }),
166 }),
167 onSuccess: (_, { did }) => {
168 queryClient.invalidateQueries({ queryKey: queryKeys.connectionNotes(did) });
169 queryClient.invalidateQueries({ queryKey: queryKeys.settingsNotes });
170 },
171 });
172}
173
174// ─── Events ─────────────────────────────────────────────────────────
175
176export function useEvents() {
177 return useQuery({
178 queryKey: queryKeys.events,
179 queryFn: () => apiFetch<Event[]>('/events'),
180 staleTime: 60 * 1000,
181 });
182}
183
184export function useEventDetail(token: string) {
185 return useQuery({
186 queryKey: queryKeys.eventDetail(token),
187 queryFn: () => apiFetch<EventDetail>(`/events/${encodeURIComponent(token)}`),
188 staleTime: 60 * 1000,
189 });
190}
191
192export function useEventStats(token: string) {
193 return useQuery({
194 queryKey: queryKeys.eventStats(token),
195 queryFn: () => apiFetch<EventDetail['stats']>(`/events/${encodeURIComponent(token)}/stats`),
196 refetchInterval: 5000,
197 });
198}
199
200export function useCheckin() {
201 const queryClient = useQueryClient();
202 return useMutation({
203 mutationFn: (token: string) =>
204 apiFetch(`/events/${encodeURIComponent(token)}/checkin`, { method: 'POST' }),
205 onSuccess: (_, token) => {
206 queryClient.invalidateQueries({ queryKey: queryKeys.eventDetail(token) });
207 queryClient.invalidateQueries({ queryKey: queryKeys.events });
208 },
209 });
210}
211
212// ─── Badges ─────────────────────────────────────────────────────────
213
214export function useBadges() {
215 return useQuery({
216 queryKey: queryKeys.badges,
217 queryFn: () => apiFetch<Badge[]>('/badges'),
218 staleTime: 5 * 60 * 1000,
219 });
220}
221
222export function useRecentBadges() {
223 return useQuery({
224 queryKey: queryKeys.badgesRecent,
225 queryFn: () => apiFetch<Badge[]>('/badges/recent'),
226 staleTime: 30 * 1000,
227 });
228}
229
230// ─── Settings ───────────────────────────────────────────────────────
231
232export function useSettings() {
233 return useQuery({
234 queryKey: queryKeys.settings,
235 queryFn: () => apiFetch<Settings>('/settings'),
236 staleTime: 5 * 60 * 1000,
237 });
238}
239
240export function useUpdatePrivacy() {
241 const queryClient = useQueryClient();
242 return useMutation({
243 mutationFn: (data: Partial<Pick<Settings, 'showInAttendees' | 'showInLeaderboard'>>) =>
244 apiFetch('/settings/privacy', { method: 'PUT', body: JSON.stringify(data) }),
245 onSuccess: () => {
246 queryClient.invalidateQueries({ queryKey: queryKeys.settings });
247 },
248 });
249}