fix(06.2): resolve four sharing UX issues found in re-test UAT
- AccountView: remove hardcoded @ prefix so handle matches what share dialog expects
- documents store: set is_shared=true optimistically after successful share so badge shows without refetch
- GET /api/documents/{id}: allow recipients of an active share to view the document (was returning 404 for non-owners)
- ShareModal: move Share button to its own full-width row so it no longer overflows the input area
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -542,7 +542,18 @@ async def get_document(
|
|||||||
raise HTTPException(404, "Document not found")
|
raise HTTPException(404, "Document not found")
|
||||||
|
|
||||||
doc = await session.get(Document, uid)
|
doc = await session.get(Document, uid)
|
||||||
if doc is None or doc.user_id != current_user.id:
|
if doc is None:
|
||||||
|
raise HTTPException(404, "Document not found")
|
||||||
|
|
||||||
|
if doc.user_id != current_user.id:
|
||||||
|
# Allow recipients of an active share to view the document
|
||||||
|
share_result = await session.execute(
|
||||||
|
select(Share).where(
|
||||||
|
Share.document_id == uid,
|
||||||
|
Share.recipient_id == current_user.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if share_result.scalar_one_or_none() is None:
|
||||||
raise HTTPException(404, "Document not found")
|
raise HTTPException(404, "Document not found")
|
||||||
|
|
||||||
meta = await storage.get_metadata(session, doc_id)
|
meta = await storage.get_metadata(session, doc_id)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<!-- Handle input row -->
|
<!-- Handle input row -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input
|
<input
|
||||||
v-model="handle"
|
v-model="handle"
|
||||||
@@ -44,12 +45,13 @@
|
|||||||
<option value="view">Can view</option>
|
<option value="view">Can view</option>
|
||||||
<option value="edit">Can edit</option>
|
<option value="edit">Can edit</option>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
@click="submitShare"
|
@click="submitShare"
|
||||||
:disabled="submitting || !handle.trim()"
|
:disabled="submitting || !handle.trim()"
|
||||||
class="bg-indigo-600 hover:bg-indigo-700 text-white text-sm px-4 py-2 rounded-lg disabled:opacity-50 transition-colors shrink-0"
|
class="w-full bg-indigo-600 hover:bg-indigo-700 text-white text-sm px-4 py-2 rounded-lg disabled:opacity-50 transition-colors"
|
||||||
>
|
>
|
||||||
<span v-if="submitting" class="flex items-center gap-1.5">
|
<span v-if="submitting" class="flex items-center justify-center gap-1.5">
|
||||||
<span class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
|
<span class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
|
||||||
Sharing…
|
Sharing…
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -157,7 +157,10 @@ export const useDocumentsStore = defineStore('documents', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function shareDocument(docId, recipientHandle, permission = 'view') {
|
async function shareDocument(docId, recipientHandle, permission = 'view') {
|
||||||
try { return await api.createShare(docId, recipientHandle, permission) } catch (e) { throw e }
|
const result = await api.createShare(docId, recipientHandle, permission)
|
||||||
|
const doc = documents.value.find(d => d.id === docId)
|
||||||
|
if (doc) doc.is_shared = true
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async function revokeShare(shareId) {
|
async function revokeShare(shareId) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<h3 class="font-semibold text-gray-800 mb-4">Account information</h3>
|
<h3 class="font-semibold text-gray-800 mb-4">Account information</h3>
|
||||||
<div class="space-y-2 text-sm text-gray-700">
|
<div class="space-y-2 text-sm text-gray-700">
|
||||||
<div><span class="text-gray-500">Email:</span> {{ authStore.user?.email }}</div>
|
<div><span class="text-gray-500">Email:</span> {{ authStore.user?.email }}</div>
|
||||||
<div><span class="text-gray-500">Username:</span> @{{ authStore.user?.handle }}</div>
|
<div><span class="text-gray-500">Username:</span> {{ authStore.user?.handle }}</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-gray-500">Role:</span>
|
<span class="text-gray-500">Role:</span>
|
||||||
<span
|
<span
|
||||||
|
|||||||
Reference in New Issue
Block a user