@dloizides/auth-web 1.17.0 → 1.18.1

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/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var reactNative = require('react-native');
4
4
  var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var uiButtons = require('@dloizides/ui-buttons');
6
7
  var reactQuery = require('@tanstack/react-query');
7
8
  var authClient = require('@dloizides/auth-client');
8
9
 
@@ -331,6 +332,27 @@ var DEFAULT_ACCOUNT_SETTINGS_LABELS = {
331
332
  errorFailed: "Could not change your password. Try again later."
332
333
  };
333
334
 
335
+ // src/components/labels/registerLabels.ts
336
+ function field(label, placeholder) {
337
+ return { label, placeholder, hint: placeholder, inputLabel: label };
338
+ }
339
+ var DEFAULT_REGISTER_LABELS = {
340
+ title: "Create account",
341
+ subtitle: "Sign up to get started",
342
+ submit: "Create account",
343
+ submitHint: "Creates your account",
344
+ submitting: "Creating account...",
345
+ fields: {
346
+ firstName: field("First name", "Enter your first name"),
347
+ lastName: field("Last name", "Enter your last name"),
348
+ username: field("Username", "Choose a username"),
349
+ email: field("Email", "Enter your email"),
350
+ password: field("Password", "Choose a password"),
351
+ confirmPassword: field("Confirm password", "Repeat your password"),
352
+ tenantName: field("Organisation name", "Enter your organisation name")
353
+ }
354
+ };
355
+
334
356
  // src/components/testIds.ts
335
357
  var AuthTestIds = {
336
358
  loginForm: "auth-login-form",
@@ -754,7 +776,6 @@ function LoginForm({
754
776
  missingFieldsLabel: labels.missingFields,
755
777
  invalidCredentialsLabel: labels.invalidCredentials
756
778
  });
757
- const submitButtonStyle = isSubmitting ? [styles3.primaryButton, styles3.primaryButtonDisabled] : styles3.primaryButton;
758
779
  const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
759
780
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.title, children: labels.title }),
760
781
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.subtitle, children: labels.subtitle }),
@@ -818,19 +839,18 @@ function LoginForm({
818
839
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.linkText, children: labels.forgotPassword })
819
840
  }
820
841
  ) : null,
821
- /* @__PURE__ */ jsxRuntime.jsx(
822
- reactNative.TouchableOpacity,
842
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.fieldGroup, children: /* @__PURE__ */ jsxRuntime.jsx(
843
+ uiButtons.Button,
823
844
  {
845
+ fullWidth: true,
824
846
  accessibilityHint: labels.submit,
825
847
  accessibilityLabel: isSubmitting ? labels.submitting : labels.submit,
826
- accessibilityRole: "button",
827
- disabled: isSubmitting,
828
- style: submitButtonStyle,
848
+ label: labels.submit,
849
+ loading: isSubmitting,
829
850
  testID: withTestIdPrefix(AuthTestIds.loginSubmitButton, testIdPrefix),
830
- onPress: handleSubmit,
831
- children: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.onPrimary, size: "small" }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.primaryButtonText, children: labels.submit })
851
+ onPress: handleSubmit
832
852
  }
833
- ),
853
+ ) }),
834
854
  onSignUp !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
835
855
  reactNative.TouchableOpacity,
836
856
  {
@@ -4031,6 +4051,327 @@ function useDemoCredentials({
4031
4051
  [filled, prefillNonce, prefillIntent, credentials, accounts, handleUse]
4032
4052
  );
4033
4053
  }
4054
+
4055
+ // src/components/registerTestIds.ts
4056
+ var RegisterTestIds = {
4057
+ form: "register-form",
4058
+ firstNameInput: "register-first-name-input",
4059
+ lastNameInput: "register-last-name-input",
4060
+ usernameInput: "register-username-input",
4061
+ emailInput: "register-email-input",
4062
+ passwordInput: "register-password-input",
4063
+ confirmPasswordInput: "register-confirm-password-input",
4064
+ tenantNameInput: "register-tenant-name-input",
4065
+ submitButton: "register-submit-button",
4066
+ websiteInput: "register-website"
4067
+ };
4068
+ var FIELDS = [
4069
+ { key: "firstName", testId: RegisterTestIds.firstNameInput, autoComplete: "name" },
4070
+ { key: "lastName", testId: RegisterTestIds.lastNameInput, autoComplete: "name" },
4071
+ { key: "username", testId: RegisterTestIds.usernameInput, autoComplete: "username" },
4072
+ { key: "email", testId: RegisterTestIds.emailInput, autoComplete: "email", keyboardType: "email-address" },
4073
+ { key: "password", testId: RegisterTestIds.passwordInput, secure: true },
4074
+ { key: "confirmPassword", testId: RegisterTestIds.confirmPasswordInput, secure: true },
4075
+ { key: "tenantName", testId: RegisterTestIds.tenantNameInput }
4076
+ ];
4077
+ var OFF_SCREEN = -9999;
4078
+ var HONEYPOT_SIZE = 1;
4079
+ var HONEYPOT_PLACEHOLDER = "Website";
4080
+ var honeypotStyles = reactNative.StyleSheet.create({
4081
+ honeypot: {
4082
+ position: "absolute",
4083
+ left: OFF_SCREEN,
4084
+ top: OFF_SCREEN,
4085
+ width: HONEYPOT_SIZE,
4086
+ height: HONEYPOT_SIZE,
4087
+ opacity: 0
4088
+ }
4089
+ });
4090
+ function RegisterFormFields({
4091
+ values,
4092
+ labels,
4093
+ disabled,
4094
+ theme,
4095
+ styles: styles3,
4096
+ onChange,
4097
+ testIdPrefix
4098
+ }) {
4099
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4100
+ FIELDS.map((config) => {
4101
+ const copy = labels.fields[config.key];
4102
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.fieldGroup, children: [
4103
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.label, children: copy.label }),
4104
+ /* @__PURE__ */ jsxRuntime.jsx(
4105
+ reactNative.TextInput,
4106
+ {
4107
+ accessibilityHint: copy.hint,
4108
+ accessibilityLabel: copy.inputLabel,
4109
+ autoCapitalize: "none",
4110
+ autoComplete: config.autoComplete,
4111
+ autoCorrect: false,
4112
+ editable: !disabled,
4113
+ keyboardType: config.keyboardType ?? "default",
4114
+ placeholder: copy.placeholder,
4115
+ placeholderTextColor: theme.colors.textSecondary,
4116
+ secureTextEntry: config.secure === true,
4117
+ style: styles3.input,
4118
+ testID: withTestIdPrefix(config.testId, testIdPrefix),
4119
+ value: values[config.key],
4120
+ onChangeText: (text) => onChange(config.key, text)
4121
+ }
4122
+ )
4123
+ ] }, config.key);
4124
+ }),
4125
+ /* @__PURE__ */ jsxRuntime.jsx(
4126
+ reactNative.TextInput,
4127
+ {
4128
+ accessibilityElementsHidden: true,
4129
+ accessibilityRole: "none",
4130
+ autoComplete: "off",
4131
+ focusable: false,
4132
+ importantForAccessibility: "no-hide-descendants",
4133
+ placeholder: HONEYPOT_PLACEHOLDER,
4134
+ style: honeypotStyles.honeypot,
4135
+ testID: withTestIdPrefix(RegisterTestIds.websiteInput, testIdPrefix),
4136
+ value: values.website,
4137
+ onChangeText: (text) => onChange("website", text)
4138
+ }
4139
+ )
4140
+ ] });
4141
+ }
4142
+
4143
+ // src/hooks/RegisterErrorCode.ts
4144
+ var RegisterErrorCode = /* @__PURE__ */ ((RegisterErrorCode2) => {
4145
+ RegisterErrorCode2["MissingFields"] = "missingFields";
4146
+ RegisterErrorCode2["InvalidEmail"] = "invalidEmail";
4147
+ RegisterErrorCode2["WeakPassword"] = "weakPassword";
4148
+ RegisterErrorCode2["PasswordMismatch"] = "passwordMismatch";
4149
+ RegisterErrorCode2["UsernameTaken"] = "usernameTaken";
4150
+ RegisterErrorCode2["EmailTaken"] = "emailTaken";
4151
+ RegisterErrorCode2["RealmInvalid"] = "realmInvalid";
4152
+ RegisterErrorCode2["ValidationFailed"] = "validationFailed";
4153
+ RegisterErrorCode2["Failed"] = "failed";
4154
+ RegisterErrorCode2["InFlight"] = "inFlight";
4155
+ return RegisterErrorCode2;
4156
+ })(RegisterErrorCode || {});
4157
+
4158
+ // src/hooks/routeRegisterOutcome.ts
4159
+ function routeRegisterOutcome(outcome, handlers) {
4160
+ if (outcome.ok) {
4161
+ handlers.onSuccess(outcome.result);
4162
+ return;
4163
+ }
4164
+ if (outcome.error !== "inFlight" /* InFlight */) {
4165
+ handlers.onError?.(outcome.error);
4166
+ }
4167
+ }
4168
+
4169
+ // src/hooks/registerErrors.ts
4170
+ var MIN_PASSWORD_LENGTH = 8;
4171
+ var WHITESPACE_REGEX = /\s/;
4172
+ var EMAIL_PART_COUNT = 2;
4173
+ var PASSWORD_LETTER_REGEX = /[a-zA-Z]/;
4174
+ var PASSWORD_DIGIT_REGEX = /\d/;
4175
+ var PASSWORD_SPECIAL_REGEX = /[^a-zA-Z0-9]/;
4176
+ var SERVER_ERROR_CODES = [
4177
+ ["USERNAME_TAKEN", "usernameTaken" /* UsernameTaken */],
4178
+ ["EMAIL_TAKEN", "emailTaken" /* EmailTaken */],
4179
+ ["REALM_REQUIRED", "realmInvalid" /* RealmInvalid */],
4180
+ ["REALM_INVALID", "realmInvalid" /* RealmInvalid */]
4181
+ ];
4182
+ function isRegisterPasswordStrong(password) {
4183
+ if (password.length < MIN_PASSWORD_LENGTH) {
4184
+ return false;
4185
+ }
4186
+ if (!PASSWORD_LETTER_REGEX.test(password)) {
4187
+ return false;
4188
+ }
4189
+ if (!PASSWORD_DIGIT_REGEX.test(password)) {
4190
+ return false;
4191
+ }
4192
+ return PASSWORD_SPECIAL_REGEX.test(password);
4193
+ }
4194
+ function isEmailShaped(email) {
4195
+ if (WHITESPACE_REGEX.test(email)) {
4196
+ return false;
4197
+ }
4198
+ const parts = email.split("@");
4199
+ if (parts.length !== EMAIL_PART_COUNT) {
4200
+ return false;
4201
+ }
4202
+ const [local = "", domain = ""] = parts;
4203
+ return local !== "" && domain.slice(1, -1).includes(".");
4204
+ }
4205
+ function trimRegisterValues(values) {
4206
+ return {
4207
+ firstName: values.firstName.trim(),
4208
+ lastName: values.lastName.trim(),
4209
+ username: values.username.trim(),
4210
+ email: values.email.trim(),
4211
+ password: values.password,
4212
+ confirmPassword: values.confirmPassword,
4213
+ tenantName: values.tenantName.trim(),
4214
+ website: values.website.trim()
4215
+ };
4216
+ }
4217
+ function hasMissingRequiredField(values) {
4218
+ const required = [
4219
+ values.firstName,
4220
+ values.lastName,
4221
+ values.username,
4222
+ values.email,
4223
+ values.password,
4224
+ values.tenantName
4225
+ ];
4226
+ return required.some((value) => value === "");
4227
+ }
4228
+ function validateRegisterValues(values, passwordPolicy) {
4229
+ if (hasMissingRequiredField(values)) {
4230
+ return "missingFields" /* MissingFields */;
4231
+ }
4232
+ if (!isEmailShaped(values.email)) {
4233
+ return "invalidEmail" /* InvalidEmail */;
4234
+ }
4235
+ if (!passwordPolicy(values.password)) {
4236
+ return "weakPassword" /* WeakPassword */;
4237
+ }
4238
+ if (values.password !== values.confirmPassword) {
4239
+ return "passwordMismatch" /* PasswordMismatch */;
4240
+ }
4241
+ return null;
4242
+ }
4243
+ function messageOf(error) {
4244
+ if (error instanceof Error) {
4245
+ return error.message;
4246
+ }
4247
+ return typeof error === "string" ? error : "";
4248
+ }
4249
+ function mapRegisterErrorCode(error) {
4250
+ const message = messageOf(error);
4251
+ if (message === "") {
4252
+ return "failed" /* Failed */;
4253
+ }
4254
+ const match = SERVER_ERROR_CODES.find(([code]) => message.includes(code));
4255
+ return match === void 0 ? "validationFailed" /* ValidationFailed */ : match[1];
4256
+ }
4257
+
4258
+ // src/hooks/useRegisterForm.ts
4259
+ var EMPTY_VALUES = {
4260
+ firstName: "",
4261
+ lastName: "",
4262
+ username: "",
4263
+ email: "",
4264
+ password: "",
4265
+ confirmPassword: "",
4266
+ tenantName: "",
4267
+ website: ""
4268
+ };
4269
+ function toSubmitValues(values) {
4270
+ return {
4271
+ firstName: values.firstName,
4272
+ lastName: values.lastName,
4273
+ username: values.username,
4274
+ email: values.email,
4275
+ password: values.password,
4276
+ tenantName: values.tenantName,
4277
+ website: values.website
4278
+ };
4279
+ }
4280
+ function useRegisterForm({
4281
+ onSubmit,
4282
+ passwordPolicy = isRegisterPasswordStrong,
4283
+ onSubmitAttempt
4284
+ }) {
4285
+ const [state, setState] = react.useState(EMPTY_VALUES);
4286
+ const [isSubmitting, setIsSubmitting] = react.useState(false);
4287
+ const [error, setError] = react.useState(null);
4288
+ const inFlight = react.useRef(false);
4289
+ const setField = react.useCallback((field2, value) => {
4290
+ setState((prev) => ({ ...prev, [field2]: value }));
4291
+ }, []);
4292
+ const submit = react.useCallback(async () => {
4293
+ if (inFlight.current) {
4294
+ return { ok: false, error: "inFlight" /* InFlight */ };
4295
+ }
4296
+ onSubmitAttempt?.();
4297
+ const trimmed = trimRegisterValues(state);
4298
+ const refusal = trimmed.website === "" ? validateRegisterValues(trimmed, passwordPolicy) : "failed" /* Failed */;
4299
+ if (refusal !== null) {
4300
+ setError(refusal);
4301
+ return { ok: false, error: refusal };
4302
+ }
4303
+ inFlight.current = true;
4304
+ setError(null);
4305
+ setIsSubmitting(true);
4306
+ try {
4307
+ const result = await onSubmit(toSubmitValues(trimmed));
4308
+ return { ok: true, result };
4309
+ } catch (caught) {
4310
+ const code = mapRegisterErrorCode(caught);
4311
+ setError(code);
4312
+ return { ok: false, error: code };
4313
+ } finally {
4314
+ inFlight.current = false;
4315
+ setIsSubmitting(false);
4316
+ }
4317
+ }, [onSubmit, onSubmitAttempt, passwordPolicy, state]);
4318
+ return { state, setField, submit, isSubmitting, error };
4319
+ }
4320
+ function RegisterForm({
4321
+ onSubmit,
4322
+ onSuccess,
4323
+ onError,
4324
+ onSubmitAttempt,
4325
+ passwordPolicy,
4326
+ labels: labelsProp,
4327
+ theme: themeProp,
4328
+ footer,
4329
+ testIdPrefix
4330
+ }) {
4331
+ const theme = useAuthTheme(themeProp);
4332
+ const styles3 = useAuthStyles(theme);
4333
+ const labels = react.useMemo(
4334
+ () => ({ ...DEFAULT_REGISTER_LABELS, ...labelsProp }),
4335
+ [labelsProp]
4336
+ );
4337
+ const { state, setField, submit, isSubmitting } = useRegisterForm({
4338
+ onSubmit,
4339
+ onSubmitAttempt,
4340
+ passwordPolicy
4341
+ });
4342
+ const handlePress = react.useCallback(async () => {
4343
+ routeRegisterOutcome(await submit(), { onSuccess, onError });
4344
+ }, [onError, onSuccess, submit]);
4345
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.screen, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.card, testID: withTestIdPrefix(RegisterTestIds.form, testIdPrefix), children: [
4346
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.title, children: labels.title }),
4347
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.subtitle, children: labels.subtitle }),
4348
+ /* @__PURE__ */ jsxRuntime.jsx(
4349
+ RegisterFormFields,
4350
+ {
4351
+ disabled: isSubmitting,
4352
+ labels,
4353
+ styles: styles3,
4354
+ testIdPrefix,
4355
+ theme,
4356
+ values: state,
4357
+ onChange: setField
4358
+ }
4359
+ ),
4360
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.fieldGroup, children: /* @__PURE__ */ jsxRuntime.jsx(
4361
+ uiButtons.Button,
4362
+ {
4363
+ fullWidth: true,
4364
+ accessibilityHint: labels.submitHint,
4365
+ accessibilityLabel: isSubmitting ? labels.submitting : labels.submit,
4366
+ label: labels.submit,
4367
+ loading: isSubmitting,
4368
+ testID: withTestIdPrefix(RegisterTestIds.submitButton, testIdPrefix),
4369
+ onPress: handlePress
4370
+ }
4371
+ ) }),
4372
+ footer
4373
+ ] }) });
4374
+ }
4034
4375
  async function lazyFetchHttpClient(request) {
4035
4376
  const fetchImpl = typeof fetch === "function" ? fetch.bind(globalThis) : void 0;
4036
4377
  if (fetchImpl === void 0) {
@@ -4198,6 +4539,7 @@ exports.DEFAULT_PASSKEY_SETTINGS_LABELS = DEFAULT_PASSKEY_SETTINGS_LABELS;
4198
4539
  exports.DEFAULT_PIN_LABELS = DEFAULT_PIN_LABELS;
4199
4540
  exports.DEFAULT_PREFERRED_METHOD_HINT_LABELS = DEFAULT_PREFERRED_METHOD_HINT_LABELS;
4200
4541
  exports.DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS = DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS;
4542
+ exports.DEFAULT_REGISTER_LABELS = DEFAULT_REGISTER_LABELS;
4201
4543
  exports.DEFAULT_RESET_PASSWORD_LABELS = DEFAULT_RESET_PASSWORD_LABELS;
4202
4544
  exports.DEVICE_PIN_ALLOWED_DIGITS = DEVICE_PIN_ALLOWED_DIGITS;
4203
4545
  exports.DEVICE_PIN_DEFAULT_DIGITS = DEVICE_PIN_DEFAULT_DIGITS;
@@ -4227,6 +4569,9 @@ exports.PinForm = PinForm;
4227
4569
  exports.PreferredMethodHint = PreferredMethodHint;
4228
4570
  exports.PreferredMethodSettingsCard = PreferredMethodSettingsCard;
4229
4571
  exports.PrefillIntent = PrefillIntent;
4572
+ exports.RegisterErrorCode = RegisterErrorCode;
4573
+ exports.RegisterForm = RegisterForm;
4574
+ exports.RegisterTestIds = RegisterTestIds;
4230
4575
  exports.ResetPasswordError = ResetPasswordError;
4231
4576
  exports.ResetPasswordForm = ResetPasswordForm;
4232
4577
  exports.cachePreferredMethod = cachePreferredMethod;
@@ -4238,6 +4583,7 @@ exports.createPreferencesClient = createPreferencesClient;
4238
4583
  exports.defaultAuthTheme = defaultAuthTheme;
4239
4584
  exports.isAllowedPinDigits = isAllowedPinDigits;
4240
4585
  exports.isPasswordValid = isPasswordValid;
4586
+ exports.isRegisterPasswordStrong = isRegisterPasswordStrong;
4241
4587
  exports.isValidForgotPasswordEmail = isValidForgotPasswordEmail;
4242
4588
  exports.performBffLogout = performBffLogout;
4243
4589
  exports.readCachedPreferredMethod = readCachedPreferredMethod;
@@ -4266,6 +4612,7 @@ exports.useOtpLogin = useOtpLogin;
4266
4612
  exports.usePasskeyCeremony = usePasskeyCeremony;
4267
4613
  exports.usePinLogin = usePinLogin;
4268
4614
  exports.usePreferredMethodSetting = usePreferredMethodSetting;
4615
+ exports.useRegisterForm = useRegisterForm;
4269
4616
  exports.useResetPasswordForm = useResetPasswordForm;
4270
4617
  exports.validatePasswordPolicy = validatePasswordPolicy;
4271
4618
  exports.withTestIdPrefix = withTestIdPrefix;