Skip to content

Commit

Permalink
fix: bugs in the Next generator (#263)
Browse files Browse the repository at this point in the history
* fix: bugs in the Next generator

* fix: more bug fixes

* Use bootstrap icons
  • Loading branch information
dunglas authored Jan 22, 2021
1 parent b8829e0 commit 44f6553
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 121 deletions.
67 changes: 30 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,50 @@
[![npm version](https://badge.fury.io/js/%40api-platform%2Fclient-generator.svg)](https://badge.fury.io/js/%40api-platform%2Fclient-generator)

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:
* Quasar Framework
* Next.js
* React/Redux
* React Native
* TypeScript Interfaces
* Vue.js
* Vuetify.js

* Next.js
* Nuxt.js
* Quasar Framework
* React/Redux
* React Native
* TypeScript Interfaces
* Vue.js
* Vuetify.js

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

## Documentation

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

## Usage

**Hydra**
```sh
npx @api-platform/client-generator https://demo.api-platform.com/ output/ --resource Book
```

**OpenAPI v2 (formerly known as Swagger)** (experimental)
```sh
npx @api-platform/client-generator https://demo.api-platform.com/docs.json output/ --resource Book --format swagger
```

or

```sh
npx @api-platform/client-generator https://demo.api-platform.com/docs.json output/ --resource Book --format openapi2
```

**OpenAPI v3** (experimental)
```sh
npx @api-platform/client-generator https://demo.api-platform.com/docs.json?spec_version=3 output/ --resource Book --format openapi3
```

## Features

* 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:
* A list view
* A creation form
* An editing form
* A deletion button
* Use the Hydra or Swagger API documentation to generate the code
* Generate high-quality TypeScript or ES6 components:
* List view
* Creation form
* Editing form
* Deletion button
* Use the Hydra or OpenAPI documentations to generate the code
* Generate the suitable HTML5 input type (`number`, `date`...) according to the type of the API property
* Display of the server-side validation errors under the related input (if using API Platform Core)
* Client-side validation (`required` attributes)
* The generated HTML is compatible with [Bootstrap](https://getbootstrap.com/) and includes mandatory classes
* The generated HTML code is accessible to people with disabilities ([ARIA](https://www.w3.org/WAI/intro/aria) support)
* The Redux and the React Router configuration is also generated


## Usage

### Hydra

npx @api-platform/client-generator https://demo.api-platform.com/ output/ --resource Book

### OpenAPI v3 (experimental)

npx @api-platform/client-generator https://demo.api-platform.com/docs.json?spec_version=3 output/ --resource Book --format openapi3

### OpenAPI v2 (formerly known as Swagger, deprecated)

npx @api-platform/client-generator https://demo.api-platform.com/docs.json?spec_version=2 output/ --resource Book --format openapi2

## Credits

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@api-platform/client-generator",
"version": "0.5.2",
"description": "Generate a CRUD application built with React, Redux and React Router from an Hydra-enabled API",
"description": "Generate apps built with Next, Nuxt, Quasar, React, React Native, Vue or Vuetify for any API documented using Hydra or OpenAPI",
"files": [
"*.md",
"docs/*.md",
Expand Down
1 change: 0 additions & 1 deletion src/generators/NextGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export default class NextGenerator extends BaseGenerator {
`${dir}/config`,
`${dir}/error`,
`${dir}/types`,
`${dir}/pages/${context.lc}s/[id]`,
`${dir}/utils`,
].forEach((dir) => this.createDir(dir, false));

Expand Down
11 changes: 8 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import generators from "./generators";
program
.version(version)
.description(
"Generate a CRUD application built with React, Redux and React Router from an Hydra-enabled API"
"Generate apps built with Next, Nuxt, Quasar, React, React Native, Vue or Vuetify for any API documented using Hydra or OpenAPI"
)
.usage("entrypoint outputDirectory")
.option(
Expand All @@ -36,7 +36,11 @@ program
"The templates directory base to use. Final directory will be ${templateDirectory}/${generator}",
`${__dirname}/../templates/`
)
.option("-f, --format [hydra|swagger]", '"hydra" or "swagger', "hydra")
.option(
"-f, --format [hydra|openapi3|openapi2]",
'"hydra", "openapi3" or "openapi2"',
"hydra"
)
.option(
"-s, --server-path [serverPath]",
"Path to express server file to allow route dynamic addition (Next.js generator only)"
Expand Down Expand Up @@ -83,7 +87,8 @@ const parser = (entrypointWithSlash) => {
options.headers.set("Authorization", `Bearer ${program.bearer}`);
}
switch (program.format) {
case "swagger":
case "swagger": // deprecated
case "openapi2":
return parseSwaggerDocumentation(entrypointWithSlash);
case "openapi3":
return parseOpenApi3Documentation(entrypointWithSlash);
Expand Down
5 changes: 3 additions & 2 deletions templates/next/components/common/ReferenceLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface Props {
type: string;
useIcon?: boolean;
}
export const ReferenceLinks: FunctionComponent<Props> = ({
const ReferenceLinks: FunctionComponent<Props> = ({
items,
type,
useIcon = false,
Expand All @@ -28,7 +28,7 @@ export const ReferenceLinks: FunctionComponent<Props> = ({
<a>
{useIcon ? (
<Fragment>
<span className="fa fa-search" aria-hidden="true" />
<i className="bi bi-search" aria-hidden="true"></i>
<span className="sr-only">Show</span>
</Fragment>
) : (
Expand All @@ -38,3 +38,4 @@ export const ReferenceLinks: FunctionComponent<Props> = ({
</Link>
);
};
export default ReferenceLinks;
64 changes: 36 additions & 28 deletions templates/next/components/foo/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FunctionComponent, useState } from "react";
import { Formik } from "formik";
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
import Link from "next/link";
import { useRouter } from "next/router";
import { Formik } from "formik";
import { fetch } from "../../utils/dataAccess";
import { {{{ucf}}} } from '../../types/{{{ucf}}}';

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

const handleDelete = () => {
if (window.confirm("Are you sure you want to delete this item?")) {
try {
fetch({{{lc}}}['@id'], { method: "DELETE" });
router.push("/{{{name}}}");
} catch (error) {
setError("Error when deleting the resource.");
console.error(error);
}
}
const handleDelete = async () => {
if (!window.confirm("Are you sure you want to delete this item?")) return;

try {
await fetch({{{lc}}}['@id'], { method: "DELETE" });
router.push("/{{{name}}}");
} catch (error) {
setError("Error when deleting the resource.");
console.error(error);
}
};

return (
<div>
{ {{{lc}}} ? <h1>Edit {{{lc}}}['@id']</h1> : <h1>Create</h1>}
{ {{{lc}}} ? <h1>Edit {{{ucf}}} { {{{lc}}}['@id'] }</h1> : <h1>Create {{{ucf}}}</h1>}
<Formik
initialValues={ {{{lc}}} ?? new{{{lc}}}() }
initialValues={ {{~lc}} ? {...{{lc~}} } : new {{{ucf}}}()}
validate={(values) => {
const errors = {};
// add your validation logic here
return errors;
}}
onSubmit={(values, { setSubmitting, setStatus }) => {
const isCreation = !{{{lc}}}["@id"];
onSubmit={async (values, { setSubmitting, setStatus }) => {
const isCreation = !values["@id"];
try {
fetch(isCreation ? "/{{{name}}}" : {{{lc}}}["@id"], {
method: isCreation ? "POST" : "PATCH",
await fetch(isCreation ? "/{{{name}}}" : values["@id"], {
method: isCreation ? "POST" : "PUT",
body: JSON.stringify(values),
});
setStatus({
Expand All @@ -57,28 +58,35 @@ export const Form: FunctionComponent<Props> = ({ {{{lc}}} }) => {
>
{({
values,
status,
status,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
{{#each fields}}
{{#each formFields}}
<div className="form-group">
<label>{{name}}</label>
<label className="form-control-label" htmlFor="{{lc}}_{{name}}">{{name}}</label>
<input
className="form-control"
type="text"
name="isbn"
name="{{name}}"
id="{{lc}}_{{name}}"
value={ values.{{name}} ?? "" }
type="{{type}}"
{{#if step}}step="{{{step}}}"{{/if}}
placeholder="{{{description}}}"
{{#if required}}required={true}{{/if}}
className={`form-control${errors.{{name}} && touched.{{name}} ? ' is-invalid' : ''}`}
aria-invalid={errors.{{name}} && touched.{{name~}} }
onChange={handleChange}
onBlur={handleBlur}
value={ values.{{name}} }
required
/>
</div>
{ errors.{{name}} && touched.{{name}} && errors.{{name}} }
{{/each}}
{ errors.{{name}} && touched.{{name}} && <div className="invalid-feedback">{ errors.{{name}} }</div> }
{{/each}}

{status && status.msg && (
<div
className={`alert ${
Expand Down
5 changes: 3 additions & 2 deletions templates/next/components/foo/List.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FunctionComponent } from 'react';
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
import { FunctionComponent } from "react";
import Link from "next/link";
import ReferenceLinks from "../../components/common/ReferenceLinks";
import { {{{ucf}}} } from '../../types/{{{ucf}}}';

interface Props {
{{{name}}}: {{{ucf}}}[];
Expand Down
27 changes: 14 additions & 13 deletions templates/next/components/foo/Show.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FunctionComponent, useState } from 'react';
import Link from 'next/link';
import { useRouter } from "next/router";
import { fetch } from "../../utils/dataAccess";
import { ReferenceLinks } from '../common/ReferenceLinks';
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
import { useRouter } from "next/router";

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

const handleDelete = () => {
if (window.confirm("Are you sure you want to delete this item?")) {
try {
fetch({{{lc}}}["@id"], { method: "DELETE" });
router.push("/{{{name}}}");
} catch (error) {
setError("Error when deleting the resource.");
console.error(error);
}
}
const handleDelete = async () => {
if (!window.confirm("Are you sure you want to delete this item?")) return;

try {
await fetch({{{lc}}}["@id"], { method: "DELETE" });
router.push("/{{{name}}}");
} catch (error) {
setError("Error when deleting the resource.");
console.error(error);
}
};

return (
Expand Down Expand Up @@ -50,8 +51,8 @@ export const Show: FunctionComponent<Props> = ({ {{{lc}}} }) => {
)}
<Link href="/{{{name}}}">
<a className="btn btn-primary">Back to list</a>
</Link>
<Link href="/{{{name}}}/edit">
</Link>{" "}
<Link href={`${ {{~lc}}["@id"]}/edit`}>
<a className="btn btn-warning">Edit</a>
</Link>
<button className="btn btn-danger" onClick={handleDelete}>
Expand Down
2 changes: 1 addition & 1 deletion templates/next/pages/foos/[id]/edit.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextComponentType, NextPageContext } from 'next';
import { Form } from '../../../components/{{{lc}}}/Form';
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
import { {{{ucf}}} } from '../../../types/{{{ucf}}}';
import { fetch } from '../../../utils/dataAccess';

interface Props {
Expand Down
4 changes: 2 additions & 2 deletions templates/next/pages/foos/[id]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextComponentType, NextPageContext } from 'next';
import { Show } from '../../components/{{{lc}}}/Show';
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
import { Show } from '../../../components/{{{lc}}}/Show';
import { {{{ucf}}} } from '../../../types/{{{ucf}}}';
import { fetch } from '../../../utils/dataAccess';

interface Props {
Expand Down
Loading

0 comments on commit 44f6553

Please sign in to comment.