less than a minute ago
· Group 30
· Place 2
diff --git a/src/authuser/datastore/auth.spec.js b/src/authuser/datastore/auth.spec.js
index 1e8b8e568..81964d6aa 100644
--- a/src/authuser/datastore/auth.spec.js
+++ b/src/authuser/datastore/auth.spec.js
@@ -1,11 +1,10 @@
-const routerMocks = require('>/routerMocks').default
const mockStatus = jest.fn()
const mockLogin = jest.fn()
-const mockRouterPush = jest.fn(routerMocks.$router.push)
-jest.mock('@/router', () => ({ push: mockRouterPush }))
jest.mock('@/authuser/api/auth', () => ({ login: mockLogin }))
jest.mock('@/authuser/api/authUser', () => ({ get: mockStatus }))
+import { router } from '>/routerMocks'
+
import { createDatastore, createValidationError, throws } from '>/helpers'
describe('auth', () => {
@@ -43,7 +42,7 @@ describe('auth', () => {
expect(datastore.getters['auth/loginStatus'].validationErrors).toEqual({})
expect(datastore.getters['auth/isLoggedIn']).toBe(true)
expect(datastore.getters['auth/user']).toBeDefined()
- expect(mockRouterPush).toBeCalledWith('/')
+ expect(router.push).toBeCalledWith('/')
})
it('can update user', () => {
diff --git a/src/base/routes/main.js b/src/base/routes/main.js
index fc18a6352..d1a500758 100644
--- a/src/base/routes/main.js
+++ b/src/base/routes/main.js
@@ -274,7 +274,6 @@ export default [
breadcrumbs: [
{ translation: 'GROUP.OFFERS', route: { name: 'groupOffers' } },
],
- afterLeave: 'offers/clear',
},
components: {
default: GroupOffers,
diff --git a/src/boot/socket.js b/src/boot/socket.js
index 01455ad03..05ab1f78f 100644
--- a/src/boot/socket.js
+++ b/src/boot/socket.js
@@ -1,5 +1,6 @@
import ReconnectingWebsocket from 'reconnecting-websocket'
import { debounce, AppVisibility } from 'quasar'
+import mitt from 'mitt'
import log from '@/utils/log'
import auth from '@/authuser/api/auth'
@@ -19,6 +20,9 @@ import { convert as convertOffer } from '@/offers/api/offers'
import { convert as convertGroup } from '@/group/api/groups'
import { convert as convertNotification, convertMeta as convertNotificationMeta } from '@/notifications/api/notifications'
+// Global event bus for websocket events
+export const socketEvents = mitt()
+
export default async function ({ store: datastore }) {
let WEBSOCKET_ENDPOINT
@@ -114,7 +118,9 @@ export default async function ({ store: datastore }) {
clearTimeout(pingTimeout)
}
- receiveMessage(data)
+ if (data.topic) {
+ receiveMessage(data)
+ }
})
// reconnect when browser tells us the connection is back
@@ -155,12 +161,40 @@ export default async function ({ store: datastore }) {
},
}
+ function convertPayload (topic, payload) {
+ switch (topic) {
+ case 'applications:update': return convertApplication(payload)
+ case 'conversations:message': return convertMessage(payload)
+ case 'conversations:conversation': return convertConversation(payload)
+ case 'conversations:meta': return convertConversationMeta(payload)
+ case 'community_feed:meta': return convertCommunityFeedMeta(payload)
+ case 'groups:group_detail': return convertGroup(payload)
+ case 'invitations:invitation': return convertInvitation(payload)
+ case 'issues:issue': return convertIssue(payload)
+ case 'activities:activity': return convertActivity(payload)
+ case 'activities:activity_deleted': return convertActivity(payload)
+ case 'activities:series': return convertSeries(payload)
+ case 'activities:series_deleted': return convertSeries(payload)
+ case 'offers:offer': return convertOffer(payload)
+ case 'offers:offer_deleted': return convertOffer(payload)
+ case 'feedback:feedback': return convertFeedback(payload)
+ case 'history:history': return convertHistory(payload)
+ case 'notifications:notification': return convertNotification(payload)
+ case 'notifications:meta': return convertNotificationMeta(payload)
+ default: return payload
+ }
+ }
+
function receiveMessage ({ topic, payload }) {
+ payload = convertPayload(topic, camelizeKeys(payload))
+
+ socketEvents.emit(topic, payload)
+
if (topic === 'applications:update') {
- datastore.commit('applications/update', [convertApplication(camelizeKeys(payload))])
+ datastore.commit('applications/update', [payload])
}
else if (topic === 'conversations:message') {
- const message = convertMessage(camelizeKeys(payload))
+ const message = payload
if (message.thread) {
datastore.dispatch('currentThread/receiveMessage', message)
datastore.dispatch('latestMessages/updateThreadsAndRelated', { messages: [message] })
@@ -175,15 +209,15 @@ export default async function ({ store: datastore }) {
}
}
else if (topic === 'conversations:conversation') {
- const conversation = convertConversation(camelizeKeys(payload))
+ const conversation = payload
datastore.dispatch('conversations/updateConversation', conversation)
datastore.dispatch('latestMessages/updateConversationsAndRelated', { conversations: [conversation] })
}
else if (topic === 'conversations:meta') {
- datastore.commit('latestMessages/setEntryMeta', convertConversationMeta(camelizeKeys(payload)))
+ datastore.commit('latestMessages/setEntryMeta', payload)
}
else if (topic === 'community_feed:meta') {
- datastore.commit('communityFeed/setMeta', convertCommunityFeedMeta(camelizeKeys(payload)))
+ datastore.commit('communityFeed/setMeta', payload)
}
else if (topic === 'conversations:leave') {
// refresh latest messages
@@ -193,10 +227,10 @@ export default async function ({ store: datastore }) {
}
}
else if (topic === 'groups:group_detail') {
- datastore.dispatch('currentGroup/maybeUpdate', convertGroup(camelizeKeys(payload)))
+ datastore.dispatch('currentGroup/maybeUpdate', payload)
}
else if (topic === 'groups:group_preview') {
- datastore.commit('groups/update', [camelizeKeys(payload)])
+ datastore.commit('groups/update', [payload])
}
else if (topic === 'groups:user_joined') {
datastore.dispatch('users/fetch', null, { root: true })
@@ -205,52 +239,47 @@ export default async function ({ store: datastore }) {
datastore.dispatch('users/fetch', null, { root: true })
}
else if (topic === 'invitations:invitation') {
- datastore.commit('invitations/update', [convertInvitation(camelizeKeys(payload))])
+ datastore.commit('invitations/update', [payload])
}
else if (topic === 'invitations:invitation_accept') {
// delete invitation from list until there is a better way to display it
datastore.commit('invitations/delete', payload.id)
}
else if (topic === 'issues:issue') {
- datastore.commit('issues/update', [convertIssue(camelizeKeys(payload))])
+ datastore.commit('issues/update', [payload])
}
else if (topic === 'places:place') {
- datastore.dispatch('places/update', [camelizeKeys(payload)])
+ datastore.dispatch('places/update', [payload])
}
else if (topic === 'activities:activity') {
- datastore.commit('activities/update', [convertActivity(camelizeKeys(payload))])
+ datastore.commit('activities/update', [payload])
}
else if (topic === 'activities:activity_deleted') {
- datastore.commit('activities/delete', convertActivity(camelizeKeys(payload)).id)
+ datastore.commit('activities/delete', payload.id)
}
else if (topic === 'activities:series') {
- datastore.commit('activitySeries/update', [convertSeries(camelizeKeys(payload))])
+ datastore.commit('activitySeries/update', [payload])
}
else if (topic === 'activities:series_deleted') {
- datastore.commit('activitySeries/delete', convertSeries(camelizeKeys(payload)).id)
+ datastore.commit('activitySeries/delete', payload.id)
}
else if (topic === 'activities:type') {
- datastore.commit('activityTypes/update', [camelizeKeys(payload)])
+ datastore.commit('activityTypes/update', [payload])
}
else if (topic === 'activities:type_deleted') {
datastore.commit('activityTypes/delete', payload.id)
}
else if (topic === 'offers:offer') {
- const offer = convertOffer(camelizeKeys(payload))
- datastore.commit('offers/update', [offer])
- datastore.commit('latestMessages/updateRelated', { type: 'offer', items: [offer] })
- datastore.commit('currentOffer/update', offer)
+ datastore.commit('latestMessages/updateRelated', { type: 'offer', items: [payload] })
}
else if (topic === 'offers:offer_deleted') {
- datastore.commit('offers/delete', payload.id)
datastore.commit('latestMessages/deleteRelated', { type: 'offer', ids: [payload.id] })
- datastore.commit('currentOffer/delete', payload.id)
}
else if (topic === 'feedback:feedback') {
- datastore.dispatch('feedback/updateOne', convertFeedback(camelizeKeys(payload)))
+ datastore.dispatch('feedback/updateOne', payload)
}
else if (topic === 'auth:user') {
- const user = camelizeKeys(payload)
+ const user = payload
datastore.commit('auth/setUser', user)
datastore.commit('users/update', [user])
datastore.dispatch('users/refreshProfile', user)
@@ -259,24 +288,24 @@ export default async function ({ store: datastore }) {
datastore.dispatch('auth/refresh')
}
else if (topic === 'users:user') {
- const user = camelizeKeys(payload)
+ const user = payload
datastore.commit('users/update', [user])
datastore.dispatch('users/refreshProfile', user)
}
else if (topic === 'history:history') {
- datastore.commit('history/update', [convertHistory(camelizeKeys(payload))])
+ datastore.commit('history/update', [payload])
}
else if (topic === 'notifications:notification') {
- datastore.commit('notifications/update', [convertNotification(camelizeKeys(payload))])
+ datastore.commit('notifications/update', [payload])
}
else if (topic === 'notifications:notification_deleted') {
datastore.commit('notifications/delete', payload.id)
}
else if (topic === 'notifications:meta') {
- datastore.commit('notifications/setEntryMeta', convertNotificationMeta(camelizeKeys(payload)))
+ datastore.commit('notifications/setEntryMeta', payload)
}
else if (topic === 'status') {
- datastore.commit('status/update', camelizeKeys(payload))
+ datastore.commit('status/update', payload)
}
}
diff --git a/src/boot/vue-query.js b/src/boot/vue-query.js
new file mode 100644
index 000000000..8a0632392
--- /dev/null
+++ b/src/boot/vue-query.js
@@ -0,0 +1,5 @@
+import { VueQueryPlugin } from 'vue-query'
+
+export default ({ app, store }) => {
+ app.use(VueQueryPlugin)
+}
diff --git a/src/group/datastore/currentGroup.js b/src/group/datastore/currentGroup.js
index 269812245..f809dc960 100644
--- a/src/group/datastore/currentGroup.js
+++ b/src/group/datastore/currentGroup.js
@@ -1,5 +1,11 @@
import groups from '@/group/api/groups'
-import { withMeta, createMetaModule, withPrefixedIdMeta, metaStatusesWithId, createRouteRedirect } from '@/utils/datastore/helpers'
+import {
+ createMetaModule,
+ createRouteRedirect,
+ metaStatusesWithId,
+ withMeta,
+ withPrefixedIdMeta,
+} from '@/utils/datastore/helpers'
import { extend } from 'quasar'
import i18n from '@/base/i18n'
import { messages as loadMessages } from '@/locales/index'
diff --git a/src/group/queries.js b/src/group/queries.js
new file mode 100644
index 000000000..7578cfcb8
--- /dev/null
+++ b/src/group/queries.js
@@ -0,0 +1,7 @@
+import { useStore } from 'vuex'
+import { computed } from 'vue'
+
+export function useCurrentGroupIdRef () {
+ const store = useStore()
+ return computed(() => store.getters['currentGroup/id'])
+}
diff --git a/src/groupInfo/datastore/groups.spec.js b/src/groupInfo/datastore/groups.spec.js
index a27abb4ac..dd4a49409 100644
--- a/src/groupInfo/datastore/groups.spec.js
+++ b/src/groupInfo/datastore/groups.spec.js
@@ -12,13 +12,7 @@ jest.mock('@/groupInfo/api/groupsInfo', () => ({
list: mockFetchGroupsPreview,
}))
-const routerMocks = require('>/routerMocks').default
-const mockRouterPush = jest.fn(routerMocks.$router.push)
-const mockRouterReplace = jest.fn(routerMocks.$router.replace)
-jest.mock('@/router', () => ({
- push: mockRouterPush,
- replace: mockRouterReplace,
-}))
+import { router } from '>/routerMocks'
import { createDatastore, createValidationError, throws, statusMocks } from '>/helpers'
import { enrichGroup } from '>/datastoreHelpers'
@@ -131,7 +125,7 @@ describe('groups', () => {
mockJoin.mockReturnValueOnce({})
expect(datastore.getters['groups/mineWithApplications'].map(e => e.id)).toEqual([group2.id, group3.id])
await datastore.dispatch('groups/join', group1.id)
- expect(mockRouterPush).toBeCalledWith({ name: 'group', params: { groupId: group1.id } })
+ expect(router.push).toBeCalledWith({ name: 'group', params: { groupId: group1.id } })
expect(mockJoin).toBeCalledWith(group1.id)
})
diff --git a/src/messages/components/LatestConversations.vue b/src/messages/components/LatestConversations.vue
index e49ea2d0b..721a068fc 100644
--- a/src/messages/components/LatestConversations.vue
+++ b/src/messages/components/LatestConversations.vue
@@ -118,7 +118,7 @@ export default {
case 'issue': return this.$router.push({ name: 'issueChat', params: { groupId: target.group.id, issueId: target.id } }).catch(() => {})
case 'offer': return this.$router.push({
name: 'offerDetail',
- params: { groupId: target.group.id, offerId: target.id },
+ params: { groupId: target.group, offerId: target.id },
query: this.$route.query,
}).catch(() => {})
}
diff --git a/src/messages/datastore/latestMessages.js b/src/messages/datastore/latestMessages.js
index bd87ce475..bffa84662 100644
--- a/src/messages/datastore/latestMessages.js
+++ b/src/messages/datastore/latestMessages.js
@@ -77,13 +77,7 @@ export default {
fetchingPastThreads: (state, getters) => getters['meta/status']('fetchPastThreads').pending,
fetchInitialPending: (state, getters) => getters['meta/status']('fetchInitial').pending,
getRelated: (state, getters, rootState, rootGetters) => (type, id) => {
- const related = state.related[type] && state.related[type][id]
- if (!related) return
- switch (type) {
- case 'offer': return rootGetters['offers/enrich'](related)
- default:
- return related
- }
+ return state.related[type] && state.related[type][id]
},
},
actions: {
diff --git a/src/offers/api/offers.mock.js b/src/offers/api/offers.mock.js
new file mode 100644
index 000000000..0674ec6f9
--- /dev/null
+++ b/src/offers/api/offers.mock.js
@@ -0,0 +1,30 @@
+import { faker } from '@faker-js/faker'
+import { createCursorPaginatedBackend, createGetByIdBackend } from '>/mockAxios'
+
+function sample (items) {
+ return items[Math.floor(Math.realRandom() * items.length)]
+}
+
+let nextOfferId = 1
+export function createOffer (params) {
+ return {
+ id: ++nextOfferId,
+ name: faker.random.words(5),
+ description: faker.lorem.paragraphs(2),
+ status: sample(['active', 'archived']),
+ created_at: faker.date.past(),
+ group: 1,
+ images: [],
+ ...params,
+ }
+}
+
+export function createMockOffersBackend (offers, options = {}) {
+ createCursorPaginatedBackend('/api/offers/', offers, ({ params }) => {
+ const status = params.status
+ const group = parseInt(params.group || '1')
+ return offer => offer.status === status && offer.group === group
+ }, options)
+
+ createGetByIdBackend('/api/offers/:id/', offers)
+}
diff --git a/src/offers/components/OfferDetailBody.vue b/src/offers/components/OfferDetailBody.vue
index 8b5d140b2..8237072ba 100644
--- a/src/offers/components/OfferDetailBody.vue
+++ b/src/offers/components/OfferDetailBody.vue
@@ -35,7 +35,7 @@
{{ offer.status }}
@@ -76,7 +79,9 @@ import ChatConversation from '@/messages/components/ChatConversation'
import Markdown from '@/utils/components/Markdown'
import KSpinner from '@/utils/components/KSpinner'
import { QBtn, QBtnDropdown, QCarousel, QCarouselSlide } from 'quasar'
-import { DEFAULT_STATUS } from '@/offers/datastore/offers'
+import { DEFAULT_STATUS, useCurrentOfferQuery } from '@/offers/queries'
+import { useCurrentUserIdRef } from '@/users/queries'
+import { useArchiveOfferMutation } from '@/offers/mutations'
export default {
components: {
@@ -94,6 +99,15 @@ export default {
default: false,
},
},
+ setup () {
+ const { mutate: archive } = useArchiveOfferMutation()
+ const { offer } = useCurrentOfferQuery()
+ return {
+ archive,
+ offer,
+ currentUserId: useCurrentUserIdRef(),
+ }
+ },
data () {
return {
selectedImageIndex: 0,
@@ -101,11 +115,13 @@ export default {
},
computed: {
...mapGetters({
- offer: 'currentOffer/value',
conversation: 'currentOffer/conversation',
away: 'presence/toggle/away',
currentUser: 'auth/user',
}),
+ canEdit () {
+ return this.offer.user === this.currentUserId
+ },
conversationWithReversedMessages () {
return {
...this.conversation,
@@ -140,7 +156,6 @@ export default {
toggleReaction: 'conversations/toggleReaction',
fetchPast: 'conversations/fetchPast',
saveConversation: 'conversations/maybeSave',
- archive: 'offers/archive',
}),
},
}
diff --git a/src/offers/components/OfferDetailHeader.vue b/src/offers/components/OfferDetailHeader.vue
index 51f4f37ff..14a7fcc1f 100644
--- a/src/offers/components/OfferDetailHeader.vue
+++ b/src/offers/components/OfferDetailHeader.vue
@@ -7,7 +7,7 @@