From 1f8ae3f19249b03d4bf5d719bd809a0b8da20f33 Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommmu Date: Thu, 4 Sep 2025 21:05:13 -0700 Subject: [PATCH] updated integration page --- apps/web/components/views/billing.tsx | 55 ++--- apps/web/components/views/integrations.tsx | 271 ++++++++++----------- apps/web/components/views/profile.tsx | 4 +- 3 files changed, 161 insertions(+), 169 deletions(-) diff --git a/apps/web/components/views/billing.tsx b/apps/web/components/views/billing.tsx index 22bed2f7..4745ff8c 100644 --- a/apps/web/components/views/billing.tsx +++ b/apps/web/components/views/billing.tsx @@ -22,22 +22,24 @@ export function BillingView() { analytics.billingViewed() }, []) - 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 { data: memoriesCheck } = fetchMemoriesFeature( + autumn, + !autumn.isLoading && !isCheckingStatus, + ) + + const memoriesUsed = memoriesCheck?.usage ?? 0 + const memoriesLimit = memoriesCheck?.included_usage ?? 0 + + const { data: connectionsCheck } = fetchConnectionsFeature(autumn) + + const connectionsUsed = connectionsCheck?.usage ?? 0 // Handle upgrade const handleUpgrade = async () => { @@ -233,23 +235,20 @@ 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 5f9a1eeb..ef16450f 100644 --- a/apps/web/components/views/integrations.tsx +++ b/apps/web/components/views/integrations.tsx @@ -1,38 +1,39 @@ -import { $fetch } from "@lib/api"; -import { authClient } from "@lib/auth"; -import { useAuth } from "@lib/auth-context"; -import { generateId } from "@lib/generate-id"; +import { $fetch } from "@lib/api" +import { authClient } from "@lib/auth" +import { useAuth } from "@lib/auth-context" +import { generateId } from "@lib/generate-id" import { ADD_MEMORY_SHORTCUT_URL, SEARCH_MEMORY_SHORTCUT_URL, -} from "@repo/lib/constants"; +} from "@repo/lib/constants" import { fetchConnectionsFeature, fetchConsumerProProduct, -} from "@repo/lib/queries"; -import { Button } from "@repo/ui/components/button"; +} from "@repo/lib/queries" +import { Button } from "@repo/ui/components/button" import { Dialog, DialogContent, DialogHeader, DialogPortal, DialogTitle, -} from "@repo/ui/components/dialog"; -import { Skeleton } from "@repo/ui/components/skeleton"; -import type { ConnectionResponseSchema } from "@repo/validation/api"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons"; -import { useCustomer } from "autumn-js/react"; -import { Check, Copy, Smartphone, Trash2 } from "lucide-react"; -import { motion } from "motion/react"; -import Image from "next/image"; -import { useEffect, useId, useState } from "react"; -import { toast } from "sonner"; -import type { z } from "zod"; -import { analytics } from "@/lib/analytics"; -import { useProject } from "@/stores"; +} from "@repo/ui/components/dialog" +import { Skeleton } from "@repo/ui/components/skeleton" +import type { ConnectionResponseSchema } from "@repo/validation/api" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons" +import { useCustomer } from "autumn-js/react" +import { Check, Copy, Smartphone, Trash2 } from "lucide-react" +import { motion } from "motion/react" +import Image from "next/image" +import { useEffect, useId, useState } from "react" +import { toast } from "sonner" +import type { z } from "zod" +import { analytics } from "@/lib/analytics" +import { useProject } from "@/stores" +import { cn } from "@lib/utils" -type Connection = z.infer; +type Connection = z.infer const CONNECTORS = { "google-drive": { @@ -50,9 +51,9 @@ const CONNECTORS = { description: "Access your Microsoft Office documents", icon: OneDrive, }, -} as const; +} as const -type ConnectorProvider = keyof typeof CONNECTORS; +type ConnectorProvider = keyof typeof CONNECTORS const ChromeIcon = ({ className }: { className?: string }) => ( ( d="M95.252 47.628h82.479A95.237 95.237 0 0 0 142.87 12.76 95.23 95.23 0 0 0 95.245 0a95.222 95.222 0 0 0-47.623 12.767 95.23 95.23 0 0 0-34.856 34.872l41.24 71.43.011.006a47.62 47.62 0 0 1-.015-47.633 47.61 47.61 0 0 1 41.252-23.815z" /> -); +) export function IntegrationsView() { - const { org } = useAuth(); - const queryClient = useQueryClient(); - const { selectedProject } = useProject(); - const autumn = useCustomer(); - const [showApiKeyModal, setShowApiKeyModal] = useState(false); - const [apiKey, setApiKey] = useState(""); - const [copied, setCopied] = useState(false); + const { org } = useAuth() + const queryClient = useQueryClient() + const { selectedProject } = useProject() + const autumn = useCustomer() + const [showApiKeyModal, setShowApiKeyModal] = useState(false) + const [apiKey, setApiKey] = useState("") + const [copied, setCopied] = useState(false) const [selectedShortcutType, setSelectedShortcutType] = useState< "add" | "search" | null - >(null); - const apiKeyId = useId(); + >(null) + const apiKeyId = useId() const handleUpgrade = async () => { try { await autumn.attach({ productId: "consumer_pro", successUrl: "https://app.supermemory.ai/", - }); - window.location.reload(); + }) + window.location.reload() } catch (error) { - console.error(error); + console.error(error) } - }; + } - const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any); - const connectionsUsed = connectionsCheck?.balance ?? 0; - const connectionsLimit = connectionsCheck?.included_usage ?? 0; + const { data: connectionsCheck } = fetchConnectionsFeature(autumn) + const connectionsUsed = connectionsCheck?.balance ?? 0 + const connectionsLimit = connectionsCheck?.included_usage ?? 0 - const { data: proCheck } = fetchConsumerProProduct(autumn as any); - const isProUser = proCheck?.allowed ?? false; + const { data: proCheck } = fetchConsumerProProduct(autumn) + const isProUser = proCheck?.allowed ?? false - const canAddConnection = connectionsUsed < connectionsLimit; + const canAddConnection = connectionsUsed < connectionsLimit const { data: connections = [], @@ -130,19 +131,17 @@ export function IntegrationsView() { body: { containerTags: [], }, - }); + }) if (response.error) { - throw new Error( - response.error?.message || "Failed to load connections", - ); + throw new Error(response.error?.message || "Failed to load connections") } - return response.data as Connection[]; + return response.data as Connection[] }, staleTime: 30 * 1000, refetchInterval: 60 * 1000, - }); + }) useEffect(() => { if (connectionsError) { @@ -151,16 +150,16 @@ export function IntegrationsView() { connectionsError instanceof Error ? connectionsError.message : "Unknown error", - }); + }) } - }, [connectionsError]); + }, [connectionsError]) const addConnectionMutation = useMutation({ mutationFn: async (provider: ConnectorProvider) => { if (!canAddConnection && !isProUser) { throw new Error( "Free plan doesn't include connections. Upgrade to Pro for unlimited connections.", - ); + ) } const response = await $fetch("@post/connections/:provider", { @@ -169,47 +168,47 @@ export function IntegrationsView() { redirectUrl: window.location.href, containerTags: [selectedProject], }, - }); + }) // biome-ignore lint/style/noNonNullAssertion: its fine if ("data" in response && !("error" in response.data!)) { - return response.data; + return response.data } - throw new Error(response.error?.message || "Failed to connect"); + throw new Error(response.error?.message || "Failed to connect") }, onSuccess: (data, provider) => { - analytics.connectionAdded(provider); - analytics.connectionAuthStarted(); + analytics.connectionAdded(provider) + analytics.connectionAuthStarted() if (data?.authLink) { - window.location.href = data.authLink; + window.location.href = data.authLink } }, onError: (error, provider) => { - analytics.connectionAuthFailed(); + analytics.connectionAuthFailed() toast.error(`Failed to connect ${provider}`, { description: error instanceof Error ? error.message : "Unknown error", - }); + }) }, - }); + }) const deleteConnectionMutation = useMutation({ mutationFn: async (connectionId: string) => { - await $fetch(`@delete/connections/${connectionId}`); + await $fetch(`@delete/connections/${connectionId}`) }, onSuccess: () => { - analytics.connectionDeleted(); + analytics.connectionDeleted() toast.success( "Connection removal has started. supermemory will permanently delete all documents related to the connection in the next few minutes.", - ); - queryClient.invalidateQueries({ queryKey: ["connections"] }); + ) + queryClient.invalidateQueries({ queryKey: ["connections"] }) }, onError: (error) => { toast.error("Failed to remove connection", { description: error instanceof Error ? error.message : "Unknown error", - }); + }) }, - }); + }) const createApiKeyMutation = useMutation({ mutationFn: async () => { @@ -220,72 +219,72 @@ export function IntegrationsView() { }, name: `ios-${generateId().slice(0, 8)}`, prefix: `sm_${org?.id}_`, - }); - return res.key; + }) + return res.key }, onSuccess: (apiKey) => { - setApiKey(apiKey); - setShowApiKeyModal(true); - setCopied(false); - handleCopyApiKey(); + setApiKey(apiKey) + setShowApiKeyModal(true) + setCopied(false) + handleCopyApiKey() }, onError: (error) => { toast.error("Failed to create API key", { description: error instanceof Error ? error.message : "Unknown error", - }); + }) }, - }); + }) const handleShortcutClick = (shortcutType: "add" | "search") => { - setSelectedShortcutType(shortcutType); - createApiKeyMutation.mutate(); - }; + setSelectedShortcutType(shortcutType) + createApiKeyMutation.mutate() + } const handleCopyApiKey = async () => { try { - await navigator.clipboard.writeText(apiKey); - setCopied(true); - toast.success("API key copied to clipboard!"); - setTimeout(() => setCopied(false), 2000); + await navigator.clipboard.writeText(apiKey) + setCopied(true) + toast.success("API key copied to clipboard!") + setTimeout(() => setCopied(false), 2000) } catch { - toast.error("Failed to copy API key"); + toast.error("Failed to copy API key") } - }; + } const handleOpenShortcut = () => { if (!selectedShortcutType) { - toast.error("No shortcut type selected"); - return; + toast.error("No shortcut type selected") + return } if (selectedShortcutType === "add") { - window.open(ADD_MEMORY_SHORTCUT_URL, "_blank"); + window.open(ADD_MEMORY_SHORTCUT_URL, "_blank") } else if (selectedShortcutType === "search") { - window.open(SEARCH_MEMORY_SHORTCUT_URL, "_blank"); + window.open(SEARCH_MEMORY_SHORTCUT_URL, "_blank") } - }; + } const handleDialogClose = (open: boolean) => { - setShowApiKeyModal(open); + setShowApiKeyModal(open) if (!open) { // Reset state when dialog closes - setSelectedShortcutType(null); - setApiKey(""); - setCopied(false); + setSelectedShortcutType(null) + setApiKey("") + setCopied(false) } - }; + } return ( -

+
{/* iOS Shortcuts */}
-
- +
+
-

+

Apple shortcuts

@@ -296,9 +295,10 @@ export function IntegrationsView() {

- +
)} {/* All Connections with Status */} @@ -430,13 +423,13 @@ export function IntegrationsView() { ))}
) : ( -
+
{Object.entries(CONNECTORS).map(([provider, config], index) => { - const Icon = config.icon; + const Icon = config.icon const connection = connections.find( (conn) => conn.provider === provider, - ); - const isConnected = !!connection; + ) + const isConnected = !!connection return ( {isConnected ? (
-
+
Connected
) : (
-
+
Disconnected @@ -509,7 +502,7 @@ export function IntegrationsView() { ) : (
-
+
Disconnected @@ -520,12 +513,12 @@ export function IntegrationsView() { className="flex-shrink-0" >
- ); + ) })}
)} @@ -555,7 +548,7 @@ export function IntegrationsView() { href="https://x.com/supermemoryai" target="_blank" rel="noopener noreferrer" - className="text-orange-600 dark:text-orange-400 hover:text-orange-500 underline" + className="text-[#267ffa] hover:text-[#267ffa]/90 underline" > X @@ -665,5 +658,5 @@ export function IntegrationsView() {
- ); + ) } diff --git a/apps/web/components/views/profile.tsx b/apps/web/components/views/profile.tsx index 7a6699c4..550f1b97 100644 --- a/apps/web/components/views/profile.tsx +++ b/apps/web/components/views/profile.tsx @@ -238,10 +238,10 @@ export function ProfileView() { ) : (