bananas-commerce-admin 0.21.2 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/Admin.js +35 -9
- package/dist/esm/Admin.js.map +1 -1
- package/dist/esm/extensions/pim/components/ProductRow.js +16 -0
- package/dist/esm/extensions/pim/components/ProductRow.js.map +1 -1
- package/dist/esm/extensions/pim/pages/product/list.js +1 -0
- package/dist/esm/extensions/pim/pages/product/list.js.map +1 -1
- package/dist/esm/extensions/user/components/InviteUserModal.js +106 -0
- package/dist/esm/extensions/user/components/InviteUserModal.js.map +1 -0
- package/dist/esm/extensions/user/index.js +30 -0
- package/dist/esm/extensions/user/index.js.map +1 -0
- package/dist/esm/extensions/user/pages/staff/detail.js +143 -0
- package/dist/esm/extensions/user/pages/staff/detail.js.map +1 -0
- package/dist/esm/extensions/user/pages/staff/list.js +61 -0
- package/dist/esm/extensions/user/pages/staff/list.js.map +1 -0
- package/dist/esm/extensions/user/types/staff.js +2 -0
- package/dist/esm/extensions/user/types/staff.js.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pages/AcceptInvitePage.js +125 -0
- package/dist/esm/pages/AcceptInvitePage.js.map +1 -0
- package/dist/esm/pages/ResetPasswordPage.js +125 -0
- package/dist/esm/pages/ResetPasswordPage.js.map +1 -0
- package/dist/types/extensions/pim/types/product.d.ts +1 -0
- package/dist/types/extensions/user/components/InviteUserModal.d.ts +9 -0
- package/dist/types/extensions/user/index.d.ts +4 -0
- package/dist/types/extensions/user/pages/staff/detail.d.ts +4 -0
- package/dist/types/extensions/user/pages/staff/list.d.ts +4 -0
- package/dist/types/extensions/user/types/staff.d.ts +24 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/pages/AcceptInvitePage.d.ts +9 -0
- package/dist/types/pages/ResetPasswordPage.d.ts +9 -0
- package/package.json +1 -1
- package/src/Admin.tsx +53 -26
- package/src/extensions/pim/components/ProductRow.tsx +32 -0
- package/src/extensions/pim/pages/product/list.tsx +1 -0
- package/src/extensions/pim/types/product.ts +1 -0
- package/src/extensions/user/components/InviteUserModal.tsx +179 -0
- package/src/extensions/user/index.tsx +51 -0
- package/src/extensions/user/pages/staff/detail.tsx +262 -0
- package/src/extensions/user/pages/staff/list.tsx +100 -0
- package/src/extensions/user/types/staff.ts +27 -0
- package/src/index.ts +1 -0
- package/src/pages/AcceptInvitePage.tsx +232 -0
- package/src/pages/ResetPasswordPage.tsx +236 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { useSearchParams } from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
import LoadingButton from "@mui/lab/LoadingButton";
|
|
5
|
+
import {
|
|
6
|
+
Alert,
|
|
7
|
+
Backdrop,
|
|
8
|
+
Box,
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogActions,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
Stack,
|
|
14
|
+
TextField,
|
|
15
|
+
Typography,
|
|
16
|
+
} from "@mui/material";
|
|
17
|
+
import { styled } from "@mui/material/styles";
|
|
18
|
+
|
|
19
|
+
import { useSnackbar } from "notistack";
|
|
20
|
+
|
|
21
|
+
import Brand from "../components/Brand";
|
|
22
|
+
import Content from "../containers/Content";
|
|
23
|
+
import { useApi } from "../contexts/ApiContext";
|
|
24
|
+
import { useI18n } from "../contexts/I18nContext";
|
|
25
|
+
import { LogoType } from "../types";
|
|
26
|
+
|
|
27
|
+
export interface AcceptInvitePageProps {
|
|
28
|
+
title?: string;
|
|
29
|
+
logo?: LogoType;
|
|
30
|
+
logoHeight?: number | string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const StyledDialog = styled(Dialog, {
|
|
34
|
+
name: "BananasAcceptInviteDialog",
|
|
35
|
+
slot: "Root",
|
|
36
|
+
})(() => ({}));
|
|
37
|
+
|
|
38
|
+
const StyledDialogTitle = styled(DialogTitle, {
|
|
39
|
+
name: "BananasAcceptInviteDialog",
|
|
40
|
+
slot: "Title",
|
|
41
|
+
})(({ theme }) =>
|
|
42
|
+
theme.unstable_sx({
|
|
43
|
+
bgcolor: "primary.main",
|
|
44
|
+
color: "primary.contrastText",
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const StyledBackdrop = styled(Backdrop, {
|
|
49
|
+
name: "BananasAcceptInviteDialog",
|
|
50
|
+
slot: "Backdrop",
|
|
51
|
+
})(({ theme }) =>
|
|
52
|
+
theme.unstable_sx({
|
|
53
|
+
bgcolor: "primary.dark",
|
|
54
|
+
m: 0,
|
|
55
|
+
p: 2,
|
|
56
|
+
textAlign: "center",
|
|
57
|
+
alignItems: "middle",
|
|
58
|
+
justifyContent: "center",
|
|
59
|
+
display: "flex",
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
export const AcceptInvitePage: React.FC<AcceptInvitePageProps> = ({ title, logo, logoHeight }) => {
|
|
64
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
65
|
+
const { t } = useI18n();
|
|
66
|
+
const api = useApi();
|
|
67
|
+
const [searchParams] = useSearchParams();
|
|
68
|
+
|
|
69
|
+
const [password, setPassword] = useState("");
|
|
70
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
71
|
+
const [loading, setLoading] = useState(false);
|
|
72
|
+
const [error, setError] = useState("");
|
|
73
|
+
const [tokenError, setTokenError] = useState("");
|
|
74
|
+
|
|
75
|
+
const token = searchParams.get("token");
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!token) {
|
|
79
|
+
setTokenError(t("No invite token provided"));
|
|
80
|
+
}
|
|
81
|
+
}, [token, t]);
|
|
82
|
+
|
|
83
|
+
const handleSubmit = async (event: React.FormEvent) => {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
setError("");
|
|
86
|
+
|
|
87
|
+
if (!token) {
|
|
88
|
+
setTokenError(t("No invite token provided"));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (password.length < 8) {
|
|
93
|
+
setError(t("Password must be at least 8 characters long"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (password !== confirmPassword) {
|
|
98
|
+
setError(t("Passwords do not match"));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setLoading(true);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await api?.operations["user.staff:invite-accept"].call({
|
|
106
|
+
body: {
|
|
107
|
+
token,
|
|
108
|
+
password,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (response?.ok) {
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
enqueueSnackbar(data.message || t("User created successfully!"), {
|
|
115
|
+
variant: "success",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// User is already logged in on the backend, redirect to dashboard
|
|
119
|
+
// Use full page navigation to reinitialize the UserContext
|
|
120
|
+
window.location.href = "/";
|
|
121
|
+
} else {
|
|
122
|
+
const errorData = await response?.json();
|
|
123
|
+
if (errorData?.detail && Array.isArray(errorData.detail)) {
|
|
124
|
+
const tokenErrors = errorData.detail.filter((err: { loc?: string[]; msg?: string }) =>
|
|
125
|
+
err.loc?.includes("token"),
|
|
126
|
+
);
|
|
127
|
+
if (tokenErrors.length > 0) {
|
|
128
|
+
setTokenError(tokenErrors[0].msg);
|
|
129
|
+
} else {
|
|
130
|
+
setError(errorData.detail[0]?.msg || t("Failed to accept invite"));
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
setError(t("Failed to accept invite. Please try again."));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error("[ACCEPT_INVITE]", error);
|
|
138
|
+
setError(t("An unexpected error occurred. Please try again."));
|
|
139
|
+
} finally {
|
|
140
|
+
setLoading(false);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<Content layout="fullWidth">
|
|
146
|
+
<StyledDialog
|
|
147
|
+
open
|
|
148
|
+
BackdropComponent={StyledBackdrop}
|
|
149
|
+
PaperProps={{ elevation: 1 }}
|
|
150
|
+
sx={{ "> * > *": { width: "100%" } }}
|
|
151
|
+
>
|
|
152
|
+
<StyledDialogTitle>
|
|
153
|
+
{logo ? (
|
|
154
|
+
<Brand
|
|
155
|
+
LogoProps={{ style: { width: "auto", height: logoHeight ?? "24px !important" } }}
|
|
156
|
+
src={logo}
|
|
157
|
+
/>
|
|
158
|
+
) : (
|
|
159
|
+
<Typography sx={{ fontWeight: "bold", color: "inherit" }}>{title}</Typography>
|
|
160
|
+
)}
|
|
161
|
+
</StyledDialogTitle>
|
|
162
|
+
|
|
163
|
+
<Box component="form" onSubmit={handleSubmit}>
|
|
164
|
+
<DialogContent>
|
|
165
|
+
<Stack spacing={2}>
|
|
166
|
+
<Typography variant="h6">{t("Accept Staff Invite")}</Typography>
|
|
167
|
+
<Typography color="text.secondary" variant="body2">
|
|
168
|
+
{t("Set your password to complete your account setup.")}
|
|
169
|
+
</Typography>
|
|
170
|
+
|
|
171
|
+
{tokenError && <Alert severity="error">{tokenError}</Alert>}
|
|
172
|
+
|
|
173
|
+
{error && !tokenError && <Alert severity="error">{error}</Alert>}
|
|
174
|
+
|
|
175
|
+
{!tokenError && (
|
|
176
|
+
<>
|
|
177
|
+
<TextField
|
|
178
|
+
fullWidth
|
|
179
|
+
required
|
|
180
|
+
color="secondary"
|
|
181
|
+
disabled={loading}
|
|
182
|
+
inputProps={{ "aria-label": "Password", minLength: 8 }}
|
|
183
|
+
label={t("Password")}
|
|
184
|
+
name="password"
|
|
185
|
+
type="password"
|
|
186
|
+
value={password}
|
|
187
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
188
|
+
/>
|
|
189
|
+
<TextField
|
|
190
|
+
fullWidth
|
|
191
|
+
required
|
|
192
|
+
color="secondary"
|
|
193
|
+
disabled={loading}
|
|
194
|
+
inputProps={{ "aria-label": "Confirm Password", minLength: 8 }}
|
|
195
|
+
label={t("Confirm Password")}
|
|
196
|
+
name="confirmPassword"
|
|
197
|
+
type="password"
|
|
198
|
+
value={confirmPassword}
|
|
199
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
200
|
+
/>
|
|
201
|
+
</>
|
|
202
|
+
)}
|
|
203
|
+
</Stack>
|
|
204
|
+
</DialogContent>
|
|
205
|
+
|
|
206
|
+
<DialogActions
|
|
207
|
+
sx={{
|
|
208
|
+
pt: 0,
|
|
209
|
+
pb: 2,
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{!tokenError && (
|
|
213
|
+
<LoadingButton
|
|
214
|
+
aria-label="accept-invite"
|
|
215
|
+
color="primary"
|
|
216
|
+
disabled={!token}
|
|
217
|
+
loading={loading}
|
|
218
|
+
sx={{ margin: "auto" }}
|
|
219
|
+
type="submit"
|
|
220
|
+
variant="contained"
|
|
221
|
+
>
|
|
222
|
+
{t("Create Account")}
|
|
223
|
+
</LoadingButton>
|
|
224
|
+
)}
|
|
225
|
+
</DialogActions>
|
|
226
|
+
</Box>
|
|
227
|
+
</StyledDialog>
|
|
228
|
+
</Content>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export default AcceptInvitePage;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { useSearchParams } from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
import LoadingButton from "@mui/lab/LoadingButton";
|
|
5
|
+
import {
|
|
6
|
+
Alert,
|
|
7
|
+
Backdrop,
|
|
8
|
+
Box,
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogActions,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
Stack,
|
|
14
|
+
TextField,
|
|
15
|
+
Typography,
|
|
16
|
+
} from "@mui/material";
|
|
17
|
+
import { styled } from "@mui/material/styles";
|
|
18
|
+
|
|
19
|
+
import { useSnackbar } from "notistack";
|
|
20
|
+
|
|
21
|
+
import Brand from "../components/Brand";
|
|
22
|
+
import Content from "../containers/Content";
|
|
23
|
+
import { useApi } from "../contexts/ApiContext";
|
|
24
|
+
import { useI18n } from "../contexts/I18nContext";
|
|
25
|
+
import { LogoType } from "../types";
|
|
26
|
+
|
|
27
|
+
export interface ResetPasswordPageProps {
|
|
28
|
+
title?: string;
|
|
29
|
+
logo?: LogoType;
|
|
30
|
+
logoHeight?: number | string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const StyledDialog = styled(Dialog, {
|
|
34
|
+
name: "BananasResetPasswordDialog",
|
|
35
|
+
slot: "Root",
|
|
36
|
+
})(() => ({}));
|
|
37
|
+
|
|
38
|
+
const StyledDialogTitle = styled(DialogTitle, {
|
|
39
|
+
name: "BananasResetPasswordDialog",
|
|
40
|
+
slot: "Title",
|
|
41
|
+
})(({ theme }) =>
|
|
42
|
+
theme.unstable_sx({
|
|
43
|
+
bgcolor: "primary.main",
|
|
44
|
+
color: "primary.contrastText",
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const StyledBackdrop = styled(Backdrop, {
|
|
49
|
+
name: "BananasResetPasswordDialog",
|
|
50
|
+
slot: "Backdrop",
|
|
51
|
+
})(({ theme }) =>
|
|
52
|
+
theme.unstable_sx({
|
|
53
|
+
bgcolor: "primary.dark",
|
|
54
|
+
m: 0,
|
|
55
|
+
p: 2,
|
|
56
|
+
textAlign: "center",
|
|
57
|
+
alignItems: "middle",
|
|
58
|
+
justifyContent: "center",
|
|
59
|
+
display: "flex",
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
export const ResetPasswordPage: React.FC<ResetPasswordPageProps> = ({
|
|
64
|
+
title,
|
|
65
|
+
logo,
|
|
66
|
+
logoHeight,
|
|
67
|
+
}) => {
|
|
68
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
69
|
+
const { t } = useI18n();
|
|
70
|
+
const api = useApi();
|
|
71
|
+
const [searchParams] = useSearchParams();
|
|
72
|
+
|
|
73
|
+
const [password, setPassword] = useState("");
|
|
74
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
75
|
+
const [loading, setLoading] = useState(false);
|
|
76
|
+
const [error, setError] = useState("");
|
|
77
|
+
const [tokenError, setTokenError] = useState("");
|
|
78
|
+
|
|
79
|
+
const token = searchParams.get("token");
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!token) {
|
|
83
|
+
setTokenError(t("No password reset token provided"));
|
|
84
|
+
}
|
|
85
|
+
}, [token, t]);
|
|
86
|
+
|
|
87
|
+
const handleSubmit = async (event: React.FormEvent) => {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
setError("");
|
|
90
|
+
|
|
91
|
+
if (!token) {
|
|
92
|
+
setTokenError(t("No password reset token provided"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (password.length < 8) {
|
|
97
|
+
setError(t("Password must be at least 8 characters long"));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (password !== confirmPassword) {
|
|
102
|
+
setError(t("Passwords do not match"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setLoading(true);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const response = await api?.operations["user.staff:password-reset-confirm"].call({
|
|
110
|
+
body: {
|
|
111
|
+
token,
|
|
112
|
+
new_password: password,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (response?.ok) {
|
|
117
|
+
const data = await response.json();
|
|
118
|
+
enqueueSnackbar(data.message || t("Password reset successfully!"), {
|
|
119
|
+
variant: "success",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// User is logged in on the backend after password reset, redirect to dashboard
|
|
123
|
+
// Use full page navigation to reinitialize the UserContext
|
|
124
|
+
window.location.href = "/";
|
|
125
|
+
} else {
|
|
126
|
+
const errorData = await response?.json();
|
|
127
|
+
if (errorData?.detail && Array.isArray(errorData.detail)) {
|
|
128
|
+
const tokenErrors = errorData.detail.filter((err: { loc?: string[]; msg?: string }) =>
|
|
129
|
+
err.loc?.includes("token"),
|
|
130
|
+
);
|
|
131
|
+
if (tokenErrors.length > 0) {
|
|
132
|
+
setTokenError(tokenErrors[0].msg);
|
|
133
|
+
} else {
|
|
134
|
+
setError(errorData.detail[0]?.msg || t("Failed to reset password"));
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
setError(t("Failed to reset password. Please try again."));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error("[RESET_PASSWORD]", error);
|
|
142
|
+
setError(t("An unexpected error occurred. Please try again."));
|
|
143
|
+
} finally {
|
|
144
|
+
setLoading(false);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Content layout="fullWidth">
|
|
150
|
+
<StyledDialog
|
|
151
|
+
open
|
|
152
|
+
BackdropComponent={StyledBackdrop}
|
|
153
|
+
PaperProps={{ elevation: 1 }}
|
|
154
|
+
sx={{ "> * > *": { width: "100%" } }}
|
|
155
|
+
>
|
|
156
|
+
<StyledDialogTitle>
|
|
157
|
+
{logo ? (
|
|
158
|
+
<Brand
|
|
159
|
+
LogoProps={{ style: { width: "auto", height: logoHeight ?? "24px !important" } }}
|
|
160
|
+
src={logo}
|
|
161
|
+
/>
|
|
162
|
+
) : (
|
|
163
|
+
<Typography sx={{ fontWeight: "bold", color: "inherit" }}>{title}</Typography>
|
|
164
|
+
)}
|
|
165
|
+
</StyledDialogTitle>
|
|
166
|
+
|
|
167
|
+
<Box component="form" onSubmit={handleSubmit}>
|
|
168
|
+
<DialogContent>
|
|
169
|
+
<Stack spacing={2}>
|
|
170
|
+
<Typography variant="h6">{t("Reset Your Password")}</Typography>
|
|
171
|
+
<Typography color="text.secondary" variant="body2">
|
|
172
|
+
{t("Enter your new password below.")}
|
|
173
|
+
</Typography>
|
|
174
|
+
|
|
175
|
+
{tokenError && <Alert severity="error">{tokenError}</Alert>}
|
|
176
|
+
|
|
177
|
+
{error && !tokenError && <Alert severity="error">{error}</Alert>}
|
|
178
|
+
|
|
179
|
+
{!tokenError && (
|
|
180
|
+
<>
|
|
181
|
+
<TextField
|
|
182
|
+
fullWidth
|
|
183
|
+
required
|
|
184
|
+
color="secondary"
|
|
185
|
+
disabled={loading}
|
|
186
|
+
inputProps={{ "aria-label": "New Password", minLength: 8 }}
|
|
187
|
+
label={t("New Password")}
|
|
188
|
+
name="password"
|
|
189
|
+
type="password"
|
|
190
|
+
value={password}
|
|
191
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
192
|
+
/>
|
|
193
|
+
<TextField
|
|
194
|
+
fullWidth
|
|
195
|
+
required
|
|
196
|
+
color="secondary"
|
|
197
|
+
disabled={loading}
|
|
198
|
+
inputProps={{ "aria-label": "Confirm New Password", minLength: 8 }}
|
|
199
|
+
label={t("Confirm New Password")}
|
|
200
|
+
name="confirmPassword"
|
|
201
|
+
type="password"
|
|
202
|
+
value={confirmPassword}
|
|
203
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
204
|
+
/>
|
|
205
|
+
</>
|
|
206
|
+
)}
|
|
207
|
+
</Stack>
|
|
208
|
+
</DialogContent>
|
|
209
|
+
|
|
210
|
+
<DialogActions
|
|
211
|
+
sx={{
|
|
212
|
+
pt: 0,
|
|
213
|
+
pb: 2,
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
{!tokenError && (
|
|
217
|
+
<LoadingButton
|
|
218
|
+
aria-label="reset-password"
|
|
219
|
+
color="primary"
|
|
220
|
+
disabled={!token}
|
|
221
|
+
loading={loading}
|
|
222
|
+
sx={{ margin: "auto" }}
|
|
223
|
+
type="submit"
|
|
224
|
+
variant="contained"
|
|
225
|
+
>
|
|
226
|
+
{t("Reset Password")}
|
|
227
|
+
</LoadingButton>
|
|
228
|
+
)}
|
|
229
|
+
</DialogActions>
|
|
230
|
+
</Box>
|
|
231
|
+
</StyledDialog>
|
|
232
|
+
</Content>
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export default ResetPasswordPage;
|