1. GO_BACK warning & stuck screens
When a deep link or direct push put the user on /feed/[id],
/profile/[address], /compose, or /settings without any prior stack
entry, tapping the header chevron emitted:
"ERROR The action 'GO_BACK' was not handled by any navigator"
and did nothing — user was stuck.
New helper lib/utils.safeBack(fallback = '/(app)/chats') wraps
router.canGoBack() — when there's history it pops; otherwise it
replace-navigates to a sensible fallback (chats list by default,
'/' for auth screens so we land back at the onboarding).
Applied to every header chevron and back-from-detail flow:
app/(app)/chats/[id], app/(app)/compose, app/(app)/feed/[id]
(header + onDeleted), app/(app)/feed/tag/[tag],
app/(app)/profile/[address], app/(app)/new-contact (header + OK
button on request-sent alert), app/(app)/settings,
app/(auth)/create, app/(auth)/import.
2. Prevent self-contact-request
new-contact.tsx now compares the resolved address against
keyFile.pub_key at two points:
- right after resolveUsername + getIdentity in search() — before
the profile card even renders, so the user doesn't see the
"Send request" CTA for themselves.
- inside sendRequest() as a belt-and-braces guard in case the
check was somehow bypassed.
The search path shows an inline error ("That's you. You can't
send a contact request to yourself."); sendRequest falls back to
an Alert with the same meaning. Both compare case-insensitively
against the pubkey hex so mixed-case pastes work.
Technically the server would still accept a self-request (the
chain stores it under contact_in:<self>:<self>), but it's a dead-
end UX-wise — the user can't chat with themselves — so the client
blocks it preemptively instead of letting users pay the fee for
nothing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { clsx, type ClassValue } from 'clsx';
|
|
import { twMerge } from 'tailwind-merge';
|
|
import { router } from 'expo-router';
|
|
|
|
/**
|
|
* Navigate back, or fall back to a sensible default route if there's
|
|
* no screen to pop to.
|
|
*
|
|
* Without this, an opened route entered via deep link / direct push
|
|
* (profile, feed/[id], etc.) would emit the "action 'GO_BACK' was not
|
|
* handled by any navigator" dev warning and do nothing — user ends up
|
|
* stuck. Default fallback is the chats list (root of the app).
|
|
*/
|
|
export function safeBack(fallback: string = '/(app)/chats'): void {
|
|
if (router.canGoBack()) {
|
|
router.back();
|
|
} else {
|
|
router.replace(fallback as never);
|
|
}
|
|
}
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
/** Format µT amount to human-readable string */
|
|
export function formatAmount(microTokens: number | undefined | null): string {
|
|
if (microTokens == null) return '—';
|
|
if (microTokens >= 1_000_000) return `${(microTokens / 1_000_000).toFixed(2)} T`;
|
|
if (microTokens >= 1_000) return `${(microTokens / 1_000).toFixed(1)} mT`;
|
|
return `${microTokens} µT`;
|
|
}
|
|
|
|
/** Format unix seconds to relative time */
|
|
export function relativeTime(unixSeconds: number | undefined | null): string {
|
|
if (!unixSeconds) return '';
|
|
const diff = Date.now() / 1000 - unixSeconds;
|
|
if (diff < 60) return 'just now';
|
|
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
return new Date(unixSeconds * 1000).toLocaleDateString();
|
|
}
|
|
|
|
/** Format unix seconds to HH:MM */
|
|
export function formatTime(unixSeconds: number | undefined | null): string {
|
|
if (!unixSeconds) return '';
|
|
return new Date(unixSeconds * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
|
|
/** Generate a random nonce string */
|
|
export function randomId(): string {
|
|
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
}
|