<div id="app" class="text-black antialiased h-screen flex flex-col">
  <!-- HEADER -->
  <div class="flex items-center shadow h-16 px-6">
    <div class="ion-md-radio-button-on text-4xl text-indigo-600 pr-2 cursor-pointer"
         @click="openView('catalog')"></div>
    <div class="flex-1 cursor-pointer font-bold uppercase hover:text-gray-700"
         @click="openView('catalog')">
      Book-a-dinner
    </div>
    <button class="px-2 ml-2 font-semibold text-gray-500"
            :class="{'text-indigo-700': currentView === 'catalog'}"
            @click="openView('catalog')">
      Catalog
    </button>
    <button class="px-2 ml-2 font-semibold text-gray-500"
            :class="{'text-indigo-700': currentView === 'favs'}"
            @click="openView('favs')">
      Favorites
    </button>
    <button class="px-2 ml-2 font-semibold text-gray-500"
            :class="{'text-indigo-700': currentView === 'reservs'}"
            @click="openView('reservs')">
      Reservations
    </button>
  </div>
  <!-- LIST -->
  <div class="flex-1 overflow-scroll pb-8" v-if="currentView === 'catalog'">
    <div class="text-3xl font-bold px-6 pt-5">Restaurants</div>
    <div class="flex flex-wrap px-4 pt-3">
      <div v-for="(rest, i) in restaurants"
           class="w-1/2 p-2">
        <div class="border border-gray-300 rounded">
          <div class="h-48 bg-gray-200 cursor-pointer rounded-t overflow-hidden flex items-center"
               @click="openDetails(i)">
            <img :src="rest.photo">
          </div>
          <div class="px-4 pt-2 pb-4 flex items-start">
            <div class="flex-1">
              <div class="hover:text-gray-800 cursor-pointer font-semibold text-lg pt-1"
                   @click="openDetails(i)">
                {{rest.name}}
              </div>
              <div class="text-gray-500 text-sm">
                {{rest.price}}
              </div>
            </div>
            <button class="ion-md-heart text-xl text-gray-300 cursor-pointer p-1"
                    :class="{'text-pink-600': rest.isFav}"
                    @click="addToFavs(i)"></button>
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- DETAILS -->
  <div class="flex-1 overflow-scroll flex h-full"
       v-if="currentView === 'details'">
    <!-- INFO -->
    <div class="flex-1"
         :class="{'opacity-50': doShowReservSidebar}">
      <!-- image -->
      <div class="flex items-center h-48 overflow-hidden">
        <img :src="restaurants[selectedRestaurant].photo" alt="">
      </div>
      <!-- header -->
      <div class="flex items-center pt-6">
        <!-- back and title -->
        <div class="flex-1">
          <button class="text-indigo-700 px-6 text-sm"
                  @click="currentView = backFromDetailsTo">
            ← Back
          </button>
          <div class="text-3xl font-bold px-6">
            {{restaurants[selectedRestaurant].name}}
          </div>
        </div>
        <!-- controls -->
        <div class="px-6 flex" v-if="!doShowReservSidebar">
          <button class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded font-semibold mr-2"
                  @click="doShowReservSidebar = true">
            Make a reservation
          </button>
          <button class="flex items-center items-center bg-gray-100 hover:bg-gray-200 px-4 py-2 rounded"
                  @click="addToFavs(selectedRestaurant)">
            <div class="ion-md-heart text-xl text-gray-400"
                 :class="{'text-pink-600': restaurants[selectedRestaurant].isFav}"></div>
  <!--           <div v-if="!restaurants[selectedRestaurant].isFav">Add to favorites</div>
            <div v-if="restaurants[selectedRestaurant].isFav">In favorites</div> -->
          </button>
        </div>
      </div>
      <!-- text -->
      <div class="px-6 pt-6 max-w-lg">
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque possimus eos qui sapiente itaque iste, illum tempore soluta nisi quas modi? Nesciunt, voluptatem deleniti! Debitis ipsum illo delectus quisquam quas!
      </div>
    </div>
    <!-- RESERVATION SIDEBAR -->
    <div class="w-full max-w-sm shadow"
         v-if="doShowReservSidebar">
      <!-- header -->
      <div class="flex px-6 pt-4 items-center">
        <div class="flex-1 text-2xl font-bold">
          New reservation
        </div>
        <button class="ion-md-close text-xl text-gray-500 px-2 pt-2 hover:text-indigo-600"
                @click="doShowReservSidebar = false"></button>
      </div>
      <!-- form -->
      <div class="px-6">
        <div class="pt-6">
          <div class="uppercase text-sm text-gray-500">
            Party size
          </div>
          <div class="flex pt-2">
            <div v-for="(party,i) in partyOptions"
                 class="w-12 h-12 flex items-center justify-center border mr-2 cursor-pointer text-gray-500 border-gray-400 font-semibold rounded"
                 :class="{'border-indigo-600 text-indigo-600 bg-indigo-100': selectedParty === i}"
                 @click="selectedParty = i">
              {{party}}
            </div>
          </div>
        </div>
        <div class="pt-6">
          <div class="uppercase text-sm text-gray-500">Date</div>
          <select v-model="selectedDate" class="border py-2 px-4 mr-2 mt-1 w-56">
            <option v-for="date in dateOptions">{{date}}</option>
          </select>
        </div>
        <div class="pt-6">
          <div class="uppercase text-sm text-gray-500">Time</div>
          <div class="flex pt-2 flex-wrap">
            <div v-for="(time,i) in timeOptions"
                 class="w-12 h-8 flex items-center justify-center border mr-2 mb-2 cursor-pointer text-gray-500 border-gray-400 font-semibold rounded text-sm"
                 :class="{'border-indigo-600 text-indigo-600 bg-indigo-100': selectedTime === i}"
                 @click="selectedTime = i">
              {{time}}
            </div>
          </div>
        </div>
        <button class="bg-indigo-600 hover:bg-indigo-700 text-white w-full py-3 rounded mt-4"
              @click="reserve()">
          Reserve
        </button>
      </div>
    </div>
  </div>
  <!-- FAVS -->
  <div class="flex-1 overflow-scroll flex-1 overflow-hidden"
       v-if="currentView === 'favs'">
    <div class="text-3xl font-bold px-6 pt-5">Favourites</div>
    <div class="flex flex-wrap px-4 pt-3">
      <div v-for="(rest, i) in restaurants"
           class="w-1/2 p-2"
           v-if="rest.isFav">
        <div class="border border-gray-300 rounded">
          <div class="h-48 bg-gray-200 cursor-pointer rounded-t overflow-hidden flex items-center"
               @click="openDetails(i)">
            <img :src="rest.photo">
          </div>
          <div class="px-4 pt-2 pb-4 flex items-start">
            <div class="flex-1">
              <div class="hover:text-gray-800 cursor-pointer font-semibold text-lg pt-1"
                   @click="openDetails(i)">
                {{rest.name}}
              </div>
              <div class="text-gray-500 text-sm">
                {{rest.price}}
              </div>
            </div>
            <button class="ion-md-heart text-xl text-gray-300 cursor-pointer p-1"
                    :class="{'text-pink-600': rest.isFav}"
                    @click="addToFavs(i)"></button>
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- RESERVS -->
  <div class="flex-1 overflow-scroll flex-1 overflow-hidden"
       v-if="currentView === 'reservs'">
    <div class="text-3xl font-bold px-6 pt-5 mb-4">Reservations</div>
    <div v-for="(reserv, i) in reservations"
         class="flex items-center h-24 mx-6 mb-2">
      <div class="w-32 h-24 rounded overflow-hidden flex items-center mr-6">
        <img :src="restaurants[reserv.restaurant].photo">
      </div>
      <div class="flex-1">
        <div class="text-lg font-semibold">
          {{reserv.date}}, {{reserv.time}}
        </div>
        <div class="">
          {{restaurants[reserv.restaurant].name}}
        </div>
        <div class="text-sm text-gray-500 font-semibold pt-1">
          {{reserv.party}}
          <span class="ion-md-person"></span>
        </div>
      </div>
      <button class="text-gray-600 hover:text-indigo-600 text-sm font-semibold px-2"
              @click="cancel(i)">
        Cancel
      </button>
    </div>
  </div>
  <!-- CONFIRMATION -->
  <div class="flex items-center py-2 bg-pink-800 px-6 text-white"
       v-if="doShowReservConfirmation">
    <div class="flex-1 font-bold py-3">
      🙌 Table booked
    </div>
    <button class="px-3 py-2 mr-2 text-pink-800 text-sm bg-white rounded"
            @click="openView('reservs'); doShowReservConfirmation = false">
      Go to reservations
    </button>
    <button class="px-3 py-2 mr-2 text-pink-300 text-sm bg-pink-700 rounded"
            @click="doShowReservConfirmation = false">
      Dismiss
    </button>
  </div>
</div>
input, textarea, button:focus, select {
  outline: none;
  resize: none;
}

input::placeholder, textarea::placeholder {
  color: #A0AEC0;
  opacity: 1;
}

select {
  -webkit-appearance: none;
  background: center right 12px / 12px url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/1703172/shevron.png) no-repeat;
}

body, * {
  font-family: Muli, sans-serif;
}
var app = new Vue({
  el: '#app',
  data: {
    currentView: 'catalog',
    backFromDetailsTo: '',
    selectedRestaurant: 1,
    doShowReservSidebar: false,
    doShowReservConfirmation: false,
    restaurants: [
      {
        name: 'Bread and Breakfast',
        price: '$$',
        photo: 'https://images.unsplash.com/photo-1482049016688-2d3e1b311543?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=953&q=80',
        isFav: false
      },
      {
        name: 'The Sober Clam',
        price: '$$$$',
        photo: 'https://images.unsplash.com/photo-1502301103665-0b95cc738daf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1300&q=80',
        isFav: true
      },
      {
        name: 'Salaterio',
        price: '$$',
        photo: 'https://images.unsplash.com/photo-1551218372-a8789b81b253?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=934&q=80',
        isFav: true
      },
      {
        name: 'Sega Mega Bar',
        price: '$',
        photo: 'https://images.unsplash.com/photo-1514134583630-250e42c36dbd?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=934&q=80',
        isFav: false
      },
      {
        name: 'Eight 9 Tin',
        price: '$$$',
        photo: 'https://images.unsplash.com/photo-1517644493876-7864565e3bf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
        isFav: false
      },
      {
        name: 'Red Green Bite',
        price: '$$',
        photo: 'https://images.unsplash.com/photo-1542814812-992b90017061?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
        isFav: false
      },
    ],
    reservations: [],
    //
    // reservation form:
    partyOptions: ['1', '2', '3', '4', '5+'],
    selectedParty: 1,
    dateOptions: ['Today, Apr 9', 'Tomorrow, Apr 10', 'Monday, Apr 11', 'Tuesday, Apr 12', 'Wednesday, Apr 13'],
    selectedDate: 'Today, Apr 9',
    timeOptions: ['12:00', '12:30', '1:00', '1:30', '2:00', '2:30', '3:00', '3:30', '4:00', '4:30', '5:00', '5:30', '6:00', '6:30', '7:00', '7:30', '8:00', '8:30', '9:00', '9:30', '10:00', '10:30', '11:00'],
    selectedTime: 12,
  },
  methods: {
    openView (viewName) {
      this.currentView = viewName
      this.doShowReservSidebar = false
    },
    openDetails (index) {
      this.selectedRestaurant = index
      this.backFromDetailsTo = this.currentView
      this.currentView = 'details'
    },
    addToFavs (index) {
      this.restaurants[index].isFav = !this.restaurants[index].isFav
    },
    reserve () {
      this.reservations.push({
        restaurant: this.selectedRestaurant,
        party: this.partyOptions[this.selectedParty],
        date: this.selectedDate,
        time: this.timeOptions[this.selectedTime]
      })
      this.doShowReservConfirmation = true
      this.doShowReservSidebar = false
    },
    cancel (index) {
      this.reservations.splice(index, 1)
    }
  }
})

External CSS

  1. https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css
  2. https://unpkg.com/[email protected]/dist/css/ionicons.min.css

External JavaScript

  1. https://cdn.jsdelivr.net/npm/vue/dist/vue.js