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:
curo1305
2026-06-01 19:32:51 +02:00
parent 52e54b859a
commit a0f6c2f663
4 changed files with 37 additions and 21 deletions
+12 -1
View File
@@ -542,9 +542,20 @@ 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") 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")
meta = await storage.get_metadata(session, doc_id) meta = await storage.get_metadata(session, doc_id)
if meta is None: if meta is None:
raise HTTPException(404, "Document not found") raise HTTPException(404, "Document not found")
+20 -18
View File
@@ -28,28 +28,30 @@
</h2> </h2>
<!-- Handle input row --> <!-- Handle input row -->
<div class="flex gap-2"> <div class="flex flex-col gap-2">
<input <div class="flex gap-2">
v-model="handle" <input
type="text" v-model="handle"
placeholder="Enter username handle" type="text"
class="flex-1 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter username handle"
@keydown.enter="submitShare" class="flex-1 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
/> @keydown.enter="submitShare"
<select />
v-model="permission" <select
aria-label="Permission level" v-model="permission"
class="border border-gray-300 rounded-lg px-3 py-2 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 shrink-0" aria-label="Permission level"
> class="border border-gray-300 rounded-lg px-3 py-2 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 shrink-0"
<option value="view">Can view</option> >
<option value="edit">Can edit</option> <option value="view">Can view</option>
</select> <option value="edit">Can edit</option>
</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>
+4 -1
View File
@@ -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) {
+1 -1
View File
@@ -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