From 7e72fa4f3f7180bea2ffd3d06c7a415e84d38780 Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Thu, 24 Apr 2025 05:30:08 +0100 Subject: [PATCH] config, alerts --- .../(personalAccount)/settings/layout.tsx | 4 +- .../(dashboard)/agents/[threadId]/page.tsx | 11 +- .../src/app/(dashboard)/dashboard/page.tsx | 232 ++++++++++-------- frontend/src/app/(dashboard)/layout.tsx | 18 +- .../basejump/account-billing-status.tsx | 185 +++++++++----- .../basejump/client-user-account-button.tsx | 4 +- ...PlanComparison.tsx => plan-comparison.tsx} | 30 ++- .../src/components/billing/pricing-alert.tsx | 211 ++++++++++++++++ ...ngErrorAlert.tsx => usage-limit-alert.tsx} | 8 +- frontend/src/components/maintenance-alert.tsx | 207 ++++++---------- .../sidebar/nav-user-with-teams.tsx | 4 +- frontend/src/hooks/useBillingError.ts | 7 + frontend/src/lib/config.ts | 55 +++++ 13 files changed, 653 insertions(+), 323 deletions(-) rename frontend/src/components/billing/{PlanComparison.tsx => plan-comparison.tsx} (96%) create mode 100644 frontend/src/components/billing/pricing-alert.tsx rename frontend/src/components/billing/{BillingErrorAlert.tsx => usage-limit-alert.tsx} (96%) create mode 100644 frontend/src/lib/config.ts diff --git a/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx b/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx index aa7a8aa3..719ddfac 100644 --- a/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx +++ b/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx @@ -7,8 +7,8 @@ import { usePathname } from "next/navigation"; export default function PersonalAccountSettingsPage({children}: {children: React.ReactNode}) { const pathname = usePathname(); const items = [ - { name: "Profile", href: "/settings" }, - { name: "Teams", href: "/settings/teams" }, + // { name: "Profile", href: "/settings" }, + // { name: "Teams", href: "/settings/teams" }, { name: "Billing", href: "/settings/billing" }, ] return ( diff --git a/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx b/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx index 83f7a53b..7130dfdb 100644 --- a/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx +++ b/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx @@ -19,9 +19,10 @@ import { useAgentStream } from '@/hooks/useAgentStream'; import { Markdown } from '@/components/ui/markdown'; import { cn } from "@/lib/utils"; import { useIsMobile } from "@/hooks/use-mobile"; -import { BillingErrorAlert } from '@/components/billing/BillingErrorAlert'; -import { SUBSCRIPTION_PLANS } from '@/components/billing/PlanComparison'; +import { BillingErrorAlert } from '@/components/billing/usage-limit-alert'; +import { SUBSCRIPTION_PLANS } from '@/components/billing/plan-comparison'; import { createClient } from '@/lib/supabase/client'; +import { isLocalMode } from "@/lib/config"; import { UnifiedMessage, ParsedContent, ParsedMetadata, ThreadParams } from '@/components/thread/types'; import { getToolIcon, extractPrimaryParam, safeJsonParse } from '@/components/thread/utils'; @@ -1069,6 +1070,12 @@ export default function ThreadPage({ params }: { params: Promise } // Check billing status when agent completes const checkBillingStatus = useCallback(async () => { + // Skip billing checks in local development mode + if (isLocalMode()) { + console.log("Running in local development mode - billing checks are disabled"); + return false; + } + if (!project?.account_id) return; const supabase = createClient(); diff --git a/frontend/src/app/(dashboard)/dashboard/page.tsx b/frontend/src/app/(dashboard)/dashboard/page.tsx index bbafea1c..2ac7ca1a 100644 --- a/frontend/src/app/(dashboard)/dashboard/page.tsx +++ b/frontend/src/app/(dashboard)/dashboard/page.tsx @@ -5,14 +5,16 @@ import { Skeleton } from "@/components/ui/skeleton"; import { useRouter } from 'next/navigation'; import { Menu } from "lucide-react"; import { ChatInput, ChatInputHandles } from '@/components/thread/chat-input'; -import { initiateAgent } from "@/lib/api"; +import { initiateAgent, createThread, addUserMessage, startAgent } from "@/lib/api"; import { useIsMobile } from "@/hooks/use-mobile"; import { useSidebar } from "@/components/ui/sidebar"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useBillingError } from "@/hooks/useBillingError"; -import { BillingErrorAlert } from "@/components/billing/BillingErrorAlert"; +import { BillingErrorAlert } from "@/components/billing/usage-limit-alert"; import { useAccounts } from "@/hooks/use-accounts"; +import { isLocalMode } from "@/lib/config"; +import { toast } from "sonner"; // Constant for localStorage key to ensure consistency const PENDING_PROMPT_KEY = 'pendingAgentPrompt'; @@ -35,113 +37,139 @@ function DashboardContent() { setIsSubmitting(true); try { - // Get any pending files - const pendingFiles = chatInputRef.current?.getPendingFiles() || []; + // Check if any files are attached + const files = chatInputRef.current?.getPendingFiles() || []; - // Create FormData for the request - const formData = new FormData(); - formData.append('prompt', message.trim()); - - // Add model options - formData.append('model_name', options?.model_name || 'anthropic/claude-3-7-sonnet-latest'); - formData.append('enable_thinking', options?.enable_thinking ? 'true' : 'false'); - - // Add files if any - pendingFiles.forEach(file => { - formData.append('files', file, file.name); - }); - - // Single API call to initialize everything - const response = await initiateAgent(formData); - - // Clear pendingFiles - chatInputRef.current?.clearPendingFiles(); - - // Clear pending prompt from localStorage + // Clear localStorage if this is a successful submission localStorage.removeItem(PENDING_PROMPT_KEY); - // Navigate to the new thread - router.push(`/agents/${response.thread_id}`); - - } catch (error: any) { - // Check specifically for billing errors (402 Payment Required) - if (error.message?.includes('(402)') || error?.status === 402) { - console.log("Billing error detected:", error); + if (files.length > 0) { + // Create a FormData instance + const formData = new FormData(); - // Try to extract the error details from the error object - try { - // Try to parse the error.response or the error itself - let errorDetails; - - // First attempt: check if error.data exists and has a detail property - if (error.data?.detail) { - errorDetails = error.data.detail; - console.log("Extracted billing error details from error.data.detail:", errorDetails); - } - // Second attempt: check if error.detail exists directly - else if (error.detail) { - errorDetails = error.detail; - console.log("Extracted billing error details from error.detail:", errorDetails); - } - // Third attempt: try to parse the error text if it's JSON - else if (typeof error.text === 'function') { - const text = await error.text(); - console.log("Extracted error text:", text); - try { - const parsed = JSON.parse(text); - errorDetails = parsed.detail || parsed; - console.log("Parsed error text as JSON:", errorDetails); - } catch (e) { - // Not JSON, use regex to extract info - console.log("Error text is not valid JSON"); - } - } - - // If we still don't have details, try to extract from the error message - if (!errorDetails && error.message) { - const match = error.message.match(/Monthly limit of (\d+) minutes reached/); - if (match) { - const minutes = parseInt(match[1]); - errorDetails = { - message: error.message, - subscription: { - price_id: "price_1RGJ9GG6l1KZGqIroxSqgphC", // Free tier by default - plan_name: "Free", - current_usage: minutes / 60, // Convert to hours - limit: minutes / 60 // Convert to hours - } - }; - console.log("Extracted billing error details from error message:", errorDetails); - } - } - - // Handle the billing error with the details we extracted - if (errorDetails) { - console.log("Handling billing error with extracted details:", errorDetails); - handleBillingError(errorDetails); - } else { - // Fallback with generic billing error - console.log("Using fallback generic billing error"); - handleBillingError({ - message: "You've reached your monthly usage limit. Please upgrade your plan.", - subscription: { - price_id: "price_1RGJ9GG6l1KZGqIroxSqgphC", // Free tier - plan_name: "Free" - } - }); - } - } catch (parseError) { - console.error("Error parsing billing error details:", parseError); - // Fallback with generic error - handleBillingError({ - message: "You've reached your monthly usage limit. Please upgrade your plan." - }); + // Append the message + formData.append('message', message); + + // Append all files + files.forEach(file => { + formData.append('files', file); + }); + + // Add any additional options + if (options) { + formData.append('options', JSON.stringify(options)); } - // Don't rethrow - we've handled this error with the billing alert - setIsSubmitting(false); - return; // Exit handleSubmit + // Call initiateAgent API + const result = await initiateAgent(formData); + console.log('Agent initiated:', result); + + // Navigate to the thread + if (result.thread_id) { + router.push(`/agents/${result.thread_id}`); + } + } else { + // For text-only messages, first create a thread + const thread = await createThread(""); + + // Then add the user message + await addUserMessage(thread.thread_id, message); + + // Start the agent on this thread with the options + await startAgent(thread.thread_id, options); + + // Navigate to thread + router.push(`/agents/${thread.thread_id}`); } + } catch (error: any) { + console.error('Error creating thread or initiating agent:', error); + + // Skip billing error checks in local development mode + if (isLocalMode()) { + console.log("Running in local development mode - billing checks are disabled"); + } else { + // Check specifically for billing errors (402 Payment Required) + if (error.message?.includes('(402)') || error?.status === 402) { + console.log("Billing error detected:", error); + + // Try to extract the error details from the error object + try { + // Try to parse the error.response or the error itself + let errorDetails; + + // First attempt: check if error.data exists and has a detail property + if (error.data?.detail) { + errorDetails = error.data.detail; + console.log("Extracted billing error details from error.data.detail:", errorDetails); + } + // Second attempt: check if error.detail exists directly + else if (error.detail) { + errorDetails = error.detail; + console.log("Extracted billing error details from error.detail:", errorDetails); + } + // Third attempt: try to parse the error text if it's JSON + else if (typeof error.text === 'function') { + const text = await error.text(); + console.log("Extracted error text:", text); + try { + const parsed = JSON.parse(text); + errorDetails = parsed.detail || parsed; + console.log("Parsed error text as JSON:", errorDetails); + } catch (e) { + // Not JSON, use regex to extract info + console.log("Error text is not valid JSON"); + } + } + + // If we still don't have details, try to extract from the error message + if (!errorDetails && error.message) { + const match = error.message.match(/Monthly limit of (\d+) minutes reached/); + if (match) { + const minutes = parseInt(match[1]); + errorDetails = { + message: error.message, + subscription: { + price_id: "price_1RGJ9GG6l1KZGqIroxSqgphC", // Free tier by default + plan_name: "Free", + current_usage: minutes / 60, // Convert to hours + limit: minutes / 60 // Convert to hours + } + }; + console.log("Extracted billing error details from error message:", errorDetails); + } + } + + // Handle the billing error with the details we extracted + if (errorDetails) { + console.log("Handling billing error with extracted details:", errorDetails); + handleBillingError(errorDetails); + } else { + // Fallback with generic billing error + console.log("Using fallback generic billing error"); + handleBillingError({ + message: "You've reached your monthly usage limit. Please upgrade your plan.", + subscription: { + price_id: "price_1RGJ9GG6l1KZGqIroxSqgphC", // Free tier + plan_name: "Free" + } + }); + } + } catch (parseError) { + console.error("Error parsing billing error details:", parseError); + // Fallback with generic error + handleBillingError({ + message: "You've reached your monthly usage limit. Please upgrade your plan." + }); + } + + // Don't rethrow - we've handled this error with the billing alert + setIsSubmitting(false); + return; // Exit handleSubmit + } + } + + // Handle other errors or rethrow + toast.error(error.message || "An error occurred"); console.error("Error creating agent:", error); setIsSubmitting(false); diff --git a/frontend/src/app/(dashboard)/layout.tsx b/frontend/src/app/(dashboard)/layout.tsx index ee485db4..bcb712ad 100644 --- a/frontend/src/app/(dashboard)/layout.tsx +++ b/frontend/src/app/(dashboard)/layout.tsx @@ -6,6 +6,7 @@ import { SidebarInset, SidebarProvider, } from "@/components/ui/sidebar" +import { PricingAlert } from "@/components/billing/pricing-alert" import { MaintenanceAlert } from "@/components/maintenance-alert" import { useAccounts } from "@/hooks/use-accounts" @@ -16,13 +17,14 @@ interface DashboardLayoutProps { export default function DashboardLayout({ children, }: DashboardLayoutProps) { + const [showPricingAlert, setShowPricingAlert] = useState(false) const [showMaintenanceAlert, setShowMaintenanceAlert] = useState(false) const { data: accounts } = useAccounts() const personalAccount = accounts?.find(account => account.personal_account) useEffect(() => { - // Show the maintenance alert when component mounts - setShowMaintenanceAlert(true) + setShowPricingAlert(true) + setShowMaintenanceAlert(false) }, []) return ( @@ -34,12 +36,18 @@ export default function DashboardLayout({ - + + ) } \ No newline at end of file diff --git a/frontend/src/components/basejump/account-billing-status.tsx b/frontend/src/components/basejump/account-billing-status.tsx index 19227a74..91d308eb 100644 --- a/frontend/src/components/basejump/account-billing-status.tsx +++ b/frontend/src/components/basejump/account-billing-status.tsx @@ -1,7 +1,8 @@ import { createClient } from "@/lib/supabase/server"; import { SubmitButton } from "../ui/submit-button"; import { manageSubscription } from "@/lib/actions/billing"; -import { PlanComparison, SUBSCRIPTION_PLANS } from "../billing/PlanComparison"; +import { PlanComparison, SUBSCRIPTION_PLANS } from "../billing/plan-comparison"; +import { isLocalMode } from "@/lib/config"; type Props = { accountId: string; @@ -9,87 +10,141 @@ type Props = { } export default async function AccountBillingStatus({ accountId, returnUrl }: Props) { + // In local development mode, show a simplified component + if (isLocalMode()) { + return ( +
+

Billing Status

+
+

+ Running in local development mode - billing features are disabled +

+

+ Agent usage limits are not enforced in this environment +

+
+
+ ); + } + const supabaseClient = await createClient(); - - const { data: billingData, error: billingError } = await supabaseClient.functions.invoke('billing-functions', { - body: { - action: "get_billing_status", - args: { - account_id: accountId - } - } - }); - - // Get current subscription details + + // Get account subscription and usage data const { data: subscriptionData } = await supabaseClient .schema('basejump') .from('billing_subscriptions') - .select('price_id') + .select('*') .eq('account_id', accountId) .eq('status', 'active') + .limit(1) + .order('created_at', { ascending: false }) .single(); - - const currentPlanId = subscriptionData?.price_id; - - // Get agent run hours for current month - const startOfMonth = new Date(); - startOfMonth.setDate(1); - startOfMonth.setHours(0, 0, 0, 0); - // First get threads for this account - const { data: threadsData } = await supabaseClient + + // Get agent runs for this account + // Get the account's threads + const { data: threads } = await supabaseClient .from('threads') .select('thread_id') .eq('account_id', accountId); - - const threadIds = threadsData?.map(t => t.thread_id) || []; - - // Then get agent runs for those threads - const { data: agentRunData, error: agentRunError } = await supabaseClient - .from('agent_runs') - .select('started_at, completed_at') - .in('thread_id', threadIds) - .gte('started_at', startOfMonth.toISOString()); - - let totalSeconds = 0; - if (agentRunData) { - totalSeconds = agentRunData.reduce((acc, run) => { - const start = new Date(run.started_at); - const end = run.completed_at ? new Date(run.completed_at) : new Date(); - const seconds = (end.getTime() - start.getTime()) / 1000; - return acc + seconds; - }, 0); + + const threadIds = threads?.map(t => t.thread_id) || []; + + // Get current month usage + const now = new Date(); + const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + const isoStartOfMonth = startOfMonth.toISOString(); + + let totalAgentTime = 0; + let usageDisplay = "No usage this month"; + + if (threadIds.length > 0) { + const { data: agentRuns } = await supabaseClient + .from('agent_runs') + .select('started_at, completed_at') + .in('thread_id', threadIds) + .gte('started_at', isoStartOfMonth); + + if (agentRuns && agentRuns.length > 0) { + const nowTimestamp = now.getTime(); + + totalAgentTime = agentRuns.reduce((total, run) => { + const startTime = new Date(run.started_at).getTime(); + const endTime = run.completed_at + ? new Date(run.completed_at).getTime() + : nowTimestamp; + + return total + (endTime - startTime) / 1000; // In seconds + }, 0); + + // Convert to minutes + const totalMinutes = Math.round(totalAgentTime / 60); + usageDisplay = `${totalMinutes} minutes`; + } } - - const hours = Math.floor(totalSeconds / 3600); - const minutes = Math.floor((totalSeconds % 3600) / 60); - const seconds = Math.floor(totalSeconds % 60); - const usageDisplay = `${hours}h ${minutes}m ${seconds}s`; + + const isPlan = (planId?: string) => { + return subscriptionData?.price_id === planId; + }; + + const planName = isPlan(SUBSCRIPTION_PLANS.FREE) + ? "Free" + : isPlan(SUBSCRIPTION_PLANS.PRO) + ? "Pro" + : isPlan(SUBSCRIPTION_PLANS.ENTERPRISE) + ? "Enterprise" + : "Unknown"; return ( -
- {!Boolean(billingData?.billing_enabled) ? ( -
-

Billing Not Enabled

-

- Billing is not enabled for this account. Check out usebasejump.com for more info or remove this component if you don't plan on enabling billing. -

-
+
+

Billing Status

+ + {subscriptionData ? ( + <> +
+
+
+
+ Current Plan + {planName} +
+
+ +
+ Agent Usage This Month + {usageDisplay} +
+
+
+ + {/* Plans Comparison */} + + + {/* Manage Subscription Button */} +
+ + + + Manage Subscription + +
+ ) : ( <> -
-
+
+
- Status - - {(!currentPlanId || currentPlanId === SUBSCRIPTION_PLANS.FREE) ? 'Active (Free)' : billingData.status === 'active' ? 'Active' : 'Inactive'} - + Current Plan + Free
- {billingData.plan_name && ( -
- Plan - {billingData.plan_name} -
- )} +
Agent Usage This Month {usageDisplay} diff --git a/frontend/src/components/basejump/client-user-account-button.tsx b/frontend/src/components/basejump/client-user-account-button.tsx index cfe28251..048e01fc 100644 --- a/frontend/src/components/basejump/client-user-account-button.tsx +++ b/frontend/src/components/basejump/client-user-account-button.tsx @@ -57,9 +57,9 @@ export default function ClientUserAccountButton({ My Account - + {/* Settings - + */} Teams diff --git a/frontend/src/components/billing/PlanComparison.tsx b/frontend/src/components/billing/plan-comparison.tsx similarity index 96% rename from frontend/src/components/billing/PlanComparison.tsx rename to frontend/src/components/billing/plan-comparison.tsx index b52b98fc..c8e0c5cf 100644 --- a/frontend/src/components/billing/PlanComparison.tsx +++ b/frontend/src/components/billing/plan-comparison.tsx @@ -8,6 +8,7 @@ import { setupNewSubscription } from "@/lib/actions/billing"; import { SubmitButton } from "@/components/ui/submit-button"; import { Button } from "@/components/ui/button"; import { siteConfig } from "@/lib/home"; +import { isLocalMode } from "@/lib/config"; // Create SUBSCRIPTION_PLANS using stripePriceId from siteConfig export const SUBSCRIPTION_PLANS = { @@ -16,15 +17,6 @@ export const SUBSCRIPTION_PLANS = { ENTERPRISE: siteConfig.cloudPricingItems.find(item => item.name === 'Enterprise')?.stripePriceId || '', }; -interface PlanComparisonProps { - accountId?: string | null; - returnUrl?: string; - isManaged?: boolean; - onPlanSelect?: (planId: string) => void; - className?: string; - isCompact?: boolean; // When true, uses vertical stacked layout for modals -} - // Price display animation component const PriceDisplay = ({ tier, isCompact }: { tier: typeof siteConfig.cloudPricingItems[number]; isCompact?: boolean }) => { return ( @@ -44,6 +36,15 @@ const PriceDisplay = ({ tier, isCompact }: { tier: typeof siteConfig.cloudPricin ); }; +interface PlanComparisonProps { + accountId?: string | null; + returnUrl?: string; + isManaged?: boolean; + onPlanSelect?: (planId: string) => void; + className?: string; + isCompact?: boolean; // When true, uses vertical stacked layout for modals +} + export function PlanComparison({ accountId, returnUrl = typeof window !== 'undefined' ? window.location.href : '', @@ -75,6 +76,17 @@ export function PlanComparison({ fetchCurrentPlan(); }, [accountId]); + // For local development mode, show a message instead + if (isLocalMode()) { + return ( +
+

+ Running in local development mode - billing features are disabled +

+
+ ); + } + return (
void + closeable?: boolean + accountId?: string | null | undefined +} + +export function PricingAlert({ open, onOpenChange, closeable = true, accountId }: PricingAlertProps) { + const returnUrl = typeof window !== 'undefined' ? window.location.href : ''; + + // Skip rendering in local development mode + if (isLocalMode() || !open) return null; + + // Filter plans to show only Pro and Enterprise + const premiumPlans = siteConfig.cloudPricingItems.filter(plan => + plan.name === 'Pro' || plan.name === 'Enterprise' + ); + + return ( + + + {open && ( + <> + + {/* Backdrop */} + onOpenChange(false) : undefined} + aria-hidden="true" + /> + + {/* Modal */} + +
+ {/* Close button */} + {closeable && ( + + )} + + {/* Header */} +
+
+ +
+

+ Choose Your Suna Experience +

+

+ Due to overwhelming demand and AI costs, we're currently focusing on delivering + our best experience to dedicated users. Select your preferred option below. +

+
+ + {/* Plan comparison - 3 column layout */} +
+ {/* Self-Host Option */} +
+
+

Open Source

+
+ Self-host +
+

Full control with your own infrastructure

+
+ ∞ hours / month +
+
+ +
+
+ + No usage limitations +
+ + + View on GitHub + +
+
+ + {/* Pro Plan */} +
+
+ + Most Popular + +
+
+

Pro

+
+ {premiumPlans[0]?.price || "$19"} + /month +
+

Supercharge your productivity with {premiumPlans[0]?.hours || "500 hours"} of Suna

+
+ {premiumPlans[0]?.hours || "500 hours"}/month +
+
+ +
+
+ + Perfect for individuals and small teams +
+
+ + + + + Get Started Now + +
+
+
+ + {/* Enterprise Plan */} +
+
+

Enterprise

+
+ {premiumPlans[1]?.price || "$99"} + /month +
+

Unlock boundless potential with {premiumPlans[1]?.hours || "2000 hours"} of Suna

+
+ {premiumPlans[1]?.hours || "2000 hours"}/month +
+
+ +
+
+ + Ideal for larger organizations and power users +
+
+ + + + + Upgrade to Enterprise + +
+
+
+
+
+
+
+ + )} +
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/billing/BillingErrorAlert.tsx b/frontend/src/components/billing/usage-limit-alert.tsx similarity index 96% rename from frontend/src/components/billing/BillingErrorAlert.tsx rename to frontend/src/components/billing/usage-limit-alert.tsx index c2eba635..c17e6f7f 100644 --- a/frontend/src/components/billing/BillingErrorAlert.tsx +++ b/frontend/src/components/billing/usage-limit-alert.tsx @@ -1,9 +1,10 @@ import { AlertCircle, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Portal } from "@/components/ui/portal"; -import { PlanComparison } from "./PlanComparison"; +import { PlanComparison } from "./plan-comparison"; import { cn } from "@/lib/utils"; import { motion, AnimatePresence } from "motion/react"; +import { isLocalMode } from "@/lib/config"; interface BillingErrorAlertProps { message?: string; @@ -25,8 +26,9 @@ export function BillingErrorAlert({ isOpen }: BillingErrorAlertProps) { const returnUrl = typeof window !== 'undefined' ? window.location.href : ''; - - if (!isOpen) return null; + + // Skip rendering in local development mode + if (isLocalMode() || !isOpen) return null; return ( diff --git a/frontend/src/components/maintenance-alert.tsx b/frontend/src/components/maintenance-alert.tsx index 33be48f3..cf58fe73 100644 --- a/frontend/src/components/maintenance-alert.tsx +++ b/frontend/src/components/maintenance-alert.tsx @@ -1,149 +1,94 @@ "use client" -import { AlertDialog, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog" -import { AlertCircle, X, Zap, Github } from "lucide-react" +import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog" +import { Clock, Github, X } from "lucide-react" import Link from "next/link" import { AnimatePresence, motion } from "motion/react" import { Button } from "@/components/ui/button" -import { Portal } from "@/components/ui/portal" -import { cn } from "@/lib/utils" -import { setupNewSubscription } from "@/lib/actions/billing" -import { SubmitButton } from "@/components/ui/submit-button" -import { siteConfig } from "@/lib/home" interface MaintenanceAlertProps { open: boolean onOpenChange: (open: boolean) => void closeable?: boolean - accountId?: string | null | undefined } -export function MaintenanceAlert({ open, onOpenChange, closeable = true, accountId }: MaintenanceAlertProps) { - const returnUrl = typeof window !== 'undefined' ? window.location.href : ''; - - if (!open) return null; - - // Filter plans to show only Pro and Enterprise - const premiumPlans = siteConfig.cloudPricingItems.filter(plan => - plan.name === 'Pro' || plan.name === 'Enterprise' - ); - +export function MaintenanceAlert({ open, onOpenChange, closeable = true }: MaintenanceAlertProps) { return ( - - - {open && ( - <> - + + + {/* Background pattern */} +
+
+
+ + {closeable && ( + - )} - - {/* Header */} -
-
- -
-

- Free Tier Unavailable At This Time -

-

- Due to extremely high demand, we cannot offer a free tier at the moment. Upgrade to Pro to continue using our service. -

-
- - {/* Custom plan comparison wrapper to show Pro, Enterprise and Self-Host side by side */} -
- {premiumPlans.map((tier) => ( -
-
-

{tier.name}

-

{tier.price}/mo

-

{tier.hours}/month

-
-
- - - item.name === 'Pro')?.stripePriceId || '' - : siteConfig.cloudPricingItems.find(item => item.name === 'Enterprise')?.stripePriceId || '' - } /> - - Upgrade - -
-
- ))} - - {/* Self-host Option as the third card */} -
-
-

Self-Host

-

Free

-

Open Source

-
- - - Self-Host - -
-
+ + Close + + )} + + + +
+
+
- +
- - )} - - + + + + High Demand Notice + + + + + + Due to exceptionally high demand, our service is currently experiencing slower response times. + We recommend returning tomorrow when our systems will be operating at normal capacity. + Thank you for your understanding. We will notify you via email once the service is fully operational again. + + +
+ + + + + Explore Self-Hosted Version + + + + + ) } \ No newline at end of file diff --git a/frontend/src/components/sidebar/nav-user-with-teams.tsx b/frontend/src/components/sidebar/nav-user-with-teams.tsx index 06388e26..b32ab25e 100644 --- a/frontend/src/components/sidebar/nav-user-with-teams.tsx +++ b/frontend/src/components/sidebar/nav-user-with-teams.tsx @@ -273,12 +273,12 @@ export function NavUserWithTeams({ Billing - + {/* Settings - + */} setTheme(theme === "light" ? "dark" : "light")}>
diff --git a/frontend/src/hooks/useBillingError.ts b/frontend/src/hooks/useBillingError.ts index 58b125ee..67f6e981 100644 --- a/frontend/src/hooks/useBillingError.ts +++ b/frontend/src/hooks/useBillingError.ts @@ -1,4 +1,5 @@ import { useState, useCallback } from 'react'; +import { isLocalMode } from '@/lib/config'; interface BillingErrorState { message: string; @@ -16,6 +17,12 @@ export function useBillingError() { const [billingError, setBillingError] = useState(null); const handleBillingError = useCallback((error: any) => { + // In local mode, don't process billing errors + if (isLocalMode()) { + console.log('Running in local development mode - billing checks are disabled'); + return false; + } + // Case 1: Error is already a formatted billing error detail object if (error && (error.message || error.subscription)) { setBillingError({ diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts new file mode 100644 index 00000000..8ed25881 --- /dev/null +++ b/frontend/src/lib/config.ts @@ -0,0 +1,55 @@ +// Environment mode types +export enum EnvMode { + LOCAL = 'local', + STAGING = 'staging', + PRODUCTION = 'production', +} + +// Configuration object +interface Config { + ENV_MODE: EnvMode; + IS_LOCAL: boolean; +} + +// Determine the environment mode from environment variables +const getEnvironmentMode = (): EnvMode => { + // Get the environment mode from the environment variable, if set + const envMode = process.env.NEXT_PUBLIC_ENV_MODE?.toLowerCase(); + + // First check if the environment variable is explicitly set + if (envMode) { + if (envMode === EnvMode.LOCAL) { + console.log('Using explicitly set LOCAL environment mode'); + return EnvMode.LOCAL; + } else if (envMode === EnvMode.STAGING) { + console.log('Using explicitly set STAGING environment mode'); + return EnvMode.STAGING; + } else if (envMode === EnvMode.PRODUCTION) { + console.log('Using explicitly set PRODUCTION environment mode'); + return EnvMode.PRODUCTION; + } + } + + // If no valid environment mode is set, fall back to defaults based on NODE_ENV + if (process.env.NODE_ENV === 'development') { + console.log('Defaulting to LOCAL environment mode in development'); + return EnvMode.LOCAL; + } else { + console.log('Defaulting to PRODUCTION environment mode'); + return EnvMode.PRODUCTION; + } +}; + +// Get the environment mode once to ensure consistency +const currentEnvMode = getEnvironmentMode(); + +// Create the config object +export const config: Config = { + ENV_MODE: currentEnvMode, + IS_LOCAL: currentEnvMode === EnvMode.LOCAL, +}; + +// Helper function to check if we're in local mode (for component conditionals) +export const isLocalMode = (): boolean => { + return config.IS_LOCAL; +}; \ No newline at end of file