feat: add i18n support and implement Grafana plugin

Adds internationalization support in filters and origins pages by 
importing the useI18n function. Expands ESLint configuration to 
include new rules and plugins, ensuring improved code quality. 
Introduces Grafana monitoring plugin to enhance performance 
tracking capabilities in the application.
This commit is contained in:
2025-06-13 15:21:27 +02:00
parent 7bbd8f522a
commit c80fd0313c
28 changed files with 1989 additions and 1948 deletions
+24 -29
View File
@@ -6,11 +6,10 @@
v-if="hasUser"
class="ml-1 mr-1 text-medium-emphasis"
size="small"
title="Dölj"
:title="t('events.hide')"
icon='mdi-eye-off'
@click="toggleIgnore('band', event.band.name)"
>
mdi-eye-off
</v-icon>
/>
{{ event.band.name }}
</h3>
</v-card-title>
@@ -21,13 +20,13 @@
xs="12"
sm="6"
>
<strong class="mr-1" v-text="$t('events.date')" />{{
<strong class="mr-1" v-text="t('events.date')" />{{
event.date
}}
({{ weekday }} {{ daysUntil }})
</v-col>
<v-col v-if="event.time" cols="12" xs="12" sm="6">
<strong class="mr-1" v-text="$t('events.time')" />{{
<strong class="mr-1" v-text="t('events.time')" />{{
event.time
}}
</v-col>
@@ -38,16 +37,15 @@
xs="12"
sm="6"
>
<strong class="mr-1" v-text="$t('events.hall')" />
<strong class="mr-1" v-text="t('events.hall')" />
<v-icon
v-if="hasUser"
class="ml-1 mr-1 text-medium-emphasis"
size="small"
:title="$t('events.hide')"
:title="t('events.hide')"
icon='mdi-eye-off'
@click="toggleIgnore('danceHall', event.danceHall.name)"
>
mdi-eye-off
</v-icon>
/>
{{ event.danceHall.name }}
</v-col>
<v-col
@@ -55,16 +53,15 @@
xs="12"
sm="6"
>
<strong class="mr-1" v-text="$t('events.city')" />
<strong class="mr-1" v-text="t('events.city')" />
<v-icon
v-if="hasUser"
class="ml-1 mr-1 text-medium-emphasis"
size="small"
:title="$t('events.hide')"
:title="t('events.hide')"
icon='mdi-eye-off'
@click="toggleIgnore('city', event.danceHall.city)"
>
mdi-eye-off
</v-icon>
/>
{{ event.danceHall.city }}
</v-col>
<v-col
@@ -72,18 +69,17 @@
xs="12"
sm="6"
>
<strong class="mr-1" v-text="$t('events.municipality')" />
<strong class="mr-1" v-text="t('events.municipality')" />
<v-icon
v-if="hasUser"
class="ml-1 mr-1 text-medium-emphasis"
size="small"
:title="$t('events.hide')"
:title="t('events.hide')"
icon='mdi-eye-off'
@click="
toggleIgnore('municipality', event.danceHall.municipality)
"
>
mdi-eye-off
</v-icon>
/>
{{ event.danceHall.municipality }}
</v-col>
<v-col
@@ -91,16 +87,15 @@
xs="12"
sm="6"
>
<strong class="mr-1" v-text="$t('events.state')" />
<strong class="mr-1" v-text="t('events.state')" />
<v-icon
v-if="hasUser"
class="ml-1 mr-1 text-medium-emphasis"
size="small"
:title="$t('events.hide')"
:title="t('events.hide')"
icon='mdi-eye-off'
@click="toggleIgnore('state', event.danceHall.state)"
>
mdi-eye-off
</v-icon>
/>
{{ event.danceHall.state }}
</v-col>
</v-row>
@@ -112,12 +107,12 @@
<script setup lang='ts'>
import { format, formatDistanceToNow, parseISO } from 'date-fns'
import { enGB, sv } from 'date-fns/locale'
import { computed, type PropType } from 'vue'
import { useI18n } from '#i18n'
import type { Event } from '~/graphql/generated/operations'
import DistanceDisplay from '~/components/pages/events/event-distance.vue'
import type { Event } from '~/graphql/generated/operations'
const props = defineProps({
event: {
@@ -133,7 +128,7 @@ const props = defineProps({
required: true,
},
})
const { locale: currentLocale } = useI18n()
const { t, locale: currentLocale } = useI18n()
const locale = computed(() => (currentLocale.value ?? 'sv') === 'en' ? enGB : sv)
const time = computed(() => (props.event.time || '').split('-')[0].replace('.', ':'))
const weekday = computed(() => format(parseISO(props.event.date), 'EEEE', { locale: locale.value }))
+7 -12
View File
@@ -1,19 +1,13 @@
<template>
<v-row dense>
<v-col cols="12" sm="12" md="6">
<v-icon class="me-1">
mdi-home
</v-icon>
<v-icon class="me-1" icon='mdi-home' />
<span><strong>{{ distance.origin }}</strong></span>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-icon class="me-1">
mdi-car
</v-icon>
<span>{{ numericDistance }} km</span>
<v-icon class="ms-2 me-1">
mdi-clock-outline
</v-icon>
<v-icon class="me-1" icon='mdi-car' />
<span>{{ numericDistance }}</span>
<v-icon class="ms-2 me-1" icon='mdi-clock-outline' />
<span>{{ distance.duration }}</span>
</v-col>
</v-row>
@@ -21,6 +15,7 @@
<script setup lang='ts'>
import { computed, type PropType } from 'vue'
import type { DanceHallDistance } from '~/graphql/generated/operations'
const props = defineProps({
@@ -30,8 +25,8 @@ const props = defineProps({
},
})
const numericDistance = computed(() =>
Number(props.distance.distance / 1000).toLocaleString('sv-SE', {
`${Number(props.distance.distance / 1000).toLocaleString('sv-SE', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}))
})} km`)
</script>
+3 -1
View File
@@ -14,9 +14,11 @@
<script setup lang='ts'>
import type { PropType } from 'vue'
import EventCard from './event-card.vue'
import type { Event } from '~/graphql/generated/operations'
import EventCard from './event-card.vue'
defineProps({
hasUser: {
type: Boolean,
+17 -17
View File
@@ -3,7 +3,7 @@
<v-container :key="range" fluid grid-list-md class="app-fade-in">
<v-row v-if="!isAuthenticated" wrap>
<v-col xs="12">
<p><b v-text="$t('events.login')" /></p>
<p><b v-text="t('events.login')" /></p>
</v-col>
</v-row>
<v-row wrap>
@@ -12,34 +12,32 @@
v-model="origin"
variant="underlined"
hide-details
:label="$t('origins.origin')"
:placeholder="$t('origins.geolocation')"
:label="t('origins.origin')"
:placeholder="t('origins.geolocation')"
>
<template #append>
<v-tooltip top>
<template #activator="{ props }">
<v-icon
icon='mdi-crosshairs-gps'
v-bind="props"
@click="fetchAddressFn()"
>
mdi-crosshairs-gps
</v-icon>
/>
</template>
<span v-text="$t('origins.fetchAddress')" />
<span v-text="t('origins.fetchAddress')" />
</v-tooltip>
</template>
<template #prepend>
<v-tooltip v-if="isAuthenticated" top>
<template #activator="{ props }">
<v-icon
icon='mdi-bookmark-plus-outline'
:disabled="!origin"
v-bind="props"
@click="saveOriginFn(origin)"
>
mdi-bookmark-plus-outline
</v-icon>
/>
</template>
<span v-text="$t('origins.save')" />
<span v-text="t('origins.save')" />
</v-tooltip>
</template>
</v-text-field>
@@ -53,7 +51,7 @@
mandatory
>
<v-btn v-for="r in ranges" :key="r.value" variant="outlined" :value="r.value">
<span v-text="$t(`events.range.${r.value}`)" />
<span v-text="t(`events.range.${r.value}`)" />
</v-btn>
</v-btn-toggle>
<v-select
@@ -73,13 +71,13 @@
v-model="state.search"
variant="underlined"
append-outer-icon="mdi-magnify"
:label="$t('events.filter')"
:placeholder="$t('events.filter')"
:label="t('events.filter')"
:placeholder="t('events.filter')"
hide-details
/>
</v-col>
<v-col cols="12" sm="4">
<v-checkbox v-model="state.includeHidden" :label="$t('events.includeHidden')" hide-details />
<v-checkbox v-model="state.includeHidden" :label="t('events.includeHidden')" hide-details />
</v-col>
</v-row>
<event-list
@@ -99,11 +97,11 @@
</template>
<script setup lang='ts'>
import { computed, ref, watch } from 'vue'
import { useAuth0 } from '@auth0/auth0-vue'
import { computed, ref, watch } from 'vue'
import { useDisplay } from 'vuetify'
import { useI18n } from '#i18n'
import EventList from './event-list.vue'
import {
type FindEventsQueryVariables,
useFetchAddressLazyQuery,
@@ -117,6 +115,8 @@ import {
} from '~/graphql/generated/operations'
import { useState } from '~/store'
import EventList from './event-list.vue'
const state = useState()
const { smAndUp } = useDisplay()
const range = computed(() => state.range)
+6 -6
View File
@@ -1,7 +1,7 @@
<template>
<v-card flat variant="outlined" rounded="xl" class="mx-3 my-3">
<v-card-title>
<span v-text="$t(title, model.length)" />
<span v-text="t(titleKey, model.length)" />
</v-card-title>
<v-list>
<v-list-item v-for="item in model" :key="item" :title="item">
@@ -9,11 +9,9 @@
<v-list-item-action @click="toggleIgnore(type, item)">
<v-tooltip top>
<template #activator="{ props }">
<v-icon v-bind="props">
mdi-delete-outline
</v-icon>
<v-icon icon='mdi-delete-outline' v-bind="props" />
</template>
<span v-text="$t('filters.remove')" />
<span v-text="t('filters.remove')" />
</v-tooltip>
</v-list-item-action>
</template>
@@ -28,7 +26,7 @@ defineProps({
type: Array,
required: true,
},
title: {
titleKey: {
type: String,
required: true,
},
@@ -41,4 +39,6 @@ defineProps({
required: true,
},
})
const { t } = useI18n()
</script>
+9 -7
View File
@@ -9,7 +9,7 @@
<v-col xs="12" sm="12" md="4" lg="4">
<list
:model="bands || []"
title="filters.band"
title-key="filters.band"
type="band"
:toggle-ignore="toggleIgnore"
/>
@@ -19,25 +19,25 @@
<v-col cols="12">
<list
:model="states || []"
title="filters.state"
title-key="filters.state"
type="state"
:toggle-ignore="toggleIgnore"
/>
<list
:model="municipalities || []"
title="filters.municipality"
title-key="filters.municipality"
type="municipality"
:toggle-ignore="toggleIgnore"
/>
<list
:model="cities || []"
title="filters.city"
title-key="filters.city"
type="city"
:toggle-ignore="toggleIgnore"
/>
<list
:model="danceHalls || []"
title="filters.hall"
title-key="filters.hall"
type="danceHall"
:toggle-ignore="toggleIgnore"
/>
@@ -61,10 +61,10 @@
</template>
<script setup lang='ts'>
import { computed, ref } from 'vue'
import { useAuth0 } from '@auth0/auth0-vue'
import { computed, ref } from 'vue'
import { useI18n } from '#i18n'
import List from './filter-list.vue'
import {
useFetchFiltersQuery,
useToggleIgnoreBandMutation,
@@ -75,6 +75,8 @@ import {
} from '~/graphql/generated/operations'
import { useState } from '~/store'
import List from './filter-list.vue'
const state = useState()
const { t } = useI18n()
state.setTitle(t('app.links.filters'))
+13 -15
View File
@@ -6,34 +6,32 @@
<v-text-field
v-model="origin"
variant="underlined"
:label="$t('origins.origin')"
:placeholder="$t('origins.geolocation')"
:label="t('origins.origin')"
:placeholder="t('origins.geolocation')"
>
<template #append>
<v-tooltip top>
<template #activator="{ props }">
<v-icon
icon='mdi-crosshairs-gps'
v-bind="props"
@click="fetchAddressFn()"
>
mdi-crosshairs-gps
</v-icon>
/>
</template>
<span v-text="$t('origins.fetchAddress')" />
<span v-text="t('origins.fetchAddress')" />
</v-tooltip>
</template>
<template #prepend>
<v-tooltip top>
<template #activator="{ props }">
<v-icon
icon='mdi-bookmark-plus-outline'
:disabled="!origin"
v-bind="props"
@click="saveOriginFn(origin)"
>
mdi-bookmark-plus-outline
</v-icon>
/>
</template>
<span v-text="$t('origins.save')" />
<span v-text="t('origins.save')" />
</v-tooltip>
</template>
</v-text-field>
@@ -44,13 +42,12 @@
<v-tooltip top>
<template #activator="{ props }">
<v-icon
icon='mdi-delete-outline'
v-bind="props"
@click="removeOriginFn(o)"
>
mdi-delete-outline
</v-icon>
/>
</template>
<span v-text="$t('origins.remove')" />
<span v-text="t('origins.remove')" />
</v-tooltip>
<span>{{ o }}</span>
</v-col>
@@ -67,8 +64,9 @@
</template>
<script setup lang='ts'>
import { computed, ref } from 'vue'
import { useAuth0 } from '@auth0/auth0-vue'
import { computed, ref } from 'vue'
import { useI18n } from '#i18n'
import {
useFetchAddressLazyQuery,
+83
View File
@@ -1,4 +1,87 @@
// @ts-check
import stylistic from '@stylistic/eslint-plugin'
import parserVue from 'vue-eslint-parser'
import simpleImportSort from "eslint-plugin-simple-import-sort"
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(
{
files: ['**/*.vue', '**/*.ts', '**/*.js'],
plugins: {
'@stylistic': stylistic,
"simple-import-sort": simpleImportSort,
},
languageOptions: {
parser: parserVue,
},
rules: {
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
'@stylistic/no-multiple-empty-lines': 'error',
'@stylistic/comma-dangle': ['error', {
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'never',
functions: 'always-multiline',
}],
'@stylistic/block-spacing': ['error'],
'@stylistic/object-curly-spacing': ['error', 'always'],
'@stylistic/indent': ['error', 2],
'@stylistic/quotes': ['error', 'single'],
'@stylistic/semi': ['error', 'never'],
'@stylistic/member-delimiter-style': [
'error',
{
multiline: {
delimiter: 'none',
requireLast: true,
},
singleline: {
delimiter: 'semi',
requireLast: false,
},
multilineDetection: 'brackets',
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
"vue/no-bare-strings-in-template": ["error", {
"allowlist": [
"(", ")", ",", ".", "&", "+", "-", "=", "*", "/", "#", "%", "!", "?", ":", "[", "]", "{", "}", "<", ">", "\u00b7", "\u2022", "\u2010", "\u2013", "\u2014", "\u2212", "|"
],
"attributes": {
"/.+/": ["title", "label", "aria-label", "aria-placeholder", "aria-roledescription", "aria-valuetext"],
"input": ["placeholder"],
"img": ["alt"],
"inplace-edit-textfield": ["empty-text"],
"inplace-edit-textarea": ["empty-text"]
},
"directives": ["v-text"]
}],
"vue/no-useless-v-bind": ["error", {
"ignoreIncludesComment": false,
"ignoreStringEscape": false
}],
"vue/no-useless-mustaches": ["error", {
"ignoreIncludesComment": false,
"ignoreStringEscape": false
}],
"vue/html-closing-bracket-spacing": ["error", {
"startTag": "never",
"endTag": "never",
"selfClosingTag": "always"
}],
},
},
)
+139 -138
View File
@@ -1,153 +1,154 @@
import * as VueApolloComposable from '@vue/apollo-composable'
import type * as VueCompositionApi from 'vue'
import { gql } from '@/utils/gql'
export type Maybe<T> = T | null | undefined;
export type InputMaybe<T> = T | null | undefined;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
export type ReactiveFunction<TParam> = () => TParam;
export type Maybe<T> = T | null | undefined
export type InputMaybe<T> = T | null | undefined
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> }
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> }
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never }
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }
export type ReactiveFunction<TParam> = () => TParam
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
LocalDate: { input: string; output: string; }
LocalDateTime: { input: string; output: string; }
};
ID: { input: string; output: string }
String: { input: string; output: string }
Boolean: { input: boolean; output: boolean }
Int: { input: number; output: number }
Float: { input: number; output: number }
LocalDate: { input: string; output: string }
LocalDateTime: { input: string; output: string }
}
/** Band */
export type Band = {
__typename?: 'Band';
created: Scalars['LocalDateTime']['output'];
id: Maybe<Scalars['Int']['output']>;
name: Scalars['String']['output'];
};
__typename?: 'Band'
created: Scalars['LocalDateTime']['output']
id: Maybe<Scalars['Int']['output']>
name: Scalars['String']['output']
}
/** DanceHall */
export type DanceHall = {
__typename?: 'DanceHall';
city: Maybe<Scalars['String']['output']>;
created: Scalars['LocalDateTime']['output'];
id: Maybe<Scalars['Int']['output']>;
latitude: Maybe<Scalars['Float']['output']>;
longitude: Maybe<Scalars['Float']['output']>;
municipality: Maybe<Scalars['String']['output']>;
name: Maybe<Scalars['String']['output']>;
state: Maybe<Scalars['String']['output']>;
};
__typename?: 'DanceHall'
city: Maybe<Scalars['String']['output']>
created: Scalars['LocalDateTime']['output']
id: Maybe<Scalars['Int']['output']>
latitude: Maybe<Scalars['Float']['output']>
longitude: Maybe<Scalars['Float']['output']>
municipality: Maybe<Scalars['String']['output']>
name: Maybe<Scalars['String']['output']>
state: Maybe<Scalars['String']['output']>
}
/** DanceHallDistance */
export type DanceHallDistance = {
__typename?: 'DanceHallDistance';
distance: Scalars['Int']['output'];
duration: Scalars['String']['output'];
origin: Scalars['String']['output'];
};
__typename?: 'DanceHallDistance'
distance: Scalars['Int']['output']
duration: Scalars['String']['output']
origin: Scalars['String']['output']
}
/** Event */
export type Event = {
__typename?: 'Event';
__typename?: 'Event'
/** The band of the event */
band: Maybe<Band>;
created: Scalars['LocalDateTime']['output'];
band: Maybe<Band>
created: Scalars['LocalDateTime']['output']
/** The place of the event */
danceHall: Maybe<DanceHall>;
danceHall: Maybe<DanceHall>
/** The date of the event */
date: Scalars['LocalDate']['output'];
date: Scalars['LocalDate']['output']
/** The driving distances and driving durations to the event from the provided origins */
distances: Array<DanceHallDistance>;
distances: Array<DanceHallDistance>
/** Additional information regarding the event */
extraInfo: Maybe<Scalars['String']['output']>;
id: Maybe<Scalars['Int']['output']>;
extraInfo: Maybe<Scalars['String']['output']>
id: Maybe<Scalars['Int']['output']>
/** The time of the event */
time: Maybe<Scalars['String']['output']>;
};
time: Maybe<Scalars['String']['output']>
}
export type Mutation = {
__typename?: 'Mutation';
__typename?: 'Mutation'
/** Remove provided origin from authenticated user */
RemoveOrigin: Scalars['Boolean']['output'];
RemoveOrigin: Scalars['Boolean']['output']
/** Save provided origin for authenticated user */
SaveOrigin: Scalars['Boolean']['output'];
SaveOrigin: Scalars['Boolean']['output']
/** Toggle band in ignore list */
ToggleIgnoreBand: Scalars['Boolean']['output'];
ToggleIgnoreBand: Scalars['Boolean']['output']
/** Toggle city in ignore list */
ToggleIgnoreCity: Scalars['Boolean']['output'];
ToggleIgnoreCity: Scalars['Boolean']['output']
/** Toggle dance hall in ignore list */
ToggleIgnoreDanceHall: Scalars['Boolean']['output'];
ToggleIgnoreDanceHall: Scalars['Boolean']['output']
/** Toggle municipality in ignore list */
ToggleIgnoreMunicipality: Scalars['Boolean']['output'];
ToggleIgnoreMunicipality: Scalars['Boolean']['output']
/** Toggle state in ignore list */
ToggleIgnoreState: Scalars['Boolean']['output'];
};
ToggleIgnoreState: Scalars['Boolean']['output']
}
export type MutationRemoveOriginArgs = {
origin: Scalars['String']['input'];
};
origin: Scalars['String']['input']
}
export type MutationSaveOriginArgs = {
origin: Scalars['String']['input'];
};
origin: Scalars['String']['input']
}
export type MutationToggleIgnoreBandArgs = {
name: Scalars['String']['input'];
};
name: Scalars['String']['input']
}
export type MutationToggleIgnoreCityArgs = {
name: Scalars['String']['input'];
};
name: Scalars['String']['input']
}
export type MutationToggleIgnoreDanceHallArgs = {
name: Scalars['String']['input'];
};
name: Scalars['String']['input']
}
export type MutationToggleIgnoreMunicipalityArgs = {
name: Scalars['String']['input'];
};
name: Scalars['String']['input']
}
export type MutationToggleIgnoreStateArgs = {
name: Scalars['String']['input'];
};
name: Scalars['String']['input']
}
export type Query = {
__typename?: 'Query';
__typename?: 'Query'
/** Fetch address for provided lat/long */
AddressFromLatLng: Scalars['String']['output'];
AddressFromLatLng: Scalars['String']['output']
/** Find bands given provided criteria */
Bands: Array<Band>;
Bands: Array<Band>
/** Find dance halls given provided criteria */
DanceHalls: Array<DanceHall>;
DanceHalls: Array<DanceHall>
/** Find events given provided criteria */
Events: Array<Event>;
Events: Array<Event>
/** Fetch ignored bands for authenticated user */
IgnoredBands: Array<Scalars['String']['output']>;
IgnoredBands: Array<Scalars['String']['output']>
/** Fetch ignored cities for authenticated user */
IgnoredCities: Array<Scalars['String']['output']>;
IgnoredCities: Array<Scalars['String']['output']>
/** Fetch ignored dance halls for authenticated user */
IgnoredDanceHalls: Array<Scalars['String']['output']>;
IgnoredDanceHalls: Array<Scalars['String']['output']>
/** Fetch ignored municipalities for authenticated user */
IgnoredMunicipalities: Array<Scalars['String']['output']>;
IgnoredMunicipalities: Array<Scalars['String']['output']>
/** Fetch ignored states for authenticated user */
IgnoredStates: Array<Scalars['String']['output']>;
IgnoredStates: Array<Scalars['String']['output']>
/** Fetch origins for authenticated user */
Origins: Array<Scalars['String']['output']>;
};
Origins: Array<Scalars['String']['output']>
}
export type QueryAddressFromLatLngArgs = {
latlng: Scalars['String']['input'];
};
latlng: Scalars['String']['input']
}
export type QueryEventsArgs = {
includeHidden?: InputMaybe<Scalars['Boolean']['input']>;
origins?: InputMaybe<Array<Scalars['String']['input']>>;
range?: InputMaybe<Range>;
search?: InputMaybe<Scalars['String']['input']>;
};
includeHidden?: InputMaybe<Scalars['Boolean']['input']>
origins?: InputMaybe<Array<Scalars['String']['input']>>
range?: InputMaybe<Range>
search?: InputMaybe<Scalars['String']['input']>
}
export enum Range {
OneMonth = 'ONE_MONTH',
@@ -158,70 +159,70 @@ export enum Range {
}
export type RemoveOriginMutationVariables = Exact<{
origin: Scalars['String']['input'];
}>;
origin: Scalars['String']['input']
}>
export type RemoveOriginMutation = { __typename?: 'Mutation', removed: boolean };
export type RemoveOriginMutation = { __typename?: 'Mutation'; removed: boolean }
export type SaveOriginMutationVariables = Exact<{
origin: Scalars['String']['input'];
}>;
origin: Scalars['String']['input']
}>
export type SaveOriginMutation = { __typename?: 'Mutation', saved: boolean };
export type SaveOriginMutation = { __typename?: 'Mutation'; saved: boolean }
export type ToggleIgnoreBandMutationVariables = Exact<{
name: Scalars['String']['input'];
}>;
name: Scalars['String']['input']
}>
export type ToggleIgnoreBandMutation = { __typename?: 'Mutation', ignore: boolean };
export type ToggleIgnoreBandMutation = { __typename?: 'Mutation'; ignore: boolean }
export type ToggleIgnoreCityMutationVariables = Exact<{
name: Scalars['String']['input'];
}>;
name: Scalars['String']['input']
}>
export type ToggleIgnoreCityMutation = { __typename?: 'Mutation', ignore: boolean };
export type ToggleIgnoreCityMutation = { __typename?: 'Mutation'; ignore: boolean }
export type ToggleIgnoreDanceHallMutationVariables = Exact<{
name: Scalars['String']['input'];
}>;
name: Scalars['String']['input']
}>
export type ToggleIgnoreDanceHallMutation = { __typename?: 'Mutation', ignore: boolean };
export type ToggleIgnoreDanceHallMutation = { __typename?: 'Mutation'; ignore: boolean }
export type ToggleIgnoreMunicipalityMutationVariables = Exact<{
name: Scalars['String']['input'];
}>;
name: Scalars['String']['input']
}>
export type ToggleIgnoreMunicipalityMutation = { __typename?: 'Mutation', ignore: boolean };
export type ToggleIgnoreMunicipalityMutation = { __typename?: 'Mutation'; ignore: boolean }
export type ToggleIgnoreStateMutationVariables = Exact<{
name: Scalars['String']['input'];
}>;
name: Scalars['String']['input']
}>
export type ToggleIgnoreStateMutation = { __typename?: 'Mutation', ignore: boolean };
export type ToggleIgnoreStateMutation = { __typename?: 'Mutation'; ignore: boolean }
export type FetchAddressQueryVariables = Exact<{
latlng: Scalars['String']['input'];
}>;
latlng: Scalars['String']['input']
}>
export type FetchAddressQuery = { __typename?: 'Query', address: string };
export type FetchAddressQuery = { __typename?: 'Query'; address: string }
export type FetchFiltersQueryVariables = Exact<{ [key: string]: never; }>;
export type FetchFiltersQueryVariables = Exact<{ [key: string]: never }>
export type FetchFiltersQuery = { __typename?: 'Query', bands: Array<string>, cities: Array<string>, states: Array<string>, danceHalls: Array<string>, municipalities: Array<string> };
export type FetchFiltersQuery = { __typename?: 'Query'; bands: Array<string>; cities: Array<string>; states: Array<string>; danceHalls: Array<string>; municipalities: Array<string> }
export type FindEventsQueryVariables = Exact<{
range?: InputMaybe<Range>;
origins?: InputMaybe<Array<Scalars['String']['input']> | Scalars['String']['input']>;
includeOrigins: Scalars['Boolean']['input'];
search?: InputMaybe<Scalars['String']['input']>;
includeHidden?: InputMaybe<Scalars['Boolean']['input']>;
}>;
range?: InputMaybe<Range>
origins?: InputMaybe<Array<Scalars['String']['input']> | Scalars['String']['input']>
includeOrigins: Scalars['Boolean']['input']
search?: InputMaybe<Scalars['String']['input']>
includeHidden?: InputMaybe<Scalars['Boolean']['input']>
}>
export type FindEventsQuery = { __typename?: 'Query', origins: Array<string>, events: Array<{ __typename?: 'Event', date: string, time: string | null | undefined, extraInfo: string | null | undefined, band: { __typename?: 'Band', name: string } | null | undefined, danceHall: { __typename?: 'DanceHall', name: string | null | undefined, city: string | null | undefined, municipality: string | null | undefined, state: string | null | undefined } | null | undefined, distances: Array<{ __typename?: 'DanceHallDistance', origin: string, distance: number, duration: string }> }> };
export type FindEventsQuery = { __typename?: 'Query'; origins: Array<string>; events: Array<{ __typename?: 'Event'; date: string; time: string | null | undefined; extraInfo: string | null | undefined; band: { __typename?: 'Band'; name: string } | null | undefined; danceHall: { __typename?: 'DanceHall'; name: string | null | undefined; city: string | null | undefined; municipality: string | null | undefined; state: string | null | undefined } | null | undefined; distances: Array<{ __typename?: 'DanceHallDistance'; origin: string; distance: number; duration: string }> }> }
export type FindOriginsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindOriginsQueryVariables = Exact<{ [key: string]: never }>
export type FindOriginsQuery = { __typename?: 'Query', origins: Array<string> };
export type FindOriginsQuery = { __typename?: 'Query'; origins: Array<string> }
export const RemoveOriginDocument = gql`
mutation RemoveOrigin($origin: String!) {
@@ -249,7 +250,7 @@ export const RemoveOriginDocument = gql`
export function useRemoveOriginMutation(options: VueApolloComposable.UseMutationOptions<RemoveOriginMutation, RemoveOriginMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<RemoveOriginMutation, RemoveOriginMutationVariables>> = {}) {
return VueApolloComposable.useMutation<RemoveOriginMutation, RemoveOriginMutationVariables>(RemoveOriginDocument, options)
}
export type RemoveOriginMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<RemoveOriginMutation, RemoveOriginMutationVariables>;
export type RemoveOriginMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<RemoveOriginMutation, RemoveOriginMutationVariables>
export const SaveOriginDocument = gql`
mutation SaveOrigin($origin: String!) {
saved: SaveOrigin(origin: $origin)
@@ -276,7 +277,7 @@ export const SaveOriginDocument = gql`
export function useSaveOriginMutation(options: VueApolloComposable.UseMutationOptions<SaveOriginMutation, SaveOriginMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<SaveOriginMutation, SaveOriginMutationVariables>> = {}) {
return VueApolloComposable.useMutation<SaveOriginMutation, SaveOriginMutationVariables>(SaveOriginDocument, options)
}
export type SaveOriginMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<SaveOriginMutation, SaveOriginMutationVariables>;
export type SaveOriginMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<SaveOriginMutation, SaveOriginMutationVariables>
export const ToggleIgnoreBandDocument = gql`
mutation ToggleIgnoreBand($name: String!) {
ignore: ToggleIgnoreBand(name: $name)
@@ -303,7 +304,7 @@ export const ToggleIgnoreBandDocument = gql`
export function useToggleIgnoreBandMutation(options: VueApolloComposable.UseMutationOptions<ToggleIgnoreBandMutation, ToggleIgnoreBandMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<ToggleIgnoreBandMutation, ToggleIgnoreBandMutationVariables>> = {}) {
return VueApolloComposable.useMutation<ToggleIgnoreBandMutation, ToggleIgnoreBandMutationVariables>(ToggleIgnoreBandDocument, options)
}
export type ToggleIgnoreBandMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreBandMutation, ToggleIgnoreBandMutationVariables>;
export type ToggleIgnoreBandMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreBandMutation, ToggleIgnoreBandMutationVariables>
export const ToggleIgnoreCityDocument = gql`
mutation ToggleIgnoreCity($name: String!) {
ignore: ToggleIgnoreCity(name: $name)
@@ -330,7 +331,7 @@ export const ToggleIgnoreCityDocument = gql`
export function useToggleIgnoreCityMutation(options: VueApolloComposable.UseMutationOptions<ToggleIgnoreCityMutation, ToggleIgnoreCityMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<ToggleIgnoreCityMutation, ToggleIgnoreCityMutationVariables>> = {}) {
return VueApolloComposable.useMutation<ToggleIgnoreCityMutation, ToggleIgnoreCityMutationVariables>(ToggleIgnoreCityDocument, options)
}
export type ToggleIgnoreCityMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreCityMutation, ToggleIgnoreCityMutationVariables>;
export type ToggleIgnoreCityMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreCityMutation, ToggleIgnoreCityMutationVariables>
export const ToggleIgnoreDanceHallDocument = gql`
mutation ToggleIgnoreDanceHall($name: String!) {
ignore: ToggleIgnoreDanceHall(name: $name)
@@ -357,7 +358,7 @@ export const ToggleIgnoreDanceHallDocument = gql`
export function useToggleIgnoreDanceHallMutation(options: VueApolloComposable.UseMutationOptions<ToggleIgnoreDanceHallMutation, ToggleIgnoreDanceHallMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<ToggleIgnoreDanceHallMutation, ToggleIgnoreDanceHallMutationVariables>> = {}) {
return VueApolloComposable.useMutation<ToggleIgnoreDanceHallMutation, ToggleIgnoreDanceHallMutationVariables>(ToggleIgnoreDanceHallDocument, options)
}
export type ToggleIgnoreDanceHallMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreDanceHallMutation, ToggleIgnoreDanceHallMutationVariables>;
export type ToggleIgnoreDanceHallMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreDanceHallMutation, ToggleIgnoreDanceHallMutationVariables>
export const ToggleIgnoreMunicipalityDocument = gql`
mutation ToggleIgnoreMunicipality($name: String!) {
ignore: ToggleIgnoreMunicipality(name: $name)
@@ -384,7 +385,7 @@ export const ToggleIgnoreMunicipalityDocument = gql`
export function useToggleIgnoreMunicipalityMutation(options: VueApolloComposable.UseMutationOptions<ToggleIgnoreMunicipalityMutation, ToggleIgnoreMunicipalityMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<ToggleIgnoreMunicipalityMutation, ToggleIgnoreMunicipalityMutationVariables>> = {}) {
return VueApolloComposable.useMutation<ToggleIgnoreMunicipalityMutation, ToggleIgnoreMunicipalityMutationVariables>(ToggleIgnoreMunicipalityDocument, options)
}
export type ToggleIgnoreMunicipalityMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreMunicipalityMutation, ToggleIgnoreMunicipalityMutationVariables>;
export type ToggleIgnoreMunicipalityMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreMunicipalityMutation, ToggleIgnoreMunicipalityMutationVariables>
export const ToggleIgnoreStateDocument = gql`
mutation ToggleIgnoreState($name: String!) {
ignore: ToggleIgnoreState(name: $name)
@@ -411,7 +412,7 @@ export const ToggleIgnoreStateDocument = gql`
export function useToggleIgnoreStateMutation(options: VueApolloComposable.UseMutationOptions<ToggleIgnoreStateMutation, ToggleIgnoreStateMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<ToggleIgnoreStateMutation, ToggleIgnoreStateMutationVariables>> = {}) {
return VueApolloComposable.useMutation<ToggleIgnoreStateMutation, ToggleIgnoreStateMutationVariables>(ToggleIgnoreStateDocument, options)
}
export type ToggleIgnoreStateMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreStateMutation, ToggleIgnoreStateMutationVariables>;
export type ToggleIgnoreStateMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ToggleIgnoreStateMutation, ToggleIgnoreStateMutationVariables>
export const FetchAddressDocument = gql`
query FetchAddress($latlng: String!) {
address: AddressFromLatLng(latlng: $latlng)
@@ -439,7 +440,7 @@ export function useFetchAddressQuery(variables: FetchAddressQueryVariables | Vue
export function useFetchAddressLazyQuery(variables?: FetchAddressQueryVariables | VueCompositionApi.Ref<FetchAddressQueryVariables> | ReactiveFunction<FetchAddressQueryVariables>, options: VueApolloComposable.UseQueryOptions<FetchAddressQuery, FetchAddressQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<FetchAddressQuery, FetchAddressQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<FetchAddressQuery, FetchAddressQueryVariables>> = {}) {
return VueApolloComposable.useLazyQuery<FetchAddressQuery, FetchAddressQueryVariables>(FetchAddressDocument, variables, options)
}
export type FetchAddressQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FetchAddressQuery, FetchAddressQueryVariables>;
export type FetchAddressQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FetchAddressQuery, FetchAddressQueryVariables>
export const FetchFiltersDocument = gql`
query FetchFilters {
bands: IgnoredBands
@@ -468,7 +469,7 @@ export function useFetchFiltersQuery(options: VueApolloComposable.UseQueryOption
export function useFetchFiltersLazyQuery(options: VueApolloComposable.UseQueryOptions<FetchFiltersQuery, FetchFiltersQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<FetchFiltersQuery, FetchFiltersQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<FetchFiltersQuery, FetchFiltersQueryVariables>> = {}) {
return VueApolloComposable.useLazyQuery<FetchFiltersQuery, FetchFiltersQueryVariables>(FetchFiltersDocument, {}, options)
}
export type FetchFiltersQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FetchFiltersQuery, FetchFiltersQueryVariables>;
export type FetchFiltersQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FetchFiltersQuery, FetchFiltersQueryVariables>
export const FindEventsDocument = gql`
query FindEvents($range: Range, $origins: [String!], $includeOrigins: Boolean!, $search: String, $includeHidden: Boolean) {
events: Events(
@@ -524,7 +525,7 @@ export function useFindEventsQuery(variables: FindEventsQueryVariables | VueComp
export function useFindEventsLazyQuery(variables?: FindEventsQueryVariables | VueCompositionApi.Ref<FindEventsQueryVariables> | ReactiveFunction<FindEventsQueryVariables>, options: VueApolloComposable.UseQueryOptions<FindEventsQuery, FindEventsQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<FindEventsQuery, FindEventsQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<FindEventsQuery, FindEventsQueryVariables>> = {}) {
return VueApolloComposable.useLazyQuery<FindEventsQuery, FindEventsQueryVariables>(FindEventsDocument, variables, options)
}
export type FindEventsQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FindEventsQuery, FindEventsQueryVariables>;
export type FindEventsQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FindEventsQuery, FindEventsQueryVariables>
export const FindOriginsDocument = gql`
query FindOrigins {
origins: Origins
@@ -549,13 +550,13 @@ export function useFindOriginsQuery(options: VueApolloComposable.UseQueryOptions
export function useFindOriginsLazyQuery(options: VueApolloComposable.UseQueryOptions<FindOriginsQuery, FindOriginsQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<FindOriginsQuery, FindOriginsQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<FindOriginsQuery, FindOriginsQueryVariables>> = {}) {
return VueApolloComposable.useLazyQuery<FindOriginsQuery, FindOriginsQueryVariables>(FindOriginsDocument, {}, options)
}
export type FindOriginsQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FindOriginsQuery, FindOriginsQueryVariables>;
export type FindOriginsQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<FindOriginsQuery, FindOriginsQueryVariables>
export interface PossibleTypesResultData {
possibleTypes: {
[key: string]: string[]
}
}
possibleTypes: {
[key: string]: string[]
}
}
const result: PossibleTypesResultData = {
possibleTypes: {},
}
+1
View File
@@ -1,4 +1,5 @@
import { defineI18nLocale } from '#i18n'
import app from './app-en'
import events from './events-en'
import filters from './filters-en'
+1
View File
@@ -1,4 +1,5 @@
import { defineI18nLocale } from '#i18n'
import app from './app-sv'
import events from './events-sv'
import filters from './filters-sv'
+38 -18
View File
@@ -1,42 +1,50 @@
<template>
<v-app :key="$i18n.locale + isAuthenticated">
<v-navigation-drawer v-model="nav" temporary app>
<v-app :key='locale + isAuthenticated'>
<v-navigation-drawer v-model='nav' temporary app>
<v-list dense>
<v-list-item v-if="isAuthenticated && user" :title="user.name" :prepend-avatar="user.picture" />
<v-list-item v-if='isAuthenticated && user' :title='user.name' :prepend-avatar='user.picture' />
<v-list-item>
<v-switch v-model="darkMode" color="primary" :label="$t('app.darkMode')" hide-details class="ms-1" />
<v-switch v-model='darkMode' color='primary' :label="t('app.darkMode')" hide-details class='ms-1' />
</v-list-item>
<v-list-item title="In English" :to="switchLocalePath('en')">
<!-- eslint-disable-next-line vue/no-bare-strings-in-template -->
<v-list-item title='In English' :to="switchLocalePath('en')">
<template #prepend>
<div class="d-flex d-inline-flex text-h5 me-8">
<!-- eslint-disable-next-line vue/no-bare-strings-in-template -->
<div class='d-flex d-inline-flex text-h5 me-8'>
🇬🇧
</div>
</template>
</v-list-item>
<v-list-item title="På Svenska" :to="switchLocalePath('sv')">
<!-- eslint-disable-next-line vue/no-bare-strings-in-template -->
<v-list-item title='På Svenska' :to="switchLocalePath('sv')">
<template #prepend>
<div class="d-flex d-inline-flex text-h5 me-8">
<!-- eslint-disable-next-line vue/no-bare-strings-in-template -->
<div class='d-flex d-inline-flex text-h5 me-8'>
🇸🇪
</div>
</template>
</v-list-item>
<v-list-item to="/" :title="$t('app.links.events')" prepend-icon="mdi-calendar-outline" />
<v-list-item v-if="isAuthenticated" to="/origins/" :title="$t('app.links.origins')" prepend-icon="mdi-home" />
<v-list-item v-if="isAuthenticated" to="/filters/" :title="$t('app.links.filters')" prepend-icon="mdi-magnify" />
<v-list-item v-if="!user" link :title="$t('app.login')" prepend-icon="mdi-login" @click="doLogin" />
<v-list-item v-if="isAuthenticated" link :title="$t('app.logout')" prepend-icon="mdi-logout" @click="doLogout" />
<v-list-item to='/' :title="t('app.links.events')" prepend-icon='mdi-calendar-outline' />
<v-list-item v-if='isAuthenticated' to='/origins/' :title="t('app.links.origins')" prepend-icon='mdi-home' />
<v-list-item
v-if='isAuthenticated' to='/filters/' :title="t('app.links.filters')"
prepend-icon='mdi-magnify' />
<v-list-item v-if='!user' link :title="t('app.login')" prepend-icon='mdi-login' @click='doLogin' />
<v-list-item
v-if='isAuthenticated' link :title="t('app.logout')" prepend-icon='mdi-logout'
@click='doLogout' />
</v-list>
</v-navigation-drawer>
<v-app-bar app scroll-off-screen>
<v-app-bar-nav-icon @click="nav = !nav" />
<v-toolbar-title :title="title" />
<v-app-bar-nav-icon @click='nav = !nav' />
<v-toolbar-title :title='title' />
<v-spacer />
<v-toolbar-items>
<v-list-item v-if="isAuthenticated && user" :prepend-avatar="user.picture" :title="user.name" />
<v-list-item v-if='isAuthenticated && user' :prepend-avatar='user.picture' :title='user.name' />
</v-toolbar-items>
</v-app-bar>
<v-main>
<v-container v-if="!isLoading" fluid>
<v-container v-if='!isLoading' fluid>
<slot />
</v-container>
</v-main>
@@ -44,12 +52,15 @@
</template>
<script setup lang='ts'>
import { useAuth0 } from '@auth0/auth0-vue'
import { computed, onMounted, ref } from 'vue'
import { useTheme } from 'vuetify'
import { useAuth0 } from '@auth0/auth0-vue'
import { useSwitchLocalePath } from '#i18n'
import { useState } from '~/store'
const { t, locale } = useI18n()
const { $faro } = useNuxtApp()
const switchLocalePath = useSwitchLocalePath()
const state = useState()
const { isLoading, isAuthenticated, user, loginWithRedirect, logout } =
@@ -88,4 +99,13 @@ const darkMode = computed<boolean>({
const nav = ref(false)
const title = computed(() => state.title)
watchEffect(() => {
if ($faro && !isLoading.value && user.value) {
$faro.api.setUser({
id: user.value.sub,
email: user.value.email,
username: user.value.preferred_username,
})
}
})
</script>
+1 -2
View File
@@ -1,5 +1,5 @@
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
import { defineNuxtConfig } from 'nuxt/config'
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
export default defineNuxtConfig({
build: {
@@ -47,7 +47,6 @@ export default defineNuxtConfig({
'pinia-plugin-persistedstate/nuxt',
'@nuxtjs/i18n',
'@nuxt/devtools',
'@sentry/nuxt/module',
],
piniaPersistedstate: {
+1533 -1645
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -27,10 +27,12 @@
"@nuxt/eslint": "1.4.1",
"@nuxtjs/eslint-module": "4.1.0",
"@nuxtjs/i18n": "9.5.5",
"@stylistic/eslint-plugin": "^4.4.1",
"@vue/test-utils": "2.4.6",
"esbuild": "0.25.5",
"eslint": "9.28.0",
"eslint-plugin-nuxt": "4.0.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-vue": "10.2.0",
"nuxt": "3.17.5",
"postcss-html": "1.8.0",
@@ -42,17 +44,18 @@
"typescript-eslint": "8.34.0",
"vite-plugin-vuetify": "2.1.1",
"vue": "3.5.16",
"vue-eslint-parser": "^10.1.3",
"vue-router": "4.5.1",
"vuetify": "3.8.9"
},
"dependencies": {
"@apollo/client": "3.13.8",
"@auth0/auth0-vue": "2.4.0",
"@grafana/faro-web-sdk": "^1.18.2",
"@grafana/faro-web-tracing": "^1.18.2",
"@mdi/font": "7.4.47",
"@pinia/nuxt": "0.11.1",
"@sentry/nuxt": "9.29.0",
"@vue/apollo-composable": "4.2.2",
"apollo-link-sentry": "4.3.0",
"date-fns": "4.1.0",
"graphql": "16.11.0",
"graphql-tag": "2.12.6",
+2
View File
@@ -4,7 +4,9 @@
<script setup lang='ts'>
import { useHead } from '@unhead/vue'
import { useI18n } from '#i18n'
import Filters from '../components/pages/filters/filter-page.vue'
const { t } = useI18n()
+1
View File
@@ -4,6 +4,7 @@
<script setup lang='ts'>
import { useHead } from '@unhead/vue'
import { useI18n } from '#i18n'
import Events from '~/components/pages/events/event-page.vue'
+2
View File
@@ -4,7 +4,9 @@
<script setup lang='ts'>
import { useHead } from '@unhead/vue'
import { useI18n } from '#i18n'
import Origins from '../components/pages/origins/origin-page.vue'
const { t } = useI18n()
+46
View File
@@ -0,0 +1,46 @@
import { FetchTransport, getWebInstrumentations, initializeFaro,InternalLoggerLevel } from '@grafana/faro-web-sdk'
import { TracingInstrumentation } from '@grafana/faro-web-tracing'
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'
import { envConfig } from '~/utils/environment'
const config = envConfig(window.location.hostname)
export default defineNuxtPlugin((nuxtApp) => {
if (config.grafanaUrl) {
const faro = initializeFaro({
app: {
name: 'Shiny Frontend',
version: '1.0.0',
environment: config.name,
},
sessionTracking: {
samplingRate: config.tracesSampleRate,
},
instrumentations: [
// Mandatory, omits default instrumentations otherwise.
...getWebInstrumentations(),
// Tracing package to get end-to-end visibility for HTTP requests.
new TracingInstrumentation({
instrumentations: [
new FetchInstrumentation({
ignoreUrls: [
config.grafanaUrl,
config.apiUrl,
new RegExp('.*/sw.js$'),
new RegExp('.*_payload.json.*'),
new RegExp(`https://${config.auth.domain}/*`),
],
}),
],
}),
],
internalLoggerLevel: InternalLoggerLevel.VERBOSE,
transports: [
new FetchTransport({ url: config.grafanaUrl }),
],
})
console.log(`initialized faro for ${config.grafanaUrl}, sample-rate ${config.tracesSampleRate * 100}%`)
nuxtApp.provide('faro', faro)
}
})
+26 -18
View File
@@ -1,18 +1,12 @@
import {
ApolloClient,
createHttpLink,
from,
InMemoryCache,
split,
} from '@apollo/client/core'
import { WebSocketLink } from '@apollo/client/link/ws'
import { ApolloClient, ApolloLink, createHttpLink, from, InMemoryCache, split } from '@apollo/client/core'
import { setContext } from '@apollo/client/link/context'
import { WebSocketLink } from '@apollo/client/link/ws'
import { getMainDefinition } from '@apollo/client/utilities'
import { SentryLink } from 'apollo-link-sentry'
import * as Sentry from '@sentry/browser'
import type { GetTokenSilentlyOptions } from '@auth0/auth0-spa-js'
import { DefaultApolloClient, provideApolloClient } from '@vue/apollo-composable'
import type { Auth0VueClient } from '@auth0/auth0-vue'
import { SpanKind, TraceFlags } from '@opentelemetry/api'
import { DefaultApolloClient, provideApolloClient } from '@vue/apollo-composable'
import { defineNuxtPlugin, useNuxtApp } from '#app'
import { envConfig } from '~/utils/environment'
@@ -27,12 +21,7 @@ const cache = new InMemoryCache({
const getToken = async (options: GetTokenSilentlyOptions) => {
const nuxtApp = useNuxtApp()
const auth0: Auth0VueClient = nuxtApp.$auth0 as Auth0VueClient
return await auth0.getAccessTokenSilently(options).catch((err) => {
Sentry.captureException(err, {
extra: {
function: 'getTokenSilently',
},
})
return await auth0.getAccessTokenSilently(options).catch(() => {
return undefined
})
}
@@ -63,9 +52,28 @@ const authLink = setContext(async (_, { headers }) => {
}))
})
const createSpanLink = new ApolloLink((operation, forward) => {
const nuxtApp = useNuxtApp()
if (nuxtApp.$faro) {
const { trace } = nuxtApp.$faro.api.getOTEL()
const span = trace.getTracer('default').startSpan(`gql.${operation.operationName}`, {
kind: SpanKind.INTERNAL, // 0: Internal, 1: Server, 2: Client, 3: Producer, 4: Consumer
})
const spanContext = span.spanContext()
const traceParent = '00' + '-' + spanContext.traceId + '-' + spanContext.spanId + '-0' + Number(spanContext.traceFlags || TraceFlags.NONE).toString(16)
operation.setContext({ span, headers: { ...operation.getContext().headers, 'traceparent': traceParent } })
return forward(operation).map((data) => {
span.end()
return data
})
}
return forward(operation)
})
const link =
from([
new SentryLink({}),
createSpanLink,
split(
({ query }) => {
const definition = getMainDefinition(query)
+1
View File
@@ -1,4 +1,5 @@
import { createAuth0 } from '@auth0/auth0-vue'
import { defineNuxtPlugin } from '#app'
import { envConfig } from '~/utils/environment'
-5
View File
@@ -1,5 +0,0 @@
import { createSentryPiniaPlugin } from '@sentry/vue'
export default defineNuxtPlugin(({ $pinia }) => {
$pinia.use(createSentryPiniaPlugin())
})
+4 -3
View File
@@ -1,9 +1,10 @@
import { createVuetify } from 'vuetify'
import { defineNuxtPlugin } from '#app'
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin((app) => {
const vuetify = createVuetify({
theme: {
+8
View File
@@ -2,5 +2,13 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"packageRules": [
{
"groupName": "Grafana Faro",
"matchPackageNames": [
"@grafana/faro-**"
]
}
]
}
-21
View File
@@ -1,21 +0,0 @@
import * as Sentry from '@sentry/nuxt';
import { usePinia } from '#imports'
import { envConfig } from '~/utils/environment'
const env = envConfig(window.location.hostname)
Sentry.init({
enabled: env.sentryEnabled,
dsn: 'https://da2e8d42185a4013909d49955432a116@o365290.ingest.sentry.io/5187660',
integrations: [
Sentry.browserTracingIntegration({ traceFetch: false }),
Sentry.replayIntegration(),
Sentry.piniaIntegration(usePinia(), {
/* optional Pinia plugin options */
}),
],
environment: env.name,
tracesSampleRate: env.tracesSampleRate,
replaysSessionSampleRate: env.replaysSessionSampleRate,
replaysOnErrorSampleRate: env.replaysOnErrorSampleRate,
})
+1
View File
@@ -1,5 +1,6 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { Range } from '~/graphql/generated/operations'
export const useState = defineStore(
+15
View File
@@ -0,0 +1,15 @@
import type { Faro } from '@grafana/faro-core'
declare module '#app' {
interface NuxtApp {
$faro: Faro
}
}
declare module 'vue' {
interface ComponentCustomProperties {
$faro: Faro
}
}
export {}
+3 -9
View File
@@ -5,10 +5,8 @@ interface EnvConfig {
apiUrl: string
auth: Auth0ClientOptions
dev: boolean
sentryEnabled: boolean
tracesSampleRate: number
replaysSessionSampleRate: number
replaysOnErrorSampleRate: number
grafanaUrl: string
}
export const envConfig = (host: string): EnvConfig => {
@@ -33,10 +31,8 @@ export const envConfig = (host: string): EnvConfig => {
apiUrl: 'http://localhost:6080/query',
auth: prod,
dev: true,
sentryEnabled: false,
tracesSampleRate: 1.0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
grafanaUrl: '',
}
case 'dancefinder.unbound.se':
return {
@@ -44,10 +40,8 @@ export const envConfig = (host: string): EnvConfig => {
apiUrl: 'https://dancefinder.unbound.se/query',
auth: prod,
dev: false,
sentryEnabled: true,
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
grafanaUrl: 'https://faro-collector-prod-eu-west-0.grafana.net/collect/532676b975308d70605d856635721a61',
}
default:
throw new Error(`unexpected host: ${host}`)