Appearance
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
- Workspace Switching: Switch between available workspaces
- Workspace Management: Create, manage, and configure workspaces
- Workspace Access Control: Middleware for workspace validation and access checks
- Workspace Context: Current workspace state management
- Workspace Settings: Workspace-specific configuration pages
- 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
changeWorkspacemethod for switching workspaces- Display current workspace
- List available workspaces
components/theme/settings/AccountSettingsLeftMenu.vue- Account settings menu- Contains workspace selector
updateRouteWpmethod 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
workspaceIdcomputed property - Current workspace state
- Workspace context helpers
- Provides
mixins/routeParams.js- Route params mixin- Workspace parameter handling
- Route parameter utilities
- Workspace ID extraction
plugins/helper.js- Helper functions pluginsetCurrentWorkspace- Set current workspacegetWorkspaceId- Get workspace ID from route/store_auth- Authentication helpersallowedWorkspaces- Get user's allowed workspacesgetUserModulesAndRoles- Get user modules and roles for workspace
Workspace Display & Integration
Vue Component Files (.vue)
components/theme/global/CoreHeader.vue- Core header component- Uses
currentWorkspacemixin - Displays current workspace
- Workspace context
- Uses
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 mixinonSwitchWorkspacemethod- 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 contextWorkspace 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 availableCreate 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 dataComponent 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'
}
}Related Documentation
- Management Modules - Module access control
- Role-Based Access Matrix - Permission system
- Notifications System - Workspace notifications