fix(mobile-ux): Mints in the nav, instant directions, no stray signer prompt
- Nav: MIDDLE_SLOTS 3->4 and add 'mints' to the default middle + desktop tabs, so the Cashu mints directory is reachable on the phone out of the box (also still linked from Wallet; any of the 4 slots can be swapped via long-press). - Directions: RouteDialog now seeds the origin from the last known location and draws immediately, then refines with a live fix — no more hanging on 'Finding your location…'. Shortened the web geolocation fallback 15s -> 8s. - Amber: /api/ai/status is now public (it only reports a small balance), so showing the AI balance no longer signs NIP-98 / opens the signer. The signer is reserved for actions that actually need it.
This commit is contained in:
parent
7969e8e9de
commit
f95e2233c8
5 changed files with 25 additions and 19 deletions
|
|
@ -202,10 +202,9 @@ function json(body, status, headers) {
|
|||
* simpler "low balance" signal than wiring a Routstr webhook, which would need a
|
||||
* notification channel (a signing key) we don't otherwise have server-side.
|
||||
*/
|
||||
async function handleStatus(request, headers) {
|
||||
const pubkey = verifyNip98(request.headers.get('x-sasquatch-auth'));
|
||||
if (!pubkey) return json({ error: 'auth required' }, 401, headers);
|
||||
if (!PLATFORM_OWNER_PUBKEYS.has(pubkey)) return json({ error: 'forbidden' }, 403, headers);
|
||||
async function handleStatus(_request, headers) {
|
||||
// Public on purpose: this is just a small prepaid balance, and requiring a
|
||||
// NIP-98 signature would make the signer pop up merely to display a number.
|
||||
if (!AI_KEY) return json({ configured: false }, 200, headers);
|
||||
try {
|
||||
const r = await fetch(`${AI_BASE}/balance/info`, {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getCurrentPosition, GeolocationError } from '@/lib/geolocation';
|
||||
import { getLastLocation } from '@/lib/lastLocation';
|
||||
import {
|
||||
fetchRoute,
|
||||
fetchTransitRoute,
|
||||
|
|
@ -123,10 +124,18 @@ export function RouteDialog({
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && !origin && !locating && !locError) {
|
||||
locate();
|
||||
if (!open) return;
|
||||
// Seed from the last known fix so a route can be drawn immediately, then
|
||||
// refine with a live reading. This is what makes opening Directions feel
|
||||
// instant instead of hanging on "Finding your location…".
|
||||
if (!origin) {
|
||||
const cached = getLastLocation();
|
||||
if (cached) setOrigin({ lat: cached.lat, lng: cached.lng });
|
||||
}
|
||||
}, [open, origin, locating, locError, locate]);
|
||||
locate();
|
||||
// Re-run only when the dialog opens (locate is stable).
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open]);
|
||||
|
||||
// Reset transient state when closed
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -69,10 +69,11 @@ export const NAV_CATALOG: NavTab[] = [
|
|||
];
|
||||
|
||||
/** Number of user-customizable middle slots. */
|
||||
export const MIDDLE_SLOTS = 3;
|
||||
export const MIDDLE_SLOTS = 4;
|
||||
|
||||
/** Default middle tabs (matches the app's original hardcoded bar). */
|
||||
export const DEFAULT_TAB_IDS = ['lab', 'events', 'crews'];
|
||||
/** Default middle tabs. Includes Mints (Cashu mint directory) so it's reachable
|
||||
* from the phone nav out of the box; users can swap any of them via long-press. */
|
||||
export const DEFAULT_TAB_IDS = ['lab', 'events', 'crews', 'mints'];
|
||||
|
||||
const STORAGE_KEY = 'skatespots:nav-tabs:v1';
|
||||
|
||||
|
|
@ -82,8 +83,8 @@ const STORAGE_KEY = 'skatespots:nav-tabs:v1';
|
|||
// order; anything not shown still lives in the "More" dropdown, so nothing is
|
||||
// ever lost. Map is always the first primary tab and cannot be hidden/moved.
|
||||
|
||||
/** Default desktop primary tabs (matches the original hardcoded header). */
|
||||
export const DEFAULT_DESKTOP_IDS = ['lab', 'events', 'crews'];
|
||||
/** Default desktop primary tabs. */
|
||||
export const DEFAULT_DESKTOP_IDS = ['lab', 'events', 'crews', 'mints'];
|
||||
|
||||
const DESKTOP_STORAGE_KEY = 'skatespots:desktop-nav:v1';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { useQuery } from '@tanstack/react-query';
|
|||
import { Capacitor } from '@capacitor/core';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
|
||||
import { buildProAuthHeaders } from '@/lib/proAuth';
|
||||
|
||||
/** Report shape from the proxy's owner-only `/api/ai/status`. */
|
||||
export interface SasquatchAiStatus {
|
||||
|
|
@ -31,13 +30,11 @@ export function useSasquatchAiStatus(options: { enabled?: boolean } = {}) {
|
|||
refetchInterval: 5 * 60 * 1000,
|
||||
staleTime: 60 * 1000,
|
||||
queryFn: async () => {
|
||||
const authHeaders = await buildProAuthHeaders({
|
||||
pubkey: user!.pubkey,
|
||||
signEvent: (e) => user!.signer.signEvent(e),
|
||||
});
|
||||
// No signature required — see handleStatus(); avoids an Amber prompt just
|
||||
// to show a balance.
|
||||
const res = await fetch(AI_STATUS_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: '{}',
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ function getWebPosition(): Promise<GeoCoords> {
|
|||
settled = true;
|
||||
cleanup();
|
||||
reject(new GeolocationError('timeout', 'Location request timed out.'));
|
||||
}, 15000);
|
||||
}, 8000);
|
||||
|
||||
// 1) watchPosition — first update wins (fast network fix).
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue