feat(crew): add a coordinator "Check status" button to the crew form

For a coordinator addressed by pubkey, the form now probes it on demand (the
same useCoordinatorHealth the Settings → Coordinator card uses) and shows
Connected / Quiet / Not seen / Unreachable. Reuses the existing socialUi
.coordinator status copy; no new keys.
This commit is contained in:
SkateSpots 2026-10-03 17:41:13 +07:00
parent 3cc16d18ca
commit c93d7e4e41

View file

@ -24,7 +24,7 @@ import { NofferField } from '@/components/NofferField';
import { SKATE_CREW_KIND, SKATE_SPOT_KIND, SKATE_SHOP_KIND, parseAddrCoord, ZAP_GOAL_KIND, CREW_VIBE_LABELS, CREW_FOCUS_LABELS, CREW_FOCUS_DESCRIPTIONS, CREW_FOCUS_TAG, CREW_LOOKING_FOR_OPTIONS, CREW_ROLE_META, CREW_ROLE_ORDER, SHOW_CREW_TREASURY, isValidNoffer, type SkateCrew, type CrewFocus, type CrewRole } from '@/lib/types';
import { CREW_VIBE_KEYS, CREW_FOCUS_KEYS, CREW_FOCUS_DESC_KEYS, CREW_LOOKING_FOR_KEYS, CREW_ROLE_KEYS } from '@/lib/labelKeys';
import { useCrewGoal } from '@/hooks/useCrewGoal';
import { useCoordinator } from '@/hooks/useCoordinator';
import { useCoordinator, useCoordinatorHealth } from '@/hooks/useCoordinator';
import { Nip05HandlesEditor } from '@/components/Nip05HandlesEditor';
import { coordinatorAddUrl, crewCoordinatorInputValue, parseCrewCoordinatorInput, shortNpub } from '@/lib/coordinator';
import { useRelays } from '@/lib/relays';
@ -191,6 +191,16 @@ interface CrewFormProps {
existing?: SkateCrew | null;
}
/** Static map for coordinator health status → i18n key (no dynamic keys). */
const HEALTH_LABEL: Record<string, string> = {
idle: 'socialUi.coordinator.statusIdle',
checking: 'socialUi.coordinator.statusChecking',
online: 'socialUi.coordinator.statusOnline',
stale: 'socialUi.coordinator.statusStale',
offline: 'socialUi.coordinator.statusOffline',
error: 'socialUi.coordinator.statusError',
};
export function CrewForm({ existing }: CrewFormProps) {
const { t } = useTranslation();
const navigate = useNavigate();
@ -295,6 +305,17 @@ export function CrewForm({ existing }: CrewFormProps) {
const { coordinator: myCoordinator } = useCoordinator();
// Live parse so the founder gets feedback (and the publish path reuses it).
const parsedCoordinator = parseCrewCoordinatorInput(coordinatorInput);
// On-demand health probe (the "Check" button) for a coordinator addressed by
// pubkey — mirrors the Settings → Coordinator card.
const coordinatorHealth = useCoordinatorHealth(
parsedCoordinator?.pubkey
? {
pubkey: parsedCoordinator.pubkey,
relays: /^wss?:\/\//i.test(parsedCoordinator.url) ? [parsedCoordinator.url] : [],
addedAt: 0,
}
: null,
);
// Crew video / montage — YouTube or Instagram URL ("The Part").
const [videoUrl, setVideoUrl] = useState(existing?.videoUrl ?? '');
@ -1154,6 +1175,39 @@ export function CrewForm({ existing }: CrewFormProps) {
{t('socialUi.coordinator.useMine')}
</Button>
)}
{parsedCoordinator?.pubkey && (
<div className="flex items-center gap-2">
<Button
type="button"
variant="outline"
size="sm"
className="gap-1.5 border-gray-700 text-gray-300 hover:bg-white/5"
onClick={() => coordinatorHealth.check()}
disabled={coordinatorHealth.status === 'checking'}
>
{coordinatorHealth.status === 'checking' ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Server className="h-3.5 w-3.5" />
)}
{t('socialUi.coordinator.check')}
</Button>
<span
className={cn(
'text-[11px] font-bold uppercase',
coordinatorHealth.status === 'online'
? 'text-teal-400'
: coordinatorHealth.status === 'stale'
? 'text-amber-400'
: coordinatorHealth.status === 'error'
? 'text-red-400'
: 'text-gray-500',
)}
>
{t(HEALTH_LABEL[coordinatorHealth.status] ?? 'socialUi.coordinator.statusIdle')}
</span>
</div>
)}
<p className="text-[10px] leading-snug text-gray-600">{t('socialUi.coordinator.pointerNote')}</p>
</div>
);