diff --git a/src/components/dialog/GlobalDialog.vue b/src/components/dialog/GlobalDialog.vue
index afc056d61..bb3b98478 100644
--- a/src/components/dialog/GlobalDialog.vue
+++ b/src/components/dialog/GlobalDialog.vue
@@ -4,7 +4,12 @@
v-for="item in dialogStore.dialogStack"
:key="item.key"
v-model:visible="item.visible"
- class="global-dialog"
+ :class="[
+ 'global-dialog',
+ item.key === 'global-settings' && teamWorkspacesEnabled
+ ? 'settings-dialog-workspace'
+ : ''
+ ]"
v-bind="item.dialogComponentProps"
:pt="item.dialogComponentProps.pt"
:aria-labelledby="item.key"
@@ -38,8 +43,13 @@
@@ -55,4 +65,27 @@ const dialogStore = useDialogStore()
@apply p-2 2xl:p-[var(--p-dialog-content-padding)];
@apply pt-0;
}
+
+/* Workspace mode: wider settings dialog */
+.settings-dialog-workspace {
+ width: 100%;
+ max-width: 1440px;
+}
+
+.settings-dialog-workspace .p-dialog-content {
+ width: 100%;
+}
+
+.manager-dialog {
+ height: 80vh;
+ max-width: 1724px;
+ max-height: 1026px;
+}
+
+@media (min-width: 3000px) {
+ .manager-dialog {
+ max-width: 2200px;
+ max-height: 1320px;
+ }
+}
diff --git a/src/components/dialog/content/setting/MembersPanelContent.vue b/src/components/dialog/content/setting/MembersPanelContent.vue
new file mode 100644
index 000000000..0056ce2b5
--- /dev/null
+++ b/src/components/dialog/content/setting/MembersPanelContent.vue
@@ -0,0 +1,483 @@
+
+
+
+
+
+
+
+ {{
+ $t('workspacePanel.members.membersCount', {
+ count: members.length
+ })
+ }}
+
+
+ {{
+ $t(
+ 'workspacePanel.members.pendingInvitesCount',
+ pendingInvites.length
+ )
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ userDisplayName }}
+
+ ({{ $t('g.you') }})
+
+
+
+ {{ workspaceRole }}
+
+
+
+ {{ userEmail }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ member.name.charAt(0).toUpperCase() }}
+
+
+
+
+ {{ member.name }}
+
+
+ {{ member.email }}
+
+
+
+
+
+ {{ formatDate(member.joinDate) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getInviteInitial(invite.email) }}
+
+
+
+
+ {{ getInviteDisplayName(invite.email) }}
+
+
+ {{ invite.email }}
+
+
+
+
+
+ {{ formatDate(invite.inviteDate) }}
+
+
+
+ {{ formatDate(invite.expiryDate) }}
+
+
+
+
+
+
+
+
+ {{ $t('workspacePanel.members.noInvites') }}
+
+
+
+
+
+
+
+
+ {{ $t('workspacePanel.members.personalWorkspaceMessage') }}
+
+
+
+
+
+
diff --git a/src/components/dialog/content/setting/WorkspacePanel.vue b/src/components/dialog/content/setting/WorkspacePanel.vue
new file mode 100644
index 000000000..aff8f3733
--- /dev/null
+++ b/src/components/dialog/content/setting/WorkspacePanel.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/setting/WorkspacePanelContent.vue b/src/components/dialog/content/setting/WorkspacePanelContent.vue
new file mode 100644
index 000000000..59128ef97
--- /dev/null
+++ b/src/components/dialog/content/setting/WorkspacePanelContent.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+ {{ workspaceName }}
+
+
+
+
+
+ {{ $t('workspacePanel.tabs.planCredits') }}
+ {{
+ $t('workspacePanel.tabs.membersCount', { count: members.length })
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dialog/content/setting/WorkspaceSidebarItem.vue b/src/components/dialog/content/setting/WorkspaceSidebarItem.vue
new file mode 100644
index 000000000..cab92c7a8
--- /dev/null
+++ b/src/components/dialog/content/setting/WorkspaceSidebarItem.vue
@@ -0,0 +1,19 @@
+
+
+
+
+ {{ workspaceName }}
+
+
+
+
diff --git a/src/components/toast/WorkspaceCreatedToast.vue b/src/components/toast/WorkspaceCreatedToast.vue
new file mode 100644
index 000000000..16f9189db
--- /dev/null
+++ b/src/components/toast/WorkspaceCreatedToast.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ {{ $t('workspacePanel.toast.workspaceCreated.title') }}
+
+
+ {{ $t('workspacePanel.toast.workspaceCreated.message') }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/topbar/CurrentUserPopoverWorkspace.vue b/src/components/topbar/CurrentUserPopoverWorkspace.vue
new file mode 100644
index 000000000..227d7098b
--- /dev/null
+++ b/src/components/topbar/CurrentUserPopoverWorkspace.vue
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+
+ {{ userDisplayName || $t('g.user') }}
+
+
+ {{ userEmail }}
+
+
+
+
+
+
+
+
+
{{
+ workspaceName
+ }}
+
+ {{ workspaceTierName }}
+
+
+ {{ $t('workspaceSwitcher.subscribe') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ displayedCredits
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ $t('subscription.plansAndPricing')
+ }}
+
+ {{ $t('subscription.upgrade') }}
+
+
+
+
+
+
+ {{
+ $t('subscription.managePlan')
+ }}
+
+
+
+
+
+ {{
+ $t('subscription.partnerNodesCredits')
+ }}
+
+
+
+
+
+
+
+ {{
+ $t('userSettings.workspaceSettings')
+ }}
+
+
+
+
+
+ {{
+ $t('userSettings.accountSettings')
+ }}
+
+
+
+
+
+
+
+ {{
+ $t('auth.signOut.signOut')
+ }}
+
+
+
+
+
diff --git a/src/components/topbar/WorkspaceSwitcherPopover.vue b/src/components/topbar/WorkspaceSwitcherPopover.vue
new file mode 100644
index 000000000..68d6941db
--- /dev/null
+++ b/src/components/topbar/WorkspaceSwitcherPopover.vue
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('workspaceSwitcher.createWorkspace') }}
+
+
+ {{ $t('workspaceSwitcher.maxWorkspacesReached') }}
+
+
+
+
+
+
+
+
+
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index fa557ee96..bcdc9fabc 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -1,6 +1,7 @@
{
"g": {
"user": "User",
+ "you": "You",
"currentUser": "Current user",
"empty": "Empty",
"noWorkflowsFound": "No workflows found.",
@@ -1264,7 +1265,10 @@
"Scene": "Scene",
"3D": "3D",
"Light": "Light",
- "User": "User",
+ "Profile": "Profile",
+ "Workspace": "Workspace",
+ "WorkspacePlan": "Plan & Credits",
+ "WorkspaceMembers": "Members",
"Credits": "Credits",
"API Nodes": "API Nodes",
"Notification Preferences": "Notification Preferences",
@@ -1997,6 +2001,8 @@
"renewsDate": "Renews {date}",
"expiresDate": "Expires {date}",
"manageSubscription": "Manage subscription",
+ "managePayment": "Manage Payment",
+ "cancelSubscription": "Cancel Subscription",
"partnerNodesBalance": "\"Partner Nodes\" Credit Balance",
"partnerNodesDescription": "For running commercial/proprietary models",
"totalCredits": "Total credits",
@@ -2051,6 +2057,9 @@
"subscribeToRunFull": "Subscribe to Run",
"subscribeNow": "Subscribe Now",
"subscribeToComfyCloud": "Subscribe to Comfy Cloud",
+ "workspaceNotSubscribed": "This workspace is not on a subscription",
+ "subscriptionRequiredMessage": "A subscription is required for members to run workflows on Cloud",
+ "contactOwnerToSubscribe": "Contact the workspace owner to subscribe",
"description": "Choose the best plan for you",
"haveQuestions": "Have questions or wondering about enterprise?",
"contactUs": "Contact us",
@@ -2086,12 +2095,132 @@
"userSettings": {
"title": "My Account Settings",
"accountSettings": "Account settings",
+ "workspaceSettings": "Workspace settings",
"name": "Name",
"email": "Email",
"provider": "Sign-in Provider",
"notSet": "Not set",
"updatePassword": "Update Password"
},
+ "workspacePanel": {
+ "invite": "Invite",
+ "inviteMember": "Invite member",
+ "inviteLimitReached": "You've reached the maximum of 50 members",
+ "tabs": {
+ "dashboard": "Dashboard",
+ "planCredits": "Plan & Credits",
+ "membersCount": "Members ({count})"
+ },
+ "dashboard": {
+ "placeholder": "Dashboard workspace settings"
+ },
+ "members": {
+ "membersCount": "{count}/50 Members",
+ "pendingInvitesCount": "{count} pending invite | {count} pending invites",
+ "tabs": {
+ "active": "Active",
+ "pendingCount": "Pending ({count})"
+ },
+ "columns": {
+ "inviteDate": "Invite date",
+ "expiryDate": "Expiry date",
+ "joinDate": "Join date"
+ },
+ "actions": {
+ "copyLink": "Copy invite link",
+ "revokeInvite": "Revoke invite",
+ "removeMember": "Remove member"
+ },
+ "noInvites": "No pending invites",
+ "noMembers": "No members",
+ "personalWorkspaceMessage": "You can't invite other members to your personal workspace right now. To add members to a workspace,",
+ "createNewWorkspace": "create a new one."
+ },
+ "menu": {
+ "editWorkspace": "Edit workspace details",
+ "leaveWorkspace": "Leave Workspace",
+ "deleteWorkspace": "Delete Workspace",
+ "deleteWorkspaceDisabledTooltip": "Cancel your workspace's active subscription first"
+ },
+ "editWorkspaceDialog": {
+ "title": "Edit workspace details",
+ "nameLabel": "Workspace name",
+ "save": "Save"
+ },
+ "leaveDialog": {
+ "title": "Leave this workspace?",
+ "message": "You won't be able to join again unless you contact the workspace owner.",
+ "leave": "Leave"
+ },
+ "deleteDialog": {
+ "title": "Delete this workspace?",
+ "message": "Any unused credits or unsaved assets will be lost. This action cannot be undone.",
+ "messageWithName": "Delete \"{name}\"? Any unused credits or unsaved assets will be lost. This action cannot be undone."
+ },
+ "removeMemberDialog": {
+ "title": "Remove this member?",
+ "message": "This member will be removed from your workspace. Credits they've used will not be refunded.",
+ "remove": "Remove member"
+ },
+ "revokeInviteDialog": {
+ "title": "Uninvite this person?",
+ "message": "This member won't be able to join your workspace anymore. Their invite link will be invalidated.",
+ "revoke": "Uninvite"
+ },
+ "inviteMemberDialog": {
+ "title": "Invite a person to this workspace",
+ "message": "Create a shareable invite link to send to someone",
+ "placeholder": "Enter the person's email",
+ "createLink": "Create link",
+ "linkStep": {
+ "title": "Send this link to the person",
+ "message": "Make sure their account uses this email.",
+ "copyLink": "Copy Link",
+ "done": "Done"
+ },
+ "linkCopied": "Copied",
+ "linkCopyFailed": "Failed to copy link"
+ },
+ "createWorkspaceDialog": {
+ "title": "Create a new workspace",
+ "message": "Workspaces let members share a single credits pool. You'll become the owner after creating this.",
+ "nameLabel": "Workspace name*",
+ "namePlaceholder": "Enter workspace name",
+ "create": "Create"
+ },
+ "toast": {
+ "workspaceCreated": {
+ "title": "Workspace created",
+ "message": "Subscribe to a plan, invite teammates, and start collaborating.",
+ "subscribe": "Subscribe"
+ },
+ "workspaceUpdated": {
+ "title": "Workspace updated",
+ "message": "Workspace details have been saved."
+ },
+ "workspaceDeleted": {
+ "title": "Workspace deleted",
+ "message": "The workspace has been permanently deleted."
+ },
+ "workspaceLeft": {
+ "title": "Left workspace",
+ "message": "You have left the workspace."
+ },
+ "failedToUpdateWorkspace": "Failed to update workspace",
+ "failedToCreateWorkspace": "Failed to create workspace",
+ "failedToDeleteWorkspace": "Failed to delete workspace",
+ "failedToLeaveWorkspace": "Failed to leave workspace",
+ "failedToFetchWorkspaces": "Failed to load workspaces"
+ }
+ },
+ "workspaceSwitcher": {
+ "switchWorkspace": "Switch workspace",
+ "subscribe": "Subscribe",
+ "roleOwner": "Owner",
+ "roleMember": "Member",
+ "createWorkspace": "Create new workspace",
+ "maxWorkspacesReached": "You can only own 10 workspaces. Delete one to create a new one."
+ },
"selectionToolbox": {
"executeButton": {
"tooltip": "Execute to selected output nodes (Highlighted with orange border)",
@@ -2608,7 +2737,10 @@
"unsavedChanges": {
"title": "Unsaved Changes",
"message": "You have unsaved changes. Do you want to discard them and switch workspaces?"
- }
+ },
+ "inviteAccepted": "Invite Accepted",
+ "addedToWorkspace": "You have been added to {workspaceName}",
+ "inviteFailed": "Failed to Accept Invite"
},
"workspaceAuth": {
"errors": {
@@ -2619,4 +2751,4 @@
"tokenExchangeFailed": "Failed to authenticate with workspace: {error}"
}
}
-}
\ No newline at end of file
+}
diff --git a/src/platform/cloud/subscription/components/SubscriptionPanel.vue b/src/platform/cloud/subscription/components/SubscriptionPanel.vue
index 7a521b504..e395cb7d1 100644
--- a/src/platform/cloud/subscription/components/SubscriptionPanel.vue
+++ b/src/platform/cloud/subscription/components/SubscriptionPanel.vue
@@ -17,208 +17,10 @@
-
-
-
-
-
-
- {{ subscriptionTierName }}
-
-
- ${{ tierPrice }}
- {{
- $t('subscription.perMonth')
- }}
-
-
-
- {{
- $t('subscription.expiresDate', {
- date: formattedEndDate
- })
- }}
-
-
- {{
- $t('subscription.renewsDate', {
- date: formattedRenewalDate
- })
- }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('subscription.totalCredits') }}
-
-
-
- {{ totalCredits }}
-
-
-
-
-
-
-
- |
-
- {{ includedCreditsDisplay }}
- |
-
- {{ creditsRemainingLabel }}
- |
-
-
- |
-
- {{ prepaidCredits }}
- |
-
- {{ $t('subscription.creditsYouveAdded') }}
- |
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('subscription.yourPlanIncludes') }}
-
-
-
-
-
-
- {{ benefit.value }}
-
-
- {{ benefit.label }}
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
diff --git a/src/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue b/src/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue
new file mode 100644
index 000000000..a6d5f063b
--- /dev/null
+++ b/src/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+
+ {{ subscriptionTierName }}
+
+
+ ${{ tierPrice }}
+ {{ $t('subscription.perMonth') }}
+
+
+
+ {{
+ $t('subscription.expiresDate', {
+ date: formattedEndDate
+ })
+ }}
+
+
+ {{
+ $t('subscription.renewsDate', {
+ date: formattedRenewalDate
+ })
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.totalCredits') }}
+
+
+
+ {{ totalCredits }}
+
+
+
+
+
+
+
+ |
+
+ {{ includedCreditsDisplay }}
+ |
+
+ {{ creditsRemainingLabel }}
+ |
+
+
+ |
+
+ {{ prepaidCredits }}
+ |
+
+ {{ $t('subscription.creditsYouveAdded') }}
+ |
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.yourPlanIncludes') }}
+
+
+
+
+
+
+ {{ benefit.value }}
+
+
+ {{ benefit.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue b/src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue
new file mode 100644
index 000000000..7605849e8
--- /dev/null
+++ b/src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue
@@ -0,0 +1,435 @@
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.workspaceNotSubscribed') }}
+
+
+ {{ $t('subscription.subscriptionRequiredMessage') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.workspaceNotSubscribed') }}
+
+
+ {{ $t('subscription.contactOwnerToSubscribe') }}
+
+
+
+
+
+
+
+
+ {{ subscriptionTierName }}
+
+
+ ${{ tierPrice }}
+ {{ $t('subscription.perMonth') }}
+
+
+
+ {{
+ $t('subscription.expiresDate', {
+ date: formattedEndDate
+ })
+ }}
+
+
+ {{
+ $t('subscription.renewsDate', {
+ date: formattedRenewalDate
+ })
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.totalCredits') }}
+
+
+
+ {{ showZeroState ? '0' : totalCredits }}
+
+
+
+
+
+
+
+ |
+
+ {{
+ showZeroState ? '0 / 0' : includedCreditsDisplay
+ }}
+ |
+
+ {{ creditsRemainingLabel }}
+ |
+
+
+ |
+
+ {{
+ showZeroState ? '0' : prepaidCredits
+ }}
+ |
+
+ {{ $t('subscription.creditsYouveAdded') }}
+ |
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.yourPlanIncludes') }}
+
+
+
+
+
+
+ {{ benefit.value }}
+
+
+ {{ benefit.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/platform/settings/components/SettingDialogContent.vue b/src/platform/settings/components/SettingDialogContent.vue
index 13da10437..00d213004 100644
--- a/src/platform/settings/components/SettingDialogContent.vue
+++ b/src/platform/settings/components/SettingDialogContent.vue
@@ -1,6 +1,18 @@
-
-