feat(03-05): 3-step presigned upload + quota state in auth store + progress UI
- api/client.js: extend request() to attach .status and .payload on 413 structured errors; remove legacy uploadDocument multipart function
- stores/auth.js: add quota ref({used_bytes:0, limit_bytes:0}) and fetchQuota() action (silent catch); expose in store return
- stores/documents.js: replace single upload() with uploadToMinIO XHR helper + 3-step async action (getUploadUrl→XHR PUT→confirmUpload); track uploadProgress map keyed by filename+timestamp (T-03-25); call fetchQuota after upload success and document delete
- components/upload/UploadProgress.vue: add aria progressbar per row, percentage label, quota rejection error block (role=alert, red-50/red-200) from item.quotaError; use plain anchor for Manage storage link
This commit is contained in:
@@ -7,14 +7,64 @@
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-800 truncate">{{ item.name }}</p>
|
||||
<p v-if="item.error" class="text-xs text-red-500 mt-0.5">{{ item.error }}</p>
|
||||
<p v-else-if="item.done" class="text-xs text-green-600 mt-0.5">
|
||||
|
||||
<!-- Error state (non-quota) -->
|
||||
<p v-if="item.error" class="text-sm text-red-500 mt-0.5">{{ item.error }}</p>
|
||||
|
||||
<!-- Done state -->
|
||||
<p v-else-if="item.done" class="text-sm text-green-600 mt-0.5">
|
||||
Done{{ item.topics?.length ? ` — classified as: ${item.topics.join(', ')}` : ' — no topics assigned' }}
|
||||
</p>
|
||||
<p v-else class="text-xs text-gray-400 mt-0.5">Uploading…</p>
|
||||
|
||||
<!-- Quota rejection error block (413 response — T-03-23, UI-SPEC) -->
|
||||
<div
|
||||
v-else-if="item.quotaError"
|
||||
role="alert"
|
||||
class="mt-1 p-3 rounded-lg bg-red-50 border border-red-200"
|
||||
>
|
||||
<p class="text-sm font-semibold text-red-700">Not enough storage</p>
|
||||
<p class="text-sm text-red-600 mt-1">
|
||||
This file ({{ (item.quotaError.rejected_bytes / 1048576).toFixed(1) }} MB) would exceed your quota.
|
||||
</p>
|
||||
<p class="text-sm text-red-600">
|
||||
You're using {{ (item.quotaError.used_bytes / 1048576).toFixed(1) }} MB of {{ (item.quotaError.limit_bytes / 1048576).toFixed(1) }} MB.
|
||||
</p>
|
||||
<!-- Plain anchor to avoid router-link import dependency in upload component -->
|
||||
<a href="/settings" class="text-sm text-red-600 underline hover:text-red-700 font-semibold">
|
||||
Manage storage →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- In-progress: progress bar + percentage + status -->
|
||||
<template v-else>
|
||||
<!-- Progress bar track + fill (UI-SPEC Upload Progress Bar Contract) -->
|
||||
<div
|
||||
v-if="item.progress !== undefined"
|
||||
class="w-full h-2 bg-gray-100 rounded-full mt-1 overflow-hidden"
|
||||
>
|
||||
<div
|
||||
role="progressbar"
|
||||
:aria-valuenow="item.progress"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
:aria-label="`Upload progress for ${item.name}`"
|
||||
class="h-full rounded-full transition-all duration-300 bg-indigo-500"
|
||||
:style="{ width: `${item.progress}%` }"
|
||||
></div>
|
||||
</div>
|
||||
<!-- Percentage label (shown during upload, hidden after completion) -->
|
||||
<p
|
||||
v-if="item.progress !== undefined"
|
||||
class="text-sm text-gray-400 mt-1 text-right"
|
||||
>{{ item.progress }}%</p>
|
||||
<!-- Step status string (UI-SPEC Copywriting Contract) -->
|
||||
<p class="text-sm text-gray-400 mt-0.5">{{ item.status || 'Uploading…' }}</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Icon slot: error / done / spinner -->
|
||||
<div class="shrink-0">
|
||||
<svg v-if="item.error" class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg v-if="item.error || item.quotaError" class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<svg v-else-if="item.done" class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
|
||||
Reference in New Issue
Block a user