mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-13 17:10:06 +00:00
## Summary Implements billing infrastructure for team workspaces, separate from legacy personal billing. ## Changes - **Billing abstraction**: New `useBillingContext` composable that switches between legacy (personal) and workspace billing based on context - **Workspace subscription flows**: Pricing tables, plan transitions, cancellation dialogs, and payment preview components for workspace billing - **Top-up credits**: Workspace-specific top-up dialog with polling for payment confirmation - **Workspace API**: Extended with billing endpoints (subscriptions, invoices, payment methods, credits top-up) - **Workspace switcher**: Now displays tier badges for each workspace - **Subscribe polling**: Added polling mechanisms (`useSubscribePolling`, `useTopupPolling`) for async payment flows ## Review Focus - Billing flow correctness for workspace vs legacy contexts - Polling timeout and error handling in payment flows ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8508-Feat-workspaces-6-billing-2f96d73d365081f69f65c1ddf369010d) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
100 lines
2.3 KiB
Vue
100 lines
2.3 KiB
Vue
<template>
|
|
<Toast />
|
|
<Toast group="billing-operation" position="top-right">
|
|
<template #message="slotProps">
|
|
<div class="flex items-center gap-2">
|
|
<i class="pi pi-spin pi-spinner text-primary" />
|
|
<span>{{ slotProps.message.summary }}</span>
|
|
</div>
|
|
</template>
|
|
</Toast>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Toast from 'primevue/toast'
|
|
import { useToast } from 'primevue/usetoast'
|
|
import { nextTick, watch } from 'vue'
|
|
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
|
|
|
const toast = useToast()
|
|
const toastStore = useToastStore()
|
|
const settingStore = useSettingStore()
|
|
|
|
watch(
|
|
() => toastStore.messagesToAdd,
|
|
(newMessages) => {
|
|
if (newMessages.length === 0) {
|
|
return
|
|
}
|
|
|
|
newMessages.forEach((message) => {
|
|
toast.add(message)
|
|
})
|
|
toastStore.messagesToAdd = []
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
watch(
|
|
() => toastStore.messagesToRemove,
|
|
(messagesToRemove) => {
|
|
if (messagesToRemove.length === 0) {
|
|
return
|
|
}
|
|
|
|
messagesToRemove.forEach((message) => {
|
|
toast.remove(message)
|
|
})
|
|
toastStore.messagesToRemove = []
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
watch(
|
|
() => toastStore.removeAllRequested,
|
|
(requested) => {
|
|
if (requested) {
|
|
toast.removeAllGroups()
|
|
toastStore.removeAllRequested = false
|
|
}
|
|
}
|
|
)
|
|
|
|
function updateToastPosition() {
|
|
const styleElement =
|
|
document.getElementById('dynamic-toast-style') || createStyleElement()
|
|
const rect = document
|
|
.querySelector('.graph-canvas-container')
|
|
?.getBoundingClientRect()
|
|
if (!rect) return
|
|
|
|
styleElement.textContent = `
|
|
.p-toast.p-component.p-toast-top-right {
|
|
top: ${rect.top + 100}px !important;
|
|
right: ${window.innerWidth - (rect.left + rect.width) + 20}px !important;
|
|
z-index: 10000 !important;
|
|
}
|
|
`
|
|
}
|
|
|
|
function createStyleElement() {
|
|
const style = document.createElement('style')
|
|
style.id = 'dynamic-toast-style'
|
|
document.head.appendChild(style)
|
|
return style
|
|
}
|
|
|
|
watch(
|
|
() => settingStore.get('Comfy.UseNewMenu'),
|
|
() => nextTick(updateToastPosition),
|
|
{ immediate: true }
|
|
)
|
|
watch(
|
|
() => settingStore.get('Comfy.Sidebar.Location'),
|
|
() => nextTick(updateToastPosition),
|
|
{ immediate: true }
|
|
)
|
|
</script>
|