@kaspernj/api-maker 1.0.304 → 1.0.306

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/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  ]
17
17
  },
18
18
  "name": "@kaspernj/api-maker",
19
- "version": "1.0.304",
19
+ "version": "1.0.306",
20
20
  "type": "module",
21
21
  "description": "",
22
22
  "main": "index.js",
@@ -121,7 +121,7 @@ const inputWrapper = (WrapperComponentClass, wrapperOptions = {}) => {
121
121
  return this.formatValue(this.props.defaultValue)
122
122
  } else if (this.props.model) {
123
123
  if (!this.props.model[this.props.attribute]) {
124
- throw new Error(`No such attribute: ${digg(this.props.model.modelClassData(), "name")}#${this.props.attribute}`)
124
+ throw new Error(`No such attribute defined on resource: ${digg(this.props.model.modelClassData(), "name")}#${this.props.attribute}`)
125
125
  }
126
126
 
127
127
  return this.formatValue(this.props.model[this.props.attribute]())
@@ -0,0 +1,100 @@
1
+ import ConfigReader from "./config-reader"
2
+ import {digg} from "diggerize"
3
+ import * as inflection from "inflection"
4
+ import Input from "../bootstrap/input"
5
+ import Locales from "shared/locales"
6
+ import {useCallback, memo} from "react"
7
+ import useCurrentUser from "../use-current-user"
8
+ import useModel from "../use-model"
9
+ import useQueryParams from "on-location-changed/src/use-query-params"
10
+
11
+ const EditPage = ({modelClass}) => {
12
+ const availableLocales = Locales.availableLocales()
13
+ const currentUser = useCurrentUser()
14
+ const queryParams = useQueryParams()
15
+ const configReader = ConfigReader.forModel(modelClass)
16
+ const camelizedLower = digg(modelClass.modelClassData(), "camelizedLower")
17
+ const modelClassName = modelClass.modelClassData().name
18
+ const modelIdVarName = `${inflection.camelize(modelClass.modelClassData().name, true)}Id`
19
+ const modelVarName = inflection.camelize(modelClass.modelClassData().name, true)
20
+ const extraContent = configReader.modelConfig?.edit?.extraContentconst
21
+ const attributes = configReader.modelConfig?.edit?.attributes
22
+ const selectedModelAttributes = ["id"]
23
+ const selectedAttributes = {}
24
+
25
+ selectedAttributes[modelClassName] = selectedModelAttributes
26
+
27
+ for (const attribute of attributes) {
28
+ if (attribute.translated) {
29
+ for (const locale of availableLocales) {
30
+ selectedModelAttributes.push(`${attribute.attribute}${inflection.camelize(locale)}`)
31
+ }
32
+ } else {
33
+ selectedModelAttributes.push(attribute.attribute)
34
+ }
35
+ }
36
+
37
+ const useModelResult = useModel(modelClass, {
38
+ cacheArgs: [currentUser?.id()],
39
+ loadByQueryParam: (props) => props.queryParams.model_id,
40
+ newIfNoId: true,
41
+ select: selectedAttributes
42
+ })
43
+
44
+ const model = digg(useModelResult, modelVarName)
45
+ const modelId = queryParams.model_id
46
+ const modelArgs = {}
47
+
48
+ modelArgs[modelIdVarName] = modelId
49
+
50
+ const onSubmit = useCallback(async (e) => {
51
+ e.preventDefault()
52
+
53
+ const form = digg(e, "target")
54
+ const formData = new FormData(form)
55
+
56
+ try {
57
+ await model.saveRaw(formData)
58
+ Params.changeParams({mode: undefined, model_id: model.id()})
59
+ } catch (error) {
60
+ FlashMessage.errorResponse(error)
61
+ }
62
+ }, [model])
63
+
64
+ return (
65
+ <div className="super-admin--edit-page">
66
+ <form onSubmit={onSubmit}>
67
+ {model && attributes?.map((attribute) =>
68
+ <div key={attribute.attribute}>
69
+ {attribute.translated && availableLocales.map((locale) =>
70
+ <div key={locale}>
71
+ <Input
72
+ attribute={`${attribute.attribute}${inflection.camelize(locale)}`}
73
+ id={`${camelizedLower}_${inflection.underscore(attribute.attribute)}_${locale}`}
74
+ label={`${modelClass.humanAttributeName(attribute.attribute)} (${locale})`}
75
+ model={model}
76
+ name={`${camelizedLower}[${inflection.underscore(attribute.attribute)}_${locale}]`}
77
+ />
78
+ </div>
79
+ )}
80
+ {!attribute.translated &&
81
+ <Input
82
+ attribute={attribute.attribute}
83
+ id={`${camelizedLower}_${inflection.underscore(attribute.attribute)}`}
84
+ label={modelClass.humanAttributeName(attribute.attribute)}
85
+ model={model}
86
+ name={`${camelizedLower}[${inflection.underscore(attribute.attribute)}]`}
87
+ />
88
+ }
89
+ </div>
90
+ )}
91
+ {extraContent && extraContent(modelArgs)}
92
+ <button style={{marginTop: "10px"}} type="submit">
93
+ Submit
94
+ </button>
95
+ </form>
96
+ </div>
97
+ )
98
+ }
99
+
100
+ export default memo(EditPage)
@@ -1,24 +1,17 @@
1
- import {digs} from "diggerize"
2
1
  import ModelClassTable from "./model-class-table"
3
2
  import PropTypes from "prop-types"
4
- import React from "react"
3
+ import {memo} from "react"
5
4
 
6
- export default class ApiMakerSuperAdminIndexPage extends React.PureComponent {
7
- static propTypes = {
8
- currentUser: PropTypes.object,
9
- modelClass: PropTypes.func.isRequired,
10
- queryParams: PropTypes.object.isRequired
11
- }
12
-
13
- render() {
14
- const {currentUser, modelClass, queryParams} = digs(this.props, "currentUser", "modelClass", "queryParams")
5
+ const ApiMakerSuperAdminIndexPage = ({modelClass}) => {
6
+ return (
7
+ <ModelClassTable
8
+ modelClass={modelClass}
9
+ />
10
+ )
11
+ }
15
12
 
16
- return (
17
- <ModelClassTable
18
- currentUser={currentUser}
19
- modelClass={modelClass}
20
- queryParams={queryParams}
21
- />
22
- )
23
- }
13
+ ApiMakerSuperAdminIndexPage.propTypes = {
14
+ modelClass: PropTypes.func.isRequired
24
15
  }
16
+
17
+ export default memo(ApiMakerSuperAdminIndexPage)
@@ -1,70 +1,80 @@
1
- import {digg, digs} from "diggerize"
1
+ import {digg} from "diggerize"
2
+ import EditPage from "./edit-page"
2
3
  import IndexPage from "./index-page"
3
4
  import Layout from "./layout"
5
+ import Link from "../link"
6
+ import {memo, useMemo} from "react"
4
7
  import * as modelsModule from "@kaspernj/api-maker/src/models.mjs.erb"
5
- import PropTypes from "prop-types"
6
8
  import ShowPage from "./show-page"
7
9
  import ShowReflectionPage from "./show-reflection-page"
8
- import withQueryParams from "on-location-changed/src/with-query-params"
10
+ import useQueryParams from "on-location-changed/src/use-query-params"
9
11
 
10
- class ApiMakerSuperAdmin extends React.PureComponent {
11
- static propTypes = {
12
- currentUser: PropTypes.object,
13
- queryParams: PropTypes.object.isRequired
14
- }
15
-
16
- render() {
17
- const {currentUser} = this.props
18
- const {queryParams} = digs(this.props, "queryParams")
19
- const pageToShow = this.pageToShow()
20
- let modelClass
12
+ const ApiMakerSuperAdmin = () => {
13
+ const queryParams = useQueryParams()
14
+ let modelClass, pageToShow
21
15
 
22
- if (queryParams.model) modelClass = modelsModule[queryParams.model]
16
+ if (queryParams.model) modelClass = modelsModule[queryParams.model]
23
17
 
24
- return (
25
- <Layout active={queryParams.model} headerTitle={modelClass?.modelName()?.human({count: 2})}>
26
- {pageToShow == "index" &&
27
- <IndexPage
28
- currentUser={currentUser}
29
- key={`index-page-${digg(modelClass.modelClassData(), "name")}`}
30
- modelClass={modelClass}
31
- queryParams={queryParams}
32
- />
33
- }
34
- {pageToShow == "show" &&
35
- <ShowPage
36
- key={`show-page-${digg(modelClass.modelClassData(), "name")}-${queryParams.modelId}`}
37
- modelClass={modelClass}
38
- modelId={queryParams.modelId}
39
- queryParams={queryParams}
40
- />
41
- }
42
- {pageToShow == "show_reflection" &&
43
- <ShowReflectionPage
44
- currentUser={currentUser}
45
- key={`show-reflection-page-${digg(modelClass.modelClassData(), "name")}-${queryParams.modelId}`}
46
- modelClass={modelClass}
47
- modelId={queryParams.modelId}
48
- queryParams={queryParams}
49
- />
50
- }
51
- </Layout>
52
- )
18
+ if (queryParams.model && queryParams.model_id && queryParams.model_reflection) {
19
+ pageToShow = "show_reflection"
20
+ } else if (queryParams.model && queryParams.model_id && queryParams.mode == "edit") {
21
+ pageToShow = "edit"
22
+ } else if (queryParams.model && queryParams.model_id) {
23
+ pageToShow = "show"
24
+ } else if (queryParams.model && queryParams.mode == "new") {
25
+ pageToShow = "edit"
26
+ } else if (queryParams.model) {
27
+ pageToShow = "index"
28
+ } else {
29
+ pageToShow = "welcome"
53
30
  }
54
31
 
55
- pageToShow() {
56
- const {queryParams} = digs(this.props, "queryParams")
32
+ const actions = useMemo(
33
+ () => <>
34
+ {modelClass && pageToShow == "index" &&
35
+ <Link className="create-new-model-link" to={Params.withParams({model: modelClass.modelClassData().name, mode: "new"})}>
36
+ Create new
37
+ </Link>
38
+ }
39
+ {modelClass && pageToShow == "show" &&
40
+ <Link to={Params.withParams({model: modelClass.modelClassData().name, model_id: queryParams.model_id, mode: "edit"})}>
41
+ Edit
42
+ </Link>
43
+ }
44
+ </>,
45
+ [modelClass, pageToShow]
46
+ )
57
47
 
58
- if (queryParams.model && queryParams.model_id && queryParams.model_reflection) {
59
- return "show_reflection"
60
- } else if (queryParams.model && queryParams.model_id) {
61
- return "show"
62
- } else if (queryParams.model) {
63
- return "index"
64
- }
65
-
66
- return "welcome"
67
- }
48
+ return (
49
+ <Layout actions={actions} active={queryParams.model} headerTitle={modelClass?.modelName()?.human({count: 2})}>
50
+ {pageToShow == "index" &&
51
+ <IndexPage
52
+ key={`index-page-${digg(modelClass.modelClassData(), "name")}`}
53
+ modelClass={modelClass}
54
+ />
55
+ }
56
+ {pageToShow == "show" &&
57
+ <ShowPage
58
+ key={`show-page-${digg(modelClass.modelClassData(), "name")}-${queryParams.modelId}`}
59
+ modelClass={modelClass}
60
+ modelId={queryParams.modelId}
61
+ />
62
+ }
63
+ {pageToShow == "show_reflection" &&
64
+ <ShowReflectionPage
65
+ key={`show-reflection-page-${digg(modelClass.modelClassData(), "name")}-${queryParams.modelId}`}
66
+ modelClass={modelClass}
67
+ modelId={queryParams.modelId}
68
+ />
69
+ }
70
+ {pageToShow == "edit" &&
71
+ <EditPage
72
+ key={`edit-page-${digg(modelClass.modelClassData(), "name")}-${queryParams.modelId}`}
73
+ modelClass={modelClass}
74
+ />
75
+ }
76
+ </Layout>
77
+ )
68
78
  }
69
79
 
70
- export default withQueryParams(ApiMakerSuperAdmin)
80
+ export default memo(ApiMakerSuperAdmin)
@@ -1,65 +1,53 @@
1
1
  import "./style"
2
- import {digg, digs} from "diggerize"
3
2
  import EventListener from "../../../event-listener"
4
3
  import PropTypes from "prop-types"
5
4
  import PropTypesExact from "prop-types-exact"
6
- import React from "react"
5
+ import {memo, useCallback, useRef, useState} from "react"
7
6
 
8
- export default class ApiMakerSuperAdminLayoutHeader extends React.PureComponent {
9
- static propTypes = PropTypesExact({
10
- actions: PropTypes.node,
11
- onTriggerMenu: PropTypes.func.isRequired,
12
- title: PropTypes.string
13
- })
14
-
15
- headerActionsRef = React.createRef()
16
- state = {headerActionsActive: false}
7
+ const ApiMakerSuperAdminLayoutHeader = ({actions, onTriggerMenu, title}) => {
8
+ const headerActionsRef = useRef()
9
+ const [headerActionsActive, setHeaderActionsActive] = useState(false)
10
+ const onGearsClicked = useCallback((e) => {
11
+ e.preventDefault()
12
+ setHeaderActionsActive(!headerActionsActive)
13
+ }, [headerActionsActive])
17
14
 
18
- render() {
19
- const {headerActionsRef} = digs(this, "headerActionsRef")
20
- const {onGearsClicked} = digs(this, "onGearsClicked")
21
- const {actions, onTriggerMenu, title} = this.props
22
- const {headerActionsActive} = digs(this.state, "headerActionsActive")
15
+ const onWindowMouseUp = useCallback((e) => {
16
+ // Close the header actions menu if clicked happened outside
17
+ if (headerActionsActive && headerActionsRef.current && !headerActionsRef.current.contains(e.target)) setHeaderActionsActive(false)
18
+ }, [headerActionsActive, headerActionsRef])
23
19
 
24
- return (
25
- <div className="components--admin--layout--header">
26
- <EventListener event="mouseup" onCalled={digg(this, "onWindowMouseUp")} target={window} />
27
- <div className="header-title-container">
28
- {title}
20
+ return (
21
+ <div className="components--admin--layout--header">
22
+ <EventListener event="mouseup" onCalled={onWindowMouseUp} target={window} />
23
+ <div className="header-title-container">
24
+ {title}
25
+ </div>
26
+ {actions &&
27
+ <div className="header-actions-container" data-active={headerActionsActive}>
28
+ <div className="header-actions" ref={headerActionsRef}>
29
+ {actions}
30
+ </div>
29
31
  </div>
32
+ }
33
+ <div className="burger-menu-container">
30
34
  {actions &&
31
- <div className="header-actions-container" data-active={headerActionsActive}>
32
- <div className="header-actions" ref={headerActionsRef}>
33
- {actions}
34
- </div>
35
- </div>
36
- }
37
- <div className="burger-menu-container">
38
- {actions &&
39
- <a className="actions-link" href="#" onClick={onGearsClicked}>
40
- <i className="fa fa-gear" />
41
- </a>
42
- }
43
- <a className="burger-menu-link" href="#" onClick={onTriggerMenu}>
44
- <i className="fa fa-bars" />
35
+ <a className="actions-link" href="#" onClick={onGearsClicked}>
36
+ <i className="fa fa-gear" />
45
37
  </a>
46
- </div>
38
+ }
39
+ <a className="burger-menu-link" href="#" onClick={onTriggerMenu}>
40
+ <i className="fa fa-bars" />
41
+ </a>
47
42
  </div>
48
- )
49
- }
50
-
51
- onGearsClicked = (e) => {
52
- e.preventDefault()
53
- this.setState({
54
- headerActionsActive: !this.state.headerActionsActive
55
- })
56
- }
43
+ </div>
44
+ )
45
+ }
57
46
 
58
- onWindowMouseUp = (e) => {
59
- const {headerActionsRef} = digs(this, "headerActionsRef")
60
- const {headerActionsActive} = digs(this.state, "headerActionsActive")
47
+ ApiMakerSuperAdminLayoutHeader.propTypes = PropTypesExact({
48
+ actions: PropTypes.node,
49
+ onTriggerMenu: PropTypes.func.isRequired,
50
+ title: PropTypes.string
51
+ })
61
52
 
62
- // Close the header actions menu if clicked happened outside
63
- if (headerActionsActive && headerActionsRef.current && !headerActionsRef.current.contains(e.target)) this.state.set({headerActionsActive: false})
64
- }
65
- }
53
+ export default memo(ApiMakerSuperAdminLayoutHeader)
@@ -1,120 +1,95 @@
1
1
  import "./style"
2
2
  import classNames from "classnames"
3
3
  import CommandsPool from "../../commands-pool"
4
- import {digg, digs} from "diggerize"
5
4
  import Header from "./header"
6
- import Link from "../../link"
7
5
  import Menu from "./menu"
8
6
  import PropTypes from "prop-types"
9
7
  import PropTypesExact from "prop-types-exact"
10
- import withCurrentUser from "../../with-current-user"
8
+ import {memo, useCallback, useEffect, useState} from "react"
9
+ import useCurrentUser from "../../use-current-user"
11
10
 
12
11
  const NoAccess = React.lazy(() => import("./no-access"))
13
12
 
14
- class ApiMakerSuperAdminLayout extends React.PureComponent {
15
- static propTypes = PropTypesExact({
16
- actions: PropTypes.node,
17
- active: PropTypes.string,
18
- children: PropTypes.node,
19
- className: PropTypes.string,
20
- currentCustomer: PropTypes.instanceOf(User),
21
- currentCustomerId: PropTypes.string,
22
- currentUser: PropTypes.instanceOf(User),
23
- headTitle: PropTypes.string,
24
- headerTitle: PropTypes.string
25
- })
26
-
27
- componentDidMount() {
13
+ const ApiMakerSuperAdminLayout = ({
14
+ actions,
15
+ active,
16
+ children,
17
+ className,
18
+ currentCustomer,
19
+ currentCustomerId,
20
+ headerTitle,
21
+ menu,
22
+ ...restProps
23
+ }) => {
24
+ const currentUser = useCurrentUser()
25
+
26
+ useEffect(() => {
28
27
  CommandsPool.current().globalRequestData.layout = "admin"
29
28
  CommandsPool.current().globalRequestData.locale = I18n.locale
29
+ }, [])
30
30
 
31
- this.setDocumentTitle()
32
- }
33
-
34
- componentDidUpdate() {
35
- this.setDocumentTitle()
36
- }
37
-
38
- setDocumentTitle() {
39
- const headTitle = this.props.headTitle || this.props.headerTitle
40
-
41
- if (headTitle) {
42
- document.title = headTitle
43
- } else {
44
- document.title = "Wooftech"
45
- }
46
- }
47
-
48
- state = {
49
- menuTriggered: false
50
- }
51
-
52
- render() {
53
- const {
54
- actions,
55
- active,
56
- children,
57
- className,
58
- currentCustomer,
59
- currentCustomerId,
60
- currentUser,
61
- headerTitle,
62
- menu,
63
- ...restProps
64
- } = this.props
65
- const {menuTriggered} = digs(this.state, "menuTriggered")
66
- const noAccess = this.noAccess()
31
+ const headTitle = headTitle || headerTitle
67
32
 
68
- return (
69
- <div className={classNames("components--admin--layout", className)} data-menu-triggered={menuTriggered} {...restProps}>
70
- <Menu
71
- active={active}
72
- noAccess={noAccess}
73
- onRequestMenuClose={digg(this, "onRequestMenuClose")}
74
- triggered={menuTriggered}
75
- />
76
- <Header actions={actions} onTriggerMenu={digg(this, "onTriggerMenu")} title={headerTitle} />
77
- <div className="app-layout-content-container">
78
- {noAccess &&
79
- <>
80
- <NoAccess />
81
- {currentUser &&
82
- <>
83
- <div className="mb-4">
84
- {I18n.t("js.api_maker.super_admin.layout.try_signing_out_and_in_with_a_different_user", {defaultValue: "Try signing in with a different user."})}
85
- </div>
86
- </>
87
- }
88
- {!currentUser &&
89
- <>
90
- <div className="mb-4">
91
- {I18n.t("js.api_maker.super_admin.layout.try_signing_in", {defaultValue: "Try signing in."})}
92
- </div>
93
- </>
94
- }
95
- </>
96
- }
97
- {!noAccess && children}
98
- </div>
99
- </div>
100
- )
33
+ if (headTitle) {
34
+ document.title = headTitle
35
+ } else {
36
+ document.title = "Wooftech"
101
37
  }
102
38
 
103
- onRequestMenuClose = () => this.setState({menuTriggered: false})
104
-
105
- onTriggerMenu = (e) => {
39
+ const [menuTriggered, setMenuTriggered] = useState(false)
40
+ const noAccess = !currentUser
41
+ const onRequestMenuClose = useCallback(() => setMenuTriggered(false), [])
42
+ const onTriggerMenu = useCallback((e) => {
106
43
  e.preventDefault()
107
44
 
108
- this.setState({menuTriggered: !this.state.menuTriggered})
109
- }
110
-
111
- noAccess() {
112
- const {currentUser} = digs(this.props, "currentUser")
113
-
114
- if (!currentUser) return true
115
-
116
- return false
117
- }
45
+ setMenuTriggered(!menuTriggered)
46
+ }, [menuTriggered])
47
+
48
+ return (
49
+ <div className={classNames("components--admin--layout", className)} data-menu-triggered={menuTriggered} {...restProps}>
50
+ <Menu
51
+ active={active}
52
+ noAccess={noAccess}
53
+ onRequestMenuClose={onRequestMenuClose}
54
+ triggered={menuTriggered}
55
+ />
56
+ <Header actions={actions} onTriggerMenu={onTriggerMenu} title={headerTitle} />
57
+ <div className="app-layout-content-container">
58
+ {noAccess &&
59
+ <>
60
+ <NoAccess />
61
+ {currentUser &&
62
+ <>
63
+ <div className="mb-4">
64
+ {I18n.t("js.api_maker.super_admin.layout.try_signing_out_and_in_with_a_different_user", {defaultValue: "Try signing in with a different user."})}
65
+ </div>
66
+ </>
67
+ }
68
+ {!currentUser &&
69
+ <>
70
+ <div className="mb-4">
71
+ {I18n.t("js.api_maker.super_admin.layout.try_signing_in", {defaultValue: "Try signing in."})}
72
+ </div>
73
+ </>
74
+ }
75
+ </>
76
+ }
77
+ {!noAccess && children}
78
+ </div>
79
+ </div>
80
+ )
118
81
  }
119
82
 
120
- export default withCurrentUser(ApiMakerSuperAdminLayout)
83
+ ApiMakerSuperAdminLayout.propTypes = PropTypesExact({
84
+ actions: PropTypes.node,
85
+ active: PropTypes.string,
86
+ children: PropTypes.node,
87
+ className: PropTypes.string,
88
+ currentCustomer: PropTypes.instanceOf(User),
89
+ currentCustomerId: PropTypes.string,
90
+ currentUser: PropTypes.instanceOf(User),
91
+ headTitle: PropTypes.string,
92
+ headerTitle: PropTypes.string
93
+ })
94
+
95
+ export default memo(ApiMakerSuperAdminLayout)