Add beenvoice mobile companion app with full dark mode support.
Expo app with dashboard, time clock, invoices, and settings — native tabs, glass UI, theme-aware components, and iOS Live Activities. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
const STORAGE_KEY = "beenvoice:instance-url";
|
||||
|
||||
export function normalizeInstanceUrl(input: string): string | null {
|
||||
const trimmed = input.trim().replace(/\/$/, "");
|
||||
if (!trimmed) return null;
|
||||
|
||||
let url = trimmed;
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
const isLocal =
|
||||
/^(localhost|127\.|192\.168\.|10\.|172\.(1[6-9]|2\d|3[01])\.)/i.test(url);
|
||||
url = `${isLocal ? "http" : "https"}://${url}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (!parsed.hostname) return null;
|
||||
return `${parsed.protocol}//${parsed.host}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadStoredInstanceUrl(): Promise<string | null> {
|
||||
const stored = await AsyncStorage.getItem(STORAGE_KEY);
|
||||
if (!stored) return null;
|
||||
return normalizeInstanceUrl(stored) ?? stored.replace(/\/$/, "");
|
||||
}
|
||||
|
||||
export async function saveStoredInstanceUrl(url: string): Promise<string> {
|
||||
const normalized = normalizeInstanceUrl(url);
|
||||
if (!normalized) {
|
||||
throw new Error("Enter a valid server URL (e.g. beenvoice.app or localhost:3000)");
|
||||
}
|
||||
await AsyncStorage.setItem(STORAGE_KEY, normalized);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export async function clearStoredInstanceUrl(): Promise<void> {
|
||||
await AsyncStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
Reference in New Issue
Block a user