From 6b15a50311771f7f3ec7db6802e492778a3f5fe1 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 19 Dec 2025 11:41:47 -0800 Subject: [PATCH] improvement(ui): updated subscription and team settings modals to emcn (#2477) --- .../cancel-subscription.tsx | 35 +++-- .../credit-balance/credit-balance.tsx | 101 ++++++------- .../components/plan-card/plan-card.tsx | 32 ++-- .../components/subscription/subscription.tsx | 138 +++++++++--------- .../components/team-members/team-members.tsx | 89 ++++++----- .../components/team-seats/team-seats.tsx | 84 +++++------ 6 files changed, 235 insertions(+), 244 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx index 247d8a2e8..36a26b6a0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx @@ -2,7 +2,15 @@ import { useEffect, useState } from 'react' import { useQueryClient } from '@tanstack/react-query' -import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn' +import { + Button, + Label, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, +} from '@/components/emcn' import { useSession, useSubscription } from '@/lib/auth/auth-client' import { getSubscriptionStatus } from '@/lib/billing/client/utils' import { cn } from '@/lib/core/utils/cn' @@ -68,7 +76,6 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub if (subscriptionStatus.isTeam && activeOrgId) { referenceId = activeOrgId - // Get subscription ID for team/enterprise subscriptionId = subData?.data?.id } @@ -132,14 +139,12 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub referenceId = activeOrgId subscriptionId = subData?.data?.id } else { - // For personal subscriptions, use user ID and let better-auth find the subscription referenceId = session.user.id subscriptionId = undefined } logger.info('Restoring subscription', { referenceId, subscriptionId }) - // Build restore params - only include subscriptionId if we have one (team/enterprise) const restoreParams: any = { referenceId } if (subscriptionId) { restoreParams.subscriptionId = subscriptionId @@ -150,7 +155,6 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub logger.info('Subscription restored successfully', result) } - // Invalidate queries to refresh data await queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() }) if (activeOrgId) { await queryClient.invalidateQueries({ queryKey: organizationKeys.detail(activeOrgId) }) @@ -175,10 +179,8 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub if (!date) return 'end of current billing period' try { - // Ensure we have a valid Date object const dateObj = date instanceof Date ? date : new Date(date) - // Check if the date is valid if (Number.isNaN(dateObj.getTime())) { return 'end of current billing period' } @@ -196,20 +198,17 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub const periodEndDate = getPeriodEndDate() - // Check if subscription is set to cancel at period end const isCancelAtPeriodEnd = subscriptionData?.cancelAtPeriodEnd === true return ( <>
-
- - {isCancelAtPeriodEnd ? 'Restore Subscription' : 'Manage Subscription'} - +
+ {isCancelAtPeriodEnd && ( -

+ You'll keep access until {formatDate(periodEndDate)} -

+ )}
+ - + Add Credits -
-

- Credits are used before overage charges. Min $10, max $1,000. -

-
- - {success ? ( -
-

+ + {success ? ( +

Credits added successfully!

-
- ) : ( -
-
- -
- - $ - - handleAmountChange(e.target.value)} - placeholder='50' - className='pl-7' - disabled={isPurchasing} - /> -
- {error && {error}} -
- -
-

- Credits are non-refundable and don't expire. They'll be applied automatically to - your {entityType === 'organization' ? 'team' : ''} usage. + ) : ( + <> +

+ Credits are used before overage charges. Min $10, max $1,000.

-
-
- )} +
+ +
+ + $ + + handleAmountChange(e.target.value)} + placeholder='50' + className='pl-7' + disabled={isPurchasing} + /> +
+ {error && {error}} +
+ +
+

+ Credits are non-refundable and don't expire. They'll be applied automatically + to your {entityType === 'organization' ? 'team' : ''} usage. +

+
+ + )} + {!success && ( - + + + + Workspace admins + {workspaceAdmins.map((admin: any) => ( + { + if (admin.userId === billedAccountUserId) return + try { + await updateWorkspaceSettings({ billedAccountUserId: admin.userId }) + } catch (error) { + // Error is already logged in updateWorkspaceSettings + } + }} + > + {admin.email} + + ))} + + )}
)} @@ -614,11 +619,14 @@ function BillingUsageNotificationsToggle() { return (
-
- Usage notifications - Email me when I reach 80% usage +
+ + + Email me when I reach 80% usage +
{ diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-members/team-members.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-members/team-members.tsx index da8d69355..3a3a4e53c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-members/team-members.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-members/team-members.tsx @@ -154,7 +154,7 @@ export function TeamMembers({
{teamItems.map((item) => (
- {/* Member info */} + {/* Left section: Avatar + Name/Role + Action buttons */}
{/* Avatar */} {/* Name and email */} -
+
{item.name} {item.type === 'member' && ( @@ -188,51 +188,50 @@ export function TeamMembers({
{item.email}
- {/* Usage stats - matching subscription layout */} + {/* Action buttons */} {isAdminOrOwner && ( -
-
-
Usage
-
- {isLoadingUsage && item.type === 'member' ? ( - - ) : ( - item.usage - )} -
-
+ <> + {/* Admin/Owner can remove other members */} + {item.type === 'member' && + item.role !== 'owner' && + item.email !== currentUserEmail && ( + + )} + + {/* Admin can cancel invitations */} + {item.type === 'invitation' && ( + + )} + + )} +
+ + {/* Right section: Usage column (right-aligned) */} + {isAdminOrOwner && ( +
+
Usage
+
+ {isLoadingUsage && item.type === 'member' ? ( + + ) : ( + item.usage + )}
- )} -
- - {/* Actions */} -
- {/* Admin/Owner can remove other members */} - {isAdminOrOwner && - item.type === 'member' && - item.role !== 'owner' && - item.email !== currentUserEmail && ( - - )} - - {/* Admin can cancel invitations */} - {isAdminOrOwner && item.type === 'invitation' && ( - - )} -
+
+ )}
))}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx index 06809f411..83442f724 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx @@ -5,11 +5,10 @@ import { type ComboboxOption, Label, Modal, + ModalBody, ModalContent, - ModalDescription, ModalFooter, ModalHeader, - ModalTitle, Tooltip, } from '@/components/emcn' import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants' @@ -55,50 +54,53 @@ export function TeamSeats({ const totalMonthlyCost = selectedSeats * costPerSeat const costChange = currentSeats ? (selectedSeats - currentSeats) * costPerSeat : 0 - const handleConfirm = async () => { - await onConfirm(selectedSeats) - } - const seatOptions: ComboboxOption[] = [1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 40, 50].map((num) => ({ value: num.toString(), - label: `${num} ${num === 1 ? 'seat' : 'seats'} ($${num * costPerSeat}/month)`, + label: `${num} ${num === 1 ? 'seat' : 'seats'}`, })) return ( - - - {title} - {description} - + + {title} + +

{description}

-
- - setSelectedSeats(Number.parseInt(value))} - placeholder='Select number of seats' - /> +
+ + 0 ? selectedSeats.toString() : ''} + onChange={(value) => { + const num = Number.parseInt(value, 10) + if (!Number.isNaN(num) && num > 0) { + setSelectedSeats(num) + } + }} + placeholder='Select or enter number of seats' + editable + disabled={isLoading} + /> +
-

+

Your team will have {selectedSeats} {selectedSeats === 1 ? 'seat' : 'seats'} with a total of ${totalMonthlyCost} inference credits per month.

{showCostBreakdown && currentSeats !== undefined && ( -
-
+
+
Current seats: - {currentSeats} + {currentSeats}
-
+
New seats: - {selectedSeats} + {selectedSeats}
-
- Monthly cost change: - +
+ Monthly cost change: + {costChange > 0 ? '+' : ''}${costChange}
@@ -106,19 +108,14 @@ export function TeamSeats({ )} {error && ( -

+

{error instanceof Error && error.message ? error.message : String(error)}

)} -
+ - @@ -127,22 +124,15 @@ export function TeamSeats({