@sbc-connect/nuxt-auth 0.0.0-test-ld-20260520212549

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.
Files changed (181) hide show
  1. package/.env.example +35 -0
  2. package/CHANGELOG.md +495 -0
  3. package/LICENSE +28 -0
  4. package/README.md +58 -0
  5. package/app/app.config.ts +22 -0
  6. package/app/components/Connect/Account/Create/Name.vue +73 -0
  7. package/app/components/Connect/Account/Create/index.vue +148 -0
  8. package/app/components/Connect/Account/Existing/Alert.vue +10 -0
  9. package/app/components/Connect/Account/Existing/List/Item.vue +54 -0
  10. package/app/components/Connect/Account/Existing/List/index.vue +30 -0
  11. package/app/components/Connect/Header/AccountLabel.vue +39 -0
  12. package/app/components/Connect/Header/AccountOptionsMenu.vue +52 -0
  13. package/app/components/Connect/Header/Auth.vue +14 -0
  14. package/app/components/Connect/Header/AuthenticatedOptions.vue +13 -0
  15. package/app/components/Connect/Header/CreateAccountButton.vue +13 -0
  16. package/app/components/Connect/Header/LoginMenu.vue +27 -0
  17. package/app/components/Connect/Header/Notifications.vue +30 -0
  18. package/app/components/Connect/Header/UnauthenticatedOptions.vue +15 -0
  19. package/app/components/Connect/Layout/Auth.vue +19 -0
  20. package/app/components/Connect/Modal/InvalidIdp.vue +55 -0
  21. package/app/components/Connect/Modal/SessionExpired.vue +107 -0
  22. package/app/components/Connect/TermsOfUse/Content.vue +28 -0
  23. package/app/components/Connect/TermsOfUse/Form.vue +101 -0
  24. package/app/components/HelloWorld/Auth.vue +12 -0
  25. package/app/composables/useAuthApi.ts +156 -0
  26. package/app/composables/useConnectAccountFlowRedirect.ts +54 -0
  27. package/app/composables/useConnectAppConfig.ts +31 -0
  28. package/app/composables/useConnectAuth.ts +90 -0
  29. package/app/composables/useConnectAuthModals.ts +76 -0
  30. package/app/composables/useConnectHeaderOptions.ts +236 -0
  31. package/app/composables/useConnectLaunchDarkly.ts +250 -0
  32. package/app/coverage/app/app.config.ts.html +148 -0
  33. package/app/coverage/app/components/Connect/Account/Create/Name.vue.html +304 -0
  34. package/app/coverage/app/components/Connect/Account/Create/index.html +131 -0
  35. package/app/coverage/app/components/Connect/Account/Create/index.vue.html +487 -0
  36. package/app/coverage/app/components/Connect/Account/Existing/Alert.vue.html +115 -0
  37. package/app/coverage/app/components/Connect/Account/Existing/List/Item.vue.html +247 -0
  38. package/app/coverage/app/components/Connect/Account/Existing/List/index.html +131 -0
  39. package/app/coverage/app/components/Connect/Account/Existing/List/index.vue.html +175 -0
  40. package/app/coverage/app/components/Connect/Account/Existing/index.html +116 -0
  41. package/app/coverage/app/components/Connect/Header/AccountLabel.vue.html +202 -0
  42. package/app/coverage/app/components/Connect/Header/AccountOptionsMenu.vue.html +241 -0
  43. package/app/coverage/app/components/Connect/Header/Auth.vue.html +127 -0
  44. package/app/coverage/app/components/Connect/Header/AuthenticatedOptions.vue.html +124 -0
  45. package/app/coverage/app/components/Connect/Header/CreateAccountButton.vue.html +124 -0
  46. package/app/coverage/app/components/Connect/Header/LoginMenu.vue.html +166 -0
  47. package/app/coverage/app/components/Connect/Header/Notifications.vue.html +175 -0
  48. package/app/coverage/app/components/Connect/Header/UnauthenticatedOptions.vue.html +130 -0
  49. package/app/coverage/app/components/Connect/Header/index.html +221 -0
  50. package/app/coverage/app/components/Connect/Layout/Auth.vue.html +142 -0
  51. package/app/coverage/app/components/Connect/Layout/index.html +116 -0
  52. package/app/coverage/app/components/Connect/Modal/SessionExpired.vue.html +364 -0
  53. package/app/coverage/app/components/Connect/Modal/index.html +116 -0
  54. package/app/coverage/app/components/Connect/TermsOfUse/Content.vue.html +169 -0
  55. package/app/coverage/app/components/Connect/TermsOfUse/Form.vue.html +388 -0
  56. package/app/coverage/app/components/Connect/TermsOfUse/index.html +131 -0
  57. package/app/coverage/app/components/HelloWorld/Auth.vue.html +121 -0
  58. package/app/coverage/app/components/HelloWorld/index.html +116 -0
  59. package/app/coverage/app/composables/index.html +191 -0
  60. package/app/coverage/app/composables/useAuthApi.ts.html +547 -0
  61. package/app/coverage/app/composables/useConnectAccountFlowRedirect.ts.html +202 -0
  62. package/app/coverage/app/composables/useConnectAuth.ts.html +352 -0
  63. package/app/coverage/app/composables/useConnectAuthModals.ts.html +313 -0
  64. package/app/coverage/app/composables/useConnectHeaderOptions.ts.html +781 -0
  65. package/app/coverage/app/composables/useConnectLaunchDarkly.ts.html +820 -0
  66. package/app/coverage/app/enums/account-status.ts.html +112 -0
  67. package/app/coverage/app/enums/account-type.ts.html +103 -0
  68. package/app/coverage/app/enums/connect-access-type.ts.html +94 -0
  69. package/app/coverage/app/enums/connect-auth-storage-key.ts.html +94 -0
  70. package/app/coverage/app/enums/connect-idp-hint.ts.html +103 -0
  71. package/app/coverage/app/enums/connect-login-source.ts.html +103 -0
  72. package/app/coverage/app/enums/connect-payment-method.ts.html +94 -0
  73. package/app/coverage/app/enums/connect-product-code.ts.html +94 -0
  74. package/app/coverage/app/enums/connect-product-type.ts.html +94 -0
  75. package/app/coverage/app/enums/index.html +251 -0
  76. package/app/coverage/app/enums/user-settings-type.ts.html +100 -0
  77. package/app/coverage/app/index.html +116 -0
  78. package/app/coverage/app/interfaces/connect-account-address.ts.html +88 -0
  79. package/app/coverage/app/interfaces/connect-account.ts.html +154 -0
  80. package/app/coverage/app/interfaces/connect-auth-contact.ts.html +130 -0
  81. package/app/coverage/app/interfaces/connect-auth-profile.ts.html +157 -0
  82. package/app/coverage/app/interfaces/connect-auth-user.ts.html +115 -0
  83. package/app/coverage/app/interfaces/connect-terms-of-use.ts.html +103 -0
  84. package/app/coverage/app/interfaces/connect-user-settings.ts.html +115 -0
  85. package/app/coverage/app/interfaces/index.html +206 -0
  86. package/app/coverage/app/layouts/ConnectAuth.vue.html +100 -0
  87. package/app/coverage/app/layouts/index.html +116 -0
  88. package/app/coverage/app/middleware/01.setup-accounts.global.ts.html +124 -0
  89. package/app/coverage/app/middleware/02.keycloak-params.global.ts.html +130 -0
  90. package/app/coverage/app/middleware/03.allowed-idps.global.ts.html +145 -0
  91. package/app/coverage/app/middleware/connect-auth.ts.html +238 -0
  92. package/app/coverage/app/middleware/connect-login-page.ts.html +163 -0
  93. package/app/coverage/app/middleware/index.html +176 -0
  94. package/app/coverage/app/pages/auth/account/index.html +116 -0
  95. package/app/coverage/app/pages/auth/account/select.vue.html +424 -0
  96. package/app/coverage/app/pages/auth/index.html +146 -0
  97. package/app/coverage/app/pages/auth/login.vue.html +334 -0
  98. package/app/coverage/app/pages/auth/logout.vue.html +154 -0
  99. package/app/coverage/app/pages/auth/terms-of-use.vue.html +316 -0
  100. package/app/coverage/app/plugins/auth-api.ts.html +169 -0
  101. package/app/coverage/app/plugins/connect-auth.client.ts.html +382 -0
  102. package/app/coverage/app/plugins/index.html +131 -0
  103. package/app/coverage/app/stores/connect-account.ts.html +826 -0
  104. package/app/coverage/app/stores/index.html +116 -0
  105. package/app/coverage/app/types/connect-valid-idps.ts.html +100 -0
  106. package/app/coverage/app/types/index.html +116 -0
  107. package/app/coverage/app/utils/constants/index.html +131 -0
  108. package/app/coverage/app/utils/constants/index.ts.html +88 -0
  109. package/app/coverage/app/utils/constants/valid-idps.ts.html +106 -0
  110. package/app/coverage/app/utils/index.html +146 -0
  111. package/app/coverage/app/utils/index.ts.html +88 -0
  112. package/app/coverage/app/utils/resetPiniaStores.ts.html +184 -0
  113. package/app/coverage/app/utils/schemas/account.ts.html +313 -0
  114. package/app/coverage/app/utils/schemas/index.html +116 -0
  115. package/app/coverage/app/utils/setOnBeforeSessionExpired.ts.html +154 -0
  116. package/app/coverage/base.css +224 -0
  117. package/app/coverage/block-navigation.js +87 -0
  118. package/app/coverage/clover.xml +1939 -0
  119. package/app/coverage/coverage-final.json +63 -0
  120. package/app/coverage/favicon.png +0 -0
  121. package/app/coverage/index.html +431 -0
  122. package/app/coverage/prettify.css +1 -0
  123. package/app/coverage/prettify.js +2 -0
  124. package/app/coverage/sort-arrow-sprite.png +0 -0
  125. package/app/coverage/sorter.js +210 -0
  126. package/app/enums/account-status.ts +9 -0
  127. package/app/enums/account-type.ts +6 -0
  128. package/app/enums/connect-access-type.ts +3 -0
  129. package/app/enums/connect-auth-storage-key.ts +3 -0
  130. package/app/enums/connect-idp-hint.ts +6 -0
  131. package/app/enums/connect-login-source.ts +6 -0
  132. package/app/enums/connect-payment-method.ts +3 -0
  133. package/app/enums/connect-preset-type.ts +4 -0
  134. package/app/enums/connect-product-code.ts +3 -0
  135. package/app/enums/connect-product-type.ts +3 -0
  136. package/app/enums/user-settings-type.ts +5 -0
  137. package/app/interfaces/app-config-shapes.ts +40 -0
  138. package/app/interfaces/connect-account-address.ts +3 -0
  139. package/app/interfaces/connect-account.ts +23 -0
  140. package/app/interfaces/connect-auth-contact.ts +15 -0
  141. package/app/interfaces/connect-auth-profile.ts +24 -0
  142. package/app/interfaces/connect-auth-user.ts +10 -0
  143. package/app/interfaces/connect-terms-of-use.ts +6 -0
  144. package/app/interfaces/connect-user-settings.ts +10 -0
  145. package/app/layouts/ConnectAuth.vue +5 -0
  146. package/app/middleware/01.setup-accounts.global.ts +13 -0
  147. package/app/middleware/02.keycloak-params.global.ts +15 -0
  148. package/app/middleware/03.app-config-presets.global.ts +10 -0
  149. package/app/middleware/04.idp-enforcement.global.ts +30 -0
  150. package/app/middleware/connect-auth.ts +72 -0
  151. package/app/pages/auth/account/select.vue +121 -0
  152. package/app/pages/auth/login.vue +104 -0
  153. package/app/pages/auth/logout.vue +23 -0
  154. package/app/pages/auth/terms-of-use.vue +77 -0
  155. package/app/plugins/auth-api.ts +28 -0
  156. package/app/plugins/connect-auth.client.ts +112 -0
  157. package/app/stores/connect-account.ts +270 -0
  158. package/app/types/auth-app-config.d.ts +16 -0
  159. package/app/types/auth-page-meta.d.ts +8 -0
  160. package/app/types/connect-valid-idps.ts +5 -0
  161. package/app/utils/constants/index.ts +1 -0
  162. package/app/utils/constants/valid-idps.ts +7 -0
  163. package/app/utils/index.ts +1 -0
  164. package/app/utils/resetPiniaStores.ts +33 -0
  165. package/app/utils/schemas/account.ts +76 -0
  166. package/app/utils/setOnBeforeSessionExpired.ts +23 -0
  167. package/i18n/locales/en-CA.ts +114 -0
  168. package/i18n/locales/fr-CA.ts +1 -0
  169. package/nuxt.config.ts +84 -0
  170. package/package.json +44 -0
  171. package/playwright-report/index.html +85 -0
  172. package/public/img/BCReg_Generic_Login_image.jpg +0 -0
  173. package/test-results/.last-run.json +4 -0
  174. package/testMocks/auth/index.ts +2 -0
  175. package/testMocks/auth/profile/index.ts +9 -0
  176. package/testMocks/auth/profile/json/PUBLIC_USER.json +29 -0
  177. package/testMocks/auth/settings/index.ts +11 -0
  178. package/testMocks/auth/settings/json/PREMIUM.json +38 -0
  179. package/testMocks/auth/settings/json/SBC_STAFF.json +27 -0
  180. package/testMocks/auth/settings/json/STAFF.json +27 -0
  181. package/testMocks/mock-helpers.ts +15 -0
@@ -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 ?? 500 // fallback as undefined is not considered an exception
22
+ } catch (err: unknown) {
23
+ statusCode.value = getErrorStatus(err) ?? 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
+ :label="$t('connect.page.createAccount.accountNameLabel')"
61
+ orientation="horizontal"
62
+ :error
63
+ padding-class="pt-6 pb-4 px-4 sm:pb-4 sm:pt-8 sm:px-8"
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>
@@ -0,0 +1,148 @@
1
+ <script setup lang="ts">
2
+ import type { Form, FormError } from '@nuxt/ui'
3
+ import type { AccountProfileSchema } from '#auth/app/utils/schemas/account'
4
+ import { getAccountCreateSchema } from '#auth/app/utils/schemas/account'
5
+
6
+ const statusCode = ref<number | undefined>(undefined)
7
+
8
+ const accountStore = useConnectAccountStore()
9
+ const { accountFormState, userFullName } = storeToRefs(accountStore)
10
+ const { submitCreateAccount } = accountStore
11
+ const accountProfileSchema = computed(() => getAccountCreateSchema(statusCode.value))
12
+ const formRef = useTemplateRef<Form<AccountProfileSchema>>('account-create-form')
13
+
14
+ const formErrors = computed<{
15
+ accountName: FormError<string> | undefined
16
+ emailAddress: FormError<string> | undefined
17
+ phone: FormError<string> | undefined
18
+ address: FormError<string> | undefined
19
+ }>(() => {
20
+ const errors = formRef.value?.getErrors()
21
+ return {
22
+ accountName: errors?.find(e => e.name === 'accountName'),
23
+ emailAddress: errors?.find(e => e.name?.startsWith('emailAddress')),
24
+ phone: errors?.find(e => e.name?.startsWith('phone')),
25
+ address: errors?.find(e => e.name?.startsWith('address'))
26
+ }
27
+ })
28
+
29
+ async function validate(fieldName?: keyof AccountProfileSchema) {
30
+ return formRef.value?.validate({
31
+ name: fieldName as never,
32
+ silent: true
33
+ })
34
+ }
35
+
36
+ watch(() => statusCode.value, async () => {
37
+ if (statusCode.value !== undefined) {
38
+ await nextTick()
39
+ await validate('accountName')
40
+ }
41
+ })
42
+ </script>
43
+
44
+ <template>
45
+ <ConnectPageSection
46
+ :heading="{
47
+ label: $t('connect.label.accountInformation'),
48
+ labelClass: 'font-bold md:ml-4',
49
+ }"
50
+ ui-body="p-0 sm:p-0"
51
+ >
52
+ <UForm
53
+ id="account-create-form"
54
+ ref="account-create-form"
55
+ no-validate
56
+ :validate-on="['input', 'change']"
57
+ :schema="accountProfileSchema"
58
+ :state="accountFormState"
59
+ @error="onFormSubmitError"
60
+ @submit="submitCreateAccount()"
61
+ >
62
+ <!-- Legal Name -->
63
+ <ConnectFormFieldWrapper
64
+ :label="$t('connect.page.createAccount.contactLabel')"
65
+ orientation="horizontal"
66
+ class="py-6 sm:py-8"
67
+ >
68
+ <p class="font-bold">
69
+ {{ userFullName }}
70
+ </p>
71
+ <p class="mt-3">
72
+ {{ $t('connect.page.createAccount.yourNameHelp') }}
73
+ </p>
74
+ </ConnectFormFieldWrapper>
75
+
76
+ <USeparator orientation="horizontal" />
77
+
78
+ <!-- Account Name -->
79
+ <ConnectAccountCreateName
80
+ v-model:status-code="statusCode"
81
+ :error="formErrors.accountName"
82
+ />
83
+
84
+ <!-- Account Email -->
85
+ <ConnectFormFieldWrapper
86
+ :label="$t('connect.page.createAccount.emailLabel')"
87
+ orientation="horizontal"
88
+ :error="formErrors.emailAddress"
89
+ padding-class="py-1 px-4 sm:py-2 sm:px-8"
90
+ >
91
+ <ConnectFormInput
92
+ v-model="accountFormState.emailAddress"
93
+ name="emailAddress"
94
+ input-id="email-input"
95
+ :label="$t('connect.page.createAccount.emailPlaceholder')"
96
+ />
97
+ </ConnectFormFieldWrapper>
98
+
99
+ <!-- Account Phone -->
100
+ <ConnectFieldset
101
+ :label="$t('connect.page.createAccount.phoneLabel')"
102
+ orientation="horizontal"
103
+ :error="formErrors.phone"
104
+ padding-class="py-1 px-4 sm:py-2 sm:px-8"
105
+ >
106
+ <div class="flex flex-row gap-2">
107
+ <!-- Disabling country code selection until Auth Model Supports individual property -->
108
+ <!-- <ConnectFormPhoneCountryCode -->
109
+ <!-- v-model:country-calling-code="accountFormState.phone.countryCode" -->
110
+ <!-- v-model:country-iso2="accountFormState.phone.countryIso2" -->
111
+ <!-- :is-invalid="!accountFormState.phone.countryIso2" -->
112
+ <!-- class="w-40 mt-[-20px]" -->
113
+ <!-- /> -->
114
+ <ConnectFormInput
115
+ v-model="accountFormState.phone.phoneNumber"
116
+ name="phone.phoneNumber"
117
+ input-id="phone-number-input"
118
+ class="flex-2"
119
+ :label="$t('connect.page.createAccount.phonePlaceholder')"
120
+ mask="(###) ###-####"
121
+ />
122
+ <ConnectFormInput
123
+ v-model="accountFormState.phone.ext"
124
+ name="phone.ext"
125
+ input-id="phone-ext-input"
126
+ :label="$t('connect.page.createAccount.phoneExtensionLabel')"
127
+ />
128
+ </div>
129
+ </ConnectFieldset>
130
+
131
+ <!-- Account Address -->
132
+ <ConnectFieldset
133
+ :label="$t('connect.page.createAccount.addressLabel')"
134
+ orientation="horizontal"
135
+ :error="formErrors.address"
136
+ padding-class="py-1 px-4 sm:pt-2 sm:pb-4 sm:px-8"
137
+ >
138
+ <ConnectFormAddress
139
+ id="account-address"
140
+ v-model="accountFormState.address"
141
+ name="address"
142
+ schema-prefix="address"
143
+ @should-validate="validate"
144
+ />
145
+ </ConnectFieldset>
146
+ </UForm>
147
+ </ConnectPageSection>
148
+ </template>
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <UAlert
3
+ color="warning"
4
+ variant="subtle"
5
+ >
6
+ <template #description>
7
+ <ConnectI18nHelper translation-path="connect.text.alertExistingAccountFound" />
8
+ </template>
9
+ </UAlert>
10
+ </template>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ account: ConnectAccount
4
+ }>()
5
+
6
+ defineEmits<{
7
+ select: [id: number]
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <li class="flex flex-col items-start justify-between gap-4 py-8 first:pt-0 last:pb-0 sm:flex-row sm:items-center">
13
+ <div
14
+ class="flex flex-row items-center gap-4 sm:gap-6"
15
+ :class="{
16
+ 'opacity-50': account.accountStatus !== AccountStatus.ACTIVE,
17
+ }"
18
+ >
19
+ <UAvatar
20
+ :alt="account.label[0]"
21
+ :ui="{
22
+ root: 'bg-blue-300 rounded-sm',
23
+ fallback: 'text-white font-bold text-xl',
24
+ }"
25
+ />
26
+ <div class="flex w-full flex-col text-left">
27
+ <span class="text-lg font-bold text-neutral-highlighted">
28
+ {{ account.label }}
29
+ </span>
30
+ </div>
31
+ </div>
32
+ <div class="flex w-full flex-col gap-4 sm:w-fit sm:flex-row">
33
+ <div class="my-auto flex gap-2">
34
+ <UBadge
35
+ v-if="account.accountStatus !== AccountStatus.ACTIVE"
36
+ :label="$t('badge.inactiveAccount')"
37
+ class="bg-[#fff7e3] px-3 text-center font-bold text-neutral"
38
+ />
39
+ </div>
40
+
41
+ <UButton
42
+ :label="$t('connect.label.useThisAccount')"
43
+ :aria-label="$t('connect.label.useThisAccountAria', { name: account.label })"
44
+ :icon="'i-mdi-chevron-right'"
45
+ trailing
46
+ :disabled="account.accountStatus !== AccountStatus.ACTIVE"
47
+ size="xl"
48
+ data-testid="choose-existing-account-button"
49
+ class="w-full justify-center sm:w-min sm:justify-normal"
50
+ @click="$emit('select', account.id)"
51
+ />
52
+ </div>
53
+ </li>
54
+ </template>
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ accounts: ConnectAccount[]
4
+ }>()
5
+
6
+ defineEmits<{
7
+ select: [id: number]
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <section class="space-y-4">
13
+ <ConnectI18nHelper
14
+ as="h2"
15
+ class="font-normal"
16
+ translation-path="connect.label.yourExistingAccounts"
17
+ :count="accounts.length"
18
+ />
19
+ <ul
20
+ class="bg-white flex flex-col divide-y divide-line-muted rounded p-8 max-h-[50dvh] overflow-y-auto"
21
+ >
22
+ <ConnectAccountExistingListItem
23
+ v-for="account in accounts"
24
+ :key="account.id"
25
+ :account
26
+ @select="$emit('select', $event)"
27
+ />
28
+ </ul>
29
+ </section>
30
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ accountName = 'N/A',
4
+ username = 'N/A',
5
+ theme = 'header'
6
+ } = defineProps<{
7
+ accountName?: string
8
+ username?: string
9
+ theme?: 'header' | 'dropdown'
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div class="flex items-center gap-1">
15
+ <UAvatar
16
+ :text="username[0] ? username[0].toLocaleUpperCase($i18n.locale) : 'U'"
17
+ size="md"
18
+ class="text-inverted self-start sm:self-center"
19
+ :ui="{ root: 'bg-blue-300 rounded-none text-lg', fallback: 'text-inverted font-bold' }"
20
+ />
21
+ <div class="mx-2 flex flex-col text-left font-normal tracking-wide">
22
+ <span
23
+ class="text-sm"
24
+ :class="{
25
+ 'line-clamp-1 overflow-hidden text-ellipsis text-inverted max-w-64': theme === 'header',
26
+ 'text-neutral-highlighted max-w-80': theme === 'dropdown',
27
+ }"
28
+ >
29
+ {{ username }}
30
+ </span>
31
+ <span
32
+ class="line-clamp-1 overflow-hidden text-ellipsis text-xs opacity-75"
33
+ :class="{ 'text-line-muted': theme === 'header', 'text-neutral': theme === 'dropdown' }"
34
+ >
35
+ {{ accountName }}
36
+ </span>
37
+ </div>
38
+ </div>
39
+ </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] account-options-menu-pw-selector',
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,19 @@
1
+ <script setup lang="ts">
2
+ const accountStore = useConnectAccountStore()
3
+ </script>
4
+
5
+ <template>
6
+ <ConnectLayout>
7
+ <template #header>
8
+ <ConnectHeaderAuth />
9
+ </template>
10
+ <template #breadcrumb>
11
+ <ConnectBreadcrumb
12
+ :account-id="accountStore.currentAccount.id
13
+ ? String(accountStore.currentAccount.id)
14
+ : undefined"
15
+ />
16
+ </template>
17
+ <slot />
18
+ </ConnectLayout>
19
+ </template>
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ currentIdp: ConnectLoginSource
4
+ redirectUrl: string
5
+ }>()
6
+
7
+ const { t, locale } = useI18n()
8
+
9
+ const formattedIdps = computed(() => {
10
+ const allowedIdps = useAppConfig().connect.login.idps
11
+ const labels = allowedIdps.map(idp => t(`connect.label.${idp}`))
12
+
13
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat#using_format
14
+ const formatter = new Intl.ListFormat(locale.value, {
15
+ style: 'long',
16
+ type: 'disjunction'
17
+ })
18
+
19
+ return formatter.format(labels)
20
+ })
21
+ </script>
22
+
23
+ <template>
24
+ <UModal
25
+ id="invalid-idp-dialog"
26
+ overlay
27
+ :dismissible="false"
28
+ >
29
+ <template #content>
30
+ <div class="p-10 flex flex-col gap-6">
31
+ <div role="alert">
32
+ <h2
33
+ id="invalid-idp-title"
34
+ class="text-xl font-bold text-neutral-highlighted"
35
+ >
36
+ {{ $t('connect.invalidIdp.title', { idp: currentIdp }) }}
37
+ </h2>
38
+ </div>
39
+ <div>
40
+ <div role="alert">
41
+ <span>{{ $t('connect.invalidIdp.content', { allowedIdps: formattedIdps }) }}</span>
42
+ </div>
43
+ </div>
44
+ <div class="flex flex-wrap items-center justify-center gap-4">
45
+ <UButton
46
+ :label="$t('connect.label.logout')"
47
+ size="xl"
48
+ class="font-bold"
49
+ @click="useConnectAuth().logout(redirectUrl)"
50
+ />
51
+ </div>
52
+ </div>
53
+ </template>
54
+ </UModal>
55
+ </template>