Skip to content

Multi-Workspace

Overview

The Multi-Workspace System enables users to work across multiple workspaces within a single application instance. It provides workspace switching, workspace management, access control, subscription handling, and workspace-specific settings. The system supports parent-child workspace relationships, workspace suspension checks, and module-based access control.

Architecture

System Components

  1. Workspace Switching: Switch between available workspaces
  2. Workspace Management: Create, manage, and configure workspaces
  3. Workspace Access Control: Middleware for workspace validation and access checks
  4. Workspace Context: Current workspace state management
  5. Workspace Settings: Workspace-specific configuration pages
  6. Subscription Integration: Workspace subscription and billing handling

File Structure

Workspace Switching & Selection

Vue Component Files (.vue)

  • components/theme/global/LeftMenu.vue - Left menu component

    • Contains workspace switcher dropdown
    • changeWorkspace method for switching workspaces
    • Display current workspace
    • List available workspaces
  • components/theme/settings/AccountSettingsLeftMenu.vue - Account settings menu

    • Contains workspace selector
    • updateRouteWp method for workspace route updates
    • Workspace selection interface

Workspace Management

Vue Component Files (.vue)

  • components/dam/Dialogs/Org-Settings/WorkspaceOwnerDialog.vue - Workspace owner dialog

    • Change workspace owner
    • Transfer ownership functionality
    • Owner selection interface
  • components/theme/settings/WorkspaceNote.vue - Workspace note component

    • Display workspace notes
    • Workspace information display
  • components/dam/StaticPlanWorkspaceBranding.vue - Static plan workspace branding

    • Workspace branding display
    • Plan-based branding customization

SVG Icon Components

  • components/svg/CollageWorkspaceIcon.vue - Workspace icon
    • Used in workspace UI elements
    • Consistent iconography

Workspace Middleware

JavaScript Files (.js)

  • middleware/checkWorkspace.js - Workspace validation middleware

    • Checks if workspace exists
    • Validates workspace access
    • Redirects if invalid
  • middleware/check-workspace-modules.js - Workspace modules middleware

    • Checks workspace modules
    • Redirects if module not available
    • Module access validation
  • middleware/can-create-childWorkspace.js - Child workspace creation middleware

    • Checks if user can create child workspace
    • Permission validation
    • Access control
  • middleware/can-access-child-workspace.js - Child workspace access middleware

    • Checks access to child workspace
    • Parent workspace validation
    • Access control
  • middleware/check-if-suspended.js - Workspace suspension middleware

    • Checks if workspace is suspended
    • Redirects if suspended
    • Suspension status validation

Workspace Mixins & Utilities

JavaScript Files (.js)

  • mixins/currentWorkspace.js - Current workspace mixin

    • Provides workspaceId computed property
    • Current workspace state
    • Workspace context helpers
  • mixins/routeParams.js - Route params mixin

    • Workspace parameter handling
    • Route parameter utilities
    • Workspace ID extraction
  • plugins/helper.js - Helper functions plugin

    • setCurrentWorkspace - Set current workspace
    • getWorkspaceId - Get workspace ID from route/store
    • _auth - Authentication helpers
    • allowedWorkspaces - Get user's allowed workspaces
    • getUserModulesAndRoles - Get user modules and roles for workspace

Workspace Display & Integration

Vue Component Files (.vue)

  • components/theme/global/CoreHeader.vue - Core header component

    • Uses currentWorkspace mixin
    • Displays current workspace
    • Workspace context
  • components/theme/global/CollageHeader.vue - Collage header component

    • May reference workspace
    • Workspace display
  • layouts/outerLayout.vue - Outer layout

    • Handles workspace context
    • Workspace-level layout

Workspace Subscription & Billing

JavaScript Files (.js)

  • mixins/subscription-functions.js - Subscription functions mixin
    • onSwitchWorkspace method
    • Subscription handling on workspace switch
    • Billing integration

Workspace Settings Pages

Vue Page Files (.vue)

  • pages/_workspace_id/workspace-settings/index.vue - Main workspace settings page
    • Workspace configuration
    • General settings
    • Workspace information

Route: /:workspace_id/workspace-settings

  • pages/_workspace_id/workspace-settings/dam/index.vue - DAM workspace settings page
    • DAM-specific workspace settings
    • Module configuration

Route: /:workspace_id/workspace-settings/dam

Workspace Structure

Workspace Object

javascript
{
  id: 123,
  name: "Marketing Team",
  slug: "marketing-team",
  parent_workspace_id: null, // null for parent workspace
  owner_id: 456,
  owner: {
    id: 456,
    name: "John Doe",
    email: "[email protected]"
  },
  plan: "professional",
  plan_type: "paid",
  is_suspended: false,
  is_active: true,
  modules: ["dam", "analytics", "collaboration"],
  settings: {
    branding: {
      logo: "/logos/marketing.png",
      primary_color: "#1976D2"
    },
    features: {
      custom_fields: true,
      advanced_search: true
    }
  },
  created_at: "2024-01-15T10:00:00Z",
  updated_at: "2024-01-20T14:30:00Z"
}

User Workspace Access Object

javascript
{
  user_id: 789,
  workspace_id: 123,
  role: "admin", // "admin", "member", "viewer"
  permissions: ["can-manage-workspace", "can-manage-users"],
  modules: ["dam", "analytics"],
  is_active: true,
  joined_at: "2024-01-15T10:00:00Z"
}

Component Implementation

Workspace Switcher Component

File: components/theme/global/LeftMenu.vue

Workspace switcher dropdown in left menu.

Features

  • Display current workspace
  • List available workspaces
  • Switch workspace functionality
  • Workspace search/filter
  • Workspace creation link (if permitted)

Methods

javascript
// Change workspace
async changeWorkspace(workspaceId) {
  try {
    // Update user profile with default workspace
    await this.$axios.post('/user/update-profile', {
      default_workspace_id: workspaceId
    })
    
    // Update current workspace in store
    this.$store.commit('workspace/setCurrentWorkspace', workspaceId)
    
    // Handle subscription on workspace switch
    if (this.onSwitchWorkspace) {
      await this.onSwitchWorkspace(workspaceId)
    }
    
    // Navigate to new workspace
    const newRoute = this.$route.path.replace(
      `/${this.currentWorkspaceId}`,
      `/${workspaceId}`
    )
    this.$router.push(newRoute)
    
    // Refresh workspace data
    await this.loadWorkspaceData(workspaceId)
  } catch (error) {
    this.$toast.error('Failed to switch workspace')
  }
}

// Load workspace data
async loadWorkspaceData(workspaceId) {
  try {
    const response = await this.$axios.get(`/view-workspace`, {
      params: { workspace_id: workspaceId }
    })
    this.$store.commit('workspace/setWorkspaceData', response.data.workspace)
  } catch (error) {
    console.error('Failed to load workspace data', error)
  }
}

Usage

vue
<template>
  <div class="workspace-switcher">
    <v-menu offset-y>
      <template v-slot:activator="{ on }">
        <v-btn text v-on="on">
          <WorkspaceIcon />
          {{ currentWorkspace.name }}
          <v-icon>mdi-chevron-down</v-icon>
        </v-btn>
      </template>
      
      <v-list>
        <v-list-item
          v-for="workspace in availableWorkspaces"
          :key="workspace.id"
          :class="{ 'active': workspace.id === currentWorkspaceId }"
          @click="changeWorkspace(workspace.id)"
        >
          <v-list-item-avatar>
            <WorkspaceIcon />
          </v-list-item-avatar>
          <v-list-item-content>
            <v-list-item-title>{{ workspace.name }}</v-list-item-title>
            <v-list-item-subtitle>{{ workspace.plan }}</v-list-item-subtitle>
          </v-list-item-content>
          <v-list-item-action v-if="workspace.id === currentWorkspaceId">
            <v-icon color="primary">mdi-check</v-icon>
          </v-list-item-action>
        </v-list-item>
        
        <v-divider />
        
        <v-list-item
          v-if="canCreateWorkspace"
          @click="createWorkspace"
        >
          <v-list-item-icon>
            <v-icon>mdi-plus</v-icon>
          </v-list-item-icon>
          <v-list-item-content>
            <v-list-item-title>Create Workspace</v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list>
    </v-menu>
  </div>
</template>

Current Workspace Mixin

File: mixins/currentWorkspace.js

Provides current workspace context to components.

Computed Properties

javascript
computed: {
  workspaceId() {
    // Get from route params
    if (this.$route.params.workspace_id) {
      return this.$route.params.workspace_id
    }
    
    // Get from store
    if (this.$store.state.workspace.currentWorkspaceId) {
      return this.$store.state.workspace.currentWorkspaceId
    }
    
    // Get from user profile
    if (this.$store.state.auth.user?.default_workspace_id) {
      return this.$store.state.auth.user.default_workspace_id
    }
    
    return null
  },
  
  currentWorkspace() {
    return this.$store.getters['workspace/currentWorkspace']
  },
  
  isWorkspaceOwner() {
    const workspace = this.currentWorkspace
    const user = this.$store.state.auth.user
    return workspace && workspace.owner_id === user.id
  },
  
  canManageWorkspace() {
    const workspace = this.currentWorkspace
    const user = this.$store.state.auth.user
    const userWorkspace = this.$store.getters['workspace/userWorkspace']
    
    return this.isWorkspaceOwner ||
           userWorkspace?.role === 'admin' ||
           userWorkspace?.permissions?.includes('can-manage-workspace')
  }
}

Usage

vue
<script>
import currentWorkspace from '~/mixins/currentWorkspace'

export default {
  mixins: [currentWorkspace],
  methods: {
    async loadData() {
      // Use workspaceId from mixin
      const response = await this.$axios.get('/api/data', {
        params: { workspace_id: this.workspaceId }
      })
      // Handle response
    }
  }
}
</script>

Middleware Implementation

Check Workspace Middleware

File: middleware/checkWorkspace.js

Validates workspace existence and access.

javascript
export default async function({ store, route, redirect, error }) {
  const workspaceId = route.params.workspace_id
  
  if (!workspaceId) {
    return redirect('/')
  }
  
  try {
    // Check if workspace exists
    const response = await this.$axios.get('/view-workspace', {
      params: { workspace_id: workspaceId }
    })
    
    if (!response.data.workspace) {
      return error({ statusCode: 404, message: 'Workspace not found' })
    }
    
    const workspace = response.data.workspace
    
    // Check if workspace is active
    if (!workspace.is_active) {
      return redirect('/workspace-inactive')
    }
    
    // Set workspace in store
    store.commit('workspace/setCurrentWorkspace', workspaceId)
    store.commit('workspace/setWorkspaceData', workspace)
    
  } catch (err) {
    if (err.response?.status === 404) {
      return error({ statusCode: 404, message: 'Workspace not found' })
    }
    return error({ statusCode: 500, message: 'Failed to load workspace' })
  }
}

Check Workspace Modules Middleware

File: middleware/check-workspace-modules.js

Validates workspace module access.

javascript
export default async function({ store, route, redirect }) {
  const workspaceId = route.params.workspace_id
  const requiredModule = route.meta.requiredModule // e.g., 'dam'
  
  if (!requiredModule) {
    return
  }
  
  const workspace = store.getters['workspace/currentWorkspace']
  
  if (!workspace) {
    return redirect(`/${workspaceId}/workspace-settings`)
  }
  
  // Check if workspace has required module
  if (!workspace.modules || !workspace.modules.includes(requiredModule)) {
    return redirect(`/${workspaceId}/workspace-settings`)
  }
  
  // Check if user has access to module
  const userWorkspace = store.getters['workspace/userWorkspace']
  if (!userWorkspace || !userWorkspace.modules.includes(requiredModule)) {
    return redirect(`/${workspaceId}/workspace-settings`)
  }
}

Check If Suspended Middleware

File: middleware/check-if-suspended.js

Checks workspace suspension status.

javascript
export default async function({ store, route, redirect }) {
  const workspaceId = route.params.workspace_id
  
  const workspace = store.getters['workspace/currentWorkspace']
  
  if (!workspace) {
    // Load workspace if not in store
    try {
      const response = await this.$axios.get('/view-workspace', {
        params: { workspace_id: workspaceId }
      })
      store.commit('workspace/setWorkspaceData', response.data.workspace)
    } catch (error) {
      return redirect('/')
    }
  }
  
  if (workspace.is_suspended) {
    return redirect(`/${workspaceId}/workspace-suspended`)
  }
}

Can Create Child Workspace Middleware

File: middleware/can-create-childWorkspace.js

Validates permission to create child workspace.

javascript
export default async function({ store, route, redirect, error }) {
  const workspaceId = route.params.workspace_id
  const user = store.state.auth.user
  
  const workspace = store.getters['workspace/currentWorkspace']
  
  if (!workspace) {
    return error({ statusCode: 404, message: 'Workspace not found' })
  }
  
  // Check if user is workspace owner or admin
  const isOwner = workspace.owner_id === user.id
  const userWorkspace = store.getters['workspace/userWorkspace']
  const isAdmin = userWorkspace?.role === 'admin'
  
  // Check if workspace allows child workspaces
  const canCreate = workspace.settings?.features?.child_workspaces !== false
  
  if (!isOwner && !isAdmin) {
    return error({ statusCode: 403, message: 'Permission denied' })
  }
  
  if (!canCreate) {
    return error({ statusCode: 403, message: 'Child workspaces not allowed' })
  }
}

Helper Functions

Workspace Helper Functions

File: plugins/helper.js

Set Current Workspace

javascript
export function setCurrentWorkspace(workspaceId) {
  // Update store
  this.$store.commit('workspace/setCurrentWorkspace', workspaceId)
  
  // Update user profile
  this.$axios.post('/user/update-profile', {
    default_workspace_id: workspaceId
  })
  
  // Update localStorage
  localStorage.setItem('current_workspace_id', workspaceId)
}

Get Workspace ID

javascript
export function getWorkspaceId() {
  // From route params
  if (this.$route.params.workspace_id) {
    return this.$route.params.workspace_id
  }
  
  // From store
  if (this.$store.state.workspace.currentWorkspaceId) {
    return this.$store.state.workspace.currentWorkspaceId
  }
  
  // From localStorage
  const stored = localStorage.getItem('current_workspace_id')
  if (stored) {
    return stored
  }
  
  // From user profile
  if (this.$store.state.auth.user?.default_workspace_id) {
    return this.$store.state.auth.user.default_workspace_id
  }
  
  return null
}

Get Allowed Workspaces

javascript
export function allowedWorkspaces() {
  const user = this.$store.state.auth.user
  if (!user) {
    return []
  }
  
  // Get from store if available
  if (this.$store.state.workspace.allowedWorkspaces) {
    return this.$store.state.workspace.allowedWorkspaces
  }
  
  // Fetch from API
  return this.$axios.get('/user/workspaces').then(response => {
    const workspaces = response.data.workspaces
    this.$store.commit('workspace/setAllowedWorkspaces', workspaces)
    return workspaces
  })
}

Get User Modules and Roles

javascript
export function getUserModulesAndRoles(workspaceId) {
  return this.$axios.get('/user/workspace-access', {
    params: { workspace_id: workspaceId }
  }).then(response => {
    return {
      modules: response.data.modules,
      role: response.data.role,
      permissions: response.data.permissions
    }
  })
}

Subscription Functions Mixin

File: mixins/subscription-functions.js

Handles subscription on workspace switch.

javascript
export default {
  methods: {
    async onSwitchWorkspace(workspaceId) {
      try {
        // Load workspace subscription
        const response = await this.$axios.get('/workspace/subscription', {
          params: { workspace_id: workspaceId }
        })
        
        const subscription = response.data.subscription
        
        // Update subscription in store
        this.$store.commit('subscription/setSubscription', subscription)
        
        // Check subscription status
        if (subscription.status === 'expired' || subscription.status === 'cancelled') {
          this.$toast.warning('Workspace subscription has expired')
        }
        
        // Handle plan changes
        if (subscription.plan_changed) {
          this.$toast.info('Workspace plan has changed')
        }
        
      } catch (error) {
        console.error('Failed to load subscription', error)
      }
    }
  }
}

Workspace Settings Pages

Main Workspace Settings Page

File: pages/_workspace_id/workspace-settings/index.vue

Route: /:workspace_id/workspace-settings

Main workspace configuration page.

Features

  • Workspace information
  • Workspace branding
  • Workspace settings
  • Workspace owner management
  • Workspace modules
  • Workspace subscription

DAM Workspace Settings Page

File: pages/_workspace_id/workspace-settings/dam/index.vue

Route: /:workspace_id/workspace-settings/dam

DAM-specific workspace settings.

Features

  • DAM module configuration
  • DAM-specific settings
  • Asset management settings

API Integration

Get Workspace Details

Endpoint: GET /view-workspace

Query Parameters:

  • workspace_id (required) - Workspace identifier

Response:

json
{
  "workspace": {
    "id": 123,
    "name": "Marketing Team",
    "slug": "marketing-team",
    "parent_workspace_id": null,
    "owner_id": 456,
    "owner": {
      "id": 456,
      "name": "John Doe",
      "email": "[email protected]"
    },
    "plan": "professional",
    "plan_type": "paid",
    "is_suspended": false,
    "is_active": true,
    "modules": ["dam", "analytics", "collaboration"],
    "settings": {
      "branding": {
        "logo": "/logos/marketing.png",
        "primary_color": "#1976D2"
      }
    },
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-20T14:30:00Z"
  }
}

Update User Profile with Default Workspace

Endpoint: POST /user/update-profile

Request Body:

json
{
  "default_workspace_id": 123
}

Response:

json
{
  "success": true,
  "user": {
    "id": 789,
    "default_workspace_id": 123,
    "updated_at": "2024-01-20T14:30:00Z"
  }
}

Get User Workspaces

Endpoint: GET /user/workspaces

Response:

json
{
  "workspaces": [
    {
      "id": 123,
      "name": "Marketing Team",
      "role": "admin",
      "is_active": true
    }
  ]
}

Get User Workspace Access

Endpoint: GET /user/workspace-access

Query Parameters:

  • workspace_id (required) - Workspace identifier

Response:

json
{
  "modules": ["dam", "analytics"],
  "role": "admin",
  "permissions": ["can-manage-workspace", "can-manage-users"],
  "is_active": true
}

Workflows

Switch Workspace

1. User clicks workspace switcher
   Component: LeftMenu.vue
   - Dropdown shows available workspaces

2. User selects workspace
   Method: changeWorkspace(workspaceId)

3. Update user profile
   API: POST /user/update-profile
   Body: { default_workspace_id: workspaceId }

4. Update store
   Store: setCurrentWorkspace(workspaceId)
   - Set current workspace ID
   - Load workspace data

5. Handle subscription
   Mixin: onSwitchWorkspace(workspaceId)
   - Load subscription data
   - Check subscription status

6. Navigate to new workspace
   Router: Update route with new workspace_id
   - Replace workspace_id in path
   - Navigate to same page in new workspace

7. Refresh data
   - Load workspace-specific data
   - Update UI with new workspace context

Workspace Access Validation

1. User navigates to workspace route
   Route: /:workspace_id/...

2. Middleware: checkWorkspace.js
   - Extract workspace_id from route
   - Call API: GET /view-workspace

3. Validate workspace
   - Check if workspace exists
   - Check if workspace is active
   - Check if user has access

4. Set workspace context
   Store: setCurrentWorkspace, setWorkspaceData
   - Set current workspace
   - Store workspace data

5. Check modules (if required)
   Middleware: check-workspace-modules.js
   - Check if workspace has required module
   - Check if user has module access

6. Check suspension
   Middleware: check-if-suspended.js
   - Check if workspace is suspended
   - Redirect if suspended

7. Allow access
   - Continue to requested route
   - Workspace context available

Create Child Workspace

1. Admin navigates to create child workspace
   Route: /:workspace_id/workspace-settings/create-child

2. Middleware: can-create-childWorkspace.js
   - Check if user is owner/admin
   - Check if workspace allows child workspaces

3. Fill workspace form
   - Enter workspace name
   - Select workspace settings
   - Configure modules

4. Create workspace
   API: POST /workspace/create-child
   Body: {
     parent_workspace_id: currentWorkspaceId,
     name, settings, modules
   }

5. Workspace created
   - Child workspace created
   - User becomes owner/admin
   - Workspace added to available workspaces

6. Navigate to new workspace
   Router: Navigate to new workspace
   - Switch to child workspace
   - Load workspace data

Component Integration

Using Current Workspace Mixin

vue
<template>
  <div>
    <h1>{{ currentWorkspace.name }}</h1>
    <p>Workspace ID: {{ workspaceId }}</p>
  </div>
</template>

<script>
import currentWorkspace from '~/mixins/currentWorkspace'

export default {
  mixins: [currentWorkspace],
  methods: {
    async loadWorkspaceData() {
      const response = await this.$axios.get('/api/data', {
        params: { workspace_id: this.workspaceId }
      })
      // Handle response
    }
  }
}
</script>

Using Workspace Switcher

vue
<template>
  <LeftMenu />
</template>

<script>
import LeftMenu from '~/components/theme/global/LeftMenu.vue'

export default {
  components: {
    LeftMenu
  }
}
</script>

Using Workspace Middleware

javascript
// In page component
export default {
  middleware: [
    'checkWorkspace',
    'check-workspace-modules',
    'check-if-suspended'
  ],
  meta: {
    requiredModule: 'dam'
  }
}