muffin-admin 3.3.12 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -8,6 +8,7 @@ export type AdminAction = {
8
8
  title: string;
9
9
  icon: string;
10
10
  action: string;
11
+ confirm?: string | false;
11
12
  };
12
13
  export type AdminField = [string, {
13
14
  source: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muffin-admin",
3
- "version": "3.3.12",
3
+ "version": "3.4.1",
4
4
  "description": "NPM package for https://github.com/klen/muffin-admin",
5
5
  "files": [
6
6
  "src",
@@ -3,17 +3,19 @@ import SvgIcon from "@mui/material/SvgIcon"
3
3
  import Tooltip from "@mui/material/Tooltip"
4
4
  import Typography from "@mui/material/Typography"
5
5
  import {
6
- Admin,
6
+ AdminContext,
7
7
  AdminProps,
8
+ AdminUI,
8
9
  AppBar,
9
10
  AppBarProps,
10
- AuthProvider,
11
- DataProvider,
11
+ defaultI18nProvider,
12
12
  Layout,
13
13
  LayoutProps,
14
+ localStorageStore,
14
15
  Login,
15
16
  } from "react-admin"
16
17
 
18
+ import { ConfirmationProvider } from "./common"
17
19
  import { useMuffinAdminOpts } from "./hooks"
18
20
  import { buildAdmin, findBuilder, findIcon, setupAdmin } from "./utils"
19
21
 
@@ -22,22 +24,64 @@ export function MuffinAdmin(props: AdminProps) {
22
24
  const { resources = [], auth, adminProps, apiUrl } = opts
23
25
 
24
26
  document.title = adminProps?.title || "Muffin Admin"
25
- const authProvider = findBuilder(["authprovider"])(auth) as AuthProvider
26
- const dataProvider = findBuilder(["dataprovider"])(apiUrl) as DataProvider
27
+
28
+ const {
29
+ basename,
30
+ authProvider = findBuilder(["authprovider"])(auth),
31
+ dataProvider = findBuilder(["dataprovider"])(apiUrl),
32
+ catchAll,
33
+ dashboard = findBuilder(["dashboard"]),
34
+ disableTelemetry,
35
+ error,
36
+ i18nProvider = defaultI18nProvider,
37
+ layout = findBuilder(["layout"]),
38
+ loading,
39
+ loginPage = findBuilder(["loginpage"]),
40
+ authCallbackPage,
41
+ notification,
42
+ queryClient,
43
+ requireAuth = auth.required,
44
+ store = localStorageStore(),
45
+ ready,
46
+ theme,
47
+ lightTheme,
48
+ darkTheme,
49
+ defaultTheme,
50
+ title = "React Admin",
51
+ } = props
27
52
 
28
53
  return (
29
- <Admin
30
- requireAuth={auth.required}
54
+ <AdminContext
31
55
  authProvider={authProvider}
56
+ basename={basename}
32
57
  dataProvider={dataProvider}
33
- layout={findBuilder(["layout"])}
34
- loginPage={findBuilder(["loginpage"])}
35
- dashboard={findBuilder(["dashboard"])}
36
- darkTheme={{ palette: { mode: "dark" } }}
37
- {...props}
58
+ i18nProvider={i18nProvider}
59
+ store={store}
60
+ queryClient={queryClient}
61
+ theme={theme}
62
+ lightTheme={lightTheme}
63
+ darkTheme={darkTheme}
64
+ defaultTheme={defaultTheme}
38
65
  >
39
- {resources?.map(({ name }) => buildAdmin(["resource", name], { name }))}
40
- </Admin>
66
+ <ConfirmationProvider>
67
+ <AdminUI
68
+ layout={layout}
69
+ dashboard={dashboard}
70
+ disableTelemetry={disableTelemetry}
71
+ catchAll={catchAll}
72
+ error={error}
73
+ title={title}
74
+ loading={loading}
75
+ loginPage={loginPage}
76
+ authCallbackPage={authCallbackPage}
77
+ notification={notification}
78
+ requireAuth={requireAuth}
79
+ ready={ready}
80
+ >
81
+ {resources?.map(({ name }) => buildAdmin(["resource", name], { name }))}
82
+ </AdminUI>
83
+ </ConfirmationProvider>
84
+ </AdminContext>
41
85
  )
42
86
  }
43
87
 
@@ -1,24 +1,54 @@
1
- import { Button, useListContext, useRecordContext, useResourceContext } from "react-admin"
1
+ import {
2
+ Button,
3
+ useListContext,
4
+ useRecordContext,
5
+ useResourceContext,
6
+ useTranslate,
7
+ } from "react-admin"
2
8
 
3
- import { useState } from "react"
9
+ import { useMemo, useState } from "react"
10
+ import { useConfirmation } from "../common"
4
11
  import { useAction } from "../hooks/useAction"
5
12
  import { AdminAction } from "../types"
6
13
  import { buildIcon, findBuilder } from "../utils"
7
14
 
8
- export function ActionButton({ icon, label, title, action, id }: AdminAction) {
9
- const [payloadActive, setPayloadActive] = useState(false)
15
+ export type ActionPayloadProps = {
16
+ active: boolean
17
+ onClose: () => void
18
+ onHandle: (payload: any) => void
19
+ }
20
+
21
+ export function ActionButton({ icon, label, title, action, id, confirm }: AdminAction) {
22
+ const translate = useTranslate()
10
23
  const record = useRecordContext()
11
24
  const resource = useResourceContext()
12
- const { mutate, isPending } = useAction(action, {})
25
+ const confirmation = useConfirmation()
26
+ const { mutate, isPending } = useAction(action)
27
+
28
+ const [payloadActive, setPayloadActive] = useState(false)
29
+ const [payloadResolver, setPayloadResolver] = useState<(data: any) => void>()
13
30
 
14
31
  const PayloadBuilder = findBuilder(["action", "payload", id, resource])
32
+ const buildPayload = useMemo(() => {
33
+ if (PayloadBuilder) {
34
+ return new Promise((resolve) => setPayloadResolver(() => resolve))
35
+ }
36
+ return Promise.resolve()
37
+ }, [PayloadBuilder])
38
+ const confirmMessage =
39
+ typeof confirm === "string" ? translate(confirm) : "Do you confirm this action?"
15
40
 
16
41
  return (
17
42
  <>
18
43
  <Button
19
44
  label={label}
20
45
  title={title}
21
- onClick={() => (PayloadBuilder ? setPayloadActive(true) : mutate({ record }))}
46
+ onClick={async () => {
47
+ if (PayloadBuilder) setPayloadActive(true)
48
+ const payload = await buildPayload
49
+ const process = confirm ? await confirmation.confirm({ message: confirmMessage }) : true
50
+ if (process) await mutate({ record, payload })
51
+ }}
22
52
  disabled={isPending}
23
53
  >
24
54
  {buildIcon(icon)}
@@ -26,7 +56,7 @@ export function ActionButton({ icon, label, title, action, id }: AdminAction) {
26
56
  {PayloadBuilder && (
27
57
  <PayloadBuilder
28
58
  active={payloadActive}
29
- onHandle={(payload) => mutate({ record, payload })}
59
+ onHandle={payloadResolver}
30
60
  onClose={() => setPayloadActive(false)}
31
61
  />
32
62
  )}
@@ -34,20 +64,36 @@ export function ActionButton({ icon, label, title, action, id }: AdminAction) {
34
64
  )
35
65
  }
36
66
 
37
- export function BulkActionButton({ label, icon, title, action, id }: AdminAction) {
67
+ export function BulkActionButton({ label, icon, title, action, id, confirm }: AdminAction) {
68
+ const translate = useTranslate()
38
69
  const { selectedIds } = useListContext()
39
70
  const { mutate, isPending } = useAction(action)
71
+ const confirmation = useConfirmation()
40
72
  const resource = useResourceContext()
41
73
  const [payloadActive, setPayloadActive] = useState(false)
74
+ const [payloadResolver, setPayloadResolver] = useState<(data: any) => void>()
42
75
 
43
76
  const PayloadBuilder = findBuilder(["action", "payload", id, resource])
77
+ const buildPayload = useMemo(() => {
78
+ if (PayloadBuilder) {
79
+ return new Promise((resolve) => setPayloadResolver(() => resolve))
80
+ }
81
+ return Promise.resolve()
82
+ }, [PayloadBuilder])
83
+ const confirmMessage =
84
+ typeof confirm === "string" ? translate(confirm) : "Do you confirm this action?"
44
85
 
45
86
  return (
46
87
  <>
47
88
  <Button
48
89
  label={label}
49
90
  title={title}
50
- onClick={() => (PayloadBuilder ? setPayloadActive(true) : mutate({ ids: selectedIds }))}
91
+ onClick={async () => {
92
+ if (PayloadBuilder) setPayloadActive(true)
93
+ const payload = await buildPayload
94
+ const process = confirm ? await confirmation.confirm({ message: confirmMessage }) : true
95
+ if (process) await mutate({ ids: selectedIds, payload })
96
+ }}
51
97
  disabled={isPending}
52
98
  >
53
99
  {buildIcon(icon)}
@@ -55,7 +101,7 @@ export function BulkActionButton({ label, icon, title, action, id }: AdminAction
55
101
  {PayloadBuilder && (
56
102
  <PayloadBuilder
57
103
  active={payloadActive}
58
- onHandle={(payload) => mutate({ ids: selectedIds, payload })}
104
+ onHandle={payloadResolver}
59
105
  onClose={() => setPayloadActive(false)}
60
106
  />
61
107
  )}
@@ -0,0 +1,26 @@
1
+ import { Dialog, DialogActions, DialogContent, DialogProps, DialogTitle } from "@mui/material"
2
+ import { PropsWithChildren } from "react"
3
+
4
+ interface IProps {
5
+ open: boolean
6
+ onClose: () => void
7
+ title?: string
8
+ actions?: React.ReactNode
9
+ }
10
+
11
+ export function AdminModal({
12
+ open,
13
+ onClose,
14
+ children,
15
+ title,
16
+ actions,
17
+ ...props
18
+ }: PropsWithChildren<IProps & DialogProps>) {
19
+ return (
20
+ <Dialog open={open} onClose={onClose} fullWidth maxWidth="md" {...props}>
21
+ {title && <DialogTitle>{title}</DialogTitle>}
22
+ <DialogContent>{children}</DialogContent>
23
+ {actions && <DialogActions>{actions}</DialogActions>}
24
+ </Dialog>
25
+ )
26
+ }
@@ -0,0 +1,75 @@
1
+ import { Button, Typography } from "@mui/material"
2
+ import { createContext, PropsWithChildren, useContext, useState } from "react"
3
+ import { useTranslate } from "react-admin"
4
+ import { AdminModal } from "./AdminModal"
5
+
6
+ type TConfirmProps = {
7
+ title?: string
8
+ message: string
9
+ actions?: React.ReactNode
10
+ }
11
+
12
+ type TConfirmContext = {
13
+ confirm: (props: TConfirmProps) => Promise<boolean>
14
+ }
15
+
16
+ export const ConfirmContext = createContext<TConfirmContext>({} as TConfirmContext)
17
+
18
+ export function ConfirmationProvider({ children }: PropsWithChildren) {
19
+ const translate = useTranslate()
20
+ const [open, setOpen] = useState(false)
21
+ const [title, setTitle] = useState(translate("ra.message.are_you_sure"))
22
+ const [message, setMessage] = useState(null)
23
+ const [resolver, setResolve] = useState<null | ((value: boolean) => void)>(null)
24
+
25
+ function handleConfirm({ title, message }: TConfirmProps) {
26
+ setOpen(true)
27
+ if (title) setTitle(title)
28
+ if (message) setMessage(message)
29
+ return new Promise<boolean>((resolve) => {
30
+ setResolve(() => resolve)
31
+ })
32
+ }
33
+ const confirmContext = {
34
+ confirm: handleConfirm,
35
+ }
36
+ return (
37
+ <ConfirmContext.Provider value={confirmContext}>
38
+ {children}
39
+ <AdminModal
40
+ open={open}
41
+ onClose={() => setOpen(false)}
42
+ title={title}
43
+ maxWidth="xs"
44
+ actions={
45
+ <>
46
+ <Button
47
+ variant="contained"
48
+ onClick={() => {
49
+ setOpen(false)
50
+ resolver(true)
51
+ }}
52
+ >
53
+ {translate("ra.action.confirm")}
54
+ </Button>
55
+ <Button
56
+ variant="outlined"
57
+ onClick={() => {
58
+ setOpen(false)
59
+ resolver(false)
60
+ }}
61
+ >
62
+ {translate("ra.action.cancel")}
63
+ </Button>
64
+ </>
65
+ }
66
+ >
67
+ <Typography>{message}</Typography>
68
+ </AdminModal>
69
+ </ConfirmContext.Provider>
70
+ )
71
+ }
72
+
73
+ export const useConfirmation = () => {
74
+ return useContext(ConfirmContext)
75
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./AdminModal"
2
+ export * from "./ConfirmationProvider"
File without changes
File without changes
package/src/index.tsx CHANGED
@@ -15,6 +15,7 @@ export * from "./actions"
15
15
  export * from "./authprovider"
16
16
  export * from "./buildRA"
17
17
  export * from "./buttons"
18
+ export * from "./common"
18
19
  export * from "./context"
19
20
  export * from "./dataprovider"
20
21
  export * from "./fields"
package/src/types.ts CHANGED
@@ -9,6 +9,7 @@ export type AdminAction = {
9
9
  title: string
10
10
  icon: string
11
11
  action: string
12
+ confirm?: string | false
12
13
  }
13
14
 
14
15
  export type AdminField = [string, { source: string }]