Skip to content

Components


In this section, we will build all the components that will be used on the frontend application.

General Components

Button

We start with a basic Button component in /src/components/componentButton.tsx with a bunch of options for modifying it based on the usage.

import React from "react";

type props = {
  text: string;
  onClick(e: any): void;
  white?: boolean;
  header?: boolean;
  modal?: boolean;
  padding?: boolean;
  alert?: boolean;
};

const Button: React.FC<props> = (props) => {
  const {
    text,
    white = false,
    modal = false,
    onClick,
    padding = false,
    alert = false,
  } = props;
  const colours = white
    ? "text-gray-900 bg-white"
    : alert
    ? "text-white bg-red-600"
    : "text-white bg-gray-900";

  return (
    <button
      className={`font-medium py-3 text-sm lg:text-base rounded-button shadow-button ${colours} ${
        modal ? "w-full lg:text-base" : "px-3 lg:px-6"
      } ${padding ? "mb-10" : ""}`}
      {...{ onClick }}
    >
      {text}
    </button>
  );
};

export default Button;

/src/components/componentButton.tsx


We then create a Header component in /src/components/componentHeader.tsx to be used on all pages.

import React from "react";
import { Link } from "react-router-dom";
import Button from "./componentButton";

type props = {
  setShowSignUp(showSignUp: boolean): void;
};

const Header: React.FC<props> = (props) => {
  const { setShowSignUp } = props;

  return (
    <header className="bg-gray-900 sticky top-0 z-10">
      <div className="container mx-auto px-4 lg:px-2 py-3 flex justify-between items-center">
        <Link
          className="text-white text-2xl font-medium cursor-pointer"
          to="/listings"
        >
          RentOnZilliqa
        </Link>
        <Button
          text={"Create Account"}
          onClick={() => setShowSignUp(true)}
          white
          header
        />
      </div>
    </header>
  );
};

export default Header;

/src/components/componentHeader.tsx


We create a Modal component at /src/components/componentModal.tsx. Most transitions take place via a modal. This component takes care of the basic Modal functionality and styling.

The title; main button set to buttonText; dismiss button; and overlay are part of this component.

The children passed to this component are the content in the modal.

The onClick function will be called when the main button is clicked.

import React, { useEffect } from "react";
import Button from "./componentButton";

type props = {
  title: string;
  children: JSX.Element | JSX.Element[];
  setVisible(visible: boolean): void;
  visible: boolean;
  buttonText: string;
  onClick(): void;
};

const Modal: React.FC<props> = (props) => {
  const { title, children, setVisible, visible, buttonText, onClick } = props;

  useEffect(() => {
    document.onkeydown = (event: KeyboardEvent) => {
      if (event.key === "Enter") {
        event.preventDefault();
        onClick();
      }
    };
  }, []);

  return (
    <div
      className={
        "w-screen h-screen bg-black bg-opacity-25 fixed top-0 left-0 z-20 transition-all"
      }
      style={
        visible
          ? {
              opacity: 1,
              visibility: "visible",
              transform: "translateY(0)",
            }
          : {
              opacity: 0,
              visibility: "hidden",
              transform: "translateY(30px)",
            }
      }
      onClick={() => setVisible(false)}
    >
      <div className="w-full h-full flex justify-center items-center px-4 lg:px-2 py-2">
        <div
          className="w-full lg:w-1/3 bg-white shadow-xl rounded-2xl max-h-full flex flex-col"
          onClick={(e) => e.stopPropagation()}
        >
          <div className="flex justify-between items-center p-8">
            <p className="text-xl font-bold text-gray-900">{title}</p>
            <button
              className="p-1 rounded hover:bg-gray-100 transition-colors -mr-1"
              onClick={() => setVisible(false)}
            >
              <svg
                className="w-6 h-6 text-gray-700"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M6 18L18 6M6 6l12 12"
                />
              </svg>
            </button>
          </div>

          <div className="w-full px-8 pt-0 overflow-y-scroll flex-grow">
            {children}
          </div>
          <div className="p-8">
            <Button
              modal
              text={buttonText}
              onClick={(e: any) => {
                onClick();
                setVisible(false);
              }}
            />
          </div>
        </div>
      </div>
    </div>
  );
};

export default Modal;

/src/components/componentModal.tsx


ListingCard

We create a ListingCard component at /src/components/componentListingCard.tsx. This component creates the listing card used on the listings page.

import React from "react";

type props = {
  id: string;
  name: string;
  price: string | number;
  rooms: string | number;
  bathrooms: string | number;
  image: string;
  renter: string | undefined;
  rented_till: string;
  accumulated_rent: string;
  rented: boolean;
  user_is_host: boolean;
  onClick(): void;
};

const ListingCard: React.FC<props> = (props) => {
  const { name, price, rooms, bathrooms, image, rented, onClick } = props;
  return (
    <div className="w-full rounded-2xl cursor-pointer" onClick={onClick}>
      <div
        className="w-full h-48 rounded-lg mb-4 bg-gray-100 flex justify-end items-start p-2"
        style={{
          backgroundImage: `url(${image})`,
          backgroundSize: "cover",
        }}
      >
        {rented && (
          <div>
            <div className="px-2 py-1 bg-gray-200 text-gray-600 rounded uppercase text-xs tracking-wide font-semibold">
              Unavailable
            </div>
          </div>
        )}
      </div>
      <div className="flex items-center text-base font-light text-gray-600">
        <p>
          {rooms} Room{rooms > 1 ? "s" : ""}
        </p>
        <div className="w-1 h-1 bg-gray-500 rounded-full mx-2"></div>
        <p>
          {bathrooms} Bathroom{bathrooms > 1 ? "s" : ""}
        </p>
      </div>
      <h3 className="text-gray-900 text-xl">{name}</h3>
      <p className="text-gray-900 font-semibold">
        {price} ZIL
        <span className="text-gray-600 font-light"> / night</span>
      </p>
    </div>
  );
};

export default ListingCard;

/src/components/componentListingCard.tsx


Form Components

Input

We create an Input component at /src/components/componentInput.tsx. This works with state variables which will be created with the useState hook in its Parent Component. We accept the name for the input field. The input type and unit are also accepted as optional props.

import React from "react";

type props = {
  name: string;
  unit?: string;
  value?: string;
  setValue(value: string): void;
  type?: string;
};

const Input: React.FC<props> = (props) => {
  const { name, unit = "", value = "", setValue, type = "text" } = props;

  return (
    <div className="">
      <div className="flex justify-between items-center py-2 text-xs tracking-wide uppercase">
        <h4 className="font-semibold text-gray-500">{name}</h4>
        <p className="font-medium text-gray-400">{unit}</p>
      </div>
      <input
        className="w-full mb-6 border-2 border-gray-300 focus:border-gray-900 rounded-button outline-none text-gray-900 lg:text-lg px-4 py-3"
        placeholder={name}
        type={"text"}
        inputMode={type === "number" ? "decimal" : "text"}
        min={type === "number" ? 1 : undefined}
        value={value}
        onChange={(e) => setValue(e.target.value)}
      ></input>
    </div>
  );
};

export default Input;

/src/components/componentInput.tsx


CheckBox

We create a CheckBox component at /src/components/componentCheckBox.tsx. This component is used within the modals. The CreateAccount modal uses it for user role selection. It is also used for selecting amenities in the CreateListing and ManageListing Modals.

import React from "react";
import Tick from "./componentTick";

type props = {
  checked: boolean;
  setChecked(checked: boolean): void;
  children: any;
  name: String;
};

const CheckBox: React.FC<props> = (props) => {
  const { checked, setChecked, children, name } = props;
  return (
    <>
      <div
        className="flex justify-between items-center cursor-pointer mt-3"
        onClick={() => setChecked(!checked)}
      >
        <div className="flex items-center">
          {children}
          <p className="text-lg text-gray-800 pl-4">{name}</p>
        </div>
        <div
          className={`p-1 bg-gray-200 rounded-lg w-8 h-8 hover:scale-95 transform transition-all ${
            checked ? "" : "hover:bg-gray-300"
          }`}
        >
          <div
            className={`w-full h-full rounded transition-colors text-transparent ${
              checked ? "bg-gray-900 text-white" : ""
            }`}
          >
            {checked && <Tick />}
          </div>
        </div>
      </div>
    </>
  );
};

export default CheckBox;

/src/components/componentCheckBox.tsx


AmenitiesInput

We create an AmenitiesInput component at /src/components/componentAmenitiesInput.tsx. It groups multiple checkboxes for collecting the amenities' availability in the CreateListing and ManageListing modals. We create this component to clean up the code.

import React, { useEffect, useState } from "react";
import {
  HvacIcon,
  KitchenIcon,
  LaundryIcon,
  TvIcon,
  WifiIcon,
} from "./componentListingIcons";
import CheckBox from "./componentCheckBox";

type props = {
  wifi: boolean;
  setWifi(wifi: boolean): void;
  kitchen: boolean;
  setKitchen(kitchen: boolean): void;
  tv: boolean;
  setTv(tv: boolean): void;
  laundry: boolean;
  setLaundry(laundry: boolean): void;
  hvac: boolean;
  setHvac(hvac: boolean): void;
};

const AmenitiesInput: React.FC<props> = (props) => {
  const {
    wifi,
    setWifi,
    kitchen,
    setKitchen,
    tv,
    setTv,
    laundry,
    setLaundry,
    hvac,
    setHvac,
  } = props;
  const [selectAll, setSelectAll] = useState(true);

  useEffect(() => {
    setSelectAll(!(wifi || kitchen || tv || laundry || hvac));
  }, [wifi, kitchen, tv, laundry, hvac]);

  const setAll = (value: boolean) => {
    setWifi(value);
    setKitchen(value);
    setTv(value);
    setLaundry(value);
    setHvac(value);
  };

  return (
    <>
      <div className="flex justify-between text-xs font-semibold text-gray-500 tracking-wide uppercase py-4">
        <h4>Amenities</h4>
        <p
          className="font-medium text-gray-400 cursor-pointer hover:text-gray-500 transition-colors"
          onClick={() => setAll(selectAll)}
        >
          {selectAll ? "Select All" : "Select None"}
        </p>
      </div>
      <CheckBox name="WiFi" checked={wifi} setChecked={setWifi}>
        <WifiIcon />
      </CheckBox>
      <CheckBox name="Kitchen" checked={kitchen} setChecked={setKitchen}>
        <KitchenIcon />
      </CheckBox>
      <CheckBox name="Television" checked={tv} setChecked={setTv}>
        <TvIcon />
      </CheckBox>
      <CheckBox name="Laundry" checked={laundry} setChecked={setLaundry}>
        <LaundryIcon />
      </CheckBox>
      <CheckBox name="HVAC" checked={hvac} setChecked={setHvac}>
        <HvacIcon />
      </CheckBox>
    </>
  );
};

export default AmenitiesInput;

/src/components/componentAmenitiesInput.tsx


SVG Components

Tick

This Tick component at /src/components/componentTick.tsx is used to make the SVG easily available for the CheckBox component.

import React from "react";

const Tick: React.FC = () => {
  return (
    <div className="w-full h-full grid place-items-center">
      <svg
        className="w-4 h-4"
        fill="none"
        stroke="currentColor"
        viewBox="0 0 24 24"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeWidth={4}
          d="M5 13l4 4L19 7"
        />
      </svg>
    </div>
  );
};

export default Tick;

/src/components/componentTick.tsx


Listing Icons

This /src/components/componentListingIcons.tsx file contains several SVG components for use on the listing page as well as the listing management modals.

It includes icons for:

  • Wifi
  • Kitchen
  • TV
  • Laundry
  • HVAC
  • Bedroom
  • Bathroom