Skip to content

Commit 44f6553

Browse files
authored
fix: bugs in the Next generator (#263)
* fix: bugs in the Next generator * fix: more bug fixes * Use bootstrap icons
1 parent b8829e0 commit 44f6553

File tree

11 files changed

+115
-121
lines changed

11 files changed

+115
-121
lines changed

README.md

+30-37
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,50 @@
44
[![npm version](https://badge.fury.io/js/%40api-platform%2Fclient-generator.svg)](https://badge.fury.io/js/%40api-platform%2Fclient-generator)
55

66
API Platform Client Generator is a generator to scaffold app with Create-Retrieve-Update-Delete features for any API exposing a [Hydra](http://www.hydra-cg.com/spec/latest/core/) or [OpenAPI](https://www.openapis.org/) documentation for:
7-
* Quasar Framework
8-
* Next.js
9-
* React/Redux
10-
* React Native
11-
* TypeScript Interfaces
12-
* Vue.js
13-
* Vuetify.js
7+
8+
* Next.js
9+
* Nuxt.js
10+
* Quasar Framework
11+
* React/Redux
12+
* React Native
13+
* TypeScript Interfaces
14+
* Vue.js
15+
* Vuetify.js
1416

1517
Works especially well with APIs built with the [API Platform](https://api-platform.com) framework.
1618

1719
## Documentation
1820

1921
The documentation of API Platform's Client Generator can be browsed [on the official website](https://api-platform.com/docs/client-generator).
2022

21-
## Usage
22-
23-
**Hydra**
24-
```sh
25-
npx @api-platform/client-generator https://demo.api-platform.com/ output/ --resource Book
26-
```
27-
28-
**OpenAPI v2 (formerly known as Swagger)** (experimental)
29-
```sh
30-
npx @api-platform/client-generator https://demo.api-platform.com/docs.json output/ --resource Book --format swagger
31-
```
32-
33-
or
34-
35-
```sh
36-
npx @api-platform/client-generator https://demo.api-platform.com/docs.json output/ --resource Book --format openapi2
37-
```
38-
39-
**OpenAPI v3** (experimental)
40-
```sh
41-
npx @api-platform/client-generator https://demo.api-platform.com/docs.json?spec_version=3 output/ --resource Book --format openapi3
42-
```
43-
4423
## Features
4524

46-
* Generate high-quality ES6 components and files built with [React](https://facebook.github.io/react/), [Redux](http://redux.js.org), [React Router](https://reacttraining.com/react-router/) and [Redux Form](http://redux-form.com/) including:
47-
* A list view
48-
* A creation form
49-
* An editing form
50-
* A deletion button
51-
* Use the Hydra or Swagger API documentation to generate the code
25+
* Generate high-quality TypeScript or ES6 components:
26+
* List view
27+
* Creation form
28+
* Editing form
29+
* Deletion button
30+
* Use the Hydra or OpenAPI documentations to generate the code
5231
* Generate the suitable HTML5 input type (`number`, `date`...) according to the type of the API property
5332
* Display of the server-side validation errors under the related input (if using API Platform Core)
5433
* Client-side validation (`required` attributes)
5534
* The generated HTML is compatible with [Bootstrap](https://getbootstrap.com/) and includes mandatory classes
5635
* The generated HTML code is accessible to people with disabilities ([ARIA](https://www.w3.org/WAI/intro/aria) support)
57-
* The Redux and the React Router configuration is also generated
36+
37+
38+
## Usage
39+
40+
### Hydra
41+
42+
npx @api-platform/client-generator https://demo.api-platform.com/ output/ --resource Book
43+
44+
### OpenAPI v3 (experimental)
45+
46+
npx @api-platform/client-generator https://demo.api-platform.com/docs.json?spec_version=3 output/ --resource Book --format openapi3
47+
48+
### OpenAPI v2 (formerly known as Swagger, deprecated)
49+
50+
npx @api-platform/client-generator https://demo.api-platform.com/docs.json?spec_version=2 output/ --resource Book --format openapi2
5851

5952
## Credits
6053

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@api-platform/client-generator",
33
"version": "0.5.2",
4-
"description": "Generate a CRUD application built with React, Redux and React Router from an Hydra-enabled API",
4+
"description": "Generate apps built with Next, Nuxt, Quasar, React, React Native, Vue or Vuetify for any API documented using Hydra or OpenAPI",
55
"files": [
66
"*.md",
77
"docs/*.md",

src/generators/NextGenerator.js

-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ export default class NextGenerator extends BaseGenerator {
6262
`${dir}/config`,
6363
`${dir}/error`,
6464
`${dir}/types`,
65-
`${dir}/pages/${context.lc}s/[id]`,
6665
`${dir}/utils`,
6766
].forEach((dir) => this.createDir(dir, false));
6867

src/index.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import generators from "./generators";
1111
program
1212
.version(version)
1313
.description(
14-
"Generate a CRUD application built with React, Redux and React Router from an Hydra-enabled API"
14+
"Generate apps built with Next, Nuxt, Quasar, React, React Native, Vue or Vuetify for any API documented using Hydra or OpenAPI"
1515
)
1616
.usage("entrypoint outputDirectory")
1717
.option(
@@ -36,7 +36,11 @@ program
3636
"The templates directory base to use. Final directory will be ${templateDirectory}/${generator}",
3737
`${__dirname}/../templates/`
3838
)
39-
.option("-f, --format [hydra|swagger]", '"hydra" or "swagger', "hydra")
39+
.option(
40+
"-f, --format [hydra|openapi3|openapi2]",
41+
'"hydra", "openapi3" or "openapi2"',
42+
"hydra"
43+
)
4044
.option(
4145
"-s, --server-path [serverPath]",
4246
"Path to express server file to allow route dynamic addition (Next.js generator only)"
@@ -83,7 +87,8 @@ const parser = (entrypointWithSlash) => {
8387
options.headers.set("Authorization", `Bearer ${program.bearer}`);
8488
}
8589
switch (program.format) {
86-
case "swagger":
90+
case "swagger": // deprecated
91+
case "openapi2":
8792
return parseSwaggerDocumentation(entrypointWithSlash);
8893
case "openapi3":
8994
return parseOpenApi3Documentation(entrypointWithSlash);

templates/next/components/common/ReferenceLinks.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ interface Props {
66
type: string;
77
useIcon?: boolean;
88
}
9-
export const ReferenceLinks: FunctionComponent<Props> = ({
9+
const ReferenceLinks: FunctionComponent<Props> = ({
1010
items,
1111
type,
1212
useIcon = false,
@@ -28,7 +28,7 @@ export const ReferenceLinks: FunctionComponent<Props> = ({
2828
<a>
2929
{useIcon ? (
3030
<Fragment>
31-
<span className="fa fa-search" aria-hidden="true" />
31+
<i className="bi bi-search" aria-hidden="true"></i>
3232
<span className="sr-only">Show</span>
3333
</Fragment>
3434
) : (
@@ -38,3 +38,4 @@ export const ReferenceLinks: FunctionComponent<Props> = ({
3838
</Link>
3939
);
4040
};
41+
export default ReferenceLinks;

templates/next/components/foo/Form.tsx

+36-28
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { FunctionComponent, useState } from "react";
2-
import { Formik } from "formik";
3-
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
42
import Link from "next/link";
53
import { useRouter } from "next/router";
4+
import { Formik } from "formik";
5+
import { fetch } from "../../utils/dataAccess";
6+
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
67

78
interface Props {
89
{{{lc}}}?: {{{ucf}}};
@@ -12,33 +13,33 @@ export const Form: FunctionComponent<Props> = ({ {{{lc}}} }) => {
1213
const [error, setError] = useState(null);
1314
const router = useRouter();
1415

15-
const handleDelete = () => {
16-
if (window.confirm("Are you sure you want to delete this item?")) {
17-
try {
18-
fetch({{{lc}}}['@id'], { method: "DELETE" });
19-
router.push("/{{{name}}}");
20-
} catch (error) {
21-
setError("Error when deleting the resource.");
22-
console.error(error);
23-
}
24-
}
16+
const handleDelete = async () => {
17+
if (!window.confirm("Are you sure you want to delete this item?")) return;
18+
19+
try {
20+
await fetch({{{lc}}}['@id'], { method: "DELETE" });
21+
router.push("/{{{name}}}");
22+
} catch (error) {
23+
setError("Error when deleting the resource.");
24+
console.error(error);
25+
}
2526
};
2627

2728
return (
2829
<div>
29-
{ {{{lc}}} ? <h1>Edit {{{lc}}}['@id']</h1> : <h1>Create</h1>}
30+
{ {{{lc}}} ? <h1>Edit {{{ucf}}} { {{{lc}}}['@id'] }</h1> : <h1>Create {{{ucf}}}</h1>}
3031
<Formik
31-
initialValues={ {{{lc}}} ?? new{{{lc}}}() }
32+
initialValues={ {{~lc}} ? {...{{lc~}} } : new {{{ucf}}}()}
3233
validate={(values) => {
3334
const errors = {};
3435
// add your validation logic here
3536
return errors;
3637
}}
37-
onSubmit={(values, { setSubmitting, setStatus }) => {
38-
const isCreation = !{{{lc}}}["@id"];
38+
onSubmit={async (values, { setSubmitting, setStatus }) => {
39+
const isCreation = !values["@id"];
3940
try {
40-
fetch(isCreation ? "/{{{name}}}" : {{{lc}}}["@id"], {
41-
method: isCreation ? "POST" : "PATCH",
41+
await fetch(isCreation ? "/{{{name}}}" : values["@id"], {
42+
method: isCreation ? "POST" : "PUT",
4243
body: JSON.stringify(values),
4344
});
4445
setStatus({
@@ -57,28 +58,35 @@ export const Form: FunctionComponent<Props> = ({ {{{lc}}} }) => {
5758
>
5859
{({
5960
values,
60-
status,
61+
status,
62+
errors,
63+
touched,
6164
handleChange,
6265
handleBlur,
6366
handleSubmit,
6467
isSubmitting,
6568
}) => (
6669
<form onSubmit={handleSubmit}>
67-
{{#each fields}}
70+
{{#each formFields}}
6871
<div className="form-group">
69-
<label>{{name}}</label>
72+
<label className="form-control-label" htmlFor="{{lc}}_{{name}}">{{name}}</label>
7073
<input
71-
className="form-control"
72-
type="text"
73-
name="isbn"
74+
name="{{name}}"
75+
id="{{lc}}_{{name}}"
76+
value={ values.{{name}} ?? "" }
77+
type="{{type}}"
78+
{{#if step}}step="{{{step}}}"{{/if}}
79+
placeholder="{{{description}}}"
80+
{{#if required}}required={true}{{/if}}
81+
className={`form-control${errors.{{name}} && touched.{{name}} ? ' is-invalid' : ''}`}
82+
aria-invalid={errors.{{name}} && touched.{{name~}} }
7483
onChange={handleChange}
7584
onBlur={handleBlur}
76-
value={ values.{{name}} }
77-
required
7885
/>
7986
</div>
80-
{ errors.{{name}} && touched.{{name}} && errors.{{name}} }
81-
{{/each}}
87+
{ errors.{{name}} && touched.{{name}} && <div className="invalid-feedback">{ errors.{{name}} }</div> }
88+
{{/each}}
89+
8290
{status && status.msg && (
8391
<div
8492
className={`alert ${

templates/next/components/foo/List.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { FunctionComponent } from 'react';
2-
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
1+
import { FunctionComponent } from "react";
32
import Link from "next/link";
3+
import ReferenceLinks from "../../components/common/ReferenceLinks";
4+
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
45

56
interface Props {
67
{{{name}}}: {{{ucf}}}[];

templates/next/components/foo/Show.tsx

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { FunctionComponent, useState } from 'react';
22
import Link from 'next/link';
3+
import { useRouter } from "next/router";
4+
import { fetch } from "../../utils/dataAccess";
35
import { ReferenceLinks } from '../common/ReferenceLinks';
46
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
5-
import { useRouter } from "next/router";
67

78
interface Props {
89
{{{lc}}}: {{{ucf}}};
@@ -12,16 +13,16 @@ export const Show: FunctionComponent<Props> = ({ {{{lc}}} }) => {
1213
const [error, setError] = useState(null);
1314
const router = useRouter();
1415

15-
const handleDelete = () => {
16-
if (window.confirm("Are you sure you want to delete this item?")) {
17-
try {
18-
fetch({{{lc}}}["@id"], { method: "DELETE" });
19-
router.push("/{{{name}}}");
20-
} catch (error) {
21-
setError("Error when deleting the resource.");
22-
console.error(error);
23-
}
24-
}
16+
const handleDelete = async () => {
17+
if (!window.confirm("Are you sure you want to delete this item?")) return;
18+
19+
try {
20+
await fetch({{{lc}}}["@id"], { method: "DELETE" });
21+
router.push("/{{{name}}}");
22+
} catch (error) {
23+
setError("Error when deleting the resource.");
24+
console.error(error);
25+
}
2526
};
2627

2728
return (
@@ -50,8 +51,8 @@ export const Show: FunctionComponent<Props> = ({ {{{lc}}} }) => {
5051
)}
5152
<Link href="/{{{name}}}">
5253
<a className="btn btn-primary">Back to list</a>
53-
</Link>
54-
<Link href="/{{{name}}}/edit">
54+
</Link>{" "}
55+
<Link href={`${ {{~lc}}["@id"]}/edit`}>
5556
<a className="btn btn-warning">Edit</a>
5657
</Link>
5758
<button className="btn btn-danger" onClick={handleDelete}>

templates/next/pages/foos/[id]/edit.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextComponentType, NextPageContext } from 'next';
22
import { Form } from '../../../components/{{{lc}}}/Form';
3-
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
3+
import { {{{ucf}}} } from '../../../types/{{{ucf}}}';
44
import { fetch } from '../../../utils/dataAccess';
55

66
interface Props {

templates/next/pages/foos/[id]/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextComponentType, NextPageContext } from 'next';
2-
import { Show } from '../../components/{{{lc}}}/Show';
3-
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
2+
import { Show } from '../../../components/{{{lc}}}/Show';
3+
import { {{{ucf}}} } from '../../../types/{{{ucf}}}';
44
import { fetch } from '../../../utils/dataAccess';
55

66
interface Props {

0 commit comments

Comments
 (0)