Move production to beenvoice.app with migrated accounts, refreshed auth and timer UX, and expanded invoice flows.
Official URL migration preserves sessions, shortcuts prefs, and last clock-in client; auth screens match web with legal links; time clock and invoice editor/send flows are updated for the new domain and UI patterns. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
authStoragePrefix,
|
||||
buildAccountId,
|
||||
loadAccounts,
|
||||
loadActiveAccountId,
|
||||
loadDraftInstanceUrl,
|
||||
saveAccounts,
|
||||
saveActiveAccountId,
|
||||
saveDraftInstanceUrl,
|
||||
type SavedAccount,
|
||||
} from "@/lib/accounts";
|
||||
import { migrateAuthStorage } from "@/lib/auth-storage";
|
||||
import { DEFAULT_API_URL } from "@/lib/config";
|
||||
import { loadStoredInstanceUrl, saveStoredInstanceUrl } from "@/lib/instance-url";
|
||||
import {
|
||||
clearTimeClockPrefsForAccount,
|
||||
getLastTimeClockClientId,
|
||||
setLastTimeClockClientId,
|
||||
} from "@/lib/time-clock-prefs";
|
||||
|
||||
const LEGACY_OFFICIAL_HOSTS = ["beenvoice.soconnor.dev"];
|
||||
|
||||
export function isLegacyOfficialUrl(url: string): boolean {
|
||||
const trimmed = url.trim();
|
||||
if (!trimmed) return false;
|
||||
|
||||
try {
|
||||
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
||||
return LEGACY_OFFICIAL_HOSTS.includes(new URL(withProtocol).host);
|
||||
} catch {
|
||||
const host = trimmed.replace(/^https?:\/\//, "").replace(/\/$/, "").split("/")[0] ?? "";
|
||||
return LEGACY_OFFICIAL_HOSTS.includes(host);
|
||||
}
|
||||
}
|
||||
|
||||
export function migrateOfficialUrl(url: string): string {
|
||||
return isLegacyOfficialUrl(url) ? DEFAULT_API_URL : url;
|
||||
}
|
||||
|
||||
export async function migrateStoredOfficialUrls(): Promise<{
|
||||
accounts: SavedAccount[];
|
||||
activeAccountId: string | null;
|
||||
draftUrl: string | null;
|
||||
}> {
|
||||
const [accounts, activeId, draftUrl, storedInstanceUrl] = await Promise.all([
|
||||
loadAccounts(),
|
||||
loadActiveAccountId(),
|
||||
loadDraftInstanceUrl(),
|
||||
loadStoredInstanceUrl(),
|
||||
]);
|
||||
|
||||
let nextActiveId = activeId;
|
||||
const migratedAccounts: SavedAccount[] = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
if (!isLegacyOfficialUrl(account.instanceUrl)) {
|
||||
migratedAccounts.push(account);
|
||||
continue;
|
||||
}
|
||||
|
||||
const newUrl = DEFAULT_API_URL;
|
||||
const newId = buildAccountId(newUrl, account.userId);
|
||||
const oldPrefix = authStoragePrefix(account.id);
|
||||
const newPrefix = authStoragePrefix(newId);
|
||||
|
||||
if (oldPrefix !== newPrefix) {
|
||||
await migrateAuthStorage(oldPrefix, newPrefix);
|
||||
}
|
||||
|
||||
const lastClientId = await getLastTimeClockClientId(account.id);
|
||||
if (lastClientId && account.id !== newId) {
|
||||
await setLastTimeClockClientId(newId, lastClientId);
|
||||
await clearTimeClockPrefsForAccount(account.id);
|
||||
}
|
||||
|
||||
migratedAccounts.push({
|
||||
...account,
|
||||
id: newId,
|
||||
instanceUrl: newUrl,
|
||||
});
|
||||
|
||||
if (nextActiveId === account.id) {
|
||||
nextActiveId = newId;
|
||||
}
|
||||
}
|
||||
|
||||
const newDraft = draftUrl && isLegacyOfficialUrl(draftUrl) ? DEFAULT_API_URL : draftUrl;
|
||||
const accountsChanged = migratedAccounts.some(
|
||||
(account, index) =>
|
||||
account.id !== accounts[index]?.id || account.instanceUrl !== accounts[index]?.instanceUrl,
|
||||
);
|
||||
const activeChanged = nextActiveId !== activeId;
|
||||
const draftChanged = newDraft !== draftUrl;
|
||||
const instanceChanged =
|
||||
storedInstanceUrl != null &&
|
||||
isLegacyOfficialUrl(storedInstanceUrl) &&
|
||||
storedInstanceUrl !== DEFAULT_API_URL;
|
||||
|
||||
if (accountsChanged) await saveAccounts(migratedAccounts);
|
||||
if (activeChanged) await saveActiveAccountId(nextActiveId);
|
||||
if (draftChanged) await saveDraftInstanceUrl(newDraft);
|
||||
if (instanceChanged) await saveStoredInstanceUrl(DEFAULT_API_URL);
|
||||
|
||||
return {
|
||||
accounts: migratedAccounts,
|
||||
activeAccountId: nextActiveId,
|
||||
draftUrl: newDraft,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user