Redesign mobile time clock, add shortcuts, and improve account management.

Add iOS Shortcuts/Siri intents, local send-reminder notifications, stable
client picker with last-client defaults, account refresh/remove, and softer
session handling on unauthorized API responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-22 16:06:17 -04:00
parent 0b2d65a4e9
commit 06bc91ac13
33 changed files with 1844 additions and 320 deletions
+38 -3
View File
@@ -21,7 +21,14 @@ import {
type SavedAccount,
} from "@/lib/accounts";
import { setRuntimeApiUrl, getApiUrl, DEFAULT_API_URL } from "@/lib/config";
import { clearAuthStorage, readStoredSessionUser } from "@/lib/auth-storage";
import { normalizeInstanceUrl, saveStoredInstanceUrl } from "@/lib/instance-url";
import { clearTimeClockPrefsForAccount } from "@/lib/time-clock-prefs";
export type RemoveAccountResult = {
wasActive: boolean;
remainingCount: number;
};
type AccountsContextValue = {
accounts: SavedAccount[];
@@ -37,7 +44,8 @@ type AccountsContextValue = {
email: string;
name: string;
}) => Promise<SavedAccount>;
removeAccount: (accountId: string) => Promise<void>;
removeAccount: (accountId: string) => Promise<RemoveAccountResult>;
refreshAccounts: () => Promise<void>;
clearActiveAccount: () => Promise<void>;
};
@@ -148,12 +156,17 @@ export function AccountsProvider({ children }: { children: ReactNode }) {
);
const removeAccount = useCallback(
async (accountId: string) => {
async (accountId: string): Promise<RemoveAccountResult> => {
const wasActive = activeAccountId === accountId;
await clearAuthStorage(authStoragePrefix(accountId));
await clearTimeClockPrefsForAccount(accountId);
const nextAccounts = accounts.filter((account) => account.id !== accountId);
setAccounts(nextAccounts);
await saveAccounts(nextAccounts);
if (activeAccountId === accountId) {
if (wasActive) {
const fallback = nextAccounts[0] ?? null;
await saveActiveAccountId(fallback?.id ?? null);
setActiveAccountId(fallback?.id ?? null);
@@ -162,10 +175,30 @@ export function AccountsProvider({ children }: { children: ReactNode }) {
setApiUrl(fallback.instanceUrl);
}
}
return { wasActive, remainingCount: nextAccounts.length };
},
[accounts, activeAccountId],
);
const refreshAccounts = useCallback(async () => {
const stored = await loadAccounts();
const refreshed = await Promise.all(
stored.map(async (account) => {
const user = await readStoredSessionUser(authStoragePrefix(account.id));
if (!user?.name && !user?.email) return account;
return {
...account,
name: user.name?.trim() || account.name,
email: user.email?.trim() || account.email,
};
}),
);
setAccounts(refreshed);
await saveAccounts(refreshed);
}, []);
const clearActiveAccount = useCallback(async () => {
await saveActiveAccountId(null);
setActiveAccountId(null);
@@ -184,6 +217,7 @@ export function AccountsProvider({ children }: { children: ReactNode }) {
switchAccount,
registerAccount,
removeAccount,
refreshAccounts,
clearActiveAccount,
}),
[
@@ -195,6 +229,7 @@ export function AccountsProvider({ children }: { children: ReactNode }) {
switchAccount,
registerAccount,
removeAccount,
refreshAccounts,
clearActiveAccount,
],
);