From ee4e6192ac91d8101e66e979369936cdc333b63f Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommmu Date: Thu, 4 Sep 2025 16:59:47 -0700 Subject: [PATCH] ui: settings revamp --- apps/web/app/settings/billing/page.tsx | 12 + apps/web/app/settings/integrations/page.tsx | 12 + apps/web/app/settings/layout.tsx | 56 +++ apps/web/app/settings/page.tsx | 12 + apps/web/app/settings/support/page.tsx | 98 +++++ apps/web/button.tsx | 2 + apps/web/components/header.tsx | 20 +- apps/web/components/views/billing.tsx | 161 ++++---- apps/web/components/views/integrations.tsx | 100 ++--- apps/web/components/views/profile.tsx | 431 +++++++++++--------- packages/lib/queries.ts | 18 +- packages/ui/components/dropdown-menu.tsx | 2 +- 12 files changed, 580 insertions(+), 344 deletions(-) create mode 100644 apps/web/app/settings/billing/page.tsx create mode 100644 apps/web/app/settings/integrations/page.tsx create mode 100644 apps/web/app/settings/layout.tsx create mode 100644 apps/web/app/settings/page.tsx create mode 100644 apps/web/app/settings/support/page.tsx diff --git a/apps/web/app/settings/billing/page.tsx b/apps/web/app/settings/billing/page.tsx new file mode 100644 index 00000000..7ff46838 --- /dev/null +++ b/apps/web/app/settings/billing/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { BillingView } from "@/components/views/billing"; + +export default function BillingPage() { + return ( +
+

Billing & Subscription

+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/settings/integrations/page.tsx b/apps/web/app/settings/integrations/page.tsx new file mode 100644 index 00000000..307a4e8f --- /dev/null +++ b/apps/web/app/settings/integrations/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { IntegrationsView } from "@/components/views/integrations"; + +export default function IntegrationsPage() { + return ( +
+

Integrations

+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/settings/layout.tsx b/apps/web/app/settings/layout.tsx new file mode 100644 index 00000000..e05c7a47 --- /dev/null +++ b/apps/web/app/settings/layout.tsx @@ -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 ( + <> + +
+
+
+ + {children} +
+
+
+ + ) +} diff --git a/apps/web/app/settings/page.tsx b/apps/web/app/settings/page.tsx new file mode 100644 index 00000000..c8d5046b --- /dev/null +++ b/apps/web/app/settings/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { ProfileView } from "@/components/views/profile"; + +export default function ProfilePage() { + return ( +
+

Profile Settings

+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/settings/support/page.tsx b/apps/web/app/settings/support/page.tsx new file mode 100644 index 00000000..33517a9a --- /dev/null +++ b/apps/web/app/settings/support/page.tsx @@ -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 ( +
+

Support & Help

+ +
+ {/* Contact Options */} +
+ Get Help +

+ Need assistance? We're here to help! Choose the best way to reach us. +

+ +
+ + + +
+
+ + {/* FAQ Section */} +
+ Frequently Asked Questions + +
+
+

How do I upgrade to Pro?

+

+ Go to the Billing tab in settings and click "Upgrade to Pro". You'll be redirected to our secure payment processor. +

+
+ +
+

What's included in the Pro plan?

+

+ 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. +

+
+ +
+

How do connections work?

+

+ Connections let you sync documents from Google Drive, Notion, and OneDrive automatically. supermemory will index and make them searchable. +

+
+ +
+

Can I cancel my subscription anytime?

+

+ Yes! You can cancel anytime from the Billing tab. Your Pro features will remain active until the end of your billing period. +

+
+
+
+ + {/* Feedback Section */} +
+ Feedback & Feature Requests +

+ Have ideas for new features or improvements? We'd love to hear from you! +

+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/button.tsx b/apps/web/button.tsx index 3a671e03..9b274b47 100644 --- a/apps/web/button.tsx +++ b/apps/web/button.tsx @@ -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", diff --git a/apps/web/components/header.tsx b/apps/web/components/header.tsx index dd24a43d..9b1373de 100644 --- a/apps/web/components/header.tsx +++ b/apps/web/components/header.tsx @@ -37,11 +37,7 @@ export function AppHeader() { return ( <>
- + {isMobile ? ( ) : ( @@ -52,7 +48,7 @@ export function AppHeader() {
- + router.push("/settings")} + > Settings - + router.push("/settings")} + > Billing @@ -110,5 +112,5 @@ export function AppHeader() { /> )} - ); + ) } \ No newline at end of file diff --git a/apps/web/components/views/billing.tsx b/apps/web/components/views/billing.tsx index 033a3915..22bed2f7 100644 --- a/apps/web/components/views/billing.tsx +++ b/apps/web/components/views/billing.tsx @@ -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 }} > -

Sign in to unlock premium features

+

+ Sign in to unlock premium features +

- ); + ) } if (isPro) { @@ -96,28 +98,28 @@ export function BillingView() { initial={{ opacity: 0, y: 10 }} >
- + Pro Plan - + Active -

+

You're enjoying expanded memory capacity with supermemory Pro!

{/* Current Usage */}
-

Current Usage

+

Current Usage

- Memories - + Memories + {memoriesUsed} / {memoriesLimit}
-
+
- Connections - + Connections + {connectionsUsed} / 10
- - - + - ); + ) } return ( @@ -158,17 +153,19 @@ export function BillingView() { > {/* Current Usage - Free Plan */}
- Current Plan: Free + + Current Plan: Free +
- Memories + Memories = memoriesLimit ? "text-red-400" : "text-white/90"}`} + className={`text-sm ${memoriesUsed >= memoriesLimit ? "text-red-500" : "text-foreground"}`} > {memoriesUsed} / {memoriesLimit}
-
+
= memoriesLimit ? "bg-red-500" : "bg-blue-500" @@ -183,23 +180,25 @@ export function BillingView() { {/* Comparison */}
- Upgrade to Pro + + Upgrade to Pro +
{/* Free Plan */} -
-

Free Plan

+
+

Free Plan

    -
  • - +
  • + 200 memories
  • -
  • - +
  • + No connections
  • -
  • - +
  • + Basic search
@@ -207,27 +206,27 @@ export function BillingView() { {/* Pro Plan */}
-

+

Pro Plan - + Recommended

    -
  • - +
  • + 5000 memories
  • -
  • - +
  • + 10 connections
  • -
  • - +
  • + Advanced search
  • -
  • - +
  • + Priority support
@@ -252,10 +251,10 @@ export function BillingView() { -

+

Cancel anytime. No questions asked.

- ); + ) } diff --git a/apps/web/components/views/integrations.tsx b/apps/web/components/views/integrations.tsx index 5174aeca..5f9a1eeb 100644 --- a/apps/web/components/views/integrations.tsx +++ b/apps/web/components/views/integrations.tsx @@ -278,25 +278,25 @@ export function IntegrationsView() { return (
{/* iOS Shortcuts */} -
+
- +
-

+

Apple shortcuts

-

+

Add memories directly from iPhone, iPad or Mac.

{/* Chrome Extension */} -
+
- +
-

+

Chrome Extension

-
+
Coming Soon
-

+

Save web content with one click

@@ -355,12 +355,12 @@ export function IntegrationsView() {
{/* Connections Section */} -
+
-

+

Connections

-

+

Connect your accounts to sync document.

{!isProUser && ( -

+

Connections require a Pro subscription

)} @@ -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 }} > -

+

🔌 Connections are a Pro feature

-

+

Connect Google Drive, Notion, OneDrive and more to automatically sync your documents.

@@ -441,7 +441,7 @@ export function IntegrationsView() { return (
-

+

{config.title}

{isConnected ? (
-
- +
+ Connected
) : (
-
- +
+ Disconnected
)}
-

+

{config.description}

{connection?.email && ( -

+

{connection.email}

)} @@ -494,7 +494,7 @@ export function IntegrationsView() { whileTap={{ scale: 0.95 }} >
-

+

More integrations are coming soon! Have a suggestion? Share it with us on{" "} X @@ -566,9 +566,9 @@ export function IntegrationsView() { {/* API Key Modal */}

- + - + Setup{" "} {selectedShortcutType === "add" ? "Add Memory" @@ -584,7 +584,7 @@ export function IntegrationsView() {
@@ -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" />
- ); + ) } return (
{/* Profile Section */} -
-
-
- +
+
+
+ {session?.image ? ( + {session?.name + ) : ( + + )}
-
-

{session?.email}

-

Logged in

+
+
+ {session?.name && ( +

+ {session.name} +

+ )} +

+ {session?.email} +

+
+
+ Active +
+
+
+
+ + {/* Additional Profile Details */} +
+
+
+ Organization + + {organizations?.name || "Personal"} + +
+
+ Member since + + {session?.createdAt + ? new Date(session.createdAt).toLocaleDateString("en-US", { + month: "short", + year: "numeric", + }) + : "Recent"} + +
{/* Billing Section */} -
-
-
- + {autumn.isLoading || isCheckingStatus ? ( +
+
+ +
+ + +
-
- - {isPro ? "Pro Plan" : "Free Plan"} - {isPro && ( - - Active - - )} - -

- {isPro ? "Expanded memory capacity" : "Basic plan"} -

+
+
+ + +
+ +
+
+ + +
+
+
- - {/* Usage Stats */} -
-
- Memories - = memoriesLimit ? "text-red-400" : "text-white/90"}`} - > - {memoriesUsed} / {memoriesLimit} - + ) : ( +
+
+
+ +
+
+ + {isPro ? "Pro Plan" : "Free Plan"} + {isPro && ( + + Active + + )} + +

+ {isPro ? "Expanded memory capacity" : "Basic plan"} +

+
-
-
= memoriesLimit - ? "bg-red-500" - : isPro - ? "bg-green-500" - : "bg-blue-500" - }`} - style={{ - width: `${Math.min((memoriesUsed / memoriesLimit) * 100, 100)}%`, - }} - /> -
-
- {isPro && ( -
- Connections - - {connectionsUsed} / 10 - + {/* Usage Stats */} +
+
+ Memories + = memoriesLimit ? "text-red-500" : "text-foreground"}`} + > + {memoriesUsed} / {memoriesLimit} + +
+
+
= memoriesLimit + ? "bg-red-500" + : isPro + ? "bg-green-500" + : "bg-blue-500" + }`} + style={{ + width: `${Math.min((memoriesUsed / memoriesLimit) * 100, 100)}%`, + }} + /> +
- )} - {/* Billing Actions */} -
- {isPro ? ( - + {isPro && ( +
+ Connections + + {connectionsUsed} / 10 + +
+ )} + + {/* Billing Actions */} +
+ {isPro ? ( - - ) : ( - + ) : ( - - )} -
-
- - {/* Plan Comparison - Only show for free users */} - {!isPro && ( -
- - Upgrade to Pro - - -
- {/* Free Plan */} -
-

- Free Plan -

-
    -
  • - - 200 memories -
  • -
  • - - No connections -
  • -
  • - - Basic search -
  • -
-
- - {/* Pro Plan */} -
-

- Pro Plan - - Recommended - -

-
    -
  • - - 5000 memories -
  • -
  • - - 10 connections -
  • -
  • - - Advanced search -
  • -
  • - - Priority support -
  • -
-
+ )}
-

- $15/month (only for first 100 users) • Cancel anytime. No questions - asked. -

+ {!isPro && ( +
+
+ {/* Free Plan */} +
+

+ Free Plan +

+
    +
  • + + 200 memories +
  • +
  • + + No connections +
  • +
  • + + Basic search +
  • +
+
+ + {/* Pro Plan */} +
+

+
+ Pro Plan + + Recommended + +
+

+
    +
  • + + 5000 memories +
  • +
  • + + 10 connections +
  • +
  • + + Advanced search +
  • +
  • + + Priority support +
  • +
+
+
+ +

+ $15/month (only for first 100 users) • Cancel anytime. No + questions asked. +

+
+ )}
)} - -
- ); + ) } diff --git a/packages/lib/queries.ts b/packages/lib/queries.ts index 36338445..601cdfe2 100644 --- a/packages/lib/queries.ts +++ b/packages/lib/queries.ts @@ -3,13 +3,11 @@ import type { useCustomer } from "autumn-js/react" export const fetchSubscriptionStatus = ( autumn: ReturnType, + isEnabled: boolean, ) => useQuery({ queryFn: async () => { const allPlans = [ - "pro", - "memory_starter", - "memory_growth", "consumer_pro", ] const statusMap: Record = {} @@ -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) => +export const fetchMemoriesFeature = (autumn: ReturnType, 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) => useQuery({ queryFn: async () => { - const res = await autumn.check({ productId: "pro" }) + const res = autumn.check({ productId: "pro" }) return res.data }, queryKey: ["autumn-product", "pro"], diff --git a/packages/ui/components/dropdown-menu.tsx b/packages/ui/components/dropdown-menu.tsx index 5af01088..0ca71c0f 100644 --- a/packages/ui/components/dropdown-menu.tsx +++ b/packages/ui/components/dropdown-menu.tsx @@ -39,7 +39,7 @@ function DropdownMenuContent({