mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-17 12:20:04 +00:00
fix: past due pending users improvements (#572)
This commit is contained in:
parent
5ec3719604
commit
2ed7ba8808
3 changed files with 120 additions and 39 deletions
|
|
@ -7,7 +7,7 @@ import {
|
|||
import { Button } from "@ui/components/button"
|
||||
import { HeadingH3Bold } from "@ui/text/heading/heading-h3-bold"
|
||||
import { useCustomer } from "autumn-js/react"
|
||||
import { CheckCircle, LoaderIcon, X } from "lucide-react"
|
||||
import { AlertTriangle, CheckCircle, LoaderIcon, X } from "lucide-react"
|
||||
import { motion } from "motion/react"
|
||||
import Link from "next/link"
|
||||
import { useEffect, useState } from "react"
|
||||
|
|
@ -24,11 +24,16 @@ export function BillingView() {
|
|||
|
||||
const {
|
||||
data: status = {
|
||||
consumer_pro: null,
|
||||
consumer_pro: { allowed: false, status: null },
|
||||
},
|
||||
isLoading: isCheckingStatus,
|
||||
} = fetchSubscriptionStatus(autumn, !autumn.isLoading)
|
||||
|
||||
const proStatus = status.consumer_pro
|
||||
const proProductStatus = proStatus?.status
|
||||
const isPastDue = proProductStatus === "past_due"
|
||||
const hasProProduct = proProductStatus !== null
|
||||
|
||||
const { data: memoriesCheck } = fetchMemoriesFeature(
|
||||
autumn,
|
||||
!autumn.isLoading && !isCheckingStatus,
|
||||
|
|
@ -37,7 +42,10 @@ export function BillingView() {
|
|||
const memoriesUsed = memoriesCheck?.usage ?? 0
|
||||
const memoriesLimit = memoriesCheck?.included_usage ?? 0
|
||||
|
||||
const { data: connectionsCheck } = fetchConnectionsFeature(autumn, !autumn.isLoading && !isCheckingStatus)
|
||||
const { data: connectionsCheck } = fetchConnectionsFeature(
|
||||
autumn,
|
||||
!autumn.isLoading && !isCheckingStatus,
|
||||
)
|
||||
|
||||
const connectionsUsed = connectionsCheck?.usage ?? 0
|
||||
|
||||
|
|
@ -66,8 +74,6 @@ export function BillingView() {
|
|||
})
|
||||
}
|
||||
|
||||
const isPro = status.consumer_pro
|
||||
|
||||
if (user?.isAnonymous) {
|
||||
return (
|
||||
<motion.div
|
||||
|
|
@ -92,7 +98,7 @@ export function BillingView() {
|
|||
)
|
||||
}
|
||||
|
||||
if (isPro) {
|
||||
if (hasProProduct) {
|
||||
return (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
|
|
@ -102,24 +108,45 @@ export function BillingView() {
|
|||
<div className="space-y-3">
|
||||
<HeadingH3Bold className="text-foreground flex items-center gap-2">
|
||||
Pro Plan
|
||||
<span className="text-xs bg-green-500/20 text-green-600 dark:text-green-400 px-2 py-0.5 rounded-full">
|
||||
Active
|
||||
</span>
|
||||
{isPastDue ? (
|
||||
<span className="text-xs bg-red-500/20 text-red-600 dark:text-red-400 px-2 py-0.5 rounded-full">
|
||||
Past Due
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs bg-green-500/20 text-green-600 dark:text-green-400 px-2 py-0.5 rounded-full">
|
||||
Active
|
||||
</span>
|
||||
)}
|
||||
</HeadingH3Bold>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You're enjoying expanded memory capacity with supermemory Pro!
|
||||
{isPastDue
|
||||
? "Your payment is past due. Please update your payment method to restore access."
|
||||
: "You're enjoying expanded memory capacity with supermemory Pro!"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isPastDue && (
|
||||
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg flex items-start gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-red-600 dark:text-red-400 font-medium mb-1">
|
||||
Payment Required
|
||||
</p>
|
||||
<p className="text-xs text-red-600/80 dark:text-red-400/80">
|
||||
Your payment method failed or payment is past due. Update your
|
||||
payment information to restore access to all Pro features.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Current Usage */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-medium text-foreground">Current Usage</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Memories</span>
|
||||
<span className="text-sm text-foreground">
|
||||
Unlimited
|
||||
</span>
|
||||
<span className="text-sm text-foreground">Unlimited</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
|
|
@ -132,8 +159,13 @@ export function BillingView() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Button onClick={handleManageBilling} size="sm" variant="default">
|
||||
Manage Billing
|
||||
<Button
|
||||
onClick={handleManageBilling}
|
||||
size="sm"
|
||||
variant="default"
|
||||
className={isPastDue ? "bg-red-600 hover:bg-red-700 text-white" : ""}
|
||||
>
|
||||
{isPastDue ? "Pay Past Due" : "Manage Billing"}
|
||||
</Button>
|
||||
</motion.div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ import { Button } from "@repo/ui/components/button"
|
|||
import { Skeleton } from "@repo/ui/components/skeleton"
|
||||
import { HeadingH3Bold } from "@repo/ui/text/heading/heading-h3-bold"
|
||||
import { useCustomer } from "autumn-js/react"
|
||||
import { CheckCircle, CreditCard, LoaderIcon, User, X } from "lucide-react"
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
CreditCard,
|
||||
LoaderIcon,
|
||||
User,
|
||||
X,
|
||||
} from "lucide-react"
|
||||
import { motion } from "motion/react"
|
||||
import Link from "next/link"
|
||||
import { useState } from "react"
|
||||
|
|
@ -23,12 +30,16 @@ export function ProfileView() {
|
|||
|
||||
const {
|
||||
data: status = {
|
||||
consumer_pro: null,
|
||||
consumer_pro: { allowed: false, status: null },
|
||||
},
|
||||
isLoading: isCheckingStatus,
|
||||
} = fetchSubscriptionStatus(autumn, !autumn.isLoading)
|
||||
|
||||
const isPro = status.consumer_pro
|
||||
const proStatus = status.consumer_pro
|
||||
const isPro = proStatus?.allowed ?? false
|
||||
const proProductStatus = proStatus?.status
|
||||
const isPastDue = proProductStatus === "past_due"
|
||||
const hasProProduct = proProductStatus !== null
|
||||
|
||||
const { data: memoriesCheck } = fetchMemoriesFeature(
|
||||
autumn,
|
||||
|
|
@ -176,24 +187,43 @@ export function ProfileView() {
|
|||
</div>
|
||||
<div className="flex-1">
|
||||
<HeadingH3Bold className="text-foreground text-sm">
|
||||
{isPro ? "Pro Plan" : "Free Plan"}
|
||||
{isPro && (
|
||||
{hasProProduct ? "Pro Plan" : "Free Plan"}
|
||||
{isPastDue ? (
|
||||
<span className="ml-2 text-xs bg-red-500/20 text-red-600 dark:text-red-400 px-2 py-0.5 rounded-full">
|
||||
Past Due
|
||||
</span>
|
||||
) : isPro ? (
|
||||
<span className="ml-2 text-xs bg-green-500/20 text-green-600 dark:text-green-400 px-2 py-0.5 rounded-full">
|
||||
Active
|
||||
</span>
|
||||
)}
|
||||
) : null}
|
||||
</HeadingH3Bold>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{isPro ? "Expanded memory capacity" : "Basic plan"}
|
||||
{hasProProduct ? "Expanded memory capacity" : "Basic plan"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isPastDue && (
|
||||
<div className="p-3 bg-red-500/10 border border-red-500/20 rounded-lg flex items-start gap-2">
|
||||
<AlertTriangle className="h-4 w-4 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-red-600 dark:text-red-400 font-medium">
|
||||
Payment Required
|
||||
</p>
|
||||
<p className="text-xs text-red-600/80 dark:text-red-400/80 mt-1">
|
||||
Your payment is past due. Please update your payment method to
|
||||
restore access to Pro features.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Usage Stats */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Memories</span>
|
||||
{isPro ? (
|
||||
{hasProProduct ? (
|
||||
<span className="text-sm text-foreground">Unlimited</span>
|
||||
) : (
|
||||
<span
|
||||
|
|
@ -203,15 +233,11 @@ export function ProfileView() {
|
|||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!isPro && (
|
||||
{!hasProProduct && (
|
||||
<div className="w-full bg-muted-foreground/50 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all ${
|
||||
memoriesUsed >= memoriesLimit
|
||||
? "bg-red-500"
|
||||
: isPro
|
||||
? "bg-green-500"
|
||||
: "bg-blue-500"
|
||||
memoriesUsed >= memoriesLimit ? "bg-red-500" : "bg-blue-500"
|
||||
}`}
|
||||
style={{
|
||||
width: `${Math.min((memoriesUsed / memoriesLimit) * 100, 100)}%`,
|
||||
|
|
@ -221,7 +247,7 @@ export function ProfileView() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{isPro && (
|
||||
{hasProProduct && (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Connections</span>
|
||||
<span className="text-sm text-foreground">
|
||||
|
|
@ -232,7 +258,16 @@ export function ProfileView() {
|
|||
|
||||
{/* Billing Actions */}
|
||||
<div className="pt-2">
|
||||
{isPro ? (
|
||||
{isPastDue ? (
|
||||
<Button
|
||||
className="w-full bg-red-600 hover:bg-red-700 text-white border-0"
|
||||
onClick={handleManageBilling}
|
||||
size="sm"
|
||||
variant="default"
|
||||
>
|
||||
Pay Past Due
|
||||
</Button>
|
||||
) : hasProProduct ? (
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={handleManageBilling}
|
||||
|
|
@ -266,7 +301,7 @@ export function ProfileView() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{!isPro && (
|
||||
{!hasProProduct && (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||
{/* Free Plan */}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,11 @@ export const fetchSubscriptionStatus = (
|
|||
) =>
|
||||
useQuery({
|
||||
queryFn: async () => {
|
||||
const allPlans = [
|
||||
"consumer_pro",
|
||||
]
|
||||
const statusMap: Record<string, boolean | null> = {}
|
||||
const allPlans = ["consumer_pro"]
|
||||
const statusMap: Record<
|
||||
string,
|
||||
{ allowed: boolean; status: string | null }
|
||||
> = {}
|
||||
|
||||
await Promise.all(
|
||||
allPlans.map(async (plan) => {
|
||||
|
|
@ -25,10 +26,20 @@ export const fetchSubscriptionStatus = (
|
|||
const res = autumn.check({
|
||||
productId: plan,
|
||||
})
|
||||
statusMap[plan] = res.data?.allowed ?? false
|
||||
const allowed = res.data?.allowed ?? false
|
||||
|
||||
const product = autumn.customer?.products?.find(
|
||||
(p) => p.id === plan,
|
||||
)
|
||||
const productStatus = product?.status ?? null
|
||||
|
||||
statusMap[plan] = {
|
||||
allowed,
|
||||
status: productStatus,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error checking status for ${plan}:`, error)
|
||||
statusMap[plan] = false
|
||||
statusMap[plan] = { allowed: false, status: null }
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
|
@ -42,7 +53,10 @@ export const fetchSubscriptionStatus = (
|
|||
})
|
||||
|
||||
// Feature checks
|
||||
export const fetchMemoriesFeature = (autumn: ReturnType<typeof useCustomer>, isEnabled: boolean) =>
|
||||
export const fetchMemoriesFeature = (
|
||||
autumn: ReturnType<typeof useCustomer>,
|
||||
isEnabled: boolean,
|
||||
) =>
|
||||
useQuery({
|
||||
queryFn: async () => {
|
||||
const res = autumn.check({ featureId: "memories" })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue