Add Sheets-first mirror control

This commit is contained in:
MOHAN 2026-07-17 23:43:11 +05:30
parent 10d386bc7d
commit fbd1c2cc4a
2 changed files with 77 additions and 1 deletions

View File

@ -0,0 +1,6 @@
import { NextRequest } from "next/server";
import { proxyRequest } from "@/lib/backend";
export async function POST(req: NextRequest) {
return proxyRequest(req, "google/data-system-mode");
}

View File

@ -24,6 +24,23 @@ type TellerEnrollment = {
}; };
}; };
type GoogleStatus = {
connected: boolean;
googleEmail?: string;
driveMirror?: {
enabled: boolean;
status: string;
url?: string | null;
lastSyncedAt?: string | null;
};
dataSystem?: {
mode: string;
operationalSystemOfRecord: string;
userOwnedMirror: boolean;
note: string;
};
};
declare global { declare global {
interface Window { interface Window {
TellerConnect?: { TellerConnect?: {
@ -53,6 +70,7 @@ export default function ConnectPage() {
const [manualType, setManualType] = useState("checking"); const [manualType, setManualType] = useState("checking");
const [accounts, setAccounts] = useState<Account[]>([]); const [accounts, setAccounts] = useState<Account[]>([]);
const [tellerReady, setTellerReady] = useState(false); const [tellerReady, setTellerReady] = useState(false);
const [googleStatus, setGoogleStatus] = useState<GoogleStatus | null>(null);
const createLinkToken = useCallback(async () => { const createLinkToken = useCallback(async () => {
setStatus("Requesting Plaid link token..."); setStatus("Requesting Plaid link token...");
@ -84,6 +102,13 @@ export default function ConnectPage() {
setAccounts(payload.data?.accounts ?? payload.data ?? []); setAccounts(payload.data?.accounts ?? payload.data ?? []);
}, []); }, []);
const loadGoogleStatus = useCallback(async () => {
const res = await fetch("/api/google/status");
if (!res.ok) return;
const payload = await res.json();
setGoogleStatus(payload.data ?? payload);
}, []);
const loadTellerScript = useCallback(() => { const loadTellerScript = useCallback(() => {
if (typeof window === "undefined") return Promise.resolve(false); if (typeof window === "undefined") return Promise.resolve(false);
if (window.TellerConnect) { if (window.TellerConnect) {
@ -115,8 +140,9 @@ export default function ConnectPage() {
useEffect(() => { useEffect(() => {
createLinkToken(); createLinkToken();
loadAccounts(); loadAccounts();
loadGoogleStatus();
loadTellerScript(); loadTellerScript();
}, [createLinkToken, loadAccounts, loadTellerScript]); }, [createLinkToken, loadAccounts, loadGoogleStatus, loadTellerScript]);
const onSuccess = useCallback( const onSuccess = useCallback(
async (publicToken: string | null) => { async (publicToken: string | null) => {
@ -301,6 +327,22 @@ export default function ConnectPage() {
await loadAccounts(); await loadAccounts();
}; };
const enableSheetsMirror = async () => {
setStatus("Enabling Sheets-first mirror mode...");
const res = await fetch("/api/google/data-system-mode", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mode: "google_sheets_mirror" })
});
const payload = await res.json();
if (!res.ok || payload.error) {
setStatus(payload.error?.message ?? "Unable to enable Sheets-first mirror mode.");
return;
}
setStatus("Sheets-first mirror mode enabled.");
await loadGoogleStatus();
};
return ( return (
<AppShell <AppShell
title="Connect a bank" title="Connect a bank"
@ -447,6 +489,34 @@ export default function ConnectPage() {
Free includes two connections. Pro supports ten active accounts, and Elite supports unlimited accounts. Free includes two connections. Pro supports ten active accounts, and Elite supports unlimited accounts.
</p> </p>
</div> </div>
<div className="glass-panel mt-6 p-6 rounded-2xl shadow-sm">
<h2 className="text-lg font-bold text-foreground">Google Sheets ownership</h2>
<p className="mt-2 text-sm text-muted-foreground">
Keep a user-owned Google Sheets mirror synced from LedgerOne transaction changes.
</p>
<div className="mt-4 rounded-xl border border-border bg-secondary/30 px-4 py-3 text-sm">
<p className="font-semibold text-foreground">
{googleStatus?.connected ? `Connected: ${googleStatus.googleEmail}` : "Google is not connected"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
Mode: {googleStatus?.dataSystem?.mode ?? "backend_db"} · Mirror: {googleStatus?.driveMirror?.status ?? "not_started"}
</p>
{googleStatus?.driveMirror?.url ? (
<a className="mt-2 inline-block text-xs font-semibold text-primary" href={googleStatus.driveMirror.url} target="_blank" rel="noreferrer">
Open Google Sheet
</a>
) : null}
</div>
<button
type="button"
className="mt-4 rounded-full border border-border bg-background px-5 py-2 text-sm font-semibold text-foreground hover:bg-secondary transition-colors disabled:opacity-50"
onClick={enableSheetsMirror}
disabled={!googleStatus?.connected || googleStatus?.dataSystem?.mode === "google_sheets_mirror"}
>
Enable Sheets-first mirror
</button>
</div>
</AppShell> </AppShell>
); );
} }