Skip to content

Commit a3b73a2

Browse files
DefaultDefault
authored andcommitted
Fix briefing page 403 errors and account disappearing issue
- Create briefing-specific layout excluding ChatProvider (briefing doesn't need chat per PRD) - Add BriefingSWRProvider without email account context dependency - Fix reconnect flow to use regular OAuth with consent instead of linking flow - Prevents 403 errors from chat API calls - Prevents accounts from being deleted during token refresh - Fork-safe: all changes isolated to briefing-specific files
1 parent 3bfd298 commit a3b73a2

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client";
2+
3+
import type React from "react";
4+
import { NuqsAdapter } from "nuqs/adapters/next/app";
5+
import { Provider } from "jotai";
6+
import { jotaiStore } from "@/store";
7+
import { ThemeProvider } from "@/components/theme-provider";
8+
import { ComposeModalProvider } from "@/providers/ComposeModalProvider";
9+
import { BriefingSWRProvider } from "@/providers/BriefingSWRProvider";
10+
import { EmailAccountProvider } from "@/providers/EmailAccountProvider";
11+
12+
/**
13+
* Briefing-specific layout that excludes ChatProvider.
14+
* Briefing doesn't need chat functionality (per PRD).
15+
* EmailAccountProvider is included for settings page compatibility.
16+
*/
17+
export default function BriefingLayout({
18+
children,
19+
}: {
20+
children: React.ReactNode;
21+
}) {
22+
return (
23+
<ThemeProvider attribute="class" defaultTheme="light">
24+
<Provider store={jotaiStore}>
25+
<NuqsAdapter>
26+
<EmailAccountProvider>
27+
<BriefingSWRProvider>
28+
<ComposeModalProvider>{children}</ComposeModalProvider>
29+
</BriefingSWRProvider>
30+
</EmailAccountProvider>
31+
</NuqsAdapter>
32+
</Provider>
33+
</ThemeProvider>
34+
);
35+
}

apps/web/components/briefing/AccountSection.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,16 @@ export function AccountSection({
8484
provider: "google",
8585
callbackURL: "/briefing",
8686
scopes: GMAIL_SCOPES,
87+
// Force consent to refresh tokens for existing account
88+
// This ensures we refresh the current account's tokens rather than creating/merging accounts
89+
consent: true,
8790
});
8891
} else if (isMicrosoft) {
8992
await signIn.social({
9093
provider: "microsoft",
9194
callbackURL: "/briefing",
95+
// Force consent to refresh tokens for existing account
96+
consent: true,
9297
});
9398
}
9499
};
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"use client";
2+
3+
import { useCallback, useState, createContext, useMemo } from "react";
4+
import { SWRConfig } from "swr";
5+
import { captureException } from "@/utils/error";
6+
import { NO_REFRESH_TOKEN_ERROR_CODE } from "@/utils/config";
7+
8+
// Simplified SWR fetcher for briefing that doesn't require email account context
9+
const fetcher = async (url: string, init?: RequestInit | undefined) => {
10+
const headers = new Headers(init?.headers);
11+
const newInit = { ...init, headers };
12+
13+
const res = await fetch(url, newInit);
14+
15+
if (!res.ok) {
16+
const errorData = await res.json();
17+
18+
const errorMessage =
19+
errorData.message || "An error occurred while fetching the data.";
20+
const error: Error & { info?: any; status?: number } = new Error(
21+
errorMessage,
22+
);
23+
24+
// Attach extra info to the error object.
25+
error.info = errorData;
26+
error.status = res.status;
27+
28+
const isKnownError = errorData.isKnownError;
29+
30+
if (!isKnownError) {
31+
captureException(error, {
32+
extra: {
33+
url,
34+
status: res.status,
35+
statusText: res.statusText,
36+
responseBody: error.info,
37+
extraMessage: "SWR fetch error",
38+
},
39+
});
40+
}
41+
42+
throw error;
43+
}
44+
45+
return res.json();
46+
};
47+
48+
interface Context {
49+
resetCache: () => void;
50+
}
51+
52+
const defaultContextValue = {
53+
resetCache: () => {},
54+
};
55+
56+
const BriefingSWRContext = createContext<Context>(defaultContextValue);
57+
58+
export const BriefingSWRProvider = (props: { children: React.ReactNode }) => {
59+
const [provider, setProvider] = useState(new Map());
60+
61+
const resetCache = useCallback(() => {
62+
setProvider(new Map());
63+
}, []);
64+
65+
const value = useMemo(() => ({ resetCache }), [resetCache]);
66+
67+
return (
68+
<BriefingSWRContext.Provider value={value}>
69+
<SWRConfig
70+
value={{
71+
fetcher,
72+
provider: () => provider,
73+
onError: (error) => console.log("SWR error:", error),
74+
}}
75+
>
76+
{props.children}
77+
</SWRConfig>
78+
</BriefingSWRContext.Provider>
79+
);
80+
};
81+
82+
export { BriefingSWRContext };

0 commit comments

Comments
 (0)