mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 07:30:11 +00:00
feat: Add one-time notification for unified credit system
Shows a popup notification to users with credits explaining the transition to Comfy Credits. Triggers on balance fetch, shows once per user, and auto-expires March 2026.
This commit is contained in:
@@ -116,6 +116,15 @@ export const useFirebaseAuthActions = () => {
|
||||
|
||||
const fetchBalance = wrapWithErrorHandlingAsync(async () => {
|
||||
const result = await authStore.fetchBalance()
|
||||
|
||||
// Show partner node pricing notification if user has credits
|
||||
if (result && result.amount_micros > 0) {
|
||||
const { useReleaseStore } =
|
||||
await import('@/platform/updates/common/releaseStore')
|
||||
const releaseStore = useReleaseStore()
|
||||
releaseStore.showPartnerNodePricingNotification()
|
||||
}
|
||||
|
||||
// Top-up completion tracking happens in UsageLogsTable when events are fetched
|
||||
return result
|
||||
}, reportError)
|
||||
|
||||
@@ -12,12 +12,22 @@ import { stringToLocale } from '@/utils/formatUtil'
|
||||
import { useReleaseService } from './releaseService'
|
||||
import type { ReleaseNote } from './releaseService'
|
||||
|
||||
interface CustomNotification {
|
||||
id: string
|
||||
title: string
|
||||
content: string
|
||||
learnMoreUrl?: string
|
||||
storageKey?: string
|
||||
}
|
||||
|
||||
// Store for managing release notes
|
||||
export const useReleaseStore = defineStore('release', () => {
|
||||
// State
|
||||
const releases = ref<ReleaseNote[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const customNotifications = ref<CustomNotification[]>([])
|
||||
const currentCustomNotification = ref<CustomNotification | null>(null)
|
||||
|
||||
// Services
|
||||
const releaseService = useReleaseService()
|
||||
@@ -172,6 +182,11 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
})
|
||||
|
||||
const shouldShowPopup = computed(() => {
|
||||
// Check for custom notification first
|
||||
if (currentCustomNotification.value) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!isElectron() && !isCloud) {
|
||||
return false
|
||||
}
|
||||
@@ -281,6 +296,77 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Custom notification management
|
||||
function addCustomNotification(notification: CustomNotification): void {
|
||||
// Check if already dismissed via storage key
|
||||
if (
|
||||
notification.storageKey &&
|
||||
localStorage.getItem(notification.storageKey)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove existing notification with same ID
|
||||
customNotifications.value = customNotifications.value.filter(
|
||||
(n) => n.id !== notification.id
|
||||
)
|
||||
|
||||
// Add new notification and set as current
|
||||
customNotifications.value.push(notification)
|
||||
currentCustomNotification.value = notification
|
||||
}
|
||||
|
||||
// Public method for showing one-time notifications
|
||||
function showPartnerNodePricingNotification(): void {
|
||||
const STORAGE_KEY = 'comfy.notifications.partner-node-pricing-shown'
|
||||
const EXPIRY_DATE = new Date('2026-03-01')
|
||||
|
||||
// Skip if past expiry or already shown
|
||||
if (new Date() > EXPIRY_DATE || localStorage.getItem(STORAGE_KEY)) {
|
||||
return
|
||||
}
|
||||
|
||||
addCustomNotification({
|
||||
id: 'partner-node-pricing-change',
|
||||
title: 'One credit system for all',
|
||||
content: `# One credit system for all
|
||||
|
||||
We've unified payments across Comfy. Everything now runs on Comfy Credits:
|
||||
|
||||
- Partner Nodes (formerly API nodes)
|
||||
- Cloud workflows
|
||||
|
||||
Your existing Partner node balance has been converted into credits.
|
||||
|
||||
Learn more about this change [here](https://blog.comfy.org/p/comfy-cloud-update-unified-credit-system)`,
|
||||
learnMoreUrl: '',
|
||||
storageKey: STORAGE_KEY
|
||||
})
|
||||
}
|
||||
|
||||
function dismissCustomNotification(notificationId: string): void {
|
||||
const notification = customNotifications.value.find(
|
||||
(n) => n.id === notificationId
|
||||
)
|
||||
|
||||
if (notification) {
|
||||
// Mark as dismissed in storage if storage key provided
|
||||
if (notification.storageKey) {
|
||||
localStorage.setItem(notification.storageKey, 'true')
|
||||
}
|
||||
|
||||
// Remove from custom notifications
|
||||
customNotifications.value = customNotifications.value.filter(
|
||||
(n) => n.id !== notificationId
|
||||
)
|
||||
|
||||
// Clear current if it was the dismissed one
|
||||
if (currentCustomNotification.value?.id === notificationId) {
|
||||
currentCustomNotification.value = customNotifications.value[0] || null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize store
|
||||
async function initialize(): Promise<void> {
|
||||
await fetchReleases()
|
||||
@@ -300,6 +386,13 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
handleShowChangelog,
|
||||
handleWhatsNewSeen,
|
||||
fetchReleases,
|
||||
initialize
|
||||
initialize,
|
||||
|
||||
// Custom notifications
|
||||
customNotifications,
|
||||
currentCustomNotification,
|
||||
addCustomNotification,
|
||||
dismissCustomNotification,
|
||||
showPartnerNodePricingNotification
|
||||
}
|
||||
})
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
class="modal-footer flex justify-between items-center gap-4 px-4 pb-4"
|
||||
>
|
||||
<a
|
||||
v-if="currentLearnMoreUrl"
|
||||
class="learn-more-link flex items-center gap-2 text-sm font-normal py-1"
|
||||
:href="changelogUrl"
|
||||
:href="currentLearnMoreUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@click="closePopup"
|
||||
@@ -81,6 +82,17 @@ const latestRelease = computed<ReleaseNote | null>(() => {
|
||||
return releaseStore.recentRelease
|
||||
})
|
||||
|
||||
// Get current content (custom notification or release)
|
||||
const currentNotification = computed(
|
||||
() => releaseStore.currentCustomNotification
|
||||
)
|
||||
const currentLearnMoreUrl = computed(() => {
|
||||
if (currentNotification.value?.learnMoreUrl) {
|
||||
return currentNotification.value.learnMoreUrl
|
||||
}
|
||||
return changelogUrl.value
|
||||
})
|
||||
|
||||
// Show popup when on latest version and not dismissed
|
||||
const shouldShow = computed(
|
||||
() => releaseStore.shouldShowPopup && !isDismissed.value
|
||||
@@ -97,12 +109,16 @@ const changelogUrl = computed(() => {
|
||||
})
|
||||
|
||||
const formattedContent = computed(() => {
|
||||
if (!latestRelease.value?.content) {
|
||||
// Use custom notification content first
|
||||
const content =
|
||||
currentNotification.value?.content || latestRelease.value?.content
|
||||
|
||||
if (!content) {
|
||||
return DOMPurify.sanitize(`<p>${t('whatsNewPopup.noReleaseNotes')}</p>`)
|
||||
}
|
||||
|
||||
try {
|
||||
const markdown = latestRelease.value.content
|
||||
const markdown = content
|
||||
|
||||
// Check if content is meaningful (not just whitespace)
|
||||
const trimmedContent = markdown.trim()
|
||||
@@ -127,7 +143,7 @@ const formattedContent = computed(() => {
|
||||
} catch (error) {
|
||||
console.error('Error parsing markdown:', error)
|
||||
// Fallback to plain text with line breaks - sanitize the HTML we create
|
||||
const fallbackContent = latestRelease.value.content.replace(/\n/g, '<br>')
|
||||
const fallbackContent = content.replace(/\n/g, '<br>')
|
||||
return fallbackContent.trim()
|
||||
? DOMPurify.sanitize(fallbackContent)
|
||||
: DOMPurify.sanitize(`<p>${t('whatsNewPopup.noReleaseNotes')}</p>`)
|
||||
@@ -144,8 +160,11 @@ const hide = () => {
|
||||
}
|
||||
|
||||
const closePopup = async () => {
|
||||
// Mark "what's new" seen when popup is closed
|
||||
if (latestRelease.value) {
|
||||
// Handle custom notification dismissal first
|
||||
if (currentNotification.value) {
|
||||
releaseStore.dismissCustomNotification(currentNotification.value.id)
|
||||
} else if (latestRelease.value) {
|
||||
// Mark "what's new" seen when popup is closed
|
||||
await releaseStore.handleWhatsNewSeen(latestRelease.value.version)
|
||||
}
|
||||
hide()
|
||||
|
||||
Reference in New Issue
Block a user