muffin-admin 3.3.12 → 3.4.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/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.0",
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,51 @@
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
9
  import { 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()
25
+ const confirmation = useConfirmation()
12
26
  const { mutate, isPending } = useAction(action, {})
13
27
 
28
+ const [payloadActive, setPayloadActive] = useState(false)
29
+ const [payloadResolver, setPayloadResolver] = useState<(data: any) => void>()
30
+
14
31
  const PayloadBuilder = findBuilder(["action", "payload", id, resource])
32
+ const buildPayload = PayloadBuilder
33
+ ? new Promise((resolve) => setPayloadResolver(() => resolve))
34
+ : Promise.resolve()
35
+ const confirmMessage =
36
+ typeof confirm === "string" ? translate(confirm) : "Do you confirm this action?"
15
37
 
16
38
  return (
17
39
  <>
18
40
  <Button
19
41
  label={label}
20
42
  title={title}
21
- onClick={() => (PayloadBuilder ? setPayloadActive(true) : mutate({ record }))}
43
+ onClick={async () => {
44
+ if (PayloadBuilder) setPayloadActive(true)
45
+ const payload = await buildPayload
46
+ const process = confirm ? await confirmation.confirm({ message: confirmMessage }) : true
47
+ if (process) await mutate({ record, payload })
48
+ }}
22
49
  disabled={isPending}
23
50
  >
24
51
  {buildIcon(icon)}
@@ -26,7 +53,7 @@ export function ActionButton({ icon, label, title, action, id }: AdminAction) {
26
53
  {PayloadBuilder && (
27
54
  <PayloadBuilder
28
55
  active={payloadActive}
29
- onHandle={(payload) => mutate({ record, payload })}
56
+ onHandle={payloadResolver}
30
57
  onClose={() => setPayloadActive(false)}
31
58
  />
32
59
  )}
@@ -34,20 +61,33 @@ export function ActionButton({ icon, label, title, action, id }: AdminAction) {
34
61
  )
35
62
  }
36
63
 
37
- export function BulkActionButton({ label, icon, title, action, id }: AdminAction) {
64
+ export function BulkActionButton({ label, icon, title, action, id, confirm }: AdminAction) {
65
+ const translate = useTranslate()
38
66
  const { selectedIds } = useListContext()
39
67
  const { mutate, isPending } = useAction(action)
68
+ const confirmation = useConfirmation()
40
69
  const resource = useResourceContext()
41
70
  const [payloadActive, setPayloadActive] = useState(false)
71
+ const [payloadResolver, setPayloadResolver] = useState<(data: any) => void>()
42
72
 
43
73
  const PayloadBuilder = findBuilder(["action", "payload", id, resource])
74
+ const buildPayload = PayloadBuilder
75
+ ? new Promise((resolve) => setPayloadResolver(() => resolve))
76
+ : Promise.resolve()
77
+ const confirmMessage =
78
+ typeof confirm === "string" ? translate(confirm) : "Do you confirm this action?"
44
79
 
45
80
  return (
46
81
  <>
47
82
  <Button
48
83
  label={label}
49
84
  title={title}
50
- onClick={() => (PayloadBuilder ? setPayloadActive(true) : mutate({ ids: selectedIds }))}
85
+ onClick={async () => {
86
+ if (PayloadBuilder) setPayloadActive(true)
87
+ const payload = await buildPayload
88
+ const process = confirm ? await confirmation.confirm({ message: confirmMessage }) : true
89
+ if (process) await mutate({ ids: selectedIds, payload })
90
+ }}
51
91
  disabled={isPending}
52
92
  >
53
93
  {buildIcon(icon)}
@@ -55,7 +95,7 @@ export function BulkActionButton({ label, icon, title, action, id }: AdminAction
55
95
  {PayloadBuilder && (
56
96
  <PayloadBuilder
57
97
  active={payloadActive}
58
- onHandle={(payload) => mutate({ ids: selectedIds, payload })}
98
+ onHandle={payloadResolver}
59
99
  onClose={() => setPayloadActive(false)}
60
100
  />
61
101
  )}
@@ -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 }]