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,67 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
const ACCOUNTS_KEY = "beenvoice:accounts";
|
||||
const ACTIVE_ACCOUNT_KEY = "beenvoice:active-account-id";
|
||||
const DRAFT_INSTANCE_URL_KEY = "beenvoice:draft-instance-url";
|
||||
|
||||
export type SavedAccount = {
|
||||
id: string;
|
||||
instanceUrl: string;
|
||||
userId: string;
|
||||
email: string;
|
||||
name: string;
|
||||
lastUsedAt: number;
|
||||
};
|
||||
|
||||
export function buildAccountId(instanceUrl: string, userId: string) {
|
||||
const host = instanceUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
||||
return `${host}::${userId}`;
|
||||
}
|
||||
|
||||
export function authStoragePrefix(accountId: string) {
|
||||
return `beenvoice:auth:${accountId}`;
|
||||
}
|
||||
|
||||
export async function loadAccounts(): Promise<SavedAccount[]> {
|
||||
const raw = await AsyncStorage.getItem(ACCOUNTS_KEY);
|
||||
if (!raw) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as SavedAccount[];
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveAccounts(accounts: SavedAccount[]) {
|
||||
await AsyncStorage.setItem(ACCOUNTS_KEY, JSON.stringify(accounts));
|
||||
}
|
||||
|
||||
export async function loadActiveAccountId(): Promise<string | null> {
|
||||
return AsyncStorage.getItem(ACTIVE_ACCOUNT_KEY);
|
||||
}
|
||||
|
||||
export async function saveActiveAccountId(accountId: string | null) {
|
||||
if (accountId) {
|
||||
await AsyncStorage.setItem(ACTIVE_ACCOUNT_KEY, accountId);
|
||||
} else {
|
||||
await AsyncStorage.removeItem(ACTIVE_ACCOUNT_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadDraftInstanceUrl(): Promise<string | null> {
|
||||
return AsyncStorage.getItem(DRAFT_INSTANCE_URL_KEY);
|
||||
}
|
||||
|
||||
export async function saveDraftInstanceUrl(url: string | null) {
|
||||
if (url) {
|
||||
await AsyncStorage.setItem(DRAFT_INSTANCE_URL_KEY, url);
|
||||
} else {
|
||||
await AsyncStorage.removeItem(DRAFT_INSTANCE_URL_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
export async function hasConfiguredInstanceUrl(): Promise<boolean> {
|
||||
const [accounts, draft] = await Promise.all([loadAccounts(), loadDraftInstanceUrl()]);
|
||||
return accounts.length > 0 || Boolean(draft);
|
||||
}
|
||||
Reference in New Issue
Block a user