mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
ui: settings revamp
This commit is contained in:
parent
497986524b
commit
ee4e6192ac
12 changed files with 580 additions and 344 deletions
12
apps/web/app/settings/billing/page.tsx
Normal file
12
apps/web/app/settings/billing/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
12
apps/web/app/settings/integrations/page.tsx
Normal file
12
apps/web/app/settings/integrations/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
56
apps/web/app/settings/layout.tsx
Normal file
56
apps/web/app/settings/layout.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
12
apps/web/app/settings/page.tsx
Normal file
12
apps/web/app/settings/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
98
apps/web/app/settings/support/page.tsx
Normal file
98
apps/web/app/settings/support/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue