diff --git a/src/components/dialog/content/setting/MembersPanelContent.vue b/src/components/dialog/content/setting/MembersPanelContent.vue index fb4d22dc7..db5e7c458 100644 --- a/src/components/dialog/content/setting/MembersPanelContent.vue +++ b/src/components/dialog/content/setting/MembersPanelContent.vue @@ -234,12 +234,7 @@
{{ invite.name.charAt(0).toUpperCase() }} @@ -343,8 +338,8 @@ const { pendingInvites, fetchMembers, fetchPendingInvites, - copyInviteLinkAndAccept, - acceptInvite, + copyInviteLink, + revokeInvite, isPersonalWorkspace, permissions, uiConfig, @@ -451,18 +446,14 @@ function formatDate(date: Date): string { return d(date, { dateStyle: 'medium' }) } -async function handleCopyInviteLink(invite: PendingInvite) { - // Demo: copying invite link simulates user accepting, moves to active members - await copyInviteLinkAndAccept(invite.id) -} - -function handleAcceptInvite(invite: PendingInvite) { - // Demo: clicking avatar accepts the invite, moves to active members - acceptInvite(invite.id) +function handleCopyInviteLink(invite: PendingInvite) { + copyInviteLink(invite.id) } function handleRevokeInvite(invite: PendingInvite) { - showRevokeInviteDialog(invite.id) + showRevokeInviteDialog(() => { + revokeInvite(invite.id) + }) } function handleCreateWorkspace() { @@ -471,7 +462,9 @@ function handleCreateWorkspace() { }) } -function handleRemoveMember(member: WorkspaceMember) { - showRemoveMemberDialog(member.id) +function handleRemoveMember(_member: WorkspaceMember) { + showRemoveMemberDialog(() => { + // TODO: Implement actual remove member API call + }) } diff --git a/src/components/dialog/content/setting/WorkspacePanelContent.vue b/src/components/dialog/content/setting/WorkspacePanelContent.vue index 73f91bc9e..8375edb69 100644 --- a/src/components/dialog/content/setting/WorkspacePanelContent.vue +++ b/src/components/dialog/content/setting/WorkspacePanelContent.vue @@ -53,7 +53,7 @@ : null " :class="[ - 'flex cursor-pointer items-center gap-2 px-3 py-2', + 'flex items-center gap-2 px-3 py-2', item.class, item.disabled ? 'pointer-events-auto' : '' ]" @@ -110,8 +110,7 @@ const { t } = useI18n() const { showLeaveWorkspaceDialog, showDeleteWorkspaceDialog, - showInviteMemberDialog, - showEditWorkspaceDialog + showInviteMemberDialog } = useDialogService() const { isActiveSubscription } = useSubscription() const { @@ -130,11 +129,15 @@ const { const menu = ref | null>(null) function handleLeaveWorkspace() { - showLeaveWorkspaceDialog() + showLeaveWorkspaceDialog(() => { + // TODO: Implement actual leave workspace API call + }) } function handleDeleteWorkspace() { - showDeleteWorkspaceDialog() + showDeleteWorkspaceDialog(() => { + // TODO: Implement actual delete workspace API call + }) } const isDeleteDisabled = computed( @@ -161,50 +164,31 @@ function handleInviteMember() { }) } -function handleEditWorkspace() { - showEditWorkspaceDialog() -} - const menuItems = computed(() => { - const items: Array<{ - label: string - icon: string - class?: string - disabled?: boolean - command?: () => void - }> = [] + const action = uiConfig.value.workspaceMenuAction + if (!action) return [] - // Add "Edit workspace details" for PERSONAL and OWNER - if (uiConfig.value.showEditWorkspaceMenuItem) { - items.push({ - label: t('workspacePanel.menu.editWorkspaceDetails'), - icon: 'pi pi-pencil', - command: handleEditWorkspace - }) + if (action === 'delete') { + return [ + { + label: t('workspacePanel.menu.deleteWorkspace'), + icon: 'pi pi-trash', + class: isDeleteDisabled.value + ? 'text-danger/50 cursor-not-allowed' + : 'text-danger', + disabled: isDeleteDisabled.value, + command: isDeleteDisabled.value ? undefined : handleDeleteWorkspace + } + ] } - const action = uiConfig.value.workspaceMenuAction - - // Add role-specific action - if (action === 'delete') { - items.push({ - label: t('workspacePanel.menu.deleteWorkspace'), - icon: 'pi pi-trash', - class: isDeleteDisabled.value - ? 'text-danger/50 cursor-not-allowed' - : 'text-danger', - disabled: isDeleteDisabled.value, - command: isDeleteDisabled.value ? undefined : handleDeleteWorkspace - }) - } else if (action === 'leave') { - items.push({ + return [ + { label: t('workspacePanel.menu.leaveWorkspace'), icon: 'pi pi-sign-out', command: handleLeaveWorkspace - }) - } - - return items + } + ] }) onMounted(() => { diff --git a/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue index 2e7b457bf..0c317c6fe 100644 --- a/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue +++ b/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue @@ -62,16 +62,14 @@ import { useToast } from 'primevue/usetoast' import { computed, ref } from 'vue' import Button from '@/components/ui/button/Button.vue' -import { useWorkspace } from '@/platform/workspace/composables/useWorkspace' import { useDialogStore } from '@/stores/dialogStore' const { onConfirm } = defineProps<{ - onConfirm?: (name: string) => void | Promise + onConfirm: (name: string) => void | Promise }>() const dialogStore = useDialogStore() const toast = useToast() -const { createWorkspace } = useWorkspace() const loading = ref(false) const workspaceName = ref('') @@ -90,13 +88,8 @@ async function onCreate() { if (!isValidName.value) return loading.value = true try { - const name = workspaceName.value.trim() - // Create workspace using global state (creates OWNER unsubscribed workspace) - createWorkspace(name) - // Call optional callback if provided - await onConfirm?.(name) + await onConfirm(workspaceName.value.trim()) dialogStore.closeDialog({ key: 'create-workspace' }) - // Show toast prompting to subscribe toast.add({ group: 'workspace-created' }) diff --git a/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue index 64341ed27..5a455a953 100644 --- a/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue +++ b/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue @@ -41,11 +41,13 @@ import { ref } from 'vue' import Button from '@/components/ui/button/Button.vue' -import { useWorkspace } from '@/platform/workspace/composables/useWorkspace' import { useDialogStore } from '@/stores/dialogStore' +const { onConfirm } = defineProps<{ + onConfirm: () => void | Promise +}>() + const dialogStore = useDialogStore() -const { deleteWorkspace } = useWorkspace() const loading = ref(false) function onCancel() { @@ -55,7 +57,7 @@ function onCancel() { async function onDelete() { loading.value = true try { - await deleteWorkspace() + await onConfirm() dialogStore.closeDialog({ key: 'delete-workspace' }) } finally { loading.value = false diff --git a/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue index 4475fb5c8..da6703cba 100644 --- a/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue +++ b/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue @@ -41,11 +41,13 @@ import { ref } from 'vue' import Button from '@/components/ui/button/Button.vue' -import { useWorkspace } from '@/platform/workspace/composables/useWorkspace' import { useDialogStore } from '@/stores/dialogStore' +const { onConfirm } = defineProps<{ + onConfirm: () => void | Promise +}>() + const dialogStore = useDialogStore() -const { leaveWorkspace } = useWorkspace() const loading = ref(false) function onCancel() { @@ -55,7 +57,7 @@ function onCancel() { async function onLeave() { loading.value = true try { - await leaveWorkspace() + await onConfirm() dialogStore.closeDialog({ key: 'leave-workspace' }) } finally { loading.value = false diff --git a/src/components/dialog/content/workspace/RemoveMemberDialogContent.vue b/src/components/dialog/content/workspace/RemoveMemberDialogContent.vue index 4dab4a779..29f444587 100644 --- a/src/components/dialog/content/workspace/RemoveMemberDialogContent.vue +++ b/src/components/dialog/content/workspace/RemoveMemberDialogContent.vue @@ -41,15 +41,13 @@ import { ref } from 'vue' import Button from '@/components/ui/button/Button.vue' -import { useWorkspace } from '@/platform/workspace/composables/useWorkspace' import { useDialogStore } from '@/stores/dialogStore' -const { memberId } = defineProps<{ - memberId: string +const { onConfirm } = defineProps<{ + onConfirm: () => void | Promise }>() const dialogStore = useDialogStore() -const { removeMember } = useWorkspace() const loading = ref(false) function onCancel() { @@ -59,7 +57,7 @@ function onCancel() { async function onRemove() { loading.value = true try { - await removeMember(memberId) + await onConfirm() dialogStore.closeDialog({ key: 'remove-member' }) } finally { loading.value = false diff --git a/src/components/dialog/content/workspace/RevokeInviteDialogContent.vue b/src/components/dialog/content/workspace/RevokeInviteDialogContent.vue index 58766372e..76744a76d 100644 --- a/src/components/dialog/content/workspace/RevokeInviteDialogContent.vue +++ b/src/components/dialog/content/workspace/RevokeInviteDialogContent.vue @@ -41,15 +41,13 @@ import { ref } from 'vue' import Button from '@/components/ui/button/Button.vue' -import { useWorkspace } from '@/platform/workspace/composables/useWorkspace' import { useDialogStore } from '@/stores/dialogStore' -const { inviteId } = defineProps<{ - inviteId: string +const { onConfirm } = defineProps<{ + onConfirm: () => void | Promise }>() const dialogStore = useDialogStore() -const { revokeInvite } = useWorkspace() const loading = ref(false) function onCancel() { @@ -59,7 +57,7 @@ function onCancel() { async function onRevoke() { loading.value = true try { - await revokeInvite(inviteId) + await onConfirm() dialogStore.closeDialog({ key: 'revoke-invite' }) } finally { loading.value = false diff --git a/src/components/topbar/CurrentUserPopover.vue b/src/components/topbar/CurrentUserPopover.vue index 96b2f4e2d..77ebdd1de 100644 --- a/src/components/topbar/CurrentUserPopover.vue +++ b/src/components/topbar/CurrentUserPopover.vue @@ -43,10 +43,10 @@ workspaceName }}
- {{ workspaceTierName }} + {{ subscriptionTierName }}
{{ $t('workspaceSwitcher.subscribe') }} @@ -112,7 +112,7 @@ size="sm" class="w-full" data-testid="subscribe-button" - @click="handleOpenWorkspaceSettings" + @click="handleOpenPlansAndPricing" > {{ $t('subscription.subscribeNow') }} @@ -246,8 +246,7 @@ const { workspaceName, workspaceRole, isPersonalWorkspace, - isWorkspaceSubscribed, - subscriptionPlan + isWorkspaceSubscribed } = useWorkspace() const workspaceSwitcherPopover = ref | null>(null) @@ -262,9 +261,14 @@ const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } = const authActions = useFirebaseAuthActions() const authStore = useFirebaseAuthStore() const dialogService = useDialogService() -const { isActiveSubscription, fetchStatus } = useSubscription() +const { + isActiveSubscription, + subscriptionTierName, + subscriptionTier, + fetchStatus +} = useSubscription() const subscriptionDialog = useSubscriptionDialog() -const { locale, t } = useI18n() +const { locale } = useI18n() const formattedBalance = computed(() => { const cents = @@ -281,23 +285,11 @@ const formattedBalance = computed(() => { }) }) -// Workspace subscription tier name (not user tier) -const workspaceTierName = computed(() => { - if (!isWorkspaceSubscribed.value) return null - if (!subscriptionPlan.value) return null - // Convert plan to display name - if (subscriptionPlan.value === 'PRO_MONTHLY') - return t('subscription.tiers.pro.name') - if (subscriptionPlan.value === 'PRO_YEARLY') - return t('subscription.tierNameYearly', { - name: t('subscription.tiers.pro.name') - }) - return null -}) - const canUpgrade = computed(() => { - // For workspace-based subscriptions, can upgrade if not on highest tier - return isWorkspaceSubscribed.value && subscriptionPlan.value !== null + const tier = subscriptionTier.value + return ( + tier === 'FOUNDERS_EDITION' || tier === 'STANDARD' || tier === 'CREATOR' + ) }) // Menu visibility based on role diff --git a/src/locales/en/main.json b/src/locales/en/main.json index d95bee73c..4137e5dea 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -2128,8 +2128,7 @@ "actions": { "copyLink": "Copy invite link", "revokeInvite": "Revoke invite", - "removeMember": "Remove member", - "acceptInvite": "Click to accept invite (demo)" + "removeMember": "Remove member" }, "noInvites": "No pending invites", "noMembers": "No members", @@ -2137,7 +2136,6 @@ "createNewWorkspace": "create a new one." }, "menu": { - "editWorkspaceDetails": "Edit workspace details", "leaveWorkspace": "Leave Workspace", "deleteWorkspace": "Delete Workspace", "deleteWorkspaceDisabledTooltip": "Cancel your workspace's active subscription first" @@ -2180,11 +2178,6 @@ "namePlaceholder": "Enter workspace name", "create": "Create" }, - "editWorkspaceDialog": { - "title": "Edit workspace details", - "nameLabel": "Workspace name", - "save": "Save changes" - }, "toast": { "workspaceCreated": { "title": "Workspace created", diff --git a/src/platform/cloud/subscription/components/SubscriptionPanelContent.vue b/src/platform/cloud/subscription/components/SubscriptionPanelContent.vue index b5b8cffbe..f1fa68c37 100644 --- a/src/platform/cloud/subscription/components/SubscriptionPanelContent.vue +++ b/src/platform/cloud/subscription/components/SubscriptionPanelContent.vue @@ -16,7 +16,7 @@ @@ -259,12 +259,7 @@ import { useWorkspace } from '@/platform/workspace/composables/useWorkspace' import { cn } from '@/utils/tailwindUtil' const authActions = useFirebaseAuthActions() -const { - permissions, - isWorkspaceSubscribed, - workspaceRole, - subscribeWorkspace -} = useWorkspace() +const { permissions, isWorkspaceSubscribed, workspaceRole } = useWorkspace() const { t, n } = useI18n() // OWNER with unsubscribed workspace @@ -272,11 +267,6 @@ const isOwnerUnsubscribed = computed( () => workspaceRole.value === 'OWNER' && !isWorkspaceSubscribed.value ) -// Demo: Subscribe workspace to PRO monthly plan -function handleSubscribeWorkspace() { - subscribeWorkspace('PRO_MONTHLY') -} - const { isActiveSubscription, isCancelled, diff --git a/src/platform/workspace/composables/useWorkspace.ts b/src/platform/workspace/composables/useWorkspace.ts index 0c18f027f..fcb81d77a 100644 --- a/src/platform/workspace/composables/useWorkspace.ts +++ b/src/platform/workspace/composables/useWorkspace.ts @@ -1,9 +1,8 @@ -import { computed, ref, shallowRef } from 'vue' +import { computed, ref } from 'vue' import { useCurrentUser } from '@/composables/auth/useCurrentUser' -type WorkspaceRole = 'PERSONAL' | 'MEMBER' | 'OWNER' -type SubscriptionPlan = 'PRO_MONTHLY' | 'PRO_YEARLY' | null +export type WorkspaceRole = 'PERSONAL' | 'MEMBER' | 'OWNER' export interface WorkspaceMember { id: string @@ -21,14 +20,10 @@ export interface PendingInvite { inviteLink: string } -interface Workspace { +interface WorkspaceMockData { id: string | null name: string role: WorkspaceRole - isSubscribed: boolean - subscriptionPlan: SubscriptionPlan - members: WorkspaceMember[] - pendingInvites: PendingInvite[] } export interface AvailableWorkspace { @@ -38,7 +33,7 @@ export interface AvailableWorkspace { } /** Permission flags for workspace actions */ -interface WorkspacePermissions { +export interface WorkspacePermissions { canViewOtherMembers: boolean canViewPendingInvites: boolean canInviteMembers: boolean @@ -50,7 +45,7 @@ interface WorkspacePermissions { } /** UI configuration for workspace role */ -interface WorkspaceUIConfig { +export interface WorkspaceUIConfig { showMembersList: boolean showPendingTab: boolean showSearch: boolean @@ -59,7 +54,6 @@ interface WorkspaceUIConfig { membersGridCols: string pendingGridCols: string headerGridCols: string - showEditWorkspaceMenuItem: boolean workspaceMenuAction: 'leave' | 'delete' | null workspaceMenuDisabledTooltip: string | null } @@ -72,7 +66,7 @@ const ROLE_PERMISSIONS: Record = { canManageInvites: false, canRemoveMembers: false, canLeaveWorkspace: false, - canAccessWorkspaceMenu: true, + canAccessWorkspaceMenu: false, canManageSubscription: true }, MEMBER: { @@ -107,7 +101,6 @@ const ROLE_UI_CONFIG: Record = { membersGridCols: 'grid-cols-1', pendingGridCols: 'grid-cols-[50%_20%_20%_10%]', headerGridCols: 'grid-cols-1', - showEditWorkspaceMenuItem: true, workspaceMenuAction: null, workspaceMenuDisabledTooltip: null }, @@ -120,7 +113,6 @@ const ROLE_UI_CONFIG: Record = { membersGridCols: 'grid-cols-[1fr_auto]', pendingGridCols: 'grid-cols-[50%_20%_20%_10%]', headerGridCols: 'grid-cols-[1fr_auto]', - showEditWorkspaceMenuItem: false, workspaceMenuAction: 'leave', workspaceMenuDisabledTooltip: null }, @@ -133,202 +125,129 @@ const ROLE_UI_CONFIG: Record = { membersGridCols: 'grid-cols-[50%_40%_10%]', pendingGridCols: 'grid-cols-[50%_20%_20%_10%]', headerGridCols: 'grid-cols-[50%_40%_10%]', - showEditWorkspaceMenuItem: true, workspaceMenuAction: 'delete', workspaceMenuDisabledTooltip: 'workspacePanel.menu.deleteWorkspaceDisabledTooltip' } } +const MOCK_DATA: Record = { + PERSONAL: { + id: null, + name: 'Personal', + role: 'PERSONAL' + }, + MEMBER: { + id: 'workspace-abc-123', + name: 'Acme Corp', + role: 'MEMBER' + }, + OWNER: { + id: 'workspace-xyz-789', + name: 'Acme Corp', + role: 'OWNER' + } +} + +/** Mock list of all available workspaces for the current user */ +const MOCK_AVAILABLE_WORKSPACES: AvailableWorkspace[] = [ + { id: null, name: 'Personal workspace', role: 'PERSONAL' }, + { id: 'workspace-comfy-001', name: 'Team Comfy', role: 'OWNER' }, + { id: 'workspace-orange-002', name: 'OrangeDesignStudio', role: 'MEMBER' }, + { id: 'workspace-001', name: 'Workspace001', role: 'MEMBER' }, + { id: 'workspace-002', name: 'Workspace002', role: 'MEMBER' } +] + const MAX_OWNED_WORKSPACES = 10 + +const MOCK_MEMBERS: WorkspaceMember[] = [ + { + id: '1', + name: 'Alice', + email: 'alice@example.com', + joinDate: new Date('2025-11-15') + }, + { + id: '2', + name: 'Bob', + email: 'bob@example.com', + joinDate: new Date('2025-12-01') + }, + { + id: '3', + name: 'Charlie', + email: 'charlie@example.com', + joinDate: new Date('2026-01-05') + } +] + +const MOCK_PENDING_INVITES: PendingInvite[] = [ + { + id: '1', + name: 'John', + email: 'john@gmail.com', + inviteDate: new Date('2026-01-02'), + expiryDate: new Date('2026-01-09'), + inviteLink: 'https://example.com/invite/abc123' + }, + { + id: '2', + name: 'User102', + email: 'user102@gmail.com', + inviteDate: new Date('2026-01-01'), + expiryDate: new Date('2026-01-08'), + inviteLink: 'https://example.com/invite/def456' + }, + { + id: '3', + name: 'User944', + email: 'user944@gmail.com', + inviteDate: new Date('2026-01-01'), + expiryDate: new Date('2026-01-08'), + inviteLink: 'https://example.com/invite/ghi789' + }, + { + id: '4', + name: 'User45', + email: 'user45@gmail.com', + inviteDate: new Date('2025-12-15'), + expiryDate: new Date('2025-12-22'), + inviteLink: 'https://example.com/invite/jkl012' + }, + { + id: '5', + name: 'User944', + email: 'user944@gmail.com', + inviteDate: new Date('2025-12-05'), + expiryDate: new Date('2025-12-22'), + inviteLink: 'https://example.com/invite/mno345' + } +] + +// Constants const MAX_WORKSPACE_MEMBERS = 50 -function generateId(): string { - return Math.random().toString(36).substring(2, 10) -} - -function createPersonalWorkspace(): Workspace { - return { - id: null, - name: 'Personal workspace', - role: 'PERSONAL', - isSubscribed: true, - subscriptionPlan: null, - members: [], - pendingInvites: [] - } -} - -// Global state - start with personal workspace only -const _workspaces = shallowRef([createPersonalWorkspace()]) -const _currentWorkspaceIndex = ref(0) +// Shared state for workspace +const _workspaceId = ref(null) +const _workspaceName = ref('Personal workspace') +const _workspaceRole = ref('PERSONAL') +const _isWorkspaceSubscribed = ref(true) const _activeTab = ref('plan') - -// Helper to get current workspace -function getCurrentWorkspace(): Workspace { - return _workspaces.value[_currentWorkspaceIndex.value] -} - -// Helper to update current workspace -function updateCurrentWorkspace(updates: Partial) { - const index = _currentWorkspaceIndex.value - const updated = { ..._workspaces.value[index], ...updates } - _workspaces.value = [ - ..._workspaces.value.slice(0, index), - updated, - ..._workspaces.value.slice(index + 1) - ] -} - -/** - * Switch to a different workspace - */ -function switchWorkspace(workspace: AvailableWorkspace) { - const index = _workspaces.value.findIndex((w) => w.id === workspace.id) - if (index !== -1) { - _currentWorkspaceIndex.value = index - } -} - -/** - * Create a new workspace - */ -function createNewWorkspace(name: string): Workspace { - const newWorkspace: Workspace = { - id: `workspace-${generateId()}`, - name, - role: 'OWNER', - isSubscribed: false, - subscriptionPlan: null, - members: [], - pendingInvites: [ - // Add one pending invite for testing revoke - { - id: generateId(), - name: 'PendingUser', - email: 'pending@example.com', - inviteDate: new Date(), - expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), - inviteLink: `https://cloud.comfy.org/workspace/invite/${generateId()}` - } - ] - } - - _workspaces.value = [..._workspaces.value, newWorkspace] - // Switch to the new workspace - _currentWorkspaceIndex.value = _workspaces.value.length - 1 - - return newWorkspace -} - -/** - * Subscribe the current workspace to a plan - */ -function subscribeCurrentWorkspace(plan: SubscriptionPlan = 'PRO_MONTHLY') { - updateCurrentWorkspace({ - isSubscribed: true, - subscriptionPlan: plan - }) -} - -/** - * Delete the current workspace (OWNER only) - */ -function deleteCurrentWorkspace() { - const current = getCurrentWorkspace() - if (current.role === 'OWNER') { - _workspaces.value = _workspaces.value.filter((w) => w.id !== current.id) - _currentWorkspaceIndex.value = 0 - } -} - -/** - * Leave the current workspace (MEMBER only) - */ -function leaveCurrentWorkspace() { - const current = getCurrentWorkspace() - if (current.role === 'MEMBER') { - _workspaces.value = _workspaces.value.filter((w) => w.id !== current.id) - _currentWorkspaceIndex.value = 0 - } -} - -/** - * Add a member to the current workspace - */ -function addMemberToWorkspace( - member: Omit -) { - const current = getCurrentWorkspace() - const newMember: WorkspaceMember = { - ...member, - id: generateId(), - joinDate: new Date() - } - updateCurrentWorkspace({ - members: [...current.members, newMember] - }) -} - -/** - * Remove a member from the current workspace - */ -function removeMemberFromWorkspace(memberId: string) { - const current = getCurrentWorkspace() - updateCurrentWorkspace({ - members: current.members.filter((m) => m.id !== memberId) - }) -} - -/** - * Update the current workspace name - */ -function updateWorkspaceNameFn(name: string) { - updateCurrentWorkspace({ name }) -} - -/** - * Revoke a pending invite - */ -function revokePendingInvite(inviteId: string) { - const current = getCurrentWorkspace() - updateCurrentWorkspace({ - pendingInvites: current.pendingInvites.filter((i) => i.id !== inviteId) - }) -} - -/** - * Accept a pending invite (move to active members) - * For demo: simulates user accepting the invite - */ -function acceptPendingInvite(inviteId: string) { - const current = getCurrentWorkspace() - const invite = current.pendingInvites.find((i) => i.id === inviteId) - if (invite) { - // Remove from pending - const updatedPending = current.pendingInvites.filter( - (i) => i.id !== inviteId - ) - // Add to active members - const newMember: WorkspaceMember = { - id: generateId(), - name: invite.name, - email: invite.email, - joinDate: new Date() - } - updateCurrentWorkspace({ - pendingInvites: updatedPending, - members: [...current.members, newMember] - }) - } -} +const _members = ref([]) +const _pendingInvites = ref([]) +const _availableWorkspaces = ref( + MOCK_AVAILABLE_WORKSPACES +) /** * Set workspace mock state for testing UI * Usage in browser console: window.__setWorkspaceRole('OWNER') */ function setMockRole(role: WorkspaceRole) { - updateCurrentWorkspace({ role }) + const data = MOCK_DATA[role] + _workspaceId.value = data.id + _workspaceName.value = data.name + _workspaceRole.value = data.role } /** @@ -336,7 +255,19 @@ function setMockRole(role: WorkspaceRole) { * Usage in browser console: window.__setWorkspaceSubscribed(false) */ function setMockSubscribed(subscribed: boolean) { - updateCurrentWorkspace({ isSubscribed: subscribed }) + _isWorkspaceSubscribed.value = subscribed +} + +/** + * Switch to a different workspace + */ +function switchWorkspace(workspace: AvailableWorkspace) { + _workspaceId.value = workspace.id + _workspaceName.value = workspace.name + _workspaceRole.value = workspace.role + // Reset members/invites when switching + _members.value = [] + _pendingInvites.value = [] } // Expose to window for dev testing @@ -359,70 +290,43 @@ if (typeof window !== 'undefined') { export function useWorkspace() { const { userDisplayName, userEmail } = useCurrentUser() - // Computed from current workspace - const currentWorkspace = computed(() => getCurrentWorkspace()) - const workspaceId = computed(() => currentWorkspace.value?.id ?? null) - const workspaceName = computed( - () => currentWorkspace.value?.name ?? 'Personal workspace' - ) - const workspaceRole = computed( - () => currentWorkspace.value?.role ?? 'PERSONAL' - ) + const workspaceId = computed(() => _workspaceId.value) + const workspaceName = computed(() => _workspaceName.value) + const workspaceRole = computed(() => _workspaceRole.value) const activeTab = computed(() => _activeTab.value) const isPersonalWorkspace = computed( - () => currentWorkspace.value?.role === 'PERSONAL' + () => _workspaceRole.value === 'PERSONAL' ) - const isWorkspaceSubscribed = computed( - () => currentWorkspace.value?.isSubscribed ?? false - ) - - const subscriptionPlan = computed( - () => currentWorkspace.value?.subscriptionPlan ?? null - ) + const isWorkspaceSubscribed = computed(() => _isWorkspaceSubscribed.value) const permissions = computed( - () => ROLE_PERMISSIONS[workspaceRole.value] + () => ROLE_PERMISSIONS[_workspaceRole.value] ) const uiConfig = computed( - () => ROLE_UI_CONFIG[workspaceRole.value] + () => ROLE_UI_CONFIG[_workspaceRole.value] ) function setActiveTab(tab: string | number) { _activeTab.value = String(tab) } - // For personal workspace, always show current user as the only member - const members = computed(() => { - if (isPersonalWorkspace.value) { - return [ - { - id: 'current-user', - name: userDisplayName.value ?? 'You', - email: userEmail.value ?? '', - joinDate: new Date() - } - ] - } - return currentWorkspace.value?.members ?? [] - }) - const pendingInvites = computed( - () => currentWorkspace.value?.pendingInvites ?? [] - ) + const members = computed(() => _members.value) + const pendingInvites = computed(() => _pendingInvites.value) const totalMemberSlots = computed( - () => members.value.length + pendingInvites.value.length + () => _members.value.length + _pendingInvites.value.length ) const isInviteLimitReached = computed( () => totalMemberSlots.value >= MAX_WORKSPACE_MEMBERS ) - // Fetch members - returns current user for personal workspace + // TODO: Replace with actual API calls async function fetchMembers(): Promise { - if (isPersonalWorkspace.value) { - return [ + if (_workspaceRole.value === 'PERSONAL') { + _members.value = [ { id: 'current-user', name: userDisplayName.value ?? 'You', @@ -430,20 +334,27 @@ export function useWorkspace() { joinDate: new Date() } ] + } else { + _members.value = MOCK_MEMBERS } - return members.value + return _members.value } async function fetchPendingInvites(): Promise { - return pendingInvites.value + if (_workspaceRole.value === 'PERSONAL') { + _pendingInvites.value = [] + } else { + _pendingInvites.value = MOCK_PENDING_INVITES + } + return _pendingInvites.value } - async function revokeInvite(inviteId: string): Promise { - revokePendingInvite(inviteId) + async function revokeInvite(_inviteId: string): Promise { + // TODO: API call to revoke invite } async function copyInviteLink(inviteId: string): Promise { - const invite = pendingInvites.value.find((i) => i.id === inviteId) + const invite = _pendingInvites.value.find((i) => i.id === inviteId) if (invite) { await navigator.clipboard.writeText(invite.inviteLink) return invite.inviteLink @@ -451,63 +362,35 @@ export function useWorkspace() { throw new Error('Invite not found') } - /** - * Copy invite link and simulate member accepting (for demo) - * When copy link is clicked, the invited user "accepts" and becomes a member - */ - async function copyInviteLinkAndAccept(inviteId: string): Promise { - const invite = pendingInvites.value.find((i) => i.id === inviteId) - if (invite) { - await navigator.clipboard.writeText(invite.inviteLink) - - // Simulate user accepting invite: move from pending to active - revokePendingInvite(inviteId) - addMemberToWorkspace({ - name: invite.name, - email: invite.email - }) - - return invite.inviteLink - } - throw new Error('Invite not found') - } - /** * Create an invite link for a given email + * TODO: Replace with actual API call */ async function createInviteLink(email: string): Promise { // Simulate API delay - await new Promise((resolve) => setTimeout(resolve, 500)) + await new Promise((resolve) => setTimeout(resolve, 1000)) - const inviteId = generateId() - const inviteLink = `https://cloud.comfy.org/workspace/invite/${inviteId}` + // Generate mock invite link + const inviteId = Math.random().toString(36).substring(2, 10) + const inviteLink = `https://cloud.comfy.org/workspace?3423532/invite/hi789jkl012mno345pq` - // Add to pending invites - const current = getCurrentWorkspace() + // Add to pending invites (mock) const newInvite: PendingInvite = { id: inviteId, name: email.split('@')[0], email, inviteDate: new Date(), - expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days inviteLink } - updateCurrentWorkspace({ - pendingInvites: [...current.pendingInvites, newInvite] - }) + _pendingInvites.value = [..._pendingInvites.value, newInvite] return inviteLink } - const availableWorkspaces = computed(() => - _workspaces.value.map((w) => ({ - id: w.id, - name: w.name, - role: w.role - })) - ) + const availableWorkspaces = computed(() => _availableWorkspaces.value) const ownedWorkspacesCount = computed( - () => _workspaces.value.filter((w) => w.role === 'OWNER').length + () => _availableWorkspaces.value.filter((w) => w.role === 'OWNER').length ) const canCreateWorkspace = computed( () => ownedWorkspacesCount.value < MAX_OWNED_WORKSPACES @@ -520,7 +403,6 @@ export function useWorkspace() { activeTab, isPersonalWorkspace, isWorkspaceSubscribed, - subscriptionPlan, permissions, uiConfig, setActiveTab, @@ -529,11 +411,6 @@ export function useWorkspace() { ownedWorkspacesCount, canCreateWorkspace, switchWorkspace, - // Workspace management - createWorkspace: createNewWorkspace, - subscribeWorkspace: subscribeCurrentWorkspace, - deleteWorkspace: deleteCurrentWorkspace, - leaveWorkspace: leaveCurrentWorkspace, // Members members, pendingInvites, @@ -542,13 +419,8 @@ export function useWorkspace() { fetchMembers, fetchPendingInvites, revokeInvite, - acceptInvite: acceptPendingInvite, copyInviteLink, - copyInviteLinkAndAccept, createInviteLink, - addMember: addMemberToWorkspace, - removeMember: removeMemberFromWorkspace, - updateWorkspaceName: updateWorkspaceNameFn, // Dev helpers setMockRole, setMockSubscribed diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index c72ca8658..944c915a6 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -3,7 +3,6 @@ import type { Component } from 'vue' import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue' import CreateWorkspaceDialogContent from '@/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue' -import EditWorkspaceDialogContent from '@/components/dialog/content/workspace/EditWorkspaceDialogContent.vue' import DeleteWorkspaceDialogContent from '@/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue' import InviteMemberDialogContent from '@/components/dialog/content/workspace/InviteMemberDialogContent.vue' import LeaveWorkspaceDialogContent from '@/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue' @@ -527,10 +526,11 @@ export const useDialogService = () => { show() } - function showLeaveWorkspaceDialog() { + function showLeaveWorkspaceDialog(onConfirm: () => void | Promise) { return dialogStore.showDialog({ key: 'leave-workspace', component: LeaveWorkspaceDialogContent, + props: { onConfirm }, dialogComponentProps: { headless: true, pt: { @@ -542,10 +542,11 @@ export const useDialogService = () => { }) } - function showDeleteWorkspaceDialog() { + function showDeleteWorkspaceDialog(onConfirm: () => void | Promise) { return dialogStore.showDialog({ key: 'delete-workspace', component: DeleteWorkspaceDialogContent, + props: { onConfirm }, dialogComponentProps: { headless: true, pt: { @@ -557,11 +558,11 @@ export const useDialogService = () => { }) } - function showRemoveMemberDialog(memberId: string) { + function showRemoveMemberDialog(onConfirm: () => void | Promise) { return dialogStore.showDialog({ key: 'remove-member', component: RemoveMemberDialogContent, - props: { memberId }, + props: { onConfirm }, dialogComponentProps: { headless: true, pt: { @@ -573,11 +574,11 @@ export const useDialogService = () => { }) } - function showRevokeInviteDialog(inviteId: string) { + function showRevokeInviteDialog(onConfirm: () => void | Promise) { return dialogStore.showDialog({ key: 'revoke-invite', component: RevokeInviteDialogContent, - props: { inviteId }, + props: { onConfirm }, dialogComponentProps: { headless: true, pt: { @@ -625,21 +626,6 @@ export const useDialogService = () => { }) } - function showEditWorkspaceDialog() { - return dialogStore.showDialog({ - key: 'edit-workspace', - component: EditWorkspaceDialogContent, - dialogComponentProps: { - headless: true, - pt: { - header: { class: 'p-0! hidden' }, - content: { class: 'p-0! m-0! rounded-2xl' }, - root: { class: 'rounded-2xl max-w-[400px] w-full' } - } - } - }) - } - return { showLoadWorkflowWarning, showMissingModelsWarning, @@ -657,7 +643,6 @@ export const useDialogService = () => { showRevokeInviteDialog, showInviteMemberDialog, showCreateWorkspaceDialog, - showEditWorkspaceDialog, showExtensionDialog, prompt, showErrorDialog,