Skip to content

Commit e05e988

Browse files
update user profile dropdown (#6475)
## Summary update user profile dropdown ## Screenshots 1. Subscribe to run button and Unsubscribe user panel <img width="433" height="480" alt="image" src="https://github.com/user-attachments/assets/bb859481-6405-44df-85ec-9935599c4be0" /> 2. Subscribed User: <img width="395" height="479" alt="image" src="https://github.com/user-attachments/assets/683de2c0-8090-4e9a-ac4e-d211fcee8921" /> 3. OSS: <img width="392" height="480" alt="image" src="https://github.com/user-attachments/assets/7d684c1a-8dee-48dd-8e7f-3c98bd98104d" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6475-update-user-profile-dropdown-29d6d73d365081ff9e14f9355a9a3bb7) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <[email protected]>
1 parent afa10f7 commit e05e988

File tree

7 files changed

+123
-47
lines changed

7 files changed

+123
-47
lines changed

packages/design-system/src/css/style.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@
157157
--button-surface: var(--color-white);
158158
--button-surface-contrast: var(--color-black);
159159

160+
--subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32);
161+
160162
--modal-card-button-surface: var(--color-smoke-300);
161163

162164
/* Code styling colors for help menu*/
@@ -258,6 +260,8 @@
258260
--button-active-surface: var(--color-charcoal-600);
259261
--button-icon: var(--color-smoke-800);
260262

263+
--subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32);
264+
261265
--modal-card-button-surface: var(--color-charcoal-300);
262266

263267
--dialog-surface: var(--color-neutral-700);
@@ -332,6 +336,7 @@
332336
--color-button-icon: var(--button-icon);
333337
--color-button-surface: var(--button-surface);
334338
--color-button-surface-contrast: var(--button-surface-contrast);
339+
--color-subscription-button-gradient: var(--subscription-button-gradient);
335340
--color-modal-card-button-surface: var(--modal-card-button-surface);
336341
--color-dialog-surface: var(--dialog-surface);
337342
--color-interface-menu-component-surface-hovered: var(

src/components/topbar/CurrentUserPopover.test.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ vi.mock('@/stores/firebaseAuthStore', () => ({
7979
}))
8080

8181
// Mock the useSubscription composable
82+
const mockFetchStatus = vi.fn().mockResolvedValue(undefined)
8283
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
8384
useSubscription: vi.fn(() => ({
84-
isActiveSubscription: vi.fn().mockReturnValue(true)
85+
isActiveSubscription: { value: true },
86+
fetchStatus: mockFetchStatus
8587
}))
8688
}))
8789

@@ -105,6 +107,15 @@ vi.mock('@/components/common/UserCredit.vue', () => ({
105107
}
106108
}))
107109

110+
vi.mock('@/platform/cloud/subscription/components/SubscribeButton.vue', () => ({
111+
default: {
112+
name: 'SubscribeButtonMock',
113+
render() {
114+
return h('div', 'Subscribe Button')
115+
}
116+
}
117+
}))
118+
108119
describe('CurrentUserPopover', () => {
109120
beforeEach(() => {
110121
vi.clearAllMocks()
@@ -137,9 +148,9 @@ describe('CurrentUserPopover', () => {
137148
it('renders logout button with correct props', () => {
138149
const wrapper = mountComponent()
139150

140-
// Find all buttons and get the logout button (second one)
151+
// Find all buttons and get the logout button (last button)
141152
const buttons = wrapper.findAllComponents(Button)
142-
const logoutButton = buttons[1]
153+
const logoutButton = buttons[4]
143154

144155
// Check that logout button has correct props
145156
expect(logoutButton.props('label')).toBe('Log Out')
@@ -149,9 +160,9 @@ describe('CurrentUserPopover', () => {
149160
it('opens user settings and emits close event when settings button is clicked', async () => {
150161
const wrapper = mountComponent()
151162

152-
// Find all buttons and get the settings button (first one)
163+
// Find all buttons and get the settings button (third button)
153164
const buttons = wrapper.findAllComponents(Button)
154-
const settingsButton = buttons[0]
165+
const settingsButton = buttons[2]
155166

156167
// Click the settings button
157168
await settingsButton.trigger('click')
@@ -167,9 +178,9 @@ describe('CurrentUserPopover', () => {
167178
it('calls logout function and emits close event when logout button is clicked', async () => {
168179
const wrapper = mountComponent()
169180

170-
// Find all buttons and get the logout button (second one)
181+
// Find all buttons and get the logout button (last button)
171182
const buttons = wrapper.findAllComponents(Button)
172-
const logoutButton = buttons[1]
183+
const logoutButton = buttons[4]
173184

174185
// Click the logout button
175186
await logoutButton.trigger('click')
@@ -185,16 +196,16 @@ describe('CurrentUserPopover', () => {
185196
it('opens API pricing docs and emits close event when API pricing button is clicked', async () => {
186197
const wrapper = mountComponent()
187198

188-
// Find all buttons and get the API pricing button (third one now)
199+
// Find all buttons and get the Partner Nodes info button (first one)
189200
const buttons = wrapper.findAllComponents(Button)
190-
const apiPricingButton = buttons[2]
201+
const partnerNodesButton = buttons[0]
191202

192-
// Click the API pricing button
193-
await apiPricingButton.trigger('click')
203+
// Click the Partner Nodes button
204+
await partnerNodesButton.trigger('click')
194205

195206
// Verify window.open was called with the correct URL
196207
expect(window.open).toHaveBeenCalledWith(
197-
'https://docs.comfy.org/tutorials/api-nodes/pricing',
208+
'https://docs.comfy.org/tutorials/api-nodes/overview#api-nodes',
198209
'_blank'
199210
)
200211

@@ -206,9 +217,9 @@ describe('CurrentUserPopover', () => {
206217
it('opens top-up dialog and emits close event when top-up button is clicked', async () => {
207218
const wrapper = mountComponent()
208219

209-
// Find all buttons and get the top-up button (last one)
220+
// Find all buttons and get the top-up button (second one)
210221
const buttons = wrapper.findAllComponents(Button)
211-
const topUpButton = buttons[buttons.length - 1]
222+
const topUpButton = buttons[1]
212223

213224
// Click the top-up button
214225
await topUpButton.trigger('click')

src/components/topbar/CurrentUserPopover.vue

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,38 @@
2323
</div>
2424
</div>
2525

26+
<div v-if="isActiveSubscription" class="flex items-center justify-between">
27+
<div class="flex flex-col gap-1">
28+
<UserCredit text-class="text-2xl" />
29+
<Button
30+
:label="$t('subscription.partnerNodesCredits')"
31+
severity="secondary"
32+
text
33+
size="small"
34+
class="pl-6 p-0 h-auto justify-start"
35+
:pt="{
36+
root: {
37+
class: 'hover:bg-transparent active:bg-transparent'
38+
}
39+
}"
40+
@click="handleOpenPartnerNodesInfo"
41+
/>
42+
</div>
43+
<Button
44+
:label="$t('credits.topUp.topUp')"
45+
severity="secondary"
46+
size="small"
47+
@click="handleTopUp"
48+
/>
49+
</div>
50+
<SubscribeButton
51+
v-else
52+
:label="$t('subscription.subscribeToComfyCloud')"
53+
size="small"
54+
variant="gradient"
55+
@subscribed="handleSubscribed"
56+
/>
57+
2658
<Divider class="my-2" />
2759

2860
<Button
@@ -35,45 +67,28 @@
3567
@click="handleOpenUserSettings"
3668
/>
3769

38-
<Divider class="my-2" />
39-
4070
<Button
71+
v-if="isActiveSubscription"
4172
class="justify-start"
42-
:label="$t('auth.signOut.signOut')"
43-
icon="pi pi-sign-out"
73+
:label="$t(planSettingsLabel)"
74+
icon="pi pi-receipt"
4475
text
4576
fluid
4677
severity="secondary"
47-
@click="handleLogout"
78+
@click="handleOpenPlanAndCreditsSettings"
4879
/>
4980

5081
<Divider class="my-2" />
5182

5283
<Button
5384
class="justify-start"
54-
:label="$t('credits.apiPricing')"
55-
icon="pi pi-external-link"
85+
:label="$t('auth.signOut.signOut')"
86+
icon="pi pi-sign-out"
5687
text
5788
fluid
5889
severity="secondary"
59-
@click="handleOpenApiPricing"
90+
@click="handleLogout"
6091
/>
61-
62-
<Divider class="my-2" />
63-
64-
<div class="flex w-full flex-col gap-2 p-2">
65-
<div class="text-sm text-muted">
66-
{{ $t('credits.yourCreditBalance') }}
67-
</div>
68-
<div class="flex items-center justify-between">
69-
<UserCredit text-class="text-2xl" />
70-
<Button
71-
v-if="isActiveSubscription"
72-
:label="$t('credits.topUp.topUp')"
73-
@click="handleTopUp"
74-
/>
75-
</div>
76-
</div>
7792
</div>
7893
</template>
7994

@@ -86,37 +101,60 @@ import UserAvatar from '@/components/common/UserAvatar.vue'
86101
import UserCredit from '@/components/common/UserCredit.vue'
87102
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
88103
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
104+
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
89105
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
106+
import { isCloud } from '@/platform/distribution/types'
90107
import { useDialogService } from '@/services/dialogService'
91108
92109
const emit = defineEmits<{
93110
close: []
94111
}>()
95112
113+
const planSettingsLabel = isCloud
114+
? 'settingsCategories.PlanCredits'
115+
: 'settingsCategories.Credits'
116+
96117
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
97118
useCurrentUser()
98119
const authActions = useFirebaseAuthActions()
99120
const dialogService = useDialogService()
100-
const { isActiveSubscription } = useSubscription()
121+
const { isActiveSubscription, fetchStatus } = useSubscription()
101122
102123
const handleOpenUserSettings = () => {
103124
dialogService.showSettingsDialog('user')
104125
emit('close')
105126
}
106127
128+
const handleOpenPlanAndCreditsSettings = () => {
129+
if (isCloud) {
130+
dialogService.showSettingsDialog('subscription')
131+
} else {
132+
dialogService.showSettingsDialog('credits')
133+
}
134+
135+
emit('close')
136+
}
137+
107138
const handleTopUp = () => {
108139
dialogService.showTopUpCreditsDialog()
109140
emit('close')
110141
}
111142
143+
const handleOpenPartnerNodesInfo = () => {
144+
window.open(
145+
'https://docs.comfy.org/tutorials/api-nodes/overview#api-nodes',
146+
'_blank'
147+
)
148+
emit('close')
149+
}
150+
112151
const handleLogout = async () => {
113152
await handleSignOut()
114153
emit('close')
115154
}
116155
117-
const handleOpenApiPricing = () => {
118-
window.open('https://docs.comfy.org/tutorials/api-nodes/pricing', '_blank')
119-
emit('close')
156+
const handleSubscribed = async () => {
157+
await fetchStatus()
120158
}
121159
122160
onMounted(() => {

src/locales/en/main.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1687,7 +1687,9 @@
16871687
"subscribe": "Subscribe"
16881688
},
16891689
"subscribeToRun": "Subscribe to Run",
1690-
"subscribeNow": "Subscribe Now"
1690+
"subscribeNow": "Subscribe Now",
1691+
"subscribeToComfyCloud": "Subscribe to Comfy Cloud",
1692+
"partnerNodesCredits": "Partner Nodes credits"
16911693
},
16921694
"userSettings": {
16931695
"title": "User Settings",

src/platform/cloud/subscription/components/SubscribeButton.vue

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
:loading="isLoading"
66
:disabled="isPolling"
77
severity="primary"
8+
:style="
9+
variant === 'gradient'
10+
? {
11+
background: 'var(--color-subscription-button-gradient)',
12+
color: 'var(--color-white)'
13+
}
14+
: undefined
15+
"
816
:pt="{
917
root: {
10-
class: 'w-full font-bold'
18+
class: rootClass
1119
}
1220
}"
1321
@click="handleSubscribe"
@@ -16,22 +24,29 @@
1624

1725
<script setup lang="ts">
1826
import Button from 'primevue/button'
19-
import { onBeforeUnmount, ref } from 'vue'
27+
import { computed, onBeforeUnmount, ref } from 'vue'
2028
2129
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
2230
import { isCloud } from '@/platform/distribution/types'
2331
import { useTelemetry } from '@/platform/telemetry'
32+
import { cn } from '@/utils/tailwindUtil'
2433
25-
withDefaults(
34+
const props = withDefaults(
2635
defineProps<{
2736
label?: string
2837
size?: 'small' | 'large'
38+
variant?: 'default' | 'gradient'
39+
fluid?: boolean
2940
}>(),
3041
{
31-
size: 'large'
42+
size: 'large',
43+
variant: 'default',
44+
fluid: true
3245
}
3346
)
3447
48+
const rootClass = computed(() => cn('font-bold', props.fluid && 'w-full'))
49+
3550
const emit = defineEmits<{
3651
subscribed: []
3752
}>()

src/platform/cloud/subscription/components/SubscribeToRun.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
icon="pi pi-lock"
1010
severity="primary"
1111
size="small"
12+
:style="{
13+
background: 'var(--color-subscription-button-gradient)',
14+
color: 'var(--color-white)'
15+
}"
1216
data-testid="subscribe-to-run-button"
1317
@click="handleSubscribeToRun"
1418
/>

src/platform/cloud/subscription/components/SubscriptionPanel.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
v-else
6262
:label="$t('subscription.subscribeNow')"
6363
size="small"
64+
:fluid="false"
6465
class="text-xs"
6566
@subscribed="handleRefresh"
6667
/>

0 commit comments

Comments
 (0)