@abpjs/account 0.7.6
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/LICENSE +165 -0
- package/README.md +316 -0
- package/dist/components/LoginForm/LoginForm.d.ts +48 -0
- package/dist/components/LoginForm/image-placeholder.d.ts +1 -0
- package/dist/components/LoginForm/index.d.ts +2 -0
- package/dist/components/RegisterForm/RegisterForm.d.ts +50 -0
- package/dist/components/RegisterForm/index.d.ts +2 -0
- package/dist/components/TenantBox/TenantBox.d.ts +21 -0
- package/dist/components/TenantBox/index.d.ts +2 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/usePasswordFlow.d.ts +27 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +574 -0
- package/dist/index.mjs +552 -0
- package/dist/models/index.d.ts +42 -0
- package/dist/pages/LoginPage.d.ts +25 -0
- package/dist/pages/RegisterPage.d.ts +25 -0
- package/dist/pages/index.d.ts +2 -0
- package/dist/providers/AccountProvider.d.ts +40 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/routes/index.d.ts +24 -0
- package/package.json +70 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// src/providers/AccountProvider.tsx
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var DEFAULT_OPTIONS = {
|
|
5
|
+
redirectUrl: "/"
|
|
6
|
+
};
|
|
7
|
+
var AccountContext = createContext(null);
|
|
8
|
+
function AccountProvider({ children, options }) {
|
|
9
|
+
const mergedOptions = useMemo(
|
|
10
|
+
() => ({
|
|
11
|
+
...DEFAULT_OPTIONS,
|
|
12
|
+
...options
|
|
13
|
+
}),
|
|
14
|
+
[options]
|
|
15
|
+
);
|
|
16
|
+
const value = useMemo(
|
|
17
|
+
() => ({
|
|
18
|
+
options: mergedOptions
|
|
19
|
+
}),
|
|
20
|
+
[mergedOptions]
|
|
21
|
+
);
|
|
22
|
+
return /* @__PURE__ */ jsx(AccountContext.Provider, { value, children });
|
|
23
|
+
}
|
|
24
|
+
function useAccountContext() {
|
|
25
|
+
const context = useContext(AccountContext);
|
|
26
|
+
if (!context) {
|
|
27
|
+
throw new Error("useAccountContext must be used within an AccountProvider");
|
|
28
|
+
}
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
function useAccountOptions() {
|
|
32
|
+
const context = useContext(AccountContext);
|
|
33
|
+
return context?.options ?? DEFAULT_OPTIONS;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/hooks/usePasswordFlow.ts
|
|
37
|
+
import { useState, useCallback } from "react";
|
|
38
|
+
import { useAbp, useConfig, configActions } from "@abpjs/core";
|
|
39
|
+
import { useNavigate } from "react-router-dom";
|
|
40
|
+
function usePasswordFlow() {
|
|
41
|
+
const { store, axiosInstance, applicationConfigurationService, userManager } = useAbp();
|
|
42
|
+
const config = useConfig();
|
|
43
|
+
const options = useAccountOptions();
|
|
44
|
+
const navigate = useNavigate();
|
|
45
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
46
|
+
const [error, setError] = useState(null);
|
|
47
|
+
const getTokenEndpoint = useCallback(() => {
|
|
48
|
+
const oAuthConfig = config.environment.oAuthConfig;
|
|
49
|
+
if (oAuthConfig?.authority) {
|
|
50
|
+
const authority = oAuthConfig.authority.replace(/\/+$/, "");
|
|
51
|
+
return `${authority}/connect/token`;
|
|
52
|
+
}
|
|
53
|
+
throw new Error("OAuth authority not configured");
|
|
54
|
+
}, [config.environment.oAuthConfig]);
|
|
55
|
+
const login = useCallback(
|
|
56
|
+
async (username, password, flowOptions) => {
|
|
57
|
+
setIsLoading(true);
|
|
58
|
+
setError(null);
|
|
59
|
+
try {
|
|
60
|
+
const oAuthConfig = config.environment.oAuthConfig;
|
|
61
|
+
if (!oAuthConfig?.client_id) {
|
|
62
|
+
throw new Error("OAuth client_id not configured");
|
|
63
|
+
}
|
|
64
|
+
const tokenEndpoint = getTokenEndpoint();
|
|
65
|
+
const formData = new URLSearchParams();
|
|
66
|
+
formData.append("grant_type", "password");
|
|
67
|
+
formData.append("username", username);
|
|
68
|
+
formData.append("password", password);
|
|
69
|
+
formData.append("client_id", oAuthConfig.client_id);
|
|
70
|
+
if (oAuthConfig.scope) {
|
|
71
|
+
formData.append("scope", oAuthConfig.scope);
|
|
72
|
+
}
|
|
73
|
+
const response = await axiosInstance.post(
|
|
74
|
+
tokenEndpoint,
|
|
75
|
+
formData.toString(),
|
|
76
|
+
{
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
const tokenData = response.data;
|
|
83
|
+
const storage = flowOptions?.remember ? localStorage : sessionStorage;
|
|
84
|
+
const storageKey = `oidc.user:${oAuthConfig.authority}:${oAuthConfig.client_id}`;
|
|
85
|
+
const userData = {
|
|
86
|
+
access_token: tokenData.access_token,
|
|
87
|
+
token_type: tokenData.token_type,
|
|
88
|
+
expires_at: Math.floor(Date.now() / 1e3) + tokenData.expires_in,
|
|
89
|
+
refresh_token: tokenData.refresh_token,
|
|
90
|
+
scope: tokenData.scope,
|
|
91
|
+
profile: {}
|
|
92
|
+
// Will be filled after config fetch
|
|
93
|
+
};
|
|
94
|
+
storage.setItem(storageKey, JSON.stringify(userData));
|
|
95
|
+
const oidcKey = `oidc.user:${oAuthConfig.authority}:${oAuthConfig.client_id}`;
|
|
96
|
+
storage.setItem(oidcKey, JSON.stringify(userData));
|
|
97
|
+
if (userManager) {
|
|
98
|
+
const loadedUser = await userManager.getUser();
|
|
99
|
+
if (loadedUser) {
|
|
100
|
+
userManager.events.load(loadedUser);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const appConfig = await applicationConfigurationService.getConfiguration();
|
|
104
|
+
store.dispatch(configActions.setApplicationConfiguration(appConfig));
|
|
105
|
+
const redirectUrl = window.history.state?.redirectUrl || options.redirectUrl;
|
|
106
|
+
navigate(redirectUrl);
|
|
107
|
+
setIsLoading(false);
|
|
108
|
+
return { success: true };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const errorMessage = err.response?.data?.error_description || err.response?.data?.error || err.message || "Login failed";
|
|
111
|
+
setError(errorMessage);
|
|
112
|
+
setIsLoading(false);
|
|
113
|
+
console.error("Password flow error:", errorMessage);
|
|
114
|
+
return { success: false, error: errorMessage };
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
[
|
|
118
|
+
config.environment.oAuthConfig,
|
|
119
|
+
getTokenEndpoint,
|
|
120
|
+
axiosInstance,
|
|
121
|
+
applicationConfigurationService,
|
|
122
|
+
store,
|
|
123
|
+
options.redirectUrl,
|
|
124
|
+
navigate,
|
|
125
|
+
userManager
|
|
126
|
+
]
|
|
127
|
+
);
|
|
128
|
+
const clearError = useCallback(() => {
|
|
129
|
+
setError(null);
|
|
130
|
+
}, []);
|
|
131
|
+
return {
|
|
132
|
+
login,
|
|
133
|
+
isLoading,
|
|
134
|
+
error,
|
|
135
|
+
clearError
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 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";
|
|
144
|
+
import { Box, Text, Link, Input, VStack } from "@chakra-ui/react";
|
|
145
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
146
|
+
function TenantBox({ containerStyle }) {
|
|
147
|
+
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]);
|
|
159
|
+
const onSwitch = useCallback2(() => {
|
|
160
|
+
setSelected(null);
|
|
161
|
+
openModal();
|
|
162
|
+
}, [openModal]);
|
|
163
|
+
const onSave = useCallback2((data) => {
|
|
164
|
+
setSelected(data.name ? data : null);
|
|
165
|
+
setModalVisible(false);
|
|
166
|
+
}, []);
|
|
167
|
+
const onClose = useCallback2(() => {
|
|
168
|
+
setModalVisible(false);
|
|
169
|
+
}, []);
|
|
170
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
171
|
+
/* @__PURE__ */ jsxs(
|
|
172
|
+
Box,
|
|
173
|
+
{
|
|
174
|
+
className: "tenant-switch-box",
|
|
175
|
+
bg: "gray.100",
|
|
176
|
+
mb: 5,
|
|
177
|
+
color: "black",
|
|
178
|
+
p: 2.5,
|
|
179
|
+
textAlign: "center",
|
|
180
|
+
borderRadius: "md",
|
|
181
|
+
style: containerStyle,
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ jsxs(Text, { as: "span", color: "gray.600", children: [
|
|
184
|
+
t("AbpUiMultiTenancy::Tenant"),
|
|
185
|
+
":",
|
|
186
|
+
" "
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ jsx2(Text, { as: "strong", children: /* @__PURE__ */ jsx2(Text, { as: "i", children: selected?.name || t("AbpUiMultiTenancy::NotSelected") }) }),
|
|
189
|
+
" (",
|
|
190
|
+
/* @__PURE__ */ jsx2(
|
|
191
|
+
Link,
|
|
192
|
+
{
|
|
193
|
+
id: "abp-tenant-switch-link",
|
|
194
|
+
color: "gray.700",
|
|
195
|
+
cursor: "pointer",
|
|
196
|
+
onClick: onSwitch,
|
|
197
|
+
_hover: { textDecoration: "underline" },
|
|
198
|
+
children: t("AbpUiMultiTenancy::Switch")
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
")"
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
/* @__PURE__ */ jsx2(
|
|
206
|
+
Modal,
|
|
207
|
+
{
|
|
208
|
+
visible: modalVisible,
|
|
209
|
+
onVisibleChange: setModalVisible,
|
|
210
|
+
size: "md",
|
|
211
|
+
header: t("AbpUiMultiTenancy::SwitchTenant") || "Switch Tenant",
|
|
212
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
213
|
+
/* @__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
|
+
] })
|
|
218
|
+
] }),
|
|
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") }) }),
|
|
221
|
+
/* @__PURE__ */ jsx2(Text, { fontSize: "sm", color: "gray.600", children: t("AbpUiMultiTenancy::SwitchTenantHint") })
|
|
222
|
+
] }) })
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
] });
|
|
226
|
+
}
|
|
227
|
+
function CheckIcon() {
|
|
228
|
+
return /* @__PURE__ */ jsx2(
|
|
229
|
+
"svg",
|
|
230
|
+
{
|
|
231
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
232
|
+
width: "16",
|
|
233
|
+
height: "16",
|
|
234
|
+
viewBox: "0 0 24 24",
|
|
235
|
+
fill: "none",
|
|
236
|
+
stroke: "currentColor",
|
|
237
|
+
strokeWidth: "2",
|
|
238
|
+
strokeLinecap: "round",
|
|
239
|
+
strokeLinejoin: "round",
|
|
240
|
+
children: /* @__PURE__ */ jsx2("polyline", { points: "20 6 9 17 4 12" })
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/components/LoginForm/LoginForm.tsx
|
|
246
|
+
import { useForm as useForm2 } from "react-hook-form";
|
|
247
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
248
|
+
import { z } from "zod";
|
|
249
|
+
import { Link as RouterLink } from "react-router-dom";
|
|
250
|
+
import { useLocalization as useLocalization2 } from "@abpjs/core";
|
|
251
|
+
import { Alert, Button as Button2, Checkbox } from "@abpjs/theme-shared";
|
|
252
|
+
import { Box as Box2, Heading, Input as Input2, Link as Link2, HStack, Show } from "@chakra-ui/react";
|
|
253
|
+
import {
|
|
254
|
+
Card,
|
|
255
|
+
Container,
|
|
256
|
+
Field,
|
|
257
|
+
Flex,
|
|
258
|
+
InputGroup,
|
|
259
|
+
Stack,
|
|
260
|
+
Text as Text2
|
|
261
|
+
} from "@chakra-ui/react";
|
|
262
|
+
import { LuLock, LuMail } from "react-icons/lu";
|
|
263
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
264
|
+
var loginSchema = z.object({
|
|
265
|
+
username: z.string().min(1, "Username is required").max(255, "Username must be at most 255 characters"),
|
|
266
|
+
password: z.string().min(1, "Password is required").max(32, "Password must be at most 32 characters"),
|
|
267
|
+
remember: z.boolean().default(false)
|
|
268
|
+
});
|
|
269
|
+
function LoginForm({
|
|
270
|
+
showTenantBox = true,
|
|
271
|
+
showRegisterLink = true,
|
|
272
|
+
registerUrl = "/account/register",
|
|
273
|
+
onLoginSuccess,
|
|
274
|
+
onLoginError
|
|
275
|
+
}) {
|
|
276
|
+
const { t } = useLocalization2();
|
|
277
|
+
const { login, isLoading, error, clearError } = usePasswordFlow();
|
|
278
|
+
const {
|
|
279
|
+
register,
|
|
280
|
+
handleSubmit,
|
|
281
|
+
formState: { errors, isSubmitting }
|
|
282
|
+
} = useForm2({
|
|
283
|
+
resolver: zodResolver(loginSchema),
|
|
284
|
+
defaultValues: {
|
|
285
|
+
username: "",
|
|
286
|
+
password: "",
|
|
287
|
+
remember: false
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
const onSubmit = async (data) => {
|
|
291
|
+
clearError();
|
|
292
|
+
const result = await login(data.username, data.password, {
|
|
293
|
+
remember: data.remember
|
|
294
|
+
});
|
|
295
|
+
if (result.success) {
|
|
296
|
+
onLoginSuccess?.();
|
|
297
|
+
} else if (result.error) {
|
|
298
|
+
onLoginError?.(result.error);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const isFormLoading = isLoading || isSubmitting;
|
|
302
|
+
return /* @__PURE__ */ jsx3(Flex, { height: "full", flex: "1", children: /* @__PURE__ */ jsx3(Box2, { flex: "1.5", py: { base: "24", md: "32" }, children: /* @__PURE__ */ jsx3(Container, { maxW: "md", children: /* @__PURE__ */ jsxs2(Stack, { gap: "8", children: [
|
|
303
|
+
/* @__PURE__ */ jsx3(Show, { when: showTenantBox, children: /* @__PURE__ */ jsx3(TenantBox, {}) }),
|
|
304
|
+
/* @__PURE__ */ jsx3(Stack, { gap: { base: "2", md: "3" }, textAlign: "center", children: /* @__PURE__ */ jsx3(Heading, { size: { base: "2xl", md: "3xl" }, children: t("AbpAccount::Login") }) }),
|
|
305
|
+
error && /* @__PURE__ */ jsx3(Alert, { status: "error", children: error }),
|
|
306
|
+
/* @__PURE__ */ jsx3("form", { onSubmit: handleSubmit(onSubmit), noValidate: true, children: /* @__PURE__ */ jsxs2(Stack, { gap: "6", children: [
|
|
307
|
+
/* @__PURE__ */ jsxs2(Stack, { gap: "5", children: [
|
|
308
|
+
/* @__PURE__ */ jsxs2(Field.Root, { invalid: !!errors.username, children: [
|
|
309
|
+
/* @__PURE__ */ jsx3(Field.Label, { children: t("AbpAccount::UserNameOrEmailAddress") }),
|
|
310
|
+
/* @__PURE__ */ jsx3(InputGroup, { startElement: /* @__PURE__ */ jsx3(LuMail, {}), width: "full", children: /* @__PURE__ */ jsx3(
|
|
311
|
+
Input2,
|
|
312
|
+
{
|
|
313
|
+
id: "login-input-user-name-or-email-address",
|
|
314
|
+
type: "text",
|
|
315
|
+
autoComplete: "username",
|
|
316
|
+
placeholder: "me@example.com",
|
|
317
|
+
...register("username")
|
|
318
|
+
}
|
|
319
|
+
) }),
|
|
320
|
+
errors.username && /* @__PURE__ */ jsx3(Field.ErrorText, { children: errors.username.message })
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsxs2(Field.Root, { invalid: !!errors.password, children: [
|
|
323
|
+
/* @__PURE__ */ jsx3(Field.Label, { children: t("AbpAccount::Password") }),
|
|
324
|
+
/* @__PURE__ */ jsx3(InputGroup, { startElement: /* @__PURE__ */ jsx3(LuLock, {}), width: "full", children: /* @__PURE__ */ jsx3(
|
|
325
|
+
Input2,
|
|
326
|
+
{
|
|
327
|
+
id: "login-input-password",
|
|
328
|
+
type: "password",
|
|
329
|
+
autoComplete: "current-password",
|
|
330
|
+
placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
331
|
+
...register("password")
|
|
332
|
+
}
|
|
333
|
+
) }),
|
|
334
|
+
errors.password && /* @__PURE__ */ jsx3(Field.ErrorText, { children: errors.password.message })
|
|
335
|
+
] }),
|
|
336
|
+
/* @__PURE__ */ jsx3(Checkbox, { id: "login-input-remember-me", ...register("remember"), children: t("AbpAccount::RememberMe") }),
|
|
337
|
+
/* @__PURE__ */ jsx3(
|
|
338
|
+
Button2,
|
|
339
|
+
{
|
|
340
|
+
type: "submit",
|
|
341
|
+
colorPalette: "blue",
|
|
342
|
+
loading: isFormLoading,
|
|
343
|
+
loadingText: t("AbpAccount::Login"),
|
|
344
|
+
children: t("AbpAccount::Login")
|
|
345
|
+
}
|
|
346
|
+
),
|
|
347
|
+
/* @__PURE__ */ jsx3(Link2, { variant: "plain", children: t("AbpAccount::ForgotPassword") })
|
|
348
|
+
] }),
|
|
349
|
+
/* @__PURE__ */ jsx3(Show, { when: showRegisterLink, children: /* @__PURE__ */ jsx3(Card.Root, { size: "sm", mt: "10", children: /* @__PURE__ */ jsx3(Card.Body, { children: /* @__PURE__ */ jsxs2(HStack, { textStyle: "sm", children: [
|
|
350
|
+
/* @__PURE__ */ jsx3(Text2, { children: t("AbpAccount::AreYouANewUser") }),
|
|
351
|
+
/* @__PURE__ */ jsx3(Link2, { asChild: true, variant: "underline", fontWeight: "semibold", children: /* @__PURE__ */ jsx3(RouterLink, { to: registerUrl, children: t("AbpAccount::Register") }) })
|
|
352
|
+
] }) }) }) })
|
|
353
|
+
] }) })
|
|
354
|
+
] }) }) }) });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/components/RegisterForm/RegisterForm.tsx
|
|
358
|
+
import { useForm as useForm3 } from "react-hook-form";
|
|
359
|
+
import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
|
|
360
|
+
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";
|
|
364
|
+
import { Box as Box3, Heading as Heading2, Input as Input3, Link as Link3, HStack as HStack2, Show as Show2 } from "@chakra-ui/react";
|
|
365
|
+
import {
|
|
366
|
+
Card as Card2,
|
|
367
|
+
Container as Container2,
|
|
368
|
+
Field as Field2,
|
|
369
|
+
Flex as Flex2,
|
|
370
|
+
InputGroup as InputGroup2,
|
|
371
|
+
Stack as Stack2,
|
|
372
|
+
Text as Text3
|
|
373
|
+
} from "@chakra-ui/react";
|
|
374
|
+
import { LuLock as LuLock2, LuMail as LuMail2, LuUser } from "react-icons/lu";
|
|
375
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
376
|
+
var passwordValidation = {
|
|
377
|
+
hasLowercase: /[a-z]/,
|
|
378
|
+
hasUppercase: /[A-Z]/,
|
|
379
|
+
hasNumber: /[0-9]/,
|
|
380
|
+
hasSpecial: /[!@#$%^&*(),.?":{}|<>]/
|
|
381
|
+
};
|
|
382
|
+
var registerSchema = z2.object({
|
|
383
|
+
username: z2.string().min(1, "Username is required").max(255, "Username must be at most 255 characters"),
|
|
384
|
+
email: z2.string().min(1, "Email is required").email("Please enter a valid email address"),
|
|
385
|
+
password: z2.string().min(6, "Password must be at least 6 characters").max(32, "Password must be at most 32 characters").refine(
|
|
386
|
+
(val) => passwordValidation.hasLowercase.test(val),
|
|
387
|
+
"Password must contain at least one lowercase letter"
|
|
388
|
+
).refine(
|
|
389
|
+
(val) => passwordValidation.hasUppercase.test(val),
|
|
390
|
+
"Password must contain at least one uppercase letter"
|
|
391
|
+
).refine(
|
|
392
|
+
(val) => passwordValidation.hasNumber.test(val),
|
|
393
|
+
"Password must contain at least one number"
|
|
394
|
+
).refine(
|
|
395
|
+
(val) => passwordValidation.hasSpecial.test(val),
|
|
396
|
+
"Password must contain at least one special character"
|
|
397
|
+
)
|
|
398
|
+
});
|
|
399
|
+
function RegisterForm({
|
|
400
|
+
showTenantBox = true,
|
|
401
|
+
showLoginLink = true,
|
|
402
|
+
loginUrl = "/account/login",
|
|
403
|
+
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
|
|
407
|
+
}) {
|
|
408
|
+
const { t } = useLocalization3();
|
|
409
|
+
const {
|
|
410
|
+
register,
|
|
411
|
+
handleSubmit,
|
|
412
|
+
formState: { errors, isSubmitting }
|
|
413
|
+
} = useForm3({
|
|
414
|
+
resolver: zodResolver2(registerSchema),
|
|
415
|
+
defaultValues: {
|
|
416
|
+
username: "",
|
|
417
|
+
email: "",
|
|
418
|
+
password: ""
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
const onSubmit = async (data) => {
|
|
422
|
+
console.log("Register form submitted (no API call in v0.7.6):", data);
|
|
423
|
+
onRegisterSuccess?.();
|
|
424
|
+
};
|
|
425
|
+
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
|
+
/* @__PURE__ */ jsx4(Show2, { when: showTenantBox, children: /* @__PURE__ */ jsx4(TenantBox, {}) }),
|
|
427
|
+
/* @__PURE__ */ jsx4(Stack2, { gap: { base: "2", md: "3" }, textAlign: "center", children: /* @__PURE__ */ jsx4(Heading2, { size: { base: "2xl", md: "3xl" }, children: t("AbpAccount::Register") }) }),
|
|
428
|
+
/* @__PURE__ */ jsx4("form", { onSubmit: handleSubmit(onSubmit), noValidate: true, children: /* @__PURE__ */ jsxs3(Stack2, { gap: "6", children: [
|
|
429
|
+
/* @__PURE__ */ jsxs3(Stack2, { gap: "5", children: [
|
|
430
|
+
/* @__PURE__ */ jsxs3(Field2.Root, { invalid: !!errors.username, children: [
|
|
431
|
+
/* @__PURE__ */ jsx4(Field2.Label, { children: t("AbpAccount::UserName") }),
|
|
432
|
+
/* @__PURE__ */ jsx4(InputGroup2, { startElement: /* @__PURE__ */ jsx4(LuUser, {}), width: "full", children: /* @__PURE__ */ jsx4(
|
|
433
|
+
Input3,
|
|
434
|
+
{
|
|
435
|
+
id: "input-user-name",
|
|
436
|
+
type: "text",
|
|
437
|
+
autoFocus: true,
|
|
438
|
+
autoComplete: "username",
|
|
439
|
+
placeholder: "johndoe",
|
|
440
|
+
...register("username")
|
|
441
|
+
}
|
|
442
|
+
) }),
|
|
443
|
+
errors.username && /* @__PURE__ */ jsx4(Field2.ErrorText, { children: errors.username.message })
|
|
444
|
+
] }),
|
|
445
|
+
/* @__PURE__ */ jsxs3(Field2.Root, { invalid: !!errors.email, children: [
|
|
446
|
+
/* @__PURE__ */ jsx4(Field2.Label, { children: t("AbpAccount::EmailAddress") }),
|
|
447
|
+
/* @__PURE__ */ jsx4(InputGroup2, { startElement: /* @__PURE__ */ jsx4(LuMail2, {}), width: "full", children: /* @__PURE__ */ jsx4(
|
|
448
|
+
Input3,
|
|
449
|
+
{
|
|
450
|
+
id: "input-email-address",
|
|
451
|
+
type: "email",
|
|
452
|
+
autoComplete: "email",
|
|
453
|
+
placeholder: "me@example.com",
|
|
454
|
+
...register("email")
|
|
455
|
+
}
|
|
456
|
+
) }),
|
|
457
|
+
errors.email && /* @__PURE__ */ jsx4(Field2.ErrorText, { children: errors.email.message })
|
|
458
|
+
] }),
|
|
459
|
+
/* @__PURE__ */ jsxs3(Field2.Root, { invalid: !!errors.password, children: [
|
|
460
|
+
/* @__PURE__ */ jsx4(Field2.Label, { children: t("AbpAccount::Password") }),
|
|
461
|
+
/* @__PURE__ */ jsx4(InputGroup2, { startElement: /* @__PURE__ */ jsx4(LuLock2, {}), width: "full", children: /* @__PURE__ */ jsx4(
|
|
462
|
+
Input3,
|
|
463
|
+
{
|
|
464
|
+
id: "input-password",
|
|
465
|
+
type: "password",
|
|
466
|
+
autoComplete: "new-password",
|
|
467
|
+
placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
468
|
+
...register("password")
|
|
469
|
+
}
|
|
470
|
+
) }),
|
|
471
|
+
errors.password && /* @__PURE__ */ jsx4(Field2.ErrorText, { children: errors.password.message })
|
|
472
|
+
] }),
|
|
473
|
+
/* @__PURE__ */ jsx4(
|
|
474
|
+
Button3,
|
|
475
|
+
{
|
|
476
|
+
type: "submit",
|
|
477
|
+
colorPalette: "blue",
|
|
478
|
+
loading: isSubmitting,
|
|
479
|
+
loadingText: t("AbpAccount::Register"),
|
|
480
|
+
children: t("AbpAccount::Register")
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
] }),
|
|
484
|
+
/* @__PURE__ */ jsx4(Show2, { when: showLoginLink, children: /* @__PURE__ */ jsx4(Card2.Root, { size: "sm", mt: "10", children: /* @__PURE__ */ jsx4(Card2.Body, { children: /* @__PURE__ */ jsxs3(HStack2, { textStyle: "sm", children: [
|
|
485
|
+
/* @__PURE__ */ jsx4(Text3, { children: t("AbpAccount::AlreadyRegistered") }),
|
|
486
|
+
/* @__PURE__ */ jsx4(Link3, { asChild: true, variant: "underline", fontWeight: "semibold", children: /* @__PURE__ */ jsx4(RouterLink2, { to: loginUrl, children: t("AbpAccount::Login") }) })
|
|
487
|
+
] }) }) }) })
|
|
488
|
+
] }) })
|
|
489
|
+
] }) }) }) });
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/pages/LoginPage.tsx
|
|
493
|
+
import { Container as Container3, Center } from "@chakra-ui/react";
|
|
494
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
495
|
+
function LoginPage({
|
|
496
|
+
maxWidth = "container.sm",
|
|
497
|
+
...loginFormProps
|
|
498
|
+
}) {
|
|
499
|
+
return /* @__PURE__ */ jsx5(Container3, { maxW: maxWidth, py: 10, children: /* @__PURE__ */ jsx5(Center, { children: /* @__PURE__ */ jsx5(LoginForm, { ...loginFormProps }) }) });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/pages/RegisterPage.tsx
|
|
503
|
+
import { Container as Container4, Center as Center2 } from "@chakra-ui/react";
|
|
504
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
505
|
+
function RegisterPage({
|
|
506
|
+
maxWidth = "container.sm",
|
|
507
|
+
...registerFormProps
|
|
508
|
+
}) {
|
|
509
|
+
return /* @__PURE__ */ jsx6(Container4, { maxW: maxWidth, py: 10, children: /* @__PURE__ */ jsx6(Center2, { children: /* @__PURE__ */ jsx6(RegisterForm, { ...registerFormProps }) }) });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/routes/index.ts
|
|
513
|
+
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
|
+
];
|
|
534
|
+
var DEFAULT_REDIRECT_URL = "/";
|
|
535
|
+
var ACCOUNT_PATHS = {
|
|
536
|
+
login: "/account/login",
|
|
537
|
+
register: "/account/register"
|
|
538
|
+
};
|
|
539
|
+
export {
|
|
540
|
+
ACCOUNT_PATHS,
|
|
541
|
+
ACCOUNT_ROUTES,
|
|
542
|
+
AccountProvider,
|
|
543
|
+
DEFAULT_REDIRECT_URL,
|
|
544
|
+
LoginForm,
|
|
545
|
+
LoginPage,
|
|
546
|
+
RegisterForm,
|
|
547
|
+
RegisterPage,
|
|
548
|
+
TenantBox,
|
|
549
|
+
useAccountContext,
|
|
550
|
+
useAccountOptions,
|
|
551
|
+
usePasswordFlow
|
|
552
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account module type definitions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Account module configuration options
|
|
6
|
+
*/
|
|
7
|
+
export interface AccountOptions {
|
|
8
|
+
/**
|
|
9
|
+
* URL to redirect to after successful login
|
|
10
|
+
* @default '/'
|
|
11
|
+
*/
|
|
12
|
+
redirectUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Login form data structure
|
|
16
|
+
*/
|
|
17
|
+
export interface LoginFormData {
|
|
18
|
+
username: string;
|
|
19
|
+
password: string;
|
|
20
|
+
remember: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register form data structure
|
|
24
|
+
*/
|
|
25
|
+
export interface RegisterFormData {
|
|
26
|
+
username: string;
|
|
27
|
+
email: string;
|
|
28
|
+
password: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Tenant selection data
|
|
32
|
+
*/
|
|
33
|
+
export interface TenantInfo {
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Password flow result
|
|
38
|
+
*/
|
|
39
|
+
export interface PasswordFlowResult {
|
|
40
|
+
success: boolean;
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type LoginFormProps } from '../components';
|
|
2
|
+
/**
|
|
3
|
+
* Props for LoginPage component
|
|
4
|
+
*/
|
|
5
|
+
export interface LoginPageProps extends LoginFormProps {
|
|
6
|
+
/**
|
|
7
|
+
* Maximum width of the container
|
|
8
|
+
* @default 'container.sm'
|
|
9
|
+
*/
|
|
10
|
+
maxWidth?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* LoginPage - Page wrapper for the login form
|
|
14
|
+
*
|
|
15
|
+
* This component wraps LoginForm with proper layout and spacing.
|
|
16
|
+
* It's designed to be used as a route component.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* // In your routes configuration
|
|
21
|
+
* <Route path="/account/login" element={<LoginPage />} />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function LoginPage({ maxWidth, ...loginFormProps }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export default LoginPage;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type RegisterFormProps } from '../components';
|
|
2
|
+
/**
|
|
3
|
+
* Props for RegisterPage component
|
|
4
|
+
*/
|
|
5
|
+
export interface RegisterPageProps extends RegisterFormProps {
|
|
6
|
+
/**
|
|
7
|
+
* Maximum width of the container
|
|
8
|
+
* @default 'container.sm'
|
|
9
|
+
*/
|
|
10
|
+
maxWidth?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* RegisterPage - Page wrapper for the registration form
|
|
14
|
+
*
|
|
15
|
+
* This component wraps RegisterForm with proper layout and spacing.
|
|
16
|
+
* It's designed to be used as a route component.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* // In your routes configuration
|
|
21
|
+
* <Route path="/account/register" element={<RegisterPage />} />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function RegisterPage({ maxWidth, ...registerFormProps }: RegisterPageProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export default RegisterPage;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AccountOptions } from '../models';
|
|
3
|
+
/**
|
|
4
|
+
* Account context value
|
|
5
|
+
*/
|
|
6
|
+
export interface AccountContextValue {
|
|
7
|
+
options: Required<AccountOptions>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Props for AccountProvider
|
|
11
|
+
*/
|
|
12
|
+
export interface AccountProviderProps {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
options?: AccountOptions;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* AccountProvider - Provides account configuration to the component tree
|
|
18
|
+
*
|
|
19
|
+
* This is the React equivalent of Angular's AccountModule.forRoot(options)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <AccountProvider options={{ redirectUrl: '/dashboard' }}>
|
|
24
|
+
* <App />
|
|
25
|
+
* </AccountProvider>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function AccountProvider({ children, options }: AccountProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
/**
|
|
30
|
+
* Hook to access the account context
|
|
31
|
+
*
|
|
32
|
+
* @throws Error if used outside of AccountProvider
|
|
33
|
+
*/
|
|
34
|
+
export declare function useAccountContext(): AccountContextValue;
|
|
35
|
+
/**
|
|
36
|
+
* Hook to access account options
|
|
37
|
+
*
|
|
38
|
+
* Returns default options if not within AccountProvider (for standalone usage)
|
|
39
|
+
*/
|
|
40
|
+
export declare function useAccountOptions(): Required<AccountOptions>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AccountProvider, useAccountContext, useAccountOptions, type AccountProviderProps, type AccountContextValue, } from './AccountProvider';
|