@sbc-connect/nuxt-auth 0.3.0 → 0.4.0
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/CHANGELOG.md +20 -0
- package/app/app.config.ts +4 -3
- package/app/components/Connect/Account/Create/Name.vue +73 -0
- package/app/components/Connect/Account/Create/index.vue +15 -21
- package/app/components/Connect/Modal/InvalidIdp.vue +45 -0
- package/app/components/Connect/TermsOfUse/Form.vue +1 -1
- package/app/composables/useAuthApi.ts +96 -2
- package/app/composables/useConnectAccountFlowRedirect.ts +0 -4
- package/app/composables/useConnectAppConfig.ts +31 -0
- package/app/composables/useConnectAuthModals.ts +76 -0
- package/app/enums/connect-access-type.ts +3 -0
- package/app/enums/connect-payment-method.ts +3 -0
- package/app/enums/connect-preset-type.ts +4 -0
- package/app/enums/connect-product-code.ts +3 -0
- package/app/enums/connect-product-type.ts +3 -0
- package/app/interfaces/app-config-shapes.ts +33 -0
- package/app/interfaces/connect-account-address.ts +1 -0
- package/app/interfaces/connect-account.ts +13 -0
- package/app/middleware/03.app-config-presets.global.ts +13 -0
- package/app/middleware/04.idp-enforcement.global.ts +39 -0
- package/app/middleware/connect-auth.ts +8 -1
- package/app/pages/auth/account/select.vue +17 -3
- package/app/pages/auth/login.vue +18 -1
- package/app/stores/connect-account.ts +64 -11
- package/app/types/auth-app-config.d.ts +11 -18
- package/app/utils/schemas/account.ts +26 -4
- package/i18n/locales/en-CA.ts +18 -0
- package/package.json +14 -15
- package/app/composables/useConnectTosModals.ts +0 -46
- package/app/middleware/03.allowed-idps.global.ts +0 -20
- package/modules/auth-assets/index.ts +0 -15
- package/modules/auth-assets/runtime/assets/connect-auth-tw.css +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-auth
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#118](https://github.com/bcgov/connect-nuxt/pull/118) [`41e58f4`](https://github.com/bcgov/connect-nuxt/commit/41e58f44e6d216e7de568bccb2e240b2d89b8408) Thanks [@deetz99](https://github.com/deetz99)! - Remove now unnecessary css module workaround.
|
|
8
|
+
|
|
9
|
+
- [#120](https://github.com/bcgov/connect-nuxt/pull/120) [`9f42868`](https://github.com/bcgov/connect-nuxt/commit/9f428681207188292d52cf6d370035f54bdb6ab4) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - Idp Enforcement Modal and bcsc user welcome msg
|
|
10
|
+
|
|
11
|
+
- [#117](https://github.com/bcgov/connect-nuxt/pull/117) [`e3da105`](https://github.com/bcgov/connect-nuxt/commit/e3da1056247f5c502578dc70f95375e66e3cc1da) Thanks [@deetz99](https://github.com/deetz99)! - Update all dependencies
|
|
12
|
+
|
|
13
|
+
- [#119](https://github.com/bcgov/connect-nuxt/pull/119) [`ee5ba9e`](https://github.com/bcgov/connect-nuxt/commit/ee5ba9e6f7302e8ea11fd0ebfa887c4f6269b0ee) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - implements Dynamic app.config and idp enforcement
|
|
14
|
+
|
|
15
|
+
- [#114](https://github.com/bcgov/connect-nuxt/pull/114) [`fe3eef1`](https://github.com/bcgov/connect-nuxt/commit/fe3eef1b39cc5883cc5e8560efe0dd89d04d0455) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - Implements Acct Creation submissions, contact updates and name lookup
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [[`41e58f4`](https://github.com/bcgov/connect-nuxt/commit/41e58f44e6d216e7de568bccb2e240b2d89b8408), [`e3da105`](https://github.com/bcgov/connect-nuxt/commit/e3da1056247f5c502578dc70f95375e66e3cc1da), [`fe3eef1`](https://github.com/bcgov/connect-nuxt/commit/fe3eef1b39cc5883cc5e8560efe0dd89d04d0455)]:
|
|
20
|
+
- @sbc-connect/nuxt-forms@0.4.0
|
|
21
|
+
- @sbc-connect/nuxt-base@0.6.0
|
|
22
|
+
|
|
3
23
|
## 0.3.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/app/app.config.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { ConnectIdpHint } from '#auth/app/enums/connect-idp-hint'
|
|
2
|
+
import type { AppConfigInput } from 'nuxt/schema'
|
|
2
3
|
|
|
3
4
|
export default defineAppConfig({
|
|
4
5
|
connect: {
|
|
5
6
|
login: {
|
|
6
7
|
redirect: '/',
|
|
7
8
|
idps: [ConnectIdpHint.BCSC, ConnectIdpHint.BCEID, ConnectIdpHint.IDIR],
|
|
8
|
-
skipAccountRedirect: false
|
|
9
|
-
|
|
9
|
+
skipAccountRedirect: false,
|
|
10
|
+
idpEnforcement: false
|
|
10
11
|
},
|
|
11
12
|
logout: {
|
|
12
13
|
redirect: ''
|
|
@@ -18,4 +19,4 @@ export default defineAppConfig({
|
|
|
18
19
|
accountOptionsMenu: true
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
})
|
|
22
|
+
} satisfies AppConfigInput) // validates input shape without losing inference
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { FormError, InputProps } from '@nuxt/ui'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
error?: FormError<string>
|
|
6
|
+
}>()
|
|
7
|
+
|
|
8
|
+
const authApi = useAuthApi()
|
|
9
|
+
const { accountFormState } = useConnectAccountStore()
|
|
10
|
+
|
|
11
|
+
const isLoading = ref(false)
|
|
12
|
+
const statusCode = defineModel<number | undefined>('statusCode')
|
|
13
|
+
|
|
14
|
+
/** Validate accountName and trigger validation API call */
|
|
15
|
+
const validateName = useDebounceFn(async (accountName: string) => {
|
|
16
|
+
if (accountName.length) {
|
|
17
|
+
isLoading.value = true
|
|
18
|
+
try {
|
|
19
|
+
const { refetch } = authApi.verifyAccountName(accountName)
|
|
20
|
+
const { data } = await refetch()
|
|
21
|
+
statusCode.value = data?.status
|
|
22
|
+
} catch (err: unknown) {
|
|
23
|
+
statusCode.value = err?.response?.status || 500
|
|
24
|
+
} finally {
|
|
25
|
+
isLoading.value = false
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
statusCode.value = undefined
|
|
29
|
+
}
|
|
30
|
+
}, 1000)
|
|
31
|
+
|
|
32
|
+
watch(() => accountFormState.accountName, validateName)
|
|
33
|
+
|
|
34
|
+
// Compute UInput props based on loading state and status code
|
|
35
|
+
const uInputProps = computed<InputProps>(() => {
|
|
36
|
+
const iconMap: Record<number, { class: string, name: string }> = {
|
|
37
|
+
204: { class: 'text-success size-7', name: 'i-mdi-check' },
|
|
38
|
+
200: { class: 'text-error size-7', name: 'i-mdi-close' },
|
|
39
|
+
500: { class: 'text-error size-7', name: 'i-mdi-close' }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const icon = statusCode.value ? iconMap[statusCode.value] : null
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
loading: isLoading.value,
|
|
46
|
+
trailing: true,
|
|
47
|
+
ui: {
|
|
48
|
+
trailingIcon: icon?.class || 'text-primary size-7'
|
|
49
|
+
},
|
|
50
|
+
trailingIcon: icon?.name
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Provide custom props for UInput
|
|
55
|
+
provide('UInput-props-account-name-input', uInputProps)
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<ConnectFormFieldWrapper
|
|
60
|
+
class="pt-2 my-6"
|
|
61
|
+
:label="$t('connect.page.createAccount.accountNameLabel')"
|
|
62
|
+
orientation="horizontal"
|
|
63
|
+
:error
|
|
64
|
+
>
|
|
65
|
+
<ConnectFormInput
|
|
66
|
+
v-model="accountFormState.accountName"
|
|
67
|
+
name="accountName"
|
|
68
|
+
input-id="account-name-input"
|
|
69
|
+
:label="$t('connect.page.createAccount.accountNameLabel')"
|
|
70
|
+
:help="$t('connect.page.createAccount.accountNameHelp')"
|
|
71
|
+
/>
|
|
72
|
+
</ConnectFormFieldWrapper>
|
|
73
|
+
</template>
|
|
@@ -3,9 +3,10 @@ import type { Form, FormError } from '@nuxt/ui'
|
|
|
3
3
|
import type { AccountProfileSchema } from '#auth/app/utils/schemas/account'
|
|
4
4
|
import { getAccountCreateSchema } from '#auth/app/utils/schemas/account'
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const accountProfileSchema = getAccountCreateSchema()
|
|
6
|
+
const statusCode = ref<number | undefined>(undefined)
|
|
8
7
|
|
|
8
|
+
const { accountFormState, submitCreateAccount, userFullName } = useConnectAccountStore()
|
|
9
|
+
const accountProfileSchema = computed(() => getAccountCreateSchema(statusCode.value))
|
|
9
10
|
const formRef = useTemplateRef<Form<AccountProfileSchema>>('account-create-form')
|
|
10
11
|
|
|
11
12
|
const formErrors = computed<{
|
|
@@ -44,6 +45,7 @@ async function validate() {
|
|
|
44
45
|
:schema="accountProfileSchema"
|
|
45
46
|
:state="accountFormState"
|
|
46
47
|
@error="onFormSubmitError"
|
|
48
|
+
@submit="submitCreateAccount()"
|
|
47
49
|
>
|
|
48
50
|
<!-- Legal Name -->
|
|
49
51
|
<ConnectFormFieldWrapper
|
|
@@ -61,20 +63,10 @@ async function validate() {
|
|
|
61
63
|
<USeparator orientation="horizontal" class="my-8" />
|
|
62
64
|
|
|
63
65
|
<!-- Account Name -->
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
:label="$t('connect.page.createAccount.accountNameLabel')"
|
|
67
|
-
orientation="horizontal"
|
|
66
|
+
<ConnectAccountCreateName
|
|
67
|
+
v-model:status-code="statusCode"
|
|
68
68
|
:error="formErrors.accountName"
|
|
69
|
-
|
|
70
|
-
<ConnectFormInput
|
|
71
|
-
v-model="accountFormState.accountName"
|
|
72
|
-
name="accountName"
|
|
73
|
-
input-id="account-name-input"
|
|
74
|
-
:label="$t('connect.page.createAccount.accountNameLabel')"
|
|
75
|
-
:help="$t('connect.page.createAccount.accountNameHelp')"
|
|
76
|
-
/>
|
|
77
|
-
</ConnectFormFieldWrapper>
|
|
69
|
+
/>
|
|
78
70
|
|
|
79
71
|
<!-- Account Email -->
|
|
80
72
|
<ConnectFormFieldWrapper
|
|
@@ -98,16 +90,18 @@ async function validate() {
|
|
|
98
90
|
:error="formErrors.phone"
|
|
99
91
|
>
|
|
100
92
|
<div class="flex flex-row gap-2">
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
<!-- Disabling country code selection until Auth Model Supports individual property -->
|
|
94
|
+
<!-- <ConnectFormPhoneCountryCode -->
|
|
95
|
+
<!-- v-model:country-calling-code="accountFormState.phone.countryCode" -->
|
|
96
|
+
<!-- v-model:country-iso2="accountFormState.phone.countryIso2" -->
|
|
97
|
+
<!-- :is-invalid="!accountFormState.phone.countryIso2" -->
|
|
98
|
+
<!-- class="w-40 mt-[-20px]" -->
|
|
99
|
+
<!-- /> -->
|
|
107
100
|
<ConnectFormInput
|
|
108
101
|
v-model="accountFormState.phone.phoneNumber"
|
|
109
102
|
name="phone.phoneNumber"
|
|
110
103
|
input-id="phone-number-input"
|
|
104
|
+
class="flex-2"
|
|
111
105
|
:label="$t('connect.page.createAccount.phonePlaceholder')"
|
|
112
106
|
mask="(###) ###-####"
|
|
113
107
|
/>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
currentIdp: ConnectLoginSource
|
|
4
|
+
}>()
|
|
5
|
+
const emit = defineEmits<{ close: [] }>()
|
|
6
|
+
|
|
7
|
+
function closeModal() {
|
|
8
|
+
emit('close')
|
|
9
|
+
}
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<UModal
|
|
14
|
+
id="invalid-idp-dialog"
|
|
15
|
+
overlay
|
|
16
|
+
:dismissible="false"
|
|
17
|
+
>
|
|
18
|
+
<template #content>
|
|
19
|
+
<div class="p-10 flex flex-col gap-6">
|
|
20
|
+
<div role="alert">
|
|
21
|
+
<h2
|
|
22
|
+
id="invalid-idp-title"
|
|
23
|
+
class="text-xl font-bold text-neutral-highlighted"
|
|
24
|
+
>
|
|
25
|
+
{{ $t('connect.invalidIdp.title') }} {{ props.currentIdp }}
|
|
26
|
+
</h2>
|
|
27
|
+
</div>
|
|
28
|
+
<div>
|
|
29
|
+
<div role="alert">
|
|
30
|
+
<span>{{ $t('connect.invalidIdp.content') }}</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="flex flex-wrap items-center justify-center gap-4">
|
|
34
|
+
<UButton
|
|
35
|
+
:label="$t('connect.label.logout')"
|
|
36
|
+
:aria-label="$t('connect.label.logout')"
|
|
37
|
+
size="xl"
|
|
38
|
+
class="font-bold"
|
|
39
|
+
@click="closeModal"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
</UModal>
|
|
45
|
+
</template>
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod'
|
|
|
3
3
|
import type { FormErrorEvent, Form } from '@nuxt/ui'
|
|
4
4
|
|
|
5
5
|
const { t } = useI18n()
|
|
6
|
-
const { openDeclineTosModal } =
|
|
6
|
+
const { openDeclineTosModal } = useConnectAuthModals()
|
|
7
7
|
|
|
8
8
|
const props = defineProps<{
|
|
9
9
|
hasReachedBottom: boolean
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ConnectCreateAccount } from '#auth/app/interfaces/connect-account'
|
|
2
|
+
|
|
1
3
|
export const useAuthApi = () => {
|
|
2
4
|
const { $authApi } = useNuxtApp()
|
|
3
5
|
const queryCache = useQueryCache()
|
|
@@ -13,6 +15,95 @@ export const useAuthApi = () => {
|
|
|
13
15
|
return query()
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Validates whether an account name is available by sending a request to the AUTH service.
|
|
20
|
+
* @param {string} accountName - The account name to validate for uniqueness.
|
|
21
|
+
*/
|
|
22
|
+
function verifyAccountName(accountName: string) {
|
|
23
|
+
const query = defineQuery({
|
|
24
|
+
key: ['auth-account-name', accountName],
|
|
25
|
+
query: () => $authApi.raw(`/orgs?validateName=true&name=${encodeURIComponent(accountName)}`),
|
|
26
|
+
staleTime: 300000
|
|
27
|
+
})
|
|
28
|
+
return query()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates an account by POSTing the given payload to `/orgs`.
|
|
33
|
+
* @returns Object containing mutation state and `createAccount` function.
|
|
34
|
+
*/
|
|
35
|
+
const useCreateAccount = defineMutation(() => {
|
|
36
|
+
const { mutateAsync, ...mutation } = useMutation({
|
|
37
|
+
mutation: (vars: { payload: ConnectCreateAccount, successCb?: () => Promise<unknown> }) => {
|
|
38
|
+
return $authApi<ConnectAuthProfile>('/orgs', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
body: vars.payload
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
onError: (error) => {
|
|
44
|
+
// TODO: FUTURE - add api error message to modal content - remove console.error
|
|
45
|
+
console.error('ERROR: ', error)
|
|
46
|
+
useConnectAuthModals().openCreateAccountModal()
|
|
47
|
+
},
|
|
48
|
+
onSuccess: async (_, _vars) => {
|
|
49
|
+
await queryCache.invalidateQueries({ key: ['auth-user-profile'], exact: true })
|
|
50
|
+
if (_vars.successCb) {
|
|
51
|
+
await _vars.successCb()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...mutation,
|
|
58
|
+
createAccount: mutateAsync
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Updates a users contact by PUTing the given payload to `/users/contacts`.
|
|
64
|
+
* @returns Object containing mutation state and `updateUserContact` function.
|
|
65
|
+
*/
|
|
66
|
+
const useUpdateUserContact = defineMutation(() => {
|
|
67
|
+
const { mutateAsync, ...mutation } = useMutation({
|
|
68
|
+
mutation: (vars: {
|
|
69
|
+
email: string
|
|
70
|
+
phone: string
|
|
71
|
+
phoneExtension: string | undefined
|
|
72
|
+
successCb?: () => Promise<unknown>
|
|
73
|
+
errorCb?: (error: unknown) => Promise<unknown>
|
|
74
|
+
}) => {
|
|
75
|
+
return $authApi<ConnectAuthProfile>('/users/contacts', {
|
|
76
|
+
method: 'PUT',
|
|
77
|
+
body: {
|
|
78
|
+
email: vars.email,
|
|
79
|
+
phone: vars.phone,
|
|
80
|
+
phoneExtension: vars.phoneExtension
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
},
|
|
84
|
+
onError: async (error, _vars) => {
|
|
85
|
+
// TODO: FUTURE - add api error message to modal content - remove console.error
|
|
86
|
+
console.error('ERROR: ', error)
|
|
87
|
+
await useConnectAuthModals().openUpdateUserContactModal()
|
|
88
|
+
|
|
89
|
+
if (_vars.errorCb) {
|
|
90
|
+
await queryCache.invalidateQueries({ key: ['auth-user-profile'], exact: true })
|
|
91
|
+
await _vars.errorCb(error)
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onSuccess: async (_, _vars) => {
|
|
95
|
+
if (_vars.successCb) {
|
|
96
|
+
await _vars.successCb()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
...mutation,
|
|
103
|
+
updateUserContact: mutateAsync
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
16
107
|
async function getTermsOfUse() {
|
|
17
108
|
const query = defineQuery({
|
|
18
109
|
key: ['auth-terms-of-use'],
|
|
@@ -36,7 +127,7 @@ export const useAuthApi = () => {
|
|
|
36
127
|
onError: (error) => {
|
|
37
128
|
// TODO: FUTURE - add api error message to modal content - remove console.error
|
|
38
129
|
console.error('ERROR: ', error)
|
|
39
|
-
|
|
130
|
+
useConnectAuthModals().openPatchTosErrorModal()
|
|
40
131
|
},
|
|
41
132
|
onSuccess: async (_, _vars) => {
|
|
42
133
|
await queryCache.invalidateQueries({ key: ['auth-user-profile'], exact: true })
|
|
@@ -55,6 +146,9 @@ export const useAuthApi = () => {
|
|
|
55
146
|
return {
|
|
56
147
|
getAuthUserProfile,
|
|
57
148
|
getTermsOfUse,
|
|
58
|
-
|
|
149
|
+
useCreateAccount,
|
|
150
|
+
usePatchTermsOfUse,
|
|
151
|
+
useUpdateUserContact,
|
|
152
|
+
verifyAccountName
|
|
59
153
|
}
|
|
60
154
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useAppConfig } from '#imports'
|
|
2
|
+
|
|
3
|
+
export const useConnectAppConfig = () => {
|
|
4
|
+
/**
|
|
5
|
+
* Merge preset overrides (from app.config.connectOverrides) into the provided baseConfig.
|
|
6
|
+
* If no override for the preset is found, baseConfig is returned unchanged.
|
|
7
|
+
*/
|
|
8
|
+
function mergeAppConfigOverrides(
|
|
9
|
+
baseConfig: ConnectConfig,
|
|
10
|
+
presetName: ConnectPresetType
|
|
11
|
+
): ConnectConfig {
|
|
12
|
+
const appConfig = useAppConfig()
|
|
13
|
+
const overrides = appConfig.connectOverrides?.[presetName] ?? null
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...baseConfig,
|
|
17
|
+
// Apply shallow merge for each subtree when present
|
|
18
|
+
...(overrides?.login
|
|
19
|
+
? { login: { ...baseConfig.login, ...overrides?.login } }
|
|
20
|
+
: { login: baseConfig.login }),
|
|
21
|
+
...(overrides?.header
|
|
22
|
+
? { header: { ...baseConfig.header, ...overrides?.header } }
|
|
23
|
+
: { header: baseConfig.header }),
|
|
24
|
+
logout: baseConfig.logout
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
mergeAppConfigOverrides
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const useConnectAuthModals = () => {
|
|
2
|
+
const { baseModal } = useConnectModal()
|
|
3
|
+
const { logout } = useConnectAuth()
|
|
4
|
+
const t = useNuxtApp().$i18n.t
|
|
5
|
+
|
|
6
|
+
function openDeclineTosModal() {
|
|
7
|
+
baseModal.open({
|
|
8
|
+
title: `${t('connect.label.declineTermsOfUse')}?`,
|
|
9
|
+
description: t('connect.text.declineTOSCantAccessService'),
|
|
10
|
+
dismissible: true,
|
|
11
|
+
buttons: [
|
|
12
|
+
{
|
|
13
|
+
label: t('connect.label.declineTermsOfUse'),
|
|
14
|
+
onClick: async () => await logout()
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: t('connect.label.cancel'),
|
|
18
|
+
shouldClose: true,
|
|
19
|
+
variant: 'outline'
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// TODO: better error text
|
|
26
|
+
function openPatchTosErrorModal() {
|
|
27
|
+
baseModal.open({
|
|
28
|
+
title: `${t('connect.text.patchTosError.title')}`,
|
|
29
|
+
description: t('connect.text.patchTosError.description'),
|
|
30
|
+
dismissible: true,
|
|
31
|
+
buttons: [
|
|
32
|
+
{
|
|
33
|
+
label: t('connect.label.close'),
|
|
34
|
+
shouldClose: true
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
// contact info ???
|
|
38
|
+
// include api error message?
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function openCreateAccountModal() {
|
|
43
|
+
baseModal.open({
|
|
44
|
+
title: `${t('connect.text.accountCreationError.title')}`,
|
|
45
|
+
description: t('connect.text.accountCreationError.description'),
|
|
46
|
+
dismissible: true,
|
|
47
|
+
buttons: [
|
|
48
|
+
{
|
|
49
|
+
label: t('connect.label.close'),
|
|
50
|
+
shouldClose: true
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function openUpdateUserContactModal() {
|
|
57
|
+
baseModal.open({
|
|
58
|
+
title: `${t('connect.text.userContactUpdateError.title')}`,
|
|
59
|
+
description: t('connect.text.userContactUpdateError.description'),
|
|
60
|
+
dismissible: true,
|
|
61
|
+
buttons: [
|
|
62
|
+
{
|
|
63
|
+
label: t('connect.label.close'),
|
|
64
|
+
shouldClose: true
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
openCreateAccountModal,
|
|
72
|
+
openDeclineTosModal,
|
|
73
|
+
openPatchTosErrorModal,
|
|
74
|
+
openUpdateUserContactModal
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ConnectLoginConfig {
|
|
2
|
+
redirect: string
|
|
3
|
+
idps: ConnectIdpHint[]
|
|
4
|
+
skipAccountRedirect: boolean
|
|
5
|
+
idpEnforcement: boolean
|
|
6
|
+
alert?: {
|
|
7
|
+
title?: string
|
|
8
|
+
message?: string
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ConnectLogoutConfig {
|
|
13
|
+
redirect: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ConnectHeaderConfig {
|
|
17
|
+
loginMenu: boolean
|
|
18
|
+
createAccount: boolean
|
|
19
|
+
notifications: boolean
|
|
20
|
+
accountOptionsMenu: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ConnectConfig {
|
|
24
|
+
login: ConnectLoginConfig
|
|
25
|
+
logout: ConnectLogoutConfig
|
|
26
|
+
header: ConnectHeaderConfig
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ConnectPresetOverrides {
|
|
30
|
+
login?: Partial<ConnectLoginConfig>
|
|
31
|
+
header?: Partial<ConnectHeaderConfig>
|
|
32
|
+
logout?: Partial<ConnectLogoutConfig>
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ConnectAccountAddress = Pick<ConnectAddress, Exclude<keyof ConnectAddress, 'locationDescription'>>
|
|
@@ -8,3 +8,16 @@ export interface ConnectAccount {
|
|
|
8
8
|
urlpath: string
|
|
9
9
|
urlorigin: string
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
export interface ConnectCreateAccount {
|
|
13
|
+
accessType: ConnectAccessType
|
|
14
|
+
isBusinessAccount: boolean
|
|
15
|
+
mailingAddress: ConnectAccountAddress
|
|
16
|
+
name: string
|
|
17
|
+
paymentInfo: {
|
|
18
|
+
paymentMethod: ConnectPaymentMethod
|
|
19
|
+
}
|
|
20
|
+
productSubscriptions: Array<{
|
|
21
|
+
productCode: ConnectProductCode
|
|
22
|
+
}>
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useAppConfig } from '#imports'
|
|
2
|
+
import { useConnectAppConfig } from '#auth/app/composables/useConnectAppConfig'
|
|
3
|
+
|
|
4
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
5
|
+
const appConfig = useAppConfig()
|
|
6
|
+
const { mergeAppConfigOverrides } = useConnectAppConfig()
|
|
7
|
+
|
|
8
|
+
// Build and assign app.config presets
|
|
9
|
+
appConfig.connect = mergeAppConfigOverrides(
|
|
10
|
+
appConfig.connect as ConnectConfig,
|
|
11
|
+
to.query.preset as ConnectPresetType
|
|
12
|
+
)
|
|
13
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useConnectAuth } from '#auth/app/composables/useConnectAuth'
|
|
2
|
+
import { withQuery } from 'ufo'
|
|
3
|
+
import type { ConnectIdpHint } from '#imports'
|
|
4
|
+
import { useAppConfig } from '#imports'
|
|
5
|
+
import { ConnectModalInvalidIdp } from '#components'
|
|
6
|
+
|
|
7
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
8
|
+
const localePath = useLocalePath()
|
|
9
|
+
const appConfig = useAppConfig()
|
|
10
|
+
const { authUser, logout } = useConnectAuth()
|
|
11
|
+
|
|
12
|
+
// IDP Enforcement Config
|
|
13
|
+
const connectConfig = appConfig.connect as ConnectConfig
|
|
14
|
+
const idpEnforcement = connectConfig?.login?.idpEnforcement
|
|
15
|
+
const allowedIdps = connectConfig?.login?.idps
|
|
16
|
+
|
|
17
|
+
// Idp overlay
|
|
18
|
+
const overlay = useOverlay()
|
|
19
|
+
const modal = overlay.create(ConnectModalInvalidIdp)
|
|
20
|
+
|
|
21
|
+
/** Show Invalid IDP Modal and Logout on modal close */
|
|
22
|
+
async function showInvalidIdpModal() {
|
|
23
|
+
// Prompt user with invalid IDP modal
|
|
24
|
+
await modal.open({ currentIdp: authUser.value?.loginSource })
|
|
25
|
+
|
|
26
|
+
// Logout and Preserve any query param
|
|
27
|
+
const pathWithQuery = withQuery(localePath('/auth/login'), to.query)
|
|
28
|
+
|
|
29
|
+
const url = `${window.location.origin}${pathWithQuery}`
|
|
30
|
+
return await logout(url)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (idpEnforcement && authUser.value?.loginSource) {
|
|
34
|
+
// User's IDP is not allowed, log them out and redirect to login page
|
|
35
|
+
if (!allowedIdps?.includes(authUser.value?.loginSource.toLowerCase() as unknown as ConnectIdpHint)) {
|
|
36
|
+
showInvalidIdpModal()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})
|
|
@@ -6,7 +6,14 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
6
6
|
const { finalRedirect } = useConnectAccountFlowRedirect()
|
|
7
7
|
|
|
8
8
|
if (!isAuthenticated.value && !rtc.playwright) {
|
|
9
|
-
return navigateTo(
|
|
9
|
+
return navigateTo({
|
|
10
|
+
path: localePath('/auth/login'),
|
|
11
|
+
query: {
|
|
12
|
+
// include preset when present
|
|
13
|
+
...(to.query.preset ? { preset: String(to.query.preset) } : {}),
|
|
14
|
+
return: `${rtc.baseUrl}${to.fullPath.slice(1)}`
|
|
15
|
+
}
|
|
16
|
+
})
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
if (isAuthenticated.value) {
|
|
@@ -5,11 +5,14 @@ definePageMeta({
|
|
|
5
5
|
middleware: 'connect-auth'
|
|
6
6
|
})
|
|
7
7
|
|
|
8
|
+
const route = useRoute()
|
|
8
9
|
const rtc = useRuntimeConfig().public
|
|
9
10
|
const accountStore = useConnectAccountStore()
|
|
10
11
|
const { authUser } = useConnectAuth()
|
|
11
12
|
const { finalRedirect } = useConnectAccountFlowRedirect()
|
|
12
13
|
const { clearAccountState } = useConnectAccountStore()
|
|
14
|
+
const { isLoading } = storeToRefs(useConnectAccountStore())
|
|
15
|
+
const isAccountCreateRoute = computed(() => route.path.includes('create'))
|
|
13
16
|
|
|
14
17
|
const addNew = ref(false)
|
|
15
18
|
const pageTitle = computed(() =>
|
|
@@ -26,7 +29,8 @@ function selectAndRedirect(id: number) {
|
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
onBeforeMount(() => {
|
|
29
|
-
if (accountStore.userAccounts.length === 0 && authUser.value.loginSource === ConnectLoginSource.BCSC)
|
|
32
|
+
if ((accountStore.userAccounts.length === 0 && authUser.value.loginSource === ConnectLoginSource.BCSC)
|
|
33
|
+
|| isAccountCreateRoute.value) {
|
|
30
34
|
addNew.value = true
|
|
31
35
|
}
|
|
32
36
|
})
|
|
@@ -63,7 +67,11 @@ const toggleCreateNewAccount = () => {
|
|
|
63
67
|
</ConnectTransitionFade>
|
|
64
68
|
|
|
65
69
|
<!-- Select Account Actions -->
|
|
66
|
-
<div
|
|
70
|
+
<div
|
|
71
|
+
v-if="!addNew"
|
|
72
|
+
class="flex justify-center"
|
|
73
|
+
data-testid="select-account-button-wrapper"
|
|
74
|
+
>
|
|
67
75
|
<UButton
|
|
68
76
|
v-if="authUser.loginSource === ConnectLoginSource.BCSC"
|
|
69
77
|
variant="outline"
|
|
@@ -88,10 +96,15 @@ const toggleCreateNewAccount = () => {
|
|
|
88
96
|
</div>
|
|
89
97
|
|
|
90
98
|
<!-- Create Account Actions -->
|
|
91
|
-
<div
|
|
99
|
+
<div
|
|
100
|
+
v-if="addNew"
|
|
101
|
+
class="flex justify-end gap-x-3"
|
|
102
|
+
data-testid="create-account-button-wrapper"
|
|
103
|
+
>
|
|
92
104
|
<UButton
|
|
93
105
|
variant="outline"
|
|
94
106
|
:label="$t('connect.label.back')"
|
|
107
|
+
:disabled="isLoading"
|
|
95
108
|
trailing
|
|
96
109
|
size="xl"
|
|
97
110
|
class="w-full justify-center sm:w-min sm:justify-normal"
|
|
@@ -99,6 +112,7 @@ const toggleCreateNewAccount = () => {
|
|
|
99
112
|
/>
|
|
100
113
|
<UButton
|
|
101
114
|
:label="$t('connect.label.saveAndContinue')"
|
|
115
|
+
:loading="isLoading"
|
|
102
116
|
form="account-create-form"
|
|
103
117
|
class="w-full justify-center sm:w-min sm:justify-normal"
|
|
104
118
|
trailing
|
package/app/pages/auth/login.vue
CHANGED
|
@@ -46,6 +46,20 @@ const loginOptions = computed(() => {
|
|
|
46
46
|
<template>
|
|
47
47
|
<div class="flex grow flex-col items-center justify-center py-10">
|
|
48
48
|
<div class="flex flex-col items-center gap-10">
|
|
49
|
+
<!-- Alert message from app config -->
|
|
50
|
+
<UAlert
|
|
51
|
+
v-if="ac.alert"
|
|
52
|
+
class="max-w-[35em]"
|
|
53
|
+
color="warning"
|
|
54
|
+
variant="subtle"
|
|
55
|
+
data-testid="login-alert"
|
|
56
|
+
:title="ac.alert.title"
|
|
57
|
+
:description="ac.alert.message"
|
|
58
|
+
icon="i-mdi-check-circle"
|
|
59
|
+
:ui="{
|
|
60
|
+
icon: 'text-success',
|
|
61
|
+
}"
|
|
62
|
+
/>
|
|
49
63
|
<h1>
|
|
50
64
|
{{ $t('connect.page.login.h1') }}
|
|
51
65
|
</h1>
|
|
@@ -57,7 +71,10 @@ const loginOptions = computed(() => {
|
|
|
57
71
|
:description="$t('connect.page.login.sessionExpiredAlert.description')"
|
|
58
72
|
icon="i-mdi-alert"
|
|
59
73
|
/>
|
|
60
|
-
<UCard
|
|
74
|
+
<UCard
|
|
75
|
+
class="my-auto max-w-md"
|
|
76
|
+
data-testid="login-card"
|
|
77
|
+
>
|
|
61
78
|
<img
|
|
62
79
|
:src="loginImage"
|
|
63
80
|
class="pb-4"
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { getAccountCreateSchema } from '#auth/app/utils/schemas/account'
|
|
2
2
|
import type { AccountProfileSchema } from '#auth/app/utils/schemas/account'
|
|
3
|
+
import type { ConnectCreateAccount } from '#auth/app/interfaces/connect-account'
|
|
3
4
|
|
|
4
5
|
export const useConnectAccountStore = defineStore('connect-auth-account-store', () => {
|
|
5
6
|
const { $authApi } = useNuxtApp()
|
|
6
|
-
const authApi = useAuthApi()
|
|
7
7
|
const rtc = useRuntimeConfig().public
|
|
8
8
|
const { authUser } = useConnectAuth()
|
|
9
|
+
const { useCreateAccount, useUpdateUserContact, getAuthUserProfile } = useAuthApi()
|
|
10
|
+
const { finalRedirect } = useConnectAccountFlowRedirect()
|
|
11
|
+
|
|
9
12
|
// selected user account
|
|
10
13
|
const currentAccount = ref<ConnectAccount>({} as ConnectAccount)
|
|
11
14
|
const userAccounts = ref<ConnectAccount[]>([])
|
|
@@ -15,9 +18,13 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
15
18
|
const userFirstName = ref<string>(user.value?.firstName || '-')
|
|
16
19
|
const userLastName = ref<string>(user.value?.lastName || '')
|
|
17
20
|
const userFullName = computed(() => `${userFirstName.value} ${userLastName.value}`)
|
|
21
|
+
|
|
22
|
+
// Create account
|
|
23
|
+
const isLoading = ref<boolean>(false)
|
|
24
|
+
const { createAccount } = useCreateAccount()
|
|
25
|
+
const { updateUserContact } = useUpdateUserContact()
|
|
18
26
|
const createAccountProfileSchema = getAccountCreateSchema()
|
|
19
27
|
const accountFormState = reactive<AccountProfileSchema>(createAccountProfileSchema.parse({}))
|
|
20
|
-
|
|
21
28
|
/**
|
|
22
29
|
* Checks if the current account or the Keycloak user has any of the specified roles.
|
|
23
30
|
*
|
|
@@ -53,9 +60,53 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
53
60
|
})
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
/** Map AccountFormState -> CreateAccountPayload */
|
|
64
|
+
function createAccountPayload(): ConnectCreateAccount {
|
|
65
|
+
return {
|
|
66
|
+
accessType: ConnectAccessType.REGULAR,
|
|
67
|
+
mailingAddress: {
|
|
68
|
+
city: accountFormState.address.city,
|
|
69
|
+
country: accountFormState.address.country,
|
|
70
|
+
region: accountFormState.address.region,
|
|
71
|
+
postalCode: accountFormState.address.postalCode,
|
|
72
|
+
street: accountFormState.address.street,
|
|
73
|
+
streetAdditional: accountFormState.address.streetAdditional || '',
|
|
74
|
+
deliveryInstructions: accountFormState.address.locationDescription || ''
|
|
75
|
+
},
|
|
76
|
+
name: accountFormState.accountName,
|
|
77
|
+
paymentInfo: { paymentMethod: ConnectPaymentMethod.DIRECT_PAY },
|
|
78
|
+
productSubscriptions: [{ productCode: ConnectProductCode.BUSINESS }]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Submit create account and user contact update requests */
|
|
83
|
+
async function submitCreateAccount(): Promise<void> {
|
|
84
|
+
try {
|
|
85
|
+
isLoading.value = true
|
|
86
|
+
// Create Account
|
|
87
|
+
const payload = createAccountPayload()
|
|
88
|
+
await createAccount({
|
|
89
|
+
payload,
|
|
90
|
+
// Update User Contact Info on create account success
|
|
91
|
+
successCb: async () => await updateUserContact({
|
|
92
|
+
email: accountFormState.emailAddress,
|
|
93
|
+
phone: accountFormState.phone.phoneNumber,
|
|
94
|
+
phoneExtension: accountFormState.phone.ext,
|
|
95
|
+
successCb: async () => await finalRedirect(useRoute()),
|
|
96
|
+
errorCb: async () => await finalRedirect(useRoute())
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// Error handled in useAuthApi
|
|
101
|
+
console.error('Account Create Submission Error: ', error)
|
|
102
|
+
} finally {
|
|
103
|
+
isLoading.value = false
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
56
107
|
/** Set user name information */
|
|
57
108
|
async function setUserName() {
|
|
58
|
-
const { data, refresh } = await
|
|
109
|
+
const { data, refresh } = await getAuthUserProfile()
|
|
59
110
|
await refresh()
|
|
60
111
|
if (data.value?.firstname && data.value?.lastname) {
|
|
61
112
|
userFirstName.value = data.value.firstname
|
|
@@ -172,21 +223,23 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
172
223
|
|
|
173
224
|
return {
|
|
174
225
|
accountFormState,
|
|
226
|
+
checkAccountStatus,
|
|
175
227
|
clearAccountState,
|
|
228
|
+
submitCreateAccount,
|
|
229
|
+
isLoading,
|
|
176
230
|
currentAccount,
|
|
177
231
|
currentAccountName,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
userFullName,
|
|
181
|
-
checkAccountStatus,
|
|
182
|
-
setUserName,
|
|
232
|
+
getPendingApprovalCount,
|
|
233
|
+
getUserAccounts,
|
|
183
234
|
hasRoles,
|
|
235
|
+
initAccountStore,
|
|
184
236
|
isCurrentAccount,
|
|
237
|
+
pendingApprovalCount,
|
|
185
238
|
setAccountInfo,
|
|
186
|
-
|
|
239
|
+
setUserName,
|
|
187
240
|
switchCurrentAccount,
|
|
188
|
-
|
|
189
|
-
|
|
241
|
+
userAccounts,
|
|
242
|
+
userFullName,
|
|
190
243
|
$reset
|
|
191
244
|
}
|
|
192
245
|
},
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
declare module '
|
|
1
|
+
declare module 'nuxt/schema' {
|
|
2
|
+
/** What users can write in app.config.ts */
|
|
2
3
|
interface AppConfigInput {
|
|
3
|
-
connect?:
|
|
4
|
-
|
|
5
|
-
redirect?: string
|
|
6
|
-
idps?: ConnectValidIdps
|
|
7
|
-
skipAccountRedirect?: boolean
|
|
8
|
-
// idpEnforcement: 'strict' | 'none' - future potentially
|
|
9
|
-
}
|
|
10
|
-
logout?: {
|
|
11
|
-
redirect?: string
|
|
12
|
-
}
|
|
13
|
-
header?: {
|
|
14
|
-
loginMenu?: boolean
|
|
15
|
-
createAccount?: boolean
|
|
16
|
-
notifications?: boolean
|
|
17
|
-
accountOptionsMenu?: boolean
|
|
18
|
-
}
|
|
19
|
-
}
|
|
4
|
+
connect?: Partial<ConnectConfig>
|
|
5
|
+
connectOverrides?: Record<string, ConnectPresetOverrides | null>
|
|
20
6
|
}
|
|
7
|
+
|
|
8
|
+
/** What useAppConfig() returns */
|
|
9
|
+
interface AppConfig {
|
|
10
|
+
connect: ConnectConfig
|
|
11
|
+
connectOverrides?: Record<string, ConnectPresetOverrides | null>
|
|
12
|
+
}
|
|
13
|
+
|
|
21
14
|
}
|
|
22
15
|
|
|
23
16
|
export {}
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { getRequiredAddressSchema } from '#forms/app/utils'
|
|
3
3
|
|
|
4
|
+
export function getAccountNameSchema(status?: number | undefined) {
|
|
5
|
+
const t = useNuxtApp().$i18n.t
|
|
6
|
+
|
|
7
|
+
return z.string().min(1, t('connect.validation.requiredAccountName'))
|
|
8
|
+
.superRefine((val, ctx) => {
|
|
9
|
+
// Only run if we have a value and a statusCode
|
|
10
|
+
if (val && status !== undefined) {
|
|
11
|
+
if (status === 200) {
|
|
12
|
+
ctx.addIssue({
|
|
13
|
+
code: 'custom',
|
|
14
|
+
message: t('connect.validation.duplicateAccountName')
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
if (status === 500) {
|
|
18
|
+
ctx.addIssue({
|
|
19
|
+
code: 'custom',
|
|
20
|
+
message: t('connect.validation.requestError')
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
// 204 (No Content) => valid -> no issue
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
4
28
|
/**
|
|
5
29
|
* Phone schema: country dialing code + local phone + optional extension.
|
|
6
30
|
* - countryCode: E.164 dialing code (e.g., "+1", "+44").
|
|
@@ -27,9 +51,7 @@ export function getPhoneSchema() {
|
|
|
27
51
|
* Account create schema — single address + name + email + phone
|
|
28
52
|
* Mirrors your .default(...) pattern.
|
|
29
53
|
*/
|
|
30
|
-
export function getAccountCreateSchema() {
|
|
31
|
-
const t = useNuxtApp().$i18n.t
|
|
32
|
-
|
|
54
|
+
export function getAccountCreateSchema(status: number | undefined = undefined) {
|
|
33
55
|
return z.object({
|
|
34
56
|
address: getRequiredAddressSchema().default({
|
|
35
57
|
street: '',
|
|
@@ -40,7 +62,7 @@ export function getAccountCreateSchema() {
|
|
|
40
62
|
country: 'CA',
|
|
41
63
|
locationDescription: ''
|
|
42
64
|
}),
|
|
43
|
-
accountName:
|
|
65
|
+
accountName: getAccountNameSchema(status).default(''),
|
|
44
66
|
emailAddress: z.string().email().default(''),
|
|
45
67
|
phone: getPhoneSchema().default({
|
|
46
68
|
countryIso2: 'CA',
|
package/i18n/locales/en-CA.ts
CHANGED
|
@@ -73,6 +73,10 @@ export default {
|
|
|
73
73
|
aria: 'Your session is about to expire, press any key to continue your session.'
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
|
+
invalidIdp: {
|
|
77
|
+
title: 'You\'re logged in with your',
|
|
78
|
+
content: 'To continue, you must sign in with your BC Services Card. Please log out and sign in again using your BC Services Card credentials.'
|
|
79
|
+
},
|
|
76
80
|
text: {
|
|
77
81
|
alertExistingAccountFound: '{boldStart}Note:{boldEnd} It looks like you already have an account with Service BC Connect. You can use an existing account to proceed or create a new one.',
|
|
78
82
|
alertUnableToLoadTermsOfUse: 'Unable to load Terms of Use, please try again later.',
|
|
@@ -82,10 +86,24 @@ export default {
|
|
|
82
86
|
notifications: {
|
|
83
87
|
none: 'No Notifications',
|
|
84
88
|
teamMemberApproval: '{count} team member requires approval to access this account. | {count} team members require approval to access this account.'
|
|
89
|
+
},
|
|
90
|
+
patchTosError: {
|
|
91
|
+
title: 'Terms of Use Update Error',
|
|
92
|
+
description: 'Unable to update Terms of Use at this time. Please try again later.'
|
|
93
|
+
},
|
|
94
|
+
accountCreationError: {
|
|
95
|
+
title: 'Account Creation Error',
|
|
96
|
+
description: 'Unable to create your account at this time. Please try again later.'
|
|
97
|
+
},
|
|
98
|
+
userContactUpdateError: {
|
|
99
|
+
title: 'User Contact Update Error',
|
|
100
|
+
description: 'Unable to update your contact information at this time. Please try again later.'
|
|
85
101
|
}
|
|
86
102
|
},
|
|
87
103
|
validation: {
|
|
88
104
|
acceptTermsOfUse: 'Please accept the Terms of Use.',
|
|
105
|
+
duplicateAccountName: 'An account with this name already exists.',
|
|
106
|
+
requestError: 'Request error, please try again.',
|
|
89
107
|
termsOfUseScrollToBottom: 'Please scroll to the bottom of the page to accept the Terms of Use.',
|
|
90
108
|
phoneNumberFormat: 'Phone must be in the format (123) 123-1231',
|
|
91
109
|
phoneExtFormat: 'Extension must be digits only',
|
package/package.json
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sbc-connect/nuxt-auth",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"repository": "github:bcgov/connect-nuxt",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@axe-core/playwright": "4.11.0",
|
|
10
|
-
"@vitest/coverage-v8": "3.2.4",
|
|
11
10
|
"dotenv": "17.2.3",
|
|
12
|
-
"nuxt": "4.
|
|
11
|
+
"nuxt": "4.2.2",
|
|
13
12
|
"typescript": "5.9.3",
|
|
14
|
-
"vue-tsc": "3.1
|
|
15
|
-
"@sbc-connect/
|
|
16
|
-
"@sbc-connect/
|
|
17
|
-
"@sbc-connect/
|
|
13
|
+
"vue-tsc": "3.2.1",
|
|
14
|
+
"@sbc-connect/playwright-config": "0.1.0",
|
|
15
|
+
"@sbc-connect/vitest-config": "0.1.0",
|
|
16
|
+
"@sbc-connect/eslint-config": "0.0.8"
|
|
18
17
|
},
|
|
19
18
|
"dependencies": {
|
|
20
|
-
"@pinia/colada": "
|
|
21
|
-
"@pinia/colada-nuxt": "
|
|
22
|
-
"@pinia/nuxt": "
|
|
23
|
-
"keycloak-js": "
|
|
24
|
-
"pinia": "
|
|
25
|
-
"pinia-plugin-persistedstate": "
|
|
26
|
-
"@sbc-connect/nuxt-base": "0.
|
|
27
|
-
"@sbc-connect/nuxt-forms": "0.
|
|
19
|
+
"@pinia/colada": "0.20.0",
|
|
20
|
+
"@pinia/colada-nuxt": "0.3.0",
|
|
21
|
+
"@pinia/nuxt": "0.11.3",
|
|
22
|
+
"keycloak-js": "26.2.2",
|
|
23
|
+
"pinia": "3.0.4",
|
|
24
|
+
"pinia-plugin-persistedstate": "4.7.1",
|
|
25
|
+
"@sbc-connect/nuxt-base": "0.6.0",
|
|
26
|
+
"@sbc-connect/nuxt-forms": "0.4.0"
|
|
28
27
|
},
|
|
29
28
|
"scripts": {
|
|
30
29
|
"preinstall": "npx only-allow pnpm",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export const useConnectTosModals = () => {
|
|
2
|
-
const { baseModal } = useConnectModal()
|
|
3
|
-
const { logout } = useConnectAuth()
|
|
4
|
-
const t = useNuxtApp().$i18n.t
|
|
5
|
-
|
|
6
|
-
function openDeclineTosModal() {
|
|
7
|
-
baseModal.open({
|
|
8
|
-
title: `${t('connect.label.declineTermsOfUse')}?`,
|
|
9
|
-
description: t('connect.text.declineTOSCantAccessService'),
|
|
10
|
-
dismissible: true,
|
|
11
|
-
buttons: [
|
|
12
|
-
{
|
|
13
|
-
label: t('connect.label.declineTermsOfUse'),
|
|
14
|
-
onClick: async () => await logout()
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
label: t('connect.label.cancel'),
|
|
18
|
-
shouldClose: true,
|
|
19
|
-
variant: 'outline'
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// TODO: better error text
|
|
26
|
-
function openPatchTosErrorModal() {
|
|
27
|
-
baseModal.open({
|
|
28
|
-
title: "Can't update TOS at this time",
|
|
29
|
-
description: 'Please try again later',
|
|
30
|
-
dismissible: true,
|
|
31
|
-
buttons: [
|
|
32
|
-
{
|
|
33
|
-
label: t('connect.label.close'),
|
|
34
|
-
shouldClose: true
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
// contact info ???
|
|
38
|
-
// include api error message?
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
openDeclineTosModal,
|
|
44
|
-
openPatchTosErrorModal
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export default defineNuxtRouteMiddleware((to) => {
|
|
2
|
-
const ac = useAppConfig().connect.login
|
|
3
|
-
const validIdps = getValidIdps()
|
|
4
|
-
const allowedIdps = to.query.allowedIdps as string | undefined
|
|
5
|
-
|
|
6
|
-
if (allowedIdps) {
|
|
7
|
-
const idpArray = allowedIdps
|
|
8
|
-
.split(',')
|
|
9
|
-
.filter(idp =>
|
|
10
|
-
validIdps.includes(idp as ConnectValidIdpOption)
|
|
11
|
-
) as ConnectValidIdps
|
|
12
|
-
|
|
13
|
-
if (idpArray.length) {
|
|
14
|
-
// updateAppConfig util doesn't seem to be updating correctly
|
|
15
|
-
// https://nuxt.com/docs/4.x/api/utils/update-app-config
|
|
16
|
-
// assign directly
|
|
17
|
-
ac.idps = idpArray
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver } from 'nuxt/kit'
|
|
2
|
-
|
|
3
|
-
export default defineNuxtModule({
|
|
4
|
-
meta: {
|
|
5
|
-
name: 'auth-assets',
|
|
6
|
-
configKey: 'authAssets'
|
|
7
|
-
},
|
|
8
|
-
defaults: {},
|
|
9
|
-
async setup(_options, _nuxt) {
|
|
10
|
-
console.info('Setting up **auth** assets module')
|
|
11
|
-
const resolver = createResolver(import.meta.url)
|
|
12
|
-
|
|
13
|
-
_nuxt.options.css.push(resolver.resolve('./runtime/assets/connect-auth-tw.css'))
|
|
14
|
-
}
|
|
15
|
-
})
|