DannyRH27 / DannyDash

DannyDash is a FullStack project inspired by DoorDash. The twist: All deliveries are estimated by Danny's walking speed. Hope you're not hungry!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

View the live site.

DannyDash

DannyDash is an on-demand prepared food delivery service inspired by DoorDash that estimates the amount of time it will take for Danny to delivery food to you by walking. It was implemented using the following technologies:

  • Frontend: React.js, Redux
  • Backend: PostgreSQL Database, Ruby on Rails
  • Others: JavaScript, Amazon S3, Google Geocoding API, Google Maps Javascript API, Google Distance Matrix API, Omniauth Facebook API,

Table of Contents

Logistics Dispatch System

The Logistics Dispatch System is implemented using Google's Geocoding, Distance Matrix, and Maps Javascript APIs.

First, the Geocoding API will take the current user's address and the destination store address and return latitude/longitude coordinates for both.

Next, the store coordinates are set as the origin and the user's coordinates are set as the destination in the Distance Matric API inputs and will return duration and distance. ETA will be calculated by manipulating the returned duration and incrementing it with a DateTime Object.

Finally, the Maps Javascript API will find the DOM element with the id of "order-map", initialize an instance of Google Maps, and use the coordinates to create two markers on the map.

  calculateDispatchDistance(order) {
    const { updateOrder } = this.props;
    function callback(response, status) {
      if (status == "OK") {
        var origins = response.originAddresses;
        var destinations = response.destinationAddresses;

        for (var i = 0; i < origins.length; i++) {
          var results = response.rows[i].elements;
          for (var j = 0; j < results.length; j++) {
            var element = results[j];
            var distance = element.distance.text;
            var durationText = element.duration.text;
            var duration = element.duration.value;
            var from = origins[i];
            var to = destinations[j];
          }
        }
        var date = new Date(order.createdAt);
        date.setMinutes(date.getMinutes() + duration / 60);
        var minutes = date.getMinutes();
        var hours = date.getHours();
        var ampm = hours >= 12 ? "pm" : "am";
        hours = hours % 12;
        hours = hours ? hours : 12; // the hour '0' should be '12'
        minutes = minutes < 10 ? "0" + minutes : minutes;
        var strTime = hours + ":" + minutes + " " + ampm;
        if (order.deliveryEta === null) {
          const newOrder = Object.assign({}, order)
          newOrder.deliveryEta = strTime
          newOrder.deliveredDate = date
          updateOrder(newOrder)
        }
        

        this.setState({ duration: duration });
        this.setState({ ETA: strTime });
        this.setState({ durationText: durationText });
        this.setState({ distance: distance });
        this.setState({ deliveryDate: date })
        this.initMap(order);

      }
    }
    const { currentUser } = this.props;
    var origin1 = order.store.address;
    var destinationA = currentUser.address;

    var service = new google.maps.DistanceMatrixService();
    service.getDistanceMatrix(
      {
        origins: [origin1],
        destinations: [destinationA],
        travelMode: "WALKING",
        unitSystem: google.maps.UnitSystem.IMPERIAL,
      },
      callback.bind(this)
    );
  }

  initMap(order) {
    const { currentUser } = this.props;
    var location = { lat: 37.75383, lng: -122.401772 };
    var map = new google.maps.Map(document.getElementById("order-map"), {
      zoom: 15,
      center: location,
    });
    var geocoder = new google.maps.Geocoder();
    geocoder.geocode({ address: currentUser.address }, function (
      results,
      status
    ) {
      var homeMark = <img src="https://dannydash-seeds.s3-us-west-1.amazonaws.com/Home.png" alt=""/>
      map.setCenter(results[0].geometry.location);
      var marker = new google.maps.Marker({
        map: map,
        position: results[0].geometry.location,
      });
      this.setState({address: results[0].formatted_address})
    // }
    }.bind(this))

    geocoder.geocode({ address: order.store.address }, function (
      results,
      status
    ) {
      if (status == google.maps.GeocoderStatus.OK) {
        var storeMark = <img src="https://dannydash-seeds.s3-us-west-1.amazonaws.com/Store.png" alt=""/>
        var marker = new google.maps.Marker({
          map: map,
          position: results[0].geometry.location,
        });
      }
    }.bind(this))
  }

Facebook Omni-Authorization

DannyDash allows you to sign in or signup for a user account using your Facebook credentials.

Omni-Authorization is achieved through Facebook Login API.

When the 'Continue with Facebook' button is hit, an API call is made to the backend OmniAuth Callbacks Controller's Facebook route and a script asynchronously initializes an instance of the Facebook app with your Facebook API key in order to request user information.

After the user inserts their credentials/authorizes DannyDash, we will be redirected back to the Omniauth Callback Controller where the information from the request is available as a hash at request.env["omniauth.auth"] and proceeds to generate a user with the information provided.

If successfully validated through the user model, the user will be created and redirected to the home page. On failure, the account will not be created and the user will be redirected back to the splash page.

Async FB API Call

<script>
      window.fbAsyncInit = function() {
        FB.init({
          appId      : '175499983639989',
          cookie     : true,
          xfbml      : true,
          version    : 'v6.0'
        });
          
        FB.AppEvents.logPageView();   
          
      };

  (function(d, s, id){
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.id = id;
    js.src = "https://connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));
</script>

<div id="fb-root"></div>
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v6.0&appId=175499983639989&autoLogAppEvents=1"></script>

Omni-Authorization Callback Controller Facebook Route

def facebook
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
        login!(@user)
        redirect_to "#/home"
        @cart = @user.cart || Cart.create(customer_id: @user.id)
        sign_in @user, event: :authentication
        set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
    else
        session["devise.facebook_data"] = request.env["omniauth.auth"]
        redirect_to new_user_registration_url
    end

end

def failure
    redirect_to root_path
end

User Model

def self.new_with_session(params, session)
    super.tap do |user|
      if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
          user.email = data["email"] if user.email.blank?
      end
    end
end

def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    user.email = auth.info.email
    user.password = Devise.friendly_token[0, 20]
    user.pass_digest = BCrypt::Password.create(user.password)
    user.name = auth.info.name   # assuming the user model has a name
    user.image = auth.info.image # assuming the user model has an image
    user.fname = auth.info.name.split[0]
    user.lname = auth.info.name.split[-1]
    # If you are using confirmable and the provider(s) you use validate emails, 
    # uncomment the line below to skip the confirmation emails.
    # user.skip_confirmation!
    end
end

validates :email, presence: true, uniqueness: true
validates :fname, :lname, presence: true
validates :password, length: { minimum: 6, allow_nil: true }
validates :pass_digest, presence: true
validates :password, length: { minimum: 6, allow_nil: true }
validates :session_token, presence: true, uniqueness: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }

Dynamic Search

Search stores for quick navigation! See the first five stores that match, or view all results!
Search is implemented using query strings taken from the URL to scan the database for queries.

AJAX Call
search(fragment) {
  $.ajax({
    method: "GET",
    url: `/api/stores/search/`,
    data: { fragment: fragment },
  }).then((res) => {
    this.setState({ searchResults: res });
  });
}

Store Controller Search Route

def search
  fragment = params[:fragment]
  @stores = Store.where("name ilike ?", "%#{fragment}%")
  render :search
end

Contact

About

DannyDash is a FullStack project inspired by DoorDash. The twist: All deliveries are estimated by Danny's walking speed. Hope you're not hungry!


Languages

Language:HTML 42.5%Language:Ruby 27.5%Language:JavaScript 18.2%Language:SCSS 9.2%Language:CSS 2.3%Language:CoffeeScript 0.2%