From 36805bd545cde4fd71dd857a229340ba2c355c1a Mon Sep 17 00:00:00 2001 From: Wouter Nederhof Date: Mon, 11 Mar 2024 12:25:48 +0100 Subject: [PATCH] Improve docs. Introduce generic loader component. Use Formik using hook instead of components for more flexibility. --- README.md | 59 +++++++------ .../[namePascalCase]Form.test.tsx.peb | 3 +- .../[namePascalCase]Details.tsx.peb | 5 +- .../[namePascalCase]Form.tsx.peb | 85 +++++++------------ .../[namePascalCase]List.tsx.peb | 3 +- .../src/components/LoadingIndicator.tsx.peb | 5 ++ .../components/layout/DefaultLayout.tsx.peb | 26 ++++++ .../src/lib/reset-urql-client-context.ts.peb | 2 +- .../src/pages/[underscore]app.tsx.peb | 45 ++++++++++ .../[artifactId]-web/src/pages/_app.js.peb | 26 ------ .../components/LoadingIndicator.test.tsx.peb | 8 ++ 11 files changed, 152 insertions(+), 115 deletions(-) create mode 100644 pkg/generator/templates/react-frontend/[artifactId]-web/src/components/LoadingIndicator.tsx.peb create mode 100644 pkg/generator/templates/react-frontend/[artifactId]-web/src/components/layout/DefaultLayout.tsx.peb create mode 100644 pkg/generator/templates/react-frontend/[artifactId]-web/src/pages/[underscore]app.tsx.peb delete mode 100644 pkg/generator/templates/react-frontend/[artifactId]-web/src/pages/_app.js.peb create mode 100644 pkg/generator/templates/react-frontend/[underscore][underscore]tests[underscore][underscore]/[artifact-web]/src/components/LoadingIndicator.test.tsx.peb diff --git a/README.md b/README.md index 6a959ac4..dfb42a78 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,36 @@ -# Basecode - The fastest way to build a web app -Build your next web app in days instead of months. +# Basecode - The fastest way to create a web app -Basecode is a full-stack code generator for Kotlin, Spring Boot, GraphQL, React (NextJS) and PostgreSQL. +Basecode is a full-stack code generator for creating web apps using Kotlin, Spring Boot, GraphQL, React (NextJS) and +PostgreSQL. The focus of Basecode is on creating lean, decoupled code with a solid foundation. -- **Productive**: Generate relational CRUD functionality for the frontend and backend including migrations, GraphQL schema extensions, unit tests and integration tests with a single command. -- **Maintainable**: A package-by-feature backend structure, GraphQL communication and an event-driven backend model make for a highly decoupled and extensible architecture which is built to last. -- **Incremental**: Start with almost no code. Then, once you're ready for the next step, add a GraphQL API, a frontend and more at your own pace. +Basecode is fully open source and community-driven ([MIT](LICENSE.md)). -Basecode introduces the concept of "non-intrusive relational scaffolding", which is designed to keep your code maintainable, even for entities with 1-N relationships. +## Installation +The following software needs to be installed on your machine before you can use Basecode effectively: -- **Relational:** the user may generate generate entities with 1-N relationships. -- **Non-intrusive:** code generated for one entity will not affect code of any another entity, nor will it *change* any other file in the project. +- Go 1.16 or later +- JDK 21 or later +- Node 18 or later +- Docker -## Installation -Make sure you have the Go 1.16 or later installed. Then run: +Install Basecode using the following command: ```shell go install github.com/wnederhof/basecode/cmd/basecode@latest ``` -Or replace `latest` with one of the tags found under Releases in GitHub. ## Usage -### New project -Provided that basecode is available under the alias `basecode`, you can create a new project using `basecode new`. +### Create a New Project +``` +basecode new +``` +Here, `groupId` and `artifactId` are the name of the group and artifact respectively, as defined by Maven. For example: ``` -basecode new com.mycorp blog +basecode new com.example blog ``` -### Generate +### Scaffold Generation Using `basecode generate`, you can generate code based using one of the following generators. ``` backend:scaffold, bes Backend Scaffold @@ -38,8 +40,14 @@ Using `basecode generate`, you can generate code based using one of the followin frontend, fe Frontend Support frontend:scaffold, fes Frontend Scaffold (Generate frontend support first) scaffold, s Backend and Frontend Scaffold (Generate frontend support first) + backend:auth, ba Backend Authentication - EXPERIMENTAL + frontend:auth, fa Frontend Authentication - EXPERIMENTAL +``` +For more information about the generators, run: ``` -For more information about the generators, use `-h`: +basecode generate -h +``` +For example: ``` basecode generate scaffold -h ``` @@ -55,15 +63,13 @@ Available types: - datetime - boolean -For each of these types, you can add `?` to make this type optional. For example: `title:string?`. - # Example When you want, for example, to generate a blog, you can do that as following: ``` basecode new com.mycorp blog cd blog -basecode generate scaffold Post title -basecode generate scaffold Comment postId:Post comment +basecode generate scaffold Post title contents:text +basecode generate scaffold Comment postId:Post contents:text ``` Most generators specify the following parameters: ``` @@ -75,15 +81,12 @@ Here: - `delete` will undo the file generation. This command may also additional generate files, such as migration scripts for dropping a previously created table. - `overwrite` will overwrite any existing files. When this option is not specified, Basecode will abort when a file is about to be overwritten. -## Development -For developing your application, you can use `docker-compose up` to spin up a development database. You can then either start the backend using your IDE by running the `main` method in the `Application.kt` file, or start the Spring Boot server using `./mvnw spring-boot:run`. You should be able to access your GraphQL dashboard at: `http://localhost:8080/graphiql`. +## After Initialization +After initializing your application, you can use `docker-compose up` to spin up a development database. -To start the frontend, make sure your artifacts are installed using `npm install` and run `npm run dev`. - -When both the backend and frontend are running, you can build your next best thing at: `http://localhost:3000`. (Note: as of yet, there is no index page). If you created an entity called `Post`, you will find your scaffolds at: `http://localhost:3000/posts`. +You can then either start the backend using your IDE by running the `main` method in the `Application.kt` file, or start the Spring Boot server using `./mvnw spring-boot:run`. You should be able to access your GraphQL dashboard at: `http://localhost:8080/graphiql`. -## License -Licensed under [MIT](LICENSE.md). +To start the frontend, make sure your artifacts are installed using `npm install` and run `npm run dev`. ## Credits - The `new` template is based on the code generated using Spring Boot Initializr. diff --git a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/components/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/components/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb index 7205b915..99b9ffec 100644 --- a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/components/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb +++ b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/components/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb @@ -45,8 +45,7 @@ describe('{{ namePascalCase }}Form', () => { ) const expectedFormValues = { - ...{{ nameCamelCase }}, - id: undefined{%for field in fields%}{%if field.isFieldRelational%}, + ...{{ nameCamelCase }}{%for field in fields%}{%if field.isFieldRelational%}, {{field.fieldNameCamelCase}}: '1'{%endif%}{%endfor%} } delete expectedFormValues.id diff --git a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Details.tsx.peb b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Details.tsx.peb index 9d5e8b5e..01495cea 100644 --- a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Details.tsx.peb +++ b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Details.tsx.peb @@ -1,6 +1,7 @@ import { gql, useQuery } from 'urql' import { useMemo } from 'react' import { Query } from '@generated/graphql' +import { LoadingIndicator } from '@components/LoadingIndicator' export interface {{ namePascalCase }}DetailsProps { id: string | number @@ -30,7 +31,7 @@ export function {{ namePascalCase }}Details(props: {{ namePascalCase }}DetailsPr } if (fetching || !data) { - return
Loading...
+ return } return ( @@ -51,4 +52,4 @@ export function {{ namePascalCase }}Details(props: {{ namePascalCase }}DetailsPr ) -} \ No newline at end of file +} diff --git a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Form.tsx.peb b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Form.tsx.peb index cba29aab..0be5a955 100644 --- a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Form.tsx.peb +++ b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]Form.tsx.peb @@ -1,7 +1,8 @@ import { gql, OperationResult, useMutation, useQuery } from 'urql' import { useMemo } from 'react' import { Mutation, Query{%for field in fields%}{%if field.isFieldRelational%}, {{ field.fieldTypePascalCase }}{%endif%}{%endfor%} } from '@generated/graphql' -import { Formik, Field, Form, FormikHelpers } from 'formik' +import { useFormik } from 'formik' +import { LoadingIndicator } from '@components/LoadingIndicator' export interface {{ namePascalCase }}FormProps { id?: string | number{%for field in fields%}{%if field.isFieldRelational%} @@ -39,14 +40,12 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { } const doSubmit = ( - formData: {{ namePascalCase }}FormData, - { setSubmitting }: FormikHelpers<{{ namePascalCase }}FormData> + formData: {{ namePascalCase }}FormData ) => { if (props.id) { executeUpdateMutation({ id: props.id, input: formData }) .then(result => handleMutationResult(result) && props.onSave()) .catch((reason) => alert(`Updating {{ nameCamelCase }} failed. Reason: ${reason}`)) - .finally(() => setSubmitting(false)) } else {{ "{" }}{%if hasRelations%} const formDataWithRelations = { ...formData }{%endif%}{%for field in fields%}{%if field.isFieldRelational%} if (props.{{ field.fieldNameCamelCase }}) { @@ -55,7 +54,6 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { executeCreateMutation({ input: formData{%if hasRelations%}WithRelations{%endif%} }) .then(result => handleMutationResult(result) && props.onSave()) .catch((reason) => alert(`Creating {{ nameCamelCase }} failed. Reason: ${reason}`)) - .finally(() => setSubmitting(false)) } } @@ -95,7 +93,7 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { }) {%endif%}{%endfor%} if (fetching{%for field in fields%}{%if field.isFieldRelational%} || {{ field.fieldTypePluralCamelCase }}QueryResult.fetching{%endif%}{%endfor%}) { - return
Loading...
+ return } const errorMessage = error?.message{%for field in fields%}{%if field.isFieldRelational%} || {{ field.fieldTypePluralCamelCase }}QueryResult.error?.message{%endif%}{%endfor%} @@ -104,61 +102,38 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { return
{errorMessage}
} + const formik = useFormik({ + initialValues: {{ "{" }}{%for field in fields%} + {{ field.fieldNameCamelCase }}: data?.{{ nameCamelCase }}?.{{ field.fieldNameCamelCase }}{%if field.fieldType == "BOOLEAN" or field.fieldType == "NULL_BOOLEAN"%} || false{%endif%}{%if field.fieldType == "STRING" or field.fieldType == "NULL_STRING" or field.fieldType == "TEXT" or field.fieldType == "NULL_TEXT"%} || ''{%endif%},{%endfor%} + }, + onSubmit: doSubmit + }) + return ( - -
-{%for field in fields%} -
- {%if field.fieldType == "STRING" or field.fieldType == "NULL_STRING" %} - + {%for field in fields%} +
+ {%if field.fieldType == "STRING" or field.fieldType == "NULL_STRING" %} + {%elif field.fieldType == "INT" or field.fieldType == "NULL_INT" %} - + {%elif field.fieldType == "TEXT" or field.fieldType == "NULL_TEXT" %} - +