refactor: 提取 useGeolocation hook 和 joinRoom 工具函数

- useGeolocation: 将 PanicPage 中 ~50 行 GPS 定位逻辑(requestGps + reverseGeocode + 状态管理)提取为独立 hook
- joinRoom: 统一 3 处重复的 POST /api/room/:id/join 调用(room、invite、panic 页面)
This commit is contained in:
2026-02-26 19:49:05 +08:00
parent 0c5676493e
commit 07541ed686
5 changed files with 102 additions and 98 deletions
+70
View File
@@ -0,0 +1,70 @@
"use client";
import { useState, useEffect, useCallback } from "react";
export type GpsStatus = "idle" | "locating" | "success" | "failed" | "denied";
type GpsResult =
| { ok: true; lat: number; lng: number }
| { ok: false; reason: "unsupported" | "denied" | "timeout" | "unknown" };
function requestGps(): Promise<GpsResult> {
return new Promise((resolve) => {
if (!navigator.geolocation) {
resolve({ ok: false, reason: "unsupported" });
return;
}
navigator.geolocation.getCurrentPosition(
(pos) =>
resolve({ ok: true, lat: pos.coords.latitude, lng: pos.coords.longitude }),
(err) => {
const reason =
err.code === err.PERMISSION_DENIED
? "denied"
: err.code === err.TIMEOUT
? "timeout"
: "unknown";
resolve({ ok: false, reason });
},
{ timeout: 8000, enableHighAccuracy: false },
);
});
}
async function reverseGeocode(lat: number, lng: number): Promise<string | null> {
try {
const res = await fetch(`/api/location/regeo?lat=${lat}&lng=${lng}`);
const data = await res.json();
return data.name || data.formatted || null;
} catch {
return null;
}
}
export function useGeolocation() {
const [status, setStatus] = useState<GpsStatus>("idle");
const [coords, setCoords] = useState<{ lat: number; lng: number } | null>(null);
const [locationName, setLocationName] = useState<string | null>(null);
const locate = useCallback(async () => {
setStatus("locating");
const result = await requestGps();
if (result.ok) {
setCoords({ lat: result.lat, lng: result.lng });
setStatus("success");
const name = await reverseGeocode(result.lat, result.lng);
if (name) setLocationName(name);
} else {
setCoords(null);
setLocationName(null);
setStatus(result.reason === "denied" ? "denied" : "failed");
}
}, []);
useEffect(() => {
locate();
}, [locate]);
return { status, coords, locationName, retry: locate };
}