Skip to content
This repository has been archived by the owner on May 20, 2024. It is now read-only.

Commit

Permalink
Use vue-query for offers (#2560)
Browse files Browse the repository at this point in the history
* Use vue-query for offers

* Add getUser function, simplify ProfilePicture

To avoid having Object|Number prop for user... and ProfilePicture
no longer handles retreiving the user

* Use vue-query 2 beta

* Implement all status fields

* Comment now absent mutation

* Uncomment canSave function

* Fix offer editing

* Re-add websocket updates for offers

* Add mitt dependency

* Refactor utilities

* Minor changes, remove console logs

* Fix mock router for tests

* Additional v-measure for offer detail

* Please the linter

* Add changelog entry

* Fix getNextPageParam to use undefined, not null

* Fixup infinite offers query

* Create mock offers backend and query test

* Add GroupOffers page test

* Fixup some details

* Disable submit if errors

* Simplify extractCursor and write tests

* Add test for useOfferQuery

* Update snapshots
  • Loading branch information
nicksellen committed Jul 18, 2022
1 parent 641693c commit b3fb6b7
Show file tree
Hide file tree
Showing 43 changed files with 1,031 additions and 412 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -22,6 +22,7 @@ Please document your changes in this format:

## [Unreleased]
### Added
- switch to vue-query for offers @nicksellen #2560
- password-less ICS subscription links, to support Google Calender #2555 @tiltec
- Korean and Greek translations

Expand Down
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -66,6 +66,7 @@
"markdown-it-emoji": "^2.0.2",
"markdown-it-link-attributes": "^4.0.0",
"markdown-it-regexp": "^0.4.0",
"mitt": "^3.0.0",
"quasar": "^2.7.5",
"reconnecting-websocket": "^4.4.0",
"twemoji": "^14.0.2",
Expand All @@ -74,7 +75,9 @@
"vue-croppa": "^1.3.8",
"vue-i18n": "^9.1.10",
"vue-mention": "^2.0.0-alpha.3",
"vue-query": "^2.0.0-beta.1",
"vue-router": "4.1.2",
"vue-router-mock": "^0.1.8",
"vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0",
"vuex-router-sync": "^5.0.0"
Expand All @@ -83,6 +86,7 @@
"@babel/core": "^7.18.6",
"@babel/eslint-parser": "^7.18.2",
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
"@faker-js/faker": "^7.3.0",
"@mapbox/appropriate-images": "^5.0.0",
"@octokit/rest": "^19.0.3",
"@quasar/app-webpack": "^3.5.7",
Expand Down Expand Up @@ -133,6 +137,7 @@
"keep-a-changelog": "^2.1.0",
"lodash": "^4.17.21",
"lolex": "^6.0.0",
"path-to-regexp": "^6.2.1",
"postcss": "^8.4.14",
"postcss-html": "^1.5.0",
"postcss-syntax": "^0.36.2",
Expand Down
1 change: 1 addition & 0 deletions quasar.conf.js
Expand Up @@ -68,6 +68,7 @@ module.exports = configure(function (ctx) {
// https://quasar.dev/quasar-cli/boot-files
boot: [
'compat',
'vue-query',
'loglevel',
'pwa',
'helloDeveloper',
Expand Down
8 changes: 8 additions & 0 deletions src/App.vue
Expand Up @@ -15,10 +15,18 @@
*/
import LoadingProgress from '@/topbar/components/LoadingProgress'
import { useClearDataOnLogout } from '@/utils/composables'
import { useOffersUpdater } from '@/offers/queries'
export default {
components: {
LoadingProgress,
},
setup () {
// Global kind of things can be registered here
useOffersUpdater()
useClearDataOnLogout()
},
computed: {
hasView () {
const firstMatched = this.$route.matched.length > 0 && this.$route.matched[0]
Expand Down
6 changes: 3 additions & 3 deletions src/__snapshots__/storyshots.spec.js.snap
Expand Up @@ -9493,7 +9493,7 @@ exports[`Storyshots Notifications activity_disabled 1`] = `
<div class="q-item q-item-type row no-wrap isUnread" style="">
<!--v-if-->
<div class="q-item__section column q-item__section--main justify-center">
<div style="overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;" class="q-item__label"><i class="q-icon fas fa-times q-mx-xs vertical-baseline" style="" aria-hidden="true" role="presentation"> </i> Pickup on Sunday, December 24 at 12:00 PM was disabled</div>
<div style="overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;" class="q-item__label"><i class="q-icon fas fa-times q-mx-xs vertical-baseline" style="" aria-hidden="true" role="presentation"> </i> Pickup on Sunday, December 24, 12:00 PM was disabled</div>
<div style="" class="q-item__label q-item__label--caption text-caption">
<div title="Dec 24, 2017, 12:00 PM" style="display:inline;">less than a minute ago</div> · Group 30
<!--[--> · Place 2
Expand All @@ -9507,7 +9507,7 @@ exports[`Storyshots Notifications activity_enabled 1`] = `
<div class="q-item q-item-type row no-wrap isUnread" style="">
<!--v-if-->
<div class="q-item__section column q-item__section--main justify-center">
<div style="overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;" class="q-item__label"><i class="q-icon fas fa-check q-mx-xs vertical-baseline" style="" aria-hidden="true" role="presentation"> </i> Pickup on Sunday, December 24 at 12:00 PM was enabled again</div>
<div style="overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;" class="q-item__label"><i class="q-icon fas fa-check q-mx-xs vertical-baseline" style="" aria-hidden="true" role="presentation"> </i> Pickup on Sunday, December 24, 12:00 PM was enabled again</div>
<div style="" class="q-item__label q-item__label--caption text-caption">
<div title="Dec 24, 2017, 12:00 PM" style="display:inline;">less than a minute ago</div> · Group 30
<!--[--> · Place 2
Expand All @@ -9521,7 +9521,7 @@ exports[`Storyshots Notifications activity_moved 1`] = `
<div class="q-item q-item-type row no-wrap isUnread" style="">
<!--v-if-->
<div class="q-item__section column q-item__section--main justify-center">
<div style="overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;" class="q-item__label"><i class="q-icon far fa-clock q-mx-xs vertical-baseline" style="" aria-hidden="true" role="presentation"> </i> Pickup was moved to Sunday, December 24 at 12:00 PM</div>
<div style="overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;" class="q-item__label"><i class="q-icon far fa-clock q-mx-xs vertical-baseline" style="" aria-hidden="true" role="presentation"> </i> Pickup was moved to Sunday, December 24, 12:00 PM</div>
<div style="" class="q-item__label q-item__label--caption text-caption">
<div title="Dec 24, 2017, 12:00 PM" style="display:inline;">less than a minute ago</div> · Group 30
<!--[--> · Place 2
Expand Down
7 changes: 3 additions & 4 deletions 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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
1 change: 0 additions & 1 deletion src/base/routes/main.js
Expand Up @@ -274,7 +274,6 @@ export default [
breadcrumbs: [
{ translation: 'GROUP.OFFERS', route: { name: 'groupOffers' } },
],
afterLeave: 'offers/clear',
},
components: {
default: GroupOffers,
Expand Down
87 changes: 58 additions & 29 deletions 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'
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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] })
Expand All @@ -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
Expand All @@ -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 })
Expand All @@ -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)
Expand All @@ -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)
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/boot/vue-query.js
@@ -0,0 +1,5 @@
import { VueQueryPlugin } from 'vue-query'

export default ({ app, store }) => {
app.use(VueQueryPlugin)
}
8 changes: 7 additions & 1 deletion 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'
Expand Down
7 changes: 7 additions & 0 deletions 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'])
}
10 changes: 2 additions & 8 deletions src/groupInfo/datastore/groups.spec.js
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
})

Expand Down
2 changes: 1 addition & 1 deletion src/messages/components/LatestConversations.vue
Expand Up @@ -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(() => {})
}
Expand Down
8 changes: 1 addition & 7 deletions src/messages/datastore/latestMessages.js
Expand Up @@ -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: {
Expand Down

0 comments on commit b3fb6b7

Please sign in to comment.