comfyui-frontend-package 1.38.5__py3-none-any.whl → 1.38.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. comfyui_frontend_package/static/assets/{AboutPanel-CHse5rOA.js → AboutPanel-lkjGFasi.js} +2 -2
  2. comfyui_frontend_package/static/assets/{AboutPanel-CHse5rOA.js.map → AboutPanel-lkjGFasi.js.map} +1 -1
  3. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-CAa8V66L.js → AudioPreviewPlayer-BxCSKPl9.js} +2 -2
  4. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-CAa8V66L.js.map → AudioPreviewPlayer-BxCSKPl9.js.map} +1 -1
  5. comfyui_frontend_package/static/assets/AudioPreviewPlayer-CkxKvcVf.js +1 -0
  6. comfyui_frontend_package/static/assets/{BaseViewTemplate-DA6zfigT.js → BaseViewTemplate-CjODF2hh.js} +2 -2
  7. comfyui_frontend_package/static/assets/{BaseViewTemplate-DA6zfigT.js.map → BaseViewTemplate-CjODF2hh.js.map} +1 -1
  8. comfyui_frontend_package/static/assets/{CloudAuthTimeoutView-9OPBS1hE.js → CloudAuthTimeoutView-D-QkjPNh.js} +2 -2
  9. comfyui_frontend_package/static/assets/{CloudAuthTimeoutView-9OPBS1hE.js.map → CloudAuthTimeoutView-D-QkjPNh.js.map} +1 -1
  10. comfyui_frontend_package/static/assets/{CloudBadge-BnLiAHDN.js → CloudBadge-B4nmLus2.js} +2 -2
  11. comfyui_frontend_package/static/assets/{CloudBadge-BnLiAHDN.js.map → CloudBadge-B4nmLus2.js.map} +1 -1
  12. comfyui_frontend_package/static/assets/{CloudForgotPasswordView-BqDR_C7K.js → CloudForgotPasswordView-DOEV9hGr.js} +2 -2
  13. comfyui_frontend_package/static/assets/{CloudForgotPasswordView-BqDR_C7K.js.map → CloudForgotPasswordView-DOEV9hGr.js.map} +1 -1
  14. comfyui_frontend_package/static/assets/{CloudLayoutView-vTrrVUOY.js → CloudLayoutView-ShKH6rRV.js} +2 -2
  15. comfyui_frontend_package/static/assets/{CloudLayoutView-vTrrVUOY.js.map → CloudLayoutView-ShKH6rRV.js.map} +1 -1
  16. comfyui_frontend_package/static/assets/{CloudLoginView-T17euJly.js → CloudLoginView-C3Te42U9.js} +2 -2
  17. comfyui_frontend_package/static/assets/{CloudLoginView-T17euJly.js.map → CloudLoginView-C3Te42U9.js.map} +1 -1
  18. comfyui_frontend_package/static/assets/CloudRunButtonWrapper-Cub7EB34.js +3 -0
  19. comfyui_frontend_package/static/assets/{CloudRunButtonWrapper-hQc4BNkX.js.map → CloudRunButtonWrapper-Cub7EB34.js.map} +1 -1
  20. comfyui_frontend_package/static/assets/{CloudSignupView-vEDby5k3.js → CloudSignupView-X2oiL3ZR.js} +2 -2
  21. comfyui_frontend_package/static/assets/{CloudSignupView-vEDby5k3.js.map → CloudSignupView-X2oiL3ZR.js.map} +1 -1
  22. comfyui_frontend_package/static/assets/{CloudSubscriptionRedirectView-DPyO745g.js → CloudSubscriptionRedirectView-UjNv8emo.js} +2 -2
  23. comfyui_frontend_package/static/assets/{CloudSubscriptionRedirectView-DPyO745g.js.map → CloudSubscriptionRedirectView-UjNv8emo.js.map} +1 -1
  24. comfyui_frontend_package/static/assets/{CloudSurveyView-CBtTd9Ru.js → CloudSurveyView-IaiucCTP.js} +2 -2
  25. comfyui_frontend_package/static/assets/{CloudSurveyView-CBtTd9Ru.js.map → CloudSurveyView-IaiucCTP.js.map} +1 -1
  26. comfyui_frontend_package/static/assets/ComfyQueueButton-BppnHbrl.js +1 -0
  27. comfyui_frontend_package/static/assets/{ComfyQueueButton-MZrp7wYJ.js → ComfyQueueButton-HjSIKZKO.js} +2 -2
  28. comfyui_frontend_package/static/assets/{ComfyQueueButton-MZrp7wYJ.js.map → ComfyQueueButton-HjSIKZKO.js.map} +1 -1
  29. comfyui_frontend_package/static/assets/{ExtensionPanel-CrWVGUtg.js → ExtensionPanel-Bzb9QtKj.js} +2 -2
  30. comfyui_frontend_package/static/assets/{ExtensionPanel-CrWVGUtg.js.map → ExtensionPanel-Bzb9QtKj.js.map} +1 -1
  31. comfyui_frontend_package/static/assets/{GlobalToast-BiCmpIvO.js → GlobalToast-BSCvu6Hw.js} +2 -2
  32. comfyui_frontend_package/static/assets/{GlobalToast-BiCmpIvO.js.map → GlobalToast-BSCvu6Hw.js.map} +1 -1
  33. comfyui_frontend_package/static/assets/{GraphView-BCkpNGgz.js → GraphView-gYVCtm1V.js} +5 -5
  34. comfyui_frontend_package/static/assets/GraphView-gYVCtm1V.js.map +1 -0
  35. comfyui_frontend_package/static/assets/{KeybindingPanel-CAXL5TlV.js → KeybindingPanel-DF-bG4iO.js} +2 -2
  36. comfyui_frontend_package/static/assets/{KeybindingPanel-CAXL5TlV.js.map → KeybindingPanel-DF-bG4iO.js.map} +1 -1
  37. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-6vR8koQy.js → LegacyCreditsPanel-D-CboO8k.js} +2 -2
  38. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-6vR8koQy.js.map → LegacyCreditsPanel-D-CboO8k.js.map} +1 -1
  39. comfyui_frontend_package/static/assets/Load3D-c9UwgGoI.js +1 -0
  40. comfyui_frontend_package/static/assets/{Load3D-ei1BUF9O.js → Load3D-vYr8M3jJ.js} +2 -2
  41. comfyui_frontend_package/static/assets/{Load3D-ei1BUF9O.js.map → Load3D-vYr8M3jJ.js.map} +1 -1
  42. comfyui_frontend_package/static/assets/{PanelTemplate-BjN5XNg2.js → PanelTemplate-BJda9e5J.js} +2 -2
  43. comfyui_frontend_package/static/assets/{PanelTemplate-BjN5XNg2.js.map → PanelTemplate-BJda9e5J.js.map} +1 -1
  44. comfyui_frontend_package/static/assets/{ServerConfigPanel-CxovH9Qk.js → ServerConfigPanel-VsC6xlZJ.js} +2 -2
  45. comfyui_frontend_package/static/assets/{ServerConfigPanel-CxovH9Qk.js.map → ServerConfigPanel-VsC6xlZJ.js.map} +1 -1
  46. comfyui_frontend_package/static/assets/{SubscribeButton-CTOQRkfg.js → SubscribeButton-DZBycfCA.js} +2 -2
  47. comfyui_frontend_package/static/assets/{SubscribeButton-CTOQRkfg.js.map → SubscribeButton-DZBycfCA.js.map} +1 -1
  48. comfyui_frontend_package/static/assets/{SubscribeToRun-DnXzV8y0.js → SubscribeToRun-4YolxBOL.js} +2 -2
  49. comfyui_frontend_package/static/assets/{SubscribeToRun-DnXzV8y0.js.map → SubscribeToRun-4YolxBOL.js.map} +1 -1
  50. comfyui_frontend_package/static/assets/{SubscriptionPanel-D9uv7z8f.js → SubscriptionPanel-B6txX4Vm.js} +2 -2
  51. comfyui_frontend_package/static/assets/{SubscriptionPanel-D9uv7z8f.js.map → SubscriptionPanel-B6txX4Vm.js.map} +1 -1
  52. comfyui_frontend_package/static/assets/{SubscriptionRequiredDialogContent--VmT16oc.js → SubscriptionRequiredDialogContent-COEF2VQ_.js} +2 -2
  53. comfyui_frontend_package/static/assets/{SubscriptionRequiredDialogContent--VmT16oc.js.map → SubscriptionRequiredDialogContent-COEF2VQ_.js.map} +1 -1
  54. comfyui_frontend_package/static/assets/{UserCheckView-spD3LyMu.js → UserCheckView-x-fkcYzc.js} +2 -2
  55. comfyui_frontend_package/static/assets/{UserCheckView-spD3LyMu.js.map → UserCheckView-x-fkcYzc.js.map} +1 -1
  56. comfyui_frontend_package/static/assets/{UserPanel-Su6NtJ5q.js → UserPanel-7N9QknQj.js} +2 -2
  57. comfyui_frontend_package/static/assets/{UserPanel-Su6NtJ5q.js.map → UserPanel-7N9QknQj.js.map} +1 -1
  58. comfyui_frontend_package/static/assets/{UserSelectView-C5LBOPcv.js → UserSelectView-BYjOkfSa.js} +2 -2
  59. comfyui_frontend_package/static/assets/{UserSelectView-C5LBOPcv.js.map → UserSelectView-BYjOkfSa.js.map} +1 -1
  60. comfyui_frontend_package/static/assets/{ValueControlPopover-BdlDzT8l.js → ValueControlPopover-BPAa35QG.js} +2 -2
  61. comfyui_frontend_package/static/assets/{ValueControlPopover-BdlDzT8l.js.map → ValueControlPopover-BPAa35QG.js.map} +1 -1
  62. comfyui_frontend_package/static/assets/{WidgetAudioUI-BDZxDx_r.js → WidgetAudioUI-Dw-r3Ews.js} +2 -2
  63. comfyui_frontend_package/static/assets/{WidgetAudioUI-BDZxDx_r.js.map → WidgetAudioUI-Dw-r3Ews.js.map} +1 -1
  64. comfyui_frontend_package/static/assets/{WidgetImageCrop-CYRW7t2Q.js → WidgetImageCrop-kERy9g5I.js} +2 -2
  65. comfyui_frontend_package/static/assets/{WidgetImageCrop-CYRW7t2Q.js.map → WidgetImageCrop-kERy9g5I.js.map} +1 -1
  66. comfyui_frontend_package/static/assets/{WidgetInputNumber-BOKO36G3.js → WidgetInputNumber-BaClCNAC.js} +1 -1
  67. comfyui_frontend_package/static/assets/WidgetInputNumber-DU_D0Fzy.js +3 -0
  68. comfyui_frontend_package/static/assets/WidgetInputNumber-DU_D0Fzy.js.map +1 -0
  69. comfyui_frontend_package/static/assets/{WidgetLegacy-Bslv9wZZ.js → WidgetLegacy-B4nipUM9.js} +1 -1
  70. comfyui_frontend_package/static/assets/{WidgetRecordAudio-Bzy8PIzN.js → WidgetRecordAudio-Nk8dH238.js} +2 -2
  71. comfyui_frontend_package/static/assets/{WidgetRecordAudio-Bzy8PIzN.js.map → WidgetRecordAudio-Nk8dH238.js.map} +1 -1
  72. comfyui_frontend_package/static/assets/WidgetSelect-DzZPpO_-.js +1 -0
  73. comfyui_frontend_package/static/assets/{WidgetSelect-zgrFVzHH.js → WidgetSelect-nSQrk_hd.js} +2 -2
  74. comfyui_frontend_package/static/assets/{WidgetSelect-zgrFVzHH.js.map → WidgetSelect-nSQrk_hd.js.map} +1 -1
  75. comfyui_frontend_package/static/assets/{WidgetWithControl-Dh2FWOiA.js → WidgetWithControl-Da6zUB5e.js} +3 -3
  76. comfyui_frontend_package/static/assets/{WidgetWithControl-Dh2FWOiA.js.map → WidgetWithControl-Da6zUB5e.js.map} +1 -1
  77. comfyui_frontend_package/static/assets/{api-CUAc7rDA.js → api-Dwq2LQIW.js} +4 -4
  78. comfyui_frontend_package/static/assets/api-Dwq2LQIW.js.map +1 -0
  79. comfyui_frontend_package/static/assets/{audioService-DvndbCi2.js → audioService-DvVaKhuU.js} +2 -2
  80. comfyui_frontend_package/static/assets/{audioService-DvndbCi2.js.map → audioService-DvVaKhuU.js.map} +1 -1
  81. comfyui_frontend_package/static/assets/{audioUtils-DpjpcKbH.js → audioUtils-DD4rUYVZ.js} +2 -2
  82. comfyui_frontend_package/static/assets/{audioUtils-DpjpcKbH.js.map → audioUtils-DD4rUYVZ.js.map} +1 -1
  83. comfyui_frontend_package/static/assets/{auth-B8ZZ0KKQ.js → auth-B9axG-yZ.js} +2 -2
  84. comfyui_frontend_package/static/assets/{auth-B8ZZ0KKQ.js.map → auth-B9axG-yZ.js.map} +1 -1
  85. comfyui_frontend_package/static/assets/auth-D74DTev8.js +1 -0
  86. comfyui_frontend_package/static/assets/{cloudBadges-C1a7fBky.js → cloudBadges-D5mGJbRy.js} +2 -2
  87. comfyui_frontend_package/static/assets/{cloudBadges-C1a7fBky.js.map → cloudBadges-D5mGJbRy.js.map} +1 -1
  88. comfyui_frontend_package/static/assets/{cloudFeedbackTopbarButton-DR0T8sWG.js → cloudFeedbackTopbarButton-RZUssOmb.js} +2 -2
  89. comfyui_frontend_package/static/assets/{cloudFeedbackTopbarButton-DR0T8sWG.js.map → cloudFeedbackTopbarButton-RZUssOmb.js.map} +1 -1
  90. comfyui_frontend_package/static/assets/{cloudRemoteConfig-DhMjC5TB.js → cloudRemoteConfig-F9J0iGyF.js} +2 -2
  91. comfyui_frontend_package/static/assets/{cloudRemoteConfig-DhMjC5TB.js.map → cloudRemoteConfig-F9J0iGyF.js.map} +1 -1
  92. comfyui_frontend_package/static/assets/{cloudSessionCookie-Duxk6ux1.js → cloudSessionCookie-MEORlqtg.js} +2 -2
  93. comfyui_frontend_package/static/assets/{cloudSessionCookie-Duxk6ux1.js.map → cloudSessionCookie-MEORlqtg.js.map} +1 -1
  94. comfyui_frontend_package/static/assets/{cloudSubscription-B8l6B9Nx.js → cloudSubscription-CuWNXKVx.js} +2 -2
  95. comfyui_frontend_package/static/assets/{cloudSubscription-B8l6B9Nx.js.map → cloudSubscription-CuWNXKVx.js.map} +1 -1
  96. comfyui_frontend_package/static/assets/{core-DBfeqMDR.js → core-IYu8XAIx.js} +4 -4
  97. comfyui_frontend_package/static/assets/{core-DBfeqMDR.js.map → core-IYu8XAIx.js.map} +1 -1
  98. comfyui_frontend_package/static/assets/{dialogService-BZ1FmjZL.js → dialogService-YG0RH337.js} +9 -9
  99. comfyui_frontend_package/static/assets/{dialogService-BZ1FmjZL.js.map → dialogService-YG0RH337.js.map} +1 -1
  100. comfyui_frontend_package/static/assets/firebaseAuthStore-DnNaPbuZ.js +1 -0
  101. comfyui_frontend_package/static/assets/{graphHasMissingNodes-C79Wi51S.js → graphHasMissingNodes-BhD1N6zI.js} +2 -2
  102. comfyui_frontend_package/static/assets/{graphHasMissingNodes-C79Wi51S.js.map → graphHasMissingNodes-BhD1N6zI.js.map} +1 -1
  103. comfyui_frontend_package/static/assets/{index-CGxJFSof.js → index-BMy3twho.js} +3 -3
  104. comfyui_frontend_package/static/assets/{index-CGxJFSof.js.map → index-BMy3twho.js.map} +1 -1
  105. comfyui_frontend_package/static/assets/{index-DNpOhRra.css → index-KMO9qFHH.css} +1 -1
  106. comfyui_frontend_package/static/assets/{keybindingService-B88NjeAU.js → keybindingService-CBLPjYHI.js} +2 -2
  107. comfyui_frontend_package/static/assets/{keybindingService-B88NjeAU.js.map → keybindingService-CBLPjYHI.js.map} +1 -1
  108. comfyui_frontend_package/static/assets/{releaseStore-x0vHjxrw.js → releaseStore-DDOxzkVb.js} +2 -2
  109. comfyui_frontend_package/static/assets/{releaseStore-x0vHjxrw.js.map → releaseStore-DDOxzkVb.js.map} +1 -1
  110. comfyui_frontend_package/static/assets/releaseStore-iVkqunL8.js +1 -0
  111. comfyui_frontend_package/static/assets/{subscriptionCheckoutUtil-B_OvUP2T.js → subscriptionCheckoutUtil-DswSOreM.js} +2 -2
  112. comfyui_frontend_package/static/assets/{subscriptionCheckoutUtil-B_OvUP2T.js.map → subscriptionCheckoutUtil-DswSOreM.js.map} +1 -1
  113. comfyui_frontend_package/static/assets/{useCurrentUser-BJcn2Vgo.js → useCurrentUser-NdaCJzIK.js} +1 -1
  114. comfyui_frontend_package/static/assets/{useErrorHandling-CI8_F4yx.js → useErrorHandling-Cfa5N_7c.js} +2 -2
  115. comfyui_frontend_package/static/assets/{useErrorHandling-CI8_F4yx.js.map → useErrorHandling-Cfa5N_7c.js.map} +1 -1
  116. comfyui_frontend_package/static/assets/{useSubscriptionDialog-Chxkdny5.js → useSubscriptionDialog-792qfEJ2.js} +3 -3
  117. comfyui_frontend_package/static/assets/{useSubscriptionDialog-Chxkdny5.js.map → useSubscriptionDialog-792qfEJ2.js.map} +1 -1
  118. comfyui_frontend_package/static/assets/useSubscriptionDialog-B-eGeK3j.js +1 -0
  119. comfyui_frontend_package/static/assets/{userStore-BkgQPjq6.js → userStore-BAS9m9W6.js} +2 -2
  120. comfyui_frontend_package/static/assets/{userStore-BkgQPjq6.js.map → userStore-BAS9m9W6.js.map} +1 -1
  121. comfyui_frontend_package/static/assets/vendor-three-BFcUNSs9.js.map +1 -1
  122. comfyui_frontend_package/static/index.html +1 -1
  123. {comfyui_frontend_package-1.38.5.dist-info → comfyui_frontend_package-1.38.6.dist-info}/METADATA +1 -1
  124. {comfyui_frontend_package-1.38.5.dist-info → comfyui_frontend_package-1.38.6.dist-info}/RECORD +126 -126
  125. comfyui_frontend_package/static/assets/AudioPreviewPlayer-BoEdyGI_.js +0 -1
  126. comfyui_frontend_package/static/assets/CloudRunButtonWrapper-hQc4BNkX.js +0 -3
  127. comfyui_frontend_package/static/assets/ComfyQueueButton-BbQnRThI.js +0 -1
  128. comfyui_frontend_package/static/assets/GraphView-BCkpNGgz.js.map +0 -1
  129. comfyui_frontend_package/static/assets/Load3D-DHBmC_AU.js +0 -1
  130. comfyui_frontend_package/static/assets/WidgetInputNumber-DGKypM5j.js +0 -3
  131. comfyui_frontend_package/static/assets/WidgetInputNumber-DGKypM5j.js.map +0 -1
  132. comfyui_frontend_package/static/assets/WidgetSelect-DsJGH12l.js +0 -1
  133. comfyui_frontend_package/static/assets/api-CUAc7rDA.js.map +0 -1
  134. comfyui_frontend_package/static/assets/auth-D3RiiqZ8.js +0 -1
  135. comfyui_frontend_package/static/assets/firebaseAuthStore-CZgxeMyf.js +0 -1
  136. comfyui_frontend_package/static/assets/releaseStore-CubqSv5t.js +0 -1
  137. comfyui_frontend_package/static/assets/useSubscriptionDialog-BzMzio2H.js +0 -1
  138. {comfyui_frontend_package-1.38.5.dist-info → comfyui_frontend_package-1.38.6.dist-info}/WHEEL +0 -0
  139. {comfyui_frontend_package-1.38.5.dist-info → comfyui_frontend_package-1.38.6.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"SubscriptionRequiredDialogContent--VmT16oc.js","names":[],"sources":["../../../../../../../assets/images/cloud-subscription.webm","../../src/config/subscriptionPricesConfig.ts","../../src/platform/cloud/subscription/utils/subscriptionTierRank.ts","../../src/platform/cloud/subscription/components/PricingTable.vue","../../src/platform/cloud/subscription/components/PricingTable.vue","../../src/platform/cloud/subscription/components/SubscriptionBenefits.vue","../../src/platform/cloud/subscription/components/SubscriptionRequiredDialogContent.vue","../../src/platform/cloud/subscription/components/SubscriptionRequiredDialogContent.vue"],"sourcesContent":["export default \"__VITE_PUBLIC_ASSET__b4a125b1__\"","export const MONTHLY_SUBSCRIPTION_PRICE = 20\n","import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing'\n\nexport type BillingCycle = 'monthly' | 'yearly'\n\ntype RankedTierKey = Exclude<TierKey, 'founder'>\ntype RankedPlanKey = `${BillingCycle}-${RankedTierKey}`\n\ninterface PlanDescriptor {\n tierKey: TierKey\n billingCycle: BillingCycle\n}\n\nconst PLAN_ORDER: RankedPlanKey[] = [\n 'yearly-pro',\n 'yearly-creator',\n 'yearly-standard',\n 'monthly-pro',\n 'monthly-creator',\n 'monthly-standard'\n]\n\nconst PLAN_RANK = PLAN_ORDER.reduce<Map<RankedPlanKey, number>>(\n (acc, plan, index) => acc.set(plan, index),\n new Map()\n)\n\nconst toRankedPlanKey = (\n tierKey: TierKey,\n billingCycle: BillingCycle\n): RankedPlanKey | null => {\n if (tierKey === 'founder') return null\n return `${billingCycle}-${tierKey}` as RankedPlanKey\n}\n\nexport const getPlanRank = ({\n tierKey,\n billingCycle\n}: PlanDescriptor): number => {\n const planKey = toRankedPlanKey(tierKey, billingCycle)\n if (!planKey) return Number.POSITIVE_INFINITY\n\n return PLAN_RANK.get(planKey) ?? Number.POSITIVE_INFINITY\n}\n\ninterface DowngradeCheckParams {\n current: PlanDescriptor\n target: PlanDescriptor\n}\n\nexport const isPlanDowngrade = ({\n current,\n target\n}: DowngradeCheckParams): boolean => {\n const currentRank = getPlanRank(current)\n const targetRank = getPlanRank(target)\n\n return targetRank > currentRank\n}\n","<template>\n <div class=\"flex flex-col gap-8\">\n <div class=\"flex justify-center\">\n <SelectButton\n v-model=\"currentBillingCycle\"\n :options=\"billingCycleOptions\"\n option-label=\"label\"\n option-value=\"value\"\n :allow-empty=\"false\"\n unstyled\n :pt=\"{\n root: {\n class: 'flex gap-1 bg-secondary-background rounded-lg p-1.5'\n },\n pcToggleButton: {\n root: ({ context }: ToggleButtonPassThroughMethodOptions) => ({\n class: [\n 'w-36 h-8 rounded-md transition-colors cursor-pointer border-none outline-none ring-0 text-sm font-medium flex items-center justify-center',\n context.active\n ? 'bg-base-foreground text-base-background'\n : 'bg-transparent text-muted-foreground hover:bg-secondary-background-hover'\n ]\n }),\n label: { class: 'flex items-center gap-2 ' }\n }\n }\"\n >\n <template #option=\"{ option }\">\n <div class=\"flex items-center gap-2\">\n <span>{{ option.label }}</span>\n <div\n v-if=\"option.value === 'yearly'\"\n class=\"bg-primary-background text-white text-[11px] px-1 py-0.5 rounded-full flex items-center font-bold\"\n >\n -20%\n </div>\n </div>\n </template>\n </SelectButton>\n </div>\n <div class=\"flex flex-col xl:flex-row items-stretch gap-6\">\n <div\n v-for=\"tier in tiers\"\n :key=\"tier.id\"\n :class=\"\n cn(\n 'flex-1 flex flex-col rounded-2xl border border-border-default bg-base-background shadow-[0_0_12px_rgba(0,0,0,0.1)]',\n tier.isPopular ? 'border-muted-foreground' : ''\n )\n \"\n >\n <div class=\"p-8 pb-0 flex flex-col gap-8\">\n <div class=\"flex flex-row items-center gap-2 justify-between\">\n <span\n class=\"font-inter text-base font-bold leading-normal text-base-foreground\"\n >\n {{ tier.name }}\n </span>\n <div\n v-if=\"tier.isPopular\"\n class=\"rounded-full bg-base-foreground px-1.5 text-[11px] font-bold uppercase text-base-background h-5 tracking-tight flex items-center\"\n >\n {{ t('subscription.mostPopular') }}\n </div>\n </div>\n <div class=\"flex flex-col\">\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-baseline gap-2\">\n <span\n class=\"font-inter text-[32px] font-semibold leading-normal text-base-foreground\"\n >\n <span\n v-show=\"currentBillingCycle === 'yearly'\"\n class=\"line-through text-2xl text-muted-foreground\"\n >\n ${{ tier.pricing.monthly }}\n </span>\n ${{ getPrice(tier) }}\n </span>\n <span\n class=\"font-inter text-xl leading-normal text-base-foreground\"\n >\n {{ t('subscription.usdPerMonth') }}\n </span>\n </div>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-sm text-muted-foreground\">\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.billedYearly', {\n total: `$${getAnnualTotal(tier)}`\n })\n : t('subscription.billedMonthly')\n }}\n </span>\n </div>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-4 pb-0 flex-1\">\n <div class=\"flex flex-row items-center justify-between\">\n <span\n class=\"font-inter text-sm font-normal leading-normal text-foreground\"\n >\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.yearlyCreditsLabel')\n : t('subscription.monthlyCreditsLabel')\n }}\n </span>\n <div class=\"flex flex-row items-center gap-1\">\n <i class=\"icon-[lucide--component] text-amber-400 text-sm\" />\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ n(getCreditsDisplay(tier)) }}\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.maxDurationLabel') }}\n </span>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ tier.maxDuration }}\n </span>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.gpuLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.addCreditsLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.customLoRAsLabel') }}\n </span>\n <i\n v-if=\"tier.customLoRAs\"\n class=\"pi pi-check text-xs text-success-foreground\"\n />\n <i v-else class=\"pi pi-times text-xs text-foreground\" />\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-start justify-between\">\n <div class=\"flex flex-col gap-2\">\n <span\n class=\"text-sm font-normal text-foreground leading-relaxed\"\n >\n {{ t('subscription.videoEstimateLabel') }}\n </span>\n <div class=\"flex flex-row items-center gap-2 group pt-2\">\n <i\n class=\"pi pi-question-circle text-xs text-muted-foreground group-hover:text-base-foreground\"\n />\n <span\n class=\"text-sm font-normal text-muted-foreground cursor-pointer group-hover:text-base-foreground\"\n @click=\"togglePopover\"\n >\n {{ t('subscription.videoEstimateHelp') }}\n </span>\n </div>\n </div>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n ~{{ n(tier.pricing.videoEstimate) }}\n </span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex flex-col p-8\">\n <Button\n :variant=\"getButtonSeverity(tier)\"\n :disabled=\"isLoading || isCurrentPlan(tier.key)\"\n :loading=\"loadingTier === tier.key\"\n :class=\"\n cn(\n 'h-10 w-full',\n getButtonTextClass(tier),\n tier.key === 'creator'\n ? 'bg-base-foreground border-transparent hover:bg-inverted-background-hover'\n : 'bg-secondary-background border-transparent hover:bg-secondary-background-hover focus:bg-secondary-background-selected'\n )\n \"\n @click=\"() => handleSubscribe(tier.key)\"\n >\n {{ getButtonLabel(tier) }}\n </Button>\n </div>\n </div>\n </div>\n\n <!-- Video Estimate Help Popover -->\n <Popover\n ref=\"popover\"\n append-to=\"body\"\n :auto-z-index=\"true\"\n :base-z-index=\"1000\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: {\n class:\n 'rounded-lg border border-interface-stroke bg-interface-panel-surface shadow-lg p-4 max-w-xs'\n }\n }\"\n >\n <div class=\"flex flex-col gap-2\">\n <p class=\"text-sm text-base-foreground leading-normal\">\n {{ t('subscription.videoEstimateExplanation') }}\n </p>\n <a\n href=\"https://cloud.comfy.org/?template=video_wan2_2_14B_fun_camera\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-sm text-azure-600 hover:text-azure-400 no-underline flex gap-1\"\n >\n <span class=\"underline\">\n {{ t('subscription.videoEstimateTryTemplate') }}\n </span>\n <span class=\"no-underline\" v-html=\"'&rarr;'\"></span>\n </a>\n </div>\n </Popover>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { cn } from '@comfyorg/tailwind-utils'\nimport Popover from 'primevue/popover'\nimport SelectButton from 'primevue/selectbutton'\nimport type { ToggleButtonPassThroughMethodOptions } from 'primevue/togglebutton'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { t } from '@/i18n'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport {\n TIER_PRICING,\n TIER_TO_KEY\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport type {\n TierKey,\n TierPricing\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport { performSubscriptionCheckout } from '@/platform/cloud/subscription/utils/subscriptionCheckoutUtil'\nimport { isPlanDowngrade } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport { isCloud } from '@/platform/distribution/types'\nimport type { components } from '@/types/comfyRegistryTypes'\n\ntype SubscriptionTier = components['schemas']['SubscriptionTier']\ntype CheckoutTierKey = Exclude<TierKey, 'founder'>\ntype CheckoutTier = CheckoutTierKey | `${CheckoutTierKey}-yearly`\n\nconst getCheckoutTier = (\n tierKey: CheckoutTierKey,\n billingCycle: BillingCycle\n): CheckoutTier => (billingCycle === 'yearly' ? `${tierKey}-yearly` : tierKey)\n\ninterface BillingCycleOption {\n label: string\n value: BillingCycle\n}\n\ninterface PricingTierConfig {\n id: SubscriptionTier\n key: CheckoutTierKey\n name: string\n pricing: TierPricing\n maxDuration: string\n customLoRAs: boolean\n isPopular?: boolean\n}\n\nconst billingCycleOptions: BillingCycleOption[] = [\n { label: t('subscription.yearly'), value: 'yearly' },\n { label: t('subscription.monthly'), value: 'monthly' }\n]\n\nconst tiers: PricingTierConfig[] = [\n {\n id: 'STANDARD',\n key: 'standard',\n name: t('subscription.tiers.standard.name'),\n pricing: TIER_PRICING.standard,\n maxDuration: t('subscription.maxDuration.standard'),\n customLoRAs: false,\n isPopular: false\n },\n {\n id: 'CREATOR',\n key: 'creator',\n name: t('subscription.tiers.creator.name'),\n pricing: TIER_PRICING.creator,\n maxDuration: t('subscription.maxDuration.creator'),\n customLoRAs: true,\n isPopular: true\n },\n {\n id: 'PRO',\n key: 'pro',\n name: t('subscription.tiers.pro.name'),\n pricing: TIER_PRICING.pro,\n maxDuration: t('subscription.maxDuration.pro'),\n customLoRAs: true,\n isPopular: false\n }\n]\n\nconst { n } = useI18n()\nconst { isActiveSubscription, subscriptionTier, isYearlySubscription } =\n useSubscription()\nconst { accessBillingPortal, reportError } = useFirebaseAuthActions()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\n\nconst isLoading = ref(false)\nconst loadingTier = ref<CheckoutTierKey | null>(null)\nconst popover = ref()\nconst currentBillingCycle = ref<BillingCycle>('yearly')\n\nconst currentTierKey = computed<TierKey | null>(() =>\n subscriptionTier.value ? TIER_TO_KEY[subscriptionTier.value] : null\n)\n\nconst currentPlanDescriptor = computed(() => {\n if (!currentTierKey.value) return null\n\n return {\n tierKey: currentTierKey.value,\n billingCycle: isYearlySubscription.value ? 'yearly' : 'monthly'\n } as const\n})\n\nconst isCurrentPlan = (tierKey: CheckoutTierKey): boolean => {\n if (!currentTierKey.value) return false\n\n const selectedIsYearly = currentBillingCycle.value === 'yearly'\n\n return (\n currentTierKey.value === tierKey &&\n isYearlySubscription.value === selectedIsYearly\n )\n}\n\nconst togglePopover = (event: Event) => {\n popover.value.toggle(event)\n}\n\nconst getButtonLabel = (tier: PricingTierConfig): string => {\n if (isCurrentPlan(tier.key)) return t('subscription.currentPlan')\n\n const planName =\n currentBillingCycle.value === 'yearly'\n ? t('subscription.tierNameYearly', { name: tier.name })\n : tier.name\n\n return isActiveSubscription.value\n ? t('subscription.changeTo', { plan: planName })\n : t('subscription.subscribeTo', { plan: planName })\n}\n\nconst getButtonSeverity = (\n tier: PricingTierConfig\n): 'primary' | 'secondary' => {\n if (isCurrentPlan(tier.key)) return 'secondary'\n if (tier.key === 'creator') return 'primary'\n return 'secondary'\n}\n\nconst getButtonTextClass = (tier: PricingTierConfig): string =>\n tier.key === 'creator'\n ? 'font-inter text-sm font-bold leading-normal text-base-background'\n : 'font-inter text-sm font-bold leading-normal text-primary-foreground'\n\nconst getPrice = (tier: PricingTierConfig): number =>\n tier.pricing[currentBillingCycle.value]\n\nconst getAnnualTotal = (tier: PricingTierConfig): number =>\n tier.pricing.yearly * 12\n\nconst getCreditsDisplay = (tier: PricingTierConfig): number =>\n tier.pricing.credits * (currentBillingCycle.value === 'yearly' ? 12 : 1)\n\nconst handleSubscribe = wrapWithErrorHandlingAsync(\n async (tierKey: CheckoutTierKey) => {\n if (!isCloud || isLoading.value || isCurrentPlan(tierKey)) return\n\n isLoading.value = true\n loadingTier.value = tierKey\n\n try {\n if (isActiveSubscription.value) {\n // Pass the target tier to create a deep link to subscription update confirmation\n const checkoutTier = getCheckoutTier(tierKey, currentBillingCycle.value)\n const targetPlan = {\n tierKey,\n billingCycle: currentBillingCycle.value\n }\n const downgrade =\n currentPlanDescriptor.value &&\n isPlanDowngrade({\n current: currentPlanDescriptor.value,\n target: targetPlan\n })\n\n if (downgrade) {\n // TODO(COMFY-StripeProration): Remove once backend checkout creation mirrors portal proration (\"change at billing end\")\n await accessBillingPortal()\n } else {\n await accessBillingPortal(checkoutTier)\n }\n } else {\n await performSubscriptionCheckout(tierKey, currentBillingCycle.value)\n }\n } finally {\n isLoading.value = false\n loadingTier.value = null\n }\n },\n reportError\n)\n</script>\n","<template>\n <div class=\"flex flex-col gap-8\">\n <div class=\"flex justify-center\">\n <SelectButton\n v-model=\"currentBillingCycle\"\n :options=\"billingCycleOptions\"\n option-label=\"label\"\n option-value=\"value\"\n :allow-empty=\"false\"\n unstyled\n :pt=\"{\n root: {\n class: 'flex gap-1 bg-secondary-background rounded-lg p-1.5'\n },\n pcToggleButton: {\n root: ({ context }: ToggleButtonPassThroughMethodOptions) => ({\n class: [\n 'w-36 h-8 rounded-md transition-colors cursor-pointer border-none outline-none ring-0 text-sm font-medium flex items-center justify-center',\n context.active\n ? 'bg-base-foreground text-base-background'\n : 'bg-transparent text-muted-foreground hover:bg-secondary-background-hover'\n ]\n }),\n label: { class: 'flex items-center gap-2 ' }\n }\n }\"\n >\n <template #option=\"{ option }\">\n <div class=\"flex items-center gap-2\">\n <span>{{ option.label }}</span>\n <div\n v-if=\"option.value === 'yearly'\"\n class=\"bg-primary-background text-white text-[11px] px-1 py-0.5 rounded-full flex items-center font-bold\"\n >\n -20%\n </div>\n </div>\n </template>\n </SelectButton>\n </div>\n <div class=\"flex flex-col xl:flex-row items-stretch gap-6\">\n <div\n v-for=\"tier in tiers\"\n :key=\"tier.id\"\n :class=\"\n cn(\n 'flex-1 flex flex-col rounded-2xl border border-border-default bg-base-background shadow-[0_0_12px_rgba(0,0,0,0.1)]',\n tier.isPopular ? 'border-muted-foreground' : ''\n )\n \"\n >\n <div class=\"p-8 pb-0 flex flex-col gap-8\">\n <div class=\"flex flex-row items-center gap-2 justify-between\">\n <span\n class=\"font-inter text-base font-bold leading-normal text-base-foreground\"\n >\n {{ tier.name }}\n </span>\n <div\n v-if=\"tier.isPopular\"\n class=\"rounded-full bg-base-foreground px-1.5 text-[11px] font-bold uppercase text-base-background h-5 tracking-tight flex items-center\"\n >\n {{ t('subscription.mostPopular') }}\n </div>\n </div>\n <div class=\"flex flex-col\">\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-baseline gap-2\">\n <span\n class=\"font-inter text-[32px] font-semibold leading-normal text-base-foreground\"\n >\n <span\n v-show=\"currentBillingCycle === 'yearly'\"\n class=\"line-through text-2xl text-muted-foreground\"\n >\n ${{ tier.pricing.monthly }}\n </span>\n ${{ getPrice(tier) }}\n </span>\n <span\n class=\"font-inter text-xl leading-normal text-base-foreground\"\n >\n {{ t('subscription.usdPerMonth') }}\n </span>\n </div>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-sm text-muted-foreground\">\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.billedYearly', {\n total: `$${getAnnualTotal(tier)}`\n })\n : t('subscription.billedMonthly')\n }}\n </span>\n </div>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-4 pb-0 flex-1\">\n <div class=\"flex flex-row items-center justify-between\">\n <span\n class=\"font-inter text-sm font-normal leading-normal text-foreground\"\n >\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.yearlyCreditsLabel')\n : t('subscription.monthlyCreditsLabel')\n }}\n </span>\n <div class=\"flex flex-row items-center gap-1\">\n <i class=\"icon-[lucide--component] text-amber-400 text-sm\" />\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ n(getCreditsDisplay(tier)) }}\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.maxDurationLabel') }}\n </span>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ tier.maxDuration }}\n </span>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.gpuLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.addCreditsLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.customLoRAsLabel') }}\n </span>\n <i\n v-if=\"tier.customLoRAs\"\n class=\"pi pi-check text-xs text-success-foreground\"\n />\n <i v-else class=\"pi pi-times text-xs text-foreground\" />\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-start justify-between\">\n <div class=\"flex flex-col gap-2\">\n <span\n class=\"text-sm font-normal text-foreground leading-relaxed\"\n >\n {{ t('subscription.videoEstimateLabel') }}\n </span>\n <div class=\"flex flex-row items-center gap-2 group pt-2\">\n <i\n class=\"pi pi-question-circle text-xs text-muted-foreground group-hover:text-base-foreground\"\n />\n <span\n class=\"text-sm font-normal text-muted-foreground cursor-pointer group-hover:text-base-foreground\"\n @click=\"togglePopover\"\n >\n {{ t('subscription.videoEstimateHelp') }}\n </span>\n </div>\n </div>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n ~{{ n(tier.pricing.videoEstimate) }}\n </span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex flex-col p-8\">\n <Button\n :variant=\"getButtonSeverity(tier)\"\n :disabled=\"isLoading || isCurrentPlan(tier.key)\"\n :loading=\"loadingTier === tier.key\"\n :class=\"\n cn(\n 'h-10 w-full',\n getButtonTextClass(tier),\n tier.key === 'creator'\n ? 'bg-base-foreground border-transparent hover:bg-inverted-background-hover'\n : 'bg-secondary-background border-transparent hover:bg-secondary-background-hover focus:bg-secondary-background-selected'\n )\n \"\n @click=\"() => handleSubscribe(tier.key)\"\n >\n {{ getButtonLabel(tier) }}\n </Button>\n </div>\n </div>\n </div>\n\n <!-- Video Estimate Help Popover -->\n <Popover\n ref=\"popover\"\n append-to=\"body\"\n :auto-z-index=\"true\"\n :base-z-index=\"1000\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: {\n class:\n 'rounded-lg border border-interface-stroke bg-interface-panel-surface shadow-lg p-4 max-w-xs'\n }\n }\"\n >\n <div class=\"flex flex-col gap-2\">\n <p class=\"text-sm text-base-foreground leading-normal\">\n {{ t('subscription.videoEstimateExplanation') }}\n </p>\n <a\n href=\"https://cloud.comfy.org/?template=video_wan2_2_14B_fun_camera\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-sm text-azure-600 hover:text-azure-400 no-underline flex gap-1\"\n >\n <span class=\"underline\">\n {{ t('subscription.videoEstimateTryTemplate') }}\n </span>\n <span class=\"no-underline\" v-html=\"'&rarr;'\"></span>\n </a>\n </div>\n </Popover>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { cn } from '@comfyorg/tailwind-utils'\nimport Popover from 'primevue/popover'\nimport SelectButton from 'primevue/selectbutton'\nimport type { ToggleButtonPassThroughMethodOptions } from 'primevue/togglebutton'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { t } from '@/i18n'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport {\n TIER_PRICING,\n TIER_TO_KEY\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport type {\n TierKey,\n TierPricing\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport { performSubscriptionCheckout } from '@/platform/cloud/subscription/utils/subscriptionCheckoutUtil'\nimport { isPlanDowngrade } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport { isCloud } from '@/platform/distribution/types'\nimport type { components } from '@/types/comfyRegistryTypes'\n\ntype SubscriptionTier = components['schemas']['SubscriptionTier']\ntype CheckoutTierKey = Exclude<TierKey, 'founder'>\ntype CheckoutTier = CheckoutTierKey | `${CheckoutTierKey}-yearly`\n\nconst getCheckoutTier = (\n tierKey: CheckoutTierKey,\n billingCycle: BillingCycle\n): CheckoutTier => (billingCycle === 'yearly' ? `${tierKey}-yearly` : tierKey)\n\ninterface BillingCycleOption {\n label: string\n value: BillingCycle\n}\n\ninterface PricingTierConfig {\n id: SubscriptionTier\n key: CheckoutTierKey\n name: string\n pricing: TierPricing\n maxDuration: string\n customLoRAs: boolean\n isPopular?: boolean\n}\n\nconst billingCycleOptions: BillingCycleOption[] = [\n { label: t('subscription.yearly'), value: 'yearly' },\n { label: t('subscription.monthly'), value: 'monthly' }\n]\n\nconst tiers: PricingTierConfig[] = [\n {\n id: 'STANDARD',\n key: 'standard',\n name: t('subscription.tiers.standard.name'),\n pricing: TIER_PRICING.standard,\n maxDuration: t('subscription.maxDuration.standard'),\n customLoRAs: false,\n isPopular: false\n },\n {\n id: 'CREATOR',\n key: 'creator',\n name: t('subscription.tiers.creator.name'),\n pricing: TIER_PRICING.creator,\n maxDuration: t('subscription.maxDuration.creator'),\n customLoRAs: true,\n isPopular: true\n },\n {\n id: 'PRO',\n key: 'pro',\n name: t('subscription.tiers.pro.name'),\n pricing: TIER_PRICING.pro,\n maxDuration: t('subscription.maxDuration.pro'),\n customLoRAs: true,\n isPopular: false\n }\n]\n\nconst { n } = useI18n()\nconst { isActiveSubscription, subscriptionTier, isYearlySubscription } =\n useSubscription()\nconst { accessBillingPortal, reportError } = useFirebaseAuthActions()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\n\nconst isLoading = ref(false)\nconst loadingTier = ref<CheckoutTierKey | null>(null)\nconst popover = ref()\nconst currentBillingCycle = ref<BillingCycle>('yearly')\n\nconst currentTierKey = computed<TierKey | null>(() =>\n subscriptionTier.value ? TIER_TO_KEY[subscriptionTier.value] : null\n)\n\nconst currentPlanDescriptor = computed(() => {\n if (!currentTierKey.value) return null\n\n return {\n tierKey: currentTierKey.value,\n billingCycle: isYearlySubscription.value ? 'yearly' : 'monthly'\n } as const\n})\n\nconst isCurrentPlan = (tierKey: CheckoutTierKey): boolean => {\n if (!currentTierKey.value) return false\n\n const selectedIsYearly = currentBillingCycle.value === 'yearly'\n\n return (\n currentTierKey.value === tierKey &&\n isYearlySubscription.value === selectedIsYearly\n )\n}\n\nconst togglePopover = (event: Event) => {\n popover.value.toggle(event)\n}\n\nconst getButtonLabel = (tier: PricingTierConfig): string => {\n if (isCurrentPlan(tier.key)) return t('subscription.currentPlan')\n\n const planName =\n currentBillingCycle.value === 'yearly'\n ? t('subscription.tierNameYearly', { name: tier.name })\n : tier.name\n\n return isActiveSubscription.value\n ? t('subscription.changeTo', { plan: planName })\n : t('subscription.subscribeTo', { plan: planName })\n}\n\nconst getButtonSeverity = (\n tier: PricingTierConfig\n): 'primary' | 'secondary' => {\n if (isCurrentPlan(tier.key)) return 'secondary'\n if (tier.key === 'creator') return 'primary'\n return 'secondary'\n}\n\nconst getButtonTextClass = (tier: PricingTierConfig): string =>\n tier.key === 'creator'\n ? 'font-inter text-sm font-bold leading-normal text-base-background'\n : 'font-inter text-sm font-bold leading-normal text-primary-foreground'\n\nconst getPrice = (tier: PricingTierConfig): number =>\n tier.pricing[currentBillingCycle.value]\n\nconst getAnnualTotal = (tier: PricingTierConfig): number =>\n tier.pricing.yearly * 12\n\nconst getCreditsDisplay = (tier: PricingTierConfig): number =>\n tier.pricing.credits * (currentBillingCycle.value === 'yearly' ? 12 : 1)\n\nconst handleSubscribe = wrapWithErrorHandlingAsync(\n async (tierKey: CheckoutTierKey) => {\n if (!isCloud || isLoading.value || isCurrentPlan(tierKey)) return\n\n isLoading.value = true\n loadingTier.value = tierKey\n\n try {\n if (isActiveSubscription.value) {\n // Pass the target tier to create a deep link to subscription update confirmation\n const checkoutTier = getCheckoutTier(tierKey, currentBillingCycle.value)\n const targetPlan = {\n tierKey,\n billingCycle: currentBillingCycle.value\n }\n const downgrade =\n currentPlanDescriptor.value &&\n isPlanDowngrade({\n current: currentPlanDescriptor.value,\n target: targetPlan\n })\n\n if (downgrade) {\n // TODO(COMFY-StripeProration): Remove once backend checkout creation mirrors portal proration (\"change at billing end\")\n await accessBillingPortal()\n } else {\n await accessBillingPortal(checkoutTier)\n }\n } else {\n await performSubscriptionCheckout(tierKey, currentBillingCycle.value)\n }\n } finally {\n isLoading.value = false\n loadingTier.value = null\n }\n },\n reportError\n)\n</script>\n","<template>\n <div class=\"flex flex-col items-start gap-0 self-stretch\">\n <div class=\"flex items-center gap-2 py-2\">\n <i class=\"pi pi-check text-xs text-text-primary\" />\n <span class=\"text-sm text-text-primary\">\n {{ $t('subscription.benefits.benefit1') }}\n </span>\n </div>\n\n <div class=\"flex items-center gap-2 py-2\">\n <i class=\"pi pi-check text-xs text-text-primary\" />\n <span class=\"text-sm text-text-primary\">\n {{ $t('subscription.benefits.benefit2') }}\n </span>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\"></script>\n","<template>\n <div\n v-if=\"showCustomPricingTable\"\n class=\"relative flex flex-col p-4 pt-8 md:p-16 !overflow-y-auto h-full gap-8\"\n >\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full shrink-0 text-text-secondary hover:bg-white/10 absolute right-2.5 top-2.5\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times text-xl\" />\n </Button>\n <div class=\"text-center\">\n <h2 class=\"text-xl lg:text-2xl text-muted-foreground m-0\">\n {{ $t('subscription.description') }}\n </h2>\n </div>\n\n <PricingTable class=\"flex-1\" />\n\n <!-- Contact and Enterprise Links -->\n <div class=\"flex flex-col items-center gap-2\">\n <p class=\"text-sm text-text-secondary m-0\">\n {{ $t('subscription.haveQuestions') }}\n </p>\n <div class=\"flex items-center gap-1.5\">\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleContactUs\"\n >\n {{ $t('subscription.contactUs') }}\n <i class=\"pi pi-comments\" />\n </Button>\n <span class=\"text-sm text-text-secondary\">{{ $t('g.or') }}</span>\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleViewEnterprise\"\n >\n {{ $t('subscription.viewEnterprise') }}\n <i class=\"pi pi-external-link\" />\n </Button>\n </div>\n </div>\n </div>\n <div v-else class=\"legacy-dialog relative grid h-full grid-cols-5\">\n <!-- Custom close button -->\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full absolute top-2.5 right-2.5 z-10 h-8 w-8 p-0 text-white hover:bg-white/20\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n\n <div\n class=\"relative col-span-2 flex items-center justify-center overflow-hidden rounded-sm\"\n >\n <video\n autoplay\n loop\n muted\n playsinline\n class=\"h-full min-w-[125%] object-cover p-0\"\n style=\"margin-left: -20%\"\n >\n <source\n src=\"/assets/images/cloud-subscription.webm\"\n type=\"video/webm\"\n />\n </video>\n </div>\n\n <div class=\"col-span-3 flex flex-col justify-between p-8\">\n <div>\n <div class=\"flex flex-col gap-6\">\n <div class=\"inline-flex items-center gap-2\">\n <div class=\"text-sm text-text-primary\">\n {{ $t('subscription.required.title') }}\n </div>\n <CloudBadge\n reverse-order\n no-padding\n background-color=\"var(--p-dialog-background)\"\n use-subscription\n />\n </div>\n\n <div class=\"flex items-baseline gap-2\">\n <span class=\"text-4xl font-bold\">{{ formattedMonthlyPrice }}</span>\n <span class=\"text-xl\">{{ $t('subscription.perMonth') }}</span>\n </div>\n </div>\n\n <SubscriptionBenefits class=\"mt-6 text-muted\" />\n </div>\n\n <div class=\"flex flex-col pt-8\">\n <SubscribeButton\n class=\"py-2 px-4 rounded-lg\"\n :pt=\"{\n root: {\n style: 'background: var(--color-accent-blue, #0B8CE9);'\n },\n label: {\n class: 'font-inter font-[700] text-sm'\n }\n }\"\n @subscribed=\"handleSubscribed\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, watch } from 'vue'\n\nimport CloudBadge from '@/components/topbar/CloudBadge.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'\nimport PricingTable from '@/platform/cloud/subscription/components/PricingTable.vue'\nimport SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'\nimport SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst props = defineProps<{\n onClose: () => void\n}>()\n\nconst emit = defineEmits<{\n close: [subscribed: boolean]\n}>()\n\nconst { fetchStatus, isActiveSubscription, isSubscriptionEnabled } =\n useSubscription()\n\n// Legacy price for non-tier flow with locale-aware formatting\nconst formattedMonthlyPrice = new Intl.NumberFormat(\n navigator.language || 'en-US',\n {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n }\n).format(MONTHLY_SUBSCRIPTION_PRICE)\nconst commandStore = useCommandStore()\nconst telemetry = useTelemetry()\n\n// Always show custom pricing table for cloud subscriptions\nconst showCustomPricingTable = computed(() => isSubscriptionEnabled())\n\nconst POLL_INTERVAL_MS = 3000\nconst MAX_POLL_ATTEMPTS = 3\nlet pollInterval: number | null = null\nlet pollAttempts = 0\n\nconst stopPolling = () => {\n if (pollInterval) {\n clearInterval(pollInterval)\n pollInterval = null\n }\n}\n\nconst startPolling = () => {\n stopPolling()\n pollAttempts = 0\n\n const poll = async () => {\n try {\n await fetchStatus()\n pollAttempts++\n\n if (pollAttempts >= MAX_POLL_ATTEMPTS) {\n stopPolling()\n }\n } catch (error) {\n console.error(\n '[SubscriptionDialog] Failed to poll subscription status',\n error\n )\n stopPolling()\n }\n }\n\n void poll()\n pollInterval = window.setInterval(() => {\n void poll()\n }, POLL_INTERVAL_MS)\n}\n\nconst handleWindowFocus = () => {\n if (showCustomPricingTable.value) {\n startPolling()\n }\n}\n\nwatch(\n showCustomPricingTable,\n (enabled) => {\n if (enabled) {\n window.addEventListener('focus', handleWindowFocus)\n } else {\n window.removeEventListener('focus', handleWindowFocus)\n stopPolling()\n }\n },\n { immediate: true }\n)\n\nwatch(\n () => isActiveSubscription.value,\n (isActive) => {\n if (isActive && showCustomPricingTable.value) {\n emit('close', true)\n }\n }\n)\n\nconst handleSubscribed = () => {\n emit('close', true)\n}\n\nconst handleClose = () => {\n stopPolling()\n props.onClose()\n}\n\nconst handleContactUs = async () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'help_feedback',\n is_external: true,\n source: 'subscription'\n })\n await commandStore.execute('Comfy.ContactSupport')\n}\n\nconst handleViewEnterprise = () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'docs',\n is_external: true,\n source: 'subscription'\n })\n window.open('https://www.comfy.org/cloud/enterprise', '_blank')\n}\n\nonBeforeUnmount(() => {\n stopPolling()\n window.removeEventListener('focus', handleWindowFocus)\n})\n</script>\n\n<style scoped>\n.legacy-dialog :deep(.bg-comfy-menu-secondary) {\n background-color: transparent;\n}\n\n.legacy-dialog :deep(.p-button) {\n color: white;\n}\n</style>\n","<template>\n <div\n v-if=\"showCustomPricingTable\"\n class=\"relative flex flex-col p-4 pt-8 md:p-16 !overflow-y-auto h-full gap-8\"\n >\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full shrink-0 text-text-secondary hover:bg-white/10 absolute right-2.5 top-2.5\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times text-xl\" />\n </Button>\n <div class=\"text-center\">\n <h2 class=\"text-xl lg:text-2xl text-muted-foreground m-0\">\n {{ $t('subscription.description') }}\n </h2>\n </div>\n\n <PricingTable class=\"flex-1\" />\n\n <!-- Contact and Enterprise Links -->\n <div class=\"flex flex-col items-center gap-2\">\n <p class=\"text-sm text-text-secondary m-0\">\n {{ $t('subscription.haveQuestions') }}\n </p>\n <div class=\"flex items-center gap-1.5\">\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleContactUs\"\n >\n {{ $t('subscription.contactUs') }}\n <i class=\"pi pi-comments\" />\n </Button>\n <span class=\"text-sm text-text-secondary\">{{ $t('g.or') }}</span>\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleViewEnterprise\"\n >\n {{ $t('subscription.viewEnterprise') }}\n <i class=\"pi pi-external-link\" />\n </Button>\n </div>\n </div>\n </div>\n <div v-else class=\"legacy-dialog relative grid h-full grid-cols-5\">\n <!-- Custom close button -->\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full absolute top-2.5 right-2.5 z-10 h-8 w-8 p-0 text-white hover:bg-white/20\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n\n <div\n class=\"relative col-span-2 flex items-center justify-center overflow-hidden rounded-sm\"\n >\n <video\n autoplay\n loop\n muted\n playsinline\n class=\"h-full min-w-[125%] object-cover p-0\"\n style=\"margin-left: -20%\"\n >\n <source\n src=\"/assets/images/cloud-subscription.webm\"\n type=\"video/webm\"\n />\n </video>\n </div>\n\n <div class=\"col-span-3 flex flex-col justify-between p-8\">\n <div>\n <div class=\"flex flex-col gap-6\">\n <div class=\"inline-flex items-center gap-2\">\n <div class=\"text-sm text-text-primary\">\n {{ $t('subscription.required.title') }}\n </div>\n <CloudBadge\n reverse-order\n no-padding\n background-color=\"var(--p-dialog-background)\"\n use-subscription\n />\n </div>\n\n <div class=\"flex items-baseline gap-2\">\n <span class=\"text-4xl font-bold\">{{ formattedMonthlyPrice }}</span>\n <span class=\"text-xl\">{{ $t('subscription.perMonth') }}</span>\n </div>\n </div>\n\n <SubscriptionBenefits class=\"mt-6 text-muted\" />\n </div>\n\n <div class=\"flex flex-col pt-8\">\n <SubscribeButton\n class=\"py-2 px-4 rounded-lg\"\n :pt=\"{\n root: {\n style: 'background: var(--color-accent-blue, #0B8CE9);'\n },\n label: {\n class: 'font-inter font-[700] text-sm'\n }\n }\"\n @subscribed=\"handleSubscribed\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, watch } from 'vue'\n\nimport CloudBadge from '@/components/topbar/CloudBadge.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'\nimport PricingTable from '@/platform/cloud/subscription/components/PricingTable.vue'\nimport SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'\nimport SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst props = defineProps<{\n onClose: () => void\n}>()\n\nconst emit = defineEmits<{\n close: [subscribed: boolean]\n}>()\n\nconst { fetchStatus, isActiveSubscription, isSubscriptionEnabled } =\n useSubscription()\n\n// Legacy price for non-tier flow with locale-aware formatting\nconst formattedMonthlyPrice = new Intl.NumberFormat(\n navigator.language || 'en-US',\n {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n }\n).format(MONTHLY_SUBSCRIPTION_PRICE)\nconst commandStore = useCommandStore()\nconst telemetry = useTelemetry()\n\n// Always show custom pricing table for cloud subscriptions\nconst showCustomPricingTable = computed(() => isSubscriptionEnabled())\n\nconst POLL_INTERVAL_MS = 3000\nconst MAX_POLL_ATTEMPTS = 3\nlet pollInterval: number | null = null\nlet pollAttempts = 0\n\nconst stopPolling = () => {\n if (pollInterval) {\n clearInterval(pollInterval)\n pollInterval = null\n }\n}\n\nconst startPolling = () => {\n stopPolling()\n pollAttempts = 0\n\n const poll = async () => {\n try {\n await fetchStatus()\n pollAttempts++\n\n if (pollAttempts >= MAX_POLL_ATTEMPTS) {\n stopPolling()\n }\n } catch (error) {\n console.error(\n '[SubscriptionDialog] Failed to poll subscription status',\n error\n )\n stopPolling()\n }\n }\n\n void poll()\n pollInterval = window.setInterval(() => {\n void poll()\n }, POLL_INTERVAL_MS)\n}\n\nconst handleWindowFocus = () => {\n if (showCustomPricingTable.value) {\n startPolling()\n }\n}\n\nwatch(\n showCustomPricingTable,\n (enabled) => {\n if (enabled) {\n window.addEventListener('focus', handleWindowFocus)\n } else {\n window.removeEventListener('focus', handleWindowFocus)\n stopPolling()\n }\n },\n { immediate: true }\n)\n\nwatch(\n () => isActiveSubscription.value,\n (isActive) => {\n if (isActive && showCustomPricingTable.value) {\n emit('close', true)\n }\n }\n)\n\nconst handleSubscribed = () => {\n emit('close', true)\n}\n\nconst handleClose = () => {\n stopPolling()\n props.onClose()\n}\n\nconst handleContactUs = async () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'help_feedback',\n is_external: true,\n source: 'subscription'\n })\n await commandStore.execute('Comfy.ContactSupport')\n}\n\nconst handleViewEnterprise = () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'docs',\n is_external: true,\n source: 'subscription'\n })\n window.open('https://www.comfy.org/cloud/enterprise', '_blank')\n}\n\nonBeforeUnmount(() => {\n stopPolling()\n window.removeEventListener('focus', handleWindowFocus)\n})\n</script>\n\n<style scoped>\n.legacy-dialog :deep(.bg-comfy-menu-secondary) {\n background-color: transparent;\n}\n\n.legacy-dialog :deep(.p-button) {\n color: white;\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"SubscriptionRequiredDialogContent-COEF2VQ_.js","names":[],"sources":["../../../../../../../assets/images/cloud-subscription.webm","../../src/config/subscriptionPricesConfig.ts","../../src/platform/cloud/subscription/utils/subscriptionTierRank.ts","../../src/platform/cloud/subscription/components/PricingTable.vue","../../src/platform/cloud/subscription/components/PricingTable.vue","../../src/platform/cloud/subscription/components/SubscriptionBenefits.vue","../../src/platform/cloud/subscription/components/SubscriptionRequiredDialogContent.vue","../../src/platform/cloud/subscription/components/SubscriptionRequiredDialogContent.vue"],"sourcesContent":["export default \"__VITE_PUBLIC_ASSET__b4a125b1__\"","export const MONTHLY_SUBSCRIPTION_PRICE = 20\n","import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing'\n\nexport type BillingCycle = 'monthly' | 'yearly'\n\ntype RankedTierKey = Exclude<TierKey, 'founder'>\ntype RankedPlanKey = `${BillingCycle}-${RankedTierKey}`\n\ninterface PlanDescriptor {\n tierKey: TierKey\n billingCycle: BillingCycle\n}\n\nconst PLAN_ORDER: RankedPlanKey[] = [\n 'yearly-pro',\n 'yearly-creator',\n 'yearly-standard',\n 'monthly-pro',\n 'monthly-creator',\n 'monthly-standard'\n]\n\nconst PLAN_RANK = PLAN_ORDER.reduce<Map<RankedPlanKey, number>>(\n (acc, plan, index) => acc.set(plan, index),\n new Map()\n)\n\nconst toRankedPlanKey = (\n tierKey: TierKey,\n billingCycle: BillingCycle\n): RankedPlanKey | null => {\n if (tierKey === 'founder') return null\n return `${billingCycle}-${tierKey}` as RankedPlanKey\n}\n\nexport const getPlanRank = ({\n tierKey,\n billingCycle\n}: PlanDescriptor): number => {\n const planKey = toRankedPlanKey(tierKey, billingCycle)\n if (!planKey) return Number.POSITIVE_INFINITY\n\n return PLAN_RANK.get(planKey) ?? Number.POSITIVE_INFINITY\n}\n\ninterface DowngradeCheckParams {\n current: PlanDescriptor\n target: PlanDescriptor\n}\n\nexport const isPlanDowngrade = ({\n current,\n target\n}: DowngradeCheckParams): boolean => {\n const currentRank = getPlanRank(current)\n const targetRank = getPlanRank(target)\n\n return targetRank > currentRank\n}\n","<template>\n <div class=\"flex flex-col gap-8\">\n <div class=\"flex justify-center\">\n <SelectButton\n v-model=\"currentBillingCycle\"\n :options=\"billingCycleOptions\"\n option-label=\"label\"\n option-value=\"value\"\n :allow-empty=\"false\"\n unstyled\n :pt=\"{\n root: {\n class: 'flex gap-1 bg-secondary-background rounded-lg p-1.5'\n },\n pcToggleButton: {\n root: ({ context }: ToggleButtonPassThroughMethodOptions) => ({\n class: [\n 'w-36 h-8 rounded-md transition-colors cursor-pointer border-none outline-none ring-0 text-sm font-medium flex items-center justify-center',\n context.active\n ? 'bg-base-foreground text-base-background'\n : 'bg-transparent text-muted-foreground hover:bg-secondary-background-hover'\n ]\n }),\n label: { class: 'flex items-center gap-2 ' }\n }\n }\"\n >\n <template #option=\"{ option }\">\n <div class=\"flex items-center gap-2\">\n <span>{{ option.label }}</span>\n <div\n v-if=\"option.value === 'yearly'\"\n class=\"bg-primary-background text-white text-[11px] px-1 py-0.5 rounded-full flex items-center font-bold\"\n >\n -20%\n </div>\n </div>\n </template>\n </SelectButton>\n </div>\n <div class=\"flex flex-col xl:flex-row items-stretch gap-6\">\n <div\n v-for=\"tier in tiers\"\n :key=\"tier.id\"\n :class=\"\n cn(\n 'flex-1 flex flex-col rounded-2xl border border-border-default bg-base-background shadow-[0_0_12px_rgba(0,0,0,0.1)]',\n tier.isPopular ? 'border-muted-foreground' : ''\n )\n \"\n >\n <div class=\"p-8 pb-0 flex flex-col gap-8\">\n <div class=\"flex flex-row items-center gap-2 justify-between\">\n <span\n class=\"font-inter text-base font-bold leading-normal text-base-foreground\"\n >\n {{ tier.name }}\n </span>\n <div\n v-if=\"tier.isPopular\"\n class=\"rounded-full bg-base-foreground px-1.5 text-[11px] font-bold uppercase text-base-background h-5 tracking-tight flex items-center\"\n >\n {{ t('subscription.mostPopular') }}\n </div>\n </div>\n <div class=\"flex flex-col\">\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-baseline gap-2\">\n <span\n class=\"font-inter text-[32px] font-semibold leading-normal text-base-foreground\"\n >\n <span\n v-show=\"currentBillingCycle === 'yearly'\"\n class=\"line-through text-2xl text-muted-foreground\"\n >\n ${{ tier.pricing.monthly }}\n </span>\n ${{ getPrice(tier) }}\n </span>\n <span\n class=\"font-inter text-xl leading-normal text-base-foreground\"\n >\n {{ t('subscription.usdPerMonth') }}\n </span>\n </div>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-sm text-muted-foreground\">\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.billedYearly', {\n total: `$${getAnnualTotal(tier)}`\n })\n : t('subscription.billedMonthly')\n }}\n </span>\n </div>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-4 pb-0 flex-1\">\n <div class=\"flex flex-row items-center justify-between\">\n <span\n class=\"font-inter text-sm font-normal leading-normal text-foreground\"\n >\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.yearlyCreditsLabel')\n : t('subscription.monthlyCreditsLabel')\n }}\n </span>\n <div class=\"flex flex-row items-center gap-1\">\n <i class=\"icon-[lucide--component] text-amber-400 text-sm\" />\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ n(getCreditsDisplay(tier)) }}\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.maxDurationLabel') }}\n </span>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ tier.maxDuration }}\n </span>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.gpuLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.addCreditsLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.customLoRAsLabel') }}\n </span>\n <i\n v-if=\"tier.customLoRAs\"\n class=\"pi pi-check text-xs text-success-foreground\"\n />\n <i v-else class=\"pi pi-times text-xs text-foreground\" />\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-start justify-between\">\n <div class=\"flex flex-col gap-2\">\n <span\n class=\"text-sm font-normal text-foreground leading-relaxed\"\n >\n {{ t('subscription.videoEstimateLabel') }}\n </span>\n <div class=\"flex flex-row items-center gap-2 group pt-2\">\n <i\n class=\"pi pi-question-circle text-xs text-muted-foreground group-hover:text-base-foreground\"\n />\n <span\n class=\"text-sm font-normal text-muted-foreground cursor-pointer group-hover:text-base-foreground\"\n @click=\"togglePopover\"\n >\n {{ t('subscription.videoEstimateHelp') }}\n </span>\n </div>\n </div>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n ~{{ n(tier.pricing.videoEstimate) }}\n </span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex flex-col p-8\">\n <Button\n :variant=\"getButtonSeverity(tier)\"\n :disabled=\"isLoading || isCurrentPlan(tier.key)\"\n :loading=\"loadingTier === tier.key\"\n :class=\"\n cn(\n 'h-10 w-full',\n getButtonTextClass(tier),\n tier.key === 'creator'\n ? 'bg-base-foreground border-transparent hover:bg-inverted-background-hover'\n : 'bg-secondary-background border-transparent hover:bg-secondary-background-hover focus:bg-secondary-background-selected'\n )\n \"\n @click=\"() => handleSubscribe(tier.key)\"\n >\n {{ getButtonLabel(tier) }}\n </Button>\n </div>\n </div>\n </div>\n\n <!-- Video Estimate Help Popover -->\n <Popover\n ref=\"popover\"\n append-to=\"body\"\n :auto-z-index=\"true\"\n :base-z-index=\"1000\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: {\n class:\n 'rounded-lg border border-interface-stroke bg-interface-panel-surface shadow-lg p-4 max-w-xs'\n }\n }\"\n >\n <div class=\"flex flex-col gap-2\">\n <p class=\"text-sm text-base-foreground leading-normal\">\n {{ t('subscription.videoEstimateExplanation') }}\n </p>\n <a\n href=\"https://cloud.comfy.org/?template=video_wan2_2_14B_fun_camera\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-sm text-azure-600 hover:text-azure-400 no-underline flex gap-1\"\n >\n <span class=\"underline\">\n {{ t('subscription.videoEstimateTryTemplate') }}\n </span>\n <span class=\"no-underline\" v-html=\"'&rarr;'\"></span>\n </a>\n </div>\n </Popover>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { cn } from '@comfyorg/tailwind-utils'\nimport Popover from 'primevue/popover'\nimport SelectButton from 'primevue/selectbutton'\nimport type { ToggleButtonPassThroughMethodOptions } from 'primevue/togglebutton'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { t } from '@/i18n'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport {\n TIER_PRICING,\n TIER_TO_KEY\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport type {\n TierKey,\n TierPricing\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport { performSubscriptionCheckout } from '@/platform/cloud/subscription/utils/subscriptionCheckoutUtil'\nimport { isPlanDowngrade } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport { isCloud } from '@/platform/distribution/types'\nimport type { components } from '@/types/comfyRegistryTypes'\n\ntype SubscriptionTier = components['schemas']['SubscriptionTier']\ntype CheckoutTierKey = Exclude<TierKey, 'founder'>\ntype CheckoutTier = CheckoutTierKey | `${CheckoutTierKey}-yearly`\n\nconst getCheckoutTier = (\n tierKey: CheckoutTierKey,\n billingCycle: BillingCycle\n): CheckoutTier => (billingCycle === 'yearly' ? `${tierKey}-yearly` : tierKey)\n\ninterface BillingCycleOption {\n label: string\n value: BillingCycle\n}\n\ninterface PricingTierConfig {\n id: SubscriptionTier\n key: CheckoutTierKey\n name: string\n pricing: TierPricing\n maxDuration: string\n customLoRAs: boolean\n isPopular?: boolean\n}\n\nconst billingCycleOptions: BillingCycleOption[] = [\n { label: t('subscription.yearly'), value: 'yearly' },\n { label: t('subscription.monthly'), value: 'monthly' }\n]\n\nconst tiers: PricingTierConfig[] = [\n {\n id: 'STANDARD',\n key: 'standard',\n name: t('subscription.tiers.standard.name'),\n pricing: TIER_PRICING.standard,\n maxDuration: t('subscription.maxDuration.standard'),\n customLoRAs: false,\n isPopular: false\n },\n {\n id: 'CREATOR',\n key: 'creator',\n name: t('subscription.tiers.creator.name'),\n pricing: TIER_PRICING.creator,\n maxDuration: t('subscription.maxDuration.creator'),\n customLoRAs: true,\n isPopular: true\n },\n {\n id: 'PRO',\n key: 'pro',\n name: t('subscription.tiers.pro.name'),\n pricing: TIER_PRICING.pro,\n maxDuration: t('subscription.maxDuration.pro'),\n customLoRAs: true,\n isPopular: false\n }\n]\n\nconst { n } = useI18n()\nconst { isActiveSubscription, subscriptionTier, isYearlySubscription } =\n useSubscription()\nconst { accessBillingPortal, reportError } = useFirebaseAuthActions()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\n\nconst isLoading = ref(false)\nconst loadingTier = ref<CheckoutTierKey | null>(null)\nconst popover = ref()\nconst currentBillingCycle = ref<BillingCycle>('yearly')\n\nconst currentTierKey = computed<TierKey | null>(() =>\n subscriptionTier.value ? TIER_TO_KEY[subscriptionTier.value] : null\n)\n\nconst currentPlanDescriptor = computed(() => {\n if (!currentTierKey.value) return null\n\n return {\n tierKey: currentTierKey.value,\n billingCycle: isYearlySubscription.value ? 'yearly' : 'monthly'\n } as const\n})\n\nconst isCurrentPlan = (tierKey: CheckoutTierKey): boolean => {\n if (!currentTierKey.value) return false\n\n const selectedIsYearly = currentBillingCycle.value === 'yearly'\n\n return (\n currentTierKey.value === tierKey &&\n isYearlySubscription.value === selectedIsYearly\n )\n}\n\nconst togglePopover = (event: Event) => {\n popover.value.toggle(event)\n}\n\nconst getButtonLabel = (tier: PricingTierConfig): string => {\n if (isCurrentPlan(tier.key)) return t('subscription.currentPlan')\n\n const planName =\n currentBillingCycle.value === 'yearly'\n ? t('subscription.tierNameYearly', { name: tier.name })\n : tier.name\n\n return isActiveSubscription.value\n ? t('subscription.changeTo', { plan: planName })\n : t('subscription.subscribeTo', { plan: planName })\n}\n\nconst getButtonSeverity = (\n tier: PricingTierConfig\n): 'primary' | 'secondary' => {\n if (isCurrentPlan(tier.key)) return 'secondary'\n if (tier.key === 'creator') return 'primary'\n return 'secondary'\n}\n\nconst getButtonTextClass = (tier: PricingTierConfig): string =>\n tier.key === 'creator'\n ? 'font-inter text-sm font-bold leading-normal text-base-background'\n : 'font-inter text-sm font-bold leading-normal text-primary-foreground'\n\nconst getPrice = (tier: PricingTierConfig): number =>\n tier.pricing[currentBillingCycle.value]\n\nconst getAnnualTotal = (tier: PricingTierConfig): number =>\n tier.pricing.yearly * 12\n\nconst getCreditsDisplay = (tier: PricingTierConfig): number =>\n tier.pricing.credits * (currentBillingCycle.value === 'yearly' ? 12 : 1)\n\nconst handleSubscribe = wrapWithErrorHandlingAsync(\n async (tierKey: CheckoutTierKey) => {\n if (!isCloud || isLoading.value || isCurrentPlan(tierKey)) return\n\n isLoading.value = true\n loadingTier.value = tierKey\n\n try {\n if (isActiveSubscription.value) {\n // Pass the target tier to create a deep link to subscription update confirmation\n const checkoutTier = getCheckoutTier(tierKey, currentBillingCycle.value)\n const targetPlan = {\n tierKey,\n billingCycle: currentBillingCycle.value\n }\n const downgrade =\n currentPlanDescriptor.value &&\n isPlanDowngrade({\n current: currentPlanDescriptor.value,\n target: targetPlan\n })\n\n if (downgrade) {\n // TODO(COMFY-StripeProration): Remove once backend checkout creation mirrors portal proration (\"change at billing end\")\n await accessBillingPortal()\n } else {\n await accessBillingPortal(checkoutTier)\n }\n } else {\n await performSubscriptionCheckout(tierKey, currentBillingCycle.value)\n }\n } finally {\n isLoading.value = false\n loadingTier.value = null\n }\n },\n reportError\n)\n</script>\n","<template>\n <div class=\"flex flex-col gap-8\">\n <div class=\"flex justify-center\">\n <SelectButton\n v-model=\"currentBillingCycle\"\n :options=\"billingCycleOptions\"\n option-label=\"label\"\n option-value=\"value\"\n :allow-empty=\"false\"\n unstyled\n :pt=\"{\n root: {\n class: 'flex gap-1 bg-secondary-background rounded-lg p-1.5'\n },\n pcToggleButton: {\n root: ({ context }: ToggleButtonPassThroughMethodOptions) => ({\n class: [\n 'w-36 h-8 rounded-md transition-colors cursor-pointer border-none outline-none ring-0 text-sm font-medium flex items-center justify-center',\n context.active\n ? 'bg-base-foreground text-base-background'\n : 'bg-transparent text-muted-foreground hover:bg-secondary-background-hover'\n ]\n }),\n label: { class: 'flex items-center gap-2 ' }\n }\n }\"\n >\n <template #option=\"{ option }\">\n <div class=\"flex items-center gap-2\">\n <span>{{ option.label }}</span>\n <div\n v-if=\"option.value === 'yearly'\"\n class=\"bg-primary-background text-white text-[11px] px-1 py-0.5 rounded-full flex items-center font-bold\"\n >\n -20%\n </div>\n </div>\n </template>\n </SelectButton>\n </div>\n <div class=\"flex flex-col xl:flex-row items-stretch gap-6\">\n <div\n v-for=\"tier in tiers\"\n :key=\"tier.id\"\n :class=\"\n cn(\n 'flex-1 flex flex-col rounded-2xl border border-border-default bg-base-background shadow-[0_0_12px_rgba(0,0,0,0.1)]',\n tier.isPopular ? 'border-muted-foreground' : ''\n )\n \"\n >\n <div class=\"p-8 pb-0 flex flex-col gap-8\">\n <div class=\"flex flex-row items-center gap-2 justify-between\">\n <span\n class=\"font-inter text-base font-bold leading-normal text-base-foreground\"\n >\n {{ tier.name }}\n </span>\n <div\n v-if=\"tier.isPopular\"\n class=\"rounded-full bg-base-foreground px-1.5 text-[11px] font-bold uppercase text-base-background h-5 tracking-tight flex items-center\"\n >\n {{ t('subscription.mostPopular') }}\n </div>\n </div>\n <div class=\"flex flex-col\">\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-baseline gap-2\">\n <span\n class=\"font-inter text-[32px] font-semibold leading-normal text-base-foreground\"\n >\n <span\n v-show=\"currentBillingCycle === 'yearly'\"\n class=\"line-through text-2xl text-muted-foreground\"\n >\n ${{ tier.pricing.monthly }}\n </span>\n ${{ getPrice(tier) }}\n </span>\n <span\n class=\"font-inter text-xl leading-normal text-base-foreground\"\n >\n {{ t('subscription.usdPerMonth') }}\n </span>\n </div>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-sm text-muted-foreground\">\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.billedYearly', {\n total: `$${getAnnualTotal(tier)}`\n })\n : t('subscription.billedMonthly')\n }}\n </span>\n </div>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-4 pb-0 flex-1\">\n <div class=\"flex flex-row items-center justify-between\">\n <span\n class=\"font-inter text-sm font-normal leading-normal text-foreground\"\n >\n {{\n currentBillingCycle === 'yearly'\n ? t('subscription.yearlyCreditsLabel')\n : t('subscription.monthlyCreditsLabel')\n }}\n </span>\n <div class=\"flex flex-row items-center gap-1\">\n <i class=\"icon-[lucide--component] text-amber-400 text-sm\" />\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ n(getCreditsDisplay(tier)) }}\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.maxDurationLabel') }}\n </span>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n {{ tier.maxDuration }}\n </span>\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.gpuLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.addCreditsLabel') }}\n </span>\n <i class=\"pi pi-check text-xs text-success-foreground\" />\n </div>\n\n <div class=\"flex flex-row items-center justify-between\">\n <span class=\"text-sm font-normal text-foreground\">\n {{ t('subscription.customLoRAsLabel') }}\n </span>\n <i\n v-if=\"tier.customLoRAs\"\n class=\"pi pi-check text-xs text-success-foreground\"\n />\n <i v-else class=\"pi pi-times text-xs text-foreground\" />\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-row items-start justify-between\">\n <div class=\"flex flex-col gap-2\">\n <span\n class=\"text-sm font-normal text-foreground leading-relaxed\"\n >\n {{ t('subscription.videoEstimateLabel') }}\n </span>\n <div class=\"flex flex-row items-center gap-2 group pt-2\">\n <i\n class=\"pi pi-question-circle text-xs text-muted-foreground group-hover:text-base-foreground\"\n />\n <span\n class=\"text-sm font-normal text-muted-foreground cursor-pointer group-hover:text-base-foreground\"\n @click=\"togglePopover\"\n >\n {{ t('subscription.videoEstimateHelp') }}\n </span>\n </div>\n </div>\n <span\n class=\"font-inter text-sm font-bold leading-normal text-base-foreground\"\n >\n ~{{ n(tier.pricing.videoEstimate) }}\n </span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex flex-col p-8\">\n <Button\n :variant=\"getButtonSeverity(tier)\"\n :disabled=\"isLoading || isCurrentPlan(tier.key)\"\n :loading=\"loadingTier === tier.key\"\n :class=\"\n cn(\n 'h-10 w-full',\n getButtonTextClass(tier),\n tier.key === 'creator'\n ? 'bg-base-foreground border-transparent hover:bg-inverted-background-hover'\n : 'bg-secondary-background border-transparent hover:bg-secondary-background-hover focus:bg-secondary-background-selected'\n )\n \"\n @click=\"() => handleSubscribe(tier.key)\"\n >\n {{ getButtonLabel(tier) }}\n </Button>\n </div>\n </div>\n </div>\n\n <!-- Video Estimate Help Popover -->\n <Popover\n ref=\"popover\"\n append-to=\"body\"\n :auto-z-index=\"true\"\n :base-z-index=\"1000\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: {\n class:\n 'rounded-lg border border-interface-stroke bg-interface-panel-surface shadow-lg p-4 max-w-xs'\n }\n }\"\n >\n <div class=\"flex flex-col gap-2\">\n <p class=\"text-sm text-base-foreground leading-normal\">\n {{ t('subscription.videoEstimateExplanation') }}\n </p>\n <a\n href=\"https://cloud.comfy.org/?template=video_wan2_2_14B_fun_camera\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-sm text-azure-600 hover:text-azure-400 no-underline flex gap-1\"\n >\n <span class=\"underline\">\n {{ t('subscription.videoEstimateTryTemplate') }}\n </span>\n <span class=\"no-underline\" v-html=\"'&rarr;'\"></span>\n </a>\n </div>\n </Popover>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { cn } from '@comfyorg/tailwind-utils'\nimport Popover from 'primevue/popover'\nimport SelectButton from 'primevue/selectbutton'\nimport type { ToggleButtonPassThroughMethodOptions } from 'primevue/togglebutton'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { t } from '@/i18n'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport {\n TIER_PRICING,\n TIER_TO_KEY\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport type {\n TierKey,\n TierPricing\n} from '@/platform/cloud/subscription/constants/tierPricing'\nimport { performSubscriptionCheckout } from '@/platform/cloud/subscription/utils/subscriptionCheckoutUtil'\nimport { isPlanDowngrade } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport type { BillingCycle } from '@/platform/cloud/subscription/utils/subscriptionTierRank'\nimport { isCloud } from '@/platform/distribution/types'\nimport type { components } from '@/types/comfyRegistryTypes'\n\ntype SubscriptionTier = components['schemas']['SubscriptionTier']\ntype CheckoutTierKey = Exclude<TierKey, 'founder'>\ntype CheckoutTier = CheckoutTierKey | `${CheckoutTierKey}-yearly`\n\nconst getCheckoutTier = (\n tierKey: CheckoutTierKey,\n billingCycle: BillingCycle\n): CheckoutTier => (billingCycle === 'yearly' ? `${tierKey}-yearly` : tierKey)\n\ninterface BillingCycleOption {\n label: string\n value: BillingCycle\n}\n\ninterface PricingTierConfig {\n id: SubscriptionTier\n key: CheckoutTierKey\n name: string\n pricing: TierPricing\n maxDuration: string\n customLoRAs: boolean\n isPopular?: boolean\n}\n\nconst billingCycleOptions: BillingCycleOption[] = [\n { label: t('subscription.yearly'), value: 'yearly' },\n { label: t('subscription.monthly'), value: 'monthly' }\n]\n\nconst tiers: PricingTierConfig[] = [\n {\n id: 'STANDARD',\n key: 'standard',\n name: t('subscription.tiers.standard.name'),\n pricing: TIER_PRICING.standard,\n maxDuration: t('subscription.maxDuration.standard'),\n customLoRAs: false,\n isPopular: false\n },\n {\n id: 'CREATOR',\n key: 'creator',\n name: t('subscription.tiers.creator.name'),\n pricing: TIER_PRICING.creator,\n maxDuration: t('subscription.maxDuration.creator'),\n customLoRAs: true,\n isPopular: true\n },\n {\n id: 'PRO',\n key: 'pro',\n name: t('subscription.tiers.pro.name'),\n pricing: TIER_PRICING.pro,\n maxDuration: t('subscription.maxDuration.pro'),\n customLoRAs: true,\n isPopular: false\n }\n]\n\nconst { n } = useI18n()\nconst { isActiveSubscription, subscriptionTier, isYearlySubscription } =\n useSubscription()\nconst { accessBillingPortal, reportError } = useFirebaseAuthActions()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\n\nconst isLoading = ref(false)\nconst loadingTier = ref<CheckoutTierKey | null>(null)\nconst popover = ref()\nconst currentBillingCycle = ref<BillingCycle>('yearly')\n\nconst currentTierKey = computed<TierKey | null>(() =>\n subscriptionTier.value ? TIER_TO_KEY[subscriptionTier.value] : null\n)\n\nconst currentPlanDescriptor = computed(() => {\n if (!currentTierKey.value) return null\n\n return {\n tierKey: currentTierKey.value,\n billingCycle: isYearlySubscription.value ? 'yearly' : 'monthly'\n } as const\n})\n\nconst isCurrentPlan = (tierKey: CheckoutTierKey): boolean => {\n if (!currentTierKey.value) return false\n\n const selectedIsYearly = currentBillingCycle.value === 'yearly'\n\n return (\n currentTierKey.value === tierKey &&\n isYearlySubscription.value === selectedIsYearly\n )\n}\n\nconst togglePopover = (event: Event) => {\n popover.value.toggle(event)\n}\n\nconst getButtonLabel = (tier: PricingTierConfig): string => {\n if (isCurrentPlan(tier.key)) return t('subscription.currentPlan')\n\n const planName =\n currentBillingCycle.value === 'yearly'\n ? t('subscription.tierNameYearly', { name: tier.name })\n : tier.name\n\n return isActiveSubscription.value\n ? t('subscription.changeTo', { plan: planName })\n : t('subscription.subscribeTo', { plan: planName })\n}\n\nconst getButtonSeverity = (\n tier: PricingTierConfig\n): 'primary' | 'secondary' => {\n if (isCurrentPlan(tier.key)) return 'secondary'\n if (tier.key === 'creator') return 'primary'\n return 'secondary'\n}\n\nconst getButtonTextClass = (tier: PricingTierConfig): string =>\n tier.key === 'creator'\n ? 'font-inter text-sm font-bold leading-normal text-base-background'\n : 'font-inter text-sm font-bold leading-normal text-primary-foreground'\n\nconst getPrice = (tier: PricingTierConfig): number =>\n tier.pricing[currentBillingCycle.value]\n\nconst getAnnualTotal = (tier: PricingTierConfig): number =>\n tier.pricing.yearly * 12\n\nconst getCreditsDisplay = (tier: PricingTierConfig): number =>\n tier.pricing.credits * (currentBillingCycle.value === 'yearly' ? 12 : 1)\n\nconst handleSubscribe = wrapWithErrorHandlingAsync(\n async (tierKey: CheckoutTierKey) => {\n if (!isCloud || isLoading.value || isCurrentPlan(tierKey)) return\n\n isLoading.value = true\n loadingTier.value = tierKey\n\n try {\n if (isActiveSubscription.value) {\n // Pass the target tier to create a deep link to subscription update confirmation\n const checkoutTier = getCheckoutTier(tierKey, currentBillingCycle.value)\n const targetPlan = {\n tierKey,\n billingCycle: currentBillingCycle.value\n }\n const downgrade =\n currentPlanDescriptor.value &&\n isPlanDowngrade({\n current: currentPlanDescriptor.value,\n target: targetPlan\n })\n\n if (downgrade) {\n // TODO(COMFY-StripeProration): Remove once backend checkout creation mirrors portal proration (\"change at billing end\")\n await accessBillingPortal()\n } else {\n await accessBillingPortal(checkoutTier)\n }\n } else {\n await performSubscriptionCheckout(tierKey, currentBillingCycle.value)\n }\n } finally {\n isLoading.value = false\n loadingTier.value = null\n }\n },\n reportError\n)\n</script>\n","<template>\n <div class=\"flex flex-col items-start gap-0 self-stretch\">\n <div class=\"flex items-center gap-2 py-2\">\n <i class=\"pi pi-check text-xs text-text-primary\" />\n <span class=\"text-sm text-text-primary\">\n {{ $t('subscription.benefits.benefit1') }}\n </span>\n </div>\n\n <div class=\"flex items-center gap-2 py-2\">\n <i class=\"pi pi-check text-xs text-text-primary\" />\n <span class=\"text-sm text-text-primary\">\n {{ $t('subscription.benefits.benefit2') }}\n </span>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\"></script>\n","<template>\n <div\n v-if=\"showCustomPricingTable\"\n class=\"relative flex flex-col p-4 pt-8 md:p-16 !overflow-y-auto h-full gap-8\"\n >\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full shrink-0 text-text-secondary hover:bg-white/10 absolute right-2.5 top-2.5\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times text-xl\" />\n </Button>\n <div class=\"text-center\">\n <h2 class=\"text-xl lg:text-2xl text-muted-foreground m-0\">\n {{ $t('subscription.description') }}\n </h2>\n </div>\n\n <PricingTable class=\"flex-1\" />\n\n <!-- Contact and Enterprise Links -->\n <div class=\"flex flex-col items-center gap-2\">\n <p class=\"text-sm text-text-secondary m-0\">\n {{ $t('subscription.haveQuestions') }}\n </p>\n <div class=\"flex items-center gap-1.5\">\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleContactUs\"\n >\n {{ $t('subscription.contactUs') }}\n <i class=\"pi pi-comments\" />\n </Button>\n <span class=\"text-sm text-text-secondary\">{{ $t('g.or') }}</span>\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleViewEnterprise\"\n >\n {{ $t('subscription.viewEnterprise') }}\n <i class=\"pi pi-external-link\" />\n </Button>\n </div>\n </div>\n </div>\n <div v-else class=\"legacy-dialog relative grid h-full grid-cols-5\">\n <!-- Custom close button -->\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full absolute top-2.5 right-2.5 z-10 h-8 w-8 p-0 text-white hover:bg-white/20\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n\n <div\n class=\"relative col-span-2 flex items-center justify-center overflow-hidden rounded-sm\"\n >\n <video\n autoplay\n loop\n muted\n playsinline\n class=\"h-full min-w-[125%] object-cover p-0\"\n style=\"margin-left: -20%\"\n >\n <source\n src=\"/assets/images/cloud-subscription.webm\"\n type=\"video/webm\"\n />\n </video>\n </div>\n\n <div class=\"col-span-3 flex flex-col justify-between p-8\">\n <div>\n <div class=\"flex flex-col gap-6\">\n <div class=\"inline-flex items-center gap-2\">\n <div class=\"text-sm text-text-primary\">\n {{ $t('subscription.required.title') }}\n </div>\n <CloudBadge\n reverse-order\n no-padding\n background-color=\"var(--p-dialog-background)\"\n use-subscription\n />\n </div>\n\n <div class=\"flex items-baseline gap-2\">\n <span class=\"text-4xl font-bold\">{{ formattedMonthlyPrice }}</span>\n <span class=\"text-xl\">{{ $t('subscription.perMonth') }}</span>\n </div>\n </div>\n\n <SubscriptionBenefits class=\"mt-6 text-muted\" />\n </div>\n\n <div class=\"flex flex-col pt-8\">\n <SubscribeButton\n class=\"py-2 px-4 rounded-lg\"\n :pt=\"{\n root: {\n style: 'background: var(--color-accent-blue, #0B8CE9);'\n },\n label: {\n class: 'font-inter font-[700] text-sm'\n }\n }\"\n @subscribed=\"handleSubscribed\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, watch } from 'vue'\n\nimport CloudBadge from '@/components/topbar/CloudBadge.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'\nimport PricingTable from '@/platform/cloud/subscription/components/PricingTable.vue'\nimport SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'\nimport SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst props = defineProps<{\n onClose: () => void\n}>()\n\nconst emit = defineEmits<{\n close: [subscribed: boolean]\n}>()\n\nconst { fetchStatus, isActiveSubscription, isSubscriptionEnabled } =\n useSubscription()\n\n// Legacy price for non-tier flow with locale-aware formatting\nconst formattedMonthlyPrice = new Intl.NumberFormat(\n navigator.language || 'en-US',\n {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n }\n).format(MONTHLY_SUBSCRIPTION_PRICE)\nconst commandStore = useCommandStore()\nconst telemetry = useTelemetry()\n\n// Always show custom pricing table for cloud subscriptions\nconst showCustomPricingTable = computed(() => isSubscriptionEnabled())\n\nconst POLL_INTERVAL_MS = 3000\nconst MAX_POLL_ATTEMPTS = 3\nlet pollInterval: number | null = null\nlet pollAttempts = 0\n\nconst stopPolling = () => {\n if (pollInterval) {\n clearInterval(pollInterval)\n pollInterval = null\n }\n}\n\nconst startPolling = () => {\n stopPolling()\n pollAttempts = 0\n\n const poll = async () => {\n try {\n await fetchStatus()\n pollAttempts++\n\n if (pollAttempts >= MAX_POLL_ATTEMPTS) {\n stopPolling()\n }\n } catch (error) {\n console.error(\n '[SubscriptionDialog] Failed to poll subscription status',\n error\n )\n stopPolling()\n }\n }\n\n void poll()\n pollInterval = window.setInterval(() => {\n void poll()\n }, POLL_INTERVAL_MS)\n}\n\nconst handleWindowFocus = () => {\n if (showCustomPricingTable.value) {\n startPolling()\n }\n}\n\nwatch(\n showCustomPricingTable,\n (enabled) => {\n if (enabled) {\n window.addEventListener('focus', handleWindowFocus)\n } else {\n window.removeEventListener('focus', handleWindowFocus)\n stopPolling()\n }\n },\n { immediate: true }\n)\n\nwatch(\n () => isActiveSubscription.value,\n (isActive) => {\n if (isActive && showCustomPricingTable.value) {\n emit('close', true)\n }\n }\n)\n\nconst handleSubscribed = () => {\n emit('close', true)\n}\n\nconst handleClose = () => {\n stopPolling()\n props.onClose()\n}\n\nconst handleContactUs = async () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'help_feedback',\n is_external: true,\n source: 'subscription'\n })\n await commandStore.execute('Comfy.ContactSupport')\n}\n\nconst handleViewEnterprise = () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'docs',\n is_external: true,\n source: 'subscription'\n })\n window.open('https://www.comfy.org/cloud/enterprise', '_blank')\n}\n\nonBeforeUnmount(() => {\n stopPolling()\n window.removeEventListener('focus', handleWindowFocus)\n})\n</script>\n\n<style scoped>\n.legacy-dialog :deep(.bg-comfy-menu-secondary) {\n background-color: transparent;\n}\n\n.legacy-dialog :deep(.p-button) {\n color: white;\n}\n</style>\n","<template>\n <div\n v-if=\"showCustomPricingTable\"\n class=\"relative flex flex-col p-4 pt-8 md:p-16 !overflow-y-auto h-full gap-8\"\n >\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full shrink-0 text-text-secondary hover:bg-white/10 absolute right-2.5 top-2.5\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times text-xl\" />\n </Button>\n <div class=\"text-center\">\n <h2 class=\"text-xl lg:text-2xl text-muted-foreground m-0\">\n {{ $t('subscription.description') }}\n </h2>\n </div>\n\n <PricingTable class=\"flex-1\" />\n\n <!-- Contact and Enterprise Links -->\n <div class=\"flex flex-col items-center gap-2\">\n <p class=\"text-sm text-text-secondary m-0\">\n {{ $t('subscription.haveQuestions') }}\n </p>\n <div class=\"flex items-center gap-1.5\">\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleContactUs\"\n >\n {{ $t('subscription.contactUs') }}\n <i class=\"pi pi-comments\" />\n </Button>\n <span class=\"text-sm text-text-secondary\">{{ $t('g.or') }}</span>\n <Button\n variant=\"muted-textonly\"\n class=\"h-6 p-1 text-sm text-text-secondary hover:text-base-foreground\"\n @click=\"handleViewEnterprise\"\n >\n {{ $t('subscription.viewEnterprise') }}\n <i class=\"pi pi-external-link\" />\n </Button>\n </div>\n </div>\n </div>\n <div v-else class=\"legacy-dialog relative grid h-full grid-cols-5\">\n <!-- Custom close button -->\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n class=\"rounded-full absolute top-2.5 right-2.5 z-10 h-8 w-8 p-0 text-white hover:bg-white/20\"\n :aria-label=\"$t('g.close')\"\n @click=\"handleClose\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n\n <div\n class=\"relative col-span-2 flex items-center justify-center overflow-hidden rounded-sm\"\n >\n <video\n autoplay\n loop\n muted\n playsinline\n class=\"h-full min-w-[125%] object-cover p-0\"\n style=\"margin-left: -20%\"\n >\n <source\n src=\"/assets/images/cloud-subscription.webm\"\n type=\"video/webm\"\n />\n </video>\n </div>\n\n <div class=\"col-span-3 flex flex-col justify-between p-8\">\n <div>\n <div class=\"flex flex-col gap-6\">\n <div class=\"inline-flex items-center gap-2\">\n <div class=\"text-sm text-text-primary\">\n {{ $t('subscription.required.title') }}\n </div>\n <CloudBadge\n reverse-order\n no-padding\n background-color=\"var(--p-dialog-background)\"\n use-subscription\n />\n </div>\n\n <div class=\"flex items-baseline gap-2\">\n <span class=\"text-4xl font-bold\">{{ formattedMonthlyPrice }}</span>\n <span class=\"text-xl\">{{ $t('subscription.perMonth') }}</span>\n </div>\n </div>\n\n <SubscriptionBenefits class=\"mt-6 text-muted\" />\n </div>\n\n <div class=\"flex flex-col pt-8\">\n <SubscribeButton\n class=\"py-2 px-4 rounded-lg\"\n :pt=\"{\n root: {\n style: 'background: var(--color-accent-blue, #0B8CE9);'\n },\n label: {\n class: 'font-inter font-[700] text-sm'\n }\n }\"\n @subscribed=\"handleSubscribed\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, watch } from 'vue'\n\nimport CloudBadge from '@/components/topbar/CloudBadge.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'\nimport PricingTable from '@/platform/cloud/subscription/components/PricingTable.vue'\nimport SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'\nimport SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst props = defineProps<{\n onClose: () => void\n}>()\n\nconst emit = defineEmits<{\n close: [subscribed: boolean]\n}>()\n\nconst { fetchStatus, isActiveSubscription, isSubscriptionEnabled } =\n useSubscription()\n\n// Legacy price for non-tier flow with locale-aware formatting\nconst formattedMonthlyPrice = new Intl.NumberFormat(\n navigator.language || 'en-US',\n {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n }\n).format(MONTHLY_SUBSCRIPTION_PRICE)\nconst commandStore = useCommandStore()\nconst telemetry = useTelemetry()\n\n// Always show custom pricing table for cloud subscriptions\nconst showCustomPricingTable = computed(() => isSubscriptionEnabled())\n\nconst POLL_INTERVAL_MS = 3000\nconst MAX_POLL_ATTEMPTS = 3\nlet pollInterval: number | null = null\nlet pollAttempts = 0\n\nconst stopPolling = () => {\n if (pollInterval) {\n clearInterval(pollInterval)\n pollInterval = null\n }\n}\n\nconst startPolling = () => {\n stopPolling()\n pollAttempts = 0\n\n const poll = async () => {\n try {\n await fetchStatus()\n pollAttempts++\n\n if (pollAttempts >= MAX_POLL_ATTEMPTS) {\n stopPolling()\n }\n } catch (error) {\n console.error(\n '[SubscriptionDialog] Failed to poll subscription status',\n error\n )\n stopPolling()\n }\n }\n\n void poll()\n pollInterval = window.setInterval(() => {\n void poll()\n }, POLL_INTERVAL_MS)\n}\n\nconst handleWindowFocus = () => {\n if (showCustomPricingTable.value) {\n startPolling()\n }\n}\n\nwatch(\n showCustomPricingTable,\n (enabled) => {\n if (enabled) {\n window.addEventListener('focus', handleWindowFocus)\n } else {\n window.removeEventListener('focus', handleWindowFocus)\n stopPolling()\n }\n },\n { immediate: true }\n)\n\nwatch(\n () => isActiveSubscription.value,\n (isActive) => {\n if (isActive && showCustomPricingTable.value) {\n emit('close', true)\n }\n }\n)\n\nconst handleSubscribed = () => {\n emit('close', true)\n}\n\nconst handleClose = () => {\n stopPolling()\n props.onClose()\n}\n\nconst handleContactUs = async () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'help_feedback',\n is_external: true,\n source: 'subscription'\n })\n await commandStore.execute('Comfy.ContactSupport')\n}\n\nconst handleViewEnterprise = () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'docs',\n is_external: true,\n source: 'subscription'\n })\n window.open('https://www.comfy.org/cloud/enterprise', '_blank')\n}\n\nonBeforeUnmount(() => {\n stopPolling()\n window.removeEventListener('focus', handleWindowFocus)\n})\n</script>\n\n<style scoped>\n.legacy-dialog :deep(.bg-comfy-menu-secondary) {\n background-color: transparent;\n}\n\n.legacy-dialog :deep(.p-button) {\n color: white;\n}\n</style>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- var V=Object.defineProperty;var p=(d,o)=>V(d,"name",{value:o,configurable:!0});import{Z as $,lt as s}from"./vendor-primevue-DcMRXJN3.js";import{$o as w,Ba as r,Do as U,Jo as e,Ka as B,Pa as E,Ua as u,Va as m,Ya as _,do as l,gi as L,ho as j,io as P,po as R,qa as t,za as v,zo as T}from"./vendor-other-DlQF6V2E.js";import{c as F}from"./vendor-vue-D9IUuwPJ.js";import"./api-CUAc7rDA.js";import"./remoteConfig-CZcEXsZS.js";import"./colorUtil-CzxntCbX.js";import{n as N,t as O}from"./useErrorHandling-CI8_F4yx.js";import{t as W}from"./Button-Do2I1OAA.js";import{n as q,t as z}from"./auth-B8ZZ0KKQ.js";var A={class:"flex h-full items-center justify-center p-8"},D={class:"max-w-[100vw] lg:w-96"},H={class:"rounded-lg bg-[#2d2e32] p-4"},J={class:"mt-6 mb-8 flex flex-col gap-4"},K={class:"flex items-center"},M={class:"mb-8"},Y={class:"my-8 flex items-center"},Z={class:"flex flex-col gap-6"},G={class:"mt-5"},I=_({__name:"CloudLoginViewSkeleton",setup(d){return(o,a)=>(l(),u("div",A,[r("div",D,[r("div",H,[t(e(s),{width:"60%",height:"1.125rem",class:"mb-2"}),t(e(s),{width:"90%",height:"1rem",class:"mb-2"}),t(e(s),{width:"80%",height:"1rem"})]),r("div",J,[t(e(s),{width:"45%",height:"1.5rem",class:"my-0"}),r("div",K,[t(e(s),{width:"25%",height:"1rem",class:"mr-1"}),t(e(s),{width:"20%",height:"1rem"})])]),r("div",M,[t(e(s),{width:"20%",height:"1rem",class:"mb-2"}),t(e(s),{width:"100%",height:"2.5rem",class:"mb-4"}),t(e(s),{width:"25%",height:"1rem",class:"mb-4"}),t(e(s),{width:"100%",height:"2.5rem",class:"mb-6"}),t(e(s),{width:"80%",height:"1rem",class:"mb-4"}),t(e(s),{width:"100%",height:"2.5rem"})]),r("div",Y,[a[0]||(a[0]=r("div",{class:"flex-1 border-t border-gray-300"},null,-1)),t(e(s),{width:"30%",height:"1rem",class:"mx-4"}),a[1]||(a[1]=r("div",{class:"flex-1 border-t border-gray-300"},null,-1))]),r("div",Z,[t(e(s),{width:"100%",height:"2.5rem"}),t(e(s),{width:"100%",height:"2.5rem"})]),r("div",G,[t(e(s),{width:"70%",height:"0.875rem"})])])]))}}),Q=I,X={class:"flex min-h-[638px] min-w-[320px] flex-col"},ee={class:"flex flex-1 flex-col p-0"},te={class:"flex min-h-full flex-1 flex-col justify-between"},se={class:"flex flex-col gap-6"},re={class:"flex justify-between pt-4"},ae=_({__name:"CloudSurveyViewSkeleton",setup(d){return(o,a)=>(l(),u("div",null,[r("div",X,[t(e(s),{width:"100%",height:"0.5rem",class:"mb-8"}),r("div",ee,[r("div",te,[r("div",null,[t(e(s),{width:"70%",height:"1.75rem",class:"mb-8"}),r("div",se,[(l(),u(E,null,R(5,h=>r("div",{key:h,class:"flex items-center gap-3"},[t(e(s),{width:"1.25rem",height:"1.25rem",shape:"circle"}),t(e(s),{width:"85%",height:"0.875rem"})])),64))])]),r("div",re,[a[0]||(a[0]=r("span",null,null,-1)),t(e(s),{width:"100%",height:"2.5rem"})])])])])]))}}),ie=ae,le={key:3,class:"flex h-full items-center justify-center p-8"},oe={class:"max-w-[100vw] p-2 text-center lg:w-96"},ne={class:"mb-4 text-red-500"},ue={key:4,class:"flex items-center justify-center"},de=_({__name:"UserCheckView",setup(d){const o=F(),{wrapWithErrorHandlingAsync:a}=O(),{flags:h}=N(),y=v(()=>h.onboardingSurveyEnabled??!0),n=T("loading"),{isLoading:x,error:c,execute:b}=L(a(async()=>{if(await P(),!y.value){await o.replace({path:"/"});return}const[i,f]=await Promise.all([q(),z()]);if(!i){n.value="login",await o.replace({name:"cloud-login"});return}if(!f){n.value="survey",await o.replace({name:"cloud-survey"});return}globalThis.location.href="/"}),null,{resetOnExecute:!1}),k=v(()=>{if(!c.value)return"";const i=c.value.toString().toLowerCase();return i.includes("network")||i.includes("fetch")?"Connection problem. Please check your internet connection.":i.includes("timeout")?"Request timed out. Please try again.":"Unable to check account status. Please try again."}),g=v(()=>x.value&&!!c.value),S=p(async()=>{await b()},"handleRetry");return(i,f)=>{const C=j("CloudWaitlistViewSkeleton");return n.value==="login"?(l(),m(Q,{key:0})):n.value==="survey"?(l(),m(ie,{key:1})):n.value==="waitlist"?(l(),m(C,{key:2})):e(c)?(l(),u("div",le,[r("div",oe,[r("p",ne,w(k.value),1),t(W,{loading:g.value,class:"w-full",onClick:S},{default:U(()=>[B(w(g.value?i.$t("cloudOnboarding.retrying"):i.$t("cloudOnboarding.retry")),1)]),_:1},8,["loading"])])])):(l(),u("div",ue,[t(e($),{class:"h-8 w-8"})]))}}}),xe=de;export{xe as default};
1
+ var V=Object.defineProperty;var p=(d,o)=>V(d,"name",{value:o,configurable:!0});import{Z as $,lt as s}from"./vendor-primevue-DcMRXJN3.js";import{$o as w,Ba as r,Do as U,Jo as e,Ka as B,Pa as E,Ua as u,Va as m,Ya as _,do as l,gi as L,ho as j,io as P,po as R,qa as t,za as v,zo as T}from"./vendor-other-DlQF6V2E.js";import{c as F}from"./vendor-vue-D9IUuwPJ.js";import"./api-Dwq2LQIW.js";import"./remoteConfig-CZcEXsZS.js";import"./colorUtil-CzxntCbX.js";import{n as N,t as O}from"./useErrorHandling-Cfa5N_7c.js";import{t as W}from"./Button-Do2I1OAA.js";import{n as q,t as z}from"./auth-B9axG-yZ.js";var A={class:"flex h-full items-center justify-center p-8"},D={class:"max-w-[100vw] lg:w-96"},H={class:"rounded-lg bg-[#2d2e32] p-4"},J={class:"mt-6 mb-8 flex flex-col gap-4"},K={class:"flex items-center"},M={class:"mb-8"},Y={class:"my-8 flex items-center"},Z={class:"flex flex-col gap-6"},G={class:"mt-5"},I=_({__name:"CloudLoginViewSkeleton",setup(d){return(o,a)=>(l(),u("div",A,[r("div",D,[r("div",H,[t(e(s),{width:"60%",height:"1.125rem",class:"mb-2"}),t(e(s),{width:"90%",height:"1rem",class:"mb-2"}),t(e(s),{width:"80%",height:"1rem"})]),r("div",J,[t(e(s),{width:"45%",height:"1.5rem",class:"my-0"}),r("div",K,[t(e(s),{width:"25%",height:"1rem",class:"mr-1"}),t(e(s),{width:"20%",height:"1rem"})])]),r("div",M,[t(e(s),{width:"20%",height:"1rem",class:"mb-2"}),t(e(s),{width:"100%",height:"2.5rem",class:"mb-4"}),t(e(s),{width:"25%",height:"1rem",class:"mb-4"}),t(e(s),{width:"100%",height:"2.5rem",class:"mb-6"}),t(e(s),{width:"80%",height:"1rem",class:"mb-4"}),t(e(s),{width:"100%",height:"2.5rem"})]),r("div",Y,[a[0]||(a[0]=r("div",{class:"flex-1 border-t border-gray-300"},null,-1)),t(e(s),{width:"30%",height:"1rem",class:"mx-4"}),a[1]||(a[1]=r("div",{class:"flex-1 border-t border-gray-300"},null,-1))]),r("div",Z,[t(e(s),{width:"100%",height:"2.5rem"}),t(e(s),{width:"100%",height:"2.5rem"})]),r("div",G,[t(e(s),{width:"70%",height:"0.875rem"})])])]))}}),Q=I,X={class:"flex min-h-[638px] min-w-[320px] flex-col"},ee={class:"flex flex-1 flex-col p-0"},te={class:"flex min-h-full flex-1 flex-col justify-between"},se={class:"flex flex-col gap-6"},re={class:"flex justify-between pt-4"},ae=_({__name:"CloudSurveyViewSkeleton",setup(d){return(o,a)=>(l(),u("div",null,[r("div",X,[t(e(s),{width:"100%",height:"0.5rem",class:"mb-8"}),r("div",ee,[r("div",te,[r("div",null,[t(e(s),{width:"70%",height:"1.75rem",class:"mb-8"}),r("div",se,[(l(),u(E,null,R(5,h=>r("div",{key:h,class:"flex items-center gap-3"},[t(e(s),{width:"1.25rem",height:"1.25rem",shape:"circle"}),t(e(s),{width:"85%",height:"0.875rem"})])),64))])]),r("div",re,[a[0]||(a[0]=r("span",null,null,-1)),t(e(s),{width:"100%",height:"2.5rem"})])])])])]))}}),ie=ae,le={key:3,class:"flex h-full items-center justify-center p-8"},oe={class:"max-w-[100vw] p-2 text-center lg:w-96"},ne={class:"mb-4 text-red-500"},ue={key:4,class:"flex items-center justify-center"},de=_({__name:"UserCheckView",setup(d){const o=F(),{wrapWithErrorHandlingAsync:a}=O(),{flags:h}=N(),y=v(()=>h.onboardingSurveyEnabled??!0),n=T("loading"),{isLoading:x,error:c,execute:b}=L(a(async()=>{if(await P(),!y.value){await o.replace({path:"/"});return}const[i,f]=await Promise.all([q(),z()]);if(!i){n.value="login",await o.replace({name:"cloud-login"});return}if(!f){n.value="survey",await o.replace({name:"cloud-survey"});return}globalThis.location.href="/"}),null,{resetOnExecute:!1}),k=v(()=>{if(!c.value)return"";const i=c.value.toString().toLowerCase();return i.includes("network")||i.includes("fetch")?"Connection problem. Please check your internet connection.":i.includes("timeout")?"Request timed out. Please try again.":"Unable to check account status. Please try again."}),g=v(()=>x.value&&!!c.value),S=p(async()=>{await b()},"handleRetry");return(i,f)=>{const C=j("CloudWaitlistViewSkeleton");return n.value==="login"?(l(),m(Q,{key:0})):n.value==="survey"?(l(),m(ie,{key:1})):n.value==="waitlist"?(l(),m(C,{key:2})):e(c)?(l(),u("div",le,[r("div",oe,[r("p",ne,w(k.value),1),t(W,{loading:g.value,class:"w-full",onClick:S},{default:U(()=>[B(w(g.value?i.$t("cloudOnboarding.retrying"):i.$t("cloudOnboarding.retry")),1)]),_:1},8,["loading"])])])):(l(),u("div",ue,[t(e($),{class:"h-8 w-8"})]))}}}),xe=de;export{xe as default};
2
2
 
3
- //# sourceMappingURL=UserCheckView-spD3LyMu.js.map
3
+ //# sourceMappingURL=UserCheckView-x-fkcYzc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"UserCheckView-spD3LyMu.js","names":[],"sources":["../../src/platform/cloud/onboarding/skeletons/CloudLoginViewSkeleton.vue","../../src/platform/cloud/onboarding/skeletons/CloudLoginViewSkeleton.vue","../../src/platform/cloud/onboarding/skeletons/CloudSurveyViewSkeleton.vue","../../src/platform/cloud/onboarding/skeletons/CloudSurveyViewSkeleton.vue","../../src/platform/cloud/onboarding/UserCheckView.vue","../../src/platform/cloud/onboarding/UserCheckView.vue"],"sourcesContent":["<template>\n <div class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] lg:w-96\">\n <div class=\"rounded-lg bg-[#2d2e32] p-4\">\n <Skeleton width=\"60%\" height=\"1.125rem\" class=\"mb-2\" />\n <Skeleton width=\"90%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"80%\" height=\"1rem\" />\n </div>\n\n <div class=\"mt-6 mb-8 flex flex-col gap-4\">\n <Skeleton width=\"45%\" height=\"1.5rem\" class=\"my-0\" />\n <div class=\"flex items-center\">\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mr-1\" />\n <Skeleton width=\"20%\" height=\"1rem\" />\n </div>\n </div>\n\n <div class=\"mb-8\">\n <Skeleton width=\"20%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-4\" />\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-6\" />\n <Skeleton width=\"80%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"my-8 flex items-center\">\n <div class=\"flex-1 border-t border-gray-300\"></div>\n <Skeleton width=\"30%\" height=\"1rem\" class=\"mx-4\" />\n <div class=\"flex-1 border-t border-gray-300\"></div>\n </div>\n\n <div class=\"flex flex-col gap-6\">\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"mt-5\">\n <Skeleton width=\"70%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <div class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] lg:w-96\">\n <div class=\"rounded-lg bg-[#2d2e32] p-4\">\n <Skeleton width=\"60%\" height=\"1.125rem\" class=\"mb-2\" />\n <Skeleton width=\"90%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"80%\" height=\"1rem\" />\n </div>\n\n <div class=\"mt-6 mb-8 flex flex-col gap-4\">\n <Skeleton width=\"45%\" height=\"1.5rem\" class=\"my-0\" />\n <div class=\"flex items-center\">\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mr-1\" />\n <Skeleton width=\"20%\" height=\"1rem\" />\n </div>\n </div>\n\n <div class=\"mb-8\">\n <Skeleton width=\"20%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-4\" />\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-6\" />\n <Skeleton width=\"80%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"my-8 flex items-center\">\n <div class=\"flex-1 border-t border-gray-300\"></div>\n <Skeleton width=\"30%\" height=\"1rem\" class=\"mx-4\" />\n <div class=\"flex-1 border-t border-gray-300\"></div>\n </div>\n\n <div class=\"flex flex-col gap-6\">\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"mt-5\">\n <Skeleton width=\"70%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <div>\n <div class=\"flex min-h-[638px] min-w-[320px] flex-col\">\n <Skeleton width=\"100%\" height=\"0.5rem\" class=\"mb-8\" />\n\n <div class=\"flex flex-1 flex-col p-0\">\n <div class=\"flex min-h-full flex-1 flex-col justify-between\">\n <div>\n <Skeleton width=\"70%\" height=\"1.75rem\" class=\"mb-8\" />\n <div class=\"flex flex-col gap-6\">\n <div v-for=\"i in 5\" :key=\"i\" class=\"flex items-center gap-3\">\n <Skeleton width=\"1.25rem\" height=\"1.25rem\" shape=\"circle\" />\n <Skeleton width=\"85%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n\n <div class=\"flex justify-between pt-4\">\n <span />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <div>\n <div class=\"flex min-h-[638px] min-w-[320px] flex-col\">\n <Skeleton width=\"100%\" height=\"0.5rem\" class=\"mb-8\" />\n\n <div class=\"flex flex-1 flex-col p-0\">\n <div class=\"flex min-h-full flex-1 flex-col justify-between\">\n <div>\n <Skeleton width=\"70%\" height=\"1.75rem\" class=\"mb-8\" />\n <div class=\"flex flex-col gap-6\">\n <div v-for=\"i in 5\" :key=\"i\" class=\"flex items-center gap-3\">\n <Skeleton width=\"1.25rem\" height=\"1.25rem\" shape=\"circle\" />\n <Skeleton width=\"85%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n\n <div class=\"flex justify-between pt-4\">\n <span />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <CloudLoginViewSkeleton v-if=\"skeletonType === 'login'\" />\n <CloudSurveyViewSkeleton v-else-if=\"skeletonType === 'survey'\" />\n <CloudWaitlistViewSkeleton v-else-if=\"skeletonType === 'waitlist'\" />\n <div v-else-if=\"error\" class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] p-2 text-center lg:w-96\">\n <p class=\"mb-4 text-red-500\">{{ errorMessage }}</p>\n <Button :loading=\"isRetrying\" class=\"w-full\" @click=\"handleRetry\">\n {{\n isRetrying\n ? $t('cloudOnboarding.retrying')\n : $t('cloudOnboarding.retry')\n }}\n </Button>\n </div>\n </div>\n <div v-else class=\"flex items-center justify-center\">\n <ProgressSpinner class=\"h-8 w-8\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useAsyncState } from '@vueuse/core'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport { computed, nextTick, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport {\n getSurveyCompletedStatus,\n getUserCloudStatus\n} from '@/platform/cloud/onboarding/auth'\n\nimport CloudLoginViewSkeleton from './skeletons/CloudLoginViewSkeleton.vue'\nimport CloudSurveyViewSkeleton from './skeletons/CloudSurveyViewSkeleton.vue'\n\nconst router = useRouter()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\nconst { flags } = useFeatureFlags()\nconst onboardingSurveyEnabled = computed(\n () => flags.onboardingSurveyEnabled ?? true\n)\n\nconst skeletonType = ref<'login' | 'survey' | 'waitlist' | 'loading'>('loading')\n\nconst {\n isLoading,\n error,\n execute: checkUserStatus\n} = useAsyncState(\n wrapWithErrorHandlingAsync(async () => {\n await nextTick()\n\n if (!onboardingSurveyEnabled.value) {\n await router.replace({ path: '/' })\n return\n }\n\n const [cloudUserStats, surveyStatus] = await Promise.all([\n getUserCloudStatus(),\n getSurveyCompletedStatus()\n ])\n\n // Navigate based on user status\n if (!cloudUserStats) {\n skeletonType.value = 'login'\n await router.replace({ name: 'cloud-login' })\n return\n }\n\n // Survey is required for all users when feature flag is enabled\n if (!surveyStatus) {\n skeletonType.value = 'survey'\n await router.replace({ name: 'cloud-survey' })\n return\n }\n\n // User is fully onboarded (active or whitelist check disabled)\n globalThis.location.href = '/'\n }),\n null,\n { resetOnExecute: false }\n)\n\nconst errorMessage = computed(() => {\n if (!error.value) return ''\n\n // Provide user-friendly error messages\n const errorStr = error.value.toString().toLowerCase()\n\n if (errorStr.includes('network') || errorStr.includes('fetch')) {\n return 'Connection problem. Please check your internet connection.'\n }\n\n if (errorStr.includes('timeout')) {\n return 'Request timed out. Please try again.'\n }\n\n return 'Unable to check account status. Please try again.'\n})\n\nconst isRetrying = computed(() => isLoading.value && !!error.value)\n\nconst handleRetry = async () => {\n await checkUserStatus()\n}\n</script>\n","<template>\n <CloudLoginViewSkeleton v-if=\"skeletonType === 'login'\" />\n <CloudSurveyViewSkeleton v-else-if=\"skeletonType === 'survey'\" />\n <CloudWaitlistViewSkeleton v-else-if=\"skeletonType === 'waitlist'\" />\n <div v-else-if=\"error\" class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] p-2 text-center lg:w-96\">\n <p class=\"mb-4 text-red-500\">{{ errorMessage }}</p>\n <Button :loading=\"isRetrying\" class=\"w-full\" @click=\"handleRetry\">\n {{\n isRetrying\n ? $t('cloudOnboarding.retrying')\n : $t('cloudOnboarding.retry')\n }}\n </Button>\n </div>\n </div>\n <div v-else class=\"flex items-center justify-center\">\n <ProgressSpinner class=\"h-8 w-8\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useAsyncState } from '@vueuse/core'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport { computed, nextTick, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport {\n getSurveyCompletedStatus,\n getUserCloudStatus\n} from '@/platform/cloud/onboarding/auth'\n\nimport CloudLoginViewSkeleton from './skeletons/CloudLoginViewSkeleton.vue'\nimport CloudSurveyViewSkeleton from './skeletons/CloudSurveyViewSkeleton.vue'\n\nconst router = useRouter()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\nconst { flags } = useFeatureFlags()\nconst onboardingSurveyEnabled = computed(\n () => flags.onboardingSurveyEnabled ?? true\n)\n\nconst skeletonType = ref<'login' | 'survey' | 'waitlist' | 'loading'>('loading')\n\nconst {\n isLoading,\n error,\n execute: checkUserStatus\n} = useAsyncState(\n wrapWithErrorHandlingAsync(async () => {\n await nextTick()\n\n if (!onboardingSurveyEnabled.value) {\n await router.replace({ path: '/' })\n return\n }\n\n const [cloudUserStats, surveyStatus] = await Promise.all([\n getUserCloudStatus(),\n getSurveyCompletedStatus()\n ])\n\n // Navigate based on user status\n if (!cloudUserStats) {\n skeletonType.value = 'login'\n await router.replace({ name: 'cloud-login' })\n return\n }\n\n // Survey is required for all users when feature flag is enabled\n if (!surveyStatus) {\n skeletonType.value = 'survey'\n await router.replace({ name: 'cloud-survey' })\n return\n }\n\n // User is fully onboarded (active or whitelist check disabled)\n globalThis.location.href = '/'\n }),\n null,\n { resetOnExecute: false }\n)\n\nconst errorMessage = computed(() => {\n if (!error.value) return ''\n\n // Provide user-friendly error messages\n const errorStr = error.value.toString().toLowerCase()\n\n if (errorStr.includes('network') || errorStr.includes('fetch')) {\n return 'Connection problem. Please check your internet connection.'\n }\n\n if (errorStr.includes('timeout')) {\n return 'Request timed out. Please try again.'\n }\n\n return 'Unable to check account status. Please try again.'\n})\n\nconst isRetrying = computed(() => isLoading.value && !!error.value)\n\nconst handleRetry = async () => {\n await checkUserStatus()\n}\n</script>\n"],"mappings":"+4FKsCA,MAAM,EAAS,EAAA,EACT,CAAE,2BAAA,CAAA,EAA+B,EAAA,EACjC,CAAE,MAAA,CAAA,EAAU,EAAA,EACZ,EAA0B,EAAA,IACxB,EAAM,yBAA2B,EAAA,EAGnC,EAAe,EAAiD,SAAA,EAEhE,CACJ,UAAA,EACA,MAAA,EACA,QAAS,CAAA,EACP,EACF,EAA2B,SAAY,CAGrC,GAFA,MAAM,EAAA,EAEF,CAAC,EAAwB,MAAO,CAClC,MAAM,EAAO,QAAQ,CAAE,KAAM,GAAA,CAAK,EAClC,OAGF,KAAM,CAAC,EAAgB,CAAA,EAAgB,MAAM,QAAQ,IAAI,CACvD,EAAA,EACA,EAAA,CAAyB,CAC1B,EAGD,GAAI,CAAC,EAAgB,CACnB,EAAa,MAAQ,QACrB,MAAM,EAAO,QAAQ,CAAE,KAAM,aAAA,CAAe,EAC5C,OAIF,GAAI,CAAC,EAAc,CACjB,EAAa,MAAQ,SACrB,MAAM,EAAO,QAAQ,CAAE,KAAM,cAAA,CAAgB,EAC7C,OAIF,WAAW,SAAS,KAAO,MAE7B,KACA,CAAE,eAAgB,EAAA,CAAM,EAGpB,EAAe,EAAA,IAAe,CAClC,GAAI,CAAC,EAAM,MAAO,MAAO,GAGzB,MAAM,EAAW,EAAM,MAAM,SAAA,EAAW,YAAA,EAExC,OAAI,EAAS,SAAS,SAAA,GAAc,EAAS,SAAS,OAAA,EAC7C,6DAGL,EAAS,SAAS,SAAA,EACb,uCAGF,sDAGH,EAAa,EAAA,IAAe,EAAU,OAAS,CAAC,CAAC,EAAM,KAAA,EAEvD,EAAc,EAAA,SAAY,CAC9B,MAAM,EAAA,GADY"}
1
+ {"version":3,"file":"UserCheckView-x-fkcYzc.js","names":[],"sources":["../../src/platform/cloud/onboarding/skeletons/CloudLoginViewSkeleton.vue","../../src/platform/cloud/onboarding/skeletons/CloudLoginViewSkeleton.vue","../../src/platform/cloud/onboarding/skeletons/CloudSurveyViewSkeleton.vue","../../src/platform/cloud/onboarding/skeletons/CloudSurveyViewSkeleton.vue","../../src/platform/cloud/onboarding/UserCheckView.vue","../../src/platform/cloud/onboarding/UserCheckView.vue"],"sourcesContent":["<template>\n <div class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] lg:w-96\">\n <div class=\"rounded-lg bg-[#2d2e32] p-4\">\n <Skeleton width=\"60%\" height=\"1.125rem\" class=\"mb-2\" />\n <Skeleton width=\"90%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"80%\" height=\"1rem\" />\n </div>\n\n <div class=\"mt-6 mb-8 flex flex-col gap-4\">\n <Skeleton width=\"45%\" height=\"1.5rem\" class=\"my-0\" />\n <div class=\"flex items-center\">\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mr-1\" />\n <Skeleton width=\"20%\" height=\"1rem\" />\n </div>\n </div>\n\n <div class=\"mb-8\">\n <Skeleton width=\"20%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-4\" />\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-6\" />\n <Skeleton width=\"80%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"my-8 flex items-center\">\n <div class=\"flex-1 border-t border-gray-300\"></div>\n <Skeleton width=\"30%\" height=\"1rem\" class=\"mx-4\" />\n <div class=\"flex-1 border-t border-gray-300\"></div>\n </div>\n\n <div class=\"flex flex-col gap-6\">\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"mt-5\">\n <Skeleton width=\"70%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <div class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] lg:w-96\">\n <div class=\"rounded-lg bg-[#2d2e32] p-4\">\n <Skeleton width=\"60%\" height=\"1.125rem\" class=\"mb-2\" />\n <Skeleton width=\"90%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"80%\" height=\"1rem\" />\n </div>\n\n <div class=\"mt-6 mb-8 flex flex-col gap-4\">\n <Skeleton width=\"45%\" height=\"1.5rem\" class=\"my-0\" />\n <div class=\"flex items-center\">\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mr-1\" />\n <Skeleton width=\"20%\" height=\"1rem\" />\n </div>\n </div>\n\n <div class=\"mb-8\">\n <Skeleton width=\"20%\" height=\"1rem\" class=\"mb-2\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-4\" />\n <Skeleton width=\"25%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" class=\"mb-6\" />\n <Skeleton width=\"80%\" height=\"1rem\" class=\"mb-4\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"my-8 flex items-center\">\n <div class=\"flex-1 border-t border-gray-300\"></div>\n <Skeleton width=\"30%\" height=\"1rem\" class=\"mx-4\" />\n <div class=\"flex-1 border-t border-gray-300\"></div>\n </div>\n\n <div class=\"flex flex-col gap-6\">\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n\n <div class=\"mt-5\">\n <Skeleton width=\"70%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <div>\n <div class=\"flex min-h-[638px] min-w-[320px] flex-col\">\n <Skeleton width=\"100%\" height=\"0.5rem\" class=\"mb-8\" />\n\n <div class=\"flex flex-1 flex-col p-0\">\n <div class=\"flex min-h-full flex-1 flex-col justify-between\">\n <div>\n <Skeleton width=\"70%\" height=\"1.75rem\" class=\"mb-8\" />\n <div class=\"flex flex-col gap-6\">\n <div v-for=\"i in 5\" :key=\"i\" class=\"flex items-center gap-3\">\n <Skeleton width=\"1.25rem\" height=\"1.25rem\" shape=\"circle\" />\n <Skeleton width=\"85%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n\n <div class=\"flex justify-between pt-4\">\n <span />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <div>\n <div class=\"flex min-h-[638px] min-w-[320px] flex-col\">\n <Skeleton width=\"100%\" height=\"0.5rem\" class=\"mb-8\" />\n\n <div class=\"flex flex-1 flex-col p-0\">\n <div class=\"flex min-h-full flex-1 flex-col justify-between\">\n <div>\n <Skeleton width=\"70%\" height=\"1.75rem\" class=\"mb-8\" />\n <div class=\"flex flex-col gap-6\">\n <div v-for=\"i in 5\" :key=\"i\" class=\"flex items-center gap-3\">\n <Skeleton width=\"1.25rem\" height=\"1.25rem\" shape=\"circle\" />\n <Skeleton width=\"85%\" height=\"0.875rem\" />\n </div>\n </div>\n </div>\n\n <div class=\"flex justify-between pt-4\">\n <span />\n <Skeleton width=\"100%\" height=\"2.5rem\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\n</script>\n","<template>\n <CloudLoginViewSkeleton v-if=\"skeletonType === 'login'\" />\n <CloudSurveyViewSkeleton v-else-if=\"skeletonType === 'survey'\" />\n <CloudWaitlistViewSkeleton v-else-if=\"skeletonType === 'waitlist'\" />\n <div v-else-if=\"error\" class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] p-2 text-center lg:w-96\">\n <p class=\"mb-4 text-red-500\">{{ errorMessage }}</p>\n <Button :loading=\"isRetrying\" class=\"w-full\" @click=\"handleRetry\">\n {{\n isRetrying\n ? $t('cloudOnboarding.retrying')\n : $t('cloudOnboarding.retry')\n }}\n </Button>\n </div>\n </div>\n <div v-else class=\"flex items-center justify-center\">\n <ProgressSpinner class=\"h-8 w-8\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useAsyncState } from '@vueuse/core'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport { computed, nextTick, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport {\n getSurveyCompletedStatus,\n getUserCloudStatus\n} from '@/platform/cloud/onboarding/auth'\n\nimport CloudLoginViewSkeleton from './skeletons/CloudLoginViewSkeleton.vue'\nimport CloudSurveyViewSkeleton from './skeletons/CloudSurveyViewSkeleton.vue'\n\nconst router = useRouter()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\nconst { flags } = useFeatureFlags()\nconst onboardingSurveyEnabled = computed(\n () => flags.onboardingSurveyEnabled ?? true\n)\n\nconst skeletonType = ref<'login' | 'survey' | 'waitlist' | 'loading'>('loading')\n\nconst {\n isLoading,\n error,\n execute: checkUserStatus\n} = useAsyncState(\n wrapWithErrorHandlingAsync(async () => {\n await nextTick()\n\n if (!onboardingSurveyEnabled.value) {\n await router.replace({ path: '/' })\n return\n }\n\n const [cloudUserStats, surveyStatus] = await Promise.all([\n getUserCloudStatus(),\n getSurveyCompletedStatus()\n ])\n\n // Navigate based on user status\n if (!cloudUserStats) {\n skeletonType.value = 'login'\n await router.replace({ name: 'cloud-login' })\n return\n }\n\n // Survey is required for all users when feature flag is enabled\n if (!surveyStatus) {\n skeletonType.value = 'survey'\n await router.replace({ name: 'cloud-survey' })\n return\n }\n\n // User is fully onboarded (active or whitelist check disabled)\n globalThis.location.href = '/'\n }),\n null,\n { resetOnExecute: false }\n)\n\nconst errorMessage = computed(() => {\n if (!error.value) return ''\n\n // Provide user-friendly error messages\n const errorStr = error.value.toString().toLowerCase()\n\n if (errorStr.includes('network') || errorStr.includes('fetch')) {\n return 'Connection problem. Please check your internet connection.'\n }\n\n if (errorStr.includes('timeout')) {\n return 'Request timed out. Please try again.'\n }\n\n return 'Unable to check account status. Please try again.'\n})\n\nconst isRetrying = computed(() => isLoading.value && !!error.value)\n\nconst handleRetry = async () => {\n await checkUserStatus()\n}\n</script>\n","<template>\n <CloudLoginViewSkeleton v-if=\"skeletonType === 'login'\" />\n <CloudSurveyViewSkeleton v-else-if=\"skeletonType === 'survey'\" />\n <CloudWaitlistViewSkeleton v-else-if=\"skeletonType === 'waitlist'\" />\n <div v-else-if=\"error\" class=\"flex h-full items-center justify-center p-8\">\n <div class=\"max-w-[100vw] p-2 text-center lg:w-96\">\n <p class=\"mb-4 text-red-500\">{{ errorMessage }}</p>\n <Button :loading=\"isRetrying\" class=\"w-full\" @click=\"handleRetry\">\n {{\n isRetrying\n ? $t('cloudOnboarding.retrying')\n : $t('cloudOnboarding.retry')\n }}\n </Button>\n </div>\n </div>\n <div v-else class=\"flex items-center justify-center\">\n <ProgressSpinner class=\"h-8 w-8\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useAsyncState } from '@vueuse/core'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport { computed, nextTick, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport {\n getSurveyCompletedStatus,\n getUserCloudStatus\n} from '@/platform/cloud/onboarding/auth'\n\nimport CloudLoginViewSkeleton from './skeletons/CloudLoginViewSkeleton.vue'\nimport CloudSurveyViewSkeleton from './skeletons/CloudSurveyViewSkeleton.vue'\n\nconst router = useRouter()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\nconst { flags } = useFeatureFlags()\nconst onboardingSurveyEnabled = computed(\n () => flags.onboardingSurveyEnabled ?? true\n)\n\nconst skeletonType = ref<'login' | 'survey' | 'waitlist' | 'loading'>('loading')\n\nconst {\n isLoading,\n error,\n execute: checkUserStatus\n} = useAsyncState(\n wrapWithErrorHandlingAsync(async () => {\n await nextTick()\n\n if (!onboardingSurveyEnabled.value) {\n await router.replace({ path: '/' })\n return\n }\n\n const [cloudUserStats, surveyStatus] = await Promise.all([\n getUserCloudStatus(),\n getSurveyCompletedStatus()\n ])\n\n // Navigate based on user status\n if (!cloudUserStats) {\n skeletonType.value = 'login'\n await router.replace({ name: 'cloud-login' })\n return\n }\n\n // Survey is required for all users when feature flag is enabled\n if (!surveyStatus) {\n skeletonType.value = 'survey'\n await router.replace({ name: 'cloud-survey' })\n return\n }\n\n // User is fully onboarded (active or whitelist check disabled)\n globalThis.location.href = '/'\n }),\n null,\n { resetOnExecute: false }\n)\n\nconst errorMessage = computed(() => {\n if (!error.value) return ''\n\n // Provide user-friendly error messages\n const errorStr = error.value.toString().toLowerCase()\n\n if (errorStr.includes('network') || errorStr.includes('fetch')) {\n return 'Connection problem. Please check your internet connection.'\n }\n\n if (errorStr.includes('timeout')) {\n return 'Request timed out. Please try again.'\n }\n\n return 'Unable to check account status. Please try again.'\n})\n\nconst isRetrying = computed(() => isLoading.value && !!error.value)\n\nconst handleRetry = async () => {\n await checkUserStatus()\n}\n</script>\n"],"mappings":"+4FKsCA,MAAM,EAAS,EAAA,EACT,CAAE,2BAAA,CAAA,EAA+B,EAAA,EACjC,CAAE,MAAA,CAAA,EAAU,EAAA,EACZ,EAA0B,EAAA,IACxB,EAAM,yBAA2B,EAAA,EAGnC,EAAe,EAAiD,SAAA,EAEhE,CACJ,UAAA,EACA,MAAA,EACA,QAAS,CAAA,EACP,EACF,EAA2B,SAAY,CAGrC,GAFA,MAAM,EAAA,EAEF,CAAC,EAAwB,MAAO,CAClC,MAAM,EAAO,QAAQ,CAAE,KAAM,GAAA,CAAK,EAClC,OAGF,KAAM,CAAC,EAAgB,CAAA,EAAgB,MAAM,QAAQ,IAAI,CACvD,EAAA,EACA,EAAA,CAAyB,CAC1B,EAGD,GAAI,CAAC,EAAgB,CACnB,EAAa,MAAQ,QACrB,MAAM,EAAO,QAAQ,CAAE,KAAM,aAAA,CAAe,EAC5C,OAIF,GAAI,CAAC,EAAc,CACjB,EAAa,MAAQ,SACrB,MAAM,EAAO,QAAQ,CAAE,KAAM,cAAA,CAAgB,EAC7C,OAIF,WAAW,SAAS,KAAO,MAE7B,KACA,CAAE,eAAgB,EAAA,CAAM,EAGpB,EAAe,EAAA,IAAe,CAClC,GAAI,CAAC,EAAM,MAAO,MAAO,GAGzB,MAAM,EAAW,EAAM,MAAM,SAAA,EAAW,YAAA,EAExC,OAAI,EAAS,SAAS,SAAA,GAAc,EAAS,SAAS,OAAA,EAC7C,6DAGL,EAAS,SAAS,SAAA,EACb,uCAGF,sDAGH,EAAa,EAAA,IAAe,EAAU,OAAS,CAAC,CAAC,EAAM,KAAA,EAEvD,EAAc,EAAA,SAAY,CAC9B,MAAM,EAAA,GADY"}
@@ -1,3 +1,3 @@
1
- import{X as U,Z as P,f as B}from"./vendor-primevue-DcMRXJN3.js";import{$o as s,Ba as t,Do as o,Ha as u,Jo as e,Ka as n,Oo as N,Ua as p,Va as r,Xo as A,Ya as O,do as l,go as V,qa as c}from"./vendor-other-DlQF6V2E.js";import"./api-CUAc7rDA.js";import"./remoteConfig-CZcEXsZS.js";import"./colorUtil-CzxntCbX.js";import"./useErrorHandling-CI8_F4yx.js";import{t as d}from"./Button-Do2I1OAA.js";import"./PanelTemplate-BjN5XNg2.js";import{Dr as I,t as b}from"./dialogService-BZ1FmjZL.js";import"./vendor-tiptap-_UqYL7N_.js";import"./vendor-xterm-BU_lcTPR.js";import"./vendor-three-BFcUNSs9.js";import"./markdownRendererUtil-DglHsU8t.js";import"./userStore-BkgQPjq6.js";import{t as z}from"./UserAvatar-Cfg5vVJL.js";var E={class:"flex h-full flex-col"},q={class:"mb-2 text-2xl font-bold"},K={key:0,class:"flex flex-col gap-2"},L={class:"flex flex-col gap-0.5"},X={class:"font-medium"},H={class:"text-muted"},J={class:"flex flex-col gap-0.5"},T={class:"font-medium"},Y={class:"text-muted"},Z={class:"flex flex-col gap-0.5"},j={class:"font-medium"},F={class:"flex items-center gap-1 text-muted"},G={key:2,class:"mt-4 flex flex-col gap-2"},M={key:1,class:"flex flex-col gap-4"},Q={class:"text-smoke-600"},R=O({__name:"UserPanel",setup(W){const f=b(),{loading:m,isLoggedIn:g,isApiKeyLogin:_,isEmailProvider:h,userDisplayName:k,userEmail:x,userPhotoUrl:v,providerName:y,providerIcon:$,handleSignOut:S,handleSignIn:C,handleDeleteAccount:w}=I();return(a,i)=>{const D=V("tooltip");return l(),r(e(B),{value:"User",class:"user-settings-container h-full"},{default:o(()=>[t("div",E,[t("h2",q,s(a.$t("userSettings.title")),1),c(e(U),{class:"mb-3"}),e(g)?(l(),p("div",K,[e(v)?(l(),r(z,{key:0,"photo-url":e(v),shape:"circle",size:"large"},null,8,["photo-url"])):u("",!0),t("div",L,[t("h3",X,s(a.$t("userSettings.name")),1),t("div",H,s(e(k)||a.$t("userSettings.notSet")),1)]),t("div",J,[t("h3",T,s(a.$t("userSettings.email")),1),t("span",Y,s(e(x)),1)]),t("div",Z,[t("h3",j,s(a.$t("userSettings.provider")),1),t("div",F,[t("i",{class:A(e($))},null,2),n(" "+s(e(y))+" ",1),e(h)?N((l(),r(d,{key:0,variant:"muted-textonly",size:"icon-sm",onClick:i[0]||(i[0]=ee=>e(f).showUpdatePasswordDialog())},{default:o(()=>i[1]||(i[1]=[t("i",{class:"pi pi-pen-to-square"},null,-1)])),_:1})),[[D,{value:a.$t("userSettings.updatePassword"),showDelay:300}]]):u("",!0)])]),e(m)?(l(),r(e(P),{key:1,class:"mt-4 h-8 w-8",style:{"--pc-spinner-color":"#000"}})):(l(),p("div",G,[c(d,{class:"w-32",variant:"secondary",onClick:e(S)},{default:o(()=>[i[2]||(i[2]=t("i",{class:"pi pi-sign-out"},null,-1)),n(" "+s(a.$t("auth.signOut.signOut")),1)]),_:1},8,["onClick"]),e(_)?u("",!0):(l(),r(d,{key:0,class:"w-fit",variant:"destructive-textonly",onClick:e(w)},{default:o(()=>[n(s(a.$t("auth.deleteAccount.deleteAccount")),1)]),_:1},8,["onClick"]))]))])):(l(),p("div",M,[t("p",Q,s(a.$t("auth.login.title")),1),c(d,{class:"w-52",variant:"primary",loading:e(m),onClick:e(C)},{default:o(()=>[i[3]||(i[3]=t("i",{class:"pi pi-user"},null,-1)),n(" "+s(a.$t("auth.login.signInOrSignUp")),1)]),_:1},8,["loading","onClick"])]))])]),_:1})}}}),ge=R;export{ge as default};
1
+ import{X as U,Z as P,f as B}from"./vendor-primevue-DcMRXJN3.js";import{$o as s,Ba as t,Do as o,Ha as u,Jo as e,Ka as n,Oo as N,Ua as p,Va as r,Xo as A,Ya as O,do as l,go as V,qa as c}from"./vendor-other-DlQF6V2E.js";import"./api-Dwq2LQIW.js";import"./remoteConfig-CZcEXsZS.js";import"./colorUtil-CzxntCbX.js";import"./useErrorHandling-Cfa5N_7c.js";import{t as d}from"./Button-Do2I1OAA.js";import"./PanelTemplate-BJda9e5J.js";import{Dr as I,t as b}from"./dialogService-YG0RH337.js";import"./vendor-tiptap-_UqYL7N_.js";import"./vendor-xterm-BU_lcTPR.js";import"./vendor-three-BFcUNSs9.js";import"./markdownRendererUtil-DglHsU8t.js";import"./userStore-BAS9m9W6.js";import{t as z}from"./UserAvatar-Cfg5vVJL.js";var E={class:"flex h-full flex-col"},q={class:"mb-2 text-2xl font-bold"},K={key:0,class:"flex flex-col gap-2"},L={class:"flex flex-col gap-0.5"},X={class:"font-medium"},H={class:"text-muted"},J={class:"flex flex-col gap-0.5"},T={class:"font-medium"},Y={class:"text-muted"},Z={class:"flex flex-col gap-0.5"},j={class:"font-medium"},F={class:"flex items-center gap-1 text-muted"},G={key:2,class:"mt-4 flex flex-col gap-2"},M={key:1,class:"flex flex-col gap-4"},Q={class:"text-smoke-600"},R=O({__name:"UserPanel",setup(W){const f=b(),{loading:m,isLoggedIn:g,isApiKeyLogin:_,isEmailProvider:h,userDisplayName:k,userEmail:x,userPhotoUrl:v,providerName:y,providerIcon:$,handleSignOut:S,handleSignIn:C,handleDeleteAccount:w}=I();return(a,i)=>{const D=V("tooltip");return l(),r(e(B),{value:"User",class:"user-settings-container h-full"},{default:o(()=>[t("div",E,[t("h2",q,s(a.$t("userSettings.title")),1),c(e(U),{class:"mb-3"}),e(g)?(l(),p("div",K,[e(v)?(l(),r(z,{key:0,"photo-url":e(v),shape:"circle",size:"large"},null,8,["photo-url"])):u("",!0),t("div",L,[t("h3",X,s(a.$t("userSettings.name")),1),t("div",H,s(e(k)||a.$t("userSettings.notSet")),1)]),t("div",J,[t("h3",T,s(a.$t("userSettings.email")),1),t("span",Y,s(e(x)),1)]),t("div",Z,[t("h3",j,s(a.$t("userSettings.provider")),1),t("div",F,[t("i",{class:A(e($))},null,2),n(" "+s(e(y))+" ",1),e(h)?N((l(),r(d,{key:0,variant:"muted-textonly",size:"icon-sm",onClick:i[0]||(i[0]=ee=>e(f).showUpdatePasswordDialog())},{default:o(()=>i[1]||(i[1]=[t("i",{class:"pi pi-pen-to-square"},null,-1)])),_:1})),[[D,{value:a.$t("userSettings.updatePassword"),showDelay:300}]]):u("",!0)])]),e(m)?(l(),r(e(P),{key:1,class:"mt-4 h-8 w-8",style:{"--pc-spinner-color":"#000"}})):(l(),p("div",G,[c(d,{class:"w-32",variant:"secondary",onClick:e(S)},{default:o(()=>[i[2]||(i[2]=t("i",{class:"pi pi-sign-out"},null,-1)),n(" "+s(a.$t("auth.signOut.signOut")),1)]),_:1},8,["onClick"]),e(_)?u("",!0):(l(),r(d,{key:0,class:"w-fit",variant:"destructive-textonly",onClick:e(w)},{default:o(()=>[n(s(a.$t("auth.deleteAccount.deleteAccount")),1)]),_:1},8,["onClick"]))]))])):(l(),p("div",M,[t("p",Q,s(a.$t("auth.login.title")),1),c(d,{class:"w-52",variant:"primary",loading:e(m),onClick:e(C)},{default:o(()=>[i[3]||(i[3]=t("i",{class:"pi pi-user"},null,-1)),n(" "+s(a.$t("auth.login.signInOrSignUp")),1)]),_:1},8,["loading","onClick"])]))])]),_:1})}}}),ge=R;export{ge as default};
2
2
 
3
- //# sourceMappingURL=UserPanel-Su6NtJ5q.js.map
3
+ //# sourceMappingURL=UserPanel-7N9QknQj.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"UserPanel-Su6NtJ5q.js","names":[],"sources":["../../src/components/dialog/content/setting/UserPanel.vue","../../src/components/dialog/content/setting/UserPanel.vue"],"sourcesContent":["<template>\n <TabPanel value=\"User\" class=\"user-settings-container h-full\">\n <div class=\"flex h-full flex-col\">\n <h2 class=\"mb-2 text-2xl font-bold\">{{ $t('userSettings.title') }}</h2>\n <Divider class=\"mb-3\" />\n\n <!-- Normal User Panel -->\n <div v-if=\"isLoggedIn\" class=\"flex flex-col gap-2\">\n <UserAvatar\n v-if=\"userPhotoUrl\"\n :photo-url=\"userPhotoUrl\"\n shape=\"circle\"\n size=\"large\"\n />\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.name') }}\n </h3>\n <div class=\"text-muted\">\n {{ userDisplayName || $t('userSettings.notSet') }}\n </div>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.email') }}\n </h3>\n <span class=\"text-muted\">\n {{ userEmail }}\n </span>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.provider') }}\n </h3>\n <div class=\"flex items-center gap-1 text-muted\">\n <i :class=\"providerIcon\" />\n {{ providerName }}\n <Button\n v-if=\"isEmailProvider\"\n v-tooltip=\"{\n value: $t('userSettings.updatePassword'),\n showDelay: 300\n }\"\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n @click=\"dialogService.showUpdatePasswordDialog()\"\n >\n <i class=\"pi pi-pen-to-square\" />\n </Button>\n </div>\n </div>\n\n <ProgressSpinner\n v-if=\"loading\"\n class=\"mt-4 h-8 w-8\"\n style=\"--pc-spinner-color: #000\"\n />\n <div v-else class=\"mt-4 flex flex-col gap-2\">\n <Button class=\"w-32\" variant=\"secondary\" @click=\"handleSignOut\">\n <i class=\"pi pi-sign-out\" />\n {{ $t('auth.signOut.signOut') }}\n </Button>\n <Button\n v-if=\"!isApiKeyLogin\"\n class=\"w-fit\"\n variant=\"destructive-textonly\"\n @click=\"handleDeleteAccount\"\n >\n {{ $t('auth.deleteAccount.deleteAccount') }}\n </Button>\n </div>\n </div>\n\n <!-- Login Section -->\n <div v-else class=\"flex flex-col gap-4\">\n <p class=\"text-smoke-600\">\n {{ $t('auth.login.title') }}\n </p>\n\n <Button\n class=\"w-52\"\n variant=\"primary\"\n :loading=\"loading\"\n @click=\"handleSignIn\"\n >\n <i class=\"pi pi-user\" />\n {{ $t('auth.login.signInOrSignUp') }}\n </Button>\n </div>\n </div>\n </TabPanel>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport TabPanel from 'primevue/tabpanel'\n\nimport UserAvatar from '@/components/common/UserAvatar.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useDialogService } from '@/services/dialogService'\n\nconst dialogService = useDialogService()\nconst {\n loading,\n isLoggedIn,\n isApiKeyLogin,\n isEmailProvider,\n userDisplayName,\n userEmail,\n userPhotoUrl,\n providerName,\n providerIcon,\n handleSignOut,\n handleSignIn,\n handleDeleteAccount\n} = useCurrentUser()\n</script>\n","<template>\n <TabPanel value=\"User\" class=\"user-settings-container h-full\">\n <div class=\"flex h-full flex-col\">\n <h2 class=\"mb-2 text-2xl font-bold\">{{ $t('userSettings.title') }}</h2>\n <Divider class=\"mb-3\" />\n\n <!-- Normal User Panel -->\n <div v-if=\"isLoggedIn\" class=\"flex flex-col gap-2\">\n <UserAvatar\n v-if=\"userPhotoUrl\"\n :photo-url=\"userPhotoUrl\"\n shape=\"circle\"\n size=\"large\"\n />\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.name') }}\n </h3>\n <div class=\"text-muted\">\n {{ userDisplayName || $t('userSettings.notSet') }}\n </div>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.email') }}\n </h3>\n <span class=\"text-muted\">\n {{ userEmail }}\n </span>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.provider') }}\n </h3>\n <div class=\"flex items-center gap-1 text-muted\">\n <i :class=\"providerIcon\" />\n {{ providerName }}\n <Button\n v-if=\"isEmailProvider\"\n v-tooltip=\"{\n value: $t('userSettings.updatePassword'),\n showDelay: 300\n }\"\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n @click=\"dialogService.showUpdatePasswordDialog()\"\n >\n <i class=\"pi pi-pen-to-square\" />\n </Button>\n </div>\n </div>\n\n <ProgressSpinner\n v-if=\"loading\"\n class=\"mt-4 h-8 w-8\"\n style=\"--pc-spinner-color: #000\"\n />\n <div v-else class=\"mt-4 flex flex-col gap-2\">\n <Button class=\"w-32\" variant=\"secondary\" @click=\"handleSignOut\">\n <i class=\"pi pi-sign-out\" />\n {{ $t('auth.signOut.signOut') }}\n </Button>\n <Button\n v-if=\"!isApiKeyLogin\"\n class=\"w-fit\"\n variant=\"destructive-textonly\"\n @click=\"handleDeleteAccount\"\n >\n {{ $t('auth.deleteAccount.deleteAccount') }}\n </Button>\n </div>\n </div>\n\n <!-- Login Section -->\n <div v-else class=\"flex flex-col gap-4\">\n <p class=\"text-smoke-600\">\n {{ $t('auth.login.title') }}\n </p>\n\n <Button\n class=\"w-52\"\n variant=\"primary\"\n :loading=\"loading\"\n @click=\"handleSignIn\"\n >\n <i class=\"pi pi-user\" />\n {{ $t('auth.login.signInOrSignUp') }}\n </Button>\n </div>\n </div>\n </TabPanel>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport TabPanel from 'primevue/tabpanel'\n\nimport UserAvatar from '@/components/common/UserAvatar.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useDialogService } from '@/services/dialogService'\n\nconst dialogService = useDialogService()\nconst {\n loading,\n isLoggedIn,\n isApiKeyLogin,\n isEmailProvider,\n userDisplayName,\n userEmail,\n userPhotoUrl,\n providerName,\n providerIcon,\n handleSignOut,\n handleSignIn,\n handleDeleteAccount\n} = useCurrentUser()\n</script>\n"],"mappings":"0sCC0GA,MAAM,EAAgB,EAAA,EAChB,CACJ,QAAA,EACA,WAAA,EACA,cAAA,EACA,gBAAA,EACA,gBAAA,EACA,UAAA,EACA,aAAA,EACA,aAAA,EACA,aAAA,EACA,cAAA,EACA,aAAA,EACA,oBAAA,CAAA,EACE,EAAA"}
1
+ {"version":3,"file":"UserPanel-7N9QknQj.js","names":[],"sources":["../../src/components/dialog/content/setting/UserPanel.vue","../../src/components/dialog/content/setting/UserPanel.vue"],"sourcesContent":["<template>\n <TabPanel value=\"User\" class=\"user-settings-container h-full\">\n <div class=\"flex h-full flex-col\">\n <h2 class=\"mb-2 text-2xl font-bold\">{{ $t('userSettings.title') }}</h2>\n <Divider class=\"mb-3\" />\n\n <!-- Normal User Panel -->\n <div v-if=\"isLoggedIn\" class=\"flex flex-col gap-2\">\n <UserAvatar\n v-if=\"userPhotoUrl\"\n :photo-url=\"userPhotoUrl\"\n shape=\"circle\"\n size=\"large\"\n />\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.name') }}\n </h3>\n <div class=\"text-muted\">\n {{ userDisplayName || $t('userSettings.notSet') }}\n </div>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.email') }}\n </h3>\n <span class=\"text-muted\">\n {{ userEmail }}\n </span>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.provider') }}\n </h3>\n <div class=\"flex items-center gap-1 text-muted\">\n <i :class=\"providerIcon\" />\n {{ providerName }}\n <Button\n v-if=\"isEmailProvider\"\n v-tooltip=\"{\n value: $t('userSettings.updatePassword'),\n showDelay: 300\n }\"\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n @click=\"dialogService.showUpdatePasswordDialog()\"\n >\n <i class=\"pi pi-pen-to-square\" />\n </Button>\n </div>\n </div>\n\n <ProgressSpinner\n v-if=\"loading\"\n class=\"mt-4 h-8 w-8\"\n style=\"--pc-spinner-color: #000\"\n />\n <div v-else class=\"mt-4 flex flex-col gap-2\">\n <Button class=\"w-32\" variant=\"secondary\" @click=\"handleSignOut\">\n <i class=\"pi pi-sign-out\" />\n {{ $t('auth.signOut.signOut') }}\n </Button>\n <Button\n v-if=\"!isApiKeyLogin\"\n class=\"w-fit\"\n variant=\"destructive-textonly\"\n @click=\"handleDeleteAccount\"\n >\n {{ $t('auth.deleteAccount.deleteAccount') }}\n </Button>\n </div>\n </div>\n\n <!-- Login Section -->\n <div v-else class=\"flex flex-col gap-4\">\n <p class=\"text-smoke-600\">\n {{ $t('auth.login.title') }}\n </p>\n\n <Button\n class=\"w-52\"\n variant=\"primary\"\n :loading=\"loading\"\n @click=\"handleSignIn\"\n >\n <i class=\"pi pi-user\" />\n {{ $t('auth.login.signInOrSignUp') }}\n </Button>\n </div>\n </div>\n </TabPanel>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport TabPanel from 'primevue/tabpanel'\n\nimport UserAvatar from '@/components/common/UserAvatar.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useDialogService } from '@/services/dialogService'\n\nconst dialogService = useDialogService()\nconst {\n loading,\n isLoggedIn,\n isApiKeyLogin,\n isEmailProvider,\n userDisplayName,\n userEmail,\n userPhotoUrl,\n providerName,\n providerIcon,\n handleSignOut,\n handleSignIn,\n handleDeleteAccount\n} = useCurrentUser()\n</script>\n","<template>\n <TabPanel value=\"User\" class=\"user-settings-container h-full\">\n <div class=\"flex h-full flex-col\">\n <h2 class=\"mb-2 text-2xl font-bold\">{{ $t('userSettings.title') }}</h2>\n <Divider class=\"mb-3\" />\n\n <!-- Normal User Panel -->\n <div v-if=\"isLoggedIn\" class=\"flex flex-col gap-2\">\n <UserAvatar\n v-if=\"userPhotoUrl\"\n :photo-url=\"userPhotoUrl\"\n shape=\"circle\"\n size=\"large\"\n />\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.name') }}\n </h3>\n <div class=\"text-muted\">\n {{ userDisplayName || $t('userSettings.notSet') }}\n </div>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.email') }}\n </h3>\n <span class=\"text-muted\">\n {{ userEmail }}\n </span>\n </div>\n\n <div class=\"flex flex-col gap-0.5\">\n <h3 class=\"font-medium\">\n {{ $t('userSettings.provider') }}\n </h3>\n <div class=\"flex items-center gap-1 text-muted\">\n <i :class=\"providerIcon\" />\n {{ providerName }}\n <Button\n v-if=\"isEmailProvider\"\n v-tooltip=\"{\n value: $t('userSettings.updatePassword'),\n showDelay: 300\n }\"\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n @click=\"dialogService.showUpdatePasswordDialog()\"\n >\n <i class=\"pi pi-pen-to-square\" />\n </Button>\n </div>\n </div>\n\n <ProgressSpinner\n v-if=\"loading\"\n class=\"mt-4 h-8 w-8\"\n style=\"--pc-spinner-color: #000\"\n />\n <div v-else class=\"mt-4 flex flex-col gap-2\">\n <Button class=\"w-32\" variant=\"secondary\" @click=\"handleSignOut\">\n <i class=\"pi pi-sign-out\" />\n {{ $t('auth.signOut.signOut') }}\n </Button>\n <Button\n v-if=\"!isApiKeyLogin\"\n class=\"w-fit\"\n variant=\"destructive-textonly\"\n @click=\"handleDeleteAccount\"\n >\n {{ $t('auth.deleteAccount.deleteAccount') }}\n </Button>\n </div>\n </div>\n\n <!-- Login Section -->\n <div v-else class=\"flex flex-col gap-4\">\n <p class=\"text-smoke-600\">\n {{ $t('auth.login.title') }}\n </p>\n\n <Button\n class=\"w-52\"\n variant=\"primary\"\n :loading=\"loading\"\n @click=\"handleSignIn\"\n >\n <i class=\"pi pi-user\" />\n {{ $t('auth.login.signInOrSignUp') }}\n </Button>\n </div>\n </div>\n </TabPanel>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport TabPanel from 'primevue/tabpanel'\n\nimport UserAvatar from '@/components/common/UserAvatar.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useDialogService } from '@/services/dialogService'\n\nconst dialogService = useDialogService()\nconst {\n loading,\n isLoggedIn,\n isApiKeyLogin,\n isEmailProvider,\n userDisplayName,\n userEmail,\n userPhotoUrl,\n providerName,\n providerIcon,\n handleSignOut,\n handleSignIn,\n handleDeleteAccount\n} = useCurrentUser()\n</script>\n"],"mappings":"0sCC0GA,MAAM,EAAgB,EAAA,EAChB,CACJ,QAAA,EACA,WAAA,EACA,cAAA,EACA,gBAAA,EACA,gBAAA,EACA,UAAA,EACA,aAAA,EACA,aAAA,EACA,aAAA,EACA,cAAA,EACA,aAAA,EACA,oBAAA,CAAA,EACE,EAAA"}
@@ -1,3 +1,3 @@
1
- var S=Object.defineProperty;var U=(p,s)=>S(p,"name",{value:s,configurable:!0});import{X as $,it as b,tt as N,ut as B}from"./vendor-primevue-DcMRXJN3.js";import{$o as o,Ba as t,Do as d,Ha as k,Jo as l,Ka as g,Va as y,Ya as C,co as E,do as V,ja as z,qa as i,za as c,zo as m}from"./vendor-other-DlQF6V2E.js";import{c as K}from"./vendor-vue-D9IUuwPJ.js";import"./api-CUAc7rDA.js";import"./colorUtil-CzxntCbX.js";import{t as D}from"./Button-Do2I1OAA.js";import{t as J}from"./userStore-BkgQPjq6.js";import{t as T}from"./BaseViewTemplate-DA6zfigT.js";var j={id:"comfy-user-selection",class:"relative min-w-84 rounded-lg bg-(--comfy-menu-bg) p-5 px-10 shadow-lg"},q={class:"flex w-full flex-col items-center"},H={class:"flex w-full flex-col gap-2"},I={for:"new-user-input"},M={class:"flex w-full flex-col gap-2"},O={for:"existing-user-select"},R={class:"mt-5"},X=C({__name:"UserSelectView",setup(p){const s=J(),h=K(),u=m(null),a=m(""),f=m(""),v=c(()=>a.value.trim()!==""),x=c(()=>s.users.find(e=>e.username===a.value)?`User "${a.value}" already exists`:""),w=c(()=>x.value||f.value),_=U(async()=>{try{const e=v.value?await s.createUser(a.value):u.value;if(!e)throw new Error("No user selected");await s.login(e),await h.push("/")}catch(e){f.value=e instanceof Error?e.message:JSON.stringify(e)}},"login");return E(async()=>{s.initialized||await s.initialize()}),(e,r)=>(V(),y(T,{dark:""},{default:d(()=>[t("main",j,[r[2]||(r[2]=t("h1",{class:"my-2.5 mb-7 font-normal"},"ComfyUI",-1)),t("div",q,[t("div",H,[t("label",I,o(e.$t("userSelect.newUser"))+":",1),i(l(b),{id:"new-user-input",modelValue:a.value,"onUpdate:modelValue":r[0]||(r[0]=n=>a.value=n),placeholder:e.$t("userSelect.enterUsername"),onKeyup:z(_,["enter"])},null,8,["modelValue","placeholder"])]),i(l($)),t("div",M,[t("label",O,o(e.$t("userSelect.existingUser"))+":",1),i(l(N),{modelValue:u.value,"onUpdate:modelValue":r[1]||(r[1]=n=>u.value=n),class:"w-full","input-id":"existing-user-select",options:l(s).users,"option-label":"username",placeholder:e.$t("userSelect.selectUser"),disabled:v.value},null,8,["modelValue","options","placeholder","disabled"]),w.value?(V(),y(l(B),{key:0,severity:"error"},{default:d(()=>[g(o(w.value),1)]),_:1})):k("",!0)]),t("footer",R,[i(D,{onClick:_},{default:d(()=>[g(o(e.$t("userSelect.next")),1)]),_:1})])])])]),_:1}))}}),ee=X;export{ee as default};
1
+ var S=Object.defineProperty;var U=(p,s)=>S(p,"name",{value:s,configurable:!0});import{X as $,it as b,tt as N,ut as B}from"./vendor-primevue-DcMRXJN3.js";import{$o as o,Ba as t,Do as d,Ha as k,Jo as l,Ka as g,Va as y,Ya as C,co as E,do as V,ja as z,qa as i,za as c,zo as m}from"./vendor-other-DlQF6V2E.js";import{c as K}from"./vendor-vue-D9IUuwPJ.js";import"./api-Dwq2LQIW.js";import"./colorUtil-CzxntCbX.js";import{t as D}from"./Button-Do2I1OAA.js";import{t as J}from"./userStore-BAS9m9W6.js";import{t as T}from"./BaseViewTemplate-CjODF2hh.js";var j={id:"comfy-user-selection",class:"relative min-w-84 rounded-lg bg-(--comfy-menu-bg) p-5 px-10 shadow-lg"},q={class:"flex w-full flex-col items-center"},H={class:"flex w-full flex-col gap-2"},I={for:"new-user-input"},M={class:"flex w-full flex-col gap-2"},O={for:"existing-user-select"},R={class:"mt-5"},X=C({__name:"UserSelectView",setup(p){const s=J(),h=K(),u=m(null),a=m(""),f=m(""),v=c(()=>a.value.trim()!==""),x=c(()=>s.users.find(e=>e.username===a.value)?`User "${a.value}" already exists`:""),w=c(()=>x.value||f.value),_=U(async()=>{try{const e=v.value?await s.createUser(a.value):u.value;if(!e)throw new Error("No user selected");await s.login(e),await h.push("/")}catch(e){f.value=e instanceof Error?e.message:JSON.stringify(e)}},"login");return E(async()=>{s.initialized||await s.initialize()}),(e,r)=>(V(),y(T,{dark:""},{default:d(()=>[t("main",j,[r[2]||(r[2]=t("h1",{class:"my-2.5 mb-7 font-normal"},"ComfyUI",-1)),t("div",q,[t("div",H,[t("label",I,o(e.$t("userSelect.newUser"))+":",1),i(l(b),{id:"new-user-input",modelValue:a.value,"onUpdate:modelValue":r[0]||(r[0]=n=>a.value=n),placeholder:e.$t("userSelect.enterUsername"),onKeyup:z(_,["enter"])},null,8,["modelValue","placeholder"])]),i(l($)),t("div",M,[t("label",O,o(e.$t("userSelect.existingUser"))+":",1),i(l(N),{modelValue:u.value,"onUpdate:modelValue":r[1]||(r[1]=n=>u.value=n),class:"w-full","input-id":"existing-user-select",options:l(s).users,"option-label":"username",placeholder:e.$t("userSelect.selectUser"),disabled:v.value},null,8,["modelValue","options","placeholder","disabled"]),w.value?(V(),y(l(B),{key:0,severity:"error"},{default:d(()=>[g(o(w.value),1)]),_:1})):k("",!0)]),t("footer",R,[i(D,{onClick:_},{default:d(()=>[g(o(e.$t("userSelect.next")),1)]),_:1})])])])]),_:1}))}}),ee=X;export{ee as default};
2
2
 
3
- //# sourceMappingURL=UserSelectView-C5LBOPcv.js.map
3
+ //# sourceMappingURL=UserSelectView-BYjOkfSa.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"UserSelectView-C5LBOPcv.js","names":[],"sources":["../../src/views/UserSelectView.vue","../../src/views/UserSelectView.vue"],"sourcesContent":["<template>\n <BaseViewTemplate dark>\n <main\n id=\"comfy-user-selection\"\n class=\"relative min-w-84 rounded-lg bg-(--comfy-menu-bg) p-5 px-10 shadow-lg\"\n >\n <h1 class=\"my-2.5 mb-7 font-normal\">ComfyUI</h1>\n <div class=\"flex w-full flex-col items-center\">\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"new-user-input\">{{ $t('userSelect.newUser') }}:</label>\n <InputText\n id=\"new-user-input\"\n v-model=\"newUsername\"\n :placeholder=\"$t('userSelect.enterUsername')\"\n @keyup.enter=\"login\"\n />\n </div>\n <Divider />\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"existing-user-select\"\n >{{ $t('userSelect.existingUser') }}:</label\n >\n <Select\n v-model=\"selectedUser\"\n class=\"w-full\"\n input-id=\"existing-user-select\"\n :options=\"userStore.users\"\n option-label=\"username\"\n :placeholder=\"$t('userSelect.selectUser')\"\n :disabled=\"createNewUser\"\n />\n <Message v-if=\"error\" severity=\"error\">\n {{ error }}\n </Message>\n </div>\n <footer class=\"mt-5\">\n <Button @click=\"login\">{{ $t('userSelect.next') }}</Button>\n </footer>\n </div>\n </main>\n </BaseViewTemplate>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport InputText from 'primevue/inputtext'\nimport Message from 'primevue/message'\nimport Select from 'primevue/select'\nimport { computed, onMounted, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport type { User } from '@/stores/userStore'\nimport { useUserStore } from '@/stores/userStore'\nimport BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'\n\nconst userStore = useUserStore()\nconst router = useRouter()\n\nconst selectedUser = ref<User | null>(null)\nconst newUsername = ref('')\nconst loginError = ref('')\n\nconst createNewUser = computed(() => newUsername.value.trim() !== '')\nconst newUserExistsError = computed(() => {\n return userStore.users.find((user) => user.username === newUsername.value)\n ? `User \"${newUsername.value}\" already exists`\n : ''\n})\nconst error = computed(() => newUserExistsError.value || loginError.value)\n\nconst login = async () => {\n try {\n const user = createNewUser.value\n ? await userStore.createUser(newUsername.value)\n : selectedUser.value\n\n if (!user) {\n throw new Error('No user selected')\n }\n\n await userStore.login(user)\n await router.push('/')\n } catch (err) {\n loginError.value = err instanceof Error ? err.message : JSON.stringify(err)\n }\n}\n\nonMounted(async () => {\n if (!userStore.initialized) {\n await userStore.initialize()\n }\n})\n</script>\n","<template>\n <BaseViewTemplate dark>\n <main\n id=\"comfy-user-selection\"\n class=\"relative min-w-84 rounded-lg bg-(--comfy-menu-bg) p-5 px-10 shadow-lg\"\n >\n <h1 class=\"my-2.5 mb-7 font-normal\">ComfyUI</h1>\n <div class=\"flex w-full flex-col items-center\">\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"new-user-input\">{{ $t('userSelect.newUser') }}:</label>\n <InputText\n id=\"new-user-input\"\n v-model=\"newUsername\"\n :placeholder=\"$t('userSelect.enterUsername')\"\n @keyup.enter=\"login\"\n />\n </div>\n <Divider />\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"existing-user-select\"\n >{{ $t('userSelect.existingUser') }}:</label\n >\n <Select\n v-model=\"selectedUser\"\n class=\"w-full\"\n input-id=\"existing-user-select\"\n :options=\"userStore.users\"\n option-label=\"username\"\n :placeholder=\"$t('userSelect.selectUser')\"\n :disabled=\"createNewUser\"\n />\n <Message v-if=\"error\" severity=\"error\">\n {{ error }}\n </Message>\n </div>\n <footer class=\"mt-5\">\n <Button @click=\"login\">{{ $t('userSelect.next') }}</Button>\n </footer>\n </div>\n </main>\n </BaseViewTemplate>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport InputText from 'primevue/inputtext'\nimport Message from 'primevue/message'\nimport Select from 'primevue/select'\nimport { computed, onMounted, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport type { User } from '@/stores/userStore'\nimport { useUserStore } from '@/stores/userStore'\nimport BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'\n\nconst userStore = useUserStore()\nconst router = useRouter()\n\nconst selectedUser = ref<User | null>(null)\nconst newUsername = ref('')\nconst loginError = ref('')\n\nconst createNewUser = computed(() => newUsername.value.trim() !== '')\nconst newUserExistsError = computed(() => {\n return userStore.users.find((user) => user.username === newUsername.value)\n ? `User \"${newUsername.value}\" already exists`\n : ''\n})\nconst error = computed(() => newUserExistsError.value || loginError.value)\n\nconst login = async () => {\n try {\n const user = createNewUser.value\n ? await userStore.createUser(newUsername.value)\n : selectedUser.value\n\n if (!user) {\n throw new Error('No user selected')\n }\n\n await userStore.login(user)\n await router.push('/')\n } catch (err) {\n loginError.value = err instanceof Error ? err.message : JSON.stringify(err)\n }\n}\n\nonMounted(async () => {\n if (!userStore.initialized) {\n await userStore.initialize()\n }\n})\n</script>\n"],"mappings":"23BCwDA,MAAM,EAAY,EAAA,EACZ,EAAS,EAAA,EAET,EAAe,EAAiB,IAAA,EAChC,EAAc,EAAI,EAAA,EAClB,EAAa,EAAI,EAAA,EAEjB,EAAgB,EAAA,IAAe,EAAY,MAAM,KAAA,IAAW,EAAA,EAC5D,EAAqB,EAAA,IAClB,EAAU,MAAM,KAAM,GAAS,EAAK,WAAa,EAAY,KAAA,EAChE,SAAS,EAAY,KAAA,mBACrB,IAEA,EAAQ,EAAA,IAAe,EAAmB,OAAS,EAAW,KAAA,EAE9D,EAAQ,EAAA,SAAY,CACxB,GAAI,CACF,MAAM,EAAO,EAAc,MACvB,MAAM,EAAU,WAAW,EAAY,KAAA,EACvC,EAAa,MAEjB,GAAI,CAAC,EACH,MAAM,IAAI,MAAM,kBAAA,EAGlB,MAAM,EAAU,MAAM,CAAA,EACtB,MAAM,EAAO,KAAK,GAAA,QACX,EAAK,CACZ,EAAW,MAAQ,aAAe,MAAQ,EAAI,QAAU,KAAK,UAAU,CAAA,IAb7D,SAiBd,OAAA,EAAU,SAAY,CACf,EAAU,aACb,MAAM,EAAU,WAAA"}
1
+ {"version":3,"file":"UserSelectView-BYjOkfSa.js","names":[],"sources":["../../src/views/UserSelectView.vue","../../src/views/UserSelectView.vue"],"sourcesContent":["<template>\n <BaseViewTemplate dark>\n <main\n id=\"comfy-user-selection\"\n class=\"relative min-w-84 rounded-lg bg-(--comfy-menu-bg) p-5 px-10 shadow-lg\"\n >\n <h1 class=\"my-2.5 mb-7 font-normal\">ComfyUI</h1>\n <div class=\"flex w-full flex-col items-center\">\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"new-user-input\">{{ $t('userSelect.newUser') }}:</label>\n <InputText\n id=\"new-user-input\"\n v-model=\"newUsername\"\n :placeholder=\"$t('userSelect.enterUsername')\"\n @keyup.enter=\"login\"\n />\n </div>\n <Divider />\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"existing-user-select\"\n >{{ $t('userSelect.existingUser') }}:</label\n >\n <Select\n v-model=\"selectedUser\"\n class=\"w-full\"\n input-id=\"existing-user-select\"\n :options=\"userStore.users\"\n option-label=\"username\"\n :placeholder=\"$t('userSelect.selectUser')\"\n :disabled=\"createNewUser\"\n />\n <Message v-if=\"error\" severity=\"error\">\n {{ error }}\n </Message>\n </div>\n <footer class=\"mt-5\">\n <Button @click=\"login\">{{ $t('userSelect.next') }}</Button>\n </footer>\n </div>\n </main>\n </BaseViewTemplate>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport InputText from 'primevue/inputtext'\nimport Message from 'primevue/message'\nimport Select from 'primevue/select'\nimport { computed, onMounted, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport type { User } from '@/stores/userStore'\nimport { useUserStore } from '@/stores/userStore'\nimport BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'\n\nconst userStore = useUserStore()\nconst router = useRouter()\n\nconst selectedUser = ref<User | null>(null)\nconst newUsername = ref('')\nconst loginError = ref('')\n\nconst createNewUser = computed(() => newUsername.value.trim() !== '')\nconst newUserExistsError = computed(() => {\n return userStore.users.find((user) => user.username === newUsername.value)\n ? `User \"${newUsername.value}\" already exists`\n : ''\n})\nconst error = computed(() => newUserExistsError.value || loginError.value)\n\nconst login = async () => {\n try {\n const user = createNewUser.value\n ? await userStore.createUser(newUsername.value)\n : selectedUser.value\n\n if (!user) {\n throw new Error('No user selected')\n }\n\n await userStore.login(user)\n await router.push('/')\n } catch (err) {\n loginError.value = err instanceof Error ? err.message : JSON.stringify(err)\n }\n}\n\nonMounted(async () => {\n if (!userStore.initialized) {\n await userStore.initialize()\n }\n})\n</script>\n","<template>\n <BaseViewTemplate dark>\n <main\n id=\"comfy-user-selection\"\n class=\"relative min-w-84 rounded-lg bg-(--comfy-menu-bg) p-5 px-10 shadow-lg\"\n >\n <h1 class=\"my-2.5 mb-7 font-normal\">ComfyUI</h1>\n <div class=\"flex w-full flex-col items-center\">\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"new-user-input\">{{ $t('userSelect.newUser') }}:</label>\n <InputText\n id=\"new-user-input\"\n v-model=\"newUsername\"\n :placeholder=\"$t('userSelect.enterUsername')\"\n @keyup.enter=\"login\"\n />\n </div>\n <Divider />\n <div class=\"flex w-full flex-col gap-2\">\n <label for=\"existing-user-select\"\n >{{ $t('userSelect.existingUser') }}:</label\n >\n <Select\n v-model=\"selectedUser\"\n class=\"w-full\"\n input-id=\"existing-user-select\"\n :options=\"userStore.users\"\n option-label=\"username\"\n :placeholder=\"$t('userSelect.selectUser')\"\n :disabled=\"createNewUser\"\n />\n <Message v-if=\"error\" severity=\"error\">\n {{ error }}\n </Message>\n </div>\n <footer class=\"mt-5\">\n <Button @click=\"login\">{{ $t('userSelect.next') }}</Button>\n </footer>\n </div>\n </main>\n </BaseViewTemplate>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport InputText from 'primevue/inputtext'\nimport Message from 'primevue/message'\nimport Select from 'primevue/select'\nimport { computed, onMounted, ref } from 'vue'\nimport { useRouter } from 'vue-router'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport type { User } from '@/stores/userStore'\nimport { useUserStore } from '@/stores/userStore'\nimport BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'\n\nconst userStore = useUserStore()\nconst router = useRouter()\n\nconst selectedUser = ref<User | null>(null)\nconst newUsername = ref('')\nconst loginError = ref('')\n\nconst createNewUser = computed(() => newUsername.value.trim() !== '')\nconst newUserExistsError = computed(() => {\n return userStore.users.find((user) => user.username === newUsername.value)\n ? `User \"${newUsername.value}\" already exists`\n : ''\n})\nconst error = computed(() => newUserExistsError.value || loginError.value)\n\nconst login = async () => {\n try {\n const user = createNewUser.value\n ? await userStore.createUser(newUsername.value)\n : selectedUser.value\n\n if (!user) {\n throw new Error('No user selected')\n }\n\n await userStore.login(user)\n await router.push('/')\n } catch (err) {\n loginError.value = err instanceof Error ? err.message : JSON.stringify(err)\n }\n}\n\nonMounted(async () => {\n if (!userStore.initialized) {\n await userStore.initialize()\n }\n})\n</script>\n"],"mappings":"23BCwDA,MAAM,EAAY,EAAA,EACZ,EAAS,EAAA,EAET,EAAe,EAAiB,IAAA,EAChC,EAAc,EAAI,EAAA,EAClB,EAAa,EAAI,EAAA,EAEjB,EAAgB,EAAA,IAAe,EAAY,MAAM,KAAA,IAAW,EAAA,EAC5D,EAAqB,EAAA,IAClB,EAAU,MAAM,KAAM,GAAS,EAAK,WAAa,EAAY,KAAA,EAChE,SAAS,EAAY,KAAA,mBACrB,IAEA,EAAQ,EAAA,IAAe,EAAmB,OAAS,EAAW,KAAA,EAE9D,EAAQ,EAAA,SAAY,CACxB,GAAI,CACF,MAAM,EAAO,EAAc,MACvB,MAAM,EAAU,WAAW,EAAY,KAAA,EACvC,EAAa,MAEjB,GAAI,CAAC,EACH,MAAM,IAAI,MAAM,kBAAA,EAGlB,MAAM,EAAU,MAAM,CAAA,EACtB,MAAM,EAAO,KAAK,GAAA,QACX,EAAK,CACZ,EAAW,MAAQ,aAAe,MAAQ,EAAI,QAAU,KAAK,UAAU,CAAA,IAb7D,SAiBd,OAAA,EAAU,SAAY,CACf,EAAU,aACb,MAAM,EAAU,WAAA"}
@@ -1,3 +1,3 @@
1
- var h=Object.defineProperty;var u=(s,l)=>h(s,"name",{value:l,configurable:!0});import{P as b,et as C}from"./vendor-primevue-DcMRXJN3.js";import{$o as r,Ba as e,Do as w,Ha as c,Jo as p,Ka as f,Pa as y,Ua as d,Va as V,Xo as k,Ya as $,do as a,po as z,qa as D,xo as B,za as M,zo as P}from"./vendor-other-DlQF6V2E.js";import"./api-CUAc7rDA.js";import"./remoteConfig-CZcEXsZS.js";import"./colorUtil-CzxntCbX.js";import"./useErrorHandling-CI8_F4yx.js";import"./Button-Do2I1OAA.js";import"./PanelTemplate-BjN5XNg2.js";import{yr as N}from"./dialogService-BZ1FmjZL.js";import"./vendor-tiptap-_UqYL7N_.js";import"./vendor-xterm-BU_lcTPR.js";import"./vendor-three-BFcUNSs9.js";import"./markdownRendererUtil-DglHsU8t.js";import"./userStore-BkgQPjq6.js";var S={class:"w-113 max-w-md p-4 space-y-4"},j={class:"text-sm text-muted-foreground leading-tight"},U={class:"text-base-foreground font-medium"},q={class:"space-y-2"},E={class:"flex items-center gap-2 flex-1 min-w-0"},F={class:"flex items-center justify-center w-8 h-8 rounded-lg flex-shrink-0 bg-secondary-background border border-border-subtle"},H={key:1,class:"text-xs font-normal text-base-foreground"},J={class:"flex flex-col gap-0.5 min-w-0 flex-1"},K={class:"text-sm font-normal text-base-foreground leading-tight"},L={class:"text-sm font-normal text-muted-foreground leading-tight"},O=$({__name:"ValueControlPopover",props:{modelValue:{},modelModifiers:{}},emits:["update:modelValue"],setup(s,{expose:l}){const i=P(),g=N();l({toggle:u(o=>{i.value.toggle(o)},"toggle")});const v=[{mode:"fixed",icon:"icon-[lucide--pencil-off]",title:"fixed",description:"fixedDesc"},{mode:"increment",text:"+1",title:"increment",description:"incrementDesc"},{mode:"decrement",text:"-1",title:"decrement",description:"decrementDesc"},{mode:"randomize",icon:"icon-[lucide--shuffle]",title:"randomize",description:"randomizeDesc"}],x=M(()=>g.get("Comfy.WidgetControlMode")),n=B(s,"modelValue");return(o,m)=>(a(),V(p(C),{ref_key:"popover",ref:i,class:"bg-interface-panel-surface border border-interface-stroke rounded-lg"},{default:w(()=>[e("div",S,[e("div",j,[f(r(o.$t("widgets.valueControl.header.prefix"))+" ",1),e("span",U,r(x.value==="before"?o.$t("widgets.valueControl.header.before"):o.$t("widgets.valueControl.header.after")),1),f(" "+r(o.$t("widgets.valueControl.header.postfix")),1)]),e("div",q,[(a(),d(y,null,z(v,t=>e("div",{key:t.mode,class:"flex items-center justify-between py-2 gap-7"},[e("div",E,[e("div",F,[t.icon?(a(),d("i",{key:0,class:k([t.icon,"text-base text-base-foreground"])},null,2)):c("",!0),t.text?(a(),d("span",H,r(t.text),1)):c("",!0)]),e("div",J,[e("div",K,[e("span",null,r(o.$t(`widgets.valueControl.${t.title}`)),1)]),e("div",L,r(o.$t(`widgets.valueControl.${t.description}`)),1)])]),D(p(b),{modelValue:n.value,"onUpdate:modelValue":m[0]||(m[0]=_=>n.value=_),class:"flex-shrink-0","input-id":t.mode,value:t.mode},null,8,["modelValue","input-id","value"])])),64))])])]),_:1},512))}}),le=O;export{le as default};
1
+ var h=Object.defineProperty;var u=(s,l)=>h(s,"name",{value:l,configurable:!0});import{P as b,et as C}from"./vendor-primevue-DcMRXJN3.js";import{$o as r,Ba as e,Do as w,Ha as c,Jo as p,Ka as f,Pa as y,Ua as d,Va as V,Xo as k,Ya as $,do as a,po as z,qa as D,xo as B,za as M,zo as P}from"./vendor-other-DlQF6V2E.js";import"./api-Dwq2LQIW.js";import"./remoteConfig-CZcEXsZS.js";import"./colorUtil-CzxntCbX.js";import"./useErrorHandling-Cfa5N_7c.js";import"./Button-Do2I1OAA.js";import"./PanelTemplate-BJda9e5J.js";import{yr as N}from"./dialogService-YG0RH337.js";import"./vendor-tiptap-_UqYL7N_.js";import"./vendor-xterm-BU_lcTPR.js";import"./vendor-three-BFcUNSs9.js";import"./markdownRendererUtil-DglHsU8t.js";import"./userStore-BAS9m9W6.js";var S={class:"w-113 max-w-md p-4 space-y-4"},j={class:"text-sm text-muted-foreground leading-tight"},U={class:"text-base-foreground font-medium"},q={class:"space-y-2"},E={class:"flex items-center gap-2 flex-1 min-w-0"},F={class:"flex items-center justify-center w-8 h-8 rounded-lg flex-shrink-0 bg-secondary-background border border-border-subtle"},H={key:1,class:"text-xs font-normal text-base-foreground"},J={class:"flex flex-col gap-0.5 min-w-0 flex-1"},K={class:"text-sm font-normal text-base-foreground leading-tight"},L={class:"text-sm font-normal text-muted-foreground leading-tight"},O=$({__name:"ValueControlPopover",props:{modelValue:{},modelModifiers:{}},emits:["update:modelValue"],setup(s,{expose:l}){const i=P(),g=N();l({toggle:u(o=>{i.value.toggle(o)},"toggle")});const v=[{mode:"fixed",icon:"icon-[lucide--pencil-off]",title:"fixed",description:"fixedDesc"},{mode:"increment",text:"+1",title:"increment",description:"incrementDesc"},{mode:"decrement",text:"-1",title:"decrement",description:"decrementDesc"},{mode:"randomize",icon:"icon-[lucide--shuffle]",title:"randomize",description:"randomizeDesc"}],x=M(()=>g.get("Comfy.WidgetControlMode")),n=B(s,"modelValue");return(o,m)=>(a(),V(p(C),{ref_key:"popover",ref:i,class:"bg-interface-panel-surface border border-interface-stroke rounded-lg"},{default:w(()=>[e("div",S,[e("div",j,[f(r(o.$t("widgets.valueControl.header.prefix"))+" ",1),e("span",U,r(x.value==="before"?o.$t("widgets.valueControl.header.before"):o.$t("widgets.valueControl.header.after")),1),f(" "+r(o.$t("widgets.valueControl.header.postfix")),1)]),e("div",q,[(a(),d(y,null,z(v,t=>e("div",{key:t.mode,class:"flex items-center justify-between py-2 gap-7"},[e("div",E,[e("div",F,[t.icon?(a(),d("i",{key:0,class:k([t.icon,"text-base text-base-foreground"])},null,2)):c("",!0),t.text?(a(),d("span",H,r(t.text),1)):c("",!0)]),e("div",J,[e("div",K,[e("span",null,r(o.$t(`widgets.valueControl.${t.title}`)),1)]),e("div",L,r(o.$t(`widgets.valueControl.${t.description}`)),1)])]),D(p(b),{modelValue:n.value,"onUpdate:modelValue":m[0]||(m[0]=_=>n.value=_),class:"flex-shrink-0","input-id":t.mode,value:t.mode},null,8,["modelValue","input-id","value"])])),64))])])]),_:1},512))}}),le=O;export{le as default};
2
2
 
3
- //# sourceMappingURL=ValueControlPopover-BdlDzT8l.js.map
3
+ //# sourceMappingURL=ValueControlPopover-BPAa35QG.js.map