diff --git a/components/AccountOverrideBadge.tsx b/components/AccountOverrideBadge.tsx
new file mode 100644
index 000000000..f36f2d8df
--- /dev/null
+++ b/components/AccountOverrideBadge.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import { X } from "lucide-react";
+import { useAccountOverride } from "@/providers/AccountOverrideProvider";
+
+/**
+ * Displays a pill badge when an account override is active.
+ * Reads from AccountOverrideProvider context.
+ */
+export default function AccountOverrideBadge() {
+ const { email, accountIdOverride, clear } = useAccountOverride();
+
+ if (!accountIdOverride || !email) return null;
+
+ return (
+
+ Viewing as
+ {email}
+
+
+ );
+}
diff --git a/hooks/useVercelChat.ts b/hooks/useVercelChat.ts
index 5990ba669..5e66d9845 100644
--- a/hooks/useVercelChat.ts
+++ b/hooks/useVercelChat.ts
@@ -13,6 +13,7 @@ import { UIMessage, FileUIPart } from "ai";
import useAvailableModels from "./useAvailableModels";
import { useLocalStorage } from "usehooks-ts";
import { DEFAULT_MODEL } from "@/lib/consts";
+import { useAccountOverride } from "@/providers/AccountOverrideProvider";
import { usePaymentProvider } from "@/providers/PaymentProvider";
import useArtistFilesForMentions from "@/hooks/useArtistFilesForMentions";
import type { KnowledgeBaseEntry } from "@/lib/supabase/getArtistKnowledge";
@@ -163,15 +164,18 @@ export function useVercelChat({
return outputs;
}, [knowledgeFiles]);
+ const { accountIdOverride } = useAccountOverride();
+
const chatRequestBody = useMemo(
() => ({
roomId: id,
artistId,
// Only include organizationId if it's not null (schema expects string | undefined)
...(organizationId && { organizationId }),
+ ...(accountIdOverride && { accountId: accountIdOverride }),
model,
}),
- [id, artistId, organizationId, model],
+ [id, artistId, organizationId, accountIdOverride, model],
);
const { messages, status, stop, sendMessage, setMessages, regenerate } =
diff --git a/lib/accounts/fetchAccountIdByEmail.ts b/lib/accounts/fetchAccountIdByEmail.ts
new file mode 100644
index 000000000..306e7df19
--- /dev/null
+++ b/lib/accounts/fetchAccountIdByEmail.ts
@@ -0,0 +1,25 @@
+import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl";
+
+/**
+ * Fetches an account ID by email address via GET /api/accounts/{email}.
+ *
+ * @param email - The email address to look up
+ * @param accessToken - Bearer token for authentication
+ * @returns The account ID, or null if not found
+ */
+export async function fetchAccountIdByEmail(
+ email: string,
+ accessToken: string,
+): Promise
{
+ const baseUrl = getClientApiBaseUrl();
+ const response = await fetch(
+ `${baseUrl}/api/accounts/${encodeURIComponent(email)}`,
+ { headers: { Authorization: `Bearer ${accessToken}` } },
+ );
+
+ if (response.status === 404) return null;
+ if (!response.ok) throw new Error(`Account lookup failed: ${response.status}`);
+
+ const data = await response.json();
+ return data.account?.account_id ?? null;
+}
diff --git a/lib/accounts/override/clearStoredAccountOverride.ts b/lib/accounts/override/clearStoredAccountOverride.ts
new file mode 100644
index 000000000..ffe396a42
--- /dev/null
+++ b/lib/accounts/override/clearStoredAccountOverride.ts
@@ -0,0 +1,9 @@
+import { ACCOUNT_OVERRIDE_STORAGE_KEY } from "@/lib/consts";
+
+/**
+ * Removes the account override from session storage.
+ */
+export function clearStoredAccountOverride(): void {
+ window.sessionStorage.removeItem(ACCOUNT_OVERRIDE_STORAGE_KEY);
+ window.sessionStorage.removeItem(`${ACCOUNT_OVERRIDE_STORAGE_KEY}_email`);
+}
diff --git a/lib/accounts/override/getStoredAccountOverride.ts b/lib/accounts/override/getStoredAccountOverride.ts
new file mode 100644
index 000000000..f7aaa69ca
--- /dev/null
+++ b/lib/accounts/override/getStoredAccountOverride.ts
@@ -0,0 +1,15 @@
+import { ACCOUNT_OVERRIDE_STORAGE_KEY } from "@/lib/consts";
+
+/**
+ * Reads the stored account override from session storage.
+ */
+export function getStoredAccountOverride(): {
+ accountId: string | null;
+ email: string | null;
+} {
+ if (typeof window === "undefined") return { accountId: null, email: null };
+ return {
+ accountId: window.sessionStorage.getItem(ACCOUNT_OVERRIDE_STORAGE_KEY),
+ email: window.sessionStorage.getItem(`${ACCOUNT_OVERRIDE_STORAGE_KEY}_email`),
+ };
+}
diff --git a/lib/accounts/override/setStoredAccountOverride.ts b/lib/accounts/override/setStoredAccountOverride.ts
new file mode 100644
index 000000000..7e0f436fd
--- /dev/null
+++ b/lib/accounts/override/setStoredAccountOverride.ts
@@ -0,0 +1,12 @@
+import { ACCOUNT_OVERRIDE_STORAGE_KEY } from "@/lib/consts";
+
+/**
+ * Persists an account override to session storage.
+ */
+export function setStoredAccountOverride(
+ accountId: string,
+ email: string,
+): void {
+ window.sessionStorage.setItem(ACCOUNT_OVERRIDE_STORAGE_KEY, accountId);
+ window.sessionStorage.setItem(`${ACCOUNT_OVERRIDE_STORAGE_KEY}_email`, email);
+}
diff --git a/lib/consts.ts b/lib/consts.ts
index df817746f..0323e5e86 100644
--- a/lib/consts.ts
+++ b/lib/consts.ts
@@ -5,6 +5,7 @@ export const NEW_API_BASE_URL = IS_PROD
? "https://recoup-api.vercel.app"
: "https://test-recoup-api.vercel.app";
export const API_OVERRIDE_STORAGE_KEY = "recoup_api_override";
+export const ACCOUNT_OVERRIDE_STORAGE_KEY = "recoup_account_override";
export const IN_PROCESS_PROTOCOL_ADDRESS = IS_PROD
? ("0x540C18B7f99b3b599c6FeB99964498931c211858" as Address)
: ("0x6832A997D8616707C7b68721D6E9332E77da7F6C" as Address);
diff --git a/providers/AccountOverrideProvider.tsx b/providers/AccountOverrideProvider.tsx
new file mode 100644
index 000000000..02117f27e
--- /dev/null
+++ b/providers/AccountOverrideProvider.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+import { createContext, useContext, useState, useCallback, ReactNode } from "react";
+import { useSearchParams, useRouter } from "next/navigation";
+import { usePrivy } from "@privy-io/react-auth";
+import { useQuery } from "@tanstack/react-query";
+import { fetchAccountIdByEmail } from "@/lib/accounts/fetchAccountIdByEmail";
+import { getStoredAccountOverride } from "@/lib/accounts/override/getStoredAccountOverride";
+import { setStoredAccountOverride } from "@/lib/accounts/override/setStoredAccountOverride";
+import { clearStoredAccountOverride } from "@/lib/accounts/override/clearStoredAccountOverride";
+
+interface AccountOverrideContextType {
+ accountIdOverride: string | null;
+ email: string | null;
+ clear: () => void;
+}
+
+const AccountOverrideContext = createContext({
+ accountIdOverride: null,
+ email: null,
+ clear: () => {},
+});
+
+/**
+ * Provider that manages the account override lifecycle.
+ * Reads ?email= from URL, resolves to accountId, persists in session storage.
+ * Single source of truth for all override consumers.
+ * Placed inside PrivyProvider because it needs getAccessToken.
+ */
+export function AccountOverrideProvider({ children }: { children: ReactNode }) {
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const { getAccessToken } = usePrivy();
+ const emailParam = searchParams.get("email");
+
+ const [stored, setStored] = useState(getStoredAccountOverride);
+ const email = emailParam || stored.email;
+ const isClear = emailParam === "clear";
+
+ const { data: resolvedAccountId } = useQuery({
+ queryKey: ["accountOverride", email],
+ queryFn: async () => {
+ if (isClear) {
+ clearStoredAccountOverride();
+ setStored({ accountId: null, email: null });
+ return null;
+ }
+ const accessToken = await getAccessToken();
+ if (!accessToken) return null;
+ const accountId = await fetchAccountIdByEmail(email!, accessToken);
+ if (accountId && email) {
+ setStoredAccountOverride(accountId, email);
+ setStored({ accountId, email });
+ }
+ return accountId;
+ },
+ enabled: (!!email || isClear) && !stored.accountId,
+ staleTime: Infinity,
+ });
+
+ const accountIdOverride = stored.accountId || resolvedAccountId || null;
+
+ const clear = useCallback(() => {
+ clearStoredAccountOverride();
+ setStored({ accountId: null, email: null });
+ const params = new URLSearchParams(searchParams.toString());
+ params.delete("email");
+ const newPath = params.toString()
+ ? `${window.location.pathname}?${params.toString()}`
+ : window.location.pathname;
+ router.replace(newPath);
+ }, [searchParams, router]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAccountOverride() {
+ return useContext(AccountOverrideContext);
+}
diff --git a/providers/Providers.tsx b/providers/Providers.tsx
index dc29b1c5b..3e6fcba85 100644
--- a/providers/Providers.tsx
+++ b/providers/Providers.tsx
@@ -14,37 +14,40 @@ import { MiniAppProvider } from "./MiniAppProvider";
import { ThemeProvider } from "./ThemeProvider";
import { OrganizationProvider } from "./OrganizationProvider";
import ApiOverrideSync from "./ApiOverrideSync";
+import { AccountOverrideProvider } from "./AccountOverrideProvider";
const queryClient = new QueryClient();
const Providers = ({ children }: { children: React.ReactNode }) => (
-
+
-
-
-
-
- {children}
-
-
-
-
+
+
+
+
+ {children}
+
+
+
+
+