@knapsack/renderer-react-components 4.79.2 → 4.79.3--canary.6036.f7fab19.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/.lintstagedrc.mjs +1 -1
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-lint.log +13 -0
- package/CHANGELOG.md +0 -27
- package/dist/demo-wrapper.d.ts +2 -0
- package/dist/demo-wrapper.d.ts.map +1 -1
- package/dist/demo-wrapper.js.map +1 -1
- package/package.json +6 -6
- package/src/demo-wrapper.tsx +14 -0
- package/src/error-catcher.tsx +79 -0
- package/src/prototype-template.tsx +5 -0
- package/src/react-renderer-client-app.tsx +26 -0
- package/src/test-fixtures/button.tsx +7 -0
- package/src/test-fixtures/card-prop-types.tsx +79 -0
- package/src/test-fixtures/card.tsx +54 -0
- package/src/test-fixtures/complex-props.tsx +25 -0
- package/src/test-fixtures/generic-component-type.tsx +20 -0
- package/src/test-fixtures/index.ts +6 -0
- package/src/test-fixtures/render-node-props.tsx +23 -0
package/.lintstagedrc.mjs
CHANGED
|
@@ -6,6 +6,6 @@ import defaultConfig from '../../../../.lintstagedrc.mjs';
|
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
|
-
'*.{ts,tsx,js,jsx,cjs,mjs
|
|
9
|
+
'*.{ts,tsx,js,jsx,cjs,mjs}': `cd ${__dirname} && ./node_modules/.bin/eslint`,
|
|
10
10
|
...defaultConfig,
|
|
11
11
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
> @knapsack/renderer-react-components@4.78.14 lint /home/runner/work/app-monorepo/app-monorepo/apps/client/libs/renderer-react-components
|
|
3
|
+
> eslint ./
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/home/runner/work/app-monorepo/app-monorepo/apps/client/libs/renderer-react-components/src/test-fixtures/complex-props.tsx
|
|
7
|
+
18:3 warning 'config' is defined but never used. Allowed unused args must match /^_/u unused-imports/no-unused-vars
|
|
8
|
+
|
|
9
|
+
/home/runner/work/app-monorepo/app-monorepo/apps/client/libs/renderer-react-components/src/test-fixtures/generic-component-type.tsx
|
|
10
|
+
16:23 warning 'index' is defined but never used. Allowed unused args must match /^_/u unused-imports/no-unused-vars
|
|
11
|
+
|
|
12
|
+
✖ 2 problems (0 errors, 2 warnings)
|
|
13
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
# v4.79.2 (Tue Apr 29 2025)
|
|
2
|
-
|
|
3
|
-
#### 🏠 Internal
|
|
4
|
-
|
|
5
|
-
- update logic for editable spec [#6077](https://github.com/knapsack-labs/app-monorepo/pull/6077) ([@mabry1985](https://github.com/mabry1985))
|
|
6
|
-
- Add .mts and .cts file extensions to ESLint configuration in lint-staged [#6079](https://github.com/knapsack-labs/app-monorepo/pull/6079) ([@EvanLovely](https://github.com/EvanLovely))
|
|
7
|
-
|
|
8
|
-
#### Authors: 2
|
|
9
|
-
|
|
10
|
-
- Evan Lovely ([@EvanLovely](https://github.com/EvanLovely))
|
|
11
|
-
- Josh Mabry ([@mabry1985](https://github.com/mabry1985))
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
# v4.79.0 (Mon Apr 28 2025)
|
|
16
|
-
|
|
17
|
-
#### 🚀 Enhancement
|
|
18
|
-
|
|
19
|
-
- setup better testing on App Client [#6072](https://github.com/knapsack-labs/app-monorepo/pull/6072) ([@EvanLovely](https://github.com/EvanLovely))
|
|
20
|
-
- setup new Demo type that is App Client Data agnostic [#6072](https://github.com/knapsack-labs/app-monorepo/pull/6072) ([@EvanLovely](https://github.com/EvanLovely))
|
|
21
|
-
|
|
22
|
-
#### Authors: 1
|
|
23
|
-
|
|
24
|
-
- Evan Lovely ([@EvanLovely](https://github.com/EvanLovely))
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
1
|
# v4.78.13 (Fri Apr 25 2025)
|
|
29
2
|
|
|
30
3
|
#### 🏠 Internal
|
package/dist/demo-wrapper.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { Demo } from '@knapsack/types';
|
|
|
2
2
|
export type DemoWrapperProps = {
|
|
3
3
|
children: React.ReactNode;
|
|
4
4
|
demo: Demo;
|
|
5
|
+
patternId: string;
|
|
6
|
+
templateId: string;
|
|
5
7
|
};
|
|
6
8
|
declare const DemoWrapper: ({ children }: DemoWrapperProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
9
|
export default DemoWrapper;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demo-wrapper.d.ts","sourceRoot":"","sources":["../src/demo-wrapper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,IAAI,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"demo-wrapper.d.ts","sourceRoot":"","sources":["../src/demo-wrapper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,QAAA,MAAM,WAAW,GAAI,cAAc,gBAAgB,4CAElD,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
package/dist/demo-wrapper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demo-wrapper.js","sourceRoot":"","sources":["../src/demo-wrapper.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"demo-wrapper.js","sourceRoot":"","sources":["../src/demo-wrapper.tsx"],"names":[],"mappings":";AASA,MAAM,WAAW,GAAG,CAAC,EAAE,QAAQ,EAAoB,EAAE,EAAE,CAAC,CACtD,cAAK,SAAS,EAAC,cAAc,YAAE,QAAQ,GAAO,CAC/C,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knapsack/renderer-react-components",
|
|
3
3
|
"description": "",
|
|
4
|
-
"version": "4.79.
|
|
4
|
+
"version": "4.79.3--canary.6036.f7fab19.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./demo-wrapper": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"lint": "eslint ./"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@knapsack/eslint-config-starter": "4.79.
|
|
39
|
-
"@knapsack/prettier-config": "4.79.
|
|
40
|
-
"@knapsack/types": "4.79.
|
|
41
|
-
"@knapsack/typescript-config-starter": "4.79.
|
|
38
|
+
"@knapsack/eslint-config-starter": "4.79.3--canary.6036.f7fab19.0",
|
|
39
|
+
"@knapsack/prettier-config": "4.79.3--canary.6036.f7fab19.0",
|
|
40
|
+
"@knapsack/types": "4.79.3--canary.6036.f7fab19.0",
|
|
41
|
+
"@knapsack/typescript-config-starter": "4.79.3--canary.6036.f7fab19.0",
|
|
42
42
|
"@types/node": "^20.17.24",
|
|
43
43
|
"@types/prop-types": "^15.7.14",
|
|
44
44
|
"@types/react": "^18.3.20",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"directory": "apps/client/libs/renderer-react-components",
|
|
57
57
|
"type": "git"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "f7fab193b76725d99a7f77f334c13776477f1378"
|
|
60
60
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Demo } from '@knapsack/types';
|
|
2
|
+
|
|
3
|
+
export type DemoWrapperProps = {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
demo: Demo;
|
|
6
|
+
patternId: string;
|
|
7
|
+
templateId: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const DemoWrapper = ({ children }: DemoWrapperProps) => (
|
|
11
|
+
<div className="demo-wrapper">{children}</div>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export default DemoWrapper;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
type State = {
|
|
4
|
+
hasError: boolean;
|
|
5
|
+
componentStack?: string;
|
|
6
|
+
error?: Error;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default class ErrorCatcher extends React.Component<
|
|
10
|
+
{ children: React.ReactNode },
|
|
11
|
+
State
|
|
12
|
+
> {
|
|
13
|
+
constructor(props: { children: React.ReactNode }) {
|
|
14
|
+
super(props);
|
|
15
|
+
this.state = {
|
|
16
|
+
hasError: false,
|
|
17
|
+
componentStack: '',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static getDerivedStateFromError(_error: Error) {
|
|
22
|
+
// Update state so the next render will show the fallback UI.
|
|
23
|
+
return {
|
|
24
|
+
hasError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
|
29
|
+
const { componentStack } = errorInfo;
|
|
30
|
+
this.setState({
|
|
31
|
+
error,
|
|
32
|
+
componentStack: componentStack ?? '',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override render() {
|
|
37
|
+
if (this.state.hasError) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
style={{
|
|
41
|
+
padding: '5px',
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<h5>Error caught in React Components</h5>
|
|
45
|
+
{this.state.error?.name && (
|
|
46
|
+
<h5>
|
|
47
|
+
Error Name: <code>{this.state.error?.name}</code>
|
|
48
|
+
</h5>
|
|
49
|
+
)}
|
|
50
|
+
{this.state.error?.message && (
|
|
51
|
+
<h5>
|
|
52
|
+
Message:
|
|
53
|
+
{this.state.error?.message}
|
|
54
|
+
</h5>
|
|
55
|
+
)}
|
|
56
|
+
{this.state.componentStack && (
|
|
57
|
+
<>
|
|
58
|
+
<h6>Component Stack:</h6>
|
|
59
|
+
<pre>
|
|
60
|
+
<code>{this.state.componentStack}</code>
|
|
61
|
+
</pre>
|
|
62
|
+
<br />
|
|
63
|
+
</>
|
|
64
|
+
)}
|
|
65
|
+
{this.state.error?.stack && (
|
|
66
|
+
<>
|
|
67
|
+
<h6>Error Stack:</h6>
|
|
68
|
+
<pre>
|
|
69
|
+
<code>{this.state.error?.stack}</code>
|
|
70
|
+
</pre>
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return this.props.children;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { StrictMode, type ComponentType } from 'react';
|
|
2
|
+
import type { Except } from '@knapsack/types';
|
|
3
|
+
import ErrorCatcher from './error-catcher.js';
|
|
4
|
+
import type { DemoWrapperProps } from './demo-wrapper.js';
|
|
5
|
+
|
|
6
|
+
export const ReactRendererClientApp = ({
|
|
7
|
+
DemoApp,
|
|
8
|
+
DemoWrapper,
|
|
9
|
+
disableReactStrictMode,
|
|
10
|
+
demoWrapperProps,
|
|
11
|
+
}: {
|
|
12
|
+
DemoApp: ComponentType<Record<string, unknown>>;
|
|
13
|
+
DemoWrapper: ComponentType<Record<string, unknown>>;
|
|
14
|
+
demoWrapperProps: Except<DemoWrapperProps, 'children'>;
|
|
15
|
+
disableReactStrictMode?: boolean;
|
|
16
|
+
}) => {
|
|
17
|
+
const app = (
|
|
18
|
+
<ErrorCatcher>
|
|
19
|
+
<DemoWrapper {...demoWrapperProps}>
|
|
20
|
+
<DemoApp />
|
|
21
|
+
</DemoWrapper>
|
|
22
|
+
</ErrorCatcher>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return disableReactStrictMode ? app : <StrictMode>{app}</StrictMode>;
|
|
26
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
export class CardPropTypes extends React.Component {
|
|
5
|
+
constructor(props: any) {
|
|
6
|
+
super(props);
|
|
7
|
+
this.state = {
|
|
8
|
+
count: 0,
|
|
9
|
+
};
|
|
10
|
+
this.handleButtonClick = this.handleButtonClick.bind(this);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
handleButtonClick() {
|
|
14
|
+
this.setState((prevState) => {
|
|
15
|
+
// @ts-expect-error purposely not typed
|
|
16
|
+
const newCount = prevState.count + 1;
|
|
17
|
+
// @ts-expect-error purposely not typed
|
|
18
|
+
this.props.handleClick(newCount);
|
|
19
|
+
return { count: newCount };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override render() {
|
|
24
|
+
return (
|
|
25
|
+
<aside
|
|
26
|
+
// @ts-expect-error purposely not typed
|
|
27
|
+
className={`card card--align-${this.props.align}${
|
|
28
|
+
// @ts-expect-error purposely not typed
|
|
29
|
+
this.props.isDark ? ' card--dark' : ''
|
|
30
|
+
}`}
|
|
31
|
+
>
|
|
32
|
+
{/* @ts-expect-error purposely not typed */}
|
|
33
|
+
{this.props.headerSlot}
|
|
34
|
+
<div
|
|
35
|
+
className="card__img"
|
|
36
|
+
// @ts-expect-error purposely not typed
|
|
37
|
+
style={{ backgroundImage: `url("${this.props.img}")` }}
|
|
38
|
+
/>
|
|
39
|
+
<div className="card__contents">
|
|
40
|
+
{/* @ts-expect-error purposely not typed */}
|
|
41
|
+
<h3 className="card__title">{this.props.title}</h3>
|
|
42
|
+
<p>
|
|
43
|
+
Count:
|
|
44
|
+
{/* @ts-expect-error purposely not typed */}
|
|
45
|
+
{this.state.count}
|
|
46
|
+
</p>
|
|
47
|
+
<button onClick={this.handleButtonClick} type="button">
|
|
48
|
+
Add
|
|
49
|
+
</button>
|
|
50
|
+
{/* @ts-expect-error purposely not typed */}
|
|
51
|
+
<p className="card__body">{this.props.body}</p>
|
|
52
|
+
</div>
|
|
53
|
+
{/* @ts-expect-error purposely not typed */}
|
|
54
|
+
<footer className="card__footer">{this.props.children}</footer>
|
|
55
|
+
</aside>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// @ts-expect-error purposely not typed
|
|
61
|
+
CardPropTypes.defaultProps = {
|
|
62
|
+
align: 'left',
|
|
63
|
+
children: null,
|
|
64
|
+
handleClick: () => {},
|
|
65
|
+
isDark: false,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// @ts-expect-error purposely not typed
|
|
69
|
+
CardPropTypes.propTypes = {
|
|
70
|
+
/**
|
|
71
|
+
* The card title
|
|
72
|
+
*/
|
|
73
|
+
title: PropTypes.string.isRequired,
|
|
74
|
+
align: PropTypes.oneOf(['left', 'right']),
|
|
75
|
+
children: PropTypes.node,
|
|
76
|
+
headerSlot: PropTypes.node.isRequired,
|
|
77
|
+
handleClick: PropTypes.func,
|
|
78
|
+
isDark: PropTypes.bool,
|
|
79
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
// export const CardButton = ({ disabled = false, text }) => (
|
|
4
|
+
// <button>{text}</button>
|
|
5
|
+
// );
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
textAlign?: 'left' | 'center' | 'right';
|
|
9
|
+
// cardHeader?: string;
|
|
10
|
+
imgSrc?: string;
|
|
11
|
+
isDark?: boolean;
|
|
12
|
+
cardTitle?: string;
|
|
13
|
+
cardSubTitle?: string;
|
|
14
|
+
cardBody: string;
|
|
15
|
+
handleIt?: (x: string) => boolean;
|
|
16
|
+
header?: React.ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Goes in footer
|
|
19
|
+
*/
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
items: {
|
|
22
|
+
id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Card: React.FC<Props> = ({
|
|
29
|
+
textAlign = 'left',
|
|
30
|
+
children,
|
|
31
|
+
cardBody,
|
|
32
|
+
// cardHeader,
|
|
33
|
+
cardSubTitle,
|
|
34
|
+
cardTitle,
|
|
35
|
+
imgSrc,
|
|
36
|
+
isDark = true,
|
|
37
|
+
}: Props) => {
|
|
38
|
+
const classes = `card text-${textAlign} ${isDark ? 'bg-dark' : ''}`;
|
|
39
|
+
return (
|
|
40
|
+
<div className={classes}>
|
|
41
|
+
{imgSrc && <img src={imgSrc} className="card-img-top" alt="hi" />}
|
|
42
|
+
<div className="card-body">
|
|
43
|
+
<h5 className="card-title">{cardTitle}</h5>
|
|
44
|
+
{cardSubTitle && (
|
|
45
|
+
<h6 className="card-subtitle mb-2 text-muted">{cardSubTitle}</h6>
|
|
46
|
+
)}
|
|
47
|
+
<p className="card-text">{cardBody}</p>
|
|
48
|
+
{children}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default Card;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Example with complex TypeScript types
|
|
2
|
+
interface ComplexProps {
|
|
3
|
+
/** Array of user objects */
|
|
4
|
+
users: Array<{
|
|
5
|
+
id: number;
|
|
6
|
+
name: string;
|
|
7
|
+
role: 'admin' | 'user';
|
|
8
|
+
}>;
|
|
9
|
+
/** Configuration object */
|
|
10
|
+
config: {
|
|
11
|
+
theme: 'light' | 'dark';
|
|
12
|
+
showHeader: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ComplexComponent = ({
|
|
17
|
+
users,
|
|
18
|
+
config,
|
|
19
|
+
}: ComplexProps): JSX.Element => (
|
|
20
|
+
<div>
|
|
21
|
+
{users.map((user) => (
|
|
22
|
+
<div key={user.id}>{user.name}</div>
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
// // Example with generic types
|
|
4
|
+
interface ListProps<T> {
|
|
5
|
+
/** List of items */
|
|
6
|
+
items: T[];
|
|
7
|
+
/** Render function for each item */
|
|
8
|
+
renderItem: (item: T) => ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const GenericComponentType = <T,>({
|
|
12
|
+
items,
|
|
13
|
+
renderItem,
|
|
14
|
+
}: ListProps<T>): JSX.Element => (
|
|
15
|
+
<ul>
|
|
16
|
+
{items.map((item, index) => (
|
|
17
|
+
<li key={JSON.stringify(item)}>{renderItem(item)}</li>
|
|
18
|
+
))}
|
|
19
|
+
</ul>
|
|
20
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
// Example with render props using TypeScript
|
|
4
|
+
interface RenderPropsComponentProps {
|
|
5
|
+
/** Header render function */
|
|
6
|
+
renderHeader: () => ReactNode;
|
|
7
|
+
/** Content render function */
|
|
8
|
+
renderContent: (data: { count: number }) => ReactNode;
|
|
9
|
+
/** Optional footer */
|
|
10
|
+
footer?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const RenderPropsComponent = ({
|
|
14
|
+
renderHeader,
|
|
15
|
+
renderContent,
|
|
16
|
+
footer,
|
|
17
|
+
}: RenderPropsComponentProps): JSX.Element => (
|
|
18
|
+
<div>
|
|
19
|
+
{renderHeader()}
|
|
20
|
+
{renderContent({ count: 0 })}
|
|
21
|
+
{footer}
|
|
22
|
+
</div>
|
|
23
|
+
);
|