diff --git a/components/pages/events/Event/index.vue b/components/pages/events/Event/index.vue index 26a4be9..af63b82 100644 --- a/components/pages/events/Event/index.vue +++ b/components/pages/events/Event/index.vue @@ -5,14 +5,14 @@ - Datum: {{event.date}} ({{ weekday }}) - Tid: {{event.time}} + {{event.date}} ({{ weekday }} {{ daysUntil }}) + {{event.time}} - Var:mdi-eye-off{{event.danceHall.name}} - Ort:mdi-eye-off{{event.danceHall.city}} - Kommun:mdi-eye-off{{event.danceHall.municipality}} - Län:mdi-eye-off{{event.danceHall.state}} + mdi-eye-off{{event.danceHall.name}} + mdi-eye-off{{event.danceHall.city}} + mdi-eye-off{{event.danceHall.municipality}} + mdi-eye-off{{event.danceHall.state}} @@ -31,6 +31,8 @@ diff --git a/components/pages/events/List/index.vue b/components/pages/events/List/index.vue new file mode 100644 index 0000000..a6e423b --- /dev/null +++ b/components/pages/events/List/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/components/pages/events/index.vue b/components/pages/events/index.vue index 75ca484..471d843 100644 --- a/components/pages/events/index.vue +++ b/components/pages/events/index.vue @@ -1,249 +1,202 @@ diff --git a/components/pages/filters/List/index.vue b/components/pages/filters/List/index.vue index cfbaa34..48bee00 100644 --- a/components/pages/filters/List/index.vue +++ b/components/pages/filters/List/index.vue @@ -2,14 +2,17 @@ - - - st + - mdi-delete-outline + + + + diff --git a/components/pages/filters/index.vue b/components/pages/filters/index.vue index cdb0f45..31ed110 100644 --- a/components/pages/filters/index.vue +++ b/components/pages/filters/index.vue @@ -1,12 +1,6 @@ @@ -44,110 +45,82 @@ } from "~/utils/graph-client"; import List from "./List"; + import { useMutation, useQuery } from '../../../plugins/apollo' + import { ref } from '@vue/composition-api' + import { useAuth } from '../../../plugins/auth' + import { useMutations } from '@u3u/vue-hooks' + import { useTranslation } from '../../../plugins/i18n' export default { components: { List }, - data() { - return { - status: "loading", - bands: [], - danceHalls: [], - cities: [], - municipalities: [], - states: [], - submitInfo: "", - snackbar: false, - snackColor: "success", - snackText: "", - }; - }, - computed: { - isLoading() { - return this.status === "loading"; - }, - isReady() { - return this.status === "ready"; - }, - isSubmitting() { - return this.status === "submitting"; - }, - isSubmitted() { - return this.status === "submitted"; - }, - isRefreshing() { - return this.status === "refreshing"; - } - }, - mounted() { - this.$store.commit('setTitle', 'Filter'); - this.fetchFilters(); - }, - methods: { - fetchFilters(status) { - this.status = status || "loading"; - fetchFilters() - .then(this.filtersFetched) - .catch(this.filtersFailed); - }, - filtersFetched(response) { - if (response.errors) { - throw new Error("Fetch failed"); + setup() { + const { setTitle } = useMutations(['setTitle']) + const { t } = useTranslation(); + setTitle(t('app.links.filters')) + const { loading: authLoading, isAuthenticated } = useAuth() + const {data, loading, error, refetch} = useQuery(fetchFilters) + const snackbar = ref({ active: false, color: 'success', text: '' }) + const [doToggleIgnoreBand, {loading: ignoringBand, error: errorIgnoreBand}] = useMutation(toggleIgnoreBand) + const [doToggleIgnoreDanceHall, {loading: ignoringDanceHall, error: errorIgnoreDanceHall}] = useMutation(toggleIgnoreDanceHall) + const [doToggleIgnoreCity, {loading: ignoringCity, error: errorIgnoreCity}] = useMutation(toggleIgnoreCity) + const [doToggleIgnoreMunicipality, {loading: ignoringMunicipality, error: errorIgnoreMunicipality}] = useMutation(toggleIgnoreMunicipality) + const [doToggleIgnoreState, {loading: ignoringState, error: errorIgnoreState}] = useMutation(toggleIgnoreState) + + const toggleIgnoreSuccess = (name) => { + return () => { + refetch.value(); + snackbar.value.color = 'success'; + snackbar.value.text = t('filters.success', { name }); + snackbar.value.active = true; } - this.bands = response.data.bands; - this.danceHalls = response.data.danceHalls; - this.cities = response.data.cities; - this.municipalities = response.data.municipalities; - this.states = response.data.states; - this.status = "ready"; - }, - filtersFailed() { - this.status = "load-failed"; - }, - toggleIgnore(type, name) { + } + + const toggleIgnoreFailed = (name) => { + return () => { + snackbar.value.color = 'error'; + snackbar.value.text = t('filters.failure', { name }); + snackbar.value.active = true; + } + } + + const toggleIgnore = (type, name) => { switch (type) { case 'band': - toggleIgnoreBand({name: name}) - .then(this.toggleIgnoreSuccess(name)) - .catch(this.toggleIgnoreFailed); + doToggleIgnoreBand({ variables: { name: name } }) + .then(toggleIgnoreSuccess(name)) + .catch(toggleIgnoreFailed); break; case 'danceHall': - toggleIgnoreDanceHall({name: name}) - .then(this.toggleIgnoreSuccess(name)) - .catch(this.toggleIgnoreFailed); + doToggleIgnoreDanceHall({ variables: { name: name } }) + .then(toggleIgnoreSuccess(name)) + .catch(toggleIgnoreFailed); break; case 'city': - toggleIgnoreCity({name: name}) - .then(this.toggleIgnoreSuccess(name)) - .catch(this.toggleIgnoreFailed); + doToggleIgnoreCity({ variables: { name: name } }) + .then(toggleIgnoreSuccess(name)) + .catch(toggleIgnoreFailed); break; case 'municipality': - toggleIgnoreMunicipality({name: name}) - .then(this.toggleIgnoreSuccess(name)) - .catch(this.toggleIgnoreFailed); + doToggleIgnoreMunicipality({ variables: { name: name } }) + .then(toggleIgnoreSuccess(name)) + .catch(toggleIgnoreFailed); break; case 'state': - toggleIgnoreState({name: name}) - .then(this.toggleIgnoreSuccess(name)) - .catch(this.toggleIgnoreFailed); + doToggleIgnoreState({ variables: { name: name } }) + .then(toggleIgnoreSuccess(name)) + .catch(toggleIgnoreFailed); break; } - }, - toggleIgnoreSuccess(name) { - return () => { - this.fetchFilters('refreshing'); - this.snackColor = 'success'; - this.snackText = `${name} har tagits bort`; - this.snackbar = true; - } - }, - toggleIgnoreFailed(name) { - return () => { - this.snackColor = 'error'; - this.snackText = `${name} kunde inte tas bort`; - this.snackbar = true; - } + } + + return { + isAuthenticated, + loading, + data, + snackbar, + toggleIgnore } } } diff --git a/components/pages/origins/index.vue b/components/pages/origins/index.vue index 02cd0ca..0f4931c 100644 --- a/components/pages/origins/index.vue +++ b/components/pages/origins/index.vue @@ -1,41 +1,47 @@ @@ -45,112 +51,50 @@ saveOrigin, removeOrigin, fetchAddress, - } from "~/utils/graph-client"; + } from "../../../utils/graph-client"; + import { useLazyQuery, useMutation, useQuery } from '../../../plugins/apollo' + import { ref } from '@vue/composition-api' + import { useAuth } from '../../../plugins/auth' + import { useMutations } from '@u3u/vue-hooks' + import { useTranslation } from '../../../plugins/i18n' export default { - data() { - return { - status: "loading", - origin: undefined, - submitInfo: "", - snackbar: false, - snackColor: "success", - snackText: "", - }; - }, - computed: { - isLoading() { - return this.status === "loading"; - }, - isReady() { - return this.status === "ready"; - }, - isSubmitting() { - return this.status === "submitting"; - }, - isSubmitted() { - return this.status === "submitted"; - }, - }, - mounted() { - this.$store.commit('setTitle', 'Startpunkter'); - this.fetchOrigins(); - }, - methods: { - fetchOrigins() { - this.status = "loading"; - findOrigins() - .then(this.originsFetched) - .catch(this.originsFailed); - }, - originsFetched(response) { - if (response.errors) { - throw new Error("Fetch failed"); - } - this.origins = response.data.origins; - this.status = "ready"; - }, - originsFailed() { - this.status = "load-failed"; - }, - saveOrigin(origin) { - this.submitInfo = 'Sparar startpunkt'; - this.status = 'submitting'; - saveOrigin({origin: origin}) - .then(saved => { - this.status = 'submitted'; - if (saved) { - this.origins = []; - this.origin = ""; - this.fetchOrigins(); - } else { - this.snackColor = 'error'; - this.snackText = `${origin} kunde inte sparas`; - this.snackbar = true; - } - }) - .catch(() => { - this.status = 'submit-failed'; - this.snackColor = 'error'; - this.snackText = `${origin} kunde inte sparas`; - this.snackbar = true; - }) - }, - removeOrigin(origin) { - this.submitInfo = 'Tar bort startpunkt'; - this.status = 'submitting'; - removeOrigin({origin: origin}) - .then(removed => { - this.status = 'submitted'; - if (removed) { - this.origins = []; - this.fetchOrigins(); - } else { - this.snackColor = 'error'; - this.snackText = `${origin} kunde inte tas bort`; - this.snackbar = true; - } - }) - .catch(() => { - this.status = 'submit-failed'; - this.snackColor = 'error'; - this.snackText = `${origin} kunde inte tas bort`; - this.snackbar = true; - }) - }, - fetchAddress() { + setup() { + const { setTitle } = useMutations(['setTitle']) + const { t } = useTranslation(); + setTitle(t('app.links.origins')) + const { loading: authLoading, isAuthenticated } = useAuth() + const {data, loading, error, refetch} = useQuery(findOrigins) + const snackbar = ref({ active: false, color: 'success', text: '' }) + const [doSaveOrigin, {loading: savingOrigin, error: errorSaveOrigin}] = useMutation(saveOrigin) + const [doRemoveOrigin, {loading: removingOrigin, error: errorRemoveOrigin}] = useMutation(removeOrigin) + const [doFetchAddress, {data: address, loading: fetchingAddress, error: errorFetchingAddress}] = useLazyQuery(fetchAddress) + const origin = ref('') + const fetchAddressFn = () => { if (window.navigator) { - this.submitInfo = 'Hämtar aktuell position'; - this.status = 'submitting'; window.navigator.geolocation.getCurrentPosition(pos => { - fetchAddress({latlng: pos.coords.latitude + "," + pos.coords.longitude}) - .then(response => { - this.status = 'submitted'; - this.origin = response.data.address; - }) + doFetchAddress({variables: {latlng: pos.coords.latitude + "," + pos.coords.longitude}}) + .then(() => { + origin.value = address.value.address; + }) }) } - }, + } + const saveOriginFn = o => doSaveOrigin({variables: { origin: o }}).then(() => { + refetch.value() + origin.value = '' + }) + const removeOriginFn = o => doRemoveOrigin({variables: { origin: o }}).then(() => refetch.value()) + return { + isAuthenticated, + loading, + data, + snackbar, + origin, + saveOrigin: saveOriginFn, + removeOrigin: removeOriginFn, + fetchAddress: fetchAddressFn + } } } diff --git a/layouts/components/themed.vue b/layouts/components/themed.vue new file mode 100644 index 0000000..e7843d3 --- /dev/null +++ b/layouts/components/themed.vue @@ -0,0 +1,15 @@ + + + diff --git a/layouts/default.vue b/layouts/default.vue index 254704b..0b65608 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,129 +1,170 @@ diff --git a/middleware/auth.js b/middleware/auth.js deleted file mode 100644 index 9dacc5f..0000000 --- a/middleware/auth.js +++ /dev/null @@ -1,30 +0,0 @@ -import auth from '~/utils/auth'; - -// do not add trailing slashes to the excluded paths. -const excludedPaths = ['', '/authorize', '/logout']; - -// This function is not SSR-compatible, and works since our code currently only runs on the client. -export default function ({ isHMR, route }) { - // If middleware is called from hot module replacement, ignore it - if (isHMR) { - return; - } - - // support both trailing slash and non-trailing slash so it works the same - // when hosted from s3 and not. - let path = route.path; - if (path.endsWith('/')) { - path = path.slice(0, -1); - } - - if (!excludedPaths.includes(path)) { - if (auth.isExpired()) { - // if the auth has just expired - try to log in silently. - auth.triggerSilentLogin({ returnUrl: route.fullPath }); - } - else if (!auth.isAuthenticated()) { - // otherwise, if the user is not authenticated, trigger a full login. - auth.triggerLogin({ returnUrl: route.fullPath }); - } - } -} diff --git a/nuxt.config.js b/nuxt.config.js index 0531b62..0ca3da4 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -1,7 +1,11 @@ -module.exports = { - css: [ - 'vuetify/dist/vuetify.css', - ], +import translations from './translations' +import numberFormats from './translations/numberFormats' + +export default { + env: { + graphqlApi: process.env.GRAPHQL_API, + }, + mode: 'spa', head: { link: [ {rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png'}, @@ -18,21 +22,63 @@ module.exports = { {name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=no, minimal-ui'} ], }, - mode: 'spa', - buildModules: [ - ['@nuxtjs/vuetify', { /* module options */ }] - ], modules: [ + 'nuxt-i18n', + '@nuxtjs/vuetify', ['@nuxtjs/moment', { locales: ['sv'], defaultLocale: 'sv' }], ], - plugins: [ - {src: '~/plugins/vuetify.js', ssr: false}, - {src: '~/plugins/graph-routing.js', ssr: false}, - {src: '~/plugins/app-components.js', ssr: false}, - {src: '~/plugins/vue-lazyload.js', ssr: false}, - {src: '~/plugins/vue-numeral-filter.js', ssr: false} + i18n: { + strategy: 'prefix_and_default', + detectBrowserLanguage: { + useCookie: true, + cookieKey: 'nuxt_i18n_redirected', + alwaysRedirect: true, + fallbackLocale: 'sv' + }, + locales: [ + { + code: 'en', + iso: 'en-US' + }, + { + code: 'sv', + iso: 'sv-SE' + } + ], + defaultLocale: 'sv', + vueI18n: { + fallbackLocale: 'sv', + messages: translations, + numberFormats + } + }, + vuetify: { + optionsPath: './vuetify.options.js' + }, + css: [ + 'vuetify/dist/vuetify.css', + '~/assets/scss/global.scss', ], - router: { - middleware: ['auth'] + plugins: [ + '~/plugins/composition', + '~/plugins/hooks', + '~/plugins/i18n', + '~/plugins/vue-numeral-filter.js' + ], + build: { + babel: { + presets({ isServer }) { + return [ + [ + require.resolve('@nuxt/babel-preset-app'), + // require.resolve('@nuxt/babel-preset-app-edge'), // For nuxt-edge users + { + buildTarget: isServer ? 'server' : 'client', + corejs: { version: 3 } + } + ] + ] + } + } } }; diff --git a/package.json b/package.json index 4a3cdfe..a332142 100644 --- a/package.json +++ b/package.json @@ -2,38 +2,44 @@ "name": "dancefinder-app", "version": "1.0.0", "engines": { - "node": ">=10" + "node": ">=12" }, "author": "Joakim Olsson ", "private": true, "dependencies": { + "@auth0/auth0-spa-js": "^1.5.0", "@nuxtjs/moment": "^1.1.0", - "apollo-link": "^1.2.6", - "apollo-link-error": "^1.1.5", - "apollo-link-http": "^1.5.9", - "auth0-js": "^9.9.0", + "@nuxtjs/vuetify": "^1.9.0", + "@u3u/vue-hooks": "^2.0.1", + "@vue/composition-api": "^0.3.4", + "apollo": "^2.17.4", + "apollo-cache-inmemory": "^1.6.3", + "apollo-client": "^2.6.4", + "apollo-link": "^1.2.12", + "apollo-link-context": "^1.0.18", + "apollo-link-error": "^1.1.11", + "apollo-link-http": "^1.5.15", + "core-js": "3", + "dayjs": "^1.8.19", "eslint": "^5.1.0", "eslint-plugin-vue": "^4.5.0", - "graphql": "^14.1.1", + "graphql": "^14.4.2", "graphql-tag": "^2.10.1", - "lodash": "^4.17.10", - "mem": "^4.0.0", "moment": "^2.24.0", "node-sass": "^4.12.0", - "nuxt": "^2.0.0", - "s-ago": "^1.3.0", + "nuxt": "^2.8.1", + "nuxt-i18n": "^6.0.1", "sass-loader": "^7.0.3", "snyk": "^1.258.2", - "vue": "^2.5.22", - "vue-lazyload": "^1.2.6", + "vue": "^2.6.10", "vue-numeral-filter": "^1.1.1", "vuetify": "^2.1.9" }, "scripts": { - "dev": "NODE_ENV=development node server/index.js", + "dev": "nuxt", "build": "nuxt build", "generate": "nuxt generate", - "lint": "echo NYI", + "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", "precommit": "yarn lint", "prepush": "yarn test", "start": "node server/index.js", @@ -44,9 +50,8 @@ "prepublish": "yarn run snyk-protect" }, "devDependencies": { - "@nuxtjs/vuetify": "^1.9.0", + "@babel/runtime-corejs3": "^7.8.3", "cypress": "^3.1.0", - "express-http-proxy": "^1.5.0", "wait-on": "^3.2.0" }, "snyk": true diff --git a/pages/authorize.vue b/pages/authorize.vue deleted file mode 100644 index ef17c5d..0000000 --- a/pages/authorize.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - - diff --git a/pages/filters.vue b/pages/filters.vue index 4482b42..4c5f931 100644 --- a/pages/filters.vue +++ b/pages/filters.vue @@ -4,14 +4,16 @@ diff --git a/pages/origins.vue b/pages/origins.vue index 450ccb2..546a5c6 100644 --- a/pages/origins.vue +++ b/pages/origins.vue @@ -4,14 +4,16 @@