@sbc-connect/nuxt-auth 0.2.1 → 0.3.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sbc-connect/nuxt-auth
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#108](https://github.com/bcgov/connect-nuxt/pull/108) [`8571754`](https://github.com/bcgov/connect-nuxt/commit/8571754160242c3233838fc039fd1a01ff9d7a82) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - Implements account creation in Auth and Country Code/Country Flag components in Forms
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`8571754`](https://github.com/bcgov/connect-nuxt/commit/8571754160242c3233838fc039fd1a01ff9d7a82), [`d9d6356`](https://github.com/bcgov/connect-nuxt/commit/d9d63560e27117c43b9e8e54d363c3fdbb620049), [`dd17349`](https://github.com/bcgov/connect-nuxt/commit/dd173497255f35415b468e17f7192722f604d3c8)]:
12
+ - @sbc-connect/nuxt-forms@0.3.0
13
+ - @sbc-connect/nuxt-base@0.5.0
14
+
3
15
  ## 0.2.1
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,140 @@
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 { accountFormState, userFullName } = useConnectAccountStore()
7
+ const accountProfileSchema = getAccountCreateSchema()
8
+
9
+ const formRef = useTemplateRef<Form<AccountProfileSchema>>('account-create-form')
10
+
11
+ const formErrors = computed<{
12
+ accountName: FormError<string> | undefined
13
+ emailAddress: FormError<string> | undefined
14
+ phone: FormError<string> | undefined
15
+ address: FormError<string> | undefined
16
+ }>(() => {
17
+ const errors = formRef.value?.getErrors()
18
+ return {
19
+ accountName: errors?.find(e => e.name === 'accountName'),
20
+ emailAddress: errors?.find(e => e.name?.startsWith('emailAddress')),
21
+ phone: errors?.find(e => e.name?.startsWith('phone')),
22
+ address: errors?.find(e => e.name?.startsWith('address'))
23
+ }
24
+ })
25
+
26
+ async function validate() {
27
+ return formRef.value?.validate({ silent: true })
28
+ }
29
+ </script>
30
+
31
+ <template>
32
+ <ConnectPageSection
33
+ :heading="{
34
+ label: $t('connect.label.accountInformation'),
35
+ labelClass: 'font-bold md:ml-4',
36
+ }"
37
+ >
38
+ <UForm
39
+ id="account-create-form"
40
+ ref="account-create-form"
41
+ class="p-8"
42
+ no-validate
43
+ :validate-on="['input', 'change']"
44
+ :schema="accountProfileSchema"
45
+ :state="accountFormState"
46
+ @error="onFormSubmitError"
47
+ >
48
+ <!-- Legal Name -->
49
+ <ConnectFormFieldWrapper
50
+ :label="$t('connect.page.createAccount.yourNameLabel')"
51
+ orientation="horizontal"
52
+ >
53
+ <p class="font-bold">
54
+ {{ userFullName }}
55
+ </p>
56
+ <p class="mt-3">
57
+ {{ $t('connect.page.createAccount.yourNameHelp') }}
58
+ </p>
59
+ </ConnectFormFieldWrapper>
60
+
61
+ <USeparator orientation="horizontal" class="my-8" />
62
+
63
+ <!-- Account Name -->
64
+ <ConnectFormFieldWrapper
65
+ class="pt-2 my-6"
66
+ :label="$t('connect.page.createAccount.accountNameLabel')"
67
+ orientation="horizontal"
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>
78
+
79
+ <!-- Account Email -->
80
+ <ConnectFormFieldWrapper
81
+ class="my-6"
82
+ :label="$t('connect.page.createAccount.emailLabel')"
83
+ orientation="horizontal"
84
+ :error="formErrors.emailAddress"
85
+ >
86
+ <ConnectFormInput
87
+ v-model="accountFormState.emailAddress"
88
+ name="emailAddress"
89
+ input-id="email-input"
90
+ :label="$t('connect.page.createAccount.emailPlaceholder')"
91
+ />
92
+ </ConnectFormFieldWrapper>
93
+
94
+ <!-- Account Phone -->
95
+ <ConnectFieldset
96
+ :label="$t('connect.page.createAccount.phoneLabel')"
97
+ orientation="horizontal"
98
+ :error="formErrors.phone"
99
+ >
100
+ <div class="flex flex-row gap-2">
101
+ <ConnectFormPhoneCountryCode
102
+ v-model:country-calling-code="accountFormState.phone.countryCode"
103
+ v-model:country-iso2="accountFormState.phone.countryIso2"
104
+ :is-invalid="!accountFormState.phone.countryIso2"
105
+ class="w-40 mt-[-20px]"
106
+ />
107
+ <ConnectFormInput
108
+ v-model="accountFormState.phone.phoneNumber"
109
+ name="phone.phoneNumber"
110
+ input-id="phone-number-input"
111
+ :label="$t('connect.page.createAccount.phonePlaceholder')"
112
+ mask="(###) ###-####"
113
+ />
114
+ <ConnectFormInput
115
+ v-model="accountFormState.phone.ext"
116
+ name="phone.ext"
117
+ input-id="phone-ext-input"
118
+ :label="$t('connect.page.createAccount.phoneExtensionLabel')"
119
+ />
120
+ </div>
121
+ </ConnectFieldset>
122
+
123
+ <!-- Account Address -->
124
+ <ConnectFieldset
125
+ class="my-6"
126
+ :label="$t('connect.page.createAccount.addressLabel')"
127
+ orientation="horizontal"
128
+ :error="formErrors.address"
129
+ >
130
+ <ConnectFormAddress
131
+ id="account-address"
132
+ v-model="accountFormState.address"
133
+ name="address"
134
+ schema-prefix="address"
135
+ @should-validate="validate"
136
+ />
137
+ </ConnectFieldset>
138
+ </UForm>
139
+ </ConnectPageSection>
140
+ </template>
@@ -9,8 +9,16 @@ const rtc = useRuntimeConfig().public
9
9
  const accountStore = useConnectAccountStore()
10
10
  const { authUser } = useConnectAuth()
11
11
  const { finalRedirect } = useConnectAccountFlowRedirect()
12
+ const { clearAccountState } = useConnectAccountStore()
12
13
 
13
14
  const addNew = ref(false)
15
+ const pageTitle = computed(() =>
16
+ !addNew.value ? $t('connect.label.existingAccountFound') : $t('connect.label.sbcAccountCreation')
17
+ )
18
+
19
+ useHead({
20
+ title: pageTitle
21
+ })
14
22
 
15
23
  function selectAndRedirect(id: number) {
16
24
  accountStore.switchCurrentAccount(id)
@@ -22,6 +30,11 @@ onBeforeMount(() => {
22
30
  addNew.value = true
23
31
  }
24
32
  })
33
+
34
+ const toggleCreateNewAccount = () => {
35
+ addNew.value = !addNew.value
36
+ clearAccountState()
37
+ }
25
38
  </script>
26
39
 
27
40
  <template>
@@ -29,6 +42,9 @@ onBeforeMount(() => {
29
42
  <ConnectTransitionFade>
30
43
  <div class="space-y-6 sm:space-y-10">
31
44
  <h1>{{ !addNew ? $t('connect.label.existingAccountFound') : $t('connect.label.sbcAccountCreation') }}</h1>
45
+ <p v-if="addNew">
46
+ {{ $t('connect.label.createNewAccountCont') }}
47
+ </p>
32
48
  <ConnectAccountExistingAlert v-if="!addNew" />
33
49
  </div>
34
50
  </ConnectTransitionFade>
@@ -40,12 +56,14 @@ onBeforeMount(() => {
40
56
  @select="selectAndRedirect"
41
57
  />
42
58
 
43
- <div v-else class="h-[66dvh] bg-white rounded border-2 border-black flex items-center justify-center text-3xl">
44
- Create Account Form Here
45
- </div>
59
+ <ConnectAccountCreate
60
+ v-else
61
+ ref="account-create-form-ref"
62
+ />
46
63
  </ConnectTransitionFade>
47
64
 
48
- <div class="flex justify-center">
65
+ <!-- Select Account Actions -->
66
+ <div v-if="!addNew" class="flex justify-center">
49
67
  <UButton
50
68
  v-if="authUser.loginSource === ConnectLoginSource.BCSC"
51
69
  variant="outline"
@@ -54,7 +72,7 @@ onBeforeMount(() => {
54
72
  trailing
55
73
  size="xl"
56
74
  class="w-full justify-center sm:w-min sm:justify-normal"
57
- @click="addNew = !addNew"
75
+ @click="toggleCreateNewAccount"
58
76
  />
59
77
  <UButton
60
78
  v-else
@@ -68,5 +86,25 @@ onBeforeMount(() => {
68
86
  :to="rtc.authWebUrl + 'setup-account'"
69
87
  />
70
88
  </div>
89
+
90
+ <!-- Create Account Actions -->
91
+ <div v-if="addNew" class="flex justify-end gap-x-3">
92
+ <UButton
93
+ variant="outline"
94
+ :label="$t('connect.label.back')"
95
+ trailing
96
+ size="xl"
97
+ class="w-full justify-center sm:w-min sm:justify-normal"
98
+ @click="toggleCreateNewAccount"
99
+ />
100
+ <UButton
101
+ :label="$t('connect.label.saveAndContinue')"
102
+ form="account-create-form"
103
+ class="w-full justify-center sm:w-min sm:justify-normal"
104
+ trailing
105
+ type="submit"
106
+ size="xl"
107
+ />
108
+ </div>
71
109
  </UContainer>
72
110
  </template>
@@ -1,3 +1,6 @@
1
+ import { getAccountCreateSchema } from '#auth/app/utils/schemas/account'
2
+ import type { AccountProfileSchema } from '#auth/app/utils/schemas/account'
3
+
1
4
  export const useConnectAccountStore = defineStore('connect-auth-account-store', () => {
2
5
  const { $authApi } = useNuxtApp()
3
6
  const authApi = useAuthApi()
@@ -12,6 +15,8 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
12
15
  const userFirstName = ref<string>(user.value?.firstName || '-')
13
16
  const userLastName = ref<string>(user.value?.lastName || '')
14
17
  const userFullName = computed(() => `${userFirstName.value} ${userLastName.value}`)
18
+ const createAccountProfileSchema = getAccountCreateSchema()
19
+ const accountFormState = reactive<AccountProfileSchema>(createAccountProfileSchema.parse({}))
15
20
 
16
21
  /**
17
22
  * Checks if the current account or the Keycloak user has any of the specified roles.
@@ -158,9 +163,16 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
158
163
  pendingApprovalCount.value = 0
159
164
  userFirstName.value = user.value?.firstName || '-'
160
165
  userLastName.value = user.value?.lastName || ''
166
+ clearAccountState()
167
+ }
168
+
169
+ function clearAccountState() {
170
+ Object.assign(accountFormState, createAccountProfileSchema.parse({}))
161
171
  }
162
172
 
163
173
  return {
174
+ accountFormState,
175
+ clearAccountState,
164
176
  currentAccount,
165
177
  currentAccountName,
166
178
  userAccounts,
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod'
2
+ import { getRequiredAddressSchema } from '#forms/app/utils'
3
+
4
+ /**
5
+ * Phone schema: country dialing code + local phone + optional extension.
6
+ * - countryCode: E.164 dialing code (e.g., "+1", "+44").
7
+ * - phoneNumber: accepts common local formats (e.g., "(123) 456-7890").
8
+ * - ext: digits only (optional).
9
+ */
10
+ export function getPhoneSchema() {
11
+ const t = useNuxtApp().$i18n.t
12
+
13
+ return z.object({
14
+ countryIso2: z.string(),
15
+ countryCode: z.string(),
16
+ phoneNumber: z
17
+ .string()
18
+ .regex(/^\(\d{3}\) \d{3}-\d{4}$/, { message: t('connect.validation.phoneNumberFormat') }),
19
+ ext: z
20
+ .string()
21
+ .regex(/^[0-9]*$/, { message: t('connect.validation.phoneExtFormat') })
22
+ .optional()
23
+ })
24
+ }
25
+
26
+ /**
27
+ * Account create schema — single address + name + email + phone
28
+ * Mirrors your .default(...) pattern.
29
+ */
30
+ export function getAccountCreateSchema() {
31
+ const t = useNuxtApp().$i18n.t
32
+
33
+ return z.object({
34
+ address: getRequiredAddressSchema().default({
35
+ street: '',
36
+ streetAdditional: '',
37
+ region: '',
38
+ city: '',
39
+ postalCode: '',
40
+ country: 'CA',
41
+ locationDescription: ''
42
+ }),
43
+ accountName: z.string().min(1, t('connect.validation.requiredAccountName')).default(''),
44
+ emailAddress: z.string().email().default(''),
45
+ phone: getPhoneSchema().default({
46
+ countryIso2: 'CA',
47
+ countryCode: '1',
48
+ phoneNumber: '',
49
+ ext: ''
50
+ })
51
+ })
52
+ }
53
+
54
+ export type AccountProfileSchema = z.output<ReturnType<typeof getAccountCreateSchema>>
@@ -5,12 +5,15 @@ export default {
5
5
  label: {
6
6
  accept: 'Accept',
7
7
  accountInfo: 'Account Info',
8
+ accountInformation: 'Account Information',
8
9
  accountOptionsMenu: 'Account Options Menu',
9
10
  accountSettings: 'Account Settings',
11
+ back: 'Back',
10
12
  bceid: 'BCeID',
11
13
  bcsc: 'BC Services Card',
12
14
  createAccount: 'Create Account',
13
15
  createNewAccount: 'Create New Account',
16
+ createNewAccountCont: 'Create a new account to continue',
14
17
  decline: 'Decline',
15
18
  declineTermsOfUse: 'Decline Terms of Use',
16
19
  editProfile: 'Edit Profile',
@@ -21,6 +24,7 @@ export default {
21
24
  mainMenu: 'Main Menu',
22
25
  notifications: 'Notifications',
23
26
  notificationsAria: 'Notifications, {count} unread',
27
+ saveAndContinue: 'Save and Continue',
24
28
  sbcAccountCreation: 'Service BC Account Creation',
25
29
  selectLoginMethod: 'Select log in method',
26
30
  switchAccount: 'Switch Account',
@@ -45,6 +49,19 @@ export default {
45
49
  termsOfUse: {
46
50
  h1: 'Terms of Use',
47
51
  title: 'Terms of Use - Service BC Connect'
52
+ },
53
+ createAccount: {
54
+ yourNameLabel: 'Your Name',
55
+ yourNameHelp: 'This is your legal name as it appears on your BC Services Card.',
56
+ accountNameLabel: 'Account Name',
57
+ accountNameHelp: 'This is your default login name.',
58
+ emailLabel: 'Email',
59
+ emailPlaceholder: 'Enter email address',
60
+ phoneLabel: 'Phone',
61
+ phonePlaceholder: 'Enter phone number',
62
+ phoneExtensionLabel: 'Phone extension (Optional)',
63
+ addressLabel: 'Address',
64
+ countryLabel: 'Country'
48
65
  }
49
66
  },
50
67
  sessionExpiry: {
@@ -69,7 +86,10 @@ export default {
69
86
  },
70
87
  validation: {
71
88
  acceptTermsOfUse: 'Please accept the Terms of Use.',
72
- termsOfUseScrollToBottom: 'Please scroll to the bottom of the page to accept the Terms of Use.'
89
+ termsOfUseScrollToBottom: 'Please scroll to the bottom of the page to accept the Terms of Use.',
90
+ phoneNumberFormat: 'Phone must be in the format (123) 123-1231',
91
+ phoneExtFormat: 'Extension must be digits only',
92
+ requiredAccountName: 'Account name is required'
73
93
  }
74
94
  }
75
95
  }
package/nuxt.config.ts CHANGED
@@ -10,7 +10,7 @@ export default defineNuxtConfig({
10
10
 
11
11
  compatibilityDate: '2025-07-10',
12
12
 
13
- extends: ['@sbc-connect/nuxt-base'],
13
+ extends: ['@sbc-connect/nuxt-base', '@sbc-connect/nuxt-forms'],
14
14
 
15
15
  imports: {
16
16
  dirs: ['interfaces', 'types', 'enums', 'stores']
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sbc-connect/nuxt-auth",
3
3
  "type": "module",
4
- "version": "0.2.1",
4
+ "version": "0.3.0",
5
5
  "repository": "github:bcgov/connect-nuxt",
6
6
  "license": "BSD-3-Clause",
7
7
  "main": "./nuxt.config.ts",
@@ -23,7 +23,8 @@
23
23
  "keycloak-js": "^26.2.1",
24
24
  "pinia": "^3.0.3",
25
25
  "pinia-plugin-persistedstate": "^4.5.0",
26
- "@sbc-connect/nuxt-base": "0.4.0"
26
+ "@sbc-connect/nuxt-base": "0.5.0",
27
+ "@sbc-connect/nuxt-forms": "0.3.0"
27
28
  },
28
29
  "scripts": {
29
30
  "preinstall": "npx only-allow pnpm",