-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Set up type safe client server communication (#201)
### Summary & Motivation We want the front-end development environment to use modern React APIs, adding the accessibility and styling ergonomics while keeping type safety when calling the API. Accessibility can be difficult to add if it's an afterthought. The React Aria Components are based on solid ground and years of hard work and effort to create the best in class components. Forms in React have always been hard, often involving a ton of libraries - we decided to go for the latest React APIs `useFormState` as it brings simplicity and readability. The code should feel familiar to developers using the latest React APIs in NextJS. Zod is one of the best schema validation libraries out there, providing common validations and the ability to validate and parse complex and even nested structures. Infer allows developers to infer the TypeScript type from a Zod Schema. OpenAPI-fetch is an ultra-slim and performant wrapper for fetch, enabling type-safe usage of API endpoints. Use Swashbuckle CLI to generate a swagger.json when building the WebApi, and use openapi-typescript CLI to generate a TypeScript definition file that can be used for the fetch API. * Add `WebApp.esproj` file enabling web development in Rider and Visual Studio. `.esproj` is the new project config and will be replacing `.njsproj` files * Set up build of `Api` typings, creating a generated typings file to be used by the API client * Configure `openapi-fetch` to call the `Api` * Configure React to enable `useFormState` APIs. This API is part of NextJS v14 `server actions` and an official to be React API for handling form data * Create an example using the latest React Aria Components form validation support. This greatly simplifies browser, client and server validation in forms and plays nicely with `useFormState` and `zod` * Configure CORS support in the API for development mode for now *(Planning to set up a reverse proxy)* Finally, update the GitHub workflow to install Bun and node modules, and ensure that the WebApi can generate the TypeScript type definition in the GitHub runner. ### Checklist - [x] I have added a Label to the pull-request - [x] I have added tests, and done manual regression tests - [x] I have updated the documentation, if necessary
- Loading branch information
Showing
23 changed files
with
304 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,12 +36,19 @@ jobs: | |
echo "Generated version: $VERSION" | ||
echo "version=$VERSION" >> $GITHUB_OUTPUT | ||
- name: Install Bun | ||
uses: oven-sh/setup-bun@v1 | ||
|
||
- name: Install Node modules | ||
working-directory: application/account-management/WebApp | ||
run: bun install | ||
|
||
- name: Setup .NET | ||
uses: actions/setup-dotnet@v3 | ||
with: | ||
dotnet-version: 7.0.x | ||
|
||
- name: Restore dependencies | ||
- name: Restore .NET dependencies | ||
run: dotnet restore application/PlatformPlatform.sln | ||
|
||
- name: Build | ||
|
@@ -55,6 +62,9 @@ jobs: | |
- name: Checkout code | ||
uses: actions/checkout@v3 | ||
|
||
- name: Install Bun | ||
uses: oven-sh/setup-bun@v1 | ||
|
||
- name: Install dotCover | ||
run: dotnet tool install --global JetBrains.dotCover.GlobalTool | ||
|
||
|
@@ -78,13 +88,19 @@ jobs: | |
- name: Checkout code | ||
uses: actions/checkout@v3 | ||
|
||
- name: Run code inspections | ||
uses: muno92/[email protected] | ||
- name: Install Bun | ||
uses: oven-sh/setup-bun@v1 | ||
|
||
- name: Setup .NET | ||
uses: actions/setup-dotnet@v3 | ||
with: | ||
solutionPath: application/PlatformPlatform.sln | ||
minimumSeverity: warning | ||
# Ignore cases where property getters are not called directly (e.g., on DTOs that are serialized) | ||
ignoreIssueType: UnusedAutoPropertyAccessor.Global | ||
dotnet-version: 7.0.x | ||
|
||
- name: Run code inspections | ||
working-directory: application | ||
run: | | ||
dotnet tool restore | ||
dotnet jb inspectcode PlatformPlatform.sln --build --output=result.xml --severity=WARNING | ||
account-management-api-publish: | ||
name: Account Management API Publish | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -378,3 +378,6 @@ FodyWeavers.xsd | |
|
||
# macOS files | ||
.DS_Store | ||
|
||
# Generated files | ||
**/*.generated.d.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Ignore artifacts: | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/0.5.88868-alpha"> | ||
|
||
<Target Name="EnsureApiBuildsFirst" BeforeTargets="Build"> | ||
<MSBuild Projects="../Api/Api.csproj" Targets="Build"/> | ||
</Target> | ||
|
||
<PropertyGroup> | ||
<StartupCommand>set BROWSER=none&&bun dev</StartupCommand> | ||
<JavaScriptTestRoot>src\</JavaScriptTestRoot> | ||
<JavaScriptTestFramework>Jest</JavaScriptTestFramework> | ||
<!-- Command to run on project build --> | ||
<BuildCommand>bun run build</BuildCommand> | ||
<!-- Command to create an optimized build of the project that's ready for publishing --> | ||
<ProductionBuildCommand>bun run build</ProductionBuildCommand> | ||
<!-- Folder where production build objects will be placed --> | ||
<BuildOutputFolder>$(MSBuildProjectDirectory)\dist</BuildOutputFolder> | ||
|
||
<NpmInstallCheck>$(MSBuildProjectDirectory)\bun.lockb</NpmInstallCheck> | ||
<ShouldRunNpmInstall>false</ShouldRunNpmInstall> | ||
|
||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Script Include="tsconfig.json"/> | ||
</ItemGroup> | ||
|
||
</Project> |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,7 @@ | ||
import { Button } from "react-aria-components"; | ||
import { CreateTenantForm } from "@/ui/tenant/CreateTenantForm.tsx"; | ||
|
||
function App() { | ||
return ( | ||
<div className="w-screen h-screen bg-slate-300 flex flex-col p-2"> | ||
<Button | ||
className="bg-slate-500 p-2 rounded-md text-white text-sm border border-black hover:bg-slate-400 w-fit" | ||
onPress={() => alert("Create tenant")} | ||
> | ||
Create tenant! | ||
</Button> | ||
</div> | ||
); | ||
return <CreateTenantForm />; | ||
} | ||
|
||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import createClient from "openapi-fetch"; | ||
import type { paths } from "./api.generated"; | ||
|
||
const baseUrl = "https://localhost:8443"; | ||
export const accountManagementApi = createClient<paths>({ baseUrl }); |
39 changes: 39 additions & 0 deletions
39
application/account-management/WebApp/src/lib/apiErrorListSchema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { getCamelCase } from "./getCamelCase.ts"; | ||
import type { FetchResponse } from "openapi-fetch"; | ||
import { z } from "zod"; | ||
|
||
const ApiErrorListSchema = z.array(z.object({ code: z.string(), message: z.string() })); | ||
const ApiErrorSchema = z.object({ | ||
title: z.string(), | ||
type: z.string(), | ||
status: z.number(), | ||
Errors: ApiErrorListSchema, | ||
}); | ||
type ApiErrorList = z.infer<typeof ApiErrorListSchema>; | ||
|
||
export function getApiError(response: FetchResponse<any>) { | ||
const { error = null } = response; | ||
const validatedApiError = ApiErrorSchema.safeParse(error); | ||
if (!validatedApiError.success) { | ||
return { | ||
title: "Unknown server error response", | ||
status: 0, | ||
type: "0", | ||
Errors: [], | ||
}; | ||
} | ||
return validatedApiError.data; | ||
} | ||
|
||
export function getFieldErrors(apiErrorList: ApiErrorList) { | ||
const fieldErrors: Record<string, string[]> = {}; | ||
apiErrorList.forEach((error) => { | ||
const key = getCamelCase(error.code); | ||
if (fieldErrors[key] == null) { | ||
fieldErrors[key] = []; | ||
} | ||
fieldErrors[key].push(error.message); | ||
}); | ||
console.log("api errors", { fieldErrors, apiErrorList }); | ||
return fieldErrors; | ||
} |
3 changes: 3 additions & 0 deletions
3
application/account-management/WebApp/src/lib/getCamelCase.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function getCamelCase(text: string): string { | ||
return text[0].toLowerCase() + text.slice(1); | ||
} |
44 changes: 44 additions & 0 deletions
44
application/account-management/WebApp/src/ui/tenant/CreateTenantForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { Button, FieldError, Form, Input, Label, TextField } from "react-aria-components"; | ||
import { useFormState } from "react-dom"; | ||
import { createTenant, State } from "./actions"; | ||
|
||
export function CreateTenantForm() { | ||
const initialState: State = { message: null, errors: {} }; | ||
const [state, formAction] = useFormState(createTenant, initialState); | ||
|
||
return ( | ||
<Form | ||
action={formAction} | ||
validationErrors={state.errors} | ||
className="w-screen h-screen bg-slate-900 flex flex-col p-2 justify-center items-center" | ||
> | ||
<div className="flex flex-col w-fit bg-slate-300 rounded-sm p-4 gap-2"> | ||
<h1 className="text-xl font-bold">Create a tenant</h1> | ||
<TextField name={"subdomain"} autoFocus className={"flex flex-col"} isRequired> | ||
<Label>Subdomain</Label> | ||
<Input className="p-2 rounded-md border border-black" placeholder="subdomain" /> | ||
<FieldError /> | ||
</TextField> | ||
|
||
<TextField name={"name"} type={"username"} className={"flex flex-col"} isRequired> | ||
<Label>Name</Label> | ||
<Input className="p-2 rounded-md border border-black" placeholder="name" /> | ||
<FieldError /> | ||
</TextField> | ||
|
||
<TextField name={"email"} type={"email"} className={"flex flex-col"} isRequired> | ||
<Label>Email</Label> | ||
<Input className="p-2 rounded-md border border-black" placeholder="email" /> | ||
<FieldError /> | ||
</TextField> | ||
|
||
<Button | ||
type="submit" | ||
className="bg-slate-500 p-2 rounded-md text-white text-sm border border-black hover:bg-slate-400 w-fit" | ||
> | ||
Create tenant! | ||
</Button> | ||
</div> | ||
</Form> | ||
); | ||
} |
Oops, something went wrong.