diff --git a/frontend/src/core/services/supabaseClient.ts b/frontend/src/core/services/supabaseClient.ts index e55948af4..c0ace3002 100644 --- a/frontend/src/core/services/supabaseClient.ts +++ b/frontend/src/core/services/supabaseClient.ts @@ -1,10 +1,20 @@ -import { createClient } from '@supabase/supabase-js'; +import { createClient, SupabaseClient } from '@supabase/supabase-js'; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY; -if (!supabaseUrl || !supabaseAnonKey) { - throw new Error('Missing Supabase environment variables'); -} +// Check if Supabase is configured +export const isSupabaseConfigured = !!(supabaseUrl && supabaseAnonKey); -export const supabase = createClient(supabaseUrl, supabaseAnonKey); +// Create client only if configured, otherwise export null +export const supabase: SupabaseClient | null = isSupabaseConfigured + ? createClient(supabaseUrl, supabaseAnonKey) + : null; + +// Log warning if not configured (for self-hosted installations) +if (!isSupabaseConfigured) { + console.warn( + 'Supabase is not configured. Checkout and billing features will be disabled. ' + + 'Static plan information will be displayed instead.' + ); +} diff --git a/frontend/src/proprietary/components/shared/UpgradeBanner.tsx b/frontend/src/proprietary/components/shared/UpgradeBanner.tsx index b1067bd12..93a5eb827 100644 --- a/frontend/src/proprietary/components/shared/UpgradeBanner.tsx +++ b/frontend/src/proprietary/components/shared/UpgradeBanner.tsx @@ -6,6 +6,7 @@ import { useCheckout } from '@app/contexts/CheckoutContext'; import { useLicense } from '@app/contexts/LicenseContext'; import { mapLicenseToTier } from '@app/services/licenseService'; import LocalIcon from '@app/components/shared/LocalIcon'; +import { isSupabaseConfigured } from '@app/services/supabaseClient'; /** * UpgradeBanner - Dismissable top banner encouraging users to upgrade @@ -35,6 +36,12 @@ const UpgradeBanner: React.FC = () => { return; } + // Don't show if Supabase is not configured (no checkout available) + if (!isSupabaseConfigured) { + setIsVisible(false); + return; + } + // Don't show while license is loading if (licenseLoading) { return; diff --git a/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx b/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx index f5efa82e1..522658004 100644 --- a/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx +++ b/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx @@ -11,6 +11,7 @@ import { alert } from '@app/components/toast'; import LocalIcon from '@app/components/shared/LocalIcon'; import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex'; import { ManageBillingButton } from '@app/components/shared/ManageBillingButton'; +import { isSupabaseConfigured } from '@app/services/supabaseClient'; const AdminPlanSection: React.FC = () => { const { t } = useTranslation(); @@ -25,9 +26,9 @@ const AdminPlanSection: React.FC = () => { // Check if we should use static version useEffect(() => { - // Check if Stripe is configured + // Check if Stripe and Supabase are configured const stripeKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY; - if (!stripeKey || error) { + if (!stripeKey || !isSupabaseConfigured || error) { setUseStaticVersion(true); } }, [error]); @@ -146,8 +147,8 @@ const AdminPlanSection: React.FC = () => { /> - {/* Manage Subscription Button - Only show if user has active license */} - {licenseInfo?.licenseKey && ( + {/* Manage Subscription Button - Only show if user has active license and Supabase is configured */} + {licenseInfo?.licenseKey && isSupabaseConfigured && ( {t('plan.manageSubscription.description', 'Manage your subscription, billing, and payment methods')} diff --git a/frontend/src/proprietary/contexts/CheckoutContext.tsx b/frontend/src/proprietary/contexts/CheckoutContext.tsx index 2ec1f35d7..f8558a650 100644 --- a/frontend/src/proprietary/contexts/CheckoutContext.tsx +++ b/frontend/src/proprietary/contexts/CheckoutContext.tsx @@ -7,6 +7,7 @@ import { userManagementService } from '@app/services/userManagementService'; import { alert } from '@app/components/toast'; import { pollLicenseKeyWithBackoff, activateLicenseKey, resyncExistingLicense } from '@app/utils/licenseCheckoutUtils'; import { useLicense } from '@app/contexts/LicenseContext'; +import { isSupabaseConfigured } from '@app/services/supabaseClient'; export interface CheckoutOptions { minimumSeats?: number; // Override calculated seats for enterprise @@ -202,6 +203,11 @@ export const CheckoutProvider: React.FC = ({ try { setIsLoading(true); + // Check if Supabase is configured + if (!isSupabaseConfigured) { + throw new Error('Checkout is not available. Supabase is not configured.'); + } + // Update currency if provided const currency = options.currency || currentCurrency; if (currency !== currentCurrency) { diff --git a/frontend/src/proprietary/services/licenseService.ts b/frontend/src/proprietary/services/licenseService.ts index 8724e74e9..2b5c6c25f 100644 --- a/frontend/src/proprietary/services/licenseService.ts +++ b/frontend/src/proprietary/services/licenseService.ts @@ -1,5 +1,5 @@ import apiClient from '@app/services/apiClient'; -import { supabase } from '@app/services/supabaseClient'; +import { supabase, isSupabaseConfigured } from '@app/services/supabaseClient'; import { getCheckoutMode } from '@app/utils/protocolDetection'; export interface PlanFeature { @@ -110,6 +110,11 @@ const licenseService = { */ async getPlans(currency: string = 'gbp'): Promise { try { + // Check if Supabase is configured + if (!isSupabaseConfigured || !supabase) { + throw new Error('Supabase is not configured. Please use static plans instead.'); + } + // Fetch all self-hosted prices from Stripe const { data, error } = await supabase.functions.invoke<{ prices: Record { + // Check if Supabase is configured + if (!isSupabaseConfigured || !supabase) { + throw new Error('Supabase is not configured. Checkout is not available.'); + } + // Detect if HTTPS is available to determine checkout mode const checkoutMode = getCheckoutMode(); const baseUrl = window.location.origin; @@ -395,6 +405,11 @@ const licenseService = { * Uses license key for self-hosted authentication */ async createBillingPortalSession(returnUrl: string, licenseKey: string): Promise { + // Check if Supabase is configured + if (!isSupabaseConfigured || !supabase) { + throw new Error('Supabase is not configured. Billing portal is not available.'); + } + const { data, error} = await supabase.functions.invoke('manage-billing', { body: { return_url: returnUrl, @@ -429,6 +444,11 @@ const licenseService = { * Check if license key is ready for the given installation ID */ async checkLicenseKey(installationId: string): Promise { + // Check if Supabase is configured + if (!isSupabaseConfigured || !supabase) { + throw new Error('Supabase is not configured. License key lookup is not available.'); + } + const { data, error } = await supabase.functions.invoke('get-license-key', { body: { installation_id: installationId,