diff --git a/backend/server/adventures/views/collection_view.py b/backend/server/adventures/views/collection_view.py index 39e8bcc8..f8909e68 100644 --- a/backend/server/adventures/views/collection_view.py +++ b/backend/server/adventures/views/collection_view.py @@ -93,7 +93,7 @@ class CollectionViewSet(viewsets.ModelViewSet): if self.action == 'destroy': return Collection.objects.filter(user=self.request.user.id) - if self.action in ['update', 'partial_update']: + if self.action in ['update', 'partial_update', 'leave']: return Collection.objects.filter( Q(user=self.request.user.id) | Q(shared_with=self.request.user) ).distinct() @@ -115,9 +115,9 @@ class CollectionViewSet(viewsets.ModelViewSet): Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user) ).distinct() - # For list action, include collections owned by the user or shared with the user, that are not archived + # For list action and default base queryset, return collections owned by the user (exclude shared) return Collection.objects.filter( - (Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False) + Q(user=self.request.user.id) & Q(is_archived=False) ).distinct() def get_queryset(self): @@ -131,8 +131,10 @@ class CollectionViewSet(viewsets.ModelViewSet): if not request.user.is_authenticated: return Response({"error": "User is not authenticated"}, status=400) + # List should only return collections owned by the requesting user (shared collections are available + # via the `shared` action). queryset = Collection.objects.filter( - (Q(user=request.user.id) | Q(shared_with=request.user)) & Q(is_archived=False) + Q(user=request.user.id) & Q(is_archived=False) ).distinct().select_related('user').prefetch_related( Prefetch( 'locations__images', diff --git a/frontend/src/routes/collections/+page.svelte b/frontend/src/routes/collections/+page.svelte index 609fa307..48b8cdae 100644 --- a/frontend/src/routes/collections/+page.svelte +++ b/frontend/src/routes/collections/+page.svelte @@ -210,11 +210,37 @@ }); if (res.ok) { + // Try to parse returned collection data + let data: any = null; + try { + data = await res.json(); + } catch (e) { + data = null; + } + // Remove invite from list invites = invites.filter((i) => i.id !== invite.id); addToast('success', `${$t('invites.accepted')} "${invite.name}"`); - // Optionally refresh shared collections - await goto(window.location.pathname, { invalidateAll: true }); + + // If API returned the accepted collection, add it to sharedCollections immediately + if (data && (data.collection || data.result || data.id)) { + // Normalize expected shapes: {collection: {...}} or collection object directly + const newCollection = data.collection ? data.collection : data; + // Prepend so it's visible at top + sharedCollections = [newCollection as SlimCollection, ...sharedCollections]; + } else { + // Fallback: refresh shared collections from API + try { + const sharedRes = await fetch(`/api/collections/shared/?nested=true`); + if (sharedRes.ok) { + const sharedData = await sharedRes.json(); + // Prefer results if paginated + sharedCollections = sharedData.results ? sharedData.results : sharedData; + } + } catch (e) { + // ignore fallback errors; user already got success toast + } + } } else { const error = await res.json(); addToast('error', error.error || $t('invites.accept_failed'));