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.
Files changed (44) hide show
  1. package/dist/esm/Admin.js +35 -9
  2. package/dist/esm/Admin.js.map +1 -1
  3. package/dist/esm/extensions/pim/components/ProductRow.js +16 -0
  4. package/dist/esm/extensions/pim/components/ProductRow.js.map +1 -1
  5. package/dist/esm/extensions/pim/pages/product/list.js +1 -0
  6. package/dist/esm/extensions/pim/pages/product/list.js.map +1 -1
  7. package/dist/esm/extensions/user/components/InviteUserModal.js +106 -0
  8. package/dist/esm/extensions/user/components/InviteUserModal.js.map +1 -0
  9. package/dist/esm/extensions/user/index.js +30 -0
  10. package/dist/esm/extensions/user/index.js.map +1 -0
  11. package/dist/esm/extensions/user/pages/staff/detail.js +143 -0
  12. package/dist/esm/extensions/user/pages/staff/detail.js.map +1 -0
  13. package/dist/esm/extensions/user/pages/staff/list.js +61 -0
  14. package/dist/esm/extensions/user/pages/staff/list.js.map +1 -0
  15. package/dist/esm/extensions/user/types/staff.js +2 -0
  16. package/dist/esm/extensions/user/types/staff.js.map +1 -0
  17. package/dist/esm/index.js +1 -0
  18. package/dist/esm/index.js.map +1 -1
  19. package/dist/esm/pages/AcceptInvitePage.js +125 -0
  20. package/dist/esm/pages/AcceptInvitePage.js.map +1 -0
  21. package/dist/esm/pages/ResetPasswordPage.js +125 -0
  22. package/dist/esm/pages/ResetPasswordPage.js.map +1 -0
  23. package/dist/types/extensions/pim/types/product.d.ts +1 -0
  24. package/dist/types/extensions/user/components/InviteUserModal.d.ts +9 -0
  25. package/dist/types/extensions/user/index.d.ts +4 -0
  26. package/dist/types/extensions/user/pages/staff/detail.d.ts +4 -0
  27. package/dist/types/extensions/user/pages/staff/list.d.ts +4 -0
  28. package/dist/types/extensions/user/types/staff.d.ts +24 -0
  29. package/dist/types/index.d.ts +1 -0
  30. package/dist/types/pages/AcceptInvitePage.d.ts +9 -0
  31. package/dist/types/pages/ResetPasswordPage.d.ts +9 -0
  32. package/package.json +1 -1
  33. package/src/Admin.tsx +53 -26
  34. package/src/extensions/pim/components/ProductRow.tsx +32 -0
  35. package/src/extensions/pim/pages/product/list.tsx +1 -0
  36. package/src/extensions/pim/types/product.ts +1 -0
  37. package/src/extensions/user/components/InviteUserModal.tsx +179 -0
  38. package/src/extensions/user/index.tsx +51 -0
  39. package/src/extensions/user/pages/staff/detail.tsx +262 -0
  40. package/src/extensions/user/pages/staff/list.tsx +100 -0
  41. package/src/extensions/user/types/staff.ts +27 -0
  42. package/src/index.ts +1 -0
  43. package/src/pages/AcceptInvitePage.tsx +232 -0
  44. package/src/pages/ResetPasswordPage.tsx +236 -0
@@ -0,0 +1,125 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useSearchParams } from "react-router-dom";
3
+ import LoadingButton from "@mui/lab/LoadingButton";
4
+ import { Alert, Backdrop, Box, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography, } from "@mui/material";
5
+ import { styled } from "@mui/material/styles";
6
+ import { useSnackbar } from "notistack";
7
+ import Brand from "../components/Brand";
8
+ import Content from "../containers/Content";
9
+ import { useApi } from "../contexts/ApiContext";
10
+ import { useI18n } from "../contexts/I18nContext";
11
+ const StyledDialog = styled(Dialog, {
12
+ name: "BananasAcceptInviteDialog",
13
+ slot: "Root",
14
+ })(() => ({}));
15
+ const StyledDialogTitle = styled(DialogTitle, {
16
+ name: "BananasAcceptInviteDialog",
17
+ slot: "Title",
18
+ })(({ theme }) => theme.unstable_sx({
19
+ bgcolor: "primary.main",
20
+ color: "primary.contrastText",
21
+ }));
22
+ const StyledBackdrop = styled(Backdrop, {
23
+ name: "BananasAcceptInviteDialog",
24
+ slot: "Backdrop",
25
+ })(({ theme }) => theme.unstable_sx({
26
+ bgcolor: "primary.dark",
27
+ m: 0,
28
+ p: 2,
29
+ textAlign: "center",
30
+ alignItems: "middle",
31
+ justifyContent: "center",
32
+ display: "flex",
33
+ }));
34
+ export const AcceptInvitePage = ({ title, logo, logoHeight }) => {
35
+ const { enqueueSnackbar } = useSnackbar();
36
+ const { t } = useI18n();
37
+ const api = useApi();
38
+ const [searchParams] = useSearchParams();
39
+ const [password, setPassword] = useState("");
40
+ const [confirmPassword, setConfirmPassword] = useState("");
41
+ const [loading, setLoading] = useState(false);
42
+ const [error, setError] = useState("");
43
+ const [tokenError, setTokenError] = useState("");
44
+ const token = searchParams.get("token");
45
+ useEffect(() => {
46
+ if (!token) {
47
+ setTokenError(t("No invite token provided"));
48
+ }
49
+ }, [token, t]);
50
+ const handleSubmit = async (event) => {
51
+ event.preventDefault();
52
+ setError("");
53
+ if (!token) {
54
+ setTokenError(t("No invite token provided"));
55
+ return;
56
+ }
57
+ if (password.length < 8) {
58
+ setError(t("Password must be at least 8 characters long"));
59
+ return;
60
+ }
61
+ if (password !== confirmPassword) {
62
+ setError(t("Passwords do not match"));
63
+ return;
64
+ }
65
+ setLoading(true);
66
+ try {
67
+ const response = await api?.operations["user.staff:invite-accept"].call({
68
+ body: {
69
+ token,
70
+ password,
71
+ },
72
+ });
73
+ if (response?.ok) {
74
+ const data = await response.json();
75
+ enqueueSnackbar(data.message || t("User created successfully!"), {
76
+ variant: "success",
77
+ });
78
+ // User is already logged in on the backend, redirect to dashboard
79
+ // Use full page navigation to reinitialize the UserContext
80
+ window.location.href = "/";
81
+ }
82
+ else {
83
+ const errorData = await response?.json();
84
+ if (errorData?.detail && Array.isArray(errorData.detail)) {
85
+ const tokenErrors = errorData.detail.filter((err) => err.loc?.includes("token"));
86
+ if (tokenErrors.length > 0) {
87
+ setTokenError(tokenErrors[0].msg);
88
+ }
89
+ else {
90
+ setError(errorData.detail[0]?.msg || t("Failed to accept invite"));
91
+ }
92
+ }
93
+ else {
94
+ setError(t("Failed to accept invite. Please try again."));
95
+ }
96
+ }
97
+ }
98
+ catch (error) {
99
+ console.error("[ACCEPT_INVITE]", error);
100
+ setError(t("An unexpected error occurred. Please try again."));
101
+ }
102
+ finally {
103
+ setLoading(false);
104
+ }
105
+ };
106
+ return (React.createElement(Content, { layout: "fullWidth" },
107
+ React.createElement(StyledDialog, { open: true, BackdropComponent: StyledBackdrop, PaperProps: { elevation: 1 }, sx: { "> * > *": { width: "100%" } } },
108
+ React.createElement(StyledDialogTitle, null, logo ? (React.createElement(Brand, { LogoProps: { style: { width: "auto", height: logoHeight ?? "24px !important" } }, src: logo })) : (React.createElement(Typography, { sx: { fontWeight: "bold", color: "inherit" } }, title))),
109
+ React.createElement(Box, { component: "form", onSubmit: handleSubmit },
110
+ React.createElement(DialogContent, null,
111
+ React.createElement(Stack, { spacing: 2 },
112
+ React.createElement(Typography, { variant: "h6" }, t("Accept Staff Invite")),
113
+ React.createElement(Typography, { color: "text.secondary", variant: "body2" }, t("Set your password to complete your account setup.")),
114
+ tokenError && React.createElement(Alert, { severity: "error" }, tokenError),
115
+ error && !tokenError && React.createElement(Alert, { severity: "error" }, error),
116
+ !tokenError && (React.createElement(React.Fragment, null,
117
+ React.createElement(TextField, { fullWidth: true, required: true, color: "secondary", disabled: loading, inputProps: { "aria-label": "Password", minLength: 8 }, label: t("Password"), name: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value) }),
118
+ React.createElement(TextField, { fullWidth: true, required: true, color: "secondary", disabled: loading, inputProps: { "aria-label": "Confirm Password", minLength: 8 }, label: t("Confirm Password"), name: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value) }))))),
119
+ React.createElement(DialogActions, { sx: {
120
+ pt: 0,
121
+ pb: 2,
122
+ } }, !tokenError && (React.createElement(LoadingButton, { "aria-label": "accept-invite", color: "primary", disabled: !token, loading: loading, sx: { margin: "auto" }, type: "submit", variant: "contained" }, t("Create Account"))))))));
123
+ };
124
+ export default AcceptInvitePage;
125
+ //# sourceMappingURL=AcceptInvitePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AcceptInvitePage.js","sourceRoot":"","sources":["../../../src/pages/AcceptInvitePage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,aAAa,MAAM,wBAAwB,CAAC;AACnD,OAAO,EACL,KAAK,EACL,QAAQ,EACR,GAAG,EACH,MAAM,EACN,aAAa,EACb,aAAa,EACb,WAAW,EACX,KAAK,EACL,SAAS,EACT,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,MAAM,qBAAqB,CAAC;AACxC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AASlD,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE;IAClC,IAAI,EAAE,2BAA2B;IACjC,IAAI,EAAE,MAAM;CACb,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAEf,MAAM,iBAAiB,GAAG,MAAM,CAAC,WAAW,EAAE;IAC5C,IAAI,EAAE,2BAA2B;IACjC,IAAI,EAAE,OAAO;CACd,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACf,KAAK,CAAC,WAAW,CAAC;IAChB,OAAO,EAAE,cAAc;IACvB,KAAK,EAAE,sBAAsB;CAC9B,CAAC,CACH,CAAC;AAEF,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,EAAE;IACtC,IAAI,EAAE,2BAA2B;IACjC,IAAI,EAAE,UAAU;CACjB,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACf,KAAK,CAAC,WAAW,CAAC;IAChB,OAAO,EAAE,cAAc;IACvB,CAAC,EAAE,CAAC;IACJ,CAAC,EAAE,CAAC;IACJ,SAAS,EAAE,QAAQ;IACnB,UAAU,EAAE,QAAQ;IACpB,cAAc,EAAE,QAAQ;IACxB,OAAO,EAAE,MAAM;CAChB,CAAC,CACH,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAoC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE;IAC/F,MAAM,EAAE,eAAe,EAAE,GAAG,WAAW,EAAE,CAAC;IAC1C,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,YAAY,CAAC,GAAG,eAAe,EAAE,CAAC;IAEzC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC7C,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEjD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAExC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,aAAa,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAEf,MAAM,YAAY,GAAG,KAAK,EAAE,KAAsB,EAAE,EAAE;QACpD,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,QAAQ,CAAC,EAAE,CAAC,CAAC;QAEb,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,aAAa,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,CAAC,CAAC,6CAA6C,CAAC,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;YACjC,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QAED,UAAU,CAAC,IAAI,CAAC,CAAC;QAEjB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,GAAG,EAAE,UAAU,CAAC,0BAA0B,CAAC,CAAC,IAAI,CAAC;gBACtE,IAAI,EAAE;oBACJ,KAAK;oBACL,QAAQ;iBACT;aACF,CAAC,CAAC;YAEH,IAAI,QAAQ,EAAE,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,eAAe,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,4BAA4B,CAAC,EAAE;oBAC/D,OAAO,EAAE,SAAS;iBACnB,CAAC,CAAC;gBAEH,kEAAkE;gBAClE,2DAA2D;gBAC3D,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,MAAM,SAAS,GAAG,MAAM,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACzC,IAAI,SAAS,EAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;oBACzD,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAqC,EAAE,EAAE,CACpF,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAC3B,CAAC;oBACF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC3B,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;oBACpC,CAAC;yBAAM,CAAC;wBACN,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC;oBACrE,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;YACxC,QAAQ,CAAC,CAAC,CAAC,iDAAiD,CAAC,CAAC,CAAC;QACjE,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,oBAAC,OAAO,IAAC,MAAM,EAAC,WAAW;QACzB,oBAAC,YAAY,IACX,IAAI,QACJ,iBAAiB,EAAE,cAAc,EACjC,UAAU,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,EAC5B,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAEpC,oBAAC,iBAAiB,QACf,IAAI,CAAC,CAAC,CAAC,CACN,oBAAC,KAAK,IACJ,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,IAAI,iBAAiB,EAAE,EAAE,EAChF,GAAG,EAAE,IAAI,GACT,CACH,CAAC,CAAC,CAAC,CACF,oBAAC,UAAU,IAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAG,KAAK,CAAc,CAC/E,CACiB;YAEpB,oBAAC,GAAG,IAAC,SAAS,EAAC,MAAM,EAAC,QAAQ,EAAE,YAAY;gBAC1C,oBAAC,aAAa;oBACZ,oBAAC,KAAK,IAAC,OAAO,EAAE,CAAC;wBACf,oBAAC,UAAU,IAAC,OAAO,EAAC,IAAI,IAAE,CAAC,CAAC,qBAAqB,CAAC,CAAc;wBAChE,oBAAC,UAAU,IAAC,KAAK,EAAC,gBAAgB,EAAC,OAAO,EAAC,OAAO,IAC/C,CAAC,CAAC,mDAAmD,CAAC,CAC5C;wBAEZ,UAAU,IAAI,oBAAC,KAAK,IAAC,QAAQ,EAAC,OAAO,IAAE,UAAU,CAAS;wBAE1D,KAAK,IAAI,CAAC,UAAU,IAAI,oBAAC,KAAK,IAAC,QAAQ,EAAC,OAAO,IAAE,KAAK,CAAS;wBAE/D,CAAC,UAAU,IAAI,CACd;4BACE,oBAAC,SAAS,IACR,SAAS,QACT,QAAQ,QACR,KAAK,EAAC,WAAW,EACjB,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,EAAE,EACtD,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC,EACpB,IAAI,EAAC,UAAU,EACf,IAAI,EAAC,UAAU,EACf,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAC5C;4BACF,oBAAC,SAAS,IACR,SAAS,QACT,QAAQ,QACR,KAAK,EAAC,WAAW,EACjB,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,EAAE,YAAY,EAAE,kBAAkB,EAAE,SAAS,EAAE,CAAC,EAAE,EAC9D,KAAK,EAAE,CAAC,CAAC,kBAAkB,CAAC,EAC5B,IAAI,EAAC,iBAAiB,EACtB,IAAI,EAAC,UAAU,EACf,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GACnD,CACD,CACJ,CACK,CACM;gBAEhB,oBAAC,aAAa,IACZ,EAAE,EAAE;wBACF,EAAE,EAAE,CAAC;wBACL,EAAE,EAAE,CAAC;qBACN,IAEA,CAAC,UAAU,IAAI,CACd,oBAAC,aAAa,kBACD,eAAe,EAC1B,KAAK,EAAC,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAChB,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EACtB,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,WAAW,IAElB,CAAC,CAAC,gBAAgB,CAAC,CACN,CACjB,CACa,CACZ,CACO,CACP,CACX,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,125 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useSearchParams } from "react-router-dom";
3
+ import LoadingButton from "@mui/lab/LoadingButton";
4
+ import { Alert, Backdrop, Box, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography, } from "@mui/material";
5
+ import { styled } from "@mui/material/styles";
6
+ import { useSnackbar } from "notistack";
7
+ import Brand from "../components/Brand";
8
+ import Content from "../containers/Content";
9
+ import { useApi } from "../contexts/ApiContext";
10
+ import { useI18n } from "../contexts/I18nContext";
11
+ const StyledDialog = styled(Dialog, {
12
+ name: "BananasResetPasswordDialog",
13
+ slot: "Root",
14
+ })(() => ({}));
15
+ const StyledDialogTitle = styled(DialogTitle, {
16
+ name: "BananasResetPasswordDialog",
17
+ slot: "Title",
18
+ })(({ theme }) => theme.unstable_sx({
19
+ bgcolor: "primary.main",
20
+ color: "primary.contrastText",
21
+ }));
22
+ const StyledBackdrop = styled(Backdrop, {
23
+ name: "BananasResetPasswordDialog",
24
+ slot: "Backdrop",
25
+ })(({ theme }) => theme.unstable_sx({
26
+ bgcolor: "primary.dark",
27
+ m: 0,
28
+ p: 2,
29
+ textAlign: "center",
30
+ alignItems: "middle",
31
+ justifyContent: "center",
32
+ display: "flex",
33
+ }));
34
+ export const ResetPasswordPage = ({ title, logo, logoHeight, }) => {
35
+ const { enqueueSnackbar } = useSnackbar();
36
+ const { t } = useI18n();
37
+ const api = useApi();
38
+ const [searchParams] = useSearchParams();
39
+ const [password, setPassword] = useState("");
40
+ const [confirmPassword, setConfirmPassword] = useState("");
41
+ const [loading, setLoading] = useState(false);
42
+ const [error, setError] = useState("");
43
+ const [tokenError, setTokenError] = useState("");
44
+ const token = searchParams.get("token");
45
+ useEffect(() => {
46
+ if (!token) {
47
+ setTokenError(t("No password reset token provided"));
48
+ }
49
+ }, [token, t]);
50
+ const handleSubmit = async (event) => {
51
+ event.preventDefault();
52
+ setError("");
53
+ if (!token) {
54
+ setTokenError(t("No password reset token provided"));
55
+ return;
56
+ }
57
+ if (password.length < 8) {
58
+ setError(t("Password must be at least 8 characters long"));
59
+ return;
60
+ }
61
+ if (password !== confirmPassword) {
62
+ setError(t("Passwords do not match"));
63
+ return;
64
+ }
65
+ setLoading(true);
66
+ try {
67
+ const response = await api?.operations["user.staff:password-reset-confirm"].call({
68
+ body: {
69
+ token,
70
+ new_password: password,
71
+ },
72
+ });
73
+ if (response?.ok) {
74
+ const data = await response.json();
75
+ enqueueSnackbar(data.message || t("Password reset successfully!"), {
76
+ variant: "success",
77
+ });
78
+ // User is logged in on the backend after password reset, redirect to dashboard
79
+ // Use full page navigation to reinitialize the UserContext
80
+ window.location.href = "/";
81
+ }
82
+ else {
83
+ const errorData = await response?.json();
84
+ if (errorData?.detail && Array.isArray(errorData.detail)) {
85
+ const tokenErrors = errorData.detail.filter((err) => err.loc?.includes("token"));
86
+ if (tokenErrors.length > 0) {
87
+ setTokenError(tokenErrors[0].msg);
88
+ }
89
+ else {
90
+ setError(errorData.detail[0]?.msg || t("Failed to reset password"));
91
+ }
92
+ }
93
+ else {
94
+ setError(t("Failed to reset password. Please try again."));
95
+ }
96
+ }
97
+ }
98
+ catch (error) {
99
+ console.error("[RESET_PASSWORD]", error);
100
+ setError(t("An unexpected error occurred. Please try again."));
101
+ }
102
+ finally {
103
+ setLoading(false);
104
+ }
105
+ };
106
+ return (React.createElement(Content, { layout: "fullWidth" },
107
+ React.createElement(StyledDialog, { open: true, BackdropComponent: StyledBackdrop, PaperProps: { elevation: 1 }, sx: { "> * > *": { width: "100%" } } },
108
+ React.createElement(StyledDialogTitle, null, logo ? (React.createElement(Brand, { LogoProps: { style: { width: "auto", height: logoHeight ?? "24px !important" } }, src: logo })) : (React.createElement(Typography, { sx: { fontWeight: "bold", color: "inherit" } }, title))),
109
+ React.createElement(Box, { component: "form", onSubmit: handleSubmit },
110
+ React.createElement(DialogContent, null,
111
+ React.createElement(Stack, { spacing: 2 },
112
+ React.createElement(Typography, { variant: "h6" }, t("Reset Your Password")),
113
+ React.createElement(Typography, { color: "text.secondary", variant: "body2" }, t("Enter your new password below.")),
114
+ tokenError && React.createElement(Alert, { severity: "error" }, tokenError),
115
+ error && !tokenError && React.createElement(Alert, { severity: "error" }, error),
116
+ !tokenError && (React.createElement(React.Fragment, null,
117
+ React.createElement(TextField, { fullWidth: true, required: true, color: "secondary", disabled: loading, inputProps: { "aria-label": "New Password", minLength: 8 }, label: t("New Password"), name: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value) }),
118
+ React.createElement(TextField, { fullWidth: true, required: true, color: "secondary", disabled: loading, inputProps: { "aria-label": "Confirm New Password", minLength: 8 }, label: t("Confirm New Password"), name: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value) }))))),
119
+ React.createElement(DialogActions, { sx: {
120
+ pt: 0,
121
+ pb: 2,
122
+ } }, !tokenError && (React.createElement(LoadingButton, { "aria-label": "reset-password", color: "primary", disabled: !token, loading: loading, sx: { margin: "auto" }, type: "submit", variant: "contained" }, t("Reset Password"))))))));
123
+ };
124
+ export default ResetPasswordPage;
125
+ //# sourceMappingURL=ResetPasswordPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetPasswordPage.js","sourceRoot":"","sources":["../../../src/pages/ResetPasswordPage.tsx"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ export interface ProductList {
5
5
  name: string;
6
6
  description: string;
7
7
  is_active: boolean;
8
+ image_url: string | null;
8
9
  }
9
10
  export type ProductPropertyValue = string | number | boolean | string[] | number[] | boolean[] | null | undefined;
10
11
  export type ProductPropertiesData = Record<string, string | number | boolean | string[] | number[] | boolean[]>;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { PageProps } from "../../../types";
3
+ export interface InviteUserModalProps {
4
+ open: boolean;
5
+ setOpen: (open: boolean) => void;
6
+ refresh: PageProps["refresh"];
7
+ }
8
+ export declare const InviteUserModal: React.FC<InviteUserModalProps>;
9
+ export default InviteUserModal;
@@ -0,0 +1,4 @@
1
+ import { RouterExtension } from "../../router/Router";
2
+ import { NavigationOverrides } from "../../types";
3
+ export declare const router: RouterExtension;
4
+ export declare const navigation: NavigationOverrides;
@@ -0,0 +1,4 @@
1
+ import { PageComponent } from "../../../../types";
2
+ import { StaffDetail } from "../../types/staff";
3
+ declare const StaffDetailPage: PageComponent<StaffDetail>;
4
+ export default StaffDetailPage;
@@ -0,0 +1,4 @@
1
+ import { LimitOffset, PageComponent } from "../../../../types";
2
+ import { StaffList } from "../../types/staff";
3
+ declare const StaffListPage: PageComponent<LimitOffset<StaffList>>;
4
+ export default StaffListPage;
@@ -0,0 +1,24 @@
1
+ export interface StaffList {
2
+ id: number;
3
+ email: string;
4
+ is_active: boolean;
5
+ groups: string[];
6
+ }
7
+ export interface StaffDetail {
8
+ id: number;
9
+ email: string;
10
+ is_staff: boolean;
11
+ is_active: boolean;
12
+ is_superuser: boolean;
13
+ groups: string[];
14
+ permissions: string[];
15
+ last_login: string | null;
16
+ date_created: string;
17
+ }
18
+ export interface GroupOption {
19
+ id: number;
20
+ name: string;
21
+ }
22
+ export interface StaffOptions {
23
+ groups: GroupOption[];
24
+ }
@@ -70,6 +70,7 @@ export * as pos from "./extensions/pos";
70
70
  export * as pricing from "./extensions/pricing";
71
71
  export * as report from "./extensions/report";
72
72
  export * as subscription from "./extensions/subscription";
73
+ export * as user from "./extensions/user";
73
74
  export * as mui from "./mui";
74
75
  export type { RouterExtension } from "./router/Router";
75
76
  export * as themes from "./themes";
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { LogoType } from "../types";
3
+ export interface AcceptInvitePageProps {
4
+ title?: string;
5
+ logo?: LogoType;
6
+ logoHeight?: number | string;
7
+ }
8
+ export declare const AcceptInvitePage: React.FC<AcceptInvitePageProps>;
9
+ export default AcceptInvitePage;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { LogoType } from "../types";
3
+ export interface ResetPasswordPageProps {
4
+ title?: string;
5
+ logo?: LogoType;
6
+ logoHeight?: number | string;
7
+ }
8
+ export declare const ResetPasswordPage: React.FC<ResetPasswordPageProps>;
9
+ export default ResetPasswordPage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bananas-commerce-admin",
3
- "version": "0.21.2",
3
+ "version": "0.22.0",
4
4
  "description": "What's this, an admin for apes?",
5
5
  "keywords": [
6
6
  "admin",
package/src/Admin.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { BrowserRouter } from "react-router-dom";
2
+ import { BrowserRouter, useLocation } from "react-router-dom";
3
3
 
4
4
  import { Stack } from "@mui/material";
5
5
 
@@ -10,7 +10,9 @@ import DialogContextProvider, { BcomDialog } from "./contexts/DialogContext";
10
10
  import { useI18n } from "./contexts/I18nContext";
11
11
  import { RouterContextProvider } from "./contexts/RouterContext";
12
12
  import { useUser } from "./contexts/UserContext";
13
+ import AcceptInvitePage from "./pages/AcceptInvitePage";
13
14
  import LoginPage from "./pages/LoginPage";
15
+ import ResetPasswordPage from "./pages/ResetPasswordPage";
14
16
  import { Router, RouterProps } from "./router/Router";
15
17
  import { LogoType, NavigationOverrides } from "./types";
16
18
 
@@ -31,7 +33,7 @@ export interface AdminProps extends RouterProps {
31
33
  basepath?: string;
32
34
  }
33
35
 
34
- export const Admin: React.FC<AdminProps> = ({
36
+ const AdminContent: React.FC<AdminProps> = ({
35
37
  logo,
36
38
  logoSymbol,
37
39
  loginLogoHeight,
@@ -47,10 +49,56 @@ export const Admin: React.FC<AdminProps> = ({
47
49
  const api = useApi();
48
50
  const i18n = useI18n();
49
51
  const { hasTriedToFetchUser, user } = useUser();
52
+ const location = useLocation();
50
53
 
51
54
  const hasLoaded = Boolean(api != null && i18n != null);
52
55
  const hasUser = user != null;
53
56
 
57
+ // Check if we're on public routes
58
+ const isAcceptInviteRoute = location.pathname === "/user/accept-invite";
59
+ const isResetPasswordRoute = location.pathname === "/user/reset-password";
60
+
61
+ if (!hasLoaded || !hasTriedToFetchUser) {
62
+ return <LoadingScreen />;
63
+ }
64
+
65
+ // Show accept invite page if on that route, regardless of auth status
66
+ if (isAcceptInviteRoute) {
67
+ return <AcceptInvitePage logo={logo} logoHeight={loginLogoHeight} title={title} />;
68
+ }
69
+
70
+ // Show reset password page if on that route, regardless of auth status
71
+ if (isResetPasswordRoute) {
72
+ return <ResetPasswordPage logo={logo} logoHeight={loginLogoHeight} title={title} />;
73
+ }
74
+
75
+ // Show login page if not authenticated
76
+ if (!hasUser) {
77
+ return <LoginPage logo={logo} logoHeight={loginLogoHeight} title={title} />;
78
+ }
79
+
80
+ // Show authenticated content
81
+ return (
82
+ <RouterContextProvider basename={basename} stripPathPrefix={basepath}>
83
+ <DialogContextProvider>
84
+ <BcomDialog />
85
+ <Stack direction={{ xs: "column", sm: "row" }}>
86
+ <NavRail
87
+ logo={logoSymbol ?? logo}
88
+ navigation={navigation}
89
+ subtitle={subtitle}
90
+ title={title}
91
+ version={version}
92
+ />
93
+
94
+ <Router dashboard={dashboard} extensions={extensions} />
95
+ </Stack>
96
+ </DialogContextProvider>
97
+ </RouterContextProvider>
98
+ );
99
+ };
100
+
101
+ export const Admin: React.FC<AdminProps> = (props) => {
54
102
  return (
55
103
  <Stack
56
104
  sx={{
@@ -59,30 +107,9 @@ export const Admin: React.FC<AdminProps> = ({
59
107
  bgcolor: "background.default",
60
108
  }}
61
109
  >
62
- {!hasLoaded || !hasTriedToFetchUser ? (
63
- <LoadingScreen />
64
- ) : !hasUser ? (
65
- <LoginPage logo={logo} logoHeight={loginLogoHeight} title={title} />
66
- ) : (
67
- <BrowserRouter basename={basename}>
68
- <RouterContextProvider basename={basename} stripPathPrefix={basepath}>
69
- <DialogContextProvider>
70
- <BcomDialog />
71
- <Stack direction={{ xs: "column", sm: "row" }}>
72
- <NavRail
73
- logo={logoSymbol ?? logo}
74
- navigation={navigation}
75
- subtitle={subtitle}
76
- title={title}
77
- version={version}
78
- />
79
-
80
- <Router dashboard={dashboard} extensions={extensions} />
81
- </Stack>
82
- </DialogContextProvider>
83
- </RouterContextProvider>
84
- </BrowserRouter>
85
- )}
110
+ <BrowserRouter basename={props.basename}>
111
+ <AdminContent {...props} />
112
+ </BrowserRouter>
86
113
  </Stack>
87
114
  );
88
115
  };
@@ -2,6 +2,7 @@ import React from "react";
2
2
 
3
3
  import CancelIcon from "@mui/icons-material/Cancel";
4
4
  import CheckCircleIcon from "@mui/icons-material/CheckCircle";
5
+ import { Box } from "@mui/material";
5
6
 
6
7
  import { TableCell } from "../../../components/Table/TableCell";
7
8
  import { NavigatingTableRow } from "../../../components/Table/TableRow";
@@ -13,6 +14,37 @@ export interface ProductRowProps {
13
14
 
14
15
  export const ProductRow: React.FC<ProductRowProps> = ({ product }) => (
15
16
  <NavigatingTableRow route="pim.product:detail" routeParams={{ id: product.id }}>
17
+ <TableCell
18
+ sx={{ py: 0.75 }}
19
+ typographyProps={{
20
+ component: "div",
21
+ sx: { display: "flex", alignItems: "center", width: 48 },
22
+ }}
23
+ >
24
+ {product.image_url ? (
25
+ <Box
26
+ alt=""
27
+ component="img"
28
+ src={product.image_url}
29
+ sx={{
30
+ width: 48,
31
+ height: 48,
32
+ borderRadius: 1,
33
+ bgcolor: "background.default",
34
+ objectFit: "cover",
35
+ }}
36
+ />
37
+ ) : (
38
+ <Box
39
+ sx={{
40
+ width: 48,
41
+ height: 48,
42
+ borderRadius: 1,
43
+ bgcolor: "action.hover",
44
+ }}
45
+ />
46
+ )}
47
+ </TableCell>
16
48
  <TableCell>{product.name}</TableCell>
17
49
  <TableCell>{product.item_type}</TableCell>
18
50
  <TableCell>{product.number}</TableCell>
@@ -55,6 +55,7 @@ const ProductListPage: PageComponent<LimitOffset<ProductList>> = ({ data }) => {
55
55
  <TableCard>
56
56
  <Table pagination count={data?.count}>
57
57
  <TableHead>
58
+ <TableHeading sx={{ width: 80 }}>{t("Image")}</TableHeading>
58
59
  <TableHeading>{t("Name")}</TableHeading>
59
60
  <TableHeading>{t("Type")}</TableHeading>
60
61
  <TableHeading>{t("Product Number")}</TableHeading>
@@ -5,6 +5,7 @@ export interface ProductList {
5
5
  name: string;
6
6
  description: string;
7
7
  is_active: boolean;
8
+ image_url: string | null;
8
9
  }
9
10
 
10
11
  export type ProductPropertyValue =