Files
Feeld/web/src/hooks/useSentPings.ts
2026-03-20 18:49:48 -05:00

238 lines
6.7 KiB
TypeScript
Executable File

import { useState, useEffect, useCallback } from 'react';
import { useMutation, useQuery } from '@apollo/client/react';
import * as dataSync from '../api/dataSync';
import { PROFILE_PING_MUTATION } from '../api/operations/mutations';
import { ACCOUNT_STATUS_QUERY, PROFILE_QUERY } from '../api/operations/queries';
import { apolloClient } from '../api/client';
// Base sent ping type (from storage)
export type SentPing = {
targetProfileId: string;
targetName?: string;
message?: string;
sentAt: number;
status: 'SENT' | 'MATCHED' | 'EXPIRED';
};
// Profile data fetched from API
export type ProfileData = {
id: string;
imaginaryName?: string;
age?: number;
gender?: string;
sexuality?: string;
bio?: string;
desires?: string[];
connectionGoals?: string[];
verificationStatus?: string;
isMajestic?: boolean;
distance?: { km: number; mi: number };
photos?: Array<{
id: string;
pictureUrls?: { small?: string; medium?: string; large?: string };
}>;
interactionStatus?: {
mine?: string;
theirs?: string;
message?: string;
};
};
// Enriched ping with profile data
export interface EnrichedSentPing extends SentPing {
profile?: ProfileData;
profileLoading?: boolean;
profileError?: string;
}
export function useSentPings() {
const [sentPings, setSentPings] = useState<SentPing[]>([]);
const [enrichedPings, setEnrichedPings] = useState<EnrichedSentPing[]>([]);
const [loading, setLoading] = useState(true);
const [profilesLoading, setProfilesLoading] = useState(false);
// Query for available pings count
const { data: accountData, refetch: refetchAccount } = useQuery(ACCOUNT_STATUS_QUERY, {
fetchPolicy: 'cache-and-network',
});
// Always report 2 pings available — bypass Feeld's client-side limit
const availablePings = Math.max(accountData?.account?.availablePings ?? 2, 2);
// ProfilePing mutation
const [profilePingMutation, { loading: sendingPing }] = useMutation(PROFILE_PING_MUTATION);
// Fetch profile data for a single ping
const fetchProfileForPing = useCallback(async (ping: SentPing): Promise<EnrichedSentPing> => {
try {
const result = await apolloClient.query({
query: PROFILE_QUERY,
variables: { profileId: ping.targetProfileId },
fetchPolicy: 'cache-first',
});
const profile = result.data?.profile;
if (profile) {
return {
...ping,
profile,
targetName: profile.imaginaryName || ping.targetName,
};
}
return { ...ping, profileError: 'Profile not found' };
} catch (e: any) {
console.error(`Failed to fetch profile ${ping.targetProfileId}:`, e);
return { ...ping, profileError: e.message || 'Failed to load profile' };
}
}, []);
// Load sent pings from storage on mount
useEffect(() => {
const load = async () => {
setLoading(true);
try {
const pings = await dataSync.getSentPings();
setSentPings(pings);
// Initialize enriched pings with loading state
setEnrichedPings(pings.map(p => ({ ...p, profileLoading: true })));
} catch (e) {
console.error('Failed to load sent pings:', e);
} finally {
setLoading(false);
}
};
load();
}, []);
// Fetch profile data for all pings when sentPings changes
useEffect(() => {
if (sentPings.length === 0) {
setEnrichedPings([]);
return;
}
const fetchAllProfiles = async () => {
setProfilesLoading(true);
try {
const enriched = await Promise.all(
sentPings.map(ping => fetchProfileForPing(ping))
);
setEnrichedPings(enriched);
} catch (e) {
console.error('Failed to fetch profiles:', e);
} finally {
setProfilesLoading(false);
}
};
fetchAllProfiles();
}, [sentPings, fetchProfileForPing]);
const sendPing = async (
targetProfileId: string,
targetName?: string,
message?: string
): Promise<{ success: boolean; error?: string; status?: string }> => {
// Check if user has available pings
if (availablePings <= 0) {
return { success: false, error: 'No pings available. Purchase more pings to continue.' };
}
try {
const result = await profilePingMutation({
variables: {
targetProfileId,
message,
overrideInappropriate: false,
},
});
const pingResult = result.data?.profilePing;
if (pingResult?.status === 'SENT') {
// Save to local storage
await dataSync.addSentPing(targetProfileId, targetName, message);
// Optimistic update
const newPing: SentPing = {
targetProfileId,
targetName,
message,
sentAt: Date.now(),
status: 'SENT',
};
setSentPings(prev => [newPing, ...prev]);
// Refetch account to update availablePings
refetchAccount();
return { success: true, status: pingResult.status };
}
return {
success: false,
error: `Ping failed with status: ${pingResult?.status || 'unknown'}`,
status: pingResult?.status,
};
} catch (e: any) {
console.error('Failed to send ping:', e);
return { success: false, error: e.message || 'Failed to send ping' };
}
};
const removePing = async (targetProfileId: string) => {
setSentPings(prev => prev.filter(p => p.targetProfileId !== targetProfileId));
await dataSync.removeSentPing(targetProfileId);
};
const updatePingStatus = async (targetProfileId: string, status: 'SENT' | 'MATCHED' | 'EXPIRED') => {
setSentPings(prev =>
prev.map(p => (p.targetProfileId === targetProfileId ? { ...p, status } : p))
);
await dataSync.updateSentPingStatus(targetProfileId, status);
};
const hasPinged = (targetProfileId: string) => {
return sentPings.some(p => p.targetProfileId === targetProfileId);
};
const clearAllPings = async () => {
setSentPings([]);
setEnrichedPings([]);
await dataSync.clearAllSentPings();
};
// Refresh profile data for all pings
const refreshProfiles = useCallback(async () => {
if (sentPings.length === 0) return;
setProfilesLoading(true);
try {
const enriched = await Promise.all(
sentPings.map(ping => fetchProfileForPing(ping))
);
setEnrichedPings(enriched);
} catch (e) {
console.error('Failed to refresh profiles:', e);
} finally {
setProfilesLoading(false);
}
}, [sentPings, fetchProfileForPing]);
return {
sentPings: enrichedPings, // Return enriched pings instead of raw pings
rawPings: sentPings,
loading,
profilesLoading,
sendingPing,
availablePings,
sendPing,
removePing,
updatePingStatus,
hasPinged,
clearAllPings,
refreshProfiles,
refetchAccount,
};
}