TripFinder React Hotel Listing Template

Sharing is caring!

React Next can be implemented for making a Hotel template. Today we will talk about such a Template which is based on React Next and solely developed keeping mind Hotel. So let us start the article.

TripFinder

TripFinder is the fastest template built with React, NextJS, Context API, & Styled-Components. It is very easy to use, comes with beautiful ready-made components. Build your amazing react app with this template. Both Create React App, and Next Js versions are available.

Features:

  • React.Js, React Context, React Hooks, React Router, Built with Create React App
  • Next.Js, Separate Next.Js implementation
  • Monorepo supported with Lerna configuration
  • Can be used without Monorepo
  • Beautiful ready-made components for your App
  • Search Components, Hotel listing
  • Glide, Ant design, Google Map, Map Marker
  • User Profile, Post Submission, Mobile Friendly design

Technology Used in TripFinder

Monorepo

The idea behind a monorepo is to store all code in a single version control system (VCS) repository. The alternative, of course, is to store code split into many different VCS repositories, usually on a service/application/library basis.

Check the below link if you want to learn more about yarn workspaces.

  1. Introducing-workspaces

  2. Workspaces.

Why developer use monorepo

  1. To store all code in a single version control system (VCS) repository,

  2. Easier collaboration and code sharing,

  3. Code refactors are easy/atomic commits.

Technology Credits

For TripFinder [ Hotel-CRA ]

  • Create React App
  • React
  • React Router 5
  • React Hooks
  • React Context API
  • Ant Design
  • Google Map
  • Styled Components …. etc.

For TripFinder [ Hotel-Next (SSR) ]

  • Create Next App
  • React
  • Next.Js
  • React Hooks
  • Context API
  • Ant Design
  • React Router 5
  • Formik
  • Google Map …. etc.

Now step by step process to make React Next Hotel App

Installation

Installation Process

  • Install node & npm
  • Install yarn
  • Install packages & dependencies

TripFinder is based on Create React App and Create Next App. It would be better if you can check their website too. There are lot of tricks that can help your app, like API connection, Deployment, etc.

https://create-react-app.dev (for hotel cra).

https://nextjs.org (for hotel next).

Installing Node & NPM:

To work with TripFinder the first thing you need is to have a stable Node version install on your system. To make sure you have already Node.Js installed on your system you may follow the below instructions:-

As Node will make sure you have node and npm commands are available via command line, just run the below command on your terminal.

node -v

npm -v

Note that if you find the npm version less than 5.0.0 you need to update it to the latest version using the below command. You may need to use sudo to grant permission.

npm install npm@latest -g 

or

sudo npm install npm@latest -g

Installing YARN:

You will need to Install Yarn for Fast, Reliable, and Secure Dependency Management. Before you start using Yarn, you’ll first need to install it on your system. And to make sure it running on your system with the latest version run the below command

yarn --version

Installing Packages & Dependencies :

This Product is based on monorepo structure. To provide easy access to different item developer provides some command line scripts, to begin with

$ yarn

NB: make sure you use yarn for installing packages, dependencies and running script.

Run the project in dev mode:

To run all the item at the same time:

$ yarn start:all

Which is start development version of every item on a different port:

hotel: at localhost:3000
hotel-next: at localhost:3001
etc.

To Run Individual Item: (ex: hotel / hotel-next)

$ yarn start:hotel


$ yarn start:hotel-next

For other scripts just follow this:

"scripts": {
    "clean": "lerna clean --yes && rimraf node_modules",
    "clean:build": "lerna exec -- rimraf \"{.next,dist,out,build,.docz}\"",
    "start:all": "lerna run --parallel start",
    "start:hotel": "yarn workspace hotel run start",
    "start:hotel-next": "yarn workspace hotel-next run dev",
    "build:hotel": "yarn workspace hotel run build",
    "build:hotel-next": "yarn workspace hotel-next run build",
    "serve:hotel": "yarn workspace hotel run serve",
    "serve:hotel-next": "yarn workspace hotel-next run serve"
  },

Google Map API Key:

For Hotel version:

First, go to .env file, Put your google map API key inside this code

REACT_APP_GOOGLE_MAP_API_KEY=https://maps.googleapis.com/maps/api/js?v=3.exp&key=YOUR_GOOGLE_MAP_API_KEY&libraries=geometry,drawing,placesyou need to put your google map api key between &key= and &libraries=geometry like this

place your map API key in place of

YOUR_GOOGLE_MAP_API_KEY

For Hotel Next version:

go to next.config.json then change the highlighted code

YOUR_GOOGLE_MAP_API_KEY

with your google map API key

map_import

Folder Structure: (Monorepo Structure using Lerna and yarn workspace)

Top-Level Folders:

Top Level Folder

Project within packages (hotel):

Project within packages

Project’s src:

Project's src

Data Provider

To get your require data from a rest end-point use useDataApi hook as

import useDataApi from '../hooks/useDataApi';

const Posts = () => {
  const { data, error, loading } = useDataApi('url');
  if (!data || loading) {
    return <div>Loading...</div>;
  };
  if (error) {
    return <div>Error! {error.message}</div>;
  };

  return (
    <ul>
      {data.posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

this hook also accepts limit as the second parameter and many more you can add into it according to your requirements.

import { useState, useReducer, useEffect } from 'react';

function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time));
}

async function SuperFetch(
  url,
  method = 'GET',
  headers = {
    'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
  },
  body = {}
) {
  await sleep(2000); // demo purpose only
  let options = {
    method,
    headers,
  };
  if (method === 'POST' || method === 'PUT') options = { ...options, body };

  // authentication
  // we will had custom headers here.

  return fetch(url, options)
    .then(res => {
      return Promise.resolve(res.json());
    })
    .catch(error => Promise.reject(error));
}
function dataFetchReducer(state, action) {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        loading: true,
        error: false,
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        data: action.payload.slice(0, state.limit),
        total: action.payload,
        loading: false,
        error: false,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        loading: false,
        error: true,
      };
    case 'LOAD_MORE':
      return {
        ...state,
        data: [
          ...state.data,
          ...state.total.slice(
            state.data.length,
            state.data.length + state.limit
          ),
        ],
        loading: false,
        error: false,
      };
    default:
      throw new Error();
  }
}

const useDataApi = (initialUrl, limit = 12, initialData = []) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    loading: false,
    error: false,
    data: initialData,
    total: initialData,
    limit: limit,
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await SuperFetch(url);
        if (!didCancel) {
          dispatch({ type: 'FETCH_SUCCESS', payload: result });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: 'FETCH_FAILURE' });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);
  const loadMoreData = () => {
    // dispatch({ type: 'FETCH_INIT' });
    dispatch({ type: 'LOAD_MORE' });
  };
  const doFetch = url => {
    setUrl(url);
  };

  return { ...state, doFetch, loadMoreData };
};

export default useDataApi;

Authentication Provider

In this chapter the authentication process with pseudocode will be demoed here. 

At first create a react context for the authentication and export it as we will used the AuthContext in multiple places inside the containers, components. 

export const AuthContext = React.createContext(); 

Then you will have to build the AuthProvider component.

<span class="hljs-keyword">const</span> fakeUserData = {
  id: <span class="hljs-number">1</span>,
  name: <span class="hljs-string">'Jhon Doe'</span>,
  avatar: <span class="hljs-string">''</span>,
  roles: [<span class="hljs-string">'USER'</span>, <span class="hljs-string">'ADMIN'</span>],
};

<span class="hljs-comment">/**
 * We have used Fake JWT token from "jwt.io"
 * This is a sample token to show auth is working
 * Your token will come from your server when user successfully loggedIn.
 */</span>

<span class="hljs-keyword">const</span> fakeToken = <span class="hljs-string">'FAKE-JWT-TOKEN-CODE'</span>;

<span class="hljs-keyword">const</span> addItem = (key, value = <span class="hljs-string">''</span>) =&gt; {};

<span class="hljs-keyword">const</span> clearItem = key =&gt; {};

<span class="hljs-keyword">const</span> isValidToken = () =&gt; {};

<span class="hljs-keyword">const</span> AuthProvider = props =&gt; {
  <span class="hljs-keyword">const</span> [loggedIn, setLoggedIn] = useState(isValidToken());
  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [token, setToken] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> signIn = params =&gt; {}
  <span class="hljs-keyword">const</span> signUp = params =&gt; {};
  <span class="hljs-comment">/**
   * For 3rd-party Authentication [e.g. Auth0, firebase, AWS etc]
   *
   */</span>
  <span class="hljs-keyword">const</span> tokenAuth = (token, user = {}) =&gt; {};
  <span class="hljs-keyword">const</span> forgetPass = params =&gt; {};
  <span class="hljs-keyword">const</span> changePass = params =&gt; {};
  <span class="hljs-keyword">const</span> logOut = () =&gt; {};

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AuthContext.Provider</span>
      <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">loggedIn</span>,
        <span class="hljs-attr">logOut</span>,
        <span class="hljs-attr">signIn</span>,
        <span class="hljs-attr">signUp</span>,
        <span class="hljs-attr">forgetPass</span>,
        <span class="hljs-attr">changePass</span>,
        <span class="hljs-attr">tokenAuth</span>,
      }}
    &gt;</span>
      <span class="hljs-tag">&lt;&gt;</span>{props.children}<span class="hljs-tag">&lt;/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthContext.Provider</span>&gt;</span>
  );
};

export default AuthProvider;</span> <span class="hljs-keyword">const</span> fakeUserData = {
  id: <span class="hljs-number">1</span>,
  name: <span class="hljs-string">'Jhon Doe'</span>,
  avatar: <span class="hljs-string">''</span>,
  roles: [<span class="hljs-string">'USER'</span>, <span class="hljs-string">'ADMIN'</span>],
};

<span class="hljs-comment">/**
 * We have used Fake JWT token from "jwt.io"
 * This is a sample token to show auth is working
 * Your token will come from your server when user successfully loggedIn.
 */</span>

<span class="hljs-keyword">const</span> fakeToken = <span class="hljs-string">'FAKE-JWT-TOKEN-CODE'</span>;

<span class="hljs-keyword">const</span> addItem = (key, value = <span class="hljs-string">''</span>) =&gt; {};

<span class="hljs-keyword">const</span> clearItem = key =&gt; {};

<span class="hljs-keyword">const</span> isValidToken = () =&gt; {};

<span class="hljs-keyword">const</span> AuthProvider = props =&gt; {
  <span class="hljs-keyword">const</span> [loggedIn, setLoggedIn] = useState(isValidToken());
  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [token, setToken] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> signIn = params =&gt; {}
  <span class="hljs-keyword">const</span> signUp = params =&gt; {};
  <span class="hljs-comment">/**
   * For 3rd-party Authentication [e.g. Auth0, firebase, AWS etc]
   *
   */</span>
  <span class="hljs-keyword">const</span> tokenAuth = (token, user = {}) =&gt; {};
  <span class="hljs-keyword">const</span> forgetPass = params =&gt; {};
  <span class="hljs-keyword">const</span> changePass = params =&gt; {};
  <span class="hljs-keyword">const</span> logOut = () =&gt; {};

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AuthContext.Provider</span>
      <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">loggedIn</span>,
        <span class="hljs-attr">logOut</span>,
        <span class="hljs-attr">signIn</span>,
        <span class="hljs-attr">signUp</span>,
        <span class="hljs-attr">forgetPass</span>,
        <span class="hljs-attr">changePass</span>,
        <span class="hljs-attr">tokenAuth</span>,
      }}
    &gt;</span>
      <span class="hljs-tag">&lt;&gt;</span>{props.children}<span class="hljs-tag">&lt;/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthContext.Provider</span>&gt;</span>
  );
};

export default AuthProvider;</span>

In the code mentioned above, the AuthProvider is build up. All the authentication-related functions loggedIn, logOut, signIn ,signUp , forgetPass , changePass, tokenAuth are passing through the AuthContext.

****** Example Usages ******

How to use AuthContext in the Login component ??

Suppose you need to create a login page for the users. But how to use the AuthProvider??

  • At create a login form wrapper component using Formik via the initial values & validation schema.
<span class="hljs-comment">// pseudocode</span> import { Formik } from 'formik'; import * as Yup from 'yup'; const initialValues = { email: '', password: '', rememberMe: false, }; const getLoginValidationSchema = () => { return Yup.object().shape({ email: Yup.string() .email('Invalid email') .required('Email is Required!'), password: Yup.string() .min(6, 'Password has to be longer than 6 characters!') .max(20, 'Too Long!') .required('Password is required!'), }); }; export default () => { return ( <Formik initialValues={initialValues} onSubmit={handleSubmit} render={RenderSignInForm} validationSchema={getLoginValidationSchema} /> ); };
  • Then Create the login form<code class="lang-js"><span class="hljs-comment">// pseudocode</span>
    const RenderBasicInfoForm = props => {
      const { values, submitCount, handleSubmit } = props;
      return (
        <FormWrapper>
          <Form onSubmit={handleSubmit}>
            <Field
              component={AntInput}
              name="email"
              type="text"
              size="large"
              placeholder="Email"
              submitCount={submitCount}
              value={values.email}
              hasFeedback
            />
            <Field
              component={AntInput}
              name="password"
              type="password"
              size="large"
              placeholder="Password"
              value={values.password}
              submitCount={submitCount}
              hasFeedback
            />
            <FieldWrapper className="field-container">
              <SwitchWrapper>
                <Field
                  component={AntSwitch}
                  name="rememberMe"
                  submitCount={submitCount}
                  value={values.rememberMe}
                />
                <Label>Remember Me</Label>
              </SwitchWrapper>
              <Link to={FORGET_PASSWORD_PAGE}>Forget Password ?</Link>
            </FieldWrapper>
    
    <code class="lang-js"> <Button className="signin-btn" type="primary" htmlType="submit" size="large" style={{ width: '100%' }} > <MdLockOpen /> Login </Button> </Form> </FormWrapper> ); }; <code class="lang-js"> export default RenderBasicInfoForm; 
  • Your login form is created, now connect the sign in form wrapper component [on step 1] with the AuthProvider!
<span class="hljs-comment">// pseudocode</span> import { AuthContext } from '../../context/AuthProvider'; import { Formik } from 'formik'; import * as Yup from 'yup'; import { Redirect } from 'react-router-dom'; export default () => { const { signIn, loggedIn } = useContext(AuthContext); if (loggedIn) return <Redirect to={{ pathname: '/' }} />; const handleSubmit = formProps => { signIn(formProps); }; return ( <Formik initialValues={initialValues} onSubmit={handleSubmit} render={RenderSignInForm} validationSchema={getLoginValidationSchema} /> ); };

You can see that we are using useContext _hook here. Using that hook we manipulate the values/functions from the AuthContext.

How to use AuthProvider in where some components need to use this?

Suppose you need to display a post grid on a page for the user, where it’s for the authenticate users only. Then wrap that post grid using the AuthProvider.


<span class="hljs-comment">// pseudocode</span>

import AuthProvider from ‘AUTH-PROVIDER-IMPORT-PATH’;

export default function DisplayPosts(props) {
return (
<DisplayPostsPage>
<AuthProvider>
.
.
. Your components will be here…
.
.
</AuthProvider>
</DisplayPostsPage>

);

That’s the overall process of how you can use the AuthProvider and AuthContext in isomorphic-hotel projects.

Google Map

Google map component is divided into 6 major portions.

  • Display Google map with a single marker or marker cluster
  • Map AutoComplete Search
  • Map Location search [with Map View]
  • Map Location Box
  • Map Data Helper Function
  • Map InfoWindow

For More help here is the link of the npm package which is used here.

Link: https://www.npmjs.com/package/react-google-maps

Display Google map with a single marker or marker cluster

Displaying a Google map is divided into two sections. One with the single map marker and the other with the marker clusters.

The map is the wrapper component where it takes other map components as a children.

<span class="hljs-keyword">import</span> <span class="hljs-built_in">Map</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'MAP-IMPORT-FILE-LOCATION-PATH'</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Map</span>&gt;</span>
.
.
. {children component}
.
.
<span class="hljs-tag">&lt;/<span class="hljs-name">MAP</span>&gt;</span></span>

Now, for displaying the there need to be import <MapDataProcessing /> component with some mandatory props.

<span class="hljs-keyword">import</span> <span class="hljs-built_in">Map</span>, { MapDataProcessing } <span class="hljs-keyword">from</span> <span class="hljs-string">'MAP-IMPORT-FILE-LOCATION-PATH'</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Map</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MapDataProcessing</span> <span class="hljs-attr">location</span>=<span class="hljs-string">{MAP_DATA}</span> <span class="hljs-attr">multiple</span>=<span class="hljs-string">{false}</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">MAP</span>&gt;</span></span>
Props name Data
location object
multiple boolean { true for marker cluster, false for single marker }

Display: For single map marker

single map marker

Display: For marker cluster

marker cluster

Map AutoComplete Search Component

Map auto complete component is designed for the locations to choose options. This component receives one prop for the data processing.

<span class="hljs-keyword">import</span> MapAutoComplete <span class="hljs-keyword">from</span> <span class="hljs-string">'MAP_AUTO_COMPLETE_PATH'</span>

<span class="hljs-keyword">const</span> updatevalueFunc = (value) =&gt; {
    <span class="hljs-built_in">console</span>.log(value)
}

&lt;MapAutoComplete updatevalue={value =&gt; updatevalueFunc(value)} /&gt;
Props data
updatevalue function

Display on view :

Display on view

Map Location Search component is designed for the locations to choose options but with the map view & marker drag & drop features. This component receives one props for the data processing.

This component receives one prop for the data processing.

<span class="hljs-keyword">import</span> MapWithSearchBox <span class="hljs-keyword">from</span> <span class="hljs-string">'PATH'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-built_in">Map</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'PATH'</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Map</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">MapWithSearchBox</span>
    <span class="hljs-attr">draggable</span>=<span class="hljs-string">{true}</span>
    <span class="hljs-attr">updatevalue</span>=<span class="hljs-string">{value</span> =&gt;</span> getUpdateValue(value)}
    {...props}
  /&gt;    
<span class="hljs-tag">&lt;/<span class="hljs-name">Map</span>&gt;</span></span>
Props Data
draggable boolean
updatevalue function

for other props, you can visit this link too. https://tomchentw.github.io/react-google-maps/#searchbox

Map Location Search

Map Info Window

Map info window component is designed for displaying the map marker data on individual points.

<span class="hljs-keyword">import</span> HotelInfoWindow <span class="hljs-keyword">from</span> <span class="hljs-string">'PATH'</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">HotelInfoWindow</span>
  <span class="hljs-attr">postData</span>=<span class="hljs-string">{POST_DATA}</span>
  <span class="hljs-attr">onCloseClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {
    infoWindowToggle(SINGLE_POST.ID);
  }}
  {...props}
/&gt;</span>
Props Date
postDate object
onCloseClick function

For other props, you can check this link too https://tomchentw.github.io/react-google-maps/#infowindow

Display of hotel info window:

Display of hotel info window

Settings or Environment Params:

Route Constants with pathname:

// General Page Constants
export const HOME_PAGE = '/';
export const AGENTS_PAGE = '/agents';

// Listing Single Page Constants
export const LISTING_POSTS_PAGE = '/listing';
export const SINGLE_POST_PAGE = '/post';

// Agent Profile Page Constants
export const AGENT_PROFILE_PAGE = '/profile';
export const AGENT_ACCOUNT_SETTINGS_PAGE = '/account-settings';
export const AGENT_PROFILE_EDIT_PAGE = '/edit';
export const AGENT_IMAGE_EDIT_PAGE = '/change-image';
export const AGENT_PASSWORD_CHANGE_PAGE = '/change-password';
export const AGENT_PROFILE_DELETE = '/delete';
export const AGENT_PROFILE_FAVOURITE = '/favourite-post';
export const AGENT_PROFILE_CONTACT = '/contact';
export const ADD_HOTEL_PAGE = '/add';

// Other Pages Constants
export const PRICING_PLAN_PAGE = '/pricing-plan';
export const PRIVACY_PAGE = '/privacy';

// Login & Registration Page Constants
export const LOGIN_PAGE = '/login';
export const REGISTRATION_PAGE = '/registration';
export const CHANGE_PASSWORD_PAGE = '/change-password';
export const FORGET_PASSWORD_PAGE = '/forget-password';
REACT_APP_GOOGLE_MAP_API_KEY = <span class="hljs-string">'https://maps.googleapis.com/maps/api/js?v=3.exp&amp;key=[YOUR_MAP_API_KEY]&amp;libraries=geometry,drawing,places'</span>,

Data format for the TripFinder

Agent Profile Data [ JSON format ]

{
    "id": INTEGER,
    "first_name": "",
    "last_name": "",
    "username": "",
    "password": "",
    "email": "",
    "cell_number": "",
    "profile_pic": {
      "id": INTEGER,
      "url": ""
    },
    "cover_pic": {
      "id": INTEGER,
      "url": ""
    },
    "date_of_birth": "",
    "gender": "",
    "content": "",
    "agent_location": {
      "lat": "",
      "lng": "",
      "formattedAddress": "",
      "zipcode": "",
      "city": "",
      "state_long": "",
      "state_short": "",
      "country_long": "",
      "country_short": ""
    },
    "gallery": [
      {
        "id": INTEGER,
        "url": ""
      },
      {
        "id": INTEGER,
        "url": ""
      },
      {
        "id": INTEGER,
        "url": ""
      }
    ],
    "social_profile": {
      "facebook": "",
      "twitter": "",
      "linkedin": "",
      "instagram": ""
    },
    "listed_post": [],
    "favourite_post": [],
    "createdAt": "",
    "updatedAt": ""
  }

Hotel Post Data [ JSON format ]

{
    "id": INTEGER,
    "agentId": INTEGER,
    "title": "",
    "slug": "",
    "content": "",
    "status": "",
    "price": "",
    "isNegotiable": BOOLEAN,
    "propertyType": "",
    "condition": "",
    "rating": FLOAT,
    "ratingCount": INTEGER,
    "contactNumber": "",
    "termsAndCondition": "",
    "amenities": [
      {
        "id": INTEGER,
        "guestRoom": INTEGER
      },
      {
        "id": INTEGER,
        "bedRoom": INTEGER
      },
      {
        "id": INTEGER,
        "wifiAvailability": BOOLEAN
      },
      {
        "id": INTEGER,
        "parkingAvailability": BOOLEAN
      },
      {
        "id": INTEGER,
        "poolAvailability": BOOLEAN
      },
      {
        "id": INTEGER,
        "airCondition": BOOLEAN
      },
      {
        "id": INTEGER,
        "extraBedFacility": BOOLEAN
      }
    ],
    "image": {
      "id": INTEGER,
      "url": ""
    },
    "location": {
      "id": INTEGER,
      "lat": "",
      "lng": "",
      "formattedAddress": "",
      "zipcode": "",
      "city": "",
      "state_long": "",
      "state_short": "",
      "country_long": "",
      "country_short": ""
    },
    "gallery": [
      {
        "id": INTEGER,
        "url": ""
      },
      {
        "id": INTEGER,
        "url": ""
      },
      {
        "id": INTEGER,
        "url": ""
      }
    ],
    "categories": [
      {
        "id": INTEGER,
        "slug": "",
        "name": "",
        "image": {
          "id": INTEGER,
          "url": ""
        }
      },
      {
        "id": INTEGER,
        "slug": "",
        "name": "",
        "image": {
          "id": INTEGER,
          "url": ""
        }
      }
    ],
    "createdAt": "",
    "updatedAt": ""
  }

Subscribe to the Newsletter

Get our latest news,tutorials,guides,tips & deals delivered to your inbox.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.