@abpjs/account 0.8.0 → 1.0.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.
@@ -31,8 +31,10 @@ export interface RegisterFormProps {
31
31
  *
32
32
  * This is the React equivalent of Angular's RegisterComponent.
33
33
  *
34
- * Note: In v0.7.6, the onSubmit only validates the form but does not
35
- * make an API call. This matches the Angular implementation.
34
+ * In v0.9.0, this component now makes API calls for registration using
35
+ * AccountService and automatically logs in the user after successful registration.
36
+ *
37
+ * @since 0.9.0 - Now uses AccountService for registration
36
38
  *
37
39
  * @example
38
40
  * ```tsx
@@ -46,5 +48,5 @@ export interface RegisterFormProps {
46
48
  * }
47
49
  * ```
48
50
  */
49
- export declare function RegisterForm({ showTenantBox, showLoginLink, loginUrl, onRegisterSuccess, onRegisterError: _onRegisterError, }: RegisterFormProps): import("react/jsx-runtime").JSX.Element;
51
+ export declare function RegisterForm({ showTenantBox, showLoginLink, loginUrl, onRegisterSuccess, onRegisterError, }: RegisterFormProps): import("react/jsx-runtime").JSX.Element;
50
52
  export default RegisterForm;
@@ -14,8 +14,10 @@ export interface TenantBoxProps {
14
14
  * Displays the currently selected tenant and allows users to switch tenants.
15
15
  * This is a direct translation of Angular's TenantBoxComponent.
16
16
  *
17
- * Note: In v0.7.6, this component stores the tenant selection locally
18
- * and does not integrate with the backend tenant resolution API.
17
+ * In v0.9.0, this component integrates with the backend tenant resolution API
18
+ * and properly updates the session state.
19
+ *
20
+ * @since 0.9.0 - Now uses AccountService for tenant lookup
19
21
  */
20
22
  export declare function TenantBox({ containerStyle }: TenantBoxProps): import("react/jsx-runtime").JSX.Element;
21
23
  export default TenantBox;
@@ -1,2 +1,3 @@
1
1
  export { usePasswordFlow, type PasswordFlowOptions } from './usePasswordFlow';
2
2
  export { useAccountOptions } from '../providers';
3
+ export { useAccountService } from './useAccountService';
@@ -0,0 +1,27 @@
1
+ import { AccountService } from '../services/account.service';
2
+ /**
3
+ * Hook to access the AccountService
4
+ *
5
+ * Provides account-related API operations like tenant lookup and registration.
6
+ *
7
+ * @returns AccountService instance
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * function MyComponent() {
12
+ * const accountService = useAccountService();
13
+ *
14
+ * const handleFindTenant = async (name: string) => {
15
+ * const result = await accountService.findTenant(name);
16
+ * if (result.success) {
17
+ * console.log('Found tenant:', result.tenantId);
18
+ * }
19
+ * };
20
+ *
21
+ * return <button onClick={() => handleFindTenant('myTenant')}>Find</button>;
22
+ * }
23
+ * ```
24
+ *
25
+ * @since 0.9.0
26
+ */
27
+ export declare function useAccountService(): AccountService;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * @abpjs/account
3
3
  * ABP Framework Account module for React
4
- * Translated from @abp/ng.account v0.8.0
4
+ * Translated from @abp/ng.account v1.0.0
5
5
  */
6
6
  export * from './models';
7
+ export * from './services';
7
8
  export * from './providers';
8
9
  export * from './hooks';
9
10
  export * from './components';
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  ACCOUNT_PATHS: () => ACCOUNT_PATHS,
24
24
  ACCOUNT_ROUTES: () => ACCOUNT_ROUTES,
25
25
  AccountProvider: () => AccountProvider,
26
+ AccountService: () => AccountService,
26
27
  DEFAULT_REDIRECT_URL: () => DEFAULT_REDIRECT_URL,
27
28
  LoginForm: () => LoginForm,
28
29
  LoginPage: () => LoginPage,
@@ -31,10 +32,42 @@ __export(index_exports, {
31
32
  TenantBox: () => TenantBox,
32
33
  useAccountContext: () => useAccountContext,
33
34
  useAccountOptions: () => useAccountOptions,
35
+ useAccountService: () => useAccountService,
34
36
  usePasswordFlow: () => usePasswordFlow
35
37
  });
36
38
  module.exports = __toCommonJS(index_exports);
37
39
 
40
+ // src/services/account.service.ts
41
+ var AccountService = class {
42
+ constructor(rest) {
43
+ this.rest = rest;
44
+ }
45
+ /**
46
+ * Find a tenant by name
47
+ *
48
+ * @param tenantName - The name of the tenant to find
49
+ * @returns Promise resolving to TenantIdResponse
50
+ */
51
+ findTenant(tenantName) {
52
+ return this.rest.get(
53
+ `/api/abp/multi-tenancy/tenants/by-name/${tenantName}`
54
+ );
55
+ }
56
+ /**
57
+ * Register a new user
58
+ *
59
+ * @param body - The registration request data
60
+ * @returns Promise resolving to RegisterResponse
61
+ */
62
+ register(body) {
63
+ return this.rest.post(
64
+ "/api/account/register",
65
+ body,
66
+ { skipHandleError: true }
67
+ );
68
+ }
69
+ };
70
+
38
71
  // src/providers/AccountProvider.tsx
39
72
  var import_react = require("react");
40
73
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -173,40 +206,80 @@ function usePasswordFlow() {
173
206
  };
174
207
  }
175
208
 
176
- // src/components/TenantBox/TenantBox.tsx
209
+ // src/hooks/useAccountService.ts
177
210
  var import_react3 = require("react");
178
- var import_react_hook_form = require("react-hook-form");
179
211
  var import_core2 = require("@abpjs/core");
212
+ function useAccountService() {
213
+ const restService = (0, import_core2.useRestService)();
214
+ return (0, import_react3.useMemo)(() => new AccountService(restService), [restService]);
215
+ }
216
+
217
+ // src/components/TenantBox/TenantBox.tsx
218
+ var import_react4 = require("react");
219
+ var import_react_redux = require("react-redux");
220
+ var import_core3 = require("@abpjs/core");
180
221
  var import_theme_shared = require("@abpjs/theme-shared");
181
- var import_react4 = require("@chakra-ui/react");
222
+ var import_react5 = require("@chakra-ui/react");
182
223
  var import_jsx_runtime2 = require("react/jsx-runtime");
183
224
  function TenantBox({ containerStyle }) {
184
- const { t } = (0, import_core2.useLocalization)();
185
- const [selected, setSelected] = (0, import_react3.useState)(null);
186
- const [modalVisible, setModalVisible] = (0, import_react3.useState)(false);
187
- const { register, handleSubmit, reset } = (0, import_react_hook_form.useForm)({
188
- defaultValues: {
189
- name: ""
190
- }
191
- });
192
- const openModal = (0, import_react3.useCallback)(() => {
193
- reset({ name: selected?.name || "" });
194
- setModalVisible(true);
195
- }, [selected, reset]);
196
- const onSwitch = (0, import_react3.useCallback)(() => {
197
- setSelected(null);
198
- openModal();
199
- }, [openModal]);
200
- const onSave = (0, import_react3.useCallback)((data) => {
201
- setSelected(data.name ? data : null);
202
- setModalVisible(false);
203
- }, []);
204
- const onClose = (0, import_react3.useCallback)(() => {
205
- setModalVisible(false);
225
+ const { t } = (0, import_core3.useLocalization)();
226
+ const dispatch = (0, import_react_redux.useDispatch)();
227
+ const accountService = useAccountService();
228
+ const toaster = (0, import_theme_shared.useToaster)();
229
+ const currentTenant = (0, import_react_redux.useSelector)(import_core3.selectTenant);
230
+ const [tenantName, setTenantName] = (0, import_react4.useState)("");
231
+ const [isModalVisible, setIsModalVisible] = (0, import_react4.useState)(false);
232
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
233
+ (0, import_react4.useEffect)(() => {
234
+ setTenantName(currentTenant?.name || "");
235
+ }, [currentTenant]);
236
+ const onSwitch = (0, import_react4.useCallback)(() => {
237
+ setIsModalVisible(true);
206
238
  }, []);
239
+ const save = (0, import_react4.useCallback)(async () => {
240
+ if (tenantName) {
241
+ setIsLoading(true);
242
+ try {
243
+ const { success, tenantId } = await accountService.findTenant(tenantName);
244
+ if (success) {
245
+ const newTenant = {
246
+ id: tenantId,
247
+ name: tenantName
248
+ };
249
+ dispatch(import_core3.sessionActions.setTenant(newTenant));
250
+ setIsModalVisible(false);
251
+ } else {
252
+ toaster.error(
253
+ t("AbpUiMultiTenancy::GivenTenantIsNotAvailable", tenantName) || `Tenant "${tenantName}" is not available`,
254
+ t("AbpUi::Error") || "Error"
255
+ );
256
+ setTenantName("");
257
+ }
258
+ } catch (err) {
259
+ const errorMessage = err?.error?.error_description || err?.error?.error?.message || t("AbpUi::DefaultErrorMessage") || "An error occurred";
260
+ toaster.error(errorMessage, t("AbpUi::Error") || "Error");
261
+ } finally {
262
+ setIsLoading(false);
263
+ }
264
+ } else {
265
+ dispatch(import_core3.sessionActions.setTenant({ id: "", name: "" }));
266
+ setIsModalVisible(false);
267
+ }
268
+ }, [tenantName, accountService, dispatch, toaster, t]);
269
+ const onClose = (0, import_react4.useCallback)(() => {
270
+ setTenantName(currentTenant?.name || "");
271
+ setIsModalVisible(false);
272
+ }, [currentTenant]);
273
+ const handleSubmit = (0, import_react4.useCallback)(
274
+ (e) => {
275
+ e.preventDefault();
276
+ save();
277
+ },
278
+ [save]
279
+ );
207
280
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
208
281
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
209
- import_react4.Box,
282
+ import_react5.Box,
210
283
  {
211
284
  className: "tenant-switch-box",
212
285
  bg: "gray.100",
@@ -217,15 +290,15 @@ function TenantBox({ containerStyle }) {
217
290
  borderRadius: "md",
218
291
  style: containerStyle,
219
292
  children: [
220
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react4.Text, { as: "span", color: "gray.600", children: [
293
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react5.Text, { as: "span", color: "gray.600", children: [
221
294
  t("AbpUiMultiTenancy::Tenant"),
222
295
  ":",
223
296
  " "
224
297
  ] }),
225
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Text, { as: "strong", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Text, { as: "i", children: selected?.name || t("AbpUiMultiTenancy::NotSelected") }) }),
298
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react5.Text, { as: "strong", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react5.Text, { as: "i", children: currentTenant?.name || t("AbpUiMultiTenancy::NotSelected") }) }),
226
299
  " (",
227
300
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
228
- import_react4.Link,
301
+ import_react5.Link,
229
302
  {
230
303
  id: "abp-tenant-switch-link",
231
304
  color: "gray.700",
@@ -242,20 +315,41 @@ function TenantBox({ containerStyle }) {
242
315
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
243
316
  import_theme_shared.Modal,
244
317
  {
245
- visible: modalVisible,
246
- onVisibleChange: setModalVisible,
318
+ visible: isModalVisible,
319
+ onVisibleChange: setIsModalVisible,
247
320
  size: "md",
248
321
  header: t("AbpUiMultiTenancy::SwitchTenant") || "Switch Tenant",
249
322
  footer: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
250
323
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_theme_shared.Button, { variant: "ghost", colorPalette: "gray", onClick: onClose, children: t("AbpTenantManagement::Cancel") }),
251
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_theme_shared.Button, { colorPalette: "blue", onClick: handleSubmit(onSave), children: [
252
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, {}),
253
- t("AbpTenantManagement::Save")
254
- ] })
324
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
325
+ import_theme_shared.Button,
326
+ {
327
+ colorPalette: "blue",
328
+ onClick: save,
329
+ loading: isLoading,
330
+ loadingText: t("AbpTenantManagement::Save"),
331
+ children: [
332
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, {}),
333
+ t("AbpTenantManagement::Save")
334
+ ]
335
+ }
336
+ )
255
337
  ] }),
256
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("form", { onSubmit: handleSubmit(onSave), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react4.VStack, { gap: 4, align: "stretch", children: [
257
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_theme_shared.FormField, { label: t("AbpUiMultiTenancy::Name"), htmlFor: "tenant-name", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Input, { id: "tenant-name", type: "text", ...register("name") }) }),
258
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Text, { fontSize: "sm", color: "gray.600", children: t("AbpUiMultiTenancy::SwitchTenantHint") })
338
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react5.VStack, { gap: 4, align: "stretch", children: [
339
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react5.Box, { children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react5.Text, { as: "label", mb: 1, display: "block", children: t("AbpUiMultiTenancy::Name") }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
342
+ import_react5.Input,
343
+ {
344
+ id: "tenant-name",
345
+ type: "text",
346
+ value: tenantName,
347
+ onChange: (e) => setTenantName(e.target.value),
348
+ autoFocus: true
349
+ }
350
+ )
351
+ ] }),
352
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react5.Text, { fontSize: "sm", color: "gray.600", children: t("AbpUiMultiTenancy::SwitchTenantHint") })
259
353
  ] }) })
260
354
  }
261
355
  )
@@ -280,14 +374,14 @@ function CheckIcon() {
280
374
  }
281
375
 
282
376
  // src/components/LoginForm/LoginForm.tsx
283
- var import_react_hook_form2 = require("react-hook-form");
377
+ var import_react_hook_form = require("react-hook-form");
284
378
  var import_zod = require("@hookform/resolvers/zod");
285
379
  var import_zod2 = require("zod");
286
380
  var import_react_router_dom2 = require("react-router-dom");
287
- var import_core3 = require("@abpjs/core");
381
+ var import_core4 = require("@abpjs/core");
288
382
  var import_theme_shared2 = require("@abpjs/theme-shared");
289
- var import_react5 = require("@chakra-ui/react");
290
383
  var import_react6 = require("@chakra-ui/react");
384
+ var import_react7 = require("@chakra-ui/react");
291
385
  var import_lu = require("react-icons/lu");
292
386
  var import_jsx_runtime3 = require("react/jsx-runtime");
293
387
  var loginSchema = import_zod2.z.object({
@@ -302,13 +396,13 @@ function LoginForm({
302
396
  onLoginSuccess,
303
397
  onLoginError
304
398
  }) {
305
- const { t } = (0, import_core3.useLocalization)();
399
+ const { t } = (0, import_core4.useLocalization)();
306
400
  const { login, isLoading, error, clearError } = usePasswordFlow();
307
401
  const {
308
402
  register,
309
403
  handleSubmit,
310
404
  formState: { errors, isSubmitting }
311
- } = (0, import_react_hook_form2.useForm)({
405
+ } = (0, import_react_hook_form.useForm)({
312
406
  resolver: (0, import_zod.zodResolver)(loginSchema),
313
407
  defaultValues: {
314
408
  username: "",
@@ -328,16 +422,16 @@ function LoginForm({
328
422
  }
329
423
  };
330
424
  const isFormLoading = isLoading || isSubmitting;
331
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Flex, { height: "full", flex: "1", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Box, { flex: "1.5", py: { base: "24", md: "32" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Container, { maxW: "md", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react6.Stack, { gap: "8", children: [
332
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Show, { when: showTenantBox, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TenantBox, {}) }),
333
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Stack, { gap: { base: "2", md: "3" }, textAlign: "center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Heading, { size: { base: "2xl", md: "3xl" }, children: t("AbpAccount::Login") }) }),
425
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Flex, { height: "full", flex: "1", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Box, { flex: "1.5", py: { base: "24", md: "32" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Container, { maxW: "md", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react7.Stack, { gap: "8", children: [
426
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Show, { when: showTenantBox, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TenantBox, {}) }),
427
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Stack, { gap: { base: "2", md: "3" }, textAlign: "center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Heading, { size: { base: "2xl", md: "3xl" }, children: t("AbpAccount::Login") }) }),
334
428
  error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_theme_shared2.Alert, { status: "error", children: error }),
335
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("form", { onSubmit: handleSubmit(onSubmit), noValidate: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react6.Stack, { gap: "6", children: [
336
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react6.Stack, { gap: "5", children: [
337
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react6.Field.Root, { invalid: !!errors.username, children: [
338
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Field.Label, { children: t("AbpAccount::UserNameOrEmailAddress") }),
339
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lu.LuMail, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
340
- import_react5.Input,
429
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("form", { onSubmit: handleSubmit(onSubmit), noValidate: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react7.Stack, { gap: "6", children: [
430
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react7.Stack, { gap: "5", children: [
431
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react7.Field.Root, { invalid: !!errors.username, children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Field.Label, { children: t("AbpAccount::UserNameOrEmailAddress") }),
433
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lu.LuMail, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
434
+ import_react6.Input,
341
435
  {
342
436
  id: "login-input-user-name-or-email-address",
343
437
  type: "text",
@@ -346,12 +440,12 @@ function LoginForm({
346
440
  ...register("username")
347
441
  }
348
442
  ) }),
349
- errors.username && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Field.ErrorText, { children: errors.username.message })
443
+ errors.username && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Field.ErrorText, { children: errors.username.message })
350
444
  ] }),
351
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react6.Field.Root, { invalid: !!errors.password, children: [
352
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Field.Label, { children: t("AbpAccount::Password") }),
353
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lu.LuLock, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
354
- import_react5.Input,
445
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react7.Field.Root, { invalid: !!errors.password, children: [
446
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Field.Label, { children: t("AbpAccount::Password") }),
447
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lu.LuLock, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
448
+ import_react6.Input,
355
449
  {
356
450
  id: "login-input-password",
357
451
  type: "password",
@@ -360,7 +454,7 @@ function LoginForm({
360
454
  ...register("password")
361
455
  }
362
456
  ) }),
363
- errors.password && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Field.ErrorText, { children: errors.password.message })
457
+ errors.password && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Field.ErrorText, { children: errors.password.message })
364
458
  ] }),
365
459
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_theme_shared2.Checkbox, { id: "login-input-remember-me", ...register("remember"), children: t("AbpAccount::RememberMe") }),
366
460
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -373,25 +467,26 @@ function LoginForm({
373
467
  children: t("AbpAccount::Login")
374
468
  }
375
469
  ),
376
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Link, { variant: "plain", children: t("AbpAccount::ForgotPassword") })
470
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Link, { variant: "plain", children: t("AbpAccount::ForgotPassword") })
377
471
  ] }),
378
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Show, { when: showRegisterLink, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Card.Root, { size: "sm", mt: "10", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Card.Body, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react5.HStack, { textStyle: "sm", children: [
379
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Text, { children: t("AbpAccount::AreYouANewUser") }),
380
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Link, { asChild: true, variant: "underline", fontWeight: "semibold", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_router_dom2.Link, { to: registerUrl, children: t("AbpAccount::Register") }) })
472
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Show, { when: showRegisterLink, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Card.Root, { size: "sm", mt: "10", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Card.Body, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react6.HStack, { textStyle: "sm", children: [
473
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.Text, { children: t("AbpAccount::AreYouANewUser") }),
474
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react6.Link, { asChild: true, variant: "underline", fontWeight: "semibold", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_router_dom2.Link, { to: registerUrl, children: t("AbpAccount::Register") }) })
381
475
  ] }) }) }) })
382
476
  ] }) })
383
477
  ] }) }) }) });
384
478
  }
385
479
 
386
480
  // src/components/RegisterForm/RegisterForm.tsx
387
- var import_react_hook_form3 = require("react-hook-form");
481
+ var import_react8 = require("react");
482
+ var import_react_hook_form2 = require("react-hook-form");
388
483
  var import_zod3 = require("@hookform/resolvers/zod");
389
484
  var import_zod4 = require("zod");
390
485
  var import_react_router_dom3 = require("react-router-dom");
391
- var import_core4 = require("@abpjs/core");
486
+ var import_core5 = require("@abpjs/core");
392
487
  var import_theme_shared3 = require("@abpjs/theme-shared");
393
- var import_react7 = require("@chakra-ui/react");
394
- var import_react8 = require("@chakra-ui/react");
488
+ var import_react9 = require("@chakra-ui/react");
489
+ var import_react10 = require("@chakra-ui/react");
395
490
  var import_lu2 = require("react-icons/lu");
396
491
  var import_jsx_runtime4 = require("react/jsx-runtime");
397
492
  var passwordValidation = {
@@ -422,16 +517,20 @@ function RegisterForm({
422
517
  showLoginLink = true,
423
518
  loginUrl = "/account/login",
424
519
  onRegisterSuccess,
425
- // Note: onRegisterError will be used when registration API is implemented
426
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
427
- onRegisterError: _onRegisterError
520
+ onRegisterError
428
521
  }) {
429
- const { t } = (0, import_core4.useLocalization)();
522
+ const { t } = (0, import_core5.useLocalization)();
523
+ const navigate = (0, import_react_router_dom3.useNavigate)();
524
+ const accountService = useAccountService();
525
+ const toaster = (0, import_theme_shared3.useToaster)();
526
+ const userManager = (0, import_core5.useUserManager)();
527
+ const { store, applicationConfigurationService } = (0, import_core5.useAbp)();
528
+ const [inProgress, setInProgress] = (0, import_react8.useState)(false);
430
529
  const {
431
530
  register,
432
531
  handleSubmit,
433
- formState: { errors, isSubmitting }
434
- } = (0, import_react_hook_form3.useForm)({
532
+ formState: { errors }
533
+ } = (0, import_react_hook_form2.useForm)({
435
534
  resolver: (0, import_zod3.zodResolver)(registerSchema),
436
535
  defaultValues: {
437
536
  username: "",
@@ -440,18 +539,59 @@ function RegisterForm({
440
539
  }
441
540
  });
442
541
  const onSubmit = async (data) => {
443
- console.log("Register form submitted (no API call in v0.7.6):", data);
444
- onRegisterSuccess?.();
542
+ setInProgress(true);
543
+ const newUser = {
544
+ userName: data.username,
545
+ password: data.password,
546
+ emailAddress: data.email,
547
+ appName: "React"
548
+ };
549
+ try {
550
+ await accountService.register(newUser);
551
+ if (userManager) {
552
+ try {
553
+ await userManager.signinResourceOwnerCredentials({
554
+ username: newUser.userName,
555
+ password: newUser.password
556
+ });
557
+ const config = await applicationConfigurationService.getConfiguration();
558
+ store.dispatch(import_core5.configActions.setApplicationConfiguration(config));
559
+ navigate("/");
560
+ onRegisterSuccess?.();
561
+ } catch (loginErr) {
562
+ console.warn("Auto-login failed after registration:", loginErr);
563
+ toaster.success(
564
+ t("AbpAccount::SuccessfullyRegistered") || "Successfully registered! Please log in.",
565
+ t("AbpAccount::Success") || "Success"
566
+ );
567
+ navigate(loginUrl);
568
+ onRegisterSuccess?.();
569
+ }
570
+ } else {
571
+ toaster.success(
572
+ t("AbpAccount::SuccessfullyRegistered") || "Successfully registered! Please log in.",
573
+ t("AbpAccount::Success") || "Success"
574
+ );
575
+ navigate(loginUrl);
576
+ onRegisterSuccess?.();
577
+ }
578
+ } catch (err) {
579
+ const errorMessage = err?.error?.error_description || err?.error?.error?.message || t("AbpAccount::DefaultErrorMessage") || "An error occurred";
580
+ toaster.error(errorMessage, t("AbpUi::Error") || "Error", { life: 7e3 });
581
+ onRegisterError?.(errorMessage);
582
+ } finally {
583
+ setInProgress(false);
584
+ }
445
585
  };
446
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Flex, { height: "full", flex: "1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react7.Box, { flex: "1.5", py: { base: "24", md: "32" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Container, { maxW: "md", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react8.Stack, { gap: "8", children: [
447
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react7.Show, { when: showTenantBox, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TenantBox, {}) }),
448
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Stack, { gap: { base: "2", md: "3" }, textAlign: "center", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react7.Heading, { size: { base: "2xl", md: "3xl" }, children: t("AbpAccount::Register") }) }),
449
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("form", { onSubmit: handleSubmit(onSubmit), noValidate: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react8.Stack, { gap: "6", children: [
450
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react8.Stack, { gap: "5", children: [
451
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react8.Field.Root, { invalid: !!errors.username, children: [
452
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Field.Label, { children: t("AbpAccount::UserName") }),
453
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lu2.LuUser, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
454
- import_react7.Input,
586
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Flex, { height: "full", flex: "1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react9.Box, { flex: "1.5", py: { base: "24", md: "32" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Container, { maxW: "md", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react10.Stack, { gap: "8", children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react9.Show, { when: showTenantBox, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TenantBox, {}) }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Stack, { gap: { base: "2", md: "3" }, textAlign: "center", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react9.Heading, { size: { base: "2xl", md: "3xl" }, children: t("AbpAccount::Register") }) }),
589
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("form", { onSubmit: handleSubmit(onSubmit), noValidate: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react10.Stack, { gap: "6", children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react10.Stack, { gap: "5", children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react10.Field.Root, { invalid: !!errors.username, children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Field.Label, { children: t("AbpAccount::UserName") }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lu2.LuUser, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
594
+ import_react9.Input,
455
595
  {
456
596
  id: "input-user-name",
457
597
  type: "text",
@@ -461,12 +601,12 @@ function RegisterForm({
461
601
  ...register("username")
462
602
  }
463
603
  ) }),
464
- errors.username && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Field.ErrorText, { children: errors.username.message })
604
+ errors.username && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Field.ErrorText, { children: errors.username.message })
465
605
  ] }),
466
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react8.Field.Root, { invalid: !!errors.email, children: [
467
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Field.Label, { children: t("AbpAccount::EmailAddress") }),
468
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lu2.LuMail, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
469
- import_react7.Input,
606
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react10.Field.Root, { invalid: !!errors.email, children: [
607
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Field.Label, { children: t("AbpAccount::EmailAddress") }),
608
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lu2.LuMail, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
609
+ import_react9.Input,
470
610
  {
471
611
  id: "input-email-address",
472
612
  type: "email",
@@ -475,12 +615,12 @@ function RegisterForm({
475
615
  ...register("email")
476
616
  }
477
617
  ) }),
478
- errors.email && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Field.ErrorText, { children: errors.email.message })
618
+ errors.email && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Field.ErrorText, { children: errors.email.message })
479
619
  ] }),
480
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react8.Field.Root, { invalid: !!errors.password, children: [
481
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Field.Label, { children: t("AbpAccount::Password") }),
482
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lu2.LuLock, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
483
- import_react7.Input,
620
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react10.Field.Root, { invalid: !!errors.password, children: [
621
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Field.Label, { children: t("AbpAccount::Password") }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.InputGroup, { startElement: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lu2.LuLock, {}), width: "full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
623
+ import_react9.Input,
484
624
  {
485
625
  id: "input-password",
486
626
  type: "password",
@@ -489,69 +629,71 @@ function RegisterForm({
489
629
  ...register("password")
490
630
  }
491
631
  ) }),
492
- errors.password && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Field.ErrorText, { children: errors.password.message })
632
+ errors.password && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Field.ErrorText, { children: errors.password.message })
493
633
  ] }),
494
634
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
495
635
  import_theme_shared3.Button,
496
636
  {
497
637
  type: "submit",
498
638
  colorPalette: "blue",
499
- loading: isSubmitting,
639
+ loading: inProgress,
500
640
  loadingText: t("AbpAccount::Register"),
501
641
  children: t("AbpAccount::Register")
502
642
  }
503
643
  )
504
644
  ] }),
505
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react7.Show, { when: showLoginLink, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Card.Root, { size: "sm", mt: "10", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Card.Body, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react7.HStack, { textStyle: "sm", children: [
506
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Text, { children: t("AbpAccount::AlreadyRegistered") }),
507
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react7.Link, { asChild: true, variant: "underline", fontWeight: "semibold", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_router_dom3.Link, { to: loginUrl, children: t("AbpAccount::Login") }) })
645
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react9.Show, { when: showLoginLink, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Card.Root, { size: "sm", mt: "10", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Card.Body, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react9.HStack, { textStyle: "sm", children: [
646
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react10.Text, { children: t("AbpAccount::AlreadyRegistered") }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react9.Link, { asChild: true, variant: "underline", fontWeight: "semibold", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_router_dom3.Link, { to: loginUrl, children: t("AbpAccount::Login") }) })
508
648
  ] }) }) }) })
509
649
  ] }) })
510
650
  ] }) }) }) });
511
651
  }
512
652
 
513
653
  // src/pages/LoginPage.tsx
514
- var import_react9 = require("@chakra-ui/react");
654
+ var import_react11 = require("@chakra-ui/react");
515
655
  var import_jsx_runtime5 = require("react/jsx-runtime");
516
656
  function LoginPage({
517
657
  maxWidth = "container.sm",
518
658
  ...loginFormProps
519
659
  }) {
520
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react9.Container, { maxW: maxWidth, py: 10, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react9.Center, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LoginForm, { ...loginFormProps }) }) });
660
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react11.Container, { maxW: maxWidth, py: 10, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react11.Center, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LoginForm, { ...loginFormProps }) }) });
521
661
  }
522
662
 
523
663
  // src/pages/RegisterPage.tsx
524
- var import_react10 = require("@chakra-ui/react");
664
+ var import_react12 = require("@chakra-ui/react");
525
665
  var import_jsx_runtime6 = require("react/jsx-runtime");
526
666
  function RegisterPage({
527
667
  maxWidth = "container.sm",
528
668
  ...registerFormProps
529
669
  }) {
530
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react10.Container, { maxW: maxWidth, py: 10, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react10.Center, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RegisterForm, { ...registerFormProps }) }) });
670
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react12.Container, { maxW: maxWidth, py: 10, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react12.Center, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RegisterForm, { ...registerFormProps }) }) });
531
671
  }
532
672
 
533
673
  // src/routes/index.ts
534
- var import_core5 = require("@abpjs/core");
535
- var ACCOUNT_ROUTES = [
536
- {
537
- name: "Account",
538
- path: "account",
539
- invisible: true,
540
- layout: import_core5.eLayoutType.application,
541
- children: [
542
- {
543
- path: "login",
544
- name: "Login",
545
- order: 1
546
- },
547
- {
548
- path: "register",
549
- name: "Register",
550
- order: 2
551
- }
552
- ]
553
- }
554
- ];
674
+ var import_core6 = require("@abpjs/core");
675
+ var ACCOUNT_ROUTES = {
676
+ routes: [
677
+ {
678
+ name: "Account",
679
+ path: "account",
680
+ invisible: true,
681
+ layout: import_core6.eLayoutType.application,
682
+ children: [
683
+ {
684
+ path: "login",
685
+ name: "Login",
686
+ order: 1
687
+ },
688
+ {
689
+ path: "register",
690
+ name: "Register",
691
+ order: 2
692
+ }
693
+ ]
694
+ }
695
+ ]
696
+ };
555
697
  var DEFAULT_REDIRECT_URL = "/";
556
698
  var ACCOUNT_PATHS = {
557
699
  login: "/account/login",
@@ -562,6 +704,7 @@ var ACCOUNT_PATHS = {
562
704
  ACCOUNT_PATHS,
563
705
  ACCOUNT_ROUTES,
564
706
  AccountProvider,
707
+ AccountService,
565
708
  DEFAULT_REDIRECT_URL,
566
709
  LoginForm,
567
710
  LoginPage,
@@ -570,5 +713,6 @@ var ACCOUNT_PATHS = {
570
713
  TenantBox,
571
714
  useAccountContext,
572
715
  useAccountOptions,
716
+ useAccountService,
573
717
  usePasswordFlow
574
718
  });
package/dist/index.mjs CHANGED
@@ -1,3 +1,34 @@
1
+ // src/services/account.service.ts
2
+ var AccountService = class {
3
+ constructor(rest) {
4
+ this.rest = rest;
5
+ }
6
+ /**
7
+ * Find a tenant by name
8
+ *
9
+ * @param tenantName - The name of the tenant to find
10
+ * @returns Promise resolving to TenantIdResponse
11
+ */
12
+ findTenant(tenantName) {
13
+ return this.rest.get(
14
+ `/api/abp/multi-tenancy/tenants/by-name/${tenantName}`
15
+ );
16
+ }
17
+ /**
18
+ * Register a new user
19
+ *
20
+ * @param body - The registration request data
21
+ * @returns Promise resolving to RegisterResponse
22
+ */
23
+ register(body) {
24
+ return this.rest.post(
25
+ "/api/account/register",
26
+ body,
27
+ { skipHandleError: true }
28
+ );
29
+ }
30
+ };
31
+
1
32
  // src/providers/AccountProvider.tsx
2
33
  import { createContext, useContext, useMemo } from "react";
3
34
  import { jsx } from "react/jsx-runtime";
@@ -136,37 +167,77 @@ function usePasswordFlow() {
136
167
  };
137
168
  }
138
169
 
170
+ // src/hooks/useAccountService.ts
171
+ import { useMemo as useMemo2 } from "react";
172
+ import { useRestService } from "@abpjs/core";
173
+ function useAccountService() {
174
+ const restService = useRestService();
175
+ return useMemo2(() => new AccountService(restService), [restService]);
176
+ }
177
+
139
178
  // src/components/TenantBox/TenantBox.tsx
140
- import { useState as useState2, useCallback as useCallback2 } from "react";
141
- import { useForm } from "react-hook-form";
142
- import { useLocalization } from "@abpjs/core";
143
- import { Modal, Button, FormField } from "@abpjs/theme-shared";
179
+ import { useState as useState2, useCallback as useCallback2, useEffect } from "react";
180
+ import { useDispatch, useSelector } from "react-redux";
181
+ import { useLocalization, sessionActions, selectTenant } from "@abpjs/core";
182
+ import { Modal, Button, useToaster } from "@abpjs/theme-shared";
144
183
  import { Box, Text, Link, Input, VStack } from "@chakra-ui/react";
145
184
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
146
185
  function TenantBox({ containerStyle }) {
147
186
  const { t } = useLocalization();
148
- const [selected, setSelected] = useState2(null);
149
- const [modalVisible, setModalVisible] = useState2(false);
150
- const { register, handleSubmit, reset } = useForm({
151
- defaultValues: {
152
- name: ""
153
- }
154
- });
155
- const openModal = useCallback2(() => {
156
- reset({ name: selected?.name || "" });
157
- setModalVisible(true);
158
- }, [selected, reset]);
187
+ const dispatch = useDispatch();
188
+ const accountService = useAccountService();
189
+ const toaster = useToaster();
190
+ const currentTenant = useSelector(selectTenant);
191
+ const [tenantName, setTenantName] = useState2("");
192
+ const [isModalVisible, setIsModalVisible] = useState2(false);
193
+ const [isLoading, setIsLoading] = useState2(false);
194
+ useEffect(() => {
195
+ setTenantName(currentTenant?.name || "");
196
+ }, [currentTenant]);
159
197
  const onSwitch = useCallback2(() => {
160
- setSelected(null);
161
- openModal();
162
- }, [openModal]);
163
- const onSave = useCallback2((data) => {
164
- setSelected(data.name ? data : null);
165
- setModalVisible(false);
198
+ setIsModalVisible(true);
166
199
  }, []);
200
+ const save = useCallback2(async () => {
201
+ if (tenantName) {
202
+ setIsLoading(true);
203
+ try {
204
+ const { success, tenantId } = await accountService.findTenant(tenantName);
205
+ if (success) {
206
+ const newTenant = {
207
+ id: tenantId,
208
+ name: tenantName
209
+ };
210
+ dispatch(sessionActions.setTenant(newTenant));
211
+ setIsModalVisible(false);
212
+ } else {
213
+ toaster.error(
214
+ t("AbpUiMultiTenancy::GivenTenantIsNotAvailable", tenantName) || `Tenant "${tenantName}" is not available`,
215
+ t("AbpUi::Error") || "Error"
216
+ );
217
+ setTenantName("");
218
+ }
219
+ } catch (err) {
220
+ const errorMessage = err?.error?.error_description || err?.error?.error?.message || t("AbpUi::DefaultErrorMessage") || "An error occurred";
221
+ toaster.error(errorMessage, t("AbpUi::Error") || "Error");
222
+ } finally {
223
+ setIsLoading(false);
224
+ }
225
+ } else {
226
+ dispatch(sessionActions.setTenant({ id: "", name: "" }));
227
+ setIsModalVisible(false);
228
+ }
229
+ }, [tenantName, accountService, dispatch, toaster, t]);
167
230
  const onClose = useCallback2(() => {
168
- setModalVisible(false);
169
- }, []);
231
+ setTenantName(currentTenant?.name || "");
232
+ setIsModalVisible(false);
233
+ }, [currentTenant]);
234
+ const handleSubmit = useCallback2(
235
+ (e) => {
236
+ e.preventDefault();
237
+ save();
238
+ },
239
+ [save]
240
+ );
170
241
  return /* @__PURE__ */ jsxs(Fragment, { children: [
171
242
  /* @__PURE__ */ jsxs(
172
243
  Box,
@@ -185,7 +256,7 @@ function TenantBox({ containerStyle }) {
185
256
  ":",
186
257
  " "
187
258
  ] }),
188
- /* @__PURE__ */ jsx2(Text, { as: "strong", children: /* @__PURE__ */ jsx2(Text, { as: "i", children: selected?.name || t("AbpUiMultiTenancy::NotSelected") }) }),
259
+ /* @__PURE__ */ jsx2(Text, { as: "strong", children: /* @__PURE__ */ jsx2(Text, { as: "i", children: currentTenant?.name || t("AbpUiMultiTenancy::NotSelected") }) }),
189
260
  " (",
190
261
  /* @__PURE__ */ jsx2(
191
262
  Link,
@@ -205,19 +276,40 @@ function TenantBox({ containerStyle }) {
205
276
  /* @__PURE__ */ jsx2(
206
277
  Modal,
207
278
  {
208
- visible: modalVisible,
209
- onVisibleChange: setModalVisible,
279
+ visible: isModalVisible,
280
+ onVisibleChange: setIsModalVisible,
210
281
  size: "md",
211
282
  header: t("AbpUiMultiTenancy::SwitchTenant") || "Switch Tenant",
212
283
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
213
284
  /* @__PURE__ */ jsx2(Button, { variant: "ghost", colorPalette: "gray", onClick: onClose, children: t("AbpTenantManagement::Cancel") }),
214
- /* @__PURE__ */ jsxs(Button, { colorPalette: "blue", onClick: handleSubmit(onSave), children: [
215
- /* @__PURE__ */ jsx2(CheckIcon, {}),
216
- t("AbpTenantManagement::Save")
217
- ] })
285
+ /* @__PURE__ */ jsxs(
286
+ Button,
287
+ {
288
+ colorPalette: "blue",
289
+ onClick: save,
290
+ loading: isLoading,
291
+ loadingText: t("AbpTenantManagement::Save"),
292
+ children: [
293
+ /* @__PURE__ */ jsx2(CheckIcon, {}),
294
+ t("AbpTenantManagement::Save")
295
+ ]
296
+ }
297
+ )
218
298
  ] }),
219
- children: /* @__PURE__ */ jsx2("form", { onSubmit: handleSubmit(onSave), children: /* @__PURE__ */ jsxs(VStack, { gap: 4, align: "stretch", children: [
220
- /* @__PURE__ */ jsx2(FormField, { label: t("AbpUiMultiTenancy::Name"), htmlFor: "tenant-name", children: /* @__PURE__ */ jsx2(Input, { id: "tenant-name", type: "text", ...register("name") }) }),
299
+ children: /* @__PURE__ */ jsx2("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs(VStack, { gap: 4, align: "stretch", children: [
300
+ /* @__PURE__ */ jsxs(Box, { children: [
301
+ /* @__PURE__ */ jsx2(Text, { as: "label", mb: 1, display: "block", children: t("AbpUiMultiTenancy::Name") }),
302
+ /* @__PURE__ */ jsx2(
303
+ Input,
304
+ {
305
+ id: "tenant-name",
306
+ type: "text",
307
+ value: tenantName,
308
+ onChange: (e) => setTenantName(e.target.value),
309
+ autoFocus: true
310
+ }
311
+ )
312
+ ] }),
221
313
  /* @__PURE__ */ jsx2(Text, { fontSize: "sm", color: "gray.600", children: t("AbpUiMultiTenancy::SwitchTenantHint") })
222
314
  ] }) })
223
315
  }
@@ -243,7 +335,7 @@ function CheckIcon() {
243
335
  }
244
336
 
245
337
  // src/components/LoginForm/LoginForm.tsx
246
- import { useForm as useForm2 } from "react-hook-form";
338
+ import { useForm } from "react-hook-form";
247
339
  import { zodResolver } from "@hookform/resolvers/zod";
248
340
  import { z } from "zod";
249
341
  import { Link as RouterLink } from "react-router-dom";
@@ -279,7 +371,7 @@ function LoginForm({
279
371
  register,
280
372
  handleSubmit,
281
373
  formState: { errors, isSubmitting }
282
- } = useForm2({
374
+ } = useForm({
283
375
  resolver: zodResolver(loginSchema),
284
376
  defaultValues: {
285
377
  username: "",
@@ -355,12 +447,13 @@ function LoginForm({
355
447
  }
356
448
 
357
449
  // src/components/RegisterForm/RegisterForm.tsx
358
- import { useForm as useForm3 } from "react-hook-form";
450
+ import { useState as useState3 } from "react";
451
+ import { useForm as useForm2 } from "react-hook-form";
359
452
  import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
360
453
  import { z as z2 } from "zod";
361
- import { Link as RouterLink2 } from "react-router-dom";
362
- import { useLocalization as useLocalization3 } from "@abpjs/core";
363
- import { Button as Button3 } from "@abpjs/theme-shared";
454
+ import { Link as RouterLink2, useNavigate as useNavigate2 } from "react-router-dom";
455
+ import { useLocalization as useLocalization3, useUserManager, useAbp as useAbp2, configActions as configActions2 } from "@abpjs/core";
456
+ import { Button as Button3, useToaster as useToaster2 } from "@abpjs/theme-shared";
364
457
  import { Box as Box3, Heading as Heading2, Input as Input3, Link as Link3, HStack as HStack2, Show as Show2 } from "@chakra-ui/react";
365
458
  import {
366
459
  Card as Card2,
@@ -401,16 +494,20 @@ function RegisterForm({
401
494
  showLoginLink = true,
402
495
  loginUrl = "/account/login",
403
496
  onRegisterSuccess,
404
- // Note: onRegisterError will be used when registration API is implemented
405
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
406
- onRegisterError: _onRegisterError
497
+ onRegisterError
407
498
  }) {
408
499
  const { t } = useLocalization3();
500
+ const navigate = useNavigate2();
501
+ const accountService = useAccountService();
502
+ const toaster = useToaster2();
503
+ const userManager = useUserManager();
504
+ const { store, applicationConfigurationService } = useAbp2();
505
+ const [inProgress, setInProgress] = useState3(false);
409
506
  const {
410
507
  register,
411
508
  handleSubmit,
412
- formState: { errors, isSubmitting }
413
- } = useForm3({
509
+ formState: { errors }
510
+ } = useForm2({
414
511
  resolver: zodResolver2(registerSchema),
415
512
  defaultValues: {
416
513
  username: "",
@@ -419,8 +516,49 @@ function RegisterForm({
419
516
  }
420
517
  });
421
518
  const onSubmit = async (data) => {
422
- console.log("Register form submitted (no API call in v0.7.6):", data);
423
- onRegisterSuccess?.();
519
+ setInProgress(true);
520
+ const newUser = {
521
+ userName: data.username,
522
+ password: data.password,
523
+ emailAddress: data.email,
524
+ appName: "React"
525
+ };
526
+ try {
527
+ await accountService.register(newUser);
528
+ if (userManager) {
529
+ try {
530
+ await userManager.signinResourceOwnerCredentials({
531
+ username: newUser.userName,
532
+ password: newUser.password
533
+ });
534
+ const config = await applicationConfigurationService.getConfiguration();
535
+ store.dispatch(configActions2.setApplicationConfiguration(config));
536
+ navigate("/");
537
+ onRegisterSuccess?.();
538
+ } catch (loginErr) {
539
+ console.warn("Auto-login failed after registration:", loginErr);
540
+ toaster.success(
541
+ t("AbpAccount::SuccessfullyRegistered") || "Successfully registered! Please log in.",
542
+ t("AbpAccount::Success") || "Success"
543
+ );
544
+ navigate(loginUrl);
545
+ onRegisterSuccess?.();
546
+ }
547
+ } else {
548
+ toaster.success(
549
+ t("AbpAccount::SuccessfullyRegistered") || "Successfully registered! Please log in.",
550
+ t("AbpAccount::Success") || "Success"
551
+ );
552
+ navigate(loginUrl);
553
+ onRegisterSuccess?.();
554
+ }
555
+ } catch (err) {
556
+ const errorMessage = err?.error?.error_description || err?.error?.error?.message || t("AbpAccount::DefaultErrorMessage") || "An error occurred";
557
+ toaster.error(errorMessage, t("AbpUi::Error") || "Error", { life: 7e3 });
558
+ onRegisterError?.(errorMessage);
559
+ } finally {
560
+ setInProgress(false);
561
+ }
424
562
  };
425
563
  return /* @__PURE__ */ jsx4(Flex2, { height: "full", flex: "1", children: /* @__PURE__ */ jsx4(Box3, { flex: "1.5", py: { base: "24", md: "32" }, children: /* @__PURE__ */ jsx4(Container2, { maxW: "md", children: /* @__PURE__ */ jsxs3(Stack2, { gap: "8", children: [
426
564
  /* @__PURE__ */ jsx4(Show2, { when: showTenantBox, children: /* @__PURE__ */ jsx4(TenantBox, {}) }),
@@ -475,7 +613,7 @@ function RegisterForm({
475
613
  {
476
614
  type: "submit",
477
615
  colorPalette: "blue",
478
- loading: isSubmitting,
616
+ loading: inProgress,
479
617
  loadingText: t("AbpAccount::Register"),
480
618
  children: t("AbpAccount::Register")
481
619
  }
@@ -511,26 +649,28 @@ function RegisterPage({
511
649
 
512
650
  // src/routes/index.ts
513
651
  import { eLayoutType } from "@abpjs/core";
514
- var ACCOUNT_ROUTES = [
515
- {
516
- name: "Account",
517
- path: "account",
518
- invisible: true,
519
- layout: eLayoutType.application,
520
- children: [
521
- {
522
- path: "login",
523
- name: "Login",
524
- order: 1
525
- },
526
- {
527
- path: "register",
528
- name: "Register",
529
- order: 2
530
- }
531
- ]
532
- }
533
- ];
652
+ var ACCOUNT_ROUTES = {
653
+ routes: [
654
+ {
655
+ name: "Account",
656
+ path: "account",
657
+ invisible: true,
658
+ layout: eLayoutType.application,
659
+ children: [
660
+ {
661
+ path: "login",
662
+ name: "Login",
663
+ order: 1
664
+ },
665
+ {
666
+ path: "register",
667
+ name: "Register",
668
+ order: 2
669
+ }
670
+ ]
671
+ }
672
+ ]
673
+ };
534
674
  var DEFAULT_REDIRECT_URL = "/";
535
675
  var ACCOUNT_PATHS = {
536
676
  login: "/account/login",
@@ -540,6 +680,7 @@ export {
540
680
  ACCOUNT_PATHS,
541
681
  ACCOUNT_ROUTES,
542
682
  AccountProvider,
683
+ AccountService,
543
684
  DEFAULT_REDIRECT_URL,
544
685
  LoginForm,
545
686
  LoginPage,
@@ -548,5 +689,6 @@ export {
548
689
  TenantBox,
549
690
  useAccountContext,
550
691
  useAccountOptions,
692
+ useAccountService,
551
693
  usePasswordFlow
552
694
  };
@@ -40,3 +40,44 @@ export interface PasswordFlowResult {
40
40
  success: boolean;
41
41
  error?: string;
42
42
  }
43
+ /**
44
+ * Request body for user registration
45
+ */
46
+ export interface RegisterRequest {
47
+ userName: string;
48
+ emailAddress: string;
49
+ password: string;
50
+ appName?: string;
51
+ }
52
+ /**
53
+ * Response from user registration API
54
+ */
55
+ export interface RegisterResponse {
56
+ tenantId: string;
57
+ userName: string;
58
+ name: string;
59
+ surname: string;
60
+ email: string;
61
+ emailConfirmed: boolean;
62
+ phoneNumber: string;
63
+ phoneNumberConfirmed: boolean;
64
+ twoFactorEnabled: boolean;
65
+ lockoutEnabled: boolean;
66
+ lockoutEnd: string;
67
+ concurrencyStamp: string;
68
+ isDeleted: boolean;
69
+ deleterId: string;
70
+ deletionTime: string;
71
+ lastModificationTime: string;
72
+ lastModifierId: string;
73
+ creationTime: string;
74
+ creatorId: string;
75
+ id: string;
76
+ }
77
+ /**
78
+ * Response from tenant lookup API
79
+ */
80
+ export interface TenantIdResponse {
81
+ success: boolean;
82
+ tenantId: string;
83
+ }
@@ -9,8 +9,13 @@ import { type ABP } from '@abpjs/core';
9
9
  * - /account (invisible, uses account layout)
10
10
  * - /account/login
11
11
  * - /account/register
12
+ *
13
+ * @since 0.9.0 - Changed from array to object with `routes` property
14
+ * @deprecated since version 0.9 - Routes are now configured via AccountProvider
12
15
  */
13
- export declare const ACCOUNT_ROUTES: ABP.FullRoute[];
16
+ export declare const ACCOUNT_ROUTES: {
17
+ routes: ABP.FullRoute[];
18
+ };
14
19
  /**
15
20
  * Default redirect path after login
16
21
  */
@@ -0,0 +1,28 @@
1
+ import { RestService } from '@abpjs/core';
2
+ import type { RegisterRequest, RegisterResponse, TenantIdResponse } from '../models';
3
+ /**
4
+ * AccountService - Service for account-related API operations
5
+ *
6
+ * This is the React equivalent of Angular's AccountService.
7
+ * Provides methods for tenant lookup and user registration.
8
+ *
9
+ * @since 0.9.0
10
+ */
11
+ export declare class AccountService {
12
+ private rest;
13
+ constructor(rest: RestService);
14
+ /**
15
+ * Find a tenant by name
16
+ *
17
+ * @param tenantName - The name of the tenant to find
18
+ * @returns Promise resolving to TenantIdResponse
19
+ */
20
+ findTenant(tenantName: string): Promise<TenantIdResponse>;
21
+ /**
22
+ * Register a new user
23
+ *
24
+ * @param body - The registration request data
25
+ * @returns Promise resolving to RegisterResponse
26
+ */
27
+ register(body: RegisterRequest): Promise<RegisterResponse>;
28
+ }
@@ -0,0 +1 @@
1
+ export * from './account.service';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abpjs/account",
3
- "version": "0.8.0",
3
+ "version": "1.0.0",
4
4
  "description": "ABP Framework Account module for React - Translation of @abp/ng.account",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -49,13 +49,26 @@
49
49
  "@emotion/react": "^11.11.0",
50
50
  "@hookform/resolvers": "^3.3.0",
51
51
  "react-hook-form": "^7.48.0",
52
+ "react-redux": "^9.0.0",
52
53
  "zod": "^3.22.0",
53
54
  "react-icons": "^5.0.0",
54
- "@abpjs/core": "0.8.0",
55
- "@abpjs/theme-shared": "0.8.0"
55
+ "@abpjs/core": "1.0.0",
56
+ "@abpjs/theme-shared": "1.0.0"
56
57
  },
57
58
  "devDependencies": {
58
- "@abp/ng.account": "0.8.0"
59
+ "@abp/ng.account": "1.0.0",
60
+ "@reduxjs/toolkit": "^2.0.0",
61
+ "@testing-library/jest-dom": "^6.4.0",
62
+ "@testing-library/react": "^14.2.0",
63
+ "@testing-library/user-event": "^14.6.1",
64
+ "@types/react": "^18.2.0",
65
+ "@types/react-dom": "^18.2.0",
66
+ "@vitest/coverage-v8": "^1.2.0",
67
+ "jsdom": "^24.0.0",
68
+ "react": "^18.2.0",
69
+ "react-dom": "^18.2.0",
70
+ "react-router-dom": "^6.20.0",
71
+ "vitest": "^1.2.0"
59
72
  },
60
73
  "scripts": {
61
74
  "build": "tsup src/index.ts --format cjs,esm --clean && tsc -p tsconfig.build.json",
@@ -65,6 +78,7 @@
65
78
  "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
66
79
  "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"",
67
80
  "type-check": "tsc --noEmit",
68
- "test": "vitest"
81
+ "test": "vitest run",
82
+ "test:watch": "vitest"
69
83
  }
70
84
  }