项目作者: DannyRH27

项目描述 :
DannyDash is a FullStack project inspired by DoorDash. The twist: All deliveries are estimated by Danny's walking speed. Hope you're not hungry!
高级语言: HTML
项目地址: git://github.com/DannyRH27/DannyDash.git
创建时间: 2020-04-21T00:30:47Z
项目社区:https://github.com/DannyRH27/DannyDash

开源协议:

下载


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.

  1. calculateDispatchDistance(order) {
  2. const { updateOrder } = this.props;
  3. function callback(response, status) {
  4. if (status == "OK") {
  5. var origins = response.originAddresses;
  6. var destinations = response.destinationAddresses;
  7. for (var i = 0; i < origins.length; i++) {
  8. var results = response.rows[i].elements;
  9. for (var j = 0; j < results.length; j++) {
  10. var element = results[j];
  11. var distance = element.distance.text;
  12. var durationText = element.duration.text;
  13. var duration = element.duration.value;
  14. var from = origins[i];
  15. var to = destinations[j];
  16. }
  17. }
  18. var date = new Date(order.createdAt);
  19. date.setMinutes(date.getMinutes() + duration / 60);
  20. var minutes = date.getMinutes();
  21. var hours = date.getHours();
  22. var ampm = hours >= 12 ? "pm" : "am";
  23. hours = hours % 12;
  24. hours = hours ? hours : 12; // the hour '0' should be '12'
  25. minutes = minutes < 10 ? "0" + minutes : minutes;
  26. var strTime = hours + ":" + minutes + " " + ampm;
  27. if (order.deliveryEta === null) {
  28. const newOrder = Object.assign({}, order)
  29. newOrder.deliveryEta = strTime
  30. newOrder.deliveredDate = date
  31. updateOrder(newOrder)
  32. }
  33. this.setState({ duration: duration });
  34. this.setState({ ETA: strTime });
  35. this.setState({ durationText: durationText });
  36. this.setState({ distance: distance });
  37. this.setState({ deliveryDate: date })
  38. this.initMap(order);
  39. }
  40. }
  41. const { currentUser } = this.props;
  42. var origin1 = order.store.address;
  43. var destinationA = currentUser.address;
  44. var service = new google.maps.DistanceMatrixService();
  45. service.getDistanceMatrix(
  46. {
  47. origins: [origin1],
  48. destinations: [destinationA],
  49. travelMode: "WALKING",
  50. unitSystem: google.maps.UnitSystem.IMPERIAL,
  51. },
  52. callback.bind(this)
  53. );
  54. }
  55. initMap(order) {
  56. const { currentUser } = this.props;
  57. var location = { lat: 37.75383, lng: -122.401772 };
  58. var map = new google.maps.Map(document.getElementById("order-map"), {
  59. zoom: 15,
  60. center: location,
  61. });
  62. var geocoder = new google.maps.Geocoder();
  63. geocoder.geocode({ address: currentUser.address }, function (
  64. results,
  65. status
  66. ) {
  67. var homeMark = <img src="https://dannydash-seeds.s3-us-west-1.amazonaws.com/Home.png" alt=""/>
  68. map.setCenter(results[0].geometry.location);
  69. var marker = new google.maps.Marker({
  70. map: map,
  71. position: results[0].geometry.location,
  72. });
  73. this.setState({address: results[0].formatted_address})
  74. // }
  75. }.bind(this))
  76. geocoder.geocode({ address: order.store.address }, function (
  77. results,
  78. status
  79. ) {
  80. if (status == google.maps.GeocoderStatus.OK) {
  81. var storeMark = <img src="https://dannydash-seeds.s3-us-west-1.amazonaws.com/Store.png" alt=""/>
  82. var marker = new google.maps.Marker({
  83. map: map,
  84. position: results[0].geometry.location,
  85. });
  86. }
  87. }.bind(this))
  88. }

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

  1. <script>
  2. window.fbAsyncInit = function() {
  3. FB.init({
  4. appId : '175499983639989',
  5. cookie : true,
  6. xfbml : true,
  7. version : 'v6.0'
  8. });
  9. FB.AppEvents.logPageView();
  10. };
  11. (function(d, s, id){
  12. var js, fjs = d.getElementsByTagName(s)[0];
  13. if (d.getElementById(id)) {return;}
  14. js = d.createElement(s); js.id = id;
  15. js.src = "https://connect.facebook.net/en_US/sdk.js";
  16. fjs.parentNode.insertBefore(js, fjs);
  17. }(document, 'script', 'facebook-jssdk'));
  18. </script>
  19. <div id="fb-root"></div>
  20. <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



  1. def facebook
  2. @user = User.from_omniauth(request.env["omniauth.auth"])
  3. if @user.persisted?
  4. login!(@user)
  5. redirect_to "#/home"
  6. @cart = @user.cart || Cart.create(customer_id: @user.id)
  7. sign_in @user, event: :authentication
  8. set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
  9. else
  10. session["devise.facebook_data"] = request.env["omniauth.auth"]
  11. redirect_to new_user_registration_url
  12. end
  13. end
  14. def failure
  15. redirect_to root_path
  16. end



User Model



  1. def self.new_with_session(params, session)
  2. super.tap do |user|
  3. if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
  4. user.email = data["email"] if user.email.blank?
  5. end
  6. end
  7. end
  8. def self.from_omniauth(auth)
  9. where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
  10. user.email = auth.info.email
  11. user.password = Devise.friendly_token[0, 20]
  12. user.pass_digest = BCrypt::Password.create(user.password)
  13. user.name = auth.info.name # assuming the user model has a name
  14. user.image = auth.info.image # assuming the user model has an image
  15. user.fname = auth.info.name.split[0]
  16. user.lname = auth.info.name.split[-1]
  17. # If you are using confirmable and the provider(s) you use validate emails,
  18. # uncomment the line below to skip the confirmation emails.
  19. # user.skip_confirmation!
  20. end
  21. end
  22. validates :email, presence: true, uniqueness: true
  23. validates :fname, :lname, presence: true
  24. validates :password, length: { minimum: 6, allow_nil: true }
  25. validates :pass_digest, presence: true
  26. validates :password, length: { minimum: 6, allow_nil: true }
  27. validates :session_token, presence: true, uniqueness: true
  28. validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }






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

  1. search(fragment) {
  2. $.ajax({
  3. method: "GET",
  4. url: `/api/stores/search/`,
  5. data: { fragment: fragment },
  6. }).then((res) => {
  7. this.setState({ searchResults: res });
  8. });
  9. }



Store Controller Search Route

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

Contact