diff --git a/build.sh b/build.sh index dc9f272..d444c6c 100644 --- a/build.sh +++ b/build.sh @@ -1,4 +1,6 @@ #!/bin/bash mkdir -p public/build -handlebars -m public/src/pages/restaurantDetail.hbs -f public/build/restaurantDetail.js +handlebars -m public/src/pages/restaurantPage.hbs -f public/build/restaurantPage.js handlebars -m public/src/pages/restaurantList.hbs -f public/build/restaurantList.js +handlebars -m public/src/components/header.hbs -f public/build/header.js +handlebars -m public/src/components/restaurantCard.hbs -f public/build/restaurantCard.js diff --git a/package.json b/package.json index 117c34c..cdf0f26 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "build": "build.sh", - "devStart": "npm run build && nodemon server/server.js" + "start": "npm run build && nodemon server/server.js" }, "repository": { "type": "git", diff --git a/public/build/header.js b/public/build/header.js new file mode 100644 index 0000000..3fa0503 --- /dev/null +++ b/public/build/header.js @@ -0,0 +1 @@ +(()=>{var r=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})["header.hbs"]=r({compiler:[8,">= 4.3.0"],main:function(r,t,n,a,e){return'
\r\n \r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n \r\n \r\n
'},useData:!0})})(); \ No newline at end of file diff --git a/public/build/restaurantCard.js b/public/build/restaurantCard.js new file mode 100644 index 0000000..3f92619 --- /dev/null +++ b/public/build/restaurantCard.js @@ -0,0 +1 @@ +(()=>{var n=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})["restaurantCard.hbs"]=n({compiler:[8,">= 4.3.0"],main:function(n,a,l,e,t){var i,r=null!=a?a:n.nullContext||{},s=n.hooks.helperMissing,c="function",d=n.escapeExpression,n=n.lookupProperty||function(n,a){if(Object.prototype.hasOwnProperty.call(n,a))return n[a]};return'
\r\n '+d(typeof(i=null!=(i=n(l,\r\n
\r\n

'+d(typeof(i=null!=(i=n(l,"name")||(null!=a?n(a,"name"):a))?i:s)==c?i.call(r,{name:"name",hash:{},data:t,loc:{start:{line:4,column:42},end:{line:4,column:50}}}):i)+'

\r\n
\r\n
'+d(typeof(i=null!=(i=n(l,"rating")||(null!=a?n(a,"rating"):a))?i:s)==c?i.call(r,{name:"rating",hash:{},data:t,loc:{start:{line:6,column:44},end:{line:6,column:54}}}):i)+' ⭐
\r\n
'+d(typeof(i=null!=(i=n(l,"distance")||(null!=a?n(a,"distance"):a))?i:s)==c?i.call(r,{name:"distance",hash:{},data:t,loc:{start:{line:7,column:46},end:{line:7,column:58}}}):i)+'
\r\n
'+d(typeof(i=null!=(i=n(l,"time")||(null!=a?n(a,"time"):a))?i:s)==c?i.call(r,{name:"time",hash:{},data:t,loc:{start:{line:8,column:42},end:{line:8,column:50}}}):i)+'
\r\n
\r\n
'+d(typeof(i=null!=(i=n(l,"additionalInfo")||(null!=a?n(a,"additionalInfo"):a))?i:s)==c?i.call(r,{name:"additionalInfo",hash:{},data:t,loc:{start:{line:10,column:48},end:{line:10,column:66}}}):i)+"
\r\n
\r\n
\r\n"},useData:!0})})(); \ No newline at end of file diff --git a/public/build/restaurantList.js b/public/build/restaurantList.js index 352ada0..12df377 100644 --- a/public/build/restaurantList.js +++ b/public/build/restaurantList.js @@ -1 +1 @@ -(()=>{var e=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})["restaurantList.hbs"]=e({1:function(e,n,a,t,r){var l=e.lookupProperty||function(e,n){if(Object.prototype.hasOwnProperty.call(e,n))return e[n]};return"
  • "+e.escapeExpression(e.lambda(null!=n?l(n,"name"):n,n))+"
  • \r\n"},compiler:[8,">= 4.3.0"],main:function(e,n,a,t,r){var l=e.lookupProperty||function(e,n){if(Object.prototype.hasOwnProperty.call(e,n))return e[n]};return null!=(a=l(a,"each").call(null!=n?n:e.nullContext||{},null!=n?l(n,"restaurantList"):n,{name:"each",hash:{},fn:e.program(1,r,0),inverse:e.noop,data:r,loc:{start:{line:1,column:0},end:{line:3,column:9}}}))?a:""},useData:!0})})(); \ No newline at end of file +(()=>{var a=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})["restaurantList.hbs"]=a({1:function(a,r,t,e,n){var l=a.lookupProperty||function(a,r){if(Object.prototype.hasOwnProperty.call(a,r))return a[r]};return null!=(l=a.invokePartial(l(e,"restaurantCard"),r,{name:"restaurantCard",data:n,indent:" ",helpers:t,partials:e,decorators:a.decorators}))?l:""},compiler:[8,">= 4.3.0"],main:function(a,r,t,e,n){var l=a.lookupProperty||function(a,r){if(Object.prototype.hasOwnProperty.call(a,r))return a[r]};return null!=(t=l(t,"each").call(null!=r?r:a.nullContext||{},null!=r?l(r,"restaurantList"):r,{name:"each",hash:{},fn:a.program(1,n,0),inverse:a.noop,data:n,loc:{start:{line:1,column:0},end:{line:3,column:9}}}))?t:""},usePartial:!0,useData:!0})})(); \ No newline at end of file diff --git a/public/build/restaurantPage.js b/public/build/restaurantPage.js new file mode 100644 index 0000000..d7cb439 --- /dev/null +++ b/public/build/restaurantPage.js @@ -0,0 +1 @@ +(()=>{var a=Handlebars.template;(Handlebars.templates=Handlebars.templates||{})["restaurantPage.hbs"]=a({compiler:[8,">= 4.3.0"],main:function(a,n,t,l,r){var e,s=a.lambda,i=a.escapeExpression,u=a.lookupProperty||function(a,n){if(Object.prototype.hasOwnProperty.call(a,n))return a[n]};return'
    \r\n '+i(s(null!=(e=null!=n?u(n,\r\n
    \r\n

    '+i(s(null!=(e=null!=n?u(n,"restaurantDetail"):n)?u(e,"name"):e,n))+'

    \r\n
    '+i(s(null!=(e=null!=n?u(n,"restaurantDetail"):n)?u(e,"additionalInfo"):e,n))+'
    \r\n

    Ratings and Reviews

    \r\n
    \r\n
    '+i(s(null!=(e=null!=n?u(n,"restaurantDetail"):n)?u(e,"rating"):e,n))+' ⭐
    \r\n
    \r\n
    '+i("function"==typeof(t=null!=(t=u(t,"openStatus")||(null!=n?u(n,"openStatus"):n))?t:a.hooks.helperMissing)?t.call(null!=n?n:a.nullContext||{},{name:"openStatus",hash:{},data:r,loc:{start:{line:10,column:46},end:{line:10,column:60}}}):t)+'
    \r\n
    '+i(s(null!=(e=null!=n?u(n,"restaurantDetail"):n)?u(e,"address"):e,n))+"
    \r\n
    \r\n
    \r\n
    \r\n
    \r\n"},useData:!0})})(); \ No newline at end of file diff --git a/public/index.html b/public/index.html index 6336ab5..e750184 100644 --- a/public/index.html +++ b/public/index.html @@ -3,12 +3,13 @@ Handlebars test - - -
    -
    - +
    + + + diff --git a/public/index.js b/public/index.js index f07630a..bc9efc6 100644 --- a/public/index.js +++ b/public/index.js @@ -1,23 +1,79 @@ import "./build/restaurantList.js"; -import "./build/restaurantDetail.js"; +import "./build/restaurantPage.js"; +import "./build/header.js"; +import "./build/restaurantCard.js" +import * as requests from "./src/modules/requests.js" -const rootElement = document.getElementById('root'); -const pageElement = document.createElement('main'); +const rootElement = document.getElementById("root"); +const pageElement = document.createElement("main"); rootElement.appendChild(pageElement); -const restaurants = [ - { name: "Restaurant A" }, - { name: "Restaurant B" }, - { name: "Restaurant C" } -]; +Handlebars.registerPartial("restaurantCard", Handlebars.templates["restaurantCard.hbs"]); +function renderHeader() { + const template = Handlebars.templates["header.hbs"]; + rootElement.insertAdjacentHTML("afterbegin", template()); -function renderRestaurantList() { - const template = Handlebars.templates['restaurantList.hbs']; - pageElement.innerHTML = template({ restaurantList: restaurants }); + document.getElementsByClassName("logo")[0].addEventListener("click", () => { + renderRestaurantList(); + history.pushState({}, "", "/"); + }) } -renderRestaurantList() +async function renderRestaurantList() { + try { + let restaurantList = await requests.getRestaurantList(); + if (!restaurantList) throw new Error("Empty restaurant list"); + const template = Handlebars.templates["restaurantList.hbs"]; + pageElement.innerHTML = template({ restaurantList }); + document.querySelectorAll(".restaurant-card").forEach(card => { + card.addEventListener("click", () => { + const restaurantId = card.dataset.id; + renderRestaurantPage(restaurantId); + history.pushState({ id: restaurantId }, "", `/restaurants/${restaurantId}`); + }); + }); + } catch (error) { + console.error("Error rendering restaurant list:", error); + } +} + +async function renderRestaurantPage(id) { + pageElement.innerHTML = ''; + try { + let restaurantDetail = await requests.getRestaurantById(id); + if (!restaurantDetail) throw new Error("Empty restaurant list"); + const currentTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + let openStatus; + if (restaurantDetail.workingHours.open < currentTime && restaurantDetail.workingHours.closed > currentTime) + openStatus = "Open" + else + openStatus = "Closed" + const template = Handlebars.templates["restaurantPage.hbs"]; + pageElement.innerHTML = template({ restaurantDetail, openStatus }); + } catch (error) { + console.error("Error rendering restaurant list:", error); + } +} + +window.addEventListener('popstate', (event) => { + const restaurantId = event.state ? event.state.id : null; + if (restaurantId) { + renderRestaurantPage(restaurantId); + } else { + renderRestaurantList(); + } +}); + +const currentPath = window.location.pathname; +if (currentPath.startsWith("/restaurants/")) { + const restaurantId = currentPath.split("/")[2]; + renderRestaurantPage(restaurantId); +} else { + renderRestaurantList(); +} + +renderHeader(); diff --git a/public/src/components/basket.hbs b/public/src/components/basket.hbs new file mode 100644 index 0000000..e69de29 diff --git a/public/src/components/header.hbs b/public/src/components/header.hbs new file mode 100644 index 0000000..a241ef1 --- /dev/null +++ b/public/src/components/header.hbs @@ -0,0 +1,13 @@ +
    + +
    +
    + + +
    +
    + + +
    \ No newline at end of file diff --git a/public/src/components/leftMenu.hbs b/public/src/components/leftMenu.hbs new file mode 100644 index 0000000..e69de29 diff --git a/public/src/components/restaurantCard.hbs b/public/src/components/restaurantCard.hbs new file mode 100644 index 0000000..7f7333b --- /dev/null +++ b/public/src/components/restaurantCard.hbs @@ -0,0 +1,12 @@ +
    + {{name}} +
    +

    {{name}}

    +
    +
    {{rating}} ⭐
    +
    {{distance}}
    +
    {{time}}
    +
    +
    {{additionalInfo}}
    +
    +
    diff --git a/public/src/modules/requests.js b/public/src/modules/requests.js new file mode 100644 index 0000000..8983add --- /dev/null +++ b/public/src/modules/requests.js @@ -0,0 +1,25 @@ +export async function getRestaurantList() { + const url = "http://localhost:3000/restaurants"; + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error(error.message); + } +} + +export async function getRestaurantById(id) { + const url = `http://localhost:3000/restaurants/${id}`; + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error(error.message); + } +} diff --git a/public/src/pages/restaurantDetail.hbs b/public/src/pages/restaurantDetail.hbs deleted file mode 100644 index e9f24a7..0000000 --- a/public/src/pages/restaurantDetail.hbs +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/public/src/pages/restaurantList.hbs b/public/src/pages/restaurantList.hbs index 4b5b8d7..6076666 100644 --- a/public/src/pages/restaurantList.hbs +++ b/public/src/pages/restaurantList.hbs @@ -1,3 +1,3 @@ {{#each restaurantList}} -
  • {{this.name}}
  • + {{> restaurantCard this}} {{/each}} \ No newline at end of file diff --git a/public/src/pages/restaurantPage.hbs b/public/src/pages/restaurantPage.hbs new file mode 100644 index 0000000..6d11f2b --- /dev/null +++ b/public/src/pages/restaurantPage.hbs @@ -0,0 +1,15 @@ +
    + {{restaurantDetail.name}} +
    +

    {{restaurantDetail.name}}

    +
    {{restaurantDetail.additionalInfo}}
    +

    Ratings and Reviews

    +
    +
    {{restaurantDetail.rating}} ⭐
    +
    +
    {{openStatus}}
    +
    {{restaurantDetail.address}}
    +
    +
    +
    +
    diff --git a/server/mocks.js b/server/mocks.js new file mode 100644 index 0000000..11cf9f1 --- /dev/null +++ b/server/mocks.js @@ -0,0 +1,129 @@ +export const restaurantList = [ + { + id: 1, + name: "Italiano Pizza", + image: "images/italiano_pizza.jpg", + rating: 4.8, + distance: "1.2 km", + time: "25-30 min", + additionalInfo: "Authentic Italian pizza with fresh ingredients.", + address: "123 Pizza Street, Downtown", + workingHours: { + Monday: { open: "10:00", close: "22:00" }, + Tuesday: { open: "10:00", close: "22:00" }, + Wednesday: { open: "10:00", close: "22:00" }, + Thursday: { open: "10:00", close: "22:00" }, + Friday: { open: "10:00", close: "23:00" }, + Saturday: { open: "11:00", close: "23:00" }, + Sunday: { open: "11:00", close: "21:00" } + } + }, + { + id: 2, + name: "Sushi Master", + image: "images/sushi_master.jpg", + rating: 4.6, + distance: "2.5 km", + time: "30-40 min", + additionalInfo: "Traditional Japanese sushi and rolls.", + address: "456 Sushi Avenue, Uptown", + workingHours: { + Monday: { open: "12:00", close: "22:00" }, + Tuesday: { open: "12:00", close: "22:00" }, + Wednesday: { open: "12:00", close: "22:00" }, + Thursday: { open: "12:00", close: "22:00" }, + Friday: { open: "12:00", close: "23:00" }, + Saturday: { open: "13:00", close: "23:00" }, + Sunday: { open: "13:00", close: "21:00" } + } + }, + { + id: 3, + name: "Burger House", + image: "images/burger_house.jpg", + rating: 4.5, + distance: "800 m", + time: "15-20 min", + additionalInfo: "Juicy burgers with premium beef and homemade sauces.", + address: "789 Burger Lane, Midtown", + workingHours: { + Monday: { open: "09:00", close: "23:00" }, + Tuesday: { open: "09:00", close: "23:00" }, + Wednesday: { open: "09:00", close: "23:00" }, + Thursday: { open: "09:00", close: "23:00" }, + Friday: { open: "09:00", close: "24:00" }, + Saturday: { open: "10:00", close: "24:00" }, + Sunday: { open: "10:00", close: "22:00" } + } + }, + { + id: 4, + name: "Vegan Delight", + image: "images/vegan_delight.jpg", + rating: 4.7, + distance: "3 km", + time: "20-25 min", + additionalInfo: "Healthy and delicious vegan meals.", + address: "101 Vegan Blvd, Green District", + workingHours: { + Monday: { open: "11:00", close: "21:00" }, + Tuesday: { open: "11:00", close: "21:00" }, + Wednesday: { open: "11:00", close: "21:00" }, + Thursday: { open: "11:00", close: "21:00" }, + Friday: { open: "11:00", close: "22:00" }, + Saturday: { open: "12:00", close: "22:00" }, + Sunday: { open: "12:00", close: "20:00" } + } + }, + { + id: 5, + name: "Steak & Grill", + image: "images/steak_grill.jpg", + rating: 4.9, + distance: "5 km", + time: "40-50 min", + additionalInfo: "High-quality steaks and grilled dishes.", + address: "202 Grill Road, West End", + workingHours: { + Monday: { open: "12:00", close: "23:00" }, + Tuesday: { open: "12:00", close: "23:00" }, + Wednesday: { open: "12:00", close: "23:00" }, + Thursday: { open: "12:00", close: "23:00" }, + Friday: { open: "12:00", close: "24:00" }, + Saturday: { open: "13:00", close: "24:00" }, + Sunday: { open: "13:00", close: "22:00" } + } + } +]; + + +export const menuItems = { + "Pizza": [ + { id: 101, name: "Margherita", price: 8.99, description: "Classic Margherita with fresh tomatoes, mozzarella, and basil." }, + { id: 102, name: "Pepperoni", price: 9.99, description: "Pepperoni pizza with spicy salami and cheese." } + ], + "Sushi": [ + { id: 201, name: "California Roll", price: 6.99, description: "Crab, avocado, cucumber, and rice wrapped in nori." }, + { id: 202, name: "Salmon Nigiri", price: 7.99, description: "Fresh salmon slice over seasoned rice." } + ], + "Burgers": [ + { id: 301, name: "Classic Cheeseburger", price: 10.99, description: "Beef patty, cheddar cheese, lettuce, and tomato." }, + { id: 302, name: "BBQ Bacon Burger", price: 12.99, description: "Beef patty with crispy bacon and BBQ sauce." } + ], + "Vegan": [ + { id: 401, name: "Vegan Buddha Bowl", price: 9.49, description: "Quinoa, chickpeas, avocado, and fresh veggies." }, + { id: 402, name: "Tofu Stir Fry", price: 8.99, description: "Tofu sautéed with vegetables and soy sauce." } + ], + "Steak": [ + { id: 501, name: "Ribeye Steak", price: 19.99, description: "Grilled ribeye steak with garlic butter." }, + { id: 502, name: "T-Bone Steak", price: 22.99, description: "Juicy T-bone steak cooked to perfection." } + ] +}; + +export const restaurantMenu = { + 1: [101, 102], + 2: [201, 202], + 3: [301, 302], + 4: [401, 402], + 5: [501, 502] +}; \ No newline at end of file diff --git a/server/server.js b/server/server.js index 7f206eb..3b2a9fb 100644 --- a/server/server.js +++ b/server/server.js @@ -1,15 +1,43 @@ import express from "express"; import path from "path"; +import * as mocks from "./mocks.js"; + const app = express(); app.use(express.static("public")); app.use("/handlebars", express.static(path.join('node_modules', 'handlebars', 'dist'))); -app.use(logger) +app.use(logger); function logger(req, res, next) { console.log(req.originalUrl); next(); } -app.listen(3000) \ No newline at end of file +app.get('/restaurants', (req, res) => { + res.json(mocks.restaurantList); +}); + +app.get('/restaurants/:id', (req, res) => { + const restaurant = mocks.restaurantList.find(r => r.id === parseInt(req.params.id)); + + if (!restaurant) { + return res.status(404).json({ error: "Restaurant not found" }); + } + + const menuItemIds = mocks.restaurantMenu[restaurant.id] || []; + const menu = []; + + for (const category in mocks.menuItems) { + const items = mocks.menuItems[category].filter(item => menuItemIds.includes(item.id)); + if (items.length > 0) { + menu.push({ category, items }); + } + } + + res.json({ ...restaurant, menu }); +}); + +app.listen(3000, () => { + console.log("Server is running on http://localhost:3000"); +});