Initial commit
This commit is contained in:
237
web/src/hooks/useSentPings.ts
Executable file
237
web/src/hooks/useSentPings.ts
Executable file
@@ -0,0 +1,237 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user