feat: Add refresh locations button to participant list
- Sends silent push to all friends requesting location updates - Shows spinner while refreshing - Displays feedback message with count of pinged friends 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4ee33f4992
commit
4ab4f28e70
|
|
@ -308,6 +308,8 @@ export default function RoomPage() {
|
|||
<ParticipantList
|
||||
participants={participants}
|
||||
currentUserId={currentUser.name}
|
||||
roomSlug={slug}
|
||||
syncUrl={process.env.NEXT_PUBLIC_SYNC_URL}
|
||||
onClose={() => setShowParticipants(false)}
|
||||
onNavigateTo={handleNavigateTo}
|
||||
onSetMeetingPoint={() => setShowMeetingPoint(true)}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import type { Participant } from '@/types';
|
||||
|
||||
interface ParticipantListProps {
|
||||
participants: Participant[];
|
||||
currentUserId?: string;
|
||||
roomSlug: string;
|
||||
syncUrl?: string;
|
||||
onClose: () => void;
|
||||
onNavigateTo: (participant: Participant) => void;
|
||||
onSetMeetingPoint?: () => void;
|
||||
|
|
@ -13,10 +16,46 @@ interface ParticipantListProps {
|
|||
export default function ParticipantList({
|
||||
participants,
|
||||
currentUserId,
|
||||
roomSlug,
|
||||
syncUrl,
|
||||
onClose,
|
||||
onNavigateTo,
|
||||
onSetMeetingPoint,
|
||||
}: ParticipantListProps) {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [refreshMessage, setRefreshMessage] = useState<string | null>(null);
|
||||
|
||||
const handleRefreshLocations = useCallback(async () => {
|
||||
if (!syncUrl || isRefreshing) return;
|
||||
|
||||
setIsRefreshing(true);
|
||||
setRefreshMessage(null);
|
||||
|
||||
try {
|
||||
const httpUrl = syncUrl.replace('wss://', 'https://').replace('ws://', 'http://');
|
||||
const response = await fetch(`${httpUrl}/push/request-location`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ roomSlug }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
if (data.sent > 0) {
|
||||
setRefreshMessage(`Pinged ${data.sent} friend${data.sent > 1 ? 's' : ''}`);
|
||||
} else {
|
||||
setRefreshMessage('No friends to ping');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh locations:', error);
|
||||
setRefreshMessage('Failed to ping');
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
// Clear message after 3 seconds
|
||||
setTimeout(() => setRefreshMessage(null), 3000);
|
||||
}
|
||||
}, [syncUrl, roomSlug, isRefreshing]);
|
||||
const formatDistance = (participant: Participant, current: Participant | undefined) => {
|
||||
if (!participant.location || !current?.location) return null;
|
||||
|
||||
|
|
@ -50,21 +89,53 @@ export default function ParticipantList({
|
|||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-white/10">
|
||||
<h2 className="font-semibold">Friends ({participants.length})</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 rounded hover:bg-white/10 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Refresh locations button */}
|
||||
{syncUrl && (
|
||||
<button
|
||||
onClick={handleRefreshLocations}
|
||||
disabled={isRefreshing}
|
||||
className="p-1.5 rounded hover:bg-white/10 transition-colors disabled:opacity-50"
|
||||
title="Ping friends for location updates"
|
||||
>
|
||||
<svg
|
||||
className={`w-5 h-5 ${isRefreshing ? 'animate-spin' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 rounded hover:bg-white/10 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Refresh status message */}
|
||||
{refreshMessage && (
|
||||
<div className="px-4 py-2 bg-rmaps-primary/20 text-rmaps-primary text-sm text-center">
|
||||
{refreshMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Participant list */}
|
||||
<div className="flex-1 overflow-y-auto p-2">
|
||||
{participants.length === 0 ? (
|
||||
|
|
|
|||
Loading…
Reference in New Issue