feat(06.2-02): frontend — is_shared badge fix + permission dropdown + View/Edit toggle
- DocumentCard.vue: fix Shared pill to read doc.is_shared (was doc.share_count > 0) - ShareModal.vue: add permission select between handle input and submit button - ShareModal.vue: replace static "view" span with View/Edit toggle group per share row - ShareModal.vue: add handlePermissionChange with optimistic update + rollback on error - documents.js: update shareDocument(docId, handle, permission='view') signature - documents.js: add updateSharePermission(shareId, permission) action - api/client.js: pass permission in createShare POST body - api/client.js: add updateSharePermission PATCH helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,14 @@
|
||||
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"
|
||||
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>
|
||||
</select>
|
||||
<button
|
||||
@click="submitShare"
|
||||
:disabled="submitting || !handle.trim()"
|
||||
@@ -51,6 +59,7 @@
|
||||
|
||||
<!-- Error -->
|
||||
<p v-if="error" class="text-xs text-red-600 mt-2">{{ error }}</p>
|
||||
<p v-if="permissionError" class="text-xs text-red-600 mt-2">{{ permissionError }}</p>
|
||||
|
||||
<!-- Separator -->
|
||||
<div class="border-t border-gray-100 my-4"></div>
|
||||
@@ -72,7 +81,24 @@
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-900">{{ share.recipient_handle }}</span>
|
||||
<span class="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded-full font-medium">view</span>
|
||||
<div
|
||||
role="group"
|
||||
:aria-label="`Permission for ${share.recipient_handle}`"
|
||||
class="flex"
|
||||
:class="{ 'opacity-50 pointer-events-none': updatingPermission.has(share.id) }"
|
||||
>
|
||||
<button
|
||||
v-for="level in ['view', 'edit']"
|
||||
:key="level"
|
||||
:aria-pressed="share.permission === level"
|
||||
:aria-label="`Change permission for ${share.recipient_handle} to ${level}`"
|
||||
class="text-xs px-2 py-1 rounded-full font-medium transition-colors first:rounded-r-none last:rounded-l-none"
|
||||
:class="share.permission === level
|
||||
? 'bg-indigo-50 text-indigo-600 font-medium'
|
||||
: 'bg-gray-100 text-gray-600'"
|
||||
@click="share.permission !== level && handlePermissionChange(share.id, level)"
|
||||
>{{ level === 'view' ? 'View' : 'Edit' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="handleRevoke(share.id)"
|
||||
@@ -102,10 +128,13 @@ const emit = defineEmits(['close'])
|
||||
const docsStore = useDocumentsStore()
|
||||
|
||||
const handle = ref('')
|
||||
const permission = ref('view')
|
||||
const submitting = ref(false)
|
||||
const error = ref(null)
|
||||
const permissionError = ref(null)
|
||||
const shares = ref([])
|
||||
const loadingShares = ref(false)
|
||||
const updatingPermission = ref(new Set())
|
||||
|
||||
onMounted(async () => {
|
||||
loadingShares.value = true
|
||||
@@ -127,9 +156,10 @@ async function submitShare() {
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const newShare = await docsStore.shareDocument(props.doc.id, trimmed)
|
||||
const newShare = await docsStore.shareDocument(props.doc.id, trimmed, permission.value)
|
||||
shares.value.push(newShare)
|
||||
handle.value = ''
|
||||
permission.value = 'view'
|
||||
} catch (e) {
|
||||
if (e.status === 404) {
|
||||
error.value = 'User not found. Check the handle and try again.'
|
||||
@@ -143,6 +173,25 @@ async function submitShare() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePermissionChange(shareId, newPermission) {
|
||||
const share = shares.value.find(s => s.id === shareId)
|
||||
if (!share) return
|
||||
const oldPermission = share.permission
|
||||
share.permission = newPermission
|
||||
updatingPermission.value = new Set([...updatingPermission.value, shareId])
|
||||
permissionError.value = null
|
||||
try {
|
||||
await docsStore.updateSharePermission(shareId, newPermission)
|
||||
} catch (e) {
|
||||
share.permission = oldPermission
|
||||
permissionError.value = 'Failed to update permission.'
|
||||
} finally {
|
||||
const next = new Set(updatingPermission.value)
|
||||
next.delete(shareId)
|
||||
updatingPermission.value = next
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRevoke(shareId) {
|
||||
// Optimistic removal
|
||||
const removedIdx = shares.value.findIndex(s => s.id === shareId)
|
||||
|
||||
Reference in New Issue
Block a user