Files
jambonz-webapp/docs/contributors.md
Dave Horton ea2713a021 update deps (#422)
* update deps

* prettier

* wip
2024-04-07 18:42:42 -04:00

8.0 KiB

jambonz

Contributing to the web app source code

🚀 Getting started

In order to run the web app you'll need your local environment setup which you can do following instructions in our environment readme.

Once your environment is setup you can fork or clone this repo. To start the web app just run npm install and then npm start.

🥞 Dev stack

We're using vite for development and the main application is react with typescript, prettier, eslint, husky and lint-staged. For testing we're using cypress.

🔒 Auth middleware

We have auth middleware that was initially based on this useAuth example but has been typed and modified to include a RequireAuth component for wrapping internal Routes. The main hook you'll use here is useAuth. This hook provides the following:

  • token
  • signin(user, pass)
  • signout()
  • authorized

A note on our ACL implementation

We have some simple ACL utilities for managing access to UI/routes based on conditions. There is a basic AccessControl component and a handy withAccessControl HOC for route containers with redirect. There is also a useAccessControl hook for use at the component-level.

🕹️ Application state

The state for the application has two parts: the local state and the remote server state. The server state is the source of truth. We keep only the minimal amount of local state necessary: the current logged in user, the list of service providers and the actively selected current service provider. We also use local state for a basic permissions matrix and for the toast notifications.

  • useSelectState(key): returns just the piece of state desired
  • useDispatch(): returns global dispatch method
  • useAccessControl(acl): returns true/false for select ACL permissions
  • useFeatureFlag(flag): returns true/false for available feature flags
  • withSelectState([...keys])(Component): like redux connect it maps state to props
  • toastError(msg): helper for dispatching error toasts
  • toastSuccess(msg): helper for dispatching success toasts

🏴󠁧󠁢󠁷󠁬󠁳󠁿 API implementation

We have a centralized API implementation that uses our normalized fetchTransport method under the hood. We have use hooks for general GET fetching that return the data fetched, a refetcher function that, when called, will update the data in the hook and therefore your component will render the new data, and a possible error if the fetch failed. The general consensus on when to use the hooks vs using a getFetch directly are dictated by whether the API response data needs to be refetched locally based on some user action, such as deleting an item from a list. In that case use the hooks, otherwise a getFetch pattern should work.

The hooks are:

  • useApiData(path): returns [data, refetcher, error]
  • useServiceProviderData(path): returns [data, refetcher, error]

All API requests are piped through the fetchTransport method which receives a generic type and returns it as the type of response data resolved. Any POST, PUT or DELETE calls should have a wrapper method that calls our more generic methods under the hood, which are:

  • getFetch(url)
  • postFetch(url, payload)
  • putFetch(url, payload)
  • deleteFetch(url)
  • getBlob(url)

Example of wrapper API methods to :POST and :PUT for the Account type:

export const postAccount = (payload: Partial<Account>) => {
  return postFetch<SidResponse, Partial<Account>>(API_ACCOUNTS, payload);
};

export const putAccount = (sid: string, payload: Partial<Account>) => {
  return putFetch<EmptyResponse, Partial<Account>>(
    `${API_ACCOUNTS}/${sid}`,
    payload,
  );
};

Local dev mock API server

There are two views that rely on call detail records (CDRs) that don't exist in the local developer db when running the docker stack. For these views we have a local node express server that replicates functional parity of the backend APIs in question so you can work on the UI. The views are Recent Calls and Alerts. They are simple table views with filter and pagination functionalities. You can view the implementation at server/dev.server.ts. The approach is to replicate the pattern of how test data is seeded for the API server and add the filtering on top of it with simple JavaScript functions. To run the dev server:

npm run dev:server

📁 Vendor data modules

Large data modules are used for menu options on the Applications and Speech Services forms. These modules are loaded lazily and set to local state in the context in which they are used. You can find the data modules and their type definitions in the src/vendor directory.

🌅 Component composition

All components that are used as Route elements are considered containers. Containers are organized by login and internal, the latter of which requires the user to be authorized via our auth middleware layer. Reusable components are small with specific pieces of functionality and their own local state. We have plenty of examples including toast, modal and so forth. You should review some of each category of component to get an idea of how the patterns are put into practice.

🎨 UI and styling

We have a UI design system called @jambonz/ui-kit. It's public on npm and is being used for this project. It's still small and simple but provides the foundational package content for building jambonz UIs. You can view the storybook for it here as well as view the docs for it here.

A note on styles

While we use sass with scss syntax it should be stated that the primary objective is to simply write generally pure css. We take advantage of a few nice features of sass like nesting for BEM module style etc. We also take advantage of loading the source sass from the UI library. Here's an example of the BEM style we use:

.example {
  // This is the block

  &--modifier {
    // This is a modifier of the block
  }

  &__item {
    // This is an element

    &--modifer {
      // This a modifer of the element
    }
  }
}

🤖 Testing

We're using cypress for component testing. Cypress is already configured and we're actively working on backfilling complete test coverage of the application. There are some issues open for this so you may refer to those to check out the progress.

All new components should have tests written alongside them. For example, if you component is called my-component.tsx then you would also have a my-component.cy.tsx test file to go with it. You can refer to existing tests for some common patterns we use to write tests.

❤️ Contributing

If you would like to contribute to this project please follow these simple guidelines:

  • Be excellent to each other!
  • Follow the best practices and coding standards outlined here.
  • Clone or fork this repo, write code and open a PR 👍
  • All code must pass the pr-checks and be reviewed by a code owner.

That's it!

🪲 Bugs?

If you find a bug please file an issue on this repository with as much information as possible regarding replication etc 🙏.