From 53469e4784e6229321bf31d51625811c56649e29 Mon Sep 17 00:00:00 2001 From: Evgeny Chaban Date: Tue, 12 Dec 2023 12:13:05 +0300 Subject: [PATCH] feat: add documentation for web (#251) --- docs/mint.json | 10 +- docs/web/calling-api.mdx | 51 ++++++++ docs/web/environment-variables.mdx | 100 +++++++++++++++ docs/web/forms.mdx | 119 ++++++++++++++++++ docs/web/overview.mdx | 40 +++--- docs/web/routing.mdx | 65 ++++++++++ docs/web/services.mdx | 59 +++++++++ docs/web/styling.mdx | 41 ++++++ template/apps/web/.storybook/preview.js | 4 +- template/apps/web/src/pages/_app/index.tsx | 6 +- .../web/src/theme/{main-theme.ts => index.ts} | 4 +- template/pnpm-lock.yaml | 3 - 12 files changed, 470 insertions(+), 32 deletions(-) create mode 100644 docs/web/calling-api.mdx create mode 100644 docs/web/environment-variables.mdx create mode 100644 docs/web/forms.mdx create mode 100644 docs/web/routing.mdx create mode 100644 docs/web/services.mdx create mode 100644 docs/web/styling.mdx rename template/apps/web/src/theme/{main-theme.ts => index.ts} (85%) diff --git a/docs/mint.json b/docs/mint.json index 6fddc913..d49c8180 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -67,7 +67,15 @@ "mailer", { "group": "Web", - "pages": ["web/overview"] + "pages": [ + "web/overview", + "web/styling", + "web/routing", + "web/calling-api", + "web/forms", + "web/services", + "web/environment-variables" + ] }, { "group": "Deployment", diff --git a/docs/web/calling-api.mdx b/docs/web/calling-api.mdx new file mode 100644 index 00000000..4a576f10 --- /dev/null +++ b/docs/web/calling-api.mdx @@ -0,0 +1,51 @@ +--- +title: "Calling API" +--- + +## Overview + +Ship uses [TanStack Query](https://tanstack.com/query/latest) with our own axios library wrapper. +All queries are located in the `/resources` folder, organized into sub-folders for each resource. + +TanStack Query helps easily fetch, cache, and update data. +Each API endpoint has a related React hook using TanStack Query. + +When getting data with **apiService** in `services/api.service.ts`, no need to add the full endpoint URL. +The axios instance already has the base URL set. + +If you need to make a request to a new endpoint, for example, for a `project` resource, +you need to add a new hook to `/resources/project/project.api.ts` + +## Examples + +```typescript resources/account/account.api.ts +export function useUpdate() { + const update = (data: T) => apiService.put("/account", data); + + return useMutation(update); +} +``` + +```typescript resources/user/user.api.ts +export function useList(params: T) { + const list = () => apiService.get("/users", params); + + interface UserListResponse { + count: number; + items: User[]; + totalPages: number; + } + + return useQuery(["users", params], list); +} +``` + +```typescript pages/profile/index.page.tsx +type UpdateParams = z.infer; + +const { mutate: update, isLoading: isUpdateLoading } = accountApi.useUpdate(); +``` + +```typescript pages/home/index.tsx +const { data: users, isLoading: isUsersLoading } = userApi.useList(params); +``` diff --git a/docs/web/environment-variables.mdx b/docs/web/environment-variables.mdx new file mode 100644 index 00000000..0970a93e --- /dev/null +++ b/docs/web/environment-variables.mdx @@ -0,0 +1,100 @@ +--- +title: Environment Variables +--- + +## Overview + +We use different environment variables for different stages like developing, testing, and when the app is live. + +This is done by using special files that have environments in them. + +### How the App Chooses the Right Environment Variables File +The app knows which file to use based on a special environment variable called `APP_ENV`. + +If `APP_ENV` is set to **staging**, the app will use the variables from the staging file. + + + In this stage, we work on making the app and adding new things. + + The app uses a file named `.env.development` which has special environment variables just for people who are building the app. + + + + Here, we test the app to make sure it's ready to go live. + + We use the `.env.staging` file which has variables that help us test everything properly. + + + + In the Production, the app is all done and people can use it. + + App uses a file named `.env.production` which has environment variables for the app when it's being used by everyone. + + +### Environment Variables Schema Validation +We use [Zod](https://zod.dev/) to check that our config is correct and in the right format. +This is important to make sure the app works without any problems. + +This setup is found in the `src/config/index.ts` file. + + +To prefix any variables intended for client-side exposure with `NEXT_PUBLIC_`. + + +When we add new environment variables to .env files, we need to make sure they match what we set up in the `Zod` schema. + +## Adding a New Variable + +Here's how to add a new environment variable to the application: + + + + Determine the environment stages where the new variable will be used. + + Refer to the following table for file associations: + + | APP_ENV | File | + |-------------|------------------| + | development | .env.development | + | staging | .env.staging | + | production | .env.production | + + + + Add the new variable to the respective `.env` files. For client-side variables, prefix them with `NEXT_PUBLIC_`. + + ```bash + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_TYooMQauvdEDq54NiTphI7jx + ``` + + + + Update the schema in the `config/index.ts` file to include the new variable. + This step is crucial for schema validation. + + ```typescript config/index.ts + const schema = z.object({ + ... + STRIPE_PUBLISHABLE_KEY: z.string(), + }); + ``` + + + + Add the new variable to the configuration object within the same file (`config/index.ts`), ensuring it matches the schema. + + ```typescript config/index.ts + const processEnv = { + ... + STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, + }; + ``` + + + + + All the environment variables we use in the front-end part of our app can be seen by everyone. + So, don't put any secret stuff like passwords or private keys there. + + Keep those safe and only on the server side! 🛡️✨ + diff --git a/docs/web/forms.mdx b/docs/web/forms.mdx new file mode 100644 index 00000000..8a347c0f --- /dev/null +++ b/docs/web/forms.mdx @@ -0,0 +1,119 @@ +--- +title: Forms +--- + +## Overview + +We use the [react-hook-form](https://react-hook-form.com/) library along with [@hookform/resolvers](https://www.npmjs.com/package/@hookform/resolvers) to simplify the process of creating and managing forms. +This setup allows for the quick development of robust forms. + +### Form Schema + +With [@hookform/resolvers](https://www.npmjs.com/package/@hookform/resolvers), you can validate your forms using a Zod schema. +Just write the schema and use it as a resolver in the `useForm` hook. +This approach not only validates your forms but also helps in creating perfectly typed forms by inferring types from the schema. + +Example of setting up a form with Zod schema: +```tsx +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; + +// Define your schema +const schema = z.object({ + firstName: z.string().min(1, 'Please enter First name').max(100), + lastName: z.string().min(1, 'Please enter Last name').max(100), + email: z.string().regex(EMAIL_REGEX, 'Email format is incorrect.'), +}); + +// Infer type from schema +type FormParams = z.infer; + +const Page = () => { + const { register, handleSubmit } = useForm({ + resolver: zodResolver(schema), + }); + + // Form handling code here +}; +``` + +### Error Handling +For error handling in forms, we use the `handle-error.util.ts` utility function. +This function parses error messages, sets them to the form fields' error states, or displays a global notification for general errors. + +Usage: +```tsx +import { useForm } from 'react-hook-form'; +import { handleError } from 'utils'; + +const Page: NextPage = () => { + const { setError } = useForm(); + + // Submit function + const onSubmit = (data: SignUpParams) => signUp(data, { + onError: (e) => handleError(e, setError), + }); +} +``` + +#### Form usage + +Here is an example of how you can create a form: + +```tsx +import { handleError } from 'utils'; +import { accountApi } from 'resources/account'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; + +// Define your schema +const schema = z.object({ + // Schema details here +}); + +// Infer type from schema +type SignUpParams = z.infer; + +const Page: NextPage = () => { + const { mutate: signUp, isLoading: isSignUpLoading } = accountApi.useSignUp(); + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + }); + + // Submit function + const onSubmit = (data: SignUpParams) => signUp(data, { + onSuccess: (data) => { + // Handle success response + queryClient.setQueryData(['account'], data); + }, + onError: (e) => handleError(e, setError), + }); + + // Form rendering + return ( +
+ + + + + ); +}; +``` + +By following these guidelines, you can effectively build and manage forms in our application, +ensuring a smooth user experience and robust form handling. \ No newline at end of file diff --git a/docs/web/overview.mdx b/docs/web/overview.mdx index 8581779b..dcf20c05 100644 --- a/docs/web/overview.mdx +++ b/docs/web/overview.mdx @@ -2,30 +2,30 @@ title: "Overview" --- -Web Starter is what we think an ideal starting point for the most React frontend applications. It is based on the following primary technologies: +Web Starter is our starting point for React front-end applications. +It's crafted using a selection of advanced technologies and tools, making it a robust and efficient foundation for development. -- Next.js -- React Query -- React Hook Form + Zod -- Mantine UI + Tabler -- Typescript -- Storybook + Chromatic +The core technologies include: -## Start application. +- [Next.js](https://nextjs.org/): A React framework that supports features like server-side rendering, static site generation and routing. +- [TanStack Query](https://tanstack.com/query): A library for managing data fetching, caching, and updating in React apps. +- [React Hook Form](https://react-hook-form.com/) + [Zod](https://zod.dev/): Efficient form management and schema validation tools. +- [Mantine UI](https://mantine.dev/) + [Tabler](https://tabler-icons.io/): UI components and icons for responsive and accessible web interfaces. +- [Typescript](https://www.typescriptlang.org/): JavaScript with syntax for types, enhancing scalability and maintainability. +- [Storybook](https://storybook.js.org/) + [Chromatic](https://www.chromatic.com/): UI development tools for building and testing components. -Run ```npm run dev``` that will start the application with ```.env.development``` config. +## Web Starter Documentation Sections -You also can start the app using Dockerfile. +Explore the following sections for detailed information on different aspects of the Web Starter: -### Important notes +- [Styling](/web/styling): Focuses on the styling approach used in the application, detailing how to work with Mantine UI to create visually appealing interfaces. +- [Routing](/web/routing): Covers the routing mechanism within the application, explaining how to organize navigation flow. +- [Calling API](/web/calling-api): Dedicated to API interactions, this section explains how to effectively make requests to back-end services, manage responses, and handle errors using the provided API service utilities. +- [Forms](/web/forms): Discusses form management, highlighting the integration of React Hook Form and Zod for efficient form creation, validation, and handling user inputs. +- [Services](/web/services): Describes the various service layers used in the application, such as the API, socket, and analytics services, providing examples of how to implement and utilize these services for different functionalities. +- [Environment Variables](/web/environment-variables): Guides on managing environment-specific configurations, explaining the use of different `.env` files for development, staging, and production environments, and the importance of securing sensitive data. -You need to set ```APP_ENV``` variable in build args in a place where you deploy application. -It is responsible for the config file from ```config``` folder that will be taken when building your application +## React TypeScript Cheatsheet -| APP_ENV | File | -| ------------- |-------------------| -| development | .env.development | -| staging | .env.staging | -| production | .env.production | - -## [React Typescript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/docs/basic/setup/) \ No newline at end of file +For a detailed guide on using React with TypeScript, +visit the [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/docs/basic/setup/). \ No newline at end of file diff --git a/docs/web/routing.mdx b/docs/web/routing.mdx new file mode 100644 index 00000000..fa89c9e2 --- /dev/null +++ b/docs/web/routing.mdx @@ -0,0 +1,65 @@ +--- +title: "Routing" +--- + +Our application's routing is powered by [Next.js Pages router](https://nextjs.org/docs/pages), which is a standard for handling routes in Next.js applications. + +## Configuration in `routes.ts` + +Each route in our application is configured in the `routes.ts` file, located in the root of the `routes` directory. +This file defines the structure and access levels of all routes using the `routesConfiguration` object. + +### Scope and Layout Types + +Routes are categorized into two scope types and layout types: + +1. **Scope Types**: +- `PUBLIC`: Routes accessible without user authentication. +- `PRIVATE`: Routes requiring user authentication. + +2. **Layout Types**: +- `MAIN`: Main layout for authenticated users. +- `UNAUTHORIZED`: Layout for non-authenticated users or authentication pages. + +### Route Configuration Example + +Here's an example of how a private and a public route are configured: + +- Private Route (Requires authentication): +```tsx src/routes.ts +[RoutePath.Profile]: { + scope: ScopeType.PRIVATE, + layout: LayoutType.MAIN, +} +``` + +- Public Route (Accessible without authentication): +```tsx src/routes.ts +[RoutePath.ForgotPassword]: { + scope: ScopeType.PUBLIC, + layout: LayoutType.UNAUTHORIZED, +}, +``` + +## Page Configuration + +The PageConfig (`/pages/_app/PageConfig/index.tsx` file) plays a crucial role in applying these configurations. +It uses the route configuration from `routes.ts` to determine the appropriate scope and layout for each page. + +### How It Works + +- The `PageConfig` component, using the Next.js router, matches the current route against the `routesConfiguration`. +- Based on the route, it applies the designated scope and layout. +- For private routes, it redirects unauthenticated users to the sign-in page. +- Conversely, for public routes, authenticated users are redirected to the home page. + +## Naming Conventions for Route Files + +- **Page Routes**: Each file representing a page route should have the `.page.tsx` postfix. +- **API Routes/Middleware**: Files for Next.js API routes or middleware should have the `.api.ts` postfix. + + +These conventions can be modified in `next.config.js` if needed. + + +By following this structure, our application maintains a clear, manageable, and scalable routing system. \ No newline at end of file diff --git a/docs/web/services.mdx b/docs/web/services.mdx new file mode 100644 index 00000000..b4b8f5d1 --- /dev/null +++ b/docs/web/services.mdx @@ -0,0 +1,59 @@ +--- +title: Services +--- + +## Overview + +Services in our application are specific functionalities or data manipulations handled by objects, classes, or functions. +You can add any service as needed for third-party API integrations or app metrics collection. + +### API Service +**Description:** +The API Service, represented by the `ApiClient` class, enhances axios client for better error handling and easier configuration. + +**Example Usage:** +```typescript +import { apiService } from 'services'; + +// Making a GET request +const data = await apiService.get('/users', { sort: { createdOn: 'desc' } }); +``` + +### Socket Service +**Description:** +Socket Service provides functions for managing websocket connections, including connecting, disconnecting, and handling events. + +**Example Usage:** +```tsx +import { socketService } from 'services'; + +// Listening for an event +socketService.on('user:updated', (data: User) => { + queryClient.setQueryData(['account'], data); +}); + +// In React component +const onCardUpdated = () => { + console.log('Card updated') +}; + +useEffect(() => { + socketService.on('card:updated', onCardUpdated); + + return () => { + socketService.off('card:updated', onCardUpdated); + }; +}, []); +``` + +### Analytics Service +**Description:** +Analytics Service offers a set of functionalities for working with third-party analytics systems like Mixpanel. It can be adapted to other platforms as needed. + +**Example Usage:** +```typescript +import { analyticsService } from 'services'; + +// Tracking an event +analyticsService.track('New user created', { firstName, lastName }); +``` \ No newline at end of file diff --git a/docs/web/styling.mdx b/docs/web/styling.mdx new file mode 100644 index 00000000..e790ddd8 --- /dev/null +++ b/docs/web/styling.mdx @@ -0,0 +1,41 @@ +--- +title: "Styling" +--- + +Ship aims for efficiency and convenience in styling by leveraging the [Mantine UI](https://v6.mantine.dev/) library. +Mantine UI offers a wide range of customizable components and hooks that facilitate the creation of visually appealing and functional interfaces. + +### Theme Configuration (`theme/index.ts`) + +Our application's design system is built on top of Mantine's [theme object](https://v6.mantine.dev/theming/theme-object/). +The theme object allows us to define global style properties that can be applied consistently across web application. + +### Custom Components (`components/index.js`) + +We extend Mantine's components to create custom-styled versions specific to our application needs. + +Here's an example of how we extend the Mantine `Button` component: + +- We set the default button size to 'large' (`lg`). + +``` typescript theme/components/Button.index.ts +import { Button } from '@mantine/core'; + +import classes from './index.module.css'; + +export default Button.extend({ + defaultProps: { + size: 'lg', + }, + classNames: { + label: classes.label, + }, +}); +``` + +- Custom class names are applied for additional styling, defined in our `index.module.css`. +``` typescript theme/components/Button.index.ts +.label { + font-weight: 500; +} +``` \ No newline at end of file diff --git a/template/apps/web/.storybook/preview.js b/template/apps/web/.storybook/preview.js index 67d34c06..759ae0e4 100644 --- a/template/apps/web/.storybook/preview.js +++ b/template/apps/web/.storybook/preview.js @@ -1,10 +1,10 @@ import { MantineProvider } from '@mantine/core'; import { addDecorator } from '@storybook/react'; import { withThemes } from '@react-theming/storybook-addon'; -import mainTheme from '../src/theme/main-theme'; +import theme from '../src/theme'; export const parameters = { actions: { argTypesRegex: "^on[A-Z].*" }, }; -addDecorator(withThemes(MantineProvider, [mainTheme])); +addDecorator(withThemes(MantineProvider, [theme])); diff --git a/template/apps/web/src/pages/_app/index.tsx b/template/apps/web/src/pages/_app/index.tsx index c6a1412d..22c5a758 100644 --- a/template/apps/web/src/pages/_app/index.tsx +++ b/template/apps/web/src/pages/_app/index.tsx @@ -9,7 +9,7 @@ import { ModalsProvider } from '@mantine/modals'; import '@mantine/core/styles.css'; import queryClient from 'query-client'; -import mainTheme from 'theme/main-theme'; +import theme from 'theme'; import PageConfig from './PageConfig'; @@ -19,9 +19,7 @@ const App: FC = ({ Component, pageProps }) => ( Ship - + diff --git a/template/apps/web/src/theme/main-theme.ts b/template/apps/web/src/theme/index.ts similarity index 85% rename from template/apps/web/src/theme/main-theme.ts rename to template/apps/web/src/theme/index.ts index 6a728738..8c03ccf6 100644 --- a/template/apps/web/src/theme/main-theme.ts +++ b/template/apps/web/src/theme/index.ts @@ -2,7 +2,7 @@ import { createTheme } from '@mantine/core'; import * as components from './components'; -const mainTheme = createTheme({ +const theme = createTheme({ fontFamily: 'Roboto, sans-serif', fontFamilyMonospace: 'monospace', headings: { @@ -17,4 +17,4 @@ const mainTheme = createTheme({ components, }); -export default mainTheme; +export default theme; diff --git a/template/pnpm-lock.yaml b/template/pnpm-lock.yaml index 9cc369b0..1603a9bd 100644 --- a/template/pnpm-lock.yaml +++ b/template/pnpm-lock.yaml @@ -641,9 +641,6 @@ importers: enums: specifier: workspace:* version: link:../enums - koa: - specifier: '*' - version: 2.14.1 mailer: specifier: workspace:* version: link:../mailer