SO LET'S RIDE!
+
+ Ready to ride? We've got you covered.
+ Whether you're exploring for the weekend or commuting daily,
+ we've got the perfect ride to fit your needs!
+
diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..869b60e2f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "rdbg", + "name": "Debug current file with rdbg", + "request": "launch", + "script": "${file}", + "args": [], + "askParameters": true + }, + { + "type": "rdbg", + "name": "Attach with rdbg", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/Gemfile b/Gemfile index 5efe339e6..578bae3f6 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,9 @@ gem "bootsnap", require: false # Use Sass to process CSS # gem "sassc-rails" +#Added gem devise to set up users +gem "devise" + # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # gem "image_processing", "~> 1.2" @@ -75,7 +78,7 @@ group :test do end # Developer Added Gems -gem 'dotenv-rails' +gem 'dotenv-rails', :groups => [:development, :test] # Suggested Potentially Useful Gems # gem 'devise' @@ -83,3 +86,6 @@ gem 'dotenv-rails' # gem 'paperclip # gem 'uniquify' # gem 'will_paginate' +gem "stripe" + +gem 'geocoder' diff --git a/Gemfile.lock b/Gemfile.lock index c3e104938..3924cbf83 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,6 +68,8 @@ GEM tzinfo (~> 2.0) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) + bcrypt (3.1.20) bindex (0.8.1) bootsnap (1.13.0) msgpack (~> 1.2) @@ -84,15 +86,25 @@ GEM childprocess (4.1.0) concurrent-ruby (1.1.10) crass (1.0.6) + csv (3.3.0) debug (1.6.2) irb (>= 1.3.6) reline (>= 0.3.1) + devise (4.9.4) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) digest (3.1.0) dotenv (2.8.1) dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) erubi (1.11.0) + geocoder (1.8.4) + base64 (>= 0.1.0) + csv (>= 3.0.0) globalid (1.0.0) activesupport (>= 5.0) i18n (1.12.0) @@ -115,6 +127,7 @@ GEM matrix (0.4.2) method_source (1.0.0) mini_mime (1.1.2) + mini_portile2 (2.8.8) minitest (5.16.3) msgpack (1.5.6) mysql2 (0.5.4) @@ -132,11 +145,17 @@ GEM digest net-protocol timeout - nio4r (2.5.8) + nio4r (2.7.4) + nokogiri (1.13.8) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) nokogiri (1.13.8-arm64-darwin) racc (~> 1.4) + nokogiri (1.13.8-x86_64-darwin) + racc (~> 1.4) nokogiri (1.13.8-x86_64-linux) racc (~> 1.4) + orm_adapter (0.5.0) public_suffix (5.0.0) puma (5.6.5) nio4r (~> 2.0) @@ -175,6 +194,9 @@ GEM regexp_parser (2.5.0) reline (0.3.1) io-console (~> 0.5) + responders (3.1.1) + actionpack (>= 5.2) + railties (>= 5.2) rexml (3.2.5) rubyzip (2.3.2) selenium-webdriver (4.4.0) @@ -191,9 +213,14 @@ GEM sprockets (>= 3.0.0) stimulus-rails (1.1.0) railties (>= 6.0.0) + stripe (13.1.1) strscan (3.0.4) + tailwindcss-rails (2.0.12) + railties (>= 6.0.0) tailwindcss-rails (2.0.12-arm64-darwin) railties (>= 6.0.0) + tailwindcss-rails (2.0.12-x86_64-darwin) + railties (>= 6.0.0) tailwindcss-rails (2.0.12-x86_64-linux) railties (>= 6.0.0) thor (1.2.1) @@ -204,6 +231,8 @@ GEM railties (>= 6.0.0) tzinfo (2.0.5) concurrent-ruby (~> 1.0) + warden (1.2.9) + rack (>= 2.0.9) web-console (4.2.0) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -222,14 +251,19 @@ GEM zeitwerk (2.6.0) PLATFORMS + # x86_64-darwin-23 arm64-darwin-20 + arm64-darwin-23 + x86_64-darwin-23 x86_64-linux DEPENDENCIES bootsnap capybara debug + devise dotenv-rails + geocoder importmap-rails jbuilder mysql2 (~> 0.5) @@ -239,6 +273,7 @@ DEPENDENCIES selenium-webdriver sprockets-rails stimulus-rails + stripe tailwindcss-rails turbo-rails tzinfo-data diff --git a/Procfile.dev b/Procfile.dev deleted file mode 100644 index 023e98a01..000000000 --- a/Procfile.dev +++ /dev/null @@ -1,2 +0,0 @@ -web: bin/rails server -p 3000 -css: bin/rails tailwindcss:watch diff --git a/README.md b/README.md index 85f6c86ae..9981654ee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ValetBike - Smith College CSC223: Software Engineering\ +Group Members: Maggie, Nafisa, Fernanda, Mackenzie, Maria Jose + Starter App for ValetBike project ## Environment Configuration diff --git a/app/assets/images/background-girl-bike.png b/app/assets/images/background-girl-bike.png new file mode 100644 index 000000000..b07192f4f Binary files /dev/null and b/app/assets/images/background-girl-bike.png differ diff --git a/app/assets/images/bikeLogo.png b/app/assets/images/bikeLogo.png new file mode 100644 index 000000000..a61981a2c Binary files /dev/null and b/app/assets/images/bikeLogo.png differ diff --git a/app/assets/images/bikes.gif b/app/assets/images/bikes.gif new file mode 100644 index 000000000..31b79fa38 Binary files /dev/null and b/app/assets/images/bikes.gif differ diff --git a/app/assets/images/bikes.png b/app/assets/images/bikes.png new file mode 100644 index 000000000..ef1ed2b32 Binary files /dev/null and b/app/assets/images/bikes.png differ diff --git a/app/assets/images/dropdown-image.png b/app/assets/images/dropdown-image.png new file mode 100644 index 000000000..f09b7152b Binary files /dev/null and b/app/assets/images/dropdown-image.png differ diff --git a/app/assets/images/girl-bike.gif b/app/assets/images/girl-bike.gif new file mode 100644 index 000000000..e3e14e042 Binary files /dev/null and b/app/assets/images/girl-bike.gif differ diff --git a/app/assets/images/icon-join.jpg b/app/assets/images/icon-join.jpg new file mode 100644 index 000000000..ba9a944b8 Binary files /dev/null and b/app/assets/images/icon-join.jpg differ diff --git a/app/assets/images/icon-park.JPG b/app/assets/images/icon-park.JPG new file mode 100644 index 000000000..e9cfb9a2e Binary files /dev/null and b/app/assets/images/icon-park.JPG differ diff --git a/app/assets/images/icon-rent.jpg b/app/assets/images/icon-rent.jpg new file mode 100644 index 000000000..209149812 Binary files /dev/null and b/app/assets/images/icon-rent.jpg differ diff --git a/app/assets/images/icon-ride.jpg b/app/assets/images/icon-ride.jpg new file mode 100644 index 000000000..6b773bafc Binary files /dev/null and b/app/assets/images/icon-ride.jpg differ diff --git a/app/assets/images/login-image.jpg b/app/assets/images/login-image.jpg new file mode 100644 index 000000000..0a3f7617b Binary files /dev/null and b/app/assets/images/login-image.jpg differ diff --git a/app/assets/images/loginpage-image.png b/app/assets/images/loginpage-image.png new file mode 100644 index 000000000..88e658eb2 Binary files /dev/null and b/app/assets/images/loginpage-image.png differ diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/images/placeholder-image.jpg b/app/assets/images/placeholder-image.jpg new file mode 100644 index 000000000..373cf3c02 Binary files /dev/null and b/app/assets/images/placeholder-image.jpg differ diff --git a/app/assets/images/registrationBike.png b/app/assets/images/registrationBike.png new file mode 100644 index 000000000..a92c3af11 Binary files /dev/null and b/app/assets/images/registrationBike.png differ diff --git a/app/assets/images/team.JPG b/app/assets/images/team.JPG new file mode 100755 index 000000000..61680fb91 Binary files /dev/null and b/app/assets/images/team.JPG differ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 288b9ab71..c782c72cc 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -13,3 +13,15 @@ *= require_tree . *= require_self */ + + .right-section{ + float: right; + position: relative; + } + + #map{ + position: relative; + width: 77.5%; + height: 600px; + /* float: right; */ + } \ No newline at end of file diff --git a/app/assets/stylesheets/custom.css b/app/assets/stylesheets/custom.css new file mode 100644 index 000000000..6a4a05021 --- /dev/null +++ b/app/assets/stylesheets/custom.css @@ -0,0 +1,138 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body { + margin: 0; + font-family: "Poppins", Arial, sans-serif; + } + + /* Main Content */ + #site-container { + padding-top: 60px; + } + + /* Navbar */ + .topnav { + width: 100%; + position: fixed; + top: 0; + left: 0; + z-index: 1000; + background-color: #ffccd5; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1); + height: 60px; + } + + .logo { + height: 50px; + width: auto; + } + + .valetbike { + flex: 1; + text-align: center; + font-size: 1rem; + font-weight: bold; + font-family: "Arial Black", Gadget, sans-serif; + color: #d80555; + letter-spacing: 1px; + } + + .nav-links { + display: flex; + align-items: center; + } + + .nav-links a { + text-decoration: none; + color: #333; + font-size: 17px; + padding: 14px 16px; + text-align: center; + transition: background-color 0.3s, color 0.3s; + } + + .nav-links a:hover, + .nav-links a.active { + background-color: #ffb6c1; + color: black; + } + + .dropdown { + position: relative; + } + + .dropdown .dropbtn { + background-color: transparent; + border: none; + color: #333; + font-size: 17px; + cursor: pointer; + padding: 14px 16px; + } + + .dropdown:hover .dropbtn { + background-color: #ffb6c1; + color: white; + } + + .dropdown-content { + display: none; + position: absolute; + background-color: white; + min-width: 160px; + box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2); + z-index: 1; + } + + .dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; + } + + .dropdown-content a:hover { + background-color: #ddd; + } + + .dropdown:hover .dropdown-content { + display: block; + } + + + + + /* Mobile Responsiveness */ + @media screen and (max-width: 600px) { + .nav-links a, + .dropdown .dropbtn { + display: none; + } + + .topnav.responsive { + flex-direction: column; + align-items: flex-start; + } + + .topnav.responsive .icon { + position: absolute; + right: 20px; + top: 20px; + } + + .topnav.responsive .nav-links a, + .topnav.responsive .dropdown .dropbtn { + display: block; + text-align: left; + width: 100%; + } + } + \ No newline at end of file diff --git a/app/assets/stylesheets/flexbox-ultralight.css b/app/assets/stylesheets/flexbox-ultralight.css index 3cec74595..96e3b1fd6 100755 --- a/app/assets/stylesheets/flexbox-ultralight.css +++ b/app/assets/stylesheets/flexbox-ultralight.css @@ -120,4 +120,7 @@ -webkit-justify-content: space-around; -ms-justify-content: space-around; justify-content: space-around; -} \ No newline at end of file +} + + + diff --git a/app/assets/stylesheets/pages.css b/app/assets/stylesheets/pages.css index 16bd64b55..51b7accb2 100644 --- a/app/assets/stylesheets/pages.css +++ b/app/assets/stylesheets/pages.css @@ -3,29 +3,789 @@ /************************************/ .page-section { - max-width: 80em; width: 100%; - margin-top: 4em; } -.page-section .section-inner { - margin-right: 1.4em; - margin-left: 1.4em; +/************************************/ +/* Welcome Page */ +/************************************/ +.landing-page { + font-family: 'Montserrat', sans-serif; + color: #333; + padding-top: none; + +} + +.top-section { + position: relative; + background-color: #fbe0e4; + padding: 20px; + min-height: 100vh; + width: 100vw; + display: flex; + justify-content: flex-start; + align-items: center; + overflow: hidden; + +} + +.header { + position: absolute; + top: 20px; + left: 20px; + display: flex; + justify-content: space-between; + align-items: center; + width: 100vw; + padding: 3%; +} + +.title { + font-size: 2.5rem; + color: #ff0055; + font-weight: bold; +} + +.login-button { + border: 1px solid #333; + padding: 14px 40px; + background: transparent; + color: #333; + cursor: pointer; + border-radius: 5px; +} + +/* Main Content */ +.main-content { + max-width: 60%; + text-align: left; + margin-left: 40px; +} + +.tagline { + font-size: 3.8rem; + color: #ff0055; + font-weight: bold; +} + +.description { + font-size: 2rem; + margin: 10px 0; +} + +.get-started-button { + background-color: #333; + color: #fff; + padding: 12px 24px; + border: none; + cursor: pointer; + font-size: 1rem; + margin-top: 15px; + border-radius: 5px; +} + +.right-image { + position: absolute; + bottom: 0; + right: 0; + width: 55%; + height: auto; +} + +.right-image img { + width: 100%; + height: auto; +} + +/* Lower Section */ +.lower-section { + background-color: #ffffff; +} + +.call-to-action { + width: 100vw; + text-align: center; + margin-bottom: 40px; +} + +.call-to-action h2 { + font-size: 2.5rem; + padding: 3%; + color: #ff0055; +} + +.cta-content { + display: flex; + align-items: center; + justify-content: center; + gap: 20px; + margin: 0 auto; + width: 100vw; + height: auto; + padding: 5%; + font-size: xx-large; +} + +.cta-content img { + max-width: 40%; +} + +/* How To Ride */ +.how-to-ride { + background-color: #ffd1dc; + text-align: center; + width: 100vw; + height: auto; + justify-content: center; +} + +.how-to-ride h2 { + color: #f5f5f5; + background-color: #ff0066; + font-size: 2em; + margin-bottom: 20px; + font-weight: bold; +} + +.how-to-ride p { + color: #f5f5f5; + font-size: 2.6rem; +} + +.ride-steps { + display: flex; + justify-content: center; + gap: 30px; + padding: 20px; +} + +.step { + text-align: center; + max-width: 25%; + justify-content: center; + background-color: #f5f5f5; + border-radius: 15px; + padding: 15px; +} + +.step-icon { + width: 100%; + height: auto; + margin-bottom: 10px; + background-color: white; + border-radius: 15px; +} + +.step h3 { + color: #ff0066; + font-size: 2rem; + margin: 10px 0; + font-weight: bold; +} + +.step p { + color: #333; + font-size: 1.2rem; +} +/* Frequently Asked Question */ + +.faq-content { + gap: 20px; + margin: 0 auto; + width: 100vw; + height: auto; + padding: 5%; + font-size: xx-large; +} + +.accordion { + background-color: #eee; + border: solid; + border: 3px; + color: #444; + cursor: pointer; + padding: 18px; + width: 100%; + text-align: left; + border: none; + outline: none; + transition: 0.4s; +} + +.active, .accordion:hover { + background-color: #ccc; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + font-weight: bold; + +} + +.panel { + padding: 0 18px; + background-color: white; + display: none; + overflow: hidden; + border-left: 4px solid #000000; + margin-bottom: 10px; + + +} + +.panel p { + margin: 0; + font-size: 1.5rem; + color: #2b2b2b; + line-height: 1.6; + text-align: left; +} + +/***********************************/ +/* Main Page */ +/***********************************/ +/* General Layout */ +body { + font-family: 'Montserrat', sans-serif; + margin: 0; + padding: 0; + box-sizing: border-box; +} + + +/* Welcome Section */ +.mainpage-welcome { + text-align: center; + padding: 20px; + background-color: #ffffff; +} + +.mainpage-welcome h1 { + font-size: 1.8rem; + color: #003366; + margin-bottom: 10px; + font-style: bold; +} +.mainpage-map-title{ + font-size: 1.8rem; + color: #003366; + margin-bottom: 10px; + text-align: center; +} + +.site-container { + flex: 1; + margin-top: 50px; + background-color: #ffffff; +} + +/* Map and Search Section */ +.mainpage-map-search-container { + display: flex; + justify-content: space-between; + align-items: stretch; + width: 100%; + height: calc(100vh - 120px); +} + +/* Map Section */ +.mainpage-map-container { + flex: 1; + display: flex; + flex-direction: column; + background-color: #ffffff; + height: 100%; +} + +.mainpage-map-wrapper { + flex: 1; + width: 100%; + height: 100%; + border-radius: 10px; + overflow: hidden; +} + +.mainpage-map-iframe { + width: 100%; + height: 100%; + border: 0; +} + +.big-button { + display: inline-block; + background-color: #ff69b4; + text-align: center; + padding: 15px 30px; + font-size: 1.5rem; + font-weight: bold; + text-decoration: none; + border-radius: 5px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, background-color 0.2s; +} + +.big-button:hover { + background-color: #ff1493; + transform: scale(1.05); } -.page-section .section-title { - font-family: var(--title-font); - font-weight: 500; - font-size: 2.5em; - margin-bottom: 1em; +/* Search Section */ +.mainpage-search { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 20px; + background-color: #ffffff; + width: 50%; + height: 100%; +} + +.mainpage-search-title { + font-size: 2.7rem; + font-weight: bold; + margin-bottom: 10px; + color: #003366; +} + +/***********************************/ +/* Station Map */ +/***********************************/ + +.mapsearch-container { + display: flex; +} + +.mapsearch-map { + width: 60%; + height: 100vh; +} + +.mapsearch-form-container { + width: 40%; + padding: 20px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + text-align: center; +} + +.mapsearch-welcome h1 { + font-size: 2rem; + color: #1a73e8; + margin-bottom: 20px; + text-align: center; +} + +.mapsearch-instruction h2 { + font-size: 1.5rem; + color: #1a73e8; + margin-bottom: 20px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} + +.mapsearch-form { + width: 100%; + margin-bottom: 20px; +} + +.mapsearch-input-wrapper { + display: flex; + align-items: center; + border: 2px solid #1a73e8; + border-radius: 5px; + overflow: hidden; +} + +.mapsearch-input-wrapper input { + flex: 1; + padding: 10px; + border: none; + outline: none; +} + +.mapsearch-input-wrapper button { + padding: 10px; + background-color: #1a73e8; + color: white; + border: none; + cursor: pointer; +} + +.mapsearch-input-wrapper button:hover { + background-color: #145dbf; +} + +/***********************************/ +/* Log In Page */ +/***********************************/ + +.outer-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + height: 100%; + width: 100%; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.image-section { + flex: 1; +} + +.image-section img { + width: 100%; + height: 100vh; + object-fit: cover; +} + +.login-form-container { + flex: 1; + background-color: #4da6ff; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + box-sizing: border-box; +} + +.login-form-inner-container { + width: 100%; + max-width: 400px; + text-align: center; + padding: 20px; + color: white; + border-radius: 10px; +} + +.login-form-inner-container h2 { + margin-bottom: 20px; + font-size: 4rem; +} + +.login-form-inner-container h3 { + margin-bottom: 20px; + font-size: 2rem; +} + +.field { + background-color: white; + border-radius: 5px; + padding: 10px; + margin: 10px 0; +} + +.input-field { + width: 100%; + border: none; + outline: none; + font-size: 1.2rem; + color: #000; + background-color: transparent; +} + +.input-field::placeholder { + color: #999; +} + +.input-field:focus { + outline: none; + box-shadow: none; +} + +.loginpage-button { + width: 100%; + padding: 10px; + background-color: #003366; + color: white; + border: none; + border-radius: 5px; + font-size: 1rem; + cursor: pointer; + margin-top: 15px; +} + +/***********************************/ +/* Registration Page */ +/***********************************/ +.top-registration { + position: relative; + background-color: #78a4eb; + padding: 20px; + min-height: 100vh; + width: 100vw; + display: flex; + justify-content: flex-start; + align-items: center; + overflow: hidden; +} + +.title-registration{ + font-family: 'monaco', monospace; + font-size: 2.0rem; + font-weight: bold; + color: white; + position: left; +} + +.form-registration{ + text-align: center; + width: 30vw; + padding: 20px; + position: absolute; + top: 100px; + right: 100px; + background-color: #1f4a91; +} + +.registration-field{ + color: white +} + +.registration-input { + color: black +} + +.signup-button{ + border: 1px solid white; + padding: 14px 40px; + background-color: white; + color: black; + cursor: pointer; + border-radius: 5px; + font-family: 'monaco', monospace; + margin-top: 20px; + width: 205px; + font-size: 1.5rem; + font-weight: bold; + margin-left: 95px; +} + +.registration-image{ + position: absolute; + top: 100px; + left: 150px; + width: 100%; + height: 100%; +} + +.registration-title { + font-size: 2.5rem; + color: white; + font-weight: bold; + position: absolute; +} + +/***********************************/ +/* Account & Settings */ +/***********************************/ + + +.profile-info { + position: relative; + margin: 0 auto; + margin-top: 30px; + margin-bottom: 30px; + border-radius: 30px; + background-color: #fbe0e4; + width: 80vw; + text-align: center; + padding: 30px; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); + justify-content: center; + align-items: center; +} + +.registration-field { + margin: 15px 0; + color: black; +} + +.registration-field input { + width: 70%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 1rem; + margin-top: 5px; +} + +.pink-title { + font-family: 'monaco', monospace; + color: #ff0055; + font-size: 2rem; + font-weight: bold; + margin: 20px; +} + +.update-button { + display: inline-block; + border: 1px solid #b9b9b9; + padding: 14px 40px; + color: black; + background-color: #ffffff; + cursor: pointer; + border-radius: 5px; + font-size: 1rem; + margin: 20px 10px; + text-align: center; + transition: background-color 0.3s ease; +} + +.update-button:hover { + background-color: #d3d3d3; +} + +.delete-account-button, +.logout-button { + display: inline-block; + padding: 14px 40px; + color: white; + background-color: #1f4a91; + border: 1px solid #1f4a91; + cursor: pointer; + border-radius: 5px; + font-size: 1rem; + margin: 20px 10px; + text-align: center; + transition: background-color 0.3s ease; +} + +.delete-account-button:hover, +.logout-button:hover { + background-color: #17366d; +} + +.return-link { + display: inline-block; + background-color: #ff0055; + color: white; + padding: 14px 40px; + border-radius: 5px; + text-decoration: none; + font-size: 1rem; + margin: 20px 10px; + text-align: center; + transition: background-color 0.3s ease; +} + +.return-link:hover { + background-color: #cc0044; } .record-row { + background-color: #d73f72e5; + border: none; + padding: 0.5em 1.25em 0.5em 1.25em; + font-size: 0.70em; + line-height: 1.75em; + border-radius: 12px; + text-align: center; + text-decoration: none; + border-radius: 12px; +} + +.record-row:hover{ + box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); +} + +/* this is alings two given buttons to the center */ +.button-container { + display: flex; + justify-content: center; + gap: 10px; + width: 100%; +} + +/* this is for the show button */ +.record-row2 { + background-color: #e089a6e5; + border: solid white; padding: 0.5em 1.25em 0.5em 1.25em; + font-size: 0.70em; + line-height: 1.75em; + text-align: center; + text-decoration: none; + display: inline-block; + border-radius: 12px; +} + +.return-stations-button { + background-color: #d73f72e5; + border: none; + padding: 0.5em 1.25em 0.5em 1.25em; + font-size: 1.0em; + line-height: 1.75em; + border-radius: 12px; + text-align: center; + text-decoration: none; + border-radius: 12px; + width: 200px; + position: relative; + left: 30px; + top: 30px; +} + + +/***********************************/ +/* SHOW STATIONS PAGE */ +/***********************************/ + +.stations-title { + background-color: #ffccd5; + font-family: 'Monaco', monospace; + color: #ff0055; + font-size: 3rem; + font-weight: bold; + height: 10vh; + width: 100%; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} + + +.loginpage-button:hover { + background-color: #002244; +} + +/***********************************/ +/* Stations Page */ +/***********************************/ + +.stations-page { + width: 100%; + background-color: #f9f9f9; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; +} + + +.station-button { + padding: 0.5em 1.25em; font-size: 0.85em; line-height: 1.75em; + border-radius: 25px; + background-color: #fff; + border: 1px solid #ddd; + cursor: pointer; + transition: background-color 0.3s ease-in-out; +} + +.station-button:hover { + background-color: #f0f0f0; } + .record-row.even { background: var(--medium-white-color); } @@ -35,6 +795,431 @@ } +.return-stations-button { + background-color: #d73f72e5; + border: none; + padding: 0.5em 1.25em; + font-size: 1rem; + line-height: 1.75em; + border-radius: 12px; + text-align: center; + text-decoration: none; + width: 200px; + position: relative; + left: 30px; + top: 30px; + cursor: pointer; + transition: background-color 0.3s ease-in-out; +} + +.return-stations-button:hover { + background-color: #c73168; +} + +/***********************************/ +/* Book Ride Page */ +/***********************************/ +.bookRide-container { + text-align: center; + margin: 20px auto; + width: 80%; + height: 80vh; + background-color: #fbe0e4; + border-radius: 10px; + padding: 20px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + align-items: center; + align-content: center; +} + +.bookRide-title { + font-size: 2rem; + font-weight: bold; + color: #e91e63; + margin-bottom: 10px; +} + +.bookRide-station-name { + font-weight: bold; + color: #007bff; +} + +.bookRide-btn { + display: inline-block; + margin: 10px 0; + padding: 10px 15px; + font-size: 1rem; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.bookRide-change-station { + background-color: #007bff; + color: white; +} + +.bookRide-primary { + background-color: #4caf50; + color: white; +} + +.bookRide-secondary { + background-color: #ffc107; + color: black; +} + +.bookRide-form-group { + margin-bottom: 15px; +} + +.bookRide-form-label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +.bookRide-form-input { + width: 50%; + padding: 8px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 1rem; +} + +.bookRide-button-group { + display: flex; + justify-content: center; + gap: 10px; +} + +.bookRide-step { + font-size: 1.5rem; + font-weight: bold; + margin-top: 20px; +} + +.bookRide-instructions { + font-size: 1rem; + margin-bottom: 20px; + color: #555; +} + + +/***********************************/ +/* Trip Show Page */ +/***********************************/ +.show-trip-body { + font-family: 'Arial', sans-serif; + margin: 0; + padding: 0; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #ffffff; + box-sizing: border-box; +} + +.show-trip-wrapper { + max-width: 100vw; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: #ffffff; +} + +.show-trip-header { + width: 100%; + height: 25%; + background-color: #ff007f; + color: white; + font-size: 3rem; + font-weight: bold; + padding: 15px; + text-align: center; + margin-bottom: 20px; +} + +.show-trip-message { + width: 90%; + padding: 20px; + font-size: 1.5rem; + line-height: 1.8; + margin-bottom: 20px; + text-align: center; +} + +.show-trip-details { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 15px; + width: 90%; + padding: 15px; + margin-bottom: 20px; +} + +.show-trip-detail { + background-color: #fce4ec; + padding: 10px 20px; + font-size: 1.2rem; + font-weight: bold; + color: #ff007f; + border-radius: 10px; + text-align: center; + display: flex; +} + +.show-trip-actions { + width: 90%; + display: flex; + justify-content: center; + padding: 20px; + margin-top: 20px; + display: flex; +} + +.show-trip-end-trip-btn { + background-color: #007bff; + color: white; + padding: 15px 30px; + font-size: 1.5rem; + font-weight: bold; + border: none; + border-radius: 8px; + cursor: pointer; + text-decoration: none; +} + +.show-trip-end-trip-btn:hover { + background-color: #0056b3; +} + + + +/***********************************/ +/* Payment New Page */ +/***********************************/ + +.paypage-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + width: 100vw; + background-color: #004b87; + margin: 0; +} + +.paypage-card { + background-color: #87c8f5; + border-radius: 10px; + text-align: center; + max-width: 60%; + height: 80%; + box-sizing: border-box; +} + +.paypage-header { + padding: 10px; + border-radius: 10px 10px 0 0; +} + +.paypage-title { + font-size: 2rem; + font-weight: bold; + color: white; +} + +.paypage-price { + background-color: white; + padding: 20px; + height: 70%; + display: flex; + justify-content: center; + align-items: center; + text-align: center; +} + +.paypage-price h3 { + font-size: 3rem; + font-weight: bold; + color: #000; +} + +.paypage-button-container { + padding: 20px; + border-radius: 0 0 10px 10px; +} + +.paypage-button { + padding: 15px 40px; + background-color: #00aaff; + color: white; + text-decoration: none; + font-size: 1.2rem; + font-weight: bold; + border-radius: 5px; +} + +.paypage-button:hover { + background-color: #0088cc; +} + +/***********************************/ +/* Payment Success Page */ +/***********************************/ + +.confirmation-container { + display: grid; + grid-template-columns: 1fr 1fr; + height: 100vh; + background-color: #ffffff; + + .left-section { + display: flex; + justify-content: center; + align-items: center; + background-color: #fff; + + img { + max-width: 100%; + height: auto; + border-radius: 10px; + } + } + + .right-section { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: 20px; + + h1 { + color: #0b5394; + font-size: 2.5rem; + font-weight: bold; + margin-bottom: 20px; + } + + .go-back-button { + display: inline-block; + background-color: #0b5394; + color: #fff; + padding: 15px 30px; + text-decoration: none; + border-radius: 10px; + font-size: 1.2rem; + font-weight: bold; + transition: background-color 0.3s; + + &:hover { + background-color: #073763; + } + } + } +} + + +/***********************************/ +/* Meet The Team Page */ +/***********************************/ + + +.blue-section { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: #d80555; + color: white; + padding: 20px; + text-align: center; + height: 100vh; + overflow: hidden; +} + +.blue-section h1 { + font-size: 3rem; + margin: 0; +} + +.blue-section img { + max-width: 70%; + max-height: calc(100vh - 100px); + height: auto; + margin-top: 20px; + border-radius: 10px; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.3); +} + + +.white-section { + background-color: #ffffff; + padding: 40px; + display: flex; + flex-wrap: nowrap; + justify-content: space-evenly; + align-items: flex-start; + gap: 15px; + overflow-x: auto; +} + +.team-card { + background-color: #d80555; + color: #d80555; + border-radius: 10px; + padding: 20px; + margin: 10px; + width: 220px; + text-align: center; + box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.2); + transition: transform 0.3s ease, box-shadow 0.3s ease; + overflow: hidden; +} + +.team-card:hover { + transform: scale(1.05); + box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.3); +} + +.team-card h2 { + margin: 10px 0; + font-size: 1.5rem; + font-weight: bold; + background-color: #ffccd5; + padding: 5px; + border-radius: 5px; + display: inline-block; + width: 100%; +} + +.team-card p { + color: white; + margin: 5px 0; + font-size: 1rem; + line-height: 1.4; + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; +} + +@media (max-width: 600px) { + .white-section { + flex-wrap: wrap; + justify-content: center; + } + + .team-card { + width: 90%; + } +} + + + /***********************************/ /* Responsiveness */ /***********************************/ @@ -50,3 +1235,57 @@ } } + +.grid-container { + display: grid; + gap: 50px 100px; + grid-template-columns: auto auto auto; + padding: 10px; +} + +.grid-item { + background-color: rgb(244, 239, 243); + padding: 20px; + font-size: 30px; + text-align: center; +} + +.center-section-title{ + text-align: center; +} + +/* Responsive Design Payment Views */ +@media (max-width: 768px) { + .paypage-title { + font-size: 1.5rem; + } + + .paypage-price h3 { + font-size: 2rem; + } + + .paypage-button { + padding: 12px 30px; + font-size: 1rem; + } +} + +@media (max-width: 480px) { + .paypage-title { + font-size: 1.2rem; + } + + .paypage-price h3 { + font-size: 1.8rem; + } + + .paypage-button { + padding: 10px 20px; + font-size: 0.9rem; + } +} + + + + + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d12a..3c6819b61 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,10 @@ class ApplicationController < ActionController::Base -end + before_action :configure_permitted_parameters, if: :devise_controller? + + protected + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: [:firstName, :lastName]) + devise_parameter_sanitizer.permit(:account_update, keys:[:firstName, :lastName]) + end + end \ No newline at end of file diff --git a/app/controllers/bikes_controller.rb b/app/controllers/bikes_controller.rb new file mode 100644 index 000000000..9ec47d342 --- /dev/null +++ b/app/controllers/bikes_controller.rb @@ -0,0 +1,13 @@ +class BikesController < ApplicationController + + def index + @bikes = Bike.all + end + + def show + @bike = Bike.find_by(current_station_id: params[:current_station_id]) + end + + + +end diff --git a/app/controllers/customer_controller.rb b/app/controllers/customer_controller.rb new file mode 100644 index 000000000..329c84bf1 --- /dev/null +++ b/app/controllers/customer_controller.rb @@ -0,0 +1,4 @@ +class CustomerController < ApplicationController + def index + end +end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 000000000..e5579796b --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,8 @@ +class HomeController < ApplicationController + def index + render + end + def stripe_payment + end + +end diff --git a/app/controllers/map_test_controller.rb b/app/controllers/map_test_controller.rb new file mode 100644 index 000000000..ddd77b11d --- /dev/null +++ b/app/controllers/map_test_controller.rb @@ -0,0 +1,33 @@ +class MapTestController < ApplicationController + def index + if params[:location].present? + # Geocode the user's input address + user_location = Geocoder.search(params[:location]).first&.coordinates + + if user_location + # Find the nearest stations + @stations = Station.near(user_location, 10).limit(3) + else + @stations = [] + end + else + @stations = Station.all + end + end + + def map_test + if params[:location].present? + user_location = Geocoder.search(params[:location]).first&.coordinates + @stations = user_location ? Station.near(user_location, 10).limit(3) : [] + else + @stations = [] # Ensure it's empty initially + end + end + + + end + + + + + \ No newline at end of file diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb new file mode 100644 index 000000000..3e924a494 --- /dev/null +++ b/app/controllers/payments_controller.rb @@ -0,0 +1,36 @@ +class PaymentsController < ApplicationController + def new + @trip = Trip.find_by_id(params[:param_1]) + @tripPrice = Integer(@trip.price * 100) + # trip must be greater than 50 cents + @tripPrice = @tripPrice > 50 ? @tripPrice : 51 + # create price object + priceObject = Stripe::Price.create({ + currency: 'usd', + unit_amount: @tripPrice, + product_data: {name: 'Trip'}, + }) + # create a new link + @paymentLink = Stripe::PaymentLink.create({ + line_items: [ + { + price: priceObject, + quantity: 1 + }, + ], + after_completion: { + type: 'redirect', + redirect: {url: 'http://localhost:3000/payments/success'}, + } + }) + end + + def create + end + + def edit + end + + def update + end +end diff --git a/app/controllers/stations_controller.rb b/app/controllers/stations_controller.rb index 88090af16..7e6e5a730 100644 --- a/app/controllers/stations_controller.rb +++ b/app/controllers/stations_controller.rb @@ -1,7 +1,14 @@ class StationsController < ApplicationController - + def index @stations = Station.all.order(identifier: :asc) + end - + + #gets the current_bike id and then displays that bike on the view template + def show + @current_station = Station.find_by(identifier: params[:identifier]) + @bikes = Bike.where(current_station_id: @current_station.identifier) + end + end diff --git a/app/controllers/stations_map_controller.rb b/app/controllers/stations_map_controller.rb new file mode 100644 index 000000000..bcfc85665 --- /dev/null +++ b/app/controllers/stations_map_controller.rb @@ -0,0 +1,17 @@ +class MapTestController < ApplicationController + def index + if params[:location].present? + # Geocode the user's input address + user_location = Geocoder.search(params[:location]).first&.coordinates + + if user_location + # Find the nearest stations within a 10 km radius + @stations = Station.near(user_location, 10) + else + @stations = [] + end + else + @stations = Station.all + end + end +end \ No newline at end of file diff --git a/app/controllers/team_controller.rb b/app/controllers/team_controller.rb new file mode 100644 index 000000000..e94a6ebc0 --- /dev/null +++ b/app/controllers/team_controller.rb @@ -0,0 +1,8 @@ +class TeamController < ApplicationController + def index + @team_members = [ + + ] + end + end + \ No newline at end of file diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb new file mode 100644 index 000000000..2a0a2c83d --- /dev/null +++ b/app/controllers/trips_controller.rb @@ -0,0 +1,100 @@ +class TripsController < ApplicationController + # shows a list of trips + def index + # select only the trips that you made with ID + # @current_user = User.find_by(id: params[:id]) + @trips = Trip.where(user_id: current_user.id) + end + + # shows a single trip + def show + @trip = Trip.find_by(identifier: params[:identifier]) + end + + def new + @bike = Bike.find_by(identifier: params[:identifier]) + if @bike + @trip = Trip.new( + bike_used_id: @bike.identifier, + start_station_id: @bike.current_station.identifier, + user_id: current_user.id, + start_time: Time.current + ) + else + flash[:alert] = "Bike not found" + redirect_to trips_path + end + end + + def create + @bike = Bike.find_by(identifier: params[:identifier]) + if @bike + @trip = Trip.new( + bike_used_id: @bike.identifier, + start_station_id: @bike.current_station.identifier, + user_id: current_user.id, + start_time: Time.current + ) + + if @trip.save + # Update the bike's status or location if necessary + bike = Bike.find_by(identifier: params[:identifier]) + bike.current_station.docked_bikes.delete(Bike.find_by(identifier: bike.identifier)) + render 'show' + else + render plain: "FAILED: #{@trip.errors.full_messages.join(', ')}" + end + else + flash[:alert] = "Bike not found" + redirect_to trips_path + end + end + + def edit + @stations = Station.all.order(identifier: :asc) + if Trip.find_by_id(params[:id]).present? + @trip = Trip.find_by(user_id: current_user.id) + @bike = Bike.find_by(identifier: @trip.bike_used); + end + + end + + def update + @trip = Trip.where(user_id: current_user.id).order(created_at: :desc).first + + if @trip.nil? + flash[:alert] = "Trip not found" + redirect_to trips_index_path and return + end + + station = Station.find_by(identifier: params[:identifier]) + + if station.nil? + flash[:alert] = "Station not found" + redirect_to edit_trips_path(@trip) and return + end + + bike = Bike.find_by(identifier: @trip.bike_used.identifier) + + if bike.nil? + flash[:alert] = "Bike not found" + redirect_to trips_edit_path(@trip) and return + end + + # station = Station.find_by(params[:identifier]) + station.docked_bikes << bike + @trip.update(end_time: Time.now, end_station_id: station.identifier) + @trip.update(price: calculate_price(@trip.id)) + @trip.save + #user is returned payment page + #redirect_to payments new path with the trip object as a param and do the calculations of price + redirect_to new_payment_path(param_1: @trip.id) + end + + def calculate_price(trip_id) + trip = Trip.find_by(id: trip_id) + rideTime = trip.end_time - trip.start_time + rideTime * 0.004166666667 + end + +end diff --git a/app/helpers/bike_helper.rb b/app/helpers/bike_helper.rb new file mode 100644 index 000000000..554085ae5 --- /dev/null +++ b/app/helpers/bike_helper.rb @@ -0,0 +1,2 @@ +module BikeHelper +end diff --git a/app/helpers/bikes_helper.rb b/app/helpers/bikes_helper.rb new file mode 100644 index 000000000..a2645e4e1 --- /dev/null +++ b/app/helpers/bikes_helper.rb @@ -0,0 +1,2 @@ +module BikesHelper +end diff --git a/app/helpers/customer_helper.rb b/app/helpers/customer_helper.rb new file mode 100644 index 000000000..ec7116d47 --- /dev/null +++ b/app/helpers/customer_helper.rb @@ -0,0 +1,2 @@ +module CustomerHelper +end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb new file mode 100644 index 000000000..23de56ac6 --- /dev/null +++ b/app/helpers/home_helper.rb @@ -0,0 +1,2 @@ +module HomeHelper +end diff --git a/app/helpers/map_test_helper.rb b/app/helpers/map_test_helper.rb new file mode 100644 index 000000000..64d14f4c7 --- /dev/null +++ b/app/helpers/map_test_helper.rb @@ -0,0 +1,2 @@ +module MapTestHelper +end diff --git a/app/helpers/payments_helper.rb b/app/helpers/payments_helper.rb new file mode 100644 index 000000000..c1b884f80 --- /dev/null +++ b/app/helpers/payments_helper.rb @@ -0,0 +1,2 @@ +module PaymentsHelper +end diff --git a/app/helpers/stations_map_helper.rb b/app/helpers/stations_map_helper.rb new file mode 100644 index 000000000..b575c7e56 --- /dev/null +++ b/app/helpers/stations_map_helper.rb @@ -0,0 +1,2 @@ +module StationsMapHelper +end diff --git a/app/helpers/team_helper.rb b/app/helpers/team_helper.rb new file mode 100644 index 000000000..e0bd96f69 --- /dev/null +++ b/app/helpers/team_helper.rb @@ -0,0 +1,2 @@ +module TeamHelper +end diff --git a/app/helpers/trips_helper.rb b/app/helpers/trips_helper.rb new file mode 100644 index 000000000..04f333d46 --- /dev/null +++ b/app/helpers/trips_helper.rb @@ -0,0 +1,2 @@ +module TripsHelper +end diff --git a/app/images/valetbikeIcon.ico b/app/images/valetbikeIcon.ico new file mode 100644 index 000000000..8a8cf30f2 Binary files /dev/null and b/app/images/valetbikeIcon.ico differ diff --git a/app/images/valetbikeIcon.jpg b/app/images/valetbikeIcon.jpg new file mode 100644 index 000000000..8a8cf30f2 Binary files /dev/null and b/app/images/valetbikeIcon.jpg differ diff --git a/app/javascript/application.js b/app/javascript/application.js index 0d7b49404..5485246f6 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,4 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" import "controllers" + diff --git a/app/models/bike.rb b/app/models/bike.rb index f3648e343..6fc684022 100644 --- a/app/models/bike.rb +++ b/app/models/bike.rb @@ -2,6 +2,10 @@ class Bike < ApplicationRecord validates_presence_of :identifier validates_uniqueness_of :identifier - belongs_to :current_station, class_name: :Station, foreign_key: :current_station_id, optional: true - + belongs_to :current_station, class_name: :Station, foreign_key: :current_station_id, primary_key: :identifier, optional: true + has_many :trips_taken, class_name: :Trip, foreign_key: :bike_used_id, primary_key: :identifier + + def current_station + Station.find_by(identifier: current_station_id) + end end diff --git a/app/models/customer.rb b/app/models/customer.rb new file mode 100644 index 000000000..7cfe8a495 --- /dev/null +++ b/app/models/customer.rb @@ -0,0 +1,3 @@ +class Customer < ApplicationRecord + has_many :trips_taken, class_name: :Trip, foreign_key: :customer_id +end diff --git a/app/models/payment.rb b/app/models/payment.rb new file mode 100644 index 000000000..eab5c2611 --- /dev/null +++ b/app/models/payment.rb @@ -0,0 +1,2 @@ +class Payment < ApplicationRecord +end diff --git a/app/models/station.rb b/app/models/station.rb index 64b9eaada..1ec043e56 100644 --- a/app/models/station.rb +++ b/app/models/station.rb @@ -1,9 +1,15 @@ class Station < ApplicationRecord + validates_presence_of :identifier, :name, :address validates_uniqueness_of :identifier - has_many :docked_bikes, class_name: :Bike, foreign_key: :current_station_id + has_many :docked_bikes, class_name: :Bike, foreign_key: :current_station_id, primary_key: :identifier + has_many :started_trips, class_name: :Trip, foreign_key: :start_station_id, primary_key: :identifier + has_many :ended_trips, class_name: :Trip, foreign_key: :end_station_id, primary_key: :identifier + + geocoded_by :address + after_validation :geocode, if: :will_save_change_to_address? end diff --git a/app/models/trip.rb b/app/models/trip.rb new file mode 100644 index 000000000..c00f7e2ca --- /dev/null +++ b/app/models/trip.rb @@ -0,0 +1,31 @@ +class Trip < ApplicationRecord + # validates_presence_of :identifier + # validates_uniqueness_of :identifier + validates_presence_of :bike_used_id, + :start_station_id, + :user_id + + + belongs_to :start_station, class_name: :Station, foreign_key: :start_station_id, primary_key: :identifier, optional: false + belongs_to :end_station, class_name: :Station, foreign_key: :end_station_id, primary_key: :identifier, optional: true + belongs_to :bike_used, class_name: :Bike, foreign_key: :bike_used_id, primary_key: :identifier, optional: false + belongs_to :user, class_name: :User, foreign_key: :user_id, primary_key: :id, optional: false + + before_create :set_identifier + + def formatted_start_time + time = start_time.is_a?(Integer) ? Time.at(start_time) : start_time + time.strftime("%B %d, %Y at %I:%M %p") if time + end + + def formatted_end_time + time = start_time.is_a?(Integer) ? Time.at(start_time) : start_time + time.strftime("%B %d, %Y at %I:%M %p") if time + end + + private + + def set_identifier + self.identifier = self.bike_used.identifier if self.bike_used + end +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 000000000..2822bfb5e --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,7 @@ +class User < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + has_many :trip_history, class_name: :Trip, foreign_key: :user_id, primary_key: :identifier + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable +end diff --git a/app/views/bikes/_row.html.erb b/app/views/bikes/_row.html.erb new file mode 100644 index 000000000..a4b031626 --- /dev/null +++ b/app/views/bikes/_row.html.erb @@ -0,0 +1,16 @@ + + +
Bike <%=bike.identifier%>
+ + +Find me in app/views/bikes/edit.html.erb
+Find me in app/views/bikes/index.html.erb
+Find me in app/views/customer/index.html.erb
+Welcome <%= @email %>!
+ +You can confirm your account email through the link below:
+ +<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 000000000..32f4ba803 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +Hello <%= @email %>!
+ +<% if @resource.try(:unconfirmed_email?) %> +We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.
+<% else %> +We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 000000000..b41daf476 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +Hello <%= @resource.email %>!
+ +We're contacting you to notify you that your password has been changed.
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 000000000..f667dc12f --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +Hello <%= @resource.email %>!
+ +Someone has requested a link to change your password. You can do this through the link below.
+ +<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
+ +If you didn't request this, please ignore this email.
+Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 000000000..41e148bf2 --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +Hello <%= @resource.email %>!
+ +Your account has been locked due to an excessive number of unsuccessful sign in attempts.
+ +Click the link below to unlock your account:
+ +<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 000000000..5fbb9ff0a --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +
+No nearby stations found.
+ <% end %> +
+ Ready to ride? We've got you covered.
+ Whether you're exploring for the weekend or commuting daily,
+ we've got the perfect ride to fit your needs!
+
+
+ Hop on and join our mission to build stronger communities, protect the environment, and help riders make a positive impact with every trip.
+ Together, we can make every journey count!
Download the app or visit the website to sign up.
+ Choose a membership or pay-as-you-go plan.
+ Verify your details and activate your account.
Unlock your bike with the app.
Give the bike a quick inspection to ensure it is ready to ride.
Take a moment to read through bike safety guidelines before you start your journey.
Enjoy the Ride!
Make the most of your trip by keeping it within the allowed time limit to avoid any extra fees.
+ Remember: The timer continues running even when you stop, so plan accordingly!
+
End your trip by parking at a designated hub.
+ Confirm the bike is securely parked in the app to complete your ride.
It’s the fastest, most affordable, and eco-friendly way to get around. Skip the traffic, save money, stay active, and help create a greener future—all with just one ride.
+Valet Bike offers various pricing options to suit your needs, including pay-per-ride and monthly or annual memberships.
+ Pay-per-ride rates start at $2.50 for the first 30 minutes. Additional charges apply for extended rides.
+ Properly ending your trip helps maintain the system's efficiency and prevents additional charges.
+ Park at a Designated Hub: Locate and return the bike to an official Valet Bike station.
+ Secure the Bike: Wrap the tether around the bike rack and insert it into the closed wheel lock to ensure it's properly locked.
+ End the Trip in the App: Follow the app's instructions to finalize your trip. This may include taking a photo to confirm the bike is correctly secured.
+
No nearby stations found.
+ <% end %> +No nearby stations found.
+ <% end %> +Find me in app/views/payments/edit.html.erb
+
+ Find me in app/views/payments/update.html.erb
+mneedham@smith.edu
+Computer Science & Music
+nmohamed@smith.edu
+Smith College
+Class of 2026
+Computer Science
+jaltamiranobello@smith.edu
+Smith College
+Class of 2026
+Computer Science
+mmartin@smith.edu
+Smith College
+Computer Science
+ +igart24m@mtholyoke.edu
+Mount Holyoke College
+Class of 2027
+Computer Science
+| Trip ID | +Start Station | +End Station | +Bike ID | +Time Rented | +
|---|---|---|---|---|
| <%= trip.id %> | +<%= trip.start_station.name %> | +<%= trip.end_station&.name || 'In Progress' %> | +<%= trip.bike_used.identifier %> | +<%= trip.formatted_start_time %> | +
Booking from <%= @bike.current_station.name %> Station
+ <%= link_to "".html_safe, stations_index_path %> + +Booking Bike ID: <%= @bike.identifier %>
+ <%= link_to "".html_safe, stations_show_path(identifier: @bike.current_station.identifier )%> + + + + + +