From a84d58468ba0530e48c91b2ffa5aaa8baa7a1305 Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:41:08 +0200 Subject: [PATCH 1/6] make refer dialog responsive --- apps/web/components/ReferralDialog.tsx | 40 +++++++++++--------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/apps/web/components/ReferralDialog.tsx b/apps/web/components/ReferralDialog.tsx index 60645fcf43..cc92ade1d0 100644 --- a/apps/web/components/ReferralDialog.tsx +++ b/apps/web/components/ReferralDialog.tsx @@ -28,7 +28,7 @@ export function ReferralDialog() { Refer friend - + @@ -96,12 +96,12 @@ export function Referrals() { } return ( -
+
-

+

Refer Friends, Get Rewards

-

+

Share Inbox Zero with friends and get a free month for each friend who completes their trial

@@ -118,21 +118,15 @@ export function Referrals() { {codeData?.code ? (
-
- - {link} - - {/* */} +
+
+ + {link} + +
-
+
} > @@ -170,11 +197,11 @@ export function PremiumExpiredCardContent({ className="w-full bg-orange-600 text-white hover:bg-orange-700 border-0 shadow-sm h-8" > - Reactivate + {buttonText}
@@ -183,7 +210,7 @@ export function PremiumExpiredCardContent({ ); } -export function PremiumExpiredCard({ +export function PremiumCard({ isCollapsed = false, }: { isCollapsed?: boolean; diff --git a/apps/web/components/SideNav.tsx b/apps/web/components/SideNav.tsx index 8c07e0eefb..5e4a5779d8 100644 --- a/apps/web/components/SideNav.tsx +++ b/apps/web/components/SideNav.tsx @@ -14,7 +14,6 @@ import { CalendarIcon, ChevronDownIcon, ChevronRightIcon, - CrownIcon, FileIcon, InboxIcon, type LucideIcon, @@ -58,7 +57,7 @@ import { prefixPath } from "@/utils/path"; import { ReferralDialog } from "@/components/ReferralDialog"; import { isGoogleProvider } from "@/utils/email/provider-types"; import { NavUser } from "@/components/NavUser"; -import { PremiumExpiredCard } from "@/components/PremiumExpiredCard"; +import { PremiumCard } from "@/components/PremiumCard"; type NavItem = { name: string; @@ -233,7 +232,7 @@ export function SideNav({ ...props }: React.ComponentProps) { - + From 54d30bedcc8666ad6771d685482625ba1a70cd81 Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:11:32 +0200 Subject: [PATCH 4/6] v2.19.1 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3588849c84..bfd2f0f6e4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.19.0 +v2.19.1 From 3bfc9564bafabeaa07fc1dbe7bb66629cfc5d65c Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:16:18 +0200 Subject: [PATCH 5/6] Hide card on mobile --- apps/web/components/VideoCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/VideoCard.tsx b/apps/web/components/VideoCard.tsx index c6fb4c1592..3e1e95344e 100644 --- a/apps/web/components/VideoCard.tsx +++ b/apps/web/components/VideoCard.tsx @@ -106,7 +106,7 @@ const VideoCard = React.forwardRef<
-
+
- - You will be billed for each additional account + + You will be billed for each account. diff --git a/apps/web/app/(app)/accounts/page.tsx b/apps/web/app/(app)/accounts/page.tsx index 75574bef1f..8f08be7b5b 100644 --- a/apps/web/app/(app)/accounts/page.tsx +++ b/apps/web/app/(app)/accounts/page.tsx @@ -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"; import { useSearchParams, useRouter, usePathname } from "next/navigation"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import { LoadingContent } from "@/components/LoadingContent"; @@ -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(); @@ -56,8 +46,6 @@ export default function AccountsPage() {
- - ); } @@ -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(); - 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 ( - - - - Merge Accounts? - - The {provider === "google" ? "Google" : "Microsoft"} account{" "} - {email} already has an Inbox Zero account. -
-
- 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. -
-
- - - Cancel - - - {isConfirming ? "Merging..." : "Merge Accounts"} - - -
-
- ); -} - function useAccountNotifications() { const searchParams = useSearchParams(); const router = useRouter(); diff --git a/apps/web/app/api/google/linking/auth-url/route.ts b/apps/web/app/api/google/linking/auth-url/route.ts index 612faa2393..c39705123c 100644 --- a/apps/web/app/api/google/linking/auth-url/route.ts +++ b/apps/web/app/api/google/linking/auth-url/route.ts @@ -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"; @@ -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", @@ -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 }); diff --git a/apps/web/app/api/google/linking/callback/route.ts b/apps/web/app/api/google/linking/callback/route.ts index 3aa3470146..fba1e8fe44 100644 --- a/apps/web/app/api/google/linking/callback/route.ts +++ b/apps/web/app/api/google/linking/callback/route.ts @@ -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); @@ -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, diff --git a/apps/web/app/api/outlook/linking/auth-url/route.ts b/apps/web/app/api/outlook/linking/auth-url/route.ts index c179e4fdb3..86e3f8ffdc 100644 --- a/apps/web/app/api/outlook/linking/auth-url/route.ts +++ b/apps/web/app/api/outlook/linking/auth-url/route.ts @@ -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"; @@ -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}`; @@ -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 }); diff --git a/apps/web/app/api/outlook/linking/callback/route.ts b/apps/web/app/api/outlook/linking/callback/route.ts index b1c4f94526..45c3172a33 100644 --- a/apps/web/app/api/outlook/linking/callback/route.ts +++ b/apps/web/app/api/outlook/linking/callback/route.ts @@ -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); @@ -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, diff --git a/apps/web/utils/oauth/account-linking.test.ts b/apps/web/utils/oauth/account-linking.test.ts index 73c483542f..5c9f925af5 100644 --- a/apps/web/utils/oauth/account-linking.test.ts +++ b/apps/web/utils/oauth/account-linking.test.ts @@ -25,7 +25,6 @@ describe("handleAccountLinking", () => { hasEmailAccount: false, existingUserId: "orphaned-user-id", targetUserId: "target-user-id", - action: "auto", provider: "google", providerEmail: "test@gmail.com", baseUrl: "http://localhost:3000", @@ -45,7 +44,6 @@ describe("handleAccountLinking", () => { hasEmailAccount: false, existingUserId: null, targetUserId: "target-user-id", - action: "auto", provider: "google", providerEmail: "new@gmail.com", baseUrl: "http://localhost:3000", @@ -61,7 +59,6 @@ describe("handleAccountLinking", () => { hasEmailAccount: true, existingUserId: "same-user-id", targetUserId: "same-user-id", - action: "auto", provider: "google", providerEmail: "test@gmail.com", baseUrl: "http://localhost:3000", @@ -75,35 +72,12 @@ describe("handleAccountLinking", () => { } }); - it("should redirect to confirm merge when account exists for different user and action is auto", async () => { + it("should return merge when account exists for different user", async () => { const result = await handleAccountLinking({ existingAccountId: "account-id", hasEmailAccount: true, existingUserId: "different-user-id", targetUserId: "target-user-id", - action: "auto", - provider: "google", - providerEmail: "test@gmail.com", - baseUrl: "http://localhost:3000", - logger, - }); - - expect(result.type).toBe("redirect"); - if (result.type === "redirect") { - const url = new URL(result.response.headers.get("location") || ""); - expect(url.searchParams.get("confirm_merge")).toBe("true"); - expect(url.searchParams.get("provider")).toBe("google"); - expect(url.searchParams.get("email")).toBe("test@gmail.com"); - } - }); - - it("should return merge when account exists for different user and action is merge_confirmed", async () => { - const result = await handleAccountLinking({ - existingAccountId: "account-id", - hasEmailAccount: true, - existingUserId: "different-user-id", - targetUserId: "target-user-id", - action: "merge_confirmed", provider: "google", providerEmail: "test@gmail.com", baseUrl: "http://localhost:3000", @@ -130,7 +104,6 @@ describe("handleAccountLinking", () => { hasEmailAccount: false, existingUserId: null, targetUserId: "target-user-id", - action: "auto", provider: "google", providerEmail: "existing@gmail.com", baseUrl: "http://localhost:3000", diff --git a/apps/web/utils/oauth/account-linking.ts b/apps/web/utils/oauth/account-linking.ts index 5a7fe4d230..1ae2e63407 100644 --- a/apps/web/utils/oauth/account-linking.ts +++ b/apps/web/utils/oauth/account-linking.ts @@ -8,7 +8,6 @@ interface AccountLinkingParams { hasEmailAccount: boolean; existingUserId: string | null; targetUserId: string; - action: "auto" | "merge_confirmed"; provider: "google" | "microsoft"; providerEmail: string; baseUrl: string; @@ -20,7 +19,6 @@ export async function handleAccountLinking({ hasEmailAccount, existingUserId, targetUserId, - action, provider, providerEmail, baseUrl, @@ -45,34 +43,27 @@ export async function handleAccountLinking({ } if (!existingAccountId || !hasEmailAccount) { - if (action === "auto") { - const existingEmailAccount = await prisma.emailAccount.findUnique({ - where: { email: providerEmail.trim().toLowerCase() }, - select: { userId: true, email: true }, - }); + const existingEmailAccount = await prisma.emailAccount.findUnique({ + where: { email: providerEmail.trim().toLowerCase() }, + select: { userId: true, email: true }, + }); - if ( - existingEmailAccount && - existingEmailAccount.userId !== targetUserId - ) { - logger.warn( - `Create Failed: ${provider} account with this email already exists for a different user.`, - { - email: providerEmail, - existingUserId: existingEmailAccount.userId, - targetUserId, - }, - ); - redirectUrl.searchParams.set( - "error", - "account_already_exists_use_merge", - ); - return { - type: "redirect", - response: NextResponse.redirect(redirectUrl), - }; - } + if (existingEmailAccount && existingEmailAccount.userId !== targetUserId) { + logger.warn( + `Create Failed: ${provider} account with this email already exists for a different user.`, + { + email: providerEmail, + existingUserId: existingEmailAccount.userId, + targetUserId, + }, + ); + redirectUrl.searchParams.set("error", "account_already_exists_use_merge"); + return { + type: "redirect", + response: NextResponse.redirect(redirectUrl), + }; } + return { type: "continue_create" }; } @@ -88,29 +79,16 @@ export async function handleAccountLinking({ }; } - if (action === "auto") { - logger.info( - "Account exists for different user, requesting merge confirmation", - { - email: providerEmail, - existingUserId, - targetUserId, - }, - ); - - redirectUrl.searchParams.set("confirm_merge", "true"); - redirectUrl.searchParams.set("provider", provider); - redirectUrl.searchParams.set("email", providerEmail); - return { - type: "redirect", - response: NextResponse.redirect(redirectUrl), - }; - } - if (!existingAccountId || !existingUserId) { throw new Error("Unexpected state: existingAccount should exist"); } + logger.info("Account exists for different user, merging accounts", { + email: providerEmail, + existingUserId, + targetUserId, + }); + return { type: "merge", sourceAccountId: existingAccountId, diff --git a/apps/web/utils/oauth/callback-validation.test.ts b/apps/web/utils/oauth/callback-validation.test.ts index 6804bc440c..9a1062d5e9 100644 --- a/apps/web/utils/oauth/callback-validation.test.ts +++ b/apps/web/utils/oauth/callback-validation.test.ts @@ -32,7 +32,6 @@ describe("validateOAuthCallback", () => { it("should return error when code is missing", () => { vi.mocked(parseOAuthState).mockReturnValue({ userId: "user-id", - action: "auto", nonce: "nonce", }); @@ -92,7 +91,6 @@ describe("validateOAuthCallback", () => { expect(result.success).toBe(true); if (result.success) { expect(result.targetUserId).toBe("user-id"); - expect(result.action).toBe("auto"); expect(result.code).toBe("valid-code"); } }); diff --git a/apps/web/utils/oauth/callback-validation.ts b/apps/web/utils/oauth/callback-validation.ts index 4d7b1628af..17c38a963e 100644 --- a/apps/web/utils/oauth/callback-validation.ts +++ b/apps/web/utils/oauth/callback-validation.ts @@ -15,7 +15,6 @@ type ValidationResult = | { success: true; targetUserId: string; - action: "auto" | "merge_confirmed"; code: string; } | { @@ -51,7 +50,6 @@ export function validateOAuthCallback({ let decodedState: { userId: string; - action: "auto" | "merge_confirmed"; nonce: string; }; try { @@ -83,7 +81,6 @@ export function validateOAuthCallback({ return { success: true, targetUserId: decodedState.userId, - action: decodedState.action, code, }; } diff --git a/version.txt b/version.txt index bfd2f0f6e4..a1109b5970 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.19.1 +v2.19.2