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