/taxi-service

Coastal Yellow Cabs enables users to book a taxi and receive an instant estimate.

Primary LanguageTypeScript

Coastal Yellow Cabs

Coastal Yellow Cabs enables users to book a taxi and recieve an instant estimate that is calculated by the distance and the type of cab requested. The admin may login and view every trip requested by users. Additionally, the admin can change the status of each trip. Users may register and/or login to view their own trips.

User Books a Taxi user books taxi GIF

Admin Uses Panel

Admin demo GIF

Deployment

Client: Coastal Yellow Cabs

Server: Server For Coastal Yellow Cabs

Tech Stack

Frontend Built Using:

Installation Instructions

Environmental Variables:

- REACT_APP_API_KEY= please see [TOMTOM MAPS API](https://developer.tomtom.com/tomtom-maps-apis-developers) to get an API key

Using the Application

Requirements:

  • Node
  • Package Manager (such as Yarn or npm)

Follow these steps to get the app running:

  1. Fork and clone repo

  2. Add an .env file at the root of the folder (same level as the package.json file).

  3. Add environmental variables for the frontend.

  4. Run yarn or npm install to install the necessary node_modules on the frontend.

  5. Run yarn start or npm start on the client folder to run the frontend on localhost:3000

  6. The application should now be running.

How Redux Is Used

  • There are three main Redux modules/slices: Auth, Book, and Trips. Each redux module is placed in their respective feature folder, colocated with components that consume redux functionality. This colocation allows components and each redux module to easily share types. Also, I think it provides a more intuitive experience when creating new features.

  • Each module contains its own actions, reducer, selectors, and types. This organization allows for reusability across other Redux modules and React components. For instance, both users and admin use the trips redux module. Another example is the auth module, in that it's used throughout the app for authentication(permission based) and also as part of the headers for HTTP requests.

  • The root-reducer combines "auth", "trips" and "book" reducers.

  • The store uses the redux-persist library to save the "auth" state in local storage. This ensures the user's authentication status persists even after a user refreshes the page or navigates to a different website.

  • The reselect library supplies memoized functions that get state. Specifically, a function called createSelector creates these memoized selector functions. Selectors can be composed as shown below.

export const selectAllTrips = createSelector([selectTripState], (tripState) => tripState.trips);

export const selectCompletedTrips = createSelector([selectAllTrips], (trips) =>
  trips.filter((trip) => trip.status === 'complete')
);
  • Reselect also provides a function called createStructuredSelector that takes an object and returns an object with the same keys, but with selectors replaced with their values. This is used throughout the app to map state to props.
const mapStateToProps = createStructuredSelector({
  vehicle: selectVehicle,
});

const mapDispatchToProps = (dispatch) => ({
  setInput: (options) => dispatch(setInput(options)),
});

export default VehicleType;
  • Regarding the above code snippet, it is worth noting that new features are now being built using React-Redux-Hooks with TypeScript. So the above snippet is similar to this:
interface InputOptions {
  name: 'vehicle';
  value: VehicleTypes;
}

const VehicleType = () => {
  const dispatch = useDispatch();
  const vehicleType = useSelector<VehicleTypes>(selectVehicle);

  const setVehicleType = (inputOptions: InputOptions) => {
    dispatch(setInput(inputOptions));
  };

  return (
    ....rest of the code
  • Redux-logger is used to track state changes while in development mode.
if (process.env.NODE_ENV === 'development') {
  middlewares.push(logger);
}
  • Redux Thunk handles asynchronous API requests. Here's an example:
export const updateTrip = (status: TripLoadingStatus, id: number) => {
  return async (dispatch: Dispatch<Action>, getState) => {
    const authHeaders = selectAuthHeaders(getState());
    dispatch({
      type: TripsActionTypes.SUBMIT,
      payload: {
        loadingType: status,
        loadingTripId: id,
      },
    });
    try {
      const result = await axios.put(
        `${process.env.NEXT_PUBLIC_TRIPS}/api/trips/${id}`,
        { status },
        authHeaders
      );
      dispatch({
        type: TripsActionTypes.UPDATE_TRIP,
        payload: {
          trip: result.data,
        },
      });
    } catch (error) {
      dispatch({
        type: TripsActionTypes.ERROR,
        payload: { error },
      });
    }
  };
};
  • User authentication and authorization functionality is placed inside the "auth" redux module.
export const userAuth = ({
  authType,
  username,
  password,
  name = '',
  email = '',
  phone = '',
}: UserAuth) => {
  return async (dispatch: Dispatch<FetchUser | FetchSuccess | Error>) => {
    dispatch({ type: AuthActionTypes.FETCH_USER });
    try {
      const result =
        authType === 'login'
          ? await axios.post(`${process.env.NEXT_PUBLIC_TRIPS}/api/${authType}`, {
              username,
              password,
            })
          : // for user registration, "name", "email", and "phone" are not required. If
            // excluded, they default to empty strings
            await axios.post(`${process.env.NEXT_PUBLIC_TRIPS}/api/${authType}`, {
              username,
              password,
              name,
              email,
              phone,
            });
      const { token } = result.data;
      dispatch({
        type: AuthActionTypes.FETCH_SUCCESS,
        payload: { token, currentUser: username },
      });
    } catch (error) {
      dispatch({
        type: AuthActionTypes.ERROR,
        payload: { errorMessage: 'Unable to login or register' },
      });
    }
  };
};
  • Auth selectors help React components to conditionally render UI based on auth role. Currently there are users and one admin.
export const selectAuthRole = createSelector([selectAuthState], (authState) => {
  if (!authState.currentUser) {
    return '';
  } else if (authState.currentUser === 'admin') {
    return 'admin';
  } else {
    return 'user';
  }
});
  • Another example of an auth selector is the "selectAuthHeaders". This selector provides authentication headers with a JSON web token for HTTP requests. This enables admin-only authorized requests such as updating trip statuses and deleting trips. Furthermore, sensitive content(user/admin auth, trip information) is managed on the back-end by encoding and decoding credentials on a JSON web token.
export const selectAuthHeaders = createSelector([selectAuthState], (authState) => {
  if (authState.token && authState.currentUser) {
    return {
      headers: {
        Authorization: authState.token,
      },
    };
  }
});