From 9691f5fd001a42ed7da8ed9c192cf588e92da1ad Mon Sep 17 00:00:00 2001 From: Simula_r <18093452+simula-r@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:16:21 -0800 Subject: [PATCH] [backport cloud/1.38] Feat/workspaces 6 billing (#8713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of #8508 to `cloud/1.38` Automatically created by manual backport (cherry-pick of c5431de123b6dd74e075b8e2225b27fed816da0f). Conflicts resolved: - `src/platform/cloud/subscription/composables/useSubscriptionCredits.test.ts` — accepted PR version (uses `useBillingContext` mock instead of pinia/firebase auth) - `src/services/dialogService.ts` — merged: kept cloud/1.38 eager imports for dialog components, replaced `TopUpCreditsDialogContent` with Legacy/Workspace variants, replaced `useSubscription` with `useBillingContext`, added workspace/legacy component selection in `showTopUpCreditsDialog`; skipped lazy loader refactor (separate PR, not part of #8508) Co-authored-by: Claude Opus 4.5 --- .gitignore | 2 + .../ComfyRunButton/CloudRunButtonWrapper.vue | 4 +- ...ue => TopUpCreditsDialogContentLegacy.vue} | 16 +- .../TopUpCreditsDialogContentWorkspace.vue | 295 ++++++++ .../content/setting/LegacyCreditsPanel.vue | 4 +- .../CancelSubscriptionDialogContent.vue | 108 +++ .../dialog/header/SettingDialogHeader.vue | 5 +- src/components/toast/GlobalToast.vue | 8 + .../topbar/CurrentUserButton.test.ts | 6 +- src/components/topbar/CurrentUserButton.vue | 13 +- ...st.ts => CurrentUserPopoverLegacy.test.ts} | 6 +- ...pover.vue => CurrentUserPopoverLegacy.vue} | 0 .../topbar/CurrentUserPopoverWorkspace.vue | 68 +- .../topbar/WorkspaceSwitcherPopover.vue | 77 +- .../auth/useFirebaseAuthActions.ts | 4 +- src/composables/billing/types.ts | 76 ++ .../billing/useBillingContext.test.ts | 164 ++++ src/composables/billing/useBillingContext.ts | 242 ++++++ src/composables/billing/useLegacyBilling.ts | 189 +++++ .../billing/useWorkspaceBilling.ts | 311 ++++++++ src/composables/useCoreCommands.test.ts | 7 + src/composables/useCoreCommands.ts | 4 +- src/extensions/core/cloudRemoteConfig.ts | 4 +- src/extensions/core/cloudSubscription.ts | 4 +- src/locales/en/main.json | 64 +- .../components/UploadModelUpgradeModal.vue | 4 +- .../CloudSubscriptionRedirectView.test.ts | 4 + .../CloudSubscriptionRedirectView.vue | 4 +- .../components/PricingTableWorkspace.vue | 527 +++++++++++++ .../components/SubscribeButton.vue | 76 +- .../components/SubscribeToRun.vue | 4 +- ...SubscriptionAddPaymentPreviewWorkspace.vue | 255 +++++++ .../components/SubscriptionPanel.test.ts | 7 + .../components/SubscriptionPanel.vue | 8 +- .../SubscriptionPanelContentWorkspace.vue | 698 +++++++++++------- .../SubscriptionRequiredDialogContent.vue | 9 +- ...criptionRequiredDialogContentWorkspace.vue | 329 +++++++++ ...SubscriptionTransitionPreviewWorkspace.vue | 264 +++++++ .../composables/useBillingPlans.ts | 60 ++ .../useSubscriptionActions.test.ts | 6 + .../composables/useSubscriptionActions.ts | 4 +- .../useSubscriptionCredits.test.ts | 124 +--- .../composables/useSubscriptionCredits.ts | 67 +- .../composables/useSubscriptionDialog.ts | 28 +- .../subscription/constants/tierPricing.ts | 9 +- .../settings/composables/useSettingUI.ts | 13 +- src/platform/workspace/api/workspaceApi.ts | 438 ++++++++++- .../workspace/stores/teamWorkspaceStore.ts | 15 +- .../extensions/linearMode/LinearControls.vue | 4 +- src/scripts/app.ts | 4 +- src/services/dialogService.ts | 34 +- src/stores/billingOperationStore.test.ts | 504 +++++++++++++ src/stores/billingOperationStore.ts | 244 ++++++ src/stores/firebaseAuthStore.ts | 2 +- 54 files changed, 4862 insertions(+), 564 deletions(-) rename src/components/dialog/content/{TopUpCreditsDialogContent.vue => TopUpCreditsDialogContentLegacy.vue} (95%) create mode 100644 src/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue create mode 100644 src/components/dialog/content/subscription/CancelSubscriptionDialogContent.vue rename src/components/topbar/{CurrentUserPopover.test.ts => CurrentUserPopoverLegacy.test.ts} (98%) rename src/components/topbar/{CurrentUserPopover.vue => CurrentUserPopoverLegacy.vue} (100%) create mode 100644 src/composables/billing/types.ts create mode 100644 src/composables/billing/useBillingContext.test.ts create mode 100644 src/composables/billing/useBillingContext.ts create mode 100644 src/composables/billing/useLegacyBilling.ts create mode 100644 src/composables/billing/useWorkspaceBilling.ts create mode 100644 src/platform/cloud/subscription/components/PricingTableWorkspace.vue create mode 100644 src/platform/cloud/subscription/components/SubscriptionAddPaymentPreviewWorkspace.vue create mode 100644 src/platform/cloud/subscription/components/SubscriptionRequiredDialogContentWorkspace.vue create mode 100644 src/platform/cloud/subscription/components/SubscriptionTransitionPreviewWorkspace.vue create mode 100644 src/platform/cloud/subscription/composables/useBillingPlans.ts create mode 100644 src/stores/billingOperationStore.test.ts create mode 100644 src/stores/billingOperationStore.ts diff --git a/.gitignore b/.gitignore index 3e400ba39..6eddbcb53 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,5 @@ vitest.config.*.timestamp* # Weekly docs check output /output.txt + +.amp \ No newline at end of file diff --git a/src/components/actionbar/ComfyRunButton/CloudRunButtonWrapper.vue b/src/components/actionbar/ComfyRunButton/CloudRunButtonWrapper.vue index c0cda19bf..6aa543eec 100644 --- a/src/components/actionbar/ComfyRunButton/CloudRunButtonWrapper.vue +++ b/src/components/actionbar/ComfyRunButton/CloudRunButtonWrapper.vue @@ -8,10 +8,10 @@ import { computed } from 'vue' import ComfyQueueButton from '@/components/actionbar/ComfyRunButton/ComfyQueueButton.vue' +import { useBillingContext } from '@/composables/billing/useBillingContext' import SubscribeToRunButton from '@/platform/cloud/subscription/components/SubscribeToRun.vue' -import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' -const { isActiveSubscription } = useSubscription() +const { isActiveSubscription } = useBillingContext() const currentButton = computed(() => isActiveSubscription.value ? ComfyQueueButton : SubscribeToRunButton diff --git a/src/components/dialog/content/TopUpCreditsDialogContent.vue b/src/components/dialog/content/TopUpCreditsDialogContentLegacy.vue similarity index 95% rename from src/components/dialog/content/TopUpCreditsDialogContent.vue rename to src/components/dialog/content/TopUpCreditsDialogContentLegacy.vue index 51c29aaf5..03640fbfa 100644 --- a/src/components/dialog/content/TopUpCreditsDialogContent.vue +++ b/src/components/dialog/content/TopUpCreditsDialogContentLegacy.vue @@ -158,6 +158,7 @@ import Button from '@/components/ui/button/Button.vue' import FormattedNumberStepper from '@/components/ui/stepper/FormattedNumberStepper.vue' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useExternalLink } from '@/composables/useExternalLink' +import { useFeatureFlags } from '@/composables/useFeatureFlags' import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' import { useTelemetry } from '@/platform/telemetry' import { clearTopupTracking } from '@/platform/telemetry/topupTracker' @@ -176,8 +177,9 @@ const dialogService = useDialogService() const telemetry = useTelemetry() const toast = useToast() const { buildDocsUrl, docsPaths } = useExternalLink() -const { isSubscriptionEnabled } = useSubscription() +const { flags } = useFeatureFlags() +const { isSubscriptionEnabled } = useSubscription() // Constants const PRESET_AMOUNTS = [10, 25, 50, 100] const MIN_AMOUNT = 5 @@ -256,9 +258,15 @@ async function handleBuy() { // Close top-up dialog (keep tracking) and open credits panel to show updated balance handleClose(false) - dialogService.showSettingsDialog( - isSubscriptionEnabled() ? 'subscription' : 'credits' - ) + + // In workspace mode (personal workspace), show workspace settings panel + // Otherwise, show legacy subscription/credits panel + const settingsPanel = flags.teamWorkspacesEnabled + ? 'workspace' + : isSubscriptionEnabled() + ? 'subscription' + : 'credits' + dialogService.showSettingsDialog(settingsPanel) } catch (error) { console.error('Purchase failed:', error) diff --git a/src/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue b/src/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue new file mode 100644 index 000000000..1f076abe0 --- /dev/null +++ b/src/components/dialog/content/TopUpCreditsDialogContentWorkspace.vue @@ -0,0 +1,295 @@ + + + diff --git a/src/components/dialog/content/setting/LegacyCreditsPanel.vue b/src/components/dialog/content/setting/LegacyCreditsPanel.vue index c85fc4b37..69993384b 100644 --- a/src/components/dialog/content/setting/LegacyCreditsPanel.vue +++ b/src/components/dialog/content/setting/LegacyCreditsPanel.vue @@ -116,9 +116,9 @@ import { computed, ref, watch } from 'vue' import UserCredit from '@/components/common/UserCredit.vue' import UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.vue' import Button from '@/components/ui/button/Button.vue' +import { useBillingContext } from '@/composables/billing/useBillingContext' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useExternalLink } from '@/composables/useExternalLink' -import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription' import { useTelemetry } from '@/platform/telemetry' import { useDialogService } from '@/services/dialogService' import { useCommandStore } from '@/stores/commandStore' @@ -138,7 +138,7 @@ const authStore = useFirebaseAuthStore() const authActions = useFirebaseAuthActions() const commandStore = useCommandStore() const telemetry = useTelemetry() -const { isActiveSubscription } = useSubscription() +const { isActiveSubscription } = useBillingContext() const loading = computed(() => authStore.loading) const balanceLoading = computed(() => authStore.isFetchingBalance) diff --git a/src/components/dialog/content/subscription/CancelSubscriptionDialogContent.vue b/src/components/dialog/content/subscription/CancelSubscriptionDialogContent.vue new file mode 100644 index 000000000..f67c26d5e --- /dev/null +++ b/src/components/dialog/content/subscription/CancelSubscriptionDialogContent.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/components/dialog/header/SettingDialogHeader.vue b/src/components/dialog/header/SettingDialogHeader.vue index 959cfa14d..3c098430e 100644 --- a/src/components/dialog/header/SettingDialogHeader.vue +++ b/src/components/dialog/header/SettingDialogHeader.vue @@ -1,6 +1,6 @@