This is a clone of AirBnb's web app, with a Star Wars theme. Instead of specific countries, users of StarWarsBnb can view and book a stay on a planet from the movies hosted by a character from the movies.
Users can:
- Stay secure through account creation
- View planets that are available to book
As this is a full-stack project, several technologies were used, including:
- React
- Redux
- Javascript
- JSX
- HTML
- CSS
- SASS
- Ruby
- Ruby on Rails
- JBuilder
- SQL
The current version of AirBnb's CSS has seemingly grown without refactoring, as almost every element on the page has a CSS tag of !important
attached. For those who are unfamiliar with this tag, it is added when the CSS is not behaving the way you would expect it to, and is kind of a master override for a particular style. For instance, you really want links to be teal, but for some reason they aren't, due to some other property they are inheriting deep in the CSS stylesheets. So, you add !important
and your links are magically teal.
I was worried that I would have to make use of this brute-force tag, but I happily did not. I have achieved an uncanny resemblance to AirBnb's site without the use of a single !important
tag.
Using a CSS pre-processor called SASS allowed me to efficiently carry out many actions that you could not normally do in CSS, such as nesting styles, and setting variables that can be used in several different selectors. For instance, I set variables in my SASS files to keep track of AirBnb color themes, and used them throughout several files
{
$airBnbPink: #FF5A5F;
$airBnbTeal: #008489;
$airBnbGray: #484848;
$airBnbLightGray: rgb(120, 120, 120);
$airBnbPurple: rgb(87, 37, 51);
$airBnbBorderColor: #DBDBDB;
$standardFontSize: 19px;
$divider: 1px solid $airBnbBorderColor;
.content {
width: 100%;
.index {
width: 90%;
margin: 0 auto;
h2 {
margin-top: 2em;
color: $airBnbGray;
font-weight: 500;
padding-left: 0.5em;
margin-bottom: 1em;
}
}
The use of nesting styles was also extremely helpful in making sure that styles were not inherited unintentionally. For instance, nesting the h2
selector inside the .index
selector meant that only h2 tags inside a tag with a class of index would have the styles shown above. This solution kept my code clean and kept me from having to make use of !important
.
For most site interactions, I implemented both front-end and backend user authentication. On the backend, I separated attributes that users could add once they had signed up from the attributes they would absolutely need to create an account. Essential attributes included a username, password digest, session token, and first name, while inessential attributes included a bio and profile picture. I validated these features on the backend at both the database and model level in Rails.
create_table "users", force: :cascade do |t|
t.string "email_address", null: false
t.string "password_digest", null: false
t.string "session_token", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "first_name", null: false
t.string "planet"
t.boolean "verified", default: false
t.text "bio"
t.string "avatar"
t.string "phone_number"
t.boolean "superhost", default: false
t.index ["email_address"], name: "index_users_on_email_address", unique: true
t.index ["session_token"], name: "index_users_on_session_token"
end
Users also had other aspects of the site associated with them. For instance, users could host a planet, but also reserve a stay on a planet in future.
On the backend, this required the implementation of relational database tables so that I could create associations between a user
and a spot
(planet).
class User < ApplicationRecord
after_initialize :ensure_session_token
validates :password, length: { minimum: 6, allow_nil: true }
validates :password_digest, :session_token, presence: true
validates :email_address, presence: true, uniqueness: true
attr_reader :password
has_many :spots,
class_name: :Spot
has_many :trips,
class_name: :Booking,
primary_key: :id,
foreign_key: :traveler_id
has_many :bookings,
through: :spots,
source: :bookings
From there, I was able to use JBuilder to dynamically pull information for multiple tables to a single webpage.
json.booking do
json.extract! @booking, :id, :reservation_code, :check_in, :check_out,
:total_cost, :num_guests, :parse_arrival_month,
:parse_depart_month, :parse_arrival_day, :parse_depart_day,
:parse_time_in, :parse_time_out, :total_days
json.extract! @booking.spot, :planet, :address, :lock_instructions,
:directions, :house_manual, :house_rules, :spot_first_photo
json.extract! @booking.spot.host, :avatar, :first_name, :phone_number
On the front end, I used React Router to protect certain routes on the site. For instance, users could not view a planet page without signing in. This also ensured that in future they could not book a planet without signing in, and could only see trips and leave reviews on their own account.
<div className="content">
<Switch>
<AuthRoute path='/signup' component={SignupFormContainer} />
<AuthRoute path='/login' component={LoginFormContainer} />
<ProtectedRoute path='/rooms/:roomId' component={SpotShowContainer} />
<ProtectedRoute path='/trips/:tripId' component={BookingShowContainer} />
<Route path="/rooms" component={SpotsIndexContainer} />
<Redirect to='/rooms'/>
</Switch>
</div>
The result was a dynamic, data-rich website with efficiently compartmentalized state.
I am excited to continue to work on the site! In future, I plan to implement:
- A booking feature that allows users to reserve a stay on a planet, view their upcoming trips, update reservations, and cancel reservations
- A search feature in combination with the Google Maps API that allows users to search listings by date availability
- A review feature that allows users to leave a review for a trip they took
- General usability updates such as modal functionality