From c60fae6df44663c35a4c27a4e9416444d8cfb86e Mon Sep 17 00:00:00 2001 From: Wouter Nederhof Date: Fri, 15 Mar 2024 21:04:00 +0100 Subject: [PATCH] Fix tests, add home page, show alert if log in failed, fix login and registration flow --- internal/cli/arg_parser.go | 16 ++-- internal/cli/cli.go | 18 ++-- pkg/generator/attributes.go | 2 +- pkg/generator/attributes_test.go | 2 +- .../components/app/auth/Login.test.tsx.peb | 78 ++++----------- .../components/app/auth/Register.test.tsx.peb | 74 +++------------ .../app/auth/RegisterSuccess.test.tsx.peb | 8 -- .../src/components/app/auth/Login.tsx.peb | 94 ++++++++----------- .../src/components/app/auth/Register.tsx.peb | 90 +++++++----------- .../app/auth/RegisterSuccess.tsx.peb | 5 - .../src/pages/auth/login.tsx.peb | 12 ++- .../src/pages/auth/register-success.tsx.peb | 8 -- .../src/pages/auth/register.tsx.peb | 12 ++- .../security/WebSecurityConfig.kt.peb | 44 ++++----- ...ionPrefix]__create_[nameSnakeCase].sql.peb | 3 +- .../schema/[namePluralCamelCase].graphqls.peb | 2 +- .../new/[artifactId]-server/pom.xml.peb | 4 - .../[namePascalCase]Form.test.tsx.peb | 37 ++++++++ .../[namePascalCase]List.test.tsx.peb | 16 ++++ .../[namePascalCase]Details.tsx.peb | 26 ++--- .../[namePascalCase]Form.tsx.peb | 48 +++++----- .../[namePascalCase]List.tsx.peb | 26 ++--- .../edit.tsx.peb | 1 - .../index.tsx.peb | 1 - .../pages/[namePluralKebabCase]/index.tsx.peb | 1 - .../pages/[namePluralKebabCase]/new.tsx.peb | 1 - .../src/components/Form.tsx.peb | 6 ++ .../components/layout/DefaultLayout.tsx.peb | 2 - .../[artifactId]-web/src/pages/index.tsx.peb | 3 + 29 files changed, 286 insertions(+), 354 deletions(-) delete mode 100644 pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/RegisterSuccess.test.tsx.peb delete mode 100644 pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/RegisterSuccess.tsx.peb delete mode 100644 pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register-success.tsx.peb create mode 100644 pkg/generator/templates/react-frontend/[artifactId]-web/src/pages/index.tsx.peb diff --git a/internal/cli/arg_parser.go b/internal/cli/arg_parser.go index 0bd8a517..fd36dbda 100644 --- a/internal/cli/arg_parser.go +++ b/internal/cli/arg_parser.go @@ -7,19 +7,19 @@ import ( "strings" ) -func parseArgs(cliArgs cli.Args) (generator.Model, error) { +func parseArgs(cliArgs cli.Args, minArgs int) (generator.Model, error) { args := make([]string, cliArgs.Len()) for i := 0; i < cliArgs.Len(); i++ { args[i] = cliArgs.Get(i) } - return parseArgsFromStrings(args) + return parseArgsFromStrings(args, minArgs) } -func parseArgsFromStrings(args []string) (generator.Model, error) { +func parseArgsFromStrings(args []string, minArgs int) (generator.Model, error) { if len(args) < 2 { return generator.Model{}, errors.New("usage: (:)+") } - attributes, err := parseAttributeArgsFromStrings(args[1:]) + attributes, err := parseAttributeArgsFromStrings(args[1:], minArgs) if err != nil { return generator.Model{}, err } @@ -29,17 +29,17 @@ func parseArgsFromStrings(args []string) (generator.Model, error) { }, nil } -func parseAttributeArgs(cliArgs cli.Args) ([]generator.ModelAttribute, error) { +func parseAttributeArgs(cliArgs cli.Args, minArgs int) ([]generator.ModelAttribute, error) { // TODO test separately args := make([]string, cliArgs.Len()) for i := 0; i < cliArgs.Len(); i++ { args[i] = cliArgs.Get(i) } - return parseAttributeArgsFromStrings(args) + return parseAttributeArgsFromStrings(args, minArgs) } -func parseAttributeArgsFromStrings(attributeArgs []string) ([]generator.ModelAttribute, error) { - if len(attributeArgs) < 1 { +func parseAttributeArgsFromStrings(attributeArgs []string, minArgs int) ([]generator.ModelAttribute, error) { + if len(attributeArgs) < minArgs { return []generator.ModelAttribute{}, errors.New("usage: (:)+") } attributeCount := len(attributeArgs) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 49f436ef..bca99cd0 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -43,7 +43,7 @@ func Run(args []string) error { Aliases: []string{"bes"}, Usage: "Backend Scaffold", Action: func(c *cli.Context) error { - model, err := parseArgs(c.Args()) + model, err := parseArgs(c.Args(), 1) if err != nil { return err } @@ -55,7 +55,7 @@ func Run(args []string) error { Aliases: []string{"bem"}, Usage: "Model files, including migration script, entity and repository", Action: func(c *cli.Context) error { - model, err := parseArgs(c.Args()) + model, err := parseArgs(c.Args(), 1) if err != nil { return err } @@ -67,7 +67,7 @@ func Run(args []string) error { Aliases: []string{"bea"}, Usage: "GraphQL API (schema and resolvers)", Action: func(c *cli.Context) error { - model, err := parseArgs(c.Args()) + model, err := parseArgs(c.Args(), 1) if err != nil { return err } @@ -79,7 +79,7 @@ func Run(args []string) error { Aliases: []string{"bsv"}, Usage: "Service between API and repository", Action: func(c *cli.Context) error { - model, err := parseArgs(c.Args()) + model, err := parseArgs(c.Args(), 1) if err != nil { return err } @@ -102,7 +102,7 @@ func Run(args []string) error { Aliases: []string{"fes"}, Usage: "Frontend Scaffold (Generate frontend support first)", Action: func(c *cli.Context) error { - model, err := parseArgs(c.Args()) + model, err := parseArgs(c.Args(), 1) if err != nil { return err } @@ -114,7 +114,7 @@ func Run(args []string) error { Aliases: []string{"s"}, Usage: "Backend and Frontend Scaffold (Generate frontend support first)", Action: func(c *cli.Context) error { - model, err := parseArgs(c.Args()) + model, err := parseArgs(c.Args(), 1) if err != nil { return err } @@ -126,7 +126,7 @@ func Run(args []string) error { Aliases: []string{"ba"}, Usage: "Backend Authentication - EXPERIMENTAL", Action: func(c *cli.Context) error { - model, err := parseAttributeArgs(c.Args()) + model, err := parseAttributeArgs(c.Args(), 0) if err != nil { return err } @@ -138,7 +138,7 @@ func Run(args []string) error { Aliases: []string{"fa"}, Usage: "Frontend Authentication - EXPERIMENTAL", Action: func(c *cli.Context) error { - model, err := parseAttributeArgs(c.Args()) + model, err := parseAttributeArgs(c.Args(), 0) if err != nil { return err } @@ -150,7 +150,7 @@ func Run(args []string) error { Aliases: []string{"a"}, Usage: "Authentication - EXPERIMENTAL", Action: func(c *cli.Context) error { - model, err := parseAttributeArgs(c.Args()) + model, err := parseAttributeArgs(c.Args(), 0) if err != nil { return err } diff --git a/pkg/generator/attributes.go b/pkg/generator/attributes.go index 14107130..bafd5381 100644 --- a/pkg/generator/attributes.go +++ b/pkg/generator/attributes.go @@ -364,7 +364,7 @@ func provideReactTemplateExpectedContextAttributes(context map[string]interface{ case NULL_BOOLEAN: context["fieldFrontendExpectedValue"] = "true" case RELATIONAL: - context["fieldFrontendExpectedValue"] = "1" + context["fieldFrontendExpectedValue"] = "'1'" default: panic("Undetermined attribute type.") } diff --git a/pkg/generator/attributes_test.go b/pkg/generator/attributes_test.go index e37f66f5..da87c851 100644 --- a/pkg/generator/attributes_test.go +++ b/pkg/generator/attributes_test.go @@ -267,7 +267,7 @@ func TestProvideFieldNameContextAttributes(t *testing.T) { assert.Equal(t, "'Some streetName'", fields[7]["fieldFrontendExpectedValue"]) assert.Equal(t, "'2000-01-01'", fields[8]["fieldFrontendExpectedValue"]) assert.Equal(t, "true", fields[9]["fieldFrontendExpectedValue"]) - assert.Equal(t, "1", fields[10]["fieldFrontendExpectedValue"]) + assert.Equal(t, "'1'", fields[10]["fieldFrontendExpectedValue"]) // fieldFrontendTestValue assert.Equal(t, "'Some streetName'", fields[0]["fieldFrontendTestValue"]) diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Login.test.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Login.test.tsx.peb index eb01a86e..8eebdfe7 100644 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Login.test.tsx.peb +++ b/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Login.test.tsx.peb @@ -9,7 +9,7 @@ describe('Login', () => { {{field.fieldNameCamelCase}}: {{field.fieldFrontendTestValue}}{%endfor%} } - it('renders form containing the necessary fields', () => { + it('has a header', () => { const mockClient: Partial = { executeQuery: jest.fn(), executeMutation: jest.fn(), @@ -17,75 +17,35 @@ describe('Login', () => { } render( - - - ){%for field in fields%} - expect(screen.queryByText('{{ field.fieldNamePascalCase }}')).not.toBeNull(){%endfor%} - }) - - it('renders form containing existing data if id is passed', () => { - const executeQuery = jest.fn() - executeQuery.mockReturnValue( - fromValue({ - data: { - user{%for field in fields%}{%if field.isFieldRelational%}, - {{field.fieldTypePluralCamelCase}}: [{ id: 1 }]{%endif%}{%endfor%} - } - }) - ) - const mockClient: Partial = { - executeQuery, - executeMutation: jest.fn(), - executeSubscription: jest.fn(), - } - render( - - + ) - const expectedFormValues = { - ...user, - id: undefined{%for field in fields%}{%if field.isFieldRelational%}, - {{field.fieldNameCamelCase}}: '1'{%endif%}{%endfor%} - } - delete expectedFormValues.id - expect(screen.getByRole('form')).toHaveFormValues(expectedFormValues) + expect(screen.queryAllByText('Login Form')).toHaveLength(1) }) - it('calls a mutation if Save is clicked if there is an id', async () => { - const executeQuery = jest.fn() - const executeMutation = jest.fn() - executeQuery.mockReturnValue( - fromValue({ - data: { user }, - }), - ) - executeMutation.mockReturnValue( - fromValue({ - data: {}, - }) - ) + it('renders form containing the necessary fields', () => { const mockClient: Partial = { - executeQuery, - executeMutation, + executeQuery: jest.fn(), + executeMutation: jest.fn(), executeSubscription: jest.fn(), } - const saveFn = jest.fn() render( - + - ) - fireEvent.click(screen.getByText('Save')) - await waitFor(() => expect(mockClient.executeMutation).toBeCalled()) - await waitFor(() => expect(saveFn).toBeCalled()) + ){%for field in fields%} + expect(screen.queryByText('{{ field.fieldNamePascalCase }}')).not.toBeNull(){%endfor%} }) - it('calls a mutation if Save is clicked if there is no id', async () => { + it('calls a mutation if Login is clicked', async () => { const executeMutation = jest.fn() executeMutation.mockReturnValue( fromValue({ - data: {}, + data: { + login: { + id: 1 + } + } }) ) const mockClient: Partial = { @@ -93,14 +53,14 @@ describe('Login', () => { executeMutation, executeSubscription: jest.fn(), } - const saveFn = jest.fn() + const loginFn = jest.fn() render( -
+ ) - fireEvent.click(screen.getByText('Save')) + fireEvent.click(screen.getByText('Login')) await waitFor(() => expect(mockClient.executeMutation).toBeCalled()) - await waitFor(() => expect(saveFn).toBeCalled()) + await waitFor(() => expect(loginFn).toBeCalled()) }) }) diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Register.test.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Register.test.tsx.peb index 430e6e35..e3333a6c 100644 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Register.test.tsx.peb +++ b/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/Register.test.tsx.peb @@ -9,7 +9,7 @@ describe('Register', () => { {{field.fieldNameCamelCase}}: {{field.fieldFrontendTestValue}}{%endfor%} } - it('renders form containing the necessary fields', () => { + it('has a header', () => { const mockClient: Partial = { executeQuery: jest.fn(), executeMutation: jest.fn(), @@ -17,75 +17,31 @@ describe('Register', () => { } render( - - - ){%for field in fields%} - expect(screen.queryByText('{{ field.fieldNamePascalCase }}')).not.toBeNull(){%endfor%} - }) - - it('renders form containing existing data if id is passed', () => { - const executeQuery = jest.fn() - executeQuery.mockReturnValue( - fromValue({ - data: { - user{%for field in fields%}{%if field.isFieldRelational%}, - {{field.fieldTypePluralCamelCase}}: [{ id: 1 }]{%endif%}{%endfor%} - } - }) - ) - const mockClient: Partial = { - executeQuery, - executeMutation: jest.fn(), - executeSubscription: jest.fn(), - } - render( - - + ) - const expectedFormValues = { - ...user, - id: undefined{%for field in fields%}{%if field.isFieldRelational%}, - {{field.fieldNameCamelCase}}: '1'{%endif%}{%endfor%} - } - delete expectedFormValues.id - expect(screen.getByRole('form')).toHaveFormValues(expectedFormValues) + expect(screen.queryAllByText('Registration Form')).toHaveLength(1) }) - it('calls a mutation if Save is clicked if there is an id', async () => { - const executeQuery = jest.fn() - const executeMutation = jest.fn() - executeQuery.mockReturnValue( - fromValue({ - data: { user }, - }), - ) - executeMutation.mockReturnValue( - fromValue({ - data: {}, - }) - ) + it('renders form containing the necessary fields', () => { const mockClient: Partial = { - executeQuery, - executeMutation, + executeQuery: jest.fn(), + executeMutation: jest.fn(), executeSubscription: jest.fn(), } - const saveFn = jest.fn() render( - + - ) - fireEvent.click(screen.getByText('Save')) - await waitFor(() => expect(mockClient.executeMutation).toBeCalled()) - await waitFor(() => expect(saveFn).toBeCalled()) + ){%for field in fields%} + expect(screen.queryByText('{{ field.fieldNamePascalCase }}')).not.toBeNull(){%endfor%} }) - it('calls a mutation if Save is clicked if there is no id', async () => { + it('calls a mutation if Register is clicked', async () => { const executeMutation = jest.fn() executeMutation.mockReturnValue( fromValue({ - data: {}, + data: {} }) ) const mockClient: Partial = { @@ -93,14 +49,14 @@ describe('Register', () => { executeMutation, executeSubscription: jest.fn(), } - const saveFn = jest.fn() + const registerFn = jest.fn() render( - + ) - fireEvent.click(screen.getByText('Save')) + fireEvent.click(screen.getByText('Register')) await waitFor(() => expect(mockClient.executeMutation).toBeCalled()) - await waitFor(() => expect(saveFn).toBeCalled()) + await waitFor(() => expect(registerFn).toBeCalled()) }) }) diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/RegisterSuccess.test.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/RegisterSuccess.test.tsx.peb deleted file mode 100644 index 4387737d..00000000 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/auth/RegisterSuccess.test.tsx.peb +++ /dev/null @@ -1,8 +0,0 @@ -import { RegisterSuccess } from '@components/app/auth/RegisterSuccess' -import { render } from '@testing-library/react' - -describe('RegisterSuccess', () => { - it('renders without errors', () => { - render() - }) -}) diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Login.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Login.tsx.peb index 61b4f679..dfde9b54 100644 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Login.tsx.peb +++ b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Login.tsx.peb @@ -1,12 +1,14 @@ +import { Header } from '@components/Header' import { gql, OperationResult, useMutation{%if hasRelations%}, useQuery{%endif%} } from 'urql'{%if hasRelations%} import { useMemo } from 'react'{%endif%} import { Mutation{%if hasRelations%}, Query{%endif%}{%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 { Form } from '@components/Form' export interface LoginProps { {%for field in fields%}{%if field.isFieldRelational%} {{ field.fieldNameCamelCase }}?: string{%endif%}{%endfor%} - onSave: () => void - onCancel: () => void + onLogin?: () => void + onCancel?: () => void } interface LoginFormData {{ "{" }}{%for field in fields%} @@ -26,21 +28,23 @@ export function Login(props: LoginProps) { if (result.error) { alert(`An error occurred: ${result.error.message}`) } + if (!result.data?.login) { + alert('Could not login.') + return false + } return !result.error } const doSubmit = ( formData: LoginFormData, - { setSubmitting }: FormikHelpers ) => { {%if hasRelations%} const formDataWithRelations = { ...formData }{%endif%}{%for field in fields%}{%if field.isFieldRelational%} if (props.{{ field.fieldNameCamelCase }}) { formData{%if hasRelations%}WithRelations{%endif%}.{{ field.fieldNameCamelCase }} = props.{{ field.fieldNameCamelCase }} }{%endif%}{%endfor%} executeLoginMutation({ input: formData{%if hasRelations%}WithRelations{%endif%} }) - .then(result => handleMutationResult(result) && props.onSave()) - .catch((reason) => alert(`Creating user failed. Reason: ${reason}`)) - .finally(() => setSubmitting(false)) + .then(result => handleMutationResult(result) && props.onLogin?.()) + .catch((reason) => alert(`Logging in failed. Reason: ${reason}`)) } {%for field in fields%}{%if field.isFieldRelational%} const [{{ field.fieldTypePluralCamelCase }}QueryResult] = useQuery({ @@ -59,61 +63,41 @@ export function Login(props: LoginProps) { ), }) {%endif%}{%endfor%} + const formik = useFormik({ + initialValues: {{ "{" }}{%for field in fields%} + {{ 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%} + }, + enableReinitialize: true, + onSubmit: doSubmit + }) + return ( - - -{%for field in fields%} -
- {%if field.fieldType == "STRING" or field.fieldType == "NULL_STRING" %} - + <> +
Login Form
+ {%for field in fields%} +{%if field.fieldType == "RELATIONAL" %}{props.{{ field.fieldNameCamelCase }} ? <> : {%endif%}{%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" %} - + {%elif field.fieldType == "INT" or field.fieldType == "NULL_INT" %} - + {%elif field.fieldType == "DATE" or field.fieldType == "NULL_DATE" %} - + {%elif field.fieldType == "BOOLEAN" or field.fieldType == "NULL_BOOLEAN" %} - + {%elif field.fieldType == "RELATIONAL" %} - -{%endif%}
{%endfor%} - - + + + {{ "{" }}{{ field.fieldTypePluralCamelCase }}QueryResult?.data?.{{ field.fieldTypePluralCamelCase }}?.map(({{ field.fieldTypeCamelCase }}: {{ field.fieldTypePascalCase }}) => + {{ "{" }}{{ field.fieldTypeCamelCase }}.id} + )} + +{%endif%} {%if field.fieldType == "RELATIONAL" %}{{ "}" }}{%endif%}{%endfor%} + Login + {props.onCancel && props.onCancel?.()}>Cancel} -
+ ) } diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Register.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Register.tsx.peb index 65b96803..b3af6b44 100644 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Register.tsx.peb +++ b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/Register.tsx.peb @@ -1,12 +1,14 @@ +import { Header } from '@components/Header' import { gql, OperationResult, useMutation{%if hasRelations%}, useQuery{%endif%} } from 'urql'{%if hasRelations%} import { useMemo } from 'react'{%endif%} import { Mutation{%if hasRelations%}, Query{%endif%}{%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 { Form } from '@components/Form' export interface RegisterProps { {%for field in fields%}{%if field.isFieldRelational%} {{ field.fieldNameCamelCase }}?: string{%endif%}{%endfor%} - onSave: () => void - onCancel: () => void + onRegister?: () => void + onCancel?: () => void } interface RegisterFormData {{ "{" }}{%for field in fields%} @@ -30,17 +32,15 @@ export function Register(props: RegisterProps) { } const doSubmit = ( - formData: RegisterFormData, - { setSubmitting }: FormikHelpers + formData: RegisterFormData ) => { {%if hasRelations%} const formDataWithRelations = { ...formData }{%endif%}{%for field in fields%}{%if field.isFieldRelational%} if (props.{{ field.fieldNameCamelCase }}) { formData{%if hasRelations%}WithRelations{%endif%}.{{ field.fieldNameCamelCase }} = props.{{ field.fieldNameCamelCase }} }{%endif%}{%endfor%} executeRegisterMutation({ input: formData{%if hasRelations%}WithRelations{%endif%} }) - .then(result => handleMutationResult(result) && props.onSave()) + .then(result => handleMutationResult(result) && props.onRegister?.()) .catch((reason) => alert(`Creating user failed. Reason: ${reason}`)) - .finally(() => setSubmitting(false)) } {%for field in fields%}{%if field.isFieldRelational%} const [{{ field.fieldTypePluralCamelCase }}QueryResult] = useQuery({ @@ -59,61 +59,41 @@ export function Register(props: RegisterProps) { ), }) {%endif%}{%endfor%} + const formik = useFormik({ + initialValues: {{ "{" }}{%for field in fields%} +{{ 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%} + }, + enableReinitialize: true, + onSubmit: doSubmit + }) + return ( - -
-{%for field in fields%} -
- {%if field.fieldType == "STRING" or field.fieldType == "NULL_STRING" %} - + <> +
Register
+ {%for field in fields%} +{%if field.fieldType == "RELATIONAL" %}{props.{{ field.fieldNameCamelCase }} ? <> : {%endif%}{%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" %} - + {%elif field.fieldType == "INT" or field.fieldType == "NULL_INT" %} - + {%elif field.fieldType == "DATE" or field.fieldType == "NULL_DATE" %} - + {%elif field.fieldType == "BOOLEAN" or field.fieldType == "NULL_BOOLEAN" %} - + {%elif field.fieldType == "RELATIONAL" %} - -{%endif%}
{%endfor%} - - + + + {{ "{" }}{{ field.fieldTypePluralCamelCase }}QueryResult?.data?.{{ field.fieldTypePluralCamelCase }}?.map(({{ field.fieldTypeCamelCase }}: {{ field.fieldTypePascalCase }}) => + {{ "{" }}{{ field.fieldTypeCamelCase }}.id} + )} + +{%endif%} {%if field.fieldType == "RELATIONAL" %}{{ "}" }}{%endif%}{%endfor%} + Register + {props.onCancel && props.onCancel?.()}>Cancel}
-
+ ) } diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/RegisterSuccess.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/RegisterSuccess.tsx.peb deleted file mode 100644 index bd38af25..00000000 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/components/app/auth/RegisterSuccess.tsx.peb +++ /dev/null @@ -1,5 +0,0 @@ -export const RegisterSuccess = () => ( -
- Registration completed. -
-) diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/login.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/login.tsx.peb index 65b7ffd6..99ccd224 100644 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/login.tsx.peb +++ b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/login.tsx.peb @@ -1,8 +1,12 @@ import { Login } from '@components/app/auth/Login' +import { useRouter } from 'next/router' export default function LoginPage() { - return <> -

Login

- - + const router = useRouter() + return ( + router.push('/')} + onLogin={() => router.push('/')} + /> + ) } diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register-success.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register-success.tsx.peb deleted file mode 100644 index 9bb4bfd2..00000000 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register-success.tsx.peb +++ /dev/null @@ -1,8 +0,0 @@ -import { RegisterSuccess } from '@components/app/auth/RegisterSuccess' - -export default function LoginPage() { - return <> -

Login

- - -} diff --git a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register.tsx.peb b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register.tsx.peb index 3a0bea44..97d8f58b 100644 --- a/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register.tsx.peb +++ b/pkg/generator/templates/auth-frontend/[artifactId]-web/src/pages/auth/register.tsx.peb @@ -1,8 +1,12 @@ import { Register } from '@components/app/auth/Register' +import { useRouter } from 'next/router' export default function LoginPage() { - return <> -

Register

- - + const router = useRouter() + return ( + router.push('/auth/login')} + onRegister={() => router.push('/auth/login')} + /> + ) } diff --git a/pkg/generator/templates/auth/[artifactId]-server/src/main/kotlin/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.kt.peb b/pkg/generator/templates/auth/[artifactId]-server/src/main/kotlin/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.kt.peb index 3e92a183..8e61a235 100644 --- a/pkg/generator/templates/auth/[artifactId]-server/src/main/kotlin/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.kt.peb +++ b/pkg/generator/templates/auth/[artifactId]-server/src/main/kotlin/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.kt.peb @@ -5,35 +5,35 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.authentication.AuthenticationProvider -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter -import org.springframework.security.config.http.SessionCreationPolicy -import org.springframework.web.cors.CorsConfiguration -import org.springframework.web.cors.UrlBasedCorsConfigurationSource -import org.springframework.web.filter.CorsFilter +import org.springframework.security.config.http.SessionCreationPolicy.IF_REQUIRED +import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) +@EnableMethodSecurity(prePostEnabled = true) @EnableConfigurationProperties(SecurityProperties::class) class WebSecurityConfig( private val authenticationProvider: AuthenticationProvider, -) : WebSecurityConfigurerAdapter() { - - override fun configure(auth: AuthenticationManagerBuilder) { - auth.authenticationProvider(authenticationProvider) - } - - override fun configure(http: HttpSecurity) { - http - .authorizeRequests() - .antMatchers("/graphql").permitAll() - .and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and() - .csrf().disable() +) { + @Bean + fun filterChain(http: HttpSecurity): SecurityFilterChain { + return http + .authenticationProvider(authenticationProvider) + .cors { it.disable() } + .csrf { it.disable() } + .authorizeHttpRequests { + it + .requestMatchers("/graphql").permitAll() + .requestMatchers("/actuator/health").permitAll() + .requestMatchers("/subscriptions").permitAll() + .requestMatchers("/graphiql/**").permitAll() + .requestMatchers("/api/**").permitAll() + } + .sessionManagement { it.sessionCreationPolicy(IF_REQUIRED) } + .securityContext { it.requireExplicitSave(false) } + .build() } - } diff --git a/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb b/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb index af9b8433..0aed9ded 100644 --- a/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb +++ b/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb @@ -2,5 +2,6 @@ CREATE TABLE users ( id BIGSERIAL PRIMARY KEY{%for field in fields%}, {%if field.fieldNameCamelCase != "passwordHash" %}{{ field.fieldNameSnakeCase }}{%else%}password_hash{%endif%} {{ field.fieldDatabaseDefinitionType }}{%endfor%}, created_at TIMESTAMP, - updated_at TIMESTAMP + updated_at TIMESTAMP, + UNIQUE(username) ); diff --git a/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb b/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb index b410c792..46d57cfc 100644 --- a/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb +++ b/pkg/generator/templates/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb @@ -5,7 +5,7 @@ extend type Query { } extend type Mutation { - login(input: LoginInput!): User! + login(input: LoginInput!): User logout: Boolean! register(input: RegisterInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User diff --git a/pkg/generator/templates/new/[artifactId]-server/pom.xml.peb b/pkg/generator/templates/new/[artifactId]-server/pom.xml.peb index 712832af..f4384835 100644 --- a/pkg/generator/templates/new/[artifactId]-server/pom.xml.peb +++ b/pkg/generator/templates/new/[artifactId]-server/pom.xml.peb @@ -91,10 +91,6 @@ org.springframework.boot spring-boot-starter-data-jdbc - - org.springframework.boot - spring-boot-starter-web - org.springframework.boot spring-boot-starter-validation diff --git a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb index 1ea2eb74..e5b78e03 100644 --- a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb +++ b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]Form.test.tsx.peb @@ -10,6 +10,43 @@ describe('{{ namePascalCase }}Form', () => { {{field.fieldNameCamelCase}}: {{field.fieldFrontendTestValue}}{%endfor%} } + it('has an appropriate header if no id is provided', () => { + const mockClient: Partial = { + executeQuery: jest.fn(), + executeMutation: jest.fn(), + executeSubscription: jest.fn(), + } + render( + + <{{ namePascalCase }}Form /> + + ) + expect(screen.queryAllByText('New {{ namePascalCase }}')).toHaveLength(1) + }) + + it('has an appropriate header if an id is provided', () => { + const executeQuery = jest.fn() + executeQuery.mockReturnValue( + fromValue<{ data: {{ namePascalCase }}FormQuery }>({ + data: { + {{ nameCamelCase }}{%for field in fields%}{%if field.isFieldRelational%}, + {{field.fieldTypePluralCamelCase}}: [{ id: 1 }]{%endif%}{%endfor%} + } + }) + ) + const mockClient: Partial = { + executeQuery, + executeMutation: jest.fn(), + executeSubscription: jest.fn(), + } + render( + + <{{ namePascalCase }}Form id="1" onSave={jest.fn()} onCancel={jest.fn()} /> + + ) + expect(screen.queryAllByText('Edit {{ namePascalCase }}')).toHaveLength(1) + }) + it('renders form containing the necessary fields', () => { const mockClient: Partial = { executeQuery: jest.fn(), diff --git a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]List.test.tsx.peb b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]List.test.tsx.peb index bd7f9ab6..dcc4129b 100644 --- a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]List.test.tsx.peb +++ b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/[underscore][underscore]tests[underscore][underscore]/components/app/[nameKebabCase]/[namePascalCase]List.test.tsx.peb @@ -11,6 +11,22 @@ describe('{{ namePascalCase }}List', () => { {{field.fieldNameCamelCase}}: {{field.fieldFrontendTestValue}}{%endfor%} } + it('has a header', () => { + const mockClient: Partial = { + executeQuery: jest.fn(), + executeMutation: jest.fn(), + executeSubscription: jest.fn(), + } + act(() => { + render( + + <{{ namePascalCase }}List /> + + ) + }) + expect(screen.queryAllByText('List of {{ namePluralPascalCase }}')).toHaveLength(1) + }) + it('forwards to {{ nameCamelCase }} page on Show', () => { const executeQuery = jest.fn() executeQuery.mockReturnValue( 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 71eb02e7..8e3835a5 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,3 +1,4 @@ +import { Header } from '@components/Header' import { gql, useQuery } from 'urql' import { useMemo } from 'react' import { Query } from '@generated/graphql' @@ -37,16 +38,19 @@ export function {{ namePascalCase }}Details(props: {{ namePascalCase }}DetailsPr } return ( -
{%for field in fields%} - - {{ "{" }}data.{{ nameCamelCase }}?.{{ field.fieldNameCamelCase }}{%if field.fieldType == "BOOLEAN" or field.fieldType == "NULL_BOOLEAN"%} ? 'Yes' : 'No'{%endif%}{{ "}" }} - {%endfor%} - - {{ "{" }}data.{{ nameCamelCase }}?.createdAt{{ "}" }} - - - {{ "{" }}data.{{ nameCamelCase }}?.updatedAt{{ "}" }} - -
+ <> +
{{ namePascalCase }} Details
+
{%for field in fields%} + + {{ "{" }}data.{{ nameCamelCase }}?.{{ field.fieldNameCamelCase }}{%if field.fieldType == "BOOLEAN" or field.fieldType == "NULL_BOOLEAN"%} ? 'Yes' : 'No'{%endif%}{{ "}" }} + {%endfor%} + + {{ "{" }}data.{{ nameCamelCase }}?.createdAt{{ "}" }} + + + {{ "{" }}data.{{ nameCamelCase }}?.updatedAt{{ "}" }} + +
+ ) } 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 50c83543..2dcbcb3a 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,3 +1,4 @@ +import { Header } from '@components/Header' 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' @@ -9,8 +10,8 @@ import { Form } from '@components/Form' export interface {{ namePascalCase }}FormProps { id?: string | number{%for field in fields%}{%if field.isFieldRelational%} {{ field.fieldNameCamelCase }}?: string{%endif%}{%endfor%} - onSave: () => void - onCancel: () => void + onSave?: () => void + onCancel?: () => void } interface {{ namePascalCase }}FormData {{ "{" }}{%for field in fields%} @@ -46,7 +47,7 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { ) => { if (props.id) { executeUpdateMutation({ id: props.id, input: formData }) - .then(result => handleMutationResult(result) && props.onSave()) + .then(result => handleMutationResult(result) && props.onSave?.()) .catch((reason) => alert(`Updating {{ nameCamelCase }} failed. Reason: ${reason}`)) } else {{ "{" }}{%if hasRelations%} const formDataWithRelations = { ...formData }{%endif%}{%for field in fields%}{%if field.isFieldRelational%} @@ -54,7 +55,7 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { formData{%if hasRelations%}WithRelations{%endif%}.{{ field.fieldNameCamelCase }} = props.{{ field.fieldNameCamelCase }} }{%endif%}{%endfor%} executeCreateMutation({ input: formData{%if hasRelations%}WithRelations{%endif%} }) - .then(result => handleMutationResult(result) && props.onSave()) + .then(result => handleMutationResult(result) && props.onSave?.()) .catch((reason) => alert(`Creating {{ nameCamelCase }} failed. Reason: ${reason}`)) } } @@ -96,7 +97,7 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { {%endif%}{%endfor%} 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%} + {{ 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%} }, enableReinitialize: true, onSubmit: doSubmit @@ -113,29 +114,32 @@ export function {{ namePascalCase }}Form(props: {{ namePascalCase }}FormProps) { } return ( -
{%for field in fields%} - {%if field.fieldType == "RELATIONAL" %}{props.{{ field.fieldNameCamelCase }} ? <> : {%endif%}{%if field.fieldType == "STRING" or field.fieldType == "NULL_STRING" %} - + <> +
{props.id ? 'Edit {{ namePascalCase }}' : 'New {{ namePascalCase }}'}
+ {%for field in fields%} + {%if field.fieldType == "RELATIONAL" %}{props.{{ field.fieldNameCamelCase }} ? <> : {%endif%}{%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" %} - + {%elif field.fieldType == "INT" or field.fieldType == "NULL_INT" %} - + {%elif field.fieldType == "DATE" or field.fieldType == "NULL_DATE" %} - + {%elif field.fieldType == "BOOLEAN" or field.fieldType == "NULL_BOOLEAN" %} - + {%elif field.fieldType == "RELATIONAL" %} - - + + {{ "{" }}{{ field.fieldTypePluralCamelCase }}QueryResult?.data?.{{ field.fieldTypePluralCamelCase }}?.map(({{ field.fieldTypeCamelCase }}: {{ field.fieldTypePascalCase }}) => - {{ "{" }}{{ field.fieldTypeCamelCase }}.id} - )} - -{%endif%} {%if field.fieldType == "RELATIONAL" %}{{ "}" }}{%endif%}{%endfor%} - Save - Cancel - + {{ "{" }}{{ field.fieldTypeCamelCase }}.id} + )} + +{%endif%}
{%if field.fieldType == "RELATIONAL" %}{{ "}" }}{%endif%}{%endfor%} + Save + {props.onCancel && props.onCancel?.()}>Cancel} + + ) } diff --git a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]List.tsx.peb b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]List.tsx.peb index c9b98879..51ee107c 100644 --- a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]List.tsx.peb +++ b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/components/app/[nameKebabCase]/[namePascalCase]List.tsx.peb @@ -1,3 +1,4 @@ +import { Header } from '@components/Header' import { gql, useMutation, useQuery } from 'urql' import { useMemo } from 'react' import { Mutation, Query } from '@generated/graphql' @@ -55,16 +56,19 @@ export function {{ namePascalCase }}List({%if hasRelations%}props: {{ namePascal } return ( - ({ - showHref: `/{{ namePluralKebabCase }}/${values.id}`, - editHref: `/{{ namePluralKebabCase }}/${values.id}/edit`, - onDeleteClick: () => handleDelete(values.id), - })} - /> + <> +
List of {{ namePluralPascalCase }}
+ ({ + showHref: `/{{ namePluralKebabCase }}/${values.id}`, + editHref: `/{{ namePluralKebabCase }}/${values.id}/edit`, + onDeleteClick: () => handleDelete(values.id), + })} + /> + ) } diff --git a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/pages/[namePluralKebabCase]/[squareBracketLeft][nameCamelCase]Id[squareBracketRight]/edit.tsx.peb b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/pages/[namePluralKebabCase]/[squareBracketLeft][nameCamelCase]Id[squareBracketRight]/edit.tsx.peb index 989ca2cb..c81ceaec 100644 --- a/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/pages/[namePluralKebabCase]/[squareBracketLeft][nameCamelCase]Id[squareBracketRight]/edit.tsx.peb +++ b/pkg/generator/templates/react-frontend-scaffold/[artifactId]-web/src/pages/[namePluralKebabCase]/[squareBracketLeft][nameCamelCase]Id[squareBracketRight]/edit.tsx.peb @@ -7,7 +7,6 @@ export default function Show{{namePascalCase}}() { const { {{nameCamelCase}}Id } = router.query return ( ) => { ) } +Form.Password = (props: React.InputHTMLAttributes) => { + return ( + + ) +} + Form.Integer = (props: React.InputHTMLAttributes) => { return ( diff --git a/pkg/generator/templates/react-frontend/[artifactId]-web/src/components/layout/DefaultLayout.tsx.peb b/pkg/generator/templates/react-frontend/[artifactId]-web/src/components/layout/DefaultLayout.tsx.peb index f715c975..62fc3840 100644 --- a/pkg/generator/templates/react-frontend/[artifactId]-web/src/components/layout/DefaultLayout.tsx.peb +++ b/pkg/generator/templates/react-frontend/[artifactId]-web/src/components/layout/DefaultLayout.tsx.peb @@ -10,7 +10,6 @@ export interface Breadcrumb { export interface DefaultLayoutProps { breadcrumbs?: Breadcrumb[] children?: ReactNode - title: string } export function DefaultLayout(props: DefaultLayoutProps) { @@ -26,7 +25,6 @@ export function DefaultLayout(props: DefaultLayoutProps) { ))} )} -
{props.title}
{props.children} ) diff --git a/pkg/generator/templates/react-frontend/[artifactId]-web/src/pages/index.tsx.peb b/pkg/generator/templates/react-frontend/[artifactId]-web/src/pages/index.tsx.peb new file mode 100644 index 00000000..23cf46ff --- /dev/null +++ b/pkg/generator/templates/react-frontend/[artifactId]-web/src/pages/index.tsx.peb @@ -0,0 +1,3 @@ +export default function Index() { + return
Welcome to {{ artifactId }}!
+}