Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions frontend/src/components/AccountMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { Link } from "@tanstack/react-router";
import { getBillingService } from "@/billing/billingService";
import { useState } from "react";
import type { TeamStatus } from "@/types/team";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { TeamManagementDialog } from "@/components/team/TeamManagementDialog";
import { ApiKeyManagementDialog } from "@/components/apikeys/ApiKeyManagementDialog";
import packageJson from "../../package.json";
Expand Down Expand Up @@ -110,6 +111,7 @@ export function AccountMenu() {
const [isTeamDialogOpen, setIsTeamDialogOpen] = useState(false);
const [isApiKeyDialogOpen, setIsApiKeyDialogOpen] = useState(false);
const [showAboutMenu, setShowAboutMenu] = useState(false);
const [portalError, setPortalError] = useState<string | null>(null);

const hasStripeAccount = billingStatus?.stripe_customer_id !== null;
const productName = billingStatus?.product_name || "";
Expand Down Expand Up @@ -176,6 +178,7 @@ export function AccountMenu() {

try {
setIsPortalLoading(true);
setPortalError(null);
const billingService = getBillingService();
const url = await billingService.getPortalUrl();

Expand Down Expand Up @@ -211,6 +214,9 @@ export function AccountMenu() {
window.open(url, "_blank");
} catch (error) {
console.error("Error fetching portal URL:", error);
setPortalError(
"Unable to open subscription management. Please try again or contact support@opensecret.cloud."
);
} finally {
setIsPortalLoading(false);
}
Expand Down Expand Up @@ -448,6 +454,12 @@ export function AccountMenu() {
</DropdownMenuContent>
<AccountDialog />
<ConfirmDeleteDialog />
{portalError && (
<Alert variant="destructive" className="mt-2">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{portalError}</AlertDescription>
</Alert>
)}
<TeamManagementDialog
open={isTeamDialogOpen}
onOpenChange={setIsTeamDialogOpen}
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/team/TeamInviteDialog.tsx
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export function TeamInviteDialog({ open, onOpenChange, teamStatus }: TeamInviteD
const [isPortalLoading, setIsPortalLoading] = useState(false);
const queryClient = useQueryClient();
const { billingStatus } = useLocalState();

const seatsAvailable = teamStatus?.seats_available || 0;
const hasStripeAccount = billingStatus?.stripe_customer_id !== null;

const handleManageSubscription = async () => {
if (!hasStripeAccount) return;

try {
setError(null);
setIsPortalLoading(true);
const billingService = getBillingService();
const url = await billingService.getPortalUrl();
Expand Down Expand Up @@ -71,6 +71,9 @@ export function TeamInviteDialog({ open, onOpenChange, teamStatus }: TeamInviteD
window.open(url, "_blank", "noopener,noreferrer");
} catch (error) {
console.error("Failed to open billing portal:", error);
setError(
"Unable to open subscription management. Please try again or contact support@opensecret.cloud."
);
} finally {
setIsPortalLoading(false);
}
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/routes/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { FullPageMain } from "@/components/FullPageMain";
import { getBillingService } from "@/billing/billingService";
import { useQuery } from "@tanstack/react-query";
import { MarketingHeader } from "@/components/MarketingHeader";
import { Loader2, Check, AlertTriangle, Bitcoin, Tag } from "lucide-react";
import { Loader2, Check, AlertTriangle, AlertCircle, Bitcoin, Tag } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert";
import type { DiscountResponse } from "@/billing/billingApi";
import { Badge } from "@/components/ui/badge";
import { useLocalState } from "@/state/useLocalState";
Expand Down Expand Up @@ -198,6 +199,7 @@ function PricingFAQ() {

function PricingPage() {
const [checkoutError, setCheckoutError] = useState<string>("");
const [portalError, setPortalError] = useState<string | null>(null);
const [loadingProductId, setLoadingProductId] = useState<string | null>(null);
const [useBitcoin, setUseBitcoin] = useState(false);
const [showTeamSeatDialog, setShowTeamSeatDialog] = useState(false);
Expand Down Expand Up @@ -476,6 +478,7 @@ function PricingPage() {

const handleButtonClick = useCallback(
(product: Product) => {
setPortalError(null);
const targetPlanName = product.name.toLowerCase();
const isFreeplan = targetPlanName.includes("free");
const isTeamPlan = targetPlanName.includes("team");
Expand Down Expand Up @@ -612,6 +615,15 @@ function PricingPage() {
return;
}

// If the user is already on a paid plan (including team) and portal URL failed to load,
// show an error instead of silently falling through to checkout
if (isCurrentPlan) {
setPortalError(
"Unable to open subscription management. Please try again or contact support@opensecret.cloud."
);
return;
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

// If no portal URL exists and it's not a free plan user upgrading,
// create checkout session
// For team plans, show seat selection dialog first
Expand Down Expand Up @@ -785,6 +797,16 @@ function PricingPage() {
</div>
)}

{/* Portal Error Message */}
{portalError && (
<div className="w-full max-w-7xl mx-auto mt-4 px-4 sm:px-6 lg:px-8">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{portalError}</AlertDescription>
</Alert>
</div>
)}

{/* Promotion Banner */}
{discount?.active && (
<div className="w-full max-w-7xl mx-auto mt-4 px-4 sm:px-6 lg:px-8">
Expand Down
Loading