Skip to content

Admin Upload & External Upload

Overview

This document covers two distinct upload systems in the Admin Frontend application:

  1. Admin Upload: Comprehensive file upload system for authenticated workspace users, supporting direct browser uploads, chunked/multipart uploads for large files, upload queue management, progress tracking, error handling, and retry logic.

  2. External Upload Integration: Guest user upload system with OTP authentication, allowing external users to upload files to workspaces with limited access and controlled permissions.

Both systems handle file validation, progress tracking, error handling, and automatic thumbnail generation.

Architecture

Admin Upload Methods

  1. Direct Upload: Small files uploaded directly via FormData
  2. Chunked/Multipart Upload: Large files split into chunks and uploaded in parts to S3
  3. Bulk Upload: Multiple files uploaded simultaneously with queue management
  4. Version Upload: Upload new versions of existing assets

External Upload Methods

  1. External Upload: Guest user uploads with OTP authentication
  2. Chunked External Upload: Large files for guest users using chunked upload

File Structure

Admin Upload Files

JavaScript Files (.js)

  • mixins/upload-common.js - Common upload functionality mixin

    • File validation
    • Upload initialization
    • Progress tracking helpers
    • Error handling utilities
  • mixins/uploadQueue.js - Upload queue management

    • Queue operations (add, remove, pause, resume)
    • Concurrent upload limits
    • Queue state management
    • Retry logic
  • api/s3.js - S3 upload API integration

    • Multipart upload initiation
    • Presigned URL generation
    • Upload completion
    • Direct upload endpoints
  • api/index.js - General API (may contain upload endpoints)

    • Asset creation endpoints
    • Upload status endpoints
  • utils/index.js - Utility functions (may contain upload helpers)

    • File type detection
    • File size formatting
    • Upload helpers
  • store/dam.js - DAM store (upload state management)

    • Upload queue state
    • Upload progress state
    • Upload history

Vue Component Files (.vue)

  • components/dam/AssetUpload.vue - Main asset upload component

    • File selection interface
    • Drag and drop support
    • Upload queue display
    • Progress indicators
  • components/dam/upload-chunk.vue - Chunked upload component

    • File chunking logic
    • Chunk upload coordination
    • Progress tracking per chunk
    • Error recovery
  • components/dam/Uploaded/UploadedListItem.vue - Uploaded item list component

    • Display uploaded files
    • Upload status indicators
    • Action buttons (retry, cancel, remove)
  • components/dam/Dialogs/miniUploadBackdrop.vue - Mini upload backdrop dialog

    • Modal backdrop for upload dialog
    • Overlay styling
  • components/dam/Dialogs/miniUploadDialog.vue - Mini upload dialog

    • Compact upload interface
    • Quick file selection
    • Minimal progress display
  • components/dam/Dialogs/VersionUploadBackdrop.vue - Version upload backdrop

    • Modal for version uploads
    • Version-specific upload UI
  • components/dam/SkeletonLoaders/UploadedTableListSkeleton.vue - Upload skeleton loader

    • Loading state for upload list
    • Skeleton UI during data fetch
  • pages/_workspace_id/dam/upload.vue - Upload page

    • Main upload interface
    • File selection
    • Upload queue management
    • Upload history
  • pages/_workspace_id/dam/uploaded.vue - Uploaded assets page

    • List of uploaded assets
    • Upload status tracking
    • Asset management
  • components/svg/CollageUploadIcon.vue - Standard upload icon
  • components/svg/CollageUploadMediumIcon.vue - Medium size upload icon
  • components/svg/CollageUploadLargeIcon.vue - Large size upload icon
  • components/svg/CollageUploadExtraLargeIcon.vue - Extra large upload icon
  • components/svg/CollageUploadCloudIcon.vue - Cloud upload icon

External Upload Files

JavaScript Files (.js)

  • mixins/external-uploads.js - External upload functionality mixin

    • Guest user upload handling
    • OTP verification integration
    • External upload queue management
    • Guest-specific validation
  • middleware/external-upload-auth.js - External upload authentication middleware

    • Guest user authentication
    • Token validation
    • Access control
  • middleware/external-guest-redirect.js - External guest redirect middleware

    • Redirect logic for guest users
    • Access verification
    • Route protection
  • middleware/external-otp-verify.js - External OTP verification middleware

    • OTP code verification
    • Session management
    • Access grant

Vue Component Files (.vue)

  • components/dam/upload-chunk-external.vue - External chunked upload component

    • Guest user chunked uploads
    • OTP-verified upload flow
    • External upload progress
  • pages/_workspace_id/external/upload.vue - External upload page

    • Guest upload interface
    • OTP input
    • External upload queue
  • pages/_workspace_id/external/verify.vue - External verification page

    • OTP verification interface
    • Access request handling
    • Verification status
  • pages/_workspace_id/external/request-access.vue - External request access page

    • Access request form
    • Email/contact input
    • Request submission
  • pages/_workspace_id/external/request-submitted.vue - External request submitted page

    • Confirmation page
    • Next steps information
    • Status display

Additional Files

  • components/dam/AssetList/Versions.vue - May handle version uploads
    • Version upload interface
    • Version management
    • Version history

Admin Upload System

Upload Common Mixin

File: mixins/upload-common.js

This mixin provides common upload functionality shared across upload components.

Key Methods

javascript
// File validation
validateFile(file) {
  // Check file size
  // Check file type
  // Return validation result
}

// Initialize upload
initUpload(file, options) {
  // Prepare file for upload
  // Set upload metadata
  // Return upload object
}

// Track progress
updateProgress(uploadId, progress) {
  // Update upload progress
  // Emit progress events
}

// Handle errors
handleUploadError(uploadId, error) {
  // Log error
  // Update upload status
  // Trigger retry if applicable
}

Usage

vue
<script>
import uploadCommon from '~/mixins/upload-common'

export default {
  mixins: [uploadCommon],
  methods: {
    async handleFileSelect(files) {
      for (const file of files) {
        const validation = this.validateFile(file)
        if (validation.valid) {
          const upload = this.initUpload(file, {
            workspaceId: this.$route.params.workspace_id,
            folderId: this.currentFolderId
          })
          this.addToQueue(upload)
        } else {
          this.showError(validation.message)
        }
      }
    }
  }
}
</script>

Upload Queue Mixin

File: mixins/uploadQueue.js

Manages the upload queue with concurrent upload limits, retry logic, and state management.

Key Methods

javascript
// Add file to queue
addToQueue(upload) {
  // Add to queue array
  // Trigger upload if under limit
}

// Remove from queue
removeFromQueue(uploadId) {
  // Remove from queue
  // Cancel active upload if in progress
}

// Pause upload
pauseUpload(uploadId) {
  // Pause upload
  // Save current state
}

// Resume upload
resumeUpload(uploadId) {
  // Resume from saved state
  // Continue upload
}

// Process queue
processQueue() {
  // Check concurrent limit
  // Start next uploads
  // Manage queue state
}

Queue State

javascript
data() {
  return {
    uploadQueue: [],           // Array of upload objects
    activeUploads: [],          // Currently uploading files
    maxConcurrent: 3,          // Max concurrent uploads
    pausedUploads: [],         // Paused uploads
    failedUploads: []          // Failed uploads (for retry)
  }
}

Usage

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

export default {
  mixins: [uploadQueue],
  methods: {
    handleFiles(files) {
      files.forEach(file => {
        const upload = {
          id: this.generateId(),
          file: file,
          status: 'pending',
          progress: 0,
          error: null
        }
        this.addToQueue(upload)
      })
    }
  }
}
</script>

Asset Upload Component

File: components/dam/AssetUpload.vue

Main upload component providing file selection, drag-and-drop, and upload queue display.

Features

  • Drag and drop file selection
  • Multiple file selection
  • File validation
  • Upload queue display
  • Progress tracking
  • Error handling
  • Retry functionality

Props

javascript
{
  workspaceId: {
    type: String,
    required: true
  },
  folderId: {
    type: String,
    default: null
  },
  maxFileSize: {
    type: Number,
    default: 1073741824 // 1GB
  },
  allowedTypes: {
    type: Array,
    default: () => []
  },
  maxConcurrent: {
    type: Number,
    default: 3
  }
}

Events

javascript
{
  'upload-complete': (asset) => {},      // Upload completed
  'upload-error': (error) => {},         // Upload error
  'upload-progress': (progress) => {},   // Progress update
  'queue-updated': (queue) => {}         // Queue state changed
}

Usage

vue
<template>
  <AssetUpload
    :workspace-id="workspaceId"
    :folder-id="currentFolderId"
    :max-file-size="maxFileSize"
    :allowed-types="allowedFileTypes"
    @upload-complete="handleUploadComplete"
    @upload-error="handleUploadError"
  />
</template>

Chunked Upload Component

File: components/dam/upload-chunk.vue

Handles chunked/multipart uploads for large files.

Chunking Logic

javascript
// Split file into chunks
createChunks(file, chunkSize = 5 * 1024 * 1024) {
  const chunks = []
  let offset = 0
  
  while (offset < file.size) {
    const chunk = file.slice(offset, offset + chunkSize)
    chunks.push({
      data: chunk,
      partNumber: chunks.length + 1,
      offset: offset,
      size: chunk.size
    })
    offset += chunkSize
  }
  
  return chunks
}

Upload Flow

javascript
async uploadChunked(file) {
  // 1. Start multipart upload
  const { uploadId, assetId } = await this.$axios.get('/s3/start-upload', {
    params: {
      workspaceId: this.workspaceId,
      fileType: file.type
    }
  })
  
  // 2. Create chunks
  const chunks = this.createChunks(file)
  const parts = []
  
  // 3. Upload each chunk
  for (const chunk of chunks) {
    // Get presigned URL
    const { url } = await this.$axios.get('/s3/get-upload-url', {
      params: {
        PartNumber: chunk.partNumber,
        UploadId: uploadId,
        assetId: assetId,
        workspaceId: this.workspaceId
      }
    })
    
    // Upload chunk to S3
    const etag = await this.uploadChunkToS3(url, chunk.data)
    parts.push({
      ETag: etag,
      PartNumber: chunk.partNumber
    })
    
    // Update progress
    this.updateProgress((chunk.partNumber / chunks.length) * 100)
  }
  
  // 4. Complete upload
  await this.$axios.post('/s3/complete-upload', {
    uploadId,
    assetId,
    workspaceId: this.workspaceId,
    parts
  })
}

Upload Pages

Upload Page

File: pages/_workspace_id/dam/upload.vue

Main upload interface page.

Route: /:workspace_id/dam/upload

Features:

  • File selection interface
  • Upload queue management
  • Progress monitoring
  • Upload history
  • Error handling

Uploaded Assets Page

File: pages/_workspace_id/dam/uploaded.vue

Displays list of uploaded assets.

Route: /:workspace_id/dam/uploaded

Features:

  • List of uploaded assets
  • Upload status tracking
  • Asset actions (view, download, delete)
  • Filtering and sorting

External Upload System

External Uploads Mixin

File: mixins/external-uploads.js

Handles external/guest user uploads with OTP authentication.

Key Methods

javascript
// Verify OTP
async verifyOTP(otpCode) {
  // Verify OTP with backend
  // Store verification token
  // Grant upload access
}

// External upload
async externalUpload(file, otpToken) {
  // Validate guest access
  // Initialize external upload
  // Use external upload queue
}

// Request access
async requestAccess(email, message) {
  // Submit access request
  // Send notification
  // Return request ID
}

External Upload Middleware

External Upload Auth

File: middleware/external-upload-auth.js

Validates guest user authentication for external uploads.

javascript
export default function ({ route, store, redirect }) {
  // Check for guest token
  // Validate token
  // Redirect if invalid
  // Allow access if valid
}

External Guest Redirect

File: middleware/external-guest-redirect.js

Handles redirects for guest users.

javascript
export default function ({ route, store, redirect }) {
  // Check if guest user
  // Redirect to appropriate page
  // Handle access requests
}

External OTP Verify

File: middleware/external-otp-verify.js

Verifies OTP codes for external uploads.

javascript
export default async function ({ route, store, redirect, $axios }) {
  // Get OTP from route/query
  // Verify OTP with backend
  // Store verification status
  // Redirect based on result
}

External Upload Component

File: components/dam/upload-chunk-external.vue

Chunked upload component specifically for external/guest users.

Features:

  • OTP-verified uploads
  • Guest user file handling
  • External upload queue
  • Limited access controls

External Upload Pages

External Upload Page

File: pages/_workspace_id/external/upload.vue

Route: /:workspace_id/external/upload

Features:

  • Guest upload interface
  • OTP input/verification
  • External upload queue
  • Limited functionality

External Verify Page

File: pages/_workspace_id/external/verify.vue

Route: /:workspace_id/external/verify

Features:

  • OTP verification interface
  • Access request handling
  • Verification status display

External Request Access Page

File: pages/_workspace_id/external/request-access.vue

Route: /:workspace_id/external/request-access

Features:

  • Access request form
  • Email/contact input
  • Request submission
  • Validation

External Request Submitted Page

File: pages/_workspace_id/external/request-submitted.vue

Route: /:workspace_id/external/request-submitted

Features:

  • Confirmation message
  • Next steps information
  • Status display

Upload Queue System

Queue Management

The upload queue system manages multiple file uploads with the following features:

  • Concurrent Upload Limits: Maximum number of simultaneous uploads
  • Queue Processing: Automatic queue processing when slots available
  • Priority Management: Priority-based queue ordering
  • State Persistence: Queue state saved across page reloads

Queue States

javascript
{
  pending: 'pending',      // Waiting in queue
  uploading: 'uploading',  // Currently uploading
  paused: 'paused',        // Paused by user
  completed: 'completed',  // Upload completed
  error: 'error',          // Upload failed
  cancelled: 'cancelled'  // Cancelled by user
}

Progress Tracking

javascript
{
  uploadId: 'unique-id',
  fileName: 'example.jpg',
  fileSize: 5242880,
  uploaded: 2621440,
  percentage: 50,
  speed: 1024000,        // bytes per second
  timeRemaining: 2.5,    // seconds
  status: 'uploading'
}

Error Handling and Retry

javascript
// Automatic retry logic
async retryUpload(uploadId) {
  const upload = this.getUpload(uploadId)
  
  // Reset upload state
  upload.status = 'pending'
  upload.error = null
  upload.retryCount = (upload.retryCount || 0) + 1
  
  // Check retry limit
  if (upload.retryCount <= this.maxRetries) {
    // Re-add to queue
    this.addToQueue(upload)
  } else {
    // Mark as failed
    upload.status = 'error'
    this.handleFinalFailure(upload)
  }
}

Chunked Upload Implementation

Chunk Creation

Files are split into chunks based on size threshold (typically 5MB per chunk).

javascript
const CHUNK_SIZE = 5 * 1024 * 1024 // 5MB

function createChunks(file) {
  const chunks = []
  let start = 0
  
  while (start < file.size) {
    const end = Math.min(start + CHUNK_SIZE, file.size)
    chunks.push({
      blob: file.slice(start, end),
      partNumber: chunks.length + 1,
      start: start,
      end: end,
      size: end - start
    })
    start = end
  }
  
  return chunks
}

Multipart Upload Flow

1. Start Multipart Upload

   GET /s3/start-upload
   Returns: { uploadId, assetId }

2. For Each Chunk:

   a. Get Presigned URL
      GET /s3/get-upload-url
      Returns: { url, expiresIn }

   b. Upload Chunk to S3
      PUT {url} (direct to S3)
      Returns: ETag header

   c. Store Part Info
      { ETag, PartNumber }

3. Complete Multipart Upload

   POST /s3/complete-upload
   Body: { uploadId, assetId, parts: [{ETag, PartNumber}] }

4. Asset Created

   Backend creates asset record
   Generates thumbnail (async)
   Indexes in Typesense (async)

Progress Tracking Per Chunk

javascript
// Track progress for each chunk
updateChunkProgress(uploadId, chunkIndex, progress) {
  const upload = this.getUpload(uploadId)
  const totalChunks = upload.chunks.length
  const chunkProgress = progress / totalChunks
  const completedChunks = upload.completedChunks || 0
  
  upload.progress = (completedChunks / totalChunks) * 100 + chunkProgress
  
  this.emit('progress', {
    uploadId,
    progress: upload.progress,
    chunkIndex,
    chunkProgress
  })
}

Error Recovery

javascript
// Handle chunk upload failure
async handleChunkError(uploadId, chunkIndex, error) {
  const upload = this.getUpload(uploadId)
  const chunk = upload.chunks[chunkIndex]
  
  // Retry chunk upload
  if (chunk.retryCount < this.maxChunkRetries) {
    chunk.retryCount++
    await this.uploadChunk(uploadId, chunkIndex)
  } else {
    // Abort multipart upload
    await this.abortMultipartUpload(upload.uploadId)
    upload.status = 'error'
    upload.error = error
  }
}

API Integration

S3 Upload API

File: api/s3.js

See S3 API Documentation for complete API reference.

Key Endpoints

  • GET /s3/start-upload - Initiate multipart upload
  • GET /s3/get-upload-url - Get presigned URL for chunk
  • POST /s3/complete-upload - Complete multipart upload
  • POST /api/assets/upload - Direct upload endpoint

Upload Endpoints

Direct Upload

javascript
POST /api/assets/upload
Content-Type: multipart/form-data

FormData:
- file: File object
- workspace_id: string
- folder_id: string (optional)
- metadata: object (optional)

Multipart Upload

See AWS S3 Integration for complete multipart upload flow.

Workflows

Admin Upload Workflow

1. User navigates to upload page
   Route: /:workspace_id/dam/upload
   Component: pages/_workspace_id/dam/upload.vue

2. User selects file(s)
   Component: AssetUpload.vue
   - Drag and drop OR file input
   - Multiple file selection supported

3. File validation
   Mixin: upload-common.js
   - Check file size (maxFileSize limit)
   - Check file type (allowedTypes)
   - Validate permissions (workspace/folder)
   - Show error if validation fails

4. Initialize upload objects
   Mixin: upload-common.js
   - Create upload object for each file
   - Set metadata (workspaceId, folderId, etc.)
   - Generate unique upload ID

5. Add to upload queue
   Mixin: uploadQueue.js
   - Add upload to queue array
   - Set status: 'pending'
   - Display in UploadedListItem component

6. Queue processing
   Mixin: uploadQueue.js
   - Check concurrent upload limit (maxConcurrent)
   - If slot available → Start upload
   - If queue full → Wait for slot

7. Upload decision
   Component: upload-chunk.vue (for large files)
   - If file < threshold (e.g., 10MB) → Direct upload
     POST /api/assets/upload (FormData)
   - If file >= threshold → Chunked upload
     Use multipart upload flow

8. Upload execution
   For Direct Upload:
   - POST /api/assets/upload
   - Track progress via onUploadProgress
   - Update progress in queue

   For Chunked Upload:
   - Start multipart upload
   - Upload chunks sequentially/parallel
   - Track progress per chunk
   - Handle chunk errors

9. Error handling
   - If error occurs → Retry logic
   - Update status: 'error'
   - Show error message
   - Allow manual retry

10. Upload completion
    - Backend creates asset record
    - Generate thumbnail (async background job)
    - Index in Typesense (async background job)
    - Return asset data to frontend

11. Update UI
    Component: UploadedListItem.vue
    - Update status: 'completed'
    - Remove from active queue
    - Show success message/notification
    - Emit 'upload-complete' event

12. Refresh asset list
    - Update DAM store
    - Refresh asset list if on uploaded page
    - Navigate to asset details (optional)

Version Upload Workflow

1. User navigates to asset details
   Route: /:workspace_id/dam/assets/:asset_id

2. User clicks "Upload New Version"
   Component: Versions.vue
   Dialog: VersionUploadBackdrop.vue

3. File selection
   - Select new version file
   - Validate file (same or compatible type)

4. Version upload
   - Use same upload flow as admin upload
   - Associate with existing asset
   - Version number auto-incremented

5. Version creation
   - Create version record
   - Link to parent asset
   - Store version metadata

6. Update asset
   - Set new version as current (optional)
   - Update asset preview
   - Show version history

External Upload Workflow

1. Guest user accesses external upload page
   Route: /:workspace_id/external/upload

2. Access verification
   Middleware: external-upload-auth.js
   - Check if access granted
   - If no access → Redirect to request-access

3. OTP verification (if required)
   Route: /:workspace_id/external/verify
   Middleware: external-otp-verify.js
   - Enter OTP code
   - Verify with backend
   - Store verification token in session

4. File selection
   Component: upload-chunk-external.vue
   - Select file(s)
   - Validate file (size, type)
   - Use external-uploads.js mixin

5. Add to external upload queue
   - Limited concurrent uploads (guest restrictions)
   - Queue management via external-uploads mixin

6. Upload execution
   - Use external upload queue
   - Chunked upload for large files
   - Track progress per chunk
   - Handle errors with retry

7. Upload completion
   - Create asset record
   - Notify workspace owner (email/notification)
   - Show confirmation message
   - Redirect to success page

External Access Request Workflow

1. Guest user requests access
   Route: /:workspace_id/external/request-access

2. Fill access request form
   - Enter email/contact information
   - Optional message
   - Submit request

3. Backend processes request
   - Store request in database
   - Send notification to workspace owner
   - Generate OTP (if auto-approve)

4. Redirect to request-submitted page
   Route: /:workspace_id/external/request-submitted
   - Show confirmation message
   - Display next steps
   - Provide OTP (if applicable)

5. Workspace owner approves (manual)
   - Owner receives notification
   - Owner approves/rejects request
   - OTP sent to guest user

6. Guest user verifies OTP
   Route: /:workspace_id/external/verify
   - Enter OTP code
   - Verify and grant access
   - Redirect to upload page

Chunked Upload Workflow

1. File selected (large file >= threshold)
   Component: upload-chunk.vue
   Mixin: upload-common.js

2. Start multipart upload
   API: GET /s3/start-upload
   Params: { workspaceId, fileType }
   Response: { uploadId, assetId }
   Store: uploadId and assetId in upload object

3. Create chunks
   Component: upload-chunk.vue
   - Calculate chunk size (default: 5MB)
   - Split file using File.slice()
   - Create chunk objects:
     {
       blob: File slice,
       partNumber: 1, 2, 3...,
       start: byte offset,
       end: byte offset,
       size: chunk size
     }
   - Store chunks array in upload object

4. For each chunk (sequential or parallel):

   a. Get presigned URL
      API: GET /s3/get-upload-url
      Params: {
        PartNumber: chunk.partNumber,
        UploadId: uploadId,
        assetId: assetId,
        workspaceId: workspaceId
      }
      Response: { url, expiresIn }

   b. Upload chunk to S3
      Method: PUT (direct to S3, not through backend)
      URL: presigned URL from step a
      Body: chunk blob
      Headers: Content-Type from file
      Response: ETag in response header

   c. Store part info
      parts.push({
        ETag: etag,
        PartNumber: chunk.partNumber
      })
      Update: completedChunks count

   d. Update progress
      Calculate: (completedChunks / totalChunks) * 100
      Emit: progress event
      Update: UploadedListItem component

5. Handle chunk errors
   - If chunk upload fails:
     - Retry chunk (up to maxChunkRetries)
     - If retries exhausted → Abort multipart upload
     - Update status: 'error'

6. Complete multipart upload
   API: POST /s3/complete-upload
   Body: {
     uploadId: uploadId,
     assetId: assetId,
     workspaceId: workspaceId,
     parts: [
       { ETag: '...', PartNumber: 1 },
       { ETag: '...', PartNumber: 2 },
       ...
     ]
   }
   Response: { success: true, location, key }

7. Backend processing
   - Merge all parts in S3
   - Create asset record in database
   - Store S3 key and metadata
   - Trigger background jobs:
     * Generate thumbnail (async)
     * Index in Typesense (async)
     * Extract metadata (async)

8. Upload completion
   - Backend returns asset data
   - Frontend updates status: 'completed'
   - Remove from upload queue
   - Emit 'upload-complete' event
   - Show success notification

9. UI update
   - Refresh asset list
   - Navigate to asset details (optional)
   - Update DAM store

Component Integration

Using Upload Components

vue
<template>
  <div>
    <!-- Main upload component -->
    <AssetUpload
      :workspace-id="workspaceId"
      :folder-id="currentFolderId"
      @upload-complete="handleUploadComplete"
    />
    
    <!-- Upload queue display -->
    <UploadedListItem
      v-for="upload in uploadQueue"
      :key="upload.id"
      :upload="upload"
      @retry="retryUpload"
      @cancel="cancelUpload"
    />
  </div>
</template>

<script>
import AssetUpload from '~/components/dam/AssetUpload.vue'
import UploadedListItem from '~/components/dam/Uploaded/UploadedListItem.vue'
import uploadQueue from '~/mixins/uploadQueue'

export default {
  components: {
    AssetUpload,
    UploadedListItem
  },
  mixins: [uploadQueue],
  data() {
    return {
      workspaceId: this.$route.params.workspace_id,
      currentFolderId: null
    }
  },
  methods: {
    handleUploadComplete(asset) {
      // Handle completed upload
      this.$store.dispatch('dam/addAsset', asset)
      this.$router.push(`/${this.workspaceId}/dam/assets/${asset.id}`)
    }
  }
}
</script>