Skip to content

fix: bugs in the Next generator #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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