@sbc-connect/nuxt-auth 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +25 -1
- package/CHANGELOG.md +34 -0
- package/app/app.config.ts +14 -0
- package/app/components/Connect/Header/AccountLabel.vue +35 -0
- package/app/components/Connect/Header/AccountOptionsMenu.vue +52 -0
- package/app/components/Connect/Header/Auth.vue +14 -0
- package/app/components/Connect/Header/AuthenticatedOptions.vue +13 -0
- package/app/components/Connect/Header/CreateAccountButton.vue +13 -0
- package/app/components/Connect/Header/LoginMenu.vue +27 -0
- package/app/components/Connect/Header/Notifications.vue +30 -0
- package/app/components/Connect/Header/UnauthenticatedOptions.vue +15 -0
- package/app/components/HelloWorld/Auth.vue +2 -2
- package/app/composables/useConnectAuth.ts +111 -0
- package/app/composables/useConnectHeaderOptions.ts +233 -0
- package/app/enums/account-status.ts +9 -0
- package/app/enums/account-type.ts +6 -0
- package/app/enums/connect-auth-storage-keys.ts +5 -0
- package/app/enums/connect-error-category.ts +6 -0
- package/app/enums/connect-idp-hint.ts +6 -0
- package/app/enums/connect-login-source.ts +6 -0
- package/app/enums/user-settings-type.ts +5 -0
- package/app/interfaces/connect-account.ts +10 -0
- package/app/interfaces/connect-api-error.ts +6 -0
- package/app/interfaces/connect-auth-user.ts +10 -0
- package/app/interfaces/connect-user-settings.ts +10 -0
- package/app/layouts/ConnectAuth.vue +8 -0
- package/app/middleware/01.setup-accounts.global.ts +13 -0
- package/app/middleware/02.keycloak-params.global.ts +15 -0
- package/app/plugins/auth-api.ts +28 -0
- package/app/plugins/connect-auth.client.ts +105 -0
- package/app/stores/connect-account.ts +236 -0
- package/app/types/auth-app-config.d.ts +18 -0
- package/i18n/locales/en-CA.ts +31 -0
- package/i18n/locales/fr-CA.ts +1 -0
- package/modules/auth-assets/index.ts +15 -0
- package/modules/auth-assets/runtime/assets/connect-auth-tw.css +2 -0
- package/nuxt.config.ts +53 -6
- package/package.json +11 -7
- package/app/assets/css/tw-auth.css +0 -2
- package/app.config.ts +0 -5
package/.env.example
CHANGED
|
@@ -1 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
# Web Urls
|
|
2
|
+
NUXT_PUBLIC_BASE_URL="http://localhost:3000/"
|
|
3
|
+
NUXT_PUBLIC_REGISTRY_HOME_URL="https://dev.bcregistry.gov.bc.ca/"
|
|
4
|
+
NUXT_PUBLIC_AUTH_WEB_URL="https://dev.account.bcregistry.gov.bc.ca/"
|
|
5
|
+
|
|
6
|
+
# API Key
|
|
7
|
+
NUXT_PUBLIC_X_API_KEY=""
|
|
8
|
+
|
|
9
|
+
# API Urls
|
|
10
|
+
# Status API
|
|
11
|
+
NUXT_PUBLIC_STATUS_API_URL="https://status-api-dev.apps.gold.devops.gov.bc.ca"
|
|
12
|
+
NUXT_PUBLIC_STATUS_API_VERSION="/api/v1"
|
|
13
|
+
|
|
14
|
+
# Auth API
|
|
15
|
+
NUXT_PUBLIC_AUTH_API_URL="https://test.api.connect.gov.bc.ca/auth-dev"
|
|
16
|
+
NUXT_PUBLIC_AUTH_API_VERSION="/api/v1"
|
|
17
|
+
|
|
18
|
+
# LaunchDarkly
|
|
19
|
+
NUXT_PUBLIC_LD_CLIENT_ID=""
|
|
20
|
+
|
|
21
|
+
# Identity Provider
|
|
22
|
+
NUXT_PUBLIC_IDP_URL="https://dev.loginproxy.gov.bc.ca/auth"
|
|
23
|
+
NUXT_PUBLIC_IDP_REALM="bcregistry"
|
|
24
|
+
NUXT_PUBLIC_IDP_CLIENTID="connect-web"
|
|
25
|
+
NUXT_PUBLIC_SITEMINDER_LOGOUT_URL="https://logontest7.gov.bc.ca/clp-cgi/logoff.cgi"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-auth
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#21](https://github.com/bcgov/connect-nuxt/pull/21) [`db2159e`](https://github.com/bcgov/connect-nuxt/commit/db2159ebc4b310c1c24986ca8ef85b5435fd50c8) Thanks [@deetz99](https://github.com/deetz99)! - Init auth plugin
|
|
8
|
+
|
|
9
|
+
- [#26](https://github.com/bcgov/connect-nuxt/pull/26) [`65ae301`](https://github.com/bcgov/connect-nuxt/commit/65ae301972b39cfed8550e49c1209133674528a4) Thanks [@deetz99](https://github.com/deetz99)! - Update CSS variables to match Nuxt UI naming convention. Issue: bcgov/entity#29335
|
|
10
|
+
|
|
11
|
+
- [#23](https://github.com/bcgov/connect-nuxt/pull/23) [`b257d0c`](https://github.com/bcgov/connect-nuxt/commit/b257d0c874138e56ae0b5d79ec6e5a7b30acec8b) Thanks [@deetz99](https://github.com/deetz99)! - - begin initial account store
|
|
12
|
+
|
|
13
|
+
- auth composable
|
|
14
|
+
- clear keycloak params in url
|
|
15
|
+
- init account in middleware
|
|
16
|
+
- several enums/types to support accounts
|
|
17
|
+
- update nuxt config with auto imports and pinia plugin module
|
|
18
|
+
|
|
19
|
+
issue: bcgov/entity#29335
|
|
20
|
+
|
|
21
|
+
- [#25](https://github.com/bcgov/connect-nuxt/pull/25) [`aba11d1`](https://github.com/bcgov/connect-nuxt/commit/aba11d1303ab1b19b3a51c27959766c4ee0cd5d8) Thanks [@deetz99](https://github.com/deetz99)! - - create auth header components
|
|
22
|
+
|
|
23
|
+
- connect-auth layout
|
|
24
|
+
|
|
25
|
+
issue: bcgov/entity#29335
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [[`65ae301`](https://github.com/bcgov/connect-nuxt/commit/65ae301972b39cfed8550e49c1209133674528a4), [`09fa4f1`](https://github.com/bcgov/connect-nuxt/commit/09fa4f1b4b2c65d189a6477c9c5f2d44607b543d), [`aba11d1`](https://github.com/bcgov/connect-nuxt/commit/aba11d1303ab1b19b3a51c27959766c4ee0cd5d8)]:
|
|
28
|
+
- @sbc-connect/nuxt-base@0.1.6
|
|
29
|
+
|
|
30
|
+
## 0.1.5
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [[`4231254`](https://github.com/bcgov/connect-nuxt/commit/42312540f5eec65f5d3979d5492bdfaa9bb0b079)]:
|
|
35
|
+
- @sbc-connect/nuxt-base@0.1.5
|
|
36
|
+
|
|
3
37
|
## 0.1.4
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps({
|
|
3
|
+
accountName: { default: 'N/A', type: String },
|
|
4
|
+
username: { default: 'N/A', type: String },
|
|
5
|
+
theme: { default: 'header', type: String } // header or dropdown
|
|
6
|
+
})
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div class="flex items-center gap-1">
|
|
11
|
+
<UAvatar
|
|
12
|
+
:text="username[0] ? username[0].toLocaleUpperCase($i18n.locale) : 'U'"
|
|
13
|
+
size="md"
|
|
14
|
+
class="text-inverted self-start sm:self-center"
|
|
15
|
+
:ui="{ root: 'bg-blue-300 rounded-none text-lg', fallback: 'text-inverted font-bold' }"
|
|
16
|
+
/>
|
|
17
|
+
<div class="mx-2 flex flex-col text-left font-normal tracking-wide">
|
|
18
|
+
<span
|
|
19
|
+
class="text-sm"
|
|
20
|
+
:class="{
|
|
21
|
+
'line-clamp-1 overflow-hidden text-ellipsis text-inverted max-w-64': theme === 'header',
|
|
22
|
+
'text-neutral-highlighted max-w-80': theme === 'dropdown',
|
|
23
|
+
}"
|
|
24
|
+
>
|
|
25
|
+
{{ username }}
|
|
26
|
+
</span>
|
|
27
|
+
<span
|
|
28
|
+
class="line-clamp-1 overflow-hidden text-ellipsis text-xs opacity-75"
|
|
29
|
+
:class="{ 'text-line': theme === 'header', 'text-neutral': theme === 'dropdown' }"
|
|
30
|
+
>
|
|
31
|
+
{{ accountName }}
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { loggedInUserOptions } = useConnectHeaderOptions()
|
|
3
|
+
const { authUser } = useConnectAuth()
|
|
4
|
+
const accountStore = useConnectAccountStore()
|
|
5
|
+
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<UDropdownMenu
|
|
10
|
+
id="account-options-menu"
|
|
11
|
+
:items="loggedInUserOptions"
|
|
12
|
+
:ui="{
|
|
13
|
+
content: 'max-w-[90dvw]',
|
|
14
|
+
}"
|
|
15
|
+
>
|
|
16
|
+
<UButton
|
|
17
|
+
id="account-options-button"
|
|
18
|
+
color="secondary"
|
|
19
|
+
variant="ghost"
|
|
20
|
+
:aria-label="$t('connect.label.accountOptionsMenu')"
|
|
21
|
+
:icon="isLargeScreen ? 'i-mdi-caret-down' : ''"
|
|
22
|
+
trailing
|
|
23
|
+
class="px-2 py-1"
|
|
24
|
+
>
|
|
25
|
+
<ConnectHeaderAccountLabel
|
|
26
|
+
v-if="isLargeScreen"
|
|
27
|
+
:username="parseSpecialCharacters(authUser.fullName, 'USER')"
|
|
28
|
+
:account-name="accountStore.currentAccount.label
|
|
29
|
+
? parseSpecialCharacters(accountStore.currentAccount.label, 'ACCOUNT')
|
|
30
|
+
: ''
|
|
31
|
+
"
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<UAvatar
|
|
35
|
+
v-if="!isLargeScreen"
|
|
36
|
+
:text="parseSpecialCharacters(authUser.fullName, 'U')[0]!.toUpperCase()"
|
|
37
|
+
size="md"
|
|
38
|
+
:ui="{ root: 'bg-blue-300 rounded-none text-lg', fallback: 'text-inverted font-bold' }"
|
|
39
|
+
/>
|
|
40
|
+
</UButton>
|
|
41
|
+
<template #account>
|
|
42
|
+
<ConnectHeaderAccountLabel
|
|
43
|
+
:username="parseSpecialCharacters(authUser.fullName, 'USER')"
|
|
44
|
+
:account-name="accountStore.currentAccount.label
|
|
45
|
+
? parseSpecialCharacters(accountStore.currentAccount.label, 'ACCOUNT')
|
|
46
|
+
: ''
|
|
47
|
+
"
|
|
48
|
+
theme="dropdown"
|
|
49
|
+
/>
|
|
50
|
+
</template>
|
|
51
|
+
</UDropdownMenu>
|
|
52
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const ac = useAppConfig().connect.header
|
|
3
|
+
const { isAuthenticated } = useConnectAuth()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<ConnectHeader>
|
|
8
|
+
<div class="flex gap-4">
|
|
9
|
+
<ConnectHeaderAuthenticatedOptions v-if="isAuthenticated" />
|
|
10
|
+
<ConnectHeaderUnauthenticatedOptions v-else />
|
|
11
|
+
<ConnectLocaleSelect v-if="ac.localeSelect" />
|
|
12
|
+
</div>
|
|
13
|
+
</ConnectHeader>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const ac = useAppConfig().connect.header
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div
|
|
7
|
+
id="connect-header-authenticated-options"
|
|
8
|
+
class="flex gap-4"
|
|
9
|
+
>
|
|
10
|
+
<ConnectHeaderNotifications v-if="ac.notifications" />
|
|
11
|
+
<ConnectHeaderAccountOptionsMenu v-if="ac.accountOptionsMenu" />
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { createAccountUrl } = useConnectHeaderOptions()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<UButton
|
|
7
|
+
variant="ghost"
|
|
8
|
+
color="secondary"
|
|
9
|
+
class="px-2 py-1 text-sm"
|
|
10
|
+
:label="$t('connect.label.createAccount')"
|
|
11
|
+
:to="createAccountUrl()"
|
|
12
|
+
/>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useStorage } from '@vueuse/core'
|
|
3
|
+
|
|
4
|
+
const { loggedOutUserOptions, loggedOutUserOptionsMobile } = useConnectHeaderOptions()
|
|
5
|
+
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
|
6
|
+
const whatsNew = useStorage<ConnectWhatsNewState>('connect-whats-new', { viewed: false, items: [] })
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<UDropdownMenu
|
|
11
|
+
id="login-menu"
|
|
12
|
+
:items="isLargeScreen ? loggedOutUserOptions : loggedOutUserOptionsMobile"
|
|
13
|
+
>
|
|
14
|
+
<UButton
|
|
15
|
+
variant="ghost"
|
|
16
|
+
color="secondary"
|
|
17
|
+
:label="isLargeScreen ? $t('connect.label.login') : undefined"
|
|
18
|
+
:aria-label="isLargeScreen ? $t('connect.label.selectLoginMethod') : $t('connect.label.mainMenu')"
|
|
19
|
+
:icon="isLargeScreen ? 'i-mdi-caret-down' : 'i-mdi-menu'"
|
|
20
|
+
trailing
|
|
21
|
+
class="px-2 py-1 text-sm"
|
|
22
|
+
/>
|
|
23
|
+
<template #whatsnew-trailing>
|
|
24
|
+
<span v-if="!whatsNew.viewed && whatsNew.items.length > 0" class="size-2 rounded-full bg-error" />
|
|
25
|
+
</template>
|
|
26
|
+
</UDropdownMenu>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { notificationsOptions } = useConnectHeaderOptions()
|
|
3
|
+
const accountStore = useConnectAccountStore()
|
|
4
|
+
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<UDropdownMenu :items="notificationsOptions">
|
|
9
|
+
<UChip
|
|
10
|
+
color="error"
|
|
11
|
+
position="top-left"
|
|
12
|
+
inset
|
|
13
|
+
:show="accountStore.pendingApprovalCount > 0"
|
|
14
|
+
>
|
|
15
|
+
<UButton
|
|
16
|
+
variant="ghost"
|
|
17
|
+
color="secondary"
|
|
18
|
+
:label="isLargeScreen ? $t('connect.label.notifications') : undefined"
|
|
19
|
+
:aria-label="$t('connect.label.notificationsAria', { count: accountStore.pendingApprovalCount })"
|
|
20
|
+
:icon="isLargeScreen ? 'i-mdi-caret-down' : ''"
|
|
21
|
+
trailing
|
|
22
|
+
class="px-2 py-1 text-sm"
|
|
23
|
+
>
|
|
24
|
+
<template #leading>
|
|
25
|
+
<UIcon name="i-mdi-bell-outline" class="size-6 shrink-0" />
|
|
26
|
+
</template>
|
|
27
|
+
</UButton>
|
|
28
|
+
</UChip>
|
|
29
|
+
</UDropdownMenu>
|
|
30
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const ac = useAppConfig().connect.header
|
|
3
|
+
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div
|
|
8
|
+
id="connect-header-unauthenticated-options"
|
|
9
|
+
class="flex gap-4"
|
|
10
|
+
>
|
|
11
|
+
<ConnectHeaderWhatsNew v-if="ac.whatsNew && isLargeScreen" />
|
|
12
|
+
<ConnectHeaderLoginMenu v-if="ac.loginMenu" />
|
|
13
|
+
<ConnectHeaderCreateAccountButton v-if="ac.createAccount && isLargeScreen" />
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export const useConnectAuth = () => {
|
|
2
|
+
const { $connectAuth } = useNuxtApp()
|
|
3
|
+
const rtc = useRuntimeConfig().public
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logs the user in using the idpHint 'bcsc', 'idir' or 'bceid' and an optional redirect URL.
|
|
7
|
+
* @param idpHint - The identity provider hint to use for login.
|
|
8
|
+
* @param redirect - An optional URL to redirect to after login. Defaults to location.origin + the current locale
|
|
9
|
+
* @returns A promise that resolves when login is complete.
|
|
10
|
+
*/
|
|
11
|
+
function login(idpHint: ConnectIdpHint, redirect?: string): Promise<void> {
|
|
12
|
+
const loginRedirectUrl = sessionStorage.getItem(ConnectAuthStorageKeys.LOGIN_REDIRECT_URL)
|
|
13
|
+
const redirectUri = redirect ?? loginRedirectUrl ?? window.location.href
|
|
14
|
+
|
|
15
|
+
return $connectAuth.login(
|
|
16
|
+
{
|
|
17
|
+
idpHint,
|
|
18
|
+
redirectUri
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Logs the user out with an optional redirect URL.
|
|
25
|
+
* @param redirect - An optional URL to redirect to after logout. Defaults to location.origin + the current locale
|
|
26
|
+
* @returns A promise that resolves when logout is complete.
|
|
27
|
+
*/
|
|
28
|
+
function logout(redirect?: string): Promise<void> {
|
|
29
|
+
const siteminderUrl = rtc.siteminderLogoutUrl
|
|
30
|
+
const logoutRedirectUrl = sessionStorage.getItem(ConnectAuthStorageKeys.LOGOUT_REDIRECT_URL)
|
|
31
|
+
let redirectUri = redirect ?? logoutRedirectUrl ?? window.location.href
|
|
32
|
+
|
|
33
|
+
if (siteminderUrl) {
|
|
34
|
+
redirectUri = `${siteminderUrl}?returl=${redirectUri.replace(/(https?:\/\/)|(\/)+/g, '$1$2')}&retnow=1`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// resetPiniaStores()
|
|
38
|
+
return $connectAuth.logout({
|
|
39
|
+
redirectUri
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const isAuthenticated = computed<boolean | undefined>(() => {
|
|
44
|
+
if (!$connectAuth) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
return $connectAuth.authenticated
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const authUser = computed<ConnectAuthUser>(() => {
|
|
51
|
+
if ($connectAuth && $connectAuth.tokenParsed) {
|
|
52
|
+
return {
|
|
53
|
+
firstName: $connectAuth.tokenParsed.firstname,
|
|
54
|
+
lastName: $connectAuth.tokenParsed.lastname,
|
|
55
|
+
fullName: $connectAuth.tokenParsed.name,
|
|
56
|
+
userName: $connectAuth.tokenParsed.username,
|
|
57
|
+
email: $connectAuth.tokenParsed.email,
|
|
58
|
+
keycloakGuid: $connectAuth.tokenParsed.sub || '',
|
|
59
|
+
loginSource: $connectAuth.tokenParsed.loginSource,
|
|
60
|
+
roles: $connectAuth.tokenParsed.realm_access?.roles || []
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {} as ConnectAuthUser
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves the current session token, with an optional force refresh.
|
|
68
|
+
* @param forceRefresh - A boolean to force a token refresh.
|
|
69
|
+
* @returns The session token or undefined if the token can't be retrieved.
|
|
70
|
+
*/
|
|
71
|
+
async function getToken(forceRefresh = false): Promise<string | undefined> {
|
|
72
|
+
const minValidity = forceRefresh ? -1 : 30
|
|
73
|
+
return await $connectAuth
|
|
74
|
+
.updateToken(minValidity)
|
|
75
|
+
.then((_refreshed) => {
|
|
76
|
+
return $connectAuth.token
|
|
77
|
+
})
|
|
78
|
+
.catch((error) => {
|
|
79
|
+
console.error(`Failed to get session token: ${error}`)
|
|
80
|
+
return undefined
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setLoginRedirectUrl(url: string) {
|
|
85
|
+
sessionStorage.setItem(ConnectAuthStorageKeys.LOGIN_REDIRECT_URL, url)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function setLogoutRedirectUrl(url: string) {
|
|
89
|
+
sessionStorage.setItem(ConnectAuthStorageKeys.LOGOUT_REDIRECT_URL, url)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function clearLoginRedirectUrl() {
|
|
93
|
+
sessionStorage.removeItem(ConnectAuthStorageKeys.LOGIN_REDIRECT_URL)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function clearLogoutRedirectUrl() {
|
|
97
|
+
sessionStorage.removeItem(ConnectAuthStorageKeys.LOGOUT_REDIRECT_URL)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
login,
|
|
102
|
+
logout,
|
|
103
|
+
getToken,
|
|
104
|
+
clearLoginRedirectUrl,
|
|
105
|
+
clearLogoutRedirectUrl,
|
|
106
|
+
setLoginRedirectUrl,
|
|
107
|
+
setLogoutRedirectUrl,
|
|
108
|
+
isAuthenticated,
|
|
109
|
+
authUser
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { DropdownMenuItem } from '@nuxt/ui'
|
|
2
|
+
import { useStorage } from '@vueuse/core'
|
|
3
|
+
import { ConnectSlideoverWhatsNew } from '#components'
|
|
4
|
+
|
|
5
|
+
// handle navigation items and functionality
|
|
6
|
+
export function useConnectHeaderOptions() {
|
|
7
|
+
const rtc = useRuntimeConfig().public
|
|
8
|
+
const authWebUrl = rtc.authWebUrl
|
|
9
|
+
const appBaseUrl = rtc.baseUrl
|
|
10
|
+
const ac = useAppConfig().connect
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
const overlay = useOverlay()
|
|
13
|
+
const { t, locale: { value: locale } } = useNuxtApp().$i18n
|
|
14
|
+
const { login, logout, isAuthenticated, authUser } = useConnectAuth()
|
|
15
|
+
const accountStore = useConnectAccountStore()
|
|
16
|
+
|
|
17
|
+
const whatsNew = useStorage<ConnectWhatsNewState>('connect-whats-new', { viewed: false, items: [] })
|
|
18
|
+
const slideover = overlay.create(ConnectSlideoverWhatsNew)
|
|
19
|
+
|
|
20
|
+
/** return the correct account creation link based on auth state */
|
|
21
|
+
function createAccountUrl(): string {
|
|
22
|
+
if (isAuthenticated.value) {
|
|
23
|
+
return authWebUrl + 'setup-account'
|
|
24
|
+
} else {
|
|
25
|
+
return authWebUrl + 'choose-authentication-method'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const basicAccountOptions = computed<DropdownMenuItem[]>(() => {
|
|
30
|
+
const options: DropdownMenuItem[] = [{ slot: 'account', type: 'label' }]
|
|
31
|
+
if ([ConnectLoginSource.BCEID, ConnectLoginSource.BCSC].includes(authUser?.value.loginSource)) {
|
|
32
|
+
options.push({
|
|
33
|
+
label: t('connect.label.editProfile'),
|
|
34
|
+
icon: 'i-mdi-account-outline',
|
|
35
|
+
to: authWebUrl + 'userprofile'
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
options.push({
|
|
39
|
+
label: t('connect.label.logout'),
|
|
40
|
+
icon: 'i-mdi-logout-variant',
|
|
41
|
+
onSelect: () => logout()
|
|
42
|
+
})
|
|
43
|
+
return options
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const accountSettingsOptions = computed<DropdownMenuItem[]>(() => {
|
|
47
|
+
const accountId = accountStore.currentAccount.id
|
|
48
|
+
const options: DropdownMenuItem[] = [
|
|
49
|
+
{
|
|
50
|
+
label: t('connect.label.accountSettings'),
|
|
51
|
+
type: 'label'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: t('connect.label.accountInfo'),
|
|
55
|
+
icon: 'i-mdi-information-outline',
|
|
56
|
+
to: authWebUrl + `account/${accountId}/settings/account-info`
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: t('connect.label.teamMembers'),
|
|
60
|
+
icon: 'i-mdi-account-group-outline',
|
|
61
|
+
to: authWebUrl + `account/${accountId}/settings/team-members`
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
// TODO: remove staff checks?
|
|
65
|
+
if (
|
|
66
|
+
[AccountType.PREMIUM, AccountType.SBC_STAFF, AccountType.STAFF].includes(accountStore.currentAccount.accountType)
|
|
67
|
+
) {
|
|
68
|
+
options.push({
|
|
69
|
+
label: t('connect.label.transactions'),
|
|
70
|
+
icon: 'i-mdi-file-document-outline',
|
|
71
|
+
to: authWebUrl + `account/${accountId}/settings/transactions`
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
return options
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const switchAccountOptions = computed<DropdownMenuItem[]>(() => {
|
|
78
|
+
const options: DropdownMenuItem[] = []
|
|
79
|
+
|
|
80
|
+
if (accountStore.userAccounts.length > 1) {
|
|
81
|
+
options.push({ label: t('connect.label.switchAccount'), type: 'label' })
|
|
82
|
+
|
|
83
|
+
accountStore.userAccounts.forEach((account) => {
|
|
84
|
+
const isActive = accountStore.currentAccount.id === account.id
|
|
85
|
+
options.push({
|
|
86
|
+
label: account.label,
|
|
87
|
+
onSelect: () => {
|
|
88
|
+
if (!isActive && account.id) {
|
|
89
|
+
if (route.meta.onAccountChange) {
|
|
90
|
+
// TODO: add route meta option
|
|
91
|
+
const allowAccountChange = true
|
|
92
|
+
// const allowAccountChange = route.meta.onAccountChange(accountStore.currentAccount, account)
|
|
93
|
+
if (allowAccountChange) {
|
|
94
|
+
accountStore.switchCurrentAccount(account.id)
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
accountStore.switchCurrentAccount(account.id)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
slot: 'account-item',
|
|
102
|
+
icon: isActive ? 'i-mdi-check' : '',
|
|
103
|
+
class: isActive ? 'bg-shade text-primary' : '',
|
|
104
|
+
ui: {
|
|
105
|
+
itemLabel: isActive ? '' : 'pl-6',
|
|
106
|
+
itemLeadingIcon: isActive ? 'size-5 text-primary shrink-0' : ''
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return options
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const createAccountOptions = computed<DropdownMenuItem[]>(() => {
|
|
116
|
+
if ([ConnectLoginSource.BCROS, ConnectLoginSource.IDIR].includes(authUser?.value.loginSource)) {
|
|
117
|
+
return []
|
|
118
|
+
}
|
|
119
|
+
return [{ label: t('connect.label.createAccount'), icon: 'i-mdi-plus', to: createAccountUrl() }]
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const loggedInUserOptions = computed<DropdownMenuItem[][]>(() => {
|
|
123
|
+
const options = [
|
|
124
|
+
basicAccountOptions.value,
|
|
125
|
+
accountSettingsOptions.value
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
if (switchAccountOptions.value.length > 0) {
|
|
129
|
+
options.push(switchAccountOptions.value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (createAccountOptions.value.length > 0) {
|
|
133
|
+
options.push(createAccountOptions.value)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return options
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const loginRedirectUrl = ac.login.redirectPath
|
|
140
|
+
? appBaseUrl + locale + ac.login.redirectPath
|
|
141
|
+
: undefined
|
|
142
|
+
|
|
143
|
+
const loginOptionsMap: Record<'bcsc' | 'bceid' | 'idir',
|
|
144
|
+
{ label: string, icon: string, onSelect: () => Promise<void> }
|
|
145
|
+
> = {
|
|
146
|
+
bcsc: {
|
|
147
|
+
label: t('connect.label.bcsc'),
|
|
148
|
+
icon: 'i-mdi-account-card-details-outline',
|
|
149
|
+
onSelect: () => login(ConnectIdpHint.BCSC, loginRedirectUrl)
|
|
150
|
+
},
|
|
151
|
+
bceid: {
|
|
152
|
+
label: t('connect.label.bceid'),
|
|
153
|
+
icon: 'i-mdi-two-factor-authentication',
|
|
154
|
+
onSelect: () => login(ConnectIdpHint.BCEID, loginRedirectUrl)
|
|
155
|
+
},
|
|
156
|
+
idir: {
|
|
157
|
+
label: t('connect.label.idir'),
|
|
158
|
+
icon: 'i-mdi-account-group-outline',
|
|
159
|
+
onSelect: () => login(ConnectIdpHint.IDIR, loginRedirectUrl)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const loggedOutUserOptions = computed<DropdownMenuItem[][]>(() => {
|
|
164
|
+
const options: DropdownMenuItem[][] = [[{ label: t('connect.label.selectLoginMethod'), type: 'label' }]]
|
|
165
|
+
|
|
166
|
+
const idps = ac.login.idps.map(key => loginOptionsMap[key as keyof typeof loginOptionsMap])
|
|
167
|
+
|
|
168
|
+
options.push(idps)
|
|
169
|
+
|
|
170
|
+
return options
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const loggedOutUserOptionsMobile = computed<DropdownMenuItem[][]>(() => {
|
|
174
|
+
const config = ac.header
|
|
175
|
+
const options: DropdownMenuItem[][] = []
|
|
176
|
+
|
|
177
|
+
if (config.loginMenu) {
|
|
178
|
+
options.push(...loggedOutUserOptions.value)
|
|
179
|
+
}
|
|
180
|
+
if (config.whatsNew) {
|
|
181
|
+
options.push([
|
|
182
|
+
{
|
|
183
|
+
'label': t('connect.label.whatsNew'),
|
|
184
|
+
'aria-label': t('connect.label.whatsNewAria', {
|
|
185
|
+
count: whatsNew.value.viewed ? 0 : whatsNew.value.items.length }
|
|
186
|
+
),
|
|
187
|
+
'slot': 'whatsnew' as const,
|
|
188
|
+
'icon': 'i-mdi-new-box',
|
|
189
|
+
'onSelect': () => {
|
|
190
|
+
slideover.open({
|
|
191
|
+
items: whatsNew.value.items
|
|
192
|
+
})
|
|
193
|
+
whatsNew.value.viewed = true
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
])
|
|
197
|
+
}
|
|
198
|
+
if (config.createAccount) {
|
|
199
|
+
options.push([{ label: t('connect.label.createAccount'), icon: 'i-mdi-plus', to: createAccountUrl() }])
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return options
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const notificationsOptions = computed<DropdownMenuItem[][]>(() => {
|
|
206
|
+
const count = accountStore.pendingApprovalCount
|
|
207
|
+
const options = []
|
|
208
|
+
if (count > 0) {
|
|
209
|
+
options.push([{
|
|
210
|
+
to: authWebUrl + `account/${accountStore.currentAccount.id}/settings/team-members`,
|
|
211
|
+
label: t('connect.text.notifications.teamMemberApproval', {
|
|
212
|
+
count: accountStore.pendingApprovalCount
|
|
213
|
+
}, accountStore.pendingApprovalCount
|
|
214
|
+
)
|
|
215
|
+
}])
|
|
216
|
+
} else {
|
|
217
|
+
options.push([{ label: t('connect.text.notifications.none') }])
|
|
218
|
+
}
|
|
219
|
+
return options
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
basicAccountOptions,
|
|
224
|
+
accountSettingsOptions,
|
|
225
|
+
switchAccountOptions,
|
|
226
|
+
createAccountOptions,
|
|
227
|
+
loggedInUserOptions,
|
|
228
|
+
loggedOutUserOptions,
|
|
229
|
+
loggedOutUserOptionsMobile,
|
|
230
|
+
notificationsOptions,
|
|
231
|
+
createAccountUrl
|
|
232
|
+
}
|
|
233
|
+
}
|