Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions apps/web/app/(app)/accounts/AddAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ export function AddAccount() {
setLoading(true);

try {
const response = await fetch(
`/api/${provider}/linking/auth-url?action=auto`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);
const response = await fetch(`/api/${provider}/linking/auth-url`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});

if (!response.ok) {
toastError({
Expand Down Expand Up @@ -86,8 +83,8 @@ export function AddAccount() {
<span className="ml-2">Add Microsoft Account</span>
</Button>

<TypographyP className="text-sm">
You will be billed for each additional account
<TypographyP className="text-sm text-muted-foreground">
You will be billed for each account.
</TypographyP>
</CardContent>
</Card>
Expand Down
107 changes: 2 additions & 105 deletions apps/web/app/(app)/accounts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useAction } from "next-safe-action/hooks";
import Link from "next/link";
import { Trash2, ArrowRight, BotIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useEffect } from "react";
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the merge confirmation dialog removes the entire confirm-merge flow that reacts to the ?confirm_merge/provider/email query params and issues the merge_confirmed redirect, so users can no longer finalize an account merge.

Prompt for AI agents
Address the following comment on apps/web/app/(app)/accounts/page.tsx at line 6:

<comment>Removing the merge confirmation dialog removes the entire confirm-merge flow that reacts to the ?confirm_merge/provider/email query params and issues the merge_confirmed redirect, so users can no longer finalize an account merge.</comment>

<file context>
@@ -3,7 +3,7 @@
 import Link from &quot;next/link&quot;;
 import { Trash2, ArrowRight, BotIcon } from &quot;lucide-react&quot;;
-import { useEffect, useState } from &quot;react&quot;;
+import { useEffect } from &quot;react&quot;;
 import { useSearchParams, useRouter, usePathname } from &quot;next/navigation&quot;;
 import { ConfirmDialog } from &quot;@/components/ConfirmDialog&quot;;
</file context>
Fix with Cubic

import { useSearchParams, useRouter, usePathname } from "next/navigation";
import { ConfirmDialog } from "@/components/ConfirmDialog";
import { LoadingContent } from "@/components/LoadingContent";
Expand All @@ -25,16 +25,6 @@ import { PageHeader } from "@/components/PageHeader";
import { PageWrapper } from "@/components/PageWrapper";
import { logOut } from "@/utils/user";
import { getAndClearAuthErrorCookie } from "@/utils/auth-cookies";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";

export default function AccountsPage() {
const { data, isLoading, error, mutate } = useAccounts();
Expand All @@ -45,7 +35,7 @@ export default function AccountsPage() {
<PageHeader title="Accounts" description="Manage your email accounts." />

<LoadingContent loading={isLoading} error={error}>
<div className="grid grid-cols-1 gap-4 py-6 md:grid-cols-2 lg:grid-cols-3">
<div className="grid grid-cols-1 gap-4 py-6 lg:grid-cols-2 xl:grid-cols-3">
{data?.emailAccounts.map((emailAccount) => (
<AccountItem
key={emailAccount.id}
Expand All @@ -56,8 +46,6 @@ export default function AccountsPage() {
<AddAccount />
</div>
</LoadingContent>

<MergeConfirmationDialog />
</PageWrapper>
);
}
Expand Down Expand Up @@ -145,97 +133,6 @@ function AccountItem({
);
}

function MergeConfirmationDialog() {
const searchParams = useSearchParams();
const router = useRouter();
const pathname = usePathname();
const [showDialog, setShowDialog] = useState(false);
const [provider, setProvider] = useState<"google" | "microsoft">();
const [email, setEmail] = useState<string>();
const [isConfirming, setIsConfirming] = useState(false);

useEffect(() => {
const confirmMerge = searchParams.get("confirm_merge");
const providerParam = searchParams.get("provider");
const emailParam = searchParams.get("email");

if (confirmMerge === "true" && providerParam && emailParam) {
setShowDialog(true);
setProvider(providerParam as "google" | "microsoft");
setEmail(emailParam);
router.replace(pathname);
}
}, [searchParams, router, pathname]);

const handleConfirm = async () => {
if (!provider) return;

setIsConfirming(true);
try {
const apiProvider = provider === "google" ? "google" : "outlook";
const response = await fetch(
`/api/${apiProvider}/linking/auth-url?action=merge_confirmed`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
},
);

if (!response.ok) {
toastError({
title: "Error initiating merge",
description: "Please try again or contact support",
});
setIsConfirming(false);
return;
}

const data = await response.json();
window.location.href = data.url;
} catch (error) {
console.error("Error initiating merge:", error);
toastError({
title: "Error initiating merge",
description: "Please try again or contact support",
});
setIsConfirming(false);
}
};

const handleCancel = () => {
setShowDialog(false);
setProvider(undefined);
setEmail(undefined);
};

return (
<AlertDialog open={showDialog} onOpenChange={setShowDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Merge Accounts?</AlertDialogTitle>
<AlertDialogDescription>
The {provider === "google" ? "Google" : "Microsoft"} account{" "}
<strong>{email}</strong> already has an Inbox Zero account.
<br />
<br />
Merging will combine both accounts into one. This will delete the
old account and move all its data to your current account. This
action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancel} disabled={isConfirming}>
Cancel
</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirm} disabled={isConfirming}>
{isConfirming ? "Merging..." : "Merge Accounts"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}

function useAccountNotifications() {
const searchParams = useSearchParams();
const router = useRouter();
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(landing)/components/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { SettingCard } from "@/components/SettingCard";
import { IconCircle } from "@/app/(app)/[emailAccountId]/onboarding/IconCircle";
import { ActionBadges } from "@/app/(app)/[emailAccountId]/assistant/Rules";
import { DismissibleVideoCard } from "@/components/VideoCard";
import { PremiumExpiredCardContent } from "@/components/PremiumExpiredCard";
import { PremiumExpiredCardContent } from "@/components/PremiumCard";
import {
ResultsDisplay,
ResultDisplayContent,
Expand Down
31 changes: 3 additions & 28 deletions apps/web/app/api/google/linking/auth-url/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { withAuth } from "@/utils/middleware";
import { getLinkingOAuth2Client } from "@/utils/gmail/client";
import { GOOGLE_LINKING_STATE_COOKIE_NAME } from "@/utils/gmail/constants";
Expand All @@ -11,18 +10,10 @@ import {

export type GetAuthLinkUrlResponse = { url: string };

const actionSchema = z.enum(["auto", "merge_confirmed"]);

const getAuthUrl = ({
userId,
action,
}: {
userId: string;
action: "auto" | "merge_confirmed";
}) => {
const getAuthUrl = ({ userId }: { userId: string }) => {
const googleAuth = getLinkingOAuth2Client();

const state = generateOAuthState({ userId, action });
const state = generateOAuthState({ userId });

const url = googleAuth.generateAuthUrl({
access_type: "offline",
Expand All @@ -36,23 +27,7 @@ const getAuthUrl = ({

export const GET = withAuth("google/linking/auth-url", async (request) => {
const userId = request.auth.userId;
const url = new URL(request.url);

const actionParam = url.searchParams.get("action");
const parseResult = actionSchema.safeParse(actionParam);

if (!parseResult.success) {
return NextResponse.json(
{
error:
"Invalid or missing action parameter. Must be 'auto' or 'merge_confirmed'.",
},
{ status: 400 },
);
}

const action = parseResult.data;
const { url: authUrl, state } = getAuthUrl({ userId, action });
const { url: authUrl, state } = getAuthUrl({ userId });

const response = NextResponse.json({ url: authUrl });

Expand Down
3 changes: 1 addition & 2 deletions apps/web/app/api/google/linking/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const GET = withError("google/linking/callback", async (request) => {
return validation.response;
}

const { targetUserId, action, code } = validation;
const { targetUserId, code } = validation;
const redirectUrl = new URL("/accounts", request.nextUrl.origin);
const response = NextResponse.redirect(redirectUrl);
response.cookies.delete(GOOGLE_LINKING_STATE_COOKIE_NAME);
Expand Down Expand Up @@ -95,7 +95,6 @@ export const GET = withError("google/linking/callback", async (request) => {
hasEmailAccount: !!existingAccount?.emailAccount,
existingUserId: existingAccount?.userId || null,
targetUserId,
action,
provider: "google",
providerEmail,
baseUrl: request.nextUrl.origin,
Expand Down
31 changes: 3 additions & 28 deletions apps/web/app/api/outlook/linking/auth-url/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { withAuth } from "@/utils/middleware";
import { getLinkingOAuth2Url } from "@/utils/outlook/client";
import { OUTLOOK_LINKING_STATE_COOKIE_NAME } from "@/utils/outlook/constants";
Expand All @@ -10,16 +9,8 @@ import {

export type GetOutlookAuthLinkUrlResponse = { url: string };

const actionSchema = z.enum(["auto", "merge_confirmed"]);

const getAuthUrl = ({
userId,
action,
}: {
userId: string;
action: "auto" | "merge_confirmed";
}) => {
const state = generateOAuthState({ userId, action });
const getAuthUrl = ({ userId }: { userId: string }) => {
const state = generateOAuthState({ userId });

const baseUrl = getLinkingOAuth2Url();
const url = `${baseUrl}&state=${state}`;
Expand All @@ -29,23 +20,7 @@ const getAuthUrl = ({

export const GET = withAuth("outlook/linking/auth-url", async (request) => {
const userId = request.auth.userId;
const url = new URL(request.url);

const actionParam = url.searchParams.get("action");
const parseResult = actionSchema.safeParse(actionParam);

if (!parseResult.success) {
return NextResponse.json(
{
error:
"Invalid or missing action parameter. Must be 'auto' or 'merge_confirmed'.",
},
{ status: 400 },
);
}

const action = parseResult.data;
const { url: authUrl, state } = getAuthUrl({ userId, action });
const { url: authUrl, state } = getAuthUrl({ userId });

const response = NextResponse.json({ url: authUrl });

Expand Down
3 changes: 1 addition & 2 deletions apps/web/app/api/outlook/linking/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const GET = withError("outlook/linking/callback", async (request) => {
return validation.response;
}

const { targetUserId, action, code } = validation;
const { targetUserId, code } = validation;
const redirectUrl = new URL("/accounts", request.nextUrl.origin);
const response = NextResponse.redirect(redirectUrl);
response.cookies.delete(OUTLOOK_LINKING_STATE_COOKIE_NAME);
Expand Down Expand Up @@ -129,7 +129,6 @@ export const GET = withError("outlook/linking/callback", async (request) => {
hasEmailAccount: !!existingAccount?.emailAccount,
existingUserId: existingAccount?.userId || null,
targetUserId,
action,
provider: "microsoft",
providerEmail,
baseUrl: request.nextUrl.origin,
Expand Down
Loading
Loading