Skip to content

Commit cc6a18f

Browse files
committed
feat: analytics
1 parent dabad5c commit cc6a18f

File tree

9 files changed

+97
-6
lines changed

9 files changed

+97
-6
lines changed

frontend/app/api/generated.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1706,7 +1706,7 @@ export type StandingsUnitPageQuery = { __typename?: 'Query', myCurrentProject: {
17061706
export type GetMeQueryVariables = Exact<{ [key: string]: never; }>;
17071707

17081708

1709-
export type GetMeQuery = { __typename?: 'Query', me: { __typename?: 'User', id: string, name: string, email: string, image?: string | null, membersId: string, gender: Gender, birthdate: string, church: { __typename?: 'Church', id: string, name: string, country: string, category: ChurchCategory }, roles: Array<{ __typename?: 'UserRole', id: string, role: RoleType, scope?: { __typename?: 'RoleScope', id: string, type: ScopeType, church?: { __typename?: 'Church', id: string } | null, team?: { __typename?: 'Team', id: string } | null, project?: { __typename?: 'Project', id: string } | null } | null }> } };
1709+
export type GetMeQuery = { __typename?: 'Query', me: { __typename?: 'User', id: string, name: string, email: string, image?: string | null, membersId: string, gender: Gender, birthdate: string, age?: number | null, church: { __typename?: 'Church', id: string, name: string, country: string, category: ChurchCategory }, roles: Array<{ __typename?: 'UserRole', id: string, role: RoleType, scope?: { __typename?: 'RoleScope', id: string, type: ScopeType, church?: { __typename?: 'Church', id: string } | null, team?: { __typename?: 'Team', id: string } | null, project?: { __typename?: 'Project', id: string } | null } | null }> } };
17101710

17111711
export type DeleteAchievementMutationVariables = Exact<{
17121712
id: Scalars['ID']['input'];
@@ -2300,6 +2300,7 @@ export const GetMeDocument = gql`
23002300
}
23012301
gender
23022302
birthdate
2303+
age
23032304
roles {
23042305
id
23052306
role

frontend/app/composables/useAuth.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ gql(`
1414
}
1515
gender
1616
birthdate
17+
age
1718
roles {
1819
id
1920
role
@@ -36,6 +37,7 @@ gql(`
3637
`)
3738

3839
export function useAuth() {
40+
const { reset } = useAnalytics()
3941
const token = useCookie('token')
4042
const isLoading = useState('isLoading', () => true)
4143
const me = useState<GetMeQuery['me'] | null | undefined>('me', () => null)
@@ -75,6 +77,7 @@ export function useAuth() {
7577
me.value = newMe
7678
isLoading.value = false
7779
},
80+
{ immediate: true },
7881
)
7982

8083
const getAccessToken = async () => {
@@ -98,6 +101,7 @@ export function useAuth() {
98101
}
99102

100103
const config = useRuntimeConfig()
104+
const { track } = useAnalytics()
101105

102106
function loginWithRedirect() {
103107
return navigateTo(
@@ -108,6 +112,14 @@ export function useAuth() {
108112
)
109113
}
110114

115+
function logout() {
116+
track(AnalyticsEvent.LogoutCompleted)
117+
reset()
118+
token.value = null
119+
me.value = null
120+
return navigateTo('/')
121+
}
122+
111123
// Authorization
112124
const isSuperAdmin = computed(() => {
113125
return me.value?.roles.some((role) => role.role === RoleType.Superadmin)
@@ -133,6 +145,7 @@ export function useAuth() {
133145
getAccessToken,
134146
setAccessToken,
135147
loginWithRedirect,
148+
logout,
136149
isLoading,
137150
me,
138151
token,

frontend/app/pages/settings.vue

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ const { open: consentsOpen } = useConsentsDialog()
4040
</NuxtLink>
4141
</template>
4242

43-
<DesignPanel class="gap-list-section-inset flex flex-col">
43+
<DesignPanel
44+
class="gap-list-section-inset flex flex-col mb-list-section-gap"
45+
>
4446
<UDropdownMenu
4547
:ui="{
4648
content:
@@ -91,12 +93,26 @@ const { open: consentsOpen } = useConsentsDialog()
9193
</div>
9294
</UDropdownMenu>
9395
<hr class="border-border-default mx-3" />
96+
<button class="flex items-center justify-between gap-2.5 px-3 py-2">
97+
<p class="text-label">{{ $t('settings.notifications') }}</p>
98+
<DesignButton size="small" variant="secondary" class="grow-0">
99+
{{ $t('settings.notificationsEnabled') }}
100+
</DesignButton>
101+
</button>
102+
</DesignPanel>
103+
104+
<DesignPanel class="gap-list-section-inset flex flex-col">
105+
<button class="flex items-center justify-between gap-2.5 px-3 py-2 h-12">
106+
<p class="text-label">{{ $t('settings.addToHomeScreen') }}</p>
107+
<Icon name="lucide:chevron-right" class="size-6" />
108+
</button>
109+
<hr class="border-border-default mx-3" />
94110
<button
95-
class="flex items-center justify-between gap-2.5 px-3 py-2"
111+
class="flex items-center justify-between gap-2.5 px-3 py-2 h-12"
96112
@click="consentsOpen = true"
97113
>
98114
<p class="text-label">{{ $t('settings.consents') }}</p>
99-
<DesignIconButton icon="lucide:chevron-right" />
115+
<Icon name="lucide:chevron-right" class="size-6" />
100116
</button>
101117
</DesignPanel>
102118

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export default defineNuxtPlugin(() => {
2+
const { me } = useAuth()
3+
const { identify } = useAnalytics()
4+
5+
watch(
6+
() => me.value,
7+
(currentMe) => {
8+
if (currentMe) {
9+
hashUserId(currentMe.id).then((hashedId) => {
10+
identify(hashedId, {
11+
age_group: getAgeGroup(currentMe.age),
12+
gender: currentMe.gender,
13+
church_id: currentMe.church.id,
14+
church_name: currentMe.church.name,
15+
church_country: currentMe.church.country,
16+
})
17+
})
18+
}
19+
},
20+
{ immediate: true },
21+
)
22+
})

frontend/app/utils/analytics.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
export async function hashUserId(userId: string): Promise<string> {
2+
const encoder = new TextEncoder()
3+
const data = encoder.encode(userId)
4+
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
5+
const hashArray = Array.from(new Uint8Array(hashBuffer))
6+
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
7+
}
8+
19
export enum AnalyticsEvent {
210
LoginCompleted = 'login_completed',
311
LogoutCompleted = 'logout_completed',
@@ -11,3 +19,26 @@ export enum AnalyticsEvent {
1119
ConsentAccepted = 'consent_accepted',
1220
ConsentRejected = 'consent_rejected',
1321
}
22+
23+
export function getAgeGroup(age?: number | null) {
24+
if (typeof age != 'number') {
25+
return 'UNKNOWN'
26+
}
27+
if (age < 10) {
28+
return '< 10'
29+
} else if (age <= 12) {
30+
return '10 - 12'
31+
} else if (age <= 18) {
32+
return '13 - 18'
33+
} else if (age <= 25) {
34+
return '19 - 25'
35+
} else if (age <= 36) {
36+
return '26 - 36'
37+
} else if (age <= 50) {
38+
return '37 - 50'
39+
} else if (age <= 64) {
40+
return '51 - 64'
41+
} else {
42+
return '65+'
43+
}
44+
}

frontend/i18n/locales/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
"light": "Light",
2525
"dark": "Dark"
2626
},
27-
"consents": "Consents"
27+
"consents": "Consents",
28+
"notifications": "Notifications",
29+
"notificationsEnabled": "On",
30+
"notificationsDisabled": "Off",
31+
"addToHomeScreen": "Add app to home screen"
2832
},
2933
"standings": {
3034
"global": "Top 20",

frontend/i18n/locales/nb.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
"light": "Lys",
2525
"dark": "Mørk"
2626
},
27-
"consents": "Samtykke"
27+
"consents": "Samtykke",
28+
"notifications": "Varslinger",
29+
"notificationsEnabled": "På",
30+
"notificationsDisabled": "Av",
31+
"addToHomeScreen": "Legg til app på hjem skjerm"
2832
},
2933
"standings": {
3034
"global": "Topp 20",

0 commit comments

Comments
 (0)