diff --git a/apps/web/app/(app)/[emailAccountId]/automation/page.tsx b/apps/web/app/(app)/[emailAccountId]/automation/page.tsx index 7a2e31187f..3b2f229c90 100644 --- a/apps/web/app/(app)/[emailAccountId]/automation/page.tsx +++ b/apps/web/app/(app)/[emailAccountId]/automation/page.tsx @@ -11,7 +11,6 @@ import { PermissionsCheck } from "@/app/(app)/[emailAccountId]/PermissionsCheck" import { EmailProvider } from "@/providers/EmailProvider"; import { ASSISTANT_ONBOARDING_COOKIE } from "@/utils/cookies"; import { prefixPath } from "@/utils/path"; -import { PremiumAlertWithData } from "@/components/PremiumAlert"; import { checkUserOwnsEmailAccount } from "@/utils/email-account"; import { SettingsTab } from "@/app/(app)/[emailAccountId]/assistant/settings/SettingsTab"; import { TabSelect } from "@/components/TabSelect"; @@ -86,8 +85,6 @@ export default async function AutomationPage({ - -
-
- -
-
diff --git a/apps/web/app/(landing)/components/page.tsx b/apps/web/app/(landing)/components/page.tsx index dec41e85a7..452bd24835 100644 --- a/apps/web/app/(landing)/components/page.tsx +++ b/apps/web/app/(landing)/components/page.tsx @@ -33,6 +33,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"; export const maxDuration = 3; @@ -381,6 +382,82 @@ export default function Components() {
+
+
Premium Expired Banners
+
+
+

+ Stripe Past Due: +

+ +
+
+

+ Stripe Canceled: +

+ +
+
+

+ LemonSqueezy Expired: +

+ +
+
+

+ No Banner (Active Premium): +

+
+ + Banner should not appear for active users +
+
+
+

+ No Banner (Never Had Premium): +

+
+ + Banner should not appear for users who never had premium +
+
+
+
+
diff --git a/apps/web/components/PremiumExpiredCard.tsx b/apps/web/components/PremiumExpiredCard.tsx new file mode 100644 index 0000000000..4fe5a935e2 --- /dev/null +++ b/apps/web/components/PremiumExpiredCard.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { XIcon, CreditCardIcon, AlertTriangleIcon } from "lucide-react"; +import { useUser } from "@/hooks/useUser"; +import { isPremium } from "@/utils/premium"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { cn } from "@/utils"; + +interface PremiumData { + lemonSqueezyRenewsAt?: Date | string | null; + stripeSubscriptionStatus?: string | null; + stripeSubscriptionId?: string | null; + lemonSqueezySubscriptionId?: number | string | null; + tier?: string | null; +} + +interface PremiumExpiredCardProps { + premium: PremiumData | null | undefined; + onDismiss?: () => void; +} + +export function PremiumExpiredCardContent({ + premium, + onDismiss, +}: PremiumExpiredCardProps) { + // Early return if no premium data + if (!premium) return null; + + // Convert string dates to Date objects if needed + const lemonSqueezyRenewsAt = premium.lemonSqueezyRenewsAt + ? typeof premium.lemonSqueezyRenewsAt === "string" + ? new Date(premium.lemonSqueezyRenewsAt) + : premium.lemonSqueezyRenewsAt + : null; + + const isUserPremium = isPremium( + lemonSqueezyRenewsAt, + premium.stripeSubscriptionStatus || null, + ); + + if (isUserPremium) return null; + + // Determine the message based on subscription state + const getSubscriptionMessage = () => { + const status = premium.stripeSubscriptionStatus; + const hasLemonSqueezyExpired = + lemonSqueezyRenewsAt && lemonSqueezyRenewsAt < new Date(); + + if (status === "past_due") { + return { + title: "Payment Past Due", + description: "Update your payment method to continue service", + }; + } + + if (status === "canceled" || status === "cancelled") { + return { + title: "Subscription Cancelled", + description: "Reactivate to resume AI email management", + }; + } + + if (status === "incomplete" || status === "incomplete_expired") { + return { + title: "Payment Incomplete", + description: "Complete your payment to activate service", + }; + } + + if (status === "unpaid") { + return { + title: "Payment Required", + description: "Update payment to continue AI features", + }; + } + + if (hasLemonSqueezyExpired || status === "expired") { + return { + title: "Subscription Expired", + description: "Renew your subscription to continue", + }; + } + + // Default fallback + return { + title: "Subscription Issue", + description: "Please check your subscription status", + }; + }; + + const { title, description } = getSubscriptionMessage(); + + return ( + +
+
+ +
+

+ {title} +

+

+ {description} +

+
+ + {onDismiss && ( + + )} +
+ +
+ +
+
+
+ ); +} + +export function PremiumExpiredCard() { + const [dismissed, setDismissed] = useState(false); + const { data: user, isLoading } = useUser(); + + if (isLoading || dismissed || !user) return null; + + return ( +
+ setDismissed(true)} + /> +
+ ); +} diff --git a/apps/web/components/SideNav.tsx b/apps/web/components/SideNav.tsx index c7d01dcb4b..035e00a46b 100644 --- a/apps/web/components/SideNav.tsx +++ b/apps/web/components/SideNav.tsx @@ -57,6 +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"; type NavItem = { name: string; @@ -224,6 +225,8 @@ export function SideNav({ ...props }: React.ComponentProps) { + + diff --git a/version.txt b/version.txt index 6998728c15..7ef6a4f64a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.10.17 +v2.10.18