ui: settings revamp

This commit is contained in:
Mahesh Sanikommmu 2025-09-04 16:59:47 -07:00
parent 497986524b
commit ee4e6192ac
12 changed files with 580 additions and 344 deletions

View file

@ -0,0 +1,12 @@
"use client";
import { BillingView } from "@/components/views/billing";
export default function BillingPage() {
return (
<div className="py-6 max-w-2xl">
<h1 className="text-2xl font-bold text-foreground mb-6">Billing & Subscription</h1>
<BillingView />
</div>
);
}

View file

@ -0,0 +1,12 @@
"use client";
import { IntegrationsView } from "@/components/views/integrations";
export default function IntegrationsPage() {
return (
<div className="py-6 max-w-4xl">
<h1 className="text-2xl font-bold text-foreground mb-6">Integrations</h1>
<IntegrationsView />
</div>
);
}

View file

@ -0,0 +1,56 @@
"use client"
import { Button } from "@/button"
import { AppHeader } from "@/components/header"
import { useRouter, usePathname } from "next/navigation"
import { cn } from "@repo/lib/utils"
export default function SettingsPageLayout({
children,
}: {
children: React.ReactNode
}) {
const router = useRouter()
const pathname = usePathname()
const navItems = [
{ label: "Profile", path: "/settings" },
{ label: "Integrations", path: "/settings/integrations" },
{ label: "Billing", path: "/settings/billing" },
{ label: "Support", path: "/settings/support" },
]
return (
<>
<AppHeader />
<div className="flex-1 overflow-hidden max-w-screen-lg mx-auto mt-4">
<div className="flex flex-col items-center">
<div className="w-full max-w-2xl">
<nav className="flex gap-[2px] px-1 py-1 text-sm rounded-[8px] bg-muted/50 text-foreground max-w-fit">
{navItems.map((item) => {
const isActive = pathname === item.path
return (
<Button
key={item.path}
onClick={() => router.push(item.path)}
variant="settingsNav"
size="sm"
className={cn(
"transition-all duration-200",
isActive
? "opacity-100 bg-card"
: "opacity-60 hover:opacity-100 hover:bg-card ",
)}
>
{item.label}
</Button>
)
})}
</nav>
{children}
</div>
</div>
</div>
</>
)
}

View file

@ -0,0 +1,12 @@
"use client";
import { ProfileView } from "@/components/views/profile";
export default function ProfilePage() {
return (
<div className="py-6 max-w-xl">
<h1 className="text-2xl font-bold text-foreground mb-2">Profile Settings</h1>
<ProfileView />
</div>
);
}

View file

@ -0,0 +1,98 @@
"use client";
import { Button } from "@repo/ui/components/button";
import { HeadingH3Bold } from "@repo/ui/text/heading/heading-h3-bold";
import { ExternalLink, Mail, MessageCircle } from "lucide-react";
export default function SupportPage() {
return (
<div className="py-6 max-w-2xl">
<h1 className="text-2xl font-bold text-foreground mb-6">Support & Help</h1>
<div className="space-y-6">
{/* Contact Options */}
<div className="bg-card border border-border rounded-lg p-6 space-y-4">
<HeadingH3Bold className="text-foreground">Get Help</HeadingH3Bold>
<p className="text-muted-foreground text-sm">
Need assistance? We're here to help! Choose the best way to reach us.
</p>
<div className="space-y-3">
<Button
className="w-full justify-start bg-blue-500/20 hover:bg-blue-500/30 text-blue-600 dark:text-blue-400 border-blue-500/30"
onClick={() => window.open("https://x.com/supermemoryai", "_blank")}
variant="outline"
>
<MessageCircle className="w-4 h-4 mr-2" />
Message us on X (Twitter)
<ExternalLink className="w-4 h-4 ml-auto" />
</Button>
<Button
className="w-full justify-start bg-green-500/20 hover:bg-green-500/30 text-green-600 dark:text-green-400 border-green-500/30"
onClick={() => window.open("mailto:dhravya@supermemory.ai", "_blank")}
variant="outline"
>
<Mail className="w-4 h-4 mr-2" />
Email us at dhravya@supermemory.ai
<ExternalLink className="w-4 h-4 ml-auto" />
</Button>
</div>
</div>
{/* FAQ Section */}
<div className="bg-card border border-border rounded-lg p-6 space-y-4">
<HeadingH3Bold className="text-foreground">Frequently Asked Questions</HeadingH3Bold>
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-foreground font-medium text-sm">How do I upgrade to Pro?</h4>
<p className="text-muted-foreground text-sm">
Go to the Billing tab in settings and click "Upgrade to Pro". You'll be redirected to our secure payment processor.
</p>
</div>
<div className="space-y-2">
<h4 className="text-foreground font-medium text-sm">What's included in the Pro plan?</h4>
<p className="text-muted-foreground text-sm">
Pro includes 5,000 memories (vs 200 in free), 10 connections to external services like Google Drive and Notion, advanced search features, and priority support.
</p>
</div>
<div className="space-y-2">
<h4 className="text-foreground font-medium text-sm">How do connections work?</h4>
<p className="text-muted-foreground text-sm">
Connections let you sync documents from Google Drive, Notion, and OneDrive automatically. supermemory will index and make them searchable.
</p>
</div>
<div className="space-y-2">
<h4 className="text-foreground font-medium text-sm">Can I cancel my subscription anytime?</h4>
<p className="text-muted-foreground text-sm">
Yes! You can cancel anytime from the Billing tab. Your Pro features will remain active until the end of your billing period.
</p>
</div>
</div>
</div>
{/* Feedback Section */}
<div className="bg-card border border-border rounded-lg p-6 space-y-4">
<HeadingH3Bold className="text-foreground">Feedback & Feature Requests</HeadingH3Bold>
<p className="text-muted-foreground text-sm">
Have ideas for new features or improvements? We'd love to hear from you!
</p>
<Button
className="w-full justify-start bg-purple-500/20 hover:bg-purple-500/30 text-purple-600 dark:text-purple-400 border-purple-500/30"
onClick={() => window.open("https://x.com/supermemoryai", "_blank")}
variant="outline"
>
<MessageCircle className="w-4 h-4 mr-2" />
Share your feedback on X
<ExternalLink className="w-4 h-4 ml-auto" />
</Button>
</div>
</div>
</div>
);
}

View file

@ -19,9 +19,11 @@ const buttonVariants = cva(
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
settingsNav: "cursor-pointer rounded-sm bg-transparent",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
settingsNav: "h-8 gap-0 px-0 py-0",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",

View file

@ -37,11 +37,7 @@ export function AppHeader() {
return (
<>
<header className="w-full h-16 px-4 flex items-center justify-between">
<Link
href="/"
rel="noopener noreferrer"
className="flex items-center"
>
<Link href="/" rel="noopener noreferrer" className="flex items-center">
{isMobile ? (
<Logo className="h-8" />
) : (
@ -52,7 +48,7 @@ export function AppHeader() {
<div className="flex items-center gap-3">
<Button
onClick={() => setShowAddMemoryView(true)}
className="bg-dodger-blue hover:bg-dodger-blue/90 text-white px-4 py-2 rounded-full cursor-pointer"
className="bg-[#09090B] hover:bg-[#09090B]/80 text-white px-4 py-2 rounded-full cursor-pointer"
variant="default"
size="default"
>
@ -78,12 +74,18 @@ export function AppHeader() {
</div>
<DropdownMenuSeparator />
<DropdownMenuItem className="cursor-pointer">
<DropdownMenuItem
className="cursor-pointer"
onClick={() => router.push("/settings")}
>
<Settings className="h-4 w-4 mr-2" />
Settings
</DropdownMenuItem>
<DropdownMenuItem className="cursor-pointer">
<DropdownMenuItem
className="cursor-pointer"
onClick={() => router.push("/settings")}
>
<CreditCard className="h-4 w-4 mr-2" />
Billing
</DropdownMenuItem>
@ -110,5 +112,5 @@ export function AppHeader() {
/>
)}
</>
);
)
}

View file

@ -1,35 +1,35 @@
import { useAuth } from "@lib/auth-context";
import { useAuth } from "@lib/auth-context"
import {
fetchConnectionsFeature,
fetchMemoriesFeature,
fetchSubscriptionStatus,
} from "@lib/queries";
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 { motion } from "motion/react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { analytics } from "@/lib/analytics";
} from "@lib/queries"
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 { motion } from "motion/react"
import Link from "next/link"
import { useEffect, useState } from "react"
import { analytics } from "@/lib/analytics"
export function BillingView() {
const autumn = useCustomer();
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const autumn = useCustomer()
const { user } = useAuth()
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
analytics.billingViewed();
}, []);
analytics.billingViewed()
}, [])
const { data: memoriesCheck } = fetchMemoriesFeature(autumn as any);
const { data: memoriesCheck } = fetchMemoriesFeature(autumn as any)
const memoriesUsed = memoriesCheck?.usage ?? 0;
const memoriesLimit = memoriesCheck?.included_usage ?? 0;
const memoriesUsed = memoriesCheck?.usage ?? 0
const memoriesLimit = memoriesCheck?.included_usage ?? 0
const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any);
const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any)
const connectionsUsed = connectionsCheck?.usage ?? 0;
const connectionsUsed = connectionsCheck?.usage ?? 0
// Fetch subscription status with React Query
const {
@ -37,34 +37,34 @@ export function BillingView() {
consumer_pro: null,
},
isLoading: isCheckingStatus,
} = fetchSubscriptionStatus(autumn as any);
} = fetchSubscriptionStatus(autumn as any)
// Handle upgrade
const handleUpgrade = async () => {
analytics.upgradeInitiated();
setIsLoading(true);
analytics.upgradeInitiated()
setIsLoading(true)
try {
await autumn.attach({
productId: "consumer_pro",
successUrl: "https://app.supermemory.ai/",
});
analytics.upgradeCompleted();
window.location.reload();
})
analytics.upgradeCompleted()
window.location.reload()
} catch (error) {
console.error(error);
setIsLoading(false);
console.error(error)
setIsLoading(false)
}
};
}
// Handle manage billing
const handleManageBilling = async () => {
analytics.billingPortalOpened();
analytics.billingPortalOpened()
await autumn.openBillingPortal({
returnUrl: "https://app.supermemory.ai",
});
};
})
}
const isPro = status.consumer_pro;
const isPro = status.consumer_pro
if (user?.isAnonymous) {
return (
@ -74,18 +74,20 @@ export function BillingView() {
initial={{ opacity: 0, scale: 0.9 }}
transition={{ type: "spring", damping: 20 }}
>
<p className="text-white/70 mb-4">Sign in to unlock premium features</p>
<p className="text-muted-foreground mb-4">
Sign in to unlock premium features
</p>
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<Button
asChild
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
className="bg-muted hover:bg-muted/80 text-foreground border-border"
size="sm"
>
<Link href="/login">Sign in</Link>
</Button>
</motion.div>
</motion.div>
);
)
}
if (isPro) {
@ -96,28 +98,28 @@ export function BillingView() {
initial={{ opacity: 0, y: 10 }}
>
<div className="space-y-3">
<HeadingH3Bold className="text-white flex items-center gap-2">
<HeadingH3Bold className="text-foreground flex items-center gap-2">
Pro Plan
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-0.5 rounded-full">
<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-white/70">
<p className="text-sm text-muted-foreground">
You're enjoying expanded memory capacity with supermemory Pro!
</p>
</div>
{/* Current Usage */}
<div className="space-y-3">
<h4 className="text-sm font-medium text-white/90">Current Usage</h4>
<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-white/70">Memories</span>
<span className="text-sm text-white/90">
<span className="text-sm text-muted-foreground">Memories</span>
<span className="text-sm text-foreground">
{memoriesUsed} / {memoriesLimit}
</span>
</div>
<div className="w-full bg-white/10 rounded-full h-2">
<div className="w-full bg-muted rounded-full h-2">
<div
className="bg-green-500 h-2 rounded-full transition-all"
style={{
@ -128,26 +130,19 @@ export function BillingView() {
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm text-white/70">Connections</span>
<span className="text-sm text-white/90">
<span className="text-sm text-muted-foreground">Connections</span>
<span className="text-sm text-foreground">
{connectionsUsed} / 10
</span>
</div>
</div>
</div>
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
<Button
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
onClick={handleManageBilling}
size="sm"
variant="outline"
>
Manage Billing
</Button>
</motion.div>
<Button onClick={handleManageBilling} size="sm" variant="default">
Manage Billing
</Button>
</motion.div>
);
)
}
return (
@ -158,17 +153,19 @@ export function BillingView() {
>
{/* Current Usage - Free Plan */}
<div className="space-y-3">
<HeadingH3Bold className="text-white">Current Plan: Free</HeadingH3Bold>
<HeadingH3Bold className="text-foreground">
Current Plan: Free
</HeadingH3Bold>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm text-white/70">Memories</span>
<span className="text-sm text-muted-foreground">Memories</span>
<span
className={`text-sm ${memoriesUsed >= memoriesLimit ? "text-red-400" : "text-white/90"}`}
className={`text-sm ${memoriesUsed >= memoriesLimit ? "text-red-500" : "text-foreground"}`}
>
{memoriesUsed} / {memoriesLimit}
</span>
</div>
<div className="w-full bg-white/10 rounded-full h-2">
<div className="w-full bg-muted rounded-full h-2">
<div
className={`h-2 rounded-full transition-all ${
memoriesUsed >= memoriesLimit ? "bg-red-500" : "bg-blue-500"
@ -183,23 +180,25 @@ export function BillingView() {
{/* Comparison */}
<div className="space-y-4">
<HeadingH3Bold className="text-white">Upgrade to Pro</HeadingH3Bold>
<HeadingH3Bold className="text-foreground">
Upgrade to Pro
</HeadingH3Bold>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Free Plan */}
<div className="p-4 bg-white/5 rounded-lg border border-white/10">
<h4 className="font-medium text-white/90 mb-3">Free Plan</h4>
<div className="p-4 bg-muted/50 rounded-lg border border-border">
<h4 className="font-medium text-foreground mb-3">Free Plan</h4>
<ul className="space-y-2">
<li className="flex items-center gap-2 text-sm text-white/70">
<CheckCircle className="h-4 w-4 text-green-400" />
<li className="flex items-center gap-2 text-sm text-muted-foreground">
<CheckCircle className="h-4 w-4 text-green-500" />
200 memories
</li>
<li className="flex items-center gap-2 text-sm text-white/70">
<X className="h-4 w-4 text-red-400" />
<li className="flex items-center gap-2 text-sm text-muted-foreground">
<X className="h-4 w-4 text-red-500" />
No connections
</li>
<li className="flex items-center gap-2 text-sm text-white/70">
<CheckCircle className="h-4 w-4 text-green-400" />
<li className="flex items-center gap-2 text-sm text-muted-foreground">
<CheckCircle className="h-4 w-4 text-green-500" />
Basic search
</li>
</ul>
@ -207,27 +206,27 @@ export function BillingView() {
{/* Pro Plan */}
<div className="p-4 bg-gradient-to-br from-blue-500/10 to-purple-500/10 rounded-lg border border-blue-500/20">
<h4 className="font-medium text-white mb-3 flex items-center gap-2">
<h4 className="font-medium text-foreground mb-3 flex items-center gap-2">
Pro Plan
<span className="text-xs bg-blue-500/20 text-blue-400 px-2 py-0.5 rounded-full">
<span className="text-xs bg-blue-500/20 text-blue-600 dark:text-blue-400 px-2 py-0.5 rounded-full">
Recommended
</span>
</h4>
<ul className="space-y-2">
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
<li className="flex items-center gap-2 text-sm text-foreground">
<CheckCircle className="h-4 w-4 text-green-500" />
5000 memories
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
<li className="flex items-center gap-2 text-sm text-foreground">
<CheckCircle className="h-4 w-4 text-green-500" />
10 connections
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
<li className="flex items-center gap-2 text-sm text-foreground">
<CheckCircle className="h-4 w-4 text-green-500" />
Advanced search
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
<li className="flex items-center gap-2 text-sm text-foreground">
<CheckCircle className="h-4 w-4 text-green-500" />
Priority support
</li>
</ul>
@ -252,10 +251,10 @@ export function BillingView() {
</Button>
</motion.div>
<p className="text-xs text-white/50 text-center">
<p className="text-xs text-muted-foreground text-center">
Cancel anytime. No questions asked.
</p>
</div>
</motion.div>
);
)
}

View file

@ -278,25 +278,25 @@ export function IntegrationsView() {
return (
<div className="space-y-4 sm:space-y-4 custom-scrollbar">
{/* iOS Shortcuts */}
<div className="bg-white/5 rounded-xl border border-white/10 overflow-hidden">
<div className="bg-card border border-border rounded-xl overflow-hidden">
<div className="p-4 sm:p-5">
<div className="flex items-start gap-3 mb-3">
<div className="p-2 bg-blue-500/20 rounded-lg flex-shrink-0">
<Smartphone className="h-5 w-5 text-blue-400" />
<Smartphone className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-white font-semibold text-base mb-1">
<h3 className="text-foreground font-semibold text-base mb-1">
Apple shortcuts
</h3>
<p className="text-white/70 text-sm leading-relaxed">
<p className="text-muted-foreground text-sm leading-relaxed">
Add memories directly from iPhone, iPad or Mac.
</p>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-2 sm:gap-3">
<Button
variant="ghost"
className="flex-1 text-white hover:bg-blue-500/10 bg-[#171F59]/75 "
variant="outline"
className="flex-1 bg-blue-500/10 hover:bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30"
onClick={() => handleShortcutClick("add")}
disabled={createApiKeyMutation.isPending}
>
@ -311,8 +311,8 @@ export function IntegrationsView() {
: "Add Memory Shortcut"}
</Button>
<Button
variant="ghost"
className="flex-1 text-white hover:bg-blue-500/10 bg-[#171F59]/75"
variant="outline"
className="flex-1 bg-blue-500/10 hover:bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30"
onClick={() => handleShortcutClick("search")}
disabled={createApiKeyMutation.isPending}
>
@ -331,22 +331,22 @@ export function IntegrationsView() {
</div>
{/* Chrome Extension */}
<div className="bg-white/5 rounded-xl border border-white/10 overflow-hidden opacity-75">
<div className="bg-card border border-border rounded-xl overflow-hidden opacity-75">
<div className="p-4 sm:p-5">
<div className="flex items-start gap-3">
<div className="p-2 bg-orange-500/20 rounded-lg flex-shrink-0">
<ChromeIcon className="h-5 w-5 text-orange-400" />
<ChromeIcon className="h-5 w-5 text-orange-600 dark:text-orange-400" />
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-col sm:flex-row sm:items-center gap-2 mb-1">
<h3 className="text-white font-semibold text-base">
<h3 className="text-foreground font-semibold text-base">
Chrome Extension
</h3>
<div className="px-2 py-1 bg-orange-500/20 text-orange-400 text-xs rounded-full flex-shrink-0 w-fit">
<div className="px-2 py-1 bg-orange-500/20 text-orange-600 dark:text-orange-400 text-xs rounded-full flex-shrink-0 w-fit">
Coming Soon
</div>
</div>
<p className="text-white/70 text-sm leading-relaxed">
<p className="text-muted-foreground text-sm leading-relaxed">
Save web content with one click
</p>
</div>
@ -355,12 +355,12 @@ export function IntegrationsView() {
</div>
{/* Connections Section */}
<div className="bg-white/5 rounded-xl border border-white/10 overflow-hidden">
<div className="bg-card border border-border rounded-xl overflow-hidden">
<div className="p-4 sm:p-5">
<div className="flex items-start gap-3 mb-3">
<div className="p-2 bg-green-500/20 rounded-lg flex-shrink-0">
<svg
className="h-5 w-5 text-green-400"
className="h-5 w-5 text-green-600 dark:text-green-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@ -375,14 +375,14 @@ export function IntegrationsView() {
</svg>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-white font-semibold text-base mb-1">
<h3 className="text-foreground font-semibold text-base mb-1">
Connections
</h3>
<p className="text-white/70 text-sm leading-relaxed mb-2">
<p className="text-muted-foreground text-sm leading-relaxed mb-2">
Connect your accounts to sync document.
</p>
{!isProUser && (
<p className="text-xs text-white/50">
<p className="text-xs text-muted-foreground">
Connections require a Pro subscription
</p>
)}
@ -396,15 +396,15 @@ export function IntegrationsView() {
className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mb-3"
initial={{ opacity: 0, y: -10 }}
>
<p className="text-sm text-yellow-400 mb-2">
<p className="text-sm text-yellow-600 dark:text-yellow-400 mb-2">
🔌 Connections are a Pro feature
</p>
<p className="text-xs text-white/60 mb-3">
<p className="text-xs text-muted-foreground mb-3">
Connect Google Drive, Notion, OneDrive and more to automatically
sync your documents.
</p>
<Button
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-400 border-yellow-500/30 w-full sm:w-auto"
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-600 dark:text-yellow-400 border-yellow-500/30 w-full sm:w-auto"
onClick={handleUpgrade}
size="sm"
variant="secondary"
@ -420,12 +420,12 @@ export function IntegrationsView() {
{Object.keys(CONNECTORS).map((_, i) => (
<motion.div
animate={{ opacity: 1 }}
className="p-3 bg-white/5 rounded-lg"
className="p-3 bg-muted/50 rounded-lg"
initial={{ opacity: 0 }}
key={`skeleton-${Date.now()}-${i}`}
transition={{ delay: i * 0.1 }}
>
<Skeleton className="h-12 w-full bg-white/10" />
<Skeleton className="h-12 w-full bg-muted" />
</motion.div>
))}
</div>
@ -441,7 +441,7 @@ export function IntegrationsView() {
return (
<motion.div
animate={{ opacity: 1, y: 0 }}
className="flex flex-col sm:flex-row sm:items-center gap-3 p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-colors"
className="flex flex-col sm:flex-row sm:items-center gap-3 p-3 bg-muted/50 rounded-lg hover:bg-muted/80 transition-colors"
initial={{ opacity: 0, y: 20 }}
key={provider}
transition={{ delay: index * 0.05 }}
@ -457,30 +457,30 @@ export function IntegrationsView() {
</motion.div>
<div className="flex-1 min-w-0">
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
<p className="font-medium text-white text-sm">
<p className="font-medium text-foreground text-sm">
{config.title}
</p>
{isConnected ? (
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-green-400 rounded-full"></div>
<span className="text-xs text-green-400 font-medium">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-xs text-green-600 dark:text-green-400 font-medium">
Connected
</span>
</div>
) : (
<div className="hidden sm:flex items-center gap-1">
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
<span className="text-xs text-gray-400 font-medium">
<div className="w-2 h-2 bg-muted-foreground rounded-full"></div>
<span className="text-xs text-muted-foreground font-medium">
Disconnected
</span>
</div>
)}
</div>
<p className="text-xs text-white/60 mt-0.5">
<p className="text-xs text-muted-foreground mt-0.5">
{config.description}
</p>
{connection?.email && (
<p className="text-xs text-white/50 mt-1">
<p className="text-xs text-muted-foreground mt-1">
{connection.email}
</p>
)}
@ -494,7 +494,7 @@ export function IntegrationsView() {
whileTap={{ scale: 0.95 }}
>
<Button
className="text-white/70 hover:text-red-400 hover:bg-red-500/10 w-full sm:w-auto"
className="text-muted-foreground hover:text-red-500 hover:bg-red-500/10 w-full sm:w-auto"
disabled={deleteConnectionMutation.isPending}
onClick={() =>
deleteConnectionMutation.mutate(connection.id)
@ -509,8 +509,8 @@ export function IntegrationsView() {
) : (
<div className="flex items-center justify-between gap-2 w-full sm:w-auto">
<div className="sm:hidden flex items-center gap-1">
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
<span className="text-xs text-gray-400 font-medium">
<div className="w-2 h-2 bg-muted-foreground rounded-full"></div>
<span className="text-xs text-muted-foreground font-medium">
Disconnected
</span>
</div>
@ -520,7 +520,7 @@ export function IntegrationsView() {
className="flex-shrink-0"
>
<Button
className="bg-blue-600/20 hover:bg-blue-600/30 text-blue-400 border-blue-600/30 min-w-[80px]"
className="bg-blue-500/20 hover:bg-blue-500/30 text-blue-600 dark:text-blue-400 border-blue-500/30 min-w-[80px]"
disabled={addConnectionMutation.isPending}
onClick={() => {
addConnectionMutation.mutate(
@ -548,14 +548,14 @@ export function IntegrationsView() {
</div>
<div className="p-3">
<p className="text-white/70 text-sm leading-relaxed text-center">
<p className="text-muted-foreground text-sm leading-relaxed text-center">
More integrations are coming soon! Have a suggestion? Share it with us
on{" "}
<a
href="https://x.com/supermemoryai"
target="_blank"
rel="noopener noreferrer"
className="text-orange-500 hover:text-orange-400 underline"
className="text-orange-600 dark:text-orange-400 hover:text-orange-500 underline"
>
X
</a>
@ -566,9 +566,9 @@ export function IntegrationsView() {
{/* API Key Modal */}
<Dialog open={showApiKeyModal} onOpenChange={handleDialogClose}>
<DialogPortal>
<DialogContent className="bg-[#0f1419] border-white/10 text-white md:max-w-md z-[100]">
<DialogContent className="bg-background border-border text-foreground md:max-w-md z-[100]">
<DialogHeader>
<DialogTitle className="text-white text-lg font-semibold">
<DialogTitle className="text-foreground text-lg font-semibold">
Setup{" "}
{selectedShortcutType === "add"
? "Add Memory"
@ -584,7 +584,7 @@ export function IntegrationsView() {
<div className="space-y-2">
<label
htmlFor={apiKeyId}
className="text-sm font-medium text-white/80"
className="text-sm font-medium text-foreground"
>
Your API Key
</label>
@ -594,16 +594,16 @@ export function IntegrationsView() {
type="text"
value={apiKey}
readOnly
className="flex-1 bg-white/5 border border-white/20 rounded-lg px-3 py-2 text-sm text-white font-mono"
className="flex-1 bg-muted border border-border rounded-lg px-3 py-2 text-sm text-foreground font-mono"
/>
<Button
size="sm"
variant="ghost"
onClick={handleCopyApiKey}
className="text-white/70 hover:text-white hover:bg-white/10"
className="text-muted-foreground hover:text-foreground hover:bg-muted"
>
{copied ? (
<Check className="h-4 w-4 text-green-400" />
<Check className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
@ -613,31 +613,31 @@ export function IntegrationsView() {
{/* Steps */}
<div className="space-y-3">
<h4 className="text-sm font-medium text-white/80">
<h4 className="text-sm font-medium text-foreground">
Follow these steps:
</h4>
<div className="space-y-2">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/20 text-blue-400 rounded-full flex items-center justify-center text-xs font-medium">
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/20 text-blue-600 dark:text-blue-400 rounded-full flex items-center justify-center text-xs font-medium">
1
</div>
<p className="text-sm text-white/70">
<p className="text-sm text-muted-foreground">
Click "Add to Shortcuts" below to open the shortcut
</p>
</div>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/20 text-blue-400 rounded-full flex items-center justify-center text-xs font-medium">
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/20 text-blue-600 dark:text-blue-400 rounded-full flex items-center justify-center text-xs font-medium">
2
</div>
<p className="text-sm text-white/70">
<p className="text-sm text-muted-foreground">
Paste your API key when prompted
</p>
</div>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/20 text-blue-400 rounded-full flex items-center justify-center text-xs font-medium">
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/20 text-blue-600 dark:text-blue-400 rounded-full flex items-center justify-center text-xs font-medium">
3
</div>
<p className="text-sm text-white/70">
<p className="text-sm text-muted-foreground">
Start using your shortcut!
</p>
</div>

View file

@ -1,80 +1,64 @@
"use client";
"use client"
import { authClient } from "@lib/auth";
import { useAuth } from "@lib/auth-context";
import { useAuth } from "@lib/auth-context"
import {
fetchConnectionsFeature,
fetchMemoriesFeature,
fetchSubscriptionStatus,
} from "@lib/queries";
import { Button } from "@repo/ui/components/button";
import { HeadingH3Bold } from "@repo/ui/text/heading/heading-h3-bold";
import { useCustomer } from "autumn-js/react";
import {
CheckCircle,
CreditCard,
LoaderIcon,
LogOut,
User,
X,
} from "lucide-react";
import { motion } from "motion/react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
import { analytics } from "@/lib/analytics";
} from "@lib/queries"
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 { motion } from "motion/react"
import Link from "next/link"
import { useState } from "react"
export function ProfileView() {
const router = useRouter();
const pathname = usePathname();
const { user: session, org } = useAuth();
const organizations = org;
const autumn = useCustomer();
const [isLoading, setIsLoading] = useState(false);
const { user: session, org } = useAuth()
const organizations = org
const autumn = useCustomer()
const [isLoading, setIsLoading] = useState(false)
const { data: memoriesCheck } = fetchMemoriesFeature(autumn as any);
const memoriesUsed = memoriesCheck?.usage ?? 0;
const memoriesLimit = memoriesCheck?.included_usage ?? 0;
const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any);
const connectionsUsed = connectionsCheck?.usage ?? 0;
// Fetch subscription status with React Query
const {
data: status = {
consumer_pro: null,
},
isLoading: isCheckingStatus,
} = fetchSubscriptionStatus(autumn as any);
} = fetchSubscriptionStatus(autumn, !autumn.isLoading)
const isPro = status.consumer_pro;
const isPro = status.consumer_pro
const handleLogout = () => {
analytics.userSignedOut();
authClient.signOut();
router.push("/login");
};
const { data: memoriesCheck } = fetchMemoriesFeature(
autumn,
!isCheckingStatus && !autumn.isLoading,
)
const memoriesUsed = memoriesCheck?.usage ?? 0
const memoriesLimit = memoriesCheck?.included_usage ?? 0
const { data: connectionsCheck } = fetchConnectionsFeature(autumn)
const connectionsUsed = connectionsCheck?.usage ?? 0
const handleUpgrade = async () => {
setIsLoading(true);
setIsLoading(true)
try {
await autumn.attach({
productId: "consumer_pro",
successUrl: "https://app.supermemory.ai/",
});
window.location.reload();
})
window.location.reload()
} catch (error) {
console.error(error);
setIsLoading(false);
console.error(error)
setIsLoading(false)
}
};
}
// Handle manage billing
const handleManageBilling = async () => {
await autumn.openBillingPortal({
returnUrl: "https://app.supermemory.ai",
});
};
})
}
if (session?.isAnonymous) {
return (
@ -85,13 +69,13 @@ export function ProfileView() {
initial={{ opacity: 0, scale: 0.9 }}
transition={{ type: "spring", damping: 20 }}
>
<p className="text-white/70 mb-4">
<p className="text-foreground/70 mb-4">
Sign in to access your profile and billing
</p>
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<Button
asChild
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
className="bg-muted hover:bg-muted/80 text-foreground border-border"
size="sm"
>
<Link href="/login">Sign in</Link>
@ -99,95 +83,160 @@ export function ProfileView() {
</motion.div>
</motion.div>
</div>
);
)
}
return (
<div className="space-y-4">
{/* Profile Section */}
<div className="bg-white/5 rounded-lg p-4 space-y-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center">
<User className="w-5 h-5 text-white/80" />
<div className="bg-card border border-border rounded-lg p-3 sm:p-4 space-y-4">
<div className="flex items-start gap-3 sm:gap-4">
<div className="w-12 h-12 sm:w-16 sm:h-16 bg-gradient-to-br from-blue-500 to-purple-500 rounded-full flex items-center justify-center flex-shrink-0">
{session?.image ? (
<img
src={session.image}
alt={session?.name || session?.email || "User"}
className="w-full h-full rounded-full object-cover"
/>
) : (
<User className="w-6 h-6 sm:w-8 sm:h-8 text-white" />
)}
</div>
<div className="flex-1">
<p className="text-white font-medium text-sm">{session?.email}</p>
<p className="text-white/60 text-xs">Logged in</p>
<div className="flex-1 min-w-0">
<div className="space-y-1">
{session?.name && (
<h3 className="text-foreground font-semibold text-base sm:text-lg truncate">
{session.name}
</h3>
)}
<p className="text-foreground font-medium text-sm truncate">
{session?.email}
</p>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<div className="w-2 h-2 bg-green-500 rounded-full" />
Active
</div>
</div>
</div>
</div>
{/* Additional Profile Details */}
<div className="border-t border-border pt-3 space-y-2">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 text-xs">
<div>
<span className="text-muted-foreground block">Organization</span>
<span className="text-foreground font-medium">
{organizations?.name || "Personal"}
</span>
</div>
<div>
<span className="text-muted-foreground block">Member since</span>
<span className="text-foreground font-medium">
{session?.createdAt
? new Date(session.createdAt).toLocaleDateString("en-US", {
month: "short",
year: "numeric",
})
: "Recent"}
</span>
</div>
</div>
</div>
</div>
{/* Billing Section */}
<div className="bg-white/5 rounded-lg p-4 space-y-3">
<div className="flex items-center gap-3 mb-3">
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center">
<CreditCard className="w-5 h-5 text-white/80" />
{autumn.isLoading || isCheckingStatus ? (
<div className="bg-card border border-border rounded-lg p-3 sm:p-4 space-y-3">
<div className="flex items-center gap-3 mb-3">
<Skeleton className="w-8 h-8 sm:w-10 sm:h-10 rounded-full" />
<div className="flex-1 space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-3 w-32" />
</div>
</div>
<div className="flex-1">
<HeadingH3Bold className="text-white text-sm">
{isPro ? "Pro Plan" : "Free Plan"}
{isPro && (
<span className="ml-2 text-xs bg-green-500/20 text-green-400 px-2 py-0.5 rounded-full">
Active
</span>
)}
</HeadingH3Bold>
<p className="text-white/60 text-xs">
{isPro ? "Expanded memory capacity" : "Basic plan"}
</p>
<div className="space-y-2">
<div className="flex justify-between items-center">
<Skeleton className="h-3 w-16" />
<Skeleton className="h-3 w-12" />
</div>
<Skeleton className="h-2 w-full rounded-full" />
</div>
<div className="flex justify-between items-center">
<Skeleton className="h-3 w-20" />
<Skeleton className="h-3 w-8" />
</div>
<div className="pt-2">
<Skeleton className="h-8 w-full rounded" />
</div>
</div>
{/* Usage Stats */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm text-white/70">Memories</span>
<span
className={`text-sm ${memoriesUsed >= memoriesLimit ? "text-red-400" : "text-white/90"}`}
>
{memoriesUsed} / {memoriesLimit}
</span>
) : (
<div className="bg-card border border-border rounded-lg p-3 sm:p-4 space-y-3">
<div className="flex items-center gap-3 mb-3">
<div className="w-8 h-8 sm:w-10 sm:h-10 bg-muted rounded-full flex items-center justify-center">
<CreditCard className="w-4 h-4 sm:w-5 sm:h-5 text-muted-foreground" />
</div>
<div className="flex-1">
<HeadingH3Bold className="text-foreground text-sm">
{isPro ? "Pro Plan" : "Free Plan"}
{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>
)}
</HeadingH3Bold>
<p className="text-muted-foreground text-xs">
{isPro ? "Expanded memory capacity" : "Basic plan"}
</p>
</div>
</div>
<div className="w-full bg-white/10 rounded-full h-2">
<div
className={`h-2 rounded-full transition-all ${
memoriesUsed >= memoriesLimit
? "bg-red-500"
: isPro
? "bg-green-500"
: "bg-blue-500"
}`}
style={{
width: `${Math.min((memoriesUsed / memoriesLimit) * 100, 100)}%`,
}}
/>
</div>
</div>
{isPro && (
<div className="flex justify-between items-center">
<span className="text-sm text-white/70">Connections</span>
<span className="text-sm text-white/90">
{connectionsUsed} / 10
</span>
{/* Usage Stats */}
<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 ${memoriesUsed >= memoriesLimit ? "text-red-500" : "text-foreground"}`}
>
{memoriesUsed} / {memoriesLimit}
</span>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className={`h-2 rounded-full transition-all ${
memoriesUsed >= memoriesLimit
? "bg-red-500"
: isPro
? "bg-green-500"
: "bg-blue-500"
}`}
style={{
width: `${Math.min((memoriesUsed / memoriesLimit) * 100, 100)}%`,
}}
/>
</div>
</div>
)}
{/* Billing Actions */}
<div className="pt-2">
{isPro ? (
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
{isPro && (
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Connections</span>
<span className="text-sm text-foreground">
{connectionsUsed} / 10
</span>
</div>
)}
{/* Billing Actions */}
<div className="pt-2">
{isPro ? (
<Button
className="w-full bg-white/10 hover:bg-white/20 text-white border-white/20"
className="w-full"
onClick={handleManageBilling}
size="sm"
variant="outline"
variant="default"
>
Manage Billing
</Button>
</motion.div>
) : (
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
) : (
<Button
className="w-full bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 text-white border-0"
disabled={isLoading || isCheckingStatus}
@ -197,90 +246,84 @@ export function ProfileView() {
{isLoading || isCheckingStatus ? (
<>
<LoaderIcon className="h-4 w-4 animate-spin mr-2" />
Upgrading...
<span className="hidden sm:inline">Upgrading...</span>
<span className="sm:hidden">Loading...</span>
</>
) : (
"Upgrade to Pro - $15/month"
<>
<span className="hidden sm:inline">
Upgrade to Pro - $15/month
</span>
<span className="sm:hidden">Upgrade to Pro</span>
</>
)}
</Button>
</motion.div>
)}
</div>
</div>
{/* Plan Comparison - Only show for free users */}
{!isPro && (
<div className="bg-white/5 rounded-lg p-4 space-y-4">
<HeadingH3Bold className="text-white text-sm">
Upgrade to Pro
</HeadingH3Bold>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Free Plan */}
<div className="p-3 bg-white/5 rounded-lg border border-white/10">
<h4 className="font-medium text-white/90 mb-3 text-sm">
Free Plan
</h4>
<ul className="space-y-2">
<li className="flex items-center gap-2 text-sm text-white/70">
<CheckCircle className="h-4 w-4 text-green-400" />
200 memories
</li>
<li className="flex items-center gap-2 text-sm text-white/70">
<X className="h-4 w-4 text-red-400" />
No connections
</li>
<li className="flex items-center gap-2 text-sm text-white/70">
<CheckCircle className="h-4 w-4 text-green-400" />
Basic search
</li>
</ul>
</div>
{/* Pro Plan */}
<div className="p-3 bg-gradient-to-br from-blue-500/10 to-purple-500/10 rounded-lg border border-blue-500/20">
<h4 className="font-medium text-white mb-3 flex items-center gap-2 text-sm">
Pro Plan
<span className="text-xs bg-blue-500/20 text-blue-400 px-2 py-0.5 rounded-full">
Recommended
</span>
</h4>
<ul className="space-y-2">
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
5000 memories
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
10 connections
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
Advanced search
</li>
<li className="flex items-center gap-2 text-sm text-white/90">
<CheckCircle className="h-4 w-4 text-green-400" />
Priority support
</li>
</ul>
</div>
)}
</div>
<p className="text-xs text-white/50 text-center">
$15/month (only for first 100 users) Cancel anytime. No questions
asked.
</p>
{!isPro && (
<div className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
{/* Free Plan */}
<div className="p-3 bg-muted/50 rounded-lg border border-border">
<h4 className="font-medium text-foreground mb-3 text-sm">
Free Plan
</h4>
<ul className="space-y-2">
<li className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground">
<CheckCircle className="h-3 w-3 sm:h-4 sm:w-4 text-green-500 flex-shrink-0" />
200 memories
</li>
<li className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground">
<X className="h-3 w-3 sm:h-4 sm:w-4 text-red-500 flex-shrink-0" />
No connections
</li>
<li className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground">
<CheckCircle className="h-3 w-3 sm:h-4 sm:w-4 text-green-500 flex-shrink-0" />
Basic search
</li>
</ul>
</div>
{/* Pro Plan */}
<div className="p-3 bg-gradient-to-br from-blue-500/10 to-purple-500/10 rounded-lg border border-blue-500/20">
<h4 className="font-medium text-foreground mb-3 text-sm">
<div className="flex items-center gap-2 flex-wrap">
<span>Pro Plan</span>
<span className="text-xs bg-blue-500/20 text-blue-600 dark:text-blue-400 px-2 py-0.5 rounded-full">
Recommended
</span>
</div>
</h4>
<ul className="space-y-2">
<li className="flex items-center gap-2 text-xs sm:text-sm text-foreground">
<CheckCircle className="h-3 w-3 sm:h-4 sm:w-4 text-green-500 flex-shrink-0" />
5000 memories
</li>
<li className="flex items-center gap-2 text-xs sm:text-sm text-foreground">
<CheckCircle className="h-3 w-3 sm:h-4 sm:w-4 text-green-500 flex-shrink-0" />
10 connections
</li>
<li className="flex items-center gap-2 text-xs sm:text-sm text-foreground">
<CheckCircle className="h-3 w-3 sm:h-4 sm:w-4 text-green-500 flex-shrink-0" />
Advanced search
</li>
<li className="flex items-center gap-2 text-xs sm:text-sm text-foreground">
<CheckCircle className="h-3 w-3 sm:h-4 sm:w-4 text-green-500 flex-shrink-0" />
Priority support
</li>
</ul>
</div>
</div>
<p className="text-xs text-muted-foreground text-center leading-relaxed">
$15/month (only for first 100 users) Cancel anytime. No
questions asked.
</p>
</div>
)}
</div>
)}
<Button
className="w-full bg-red-500/20 hover:bg-red-500/30 text-red-200 border-red-500/30"
onClick={handleLogout}
variant="destructive"
>
<LogOut className="w-4 h-4 mr-2" />
Sign Out
</Button>
</div>
);
)
}

View file

@ -3,13 +3,11 @@ import type { useCustomer } from "autumn-js/react"
export const fetchSubscriptionStatus = (
autumn: ReturnType<typeof useCustomer>,
isEnabled: boolean,
) =>
useQuery({
queryFn: async () => {
const allPlans = [
"pro",
"memory_starter",
"memory_growth",
"consumer_pro",
]
const statusMap: Record<string, boolean | null> = {}
@ -17,7 +15,7 @@ export const fetchSubscriptionStatus = (
await Promise.all(
allPlans.map(async (plan) => {
try {
const res = await autumn.check({
const res = autumn.check({
productId: plan,
})
statusMap[plan] = res.data?.allowed ?? false
@ -33,18 +31,20 @@ export const fetchSubscriptionStatus = (
queryKey: ["subscription-status"],
refetchInterval: 5000, // Refetch every 5 seconds
staleTime: 4000, // Consider data stale after 4 seconds
enabled: isEnabled,
})
// Feature checks
export const fetchMemoriesFeature = (autumn: ReturnType<typeof useCustomer>) =>
export const fetchMemoriesFeature = (autumn: ReturnType<typeof useCustomer>, isEnabled: boolean) =>
useQuery({
queryFn: async () => {
const res = await autumn.check({ featureId: "memories" })
const res = autumn.check({ featureId: "memories" })
return res.data
},
queryKey: ["autumn-feature", "memories"],
staleTime: 30 * 1000, // 30 seconds
gcTime: 5 * 60 * 1000, // 5 minutes
enabled: isEnabled,
})
export const fetchConnectionsFeature = (
@ -52,7 +52,7 @@ export const fetchConnectionsFeature = (
) =>
useQuery({
queryFn: async () => {
const res = await autumn.check({ featureId: "connections" })
const res = autumn.check({ featureId: "connections" })
return res.data
},
queryKey: ["autumn-feature", "connections"],
@ -66,7 +66,7 @@ export const fetchConsumerProProduct = (
) =>
useQuery({
queryFn: async () => {
const res = await autumn.check({ productId: "consumer_pro" })
const res = autumn.check({ productId: "consumer_pro" })
return res.data
},
queryKey: ["autumn-product", "consumer_pro"],
@ -77,7 +77,7 @@ export const fetchConsumerProProduct = (
export const fetchProProduct = (autumn: ReturnType<typeof useCustomer>) =>
useQuery({
queryFn: async () => {
const res = await autumn.check({ productId: "pro" })
const res = autumn.check({ productId: "pro" })
return res.data
},
queryKey: ["autumn-product", "pro"],

View file

@ -39,7 +39,7 @@ function DropdownMenuContent({
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md p-1 shadow-md",
className,
)}
data-slot="dropdown-menu-content"