feat: add i18n support and implement Grafana plugin #2479

Merged
argoyle merged 1 commits from feat/i18n-support-grafana-plugin into main 2025-06-13 13:24:53 +00:00
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}`)