├── .gitignore ├── README.md ├── index.html ├── style.css ├── img └── plane.svg └── script.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aviasales-html-css-js 2 | Сайт для поиска и заказа авиабилетов 3 | [Demo](https://alvar91.github.io/aviasales-html-css-js/) 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ависалес 7 | 8 | 9 | 10 |

Авиасалес

11 |
12 |
13 | 45 |
46 |
47 | 52 | 57 |
58 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* prefixed by https://autoprefixer.github.io (PostCSS: v7.0.26, autoprefixer: v9.7.3) */*{-webkit-box-sizing: border-box; box-sizing: border-box; margin: 0; padding: 0; list-style-type: none;}body{font-family: "Open Sans", Helvetica, Arial, sans-serif; font-weight: 400; background: #05263F; color: #fff;}button{cursor: pointer;}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance: none;}h1,h2,h3{text-align: center; padding: 30px 0 15px;}.wrapper{max-width: 1000px; margin: 0 auto;}.wrapper__search,.wrapper__button{display: -webkit-box; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center;}.wrapper__button{margin-top: 50px;}.wrapper__search .input{width: 200px; max-width: 250px; height: 50px; margin: 20px 5px; letter-spacing: 0.6px; position: relative;}.wrapper__search .input input{-webkit-appearance: none; border: 0; -webkit-box-sizing: border-box; box-sizing: border-box; height: 60px; line-height: 2.25rem; padding: 9px 18px; position: relative; -webkit-transition: border-radius .3s, width .3s; -o-transition: border-radius .3s, width .3s; transition: border-radius .3s, width .3s; -webkit-box-shadow: inset 0 0 0 2px transparent; box-shadow: inset 0 0 0 2px transparent; z-index: 1; width: 100%; border-radius: inherit; color: inherit; background-color: #1e3c53;}.wrapper__search .input input:focus~.label{top: -25px; left: 0; color: #00416A;}input{-webkit-box-shadow: 0 0 0 2px #ff6d00; box-shadow: 0 0 0 2px #ff6d00; outline: none; font-family: inherit;}.dropdown{width: 100%; position: relative; z-index: 2; background: #fff; color: #000; max-height: 300px; overflow-x: hidden;}.dropdown__city{display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; padding: 5px 15px; cursor: pointer;}.dropdown__city:hover{color: #d84d00;}.button{display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; text-align: center; width: 100%; max-width: 274px; min-height: 59px; font-size: 1.375rem; font-style: normal; line-height: 1.4; font-weight: 500; color: inherit; padding: 10px 20px; border: none; border-radius: 5px; -webkit-appearance: none; background-color: #f57c00; -webkit-box-shadow: 0 1px 0 0 #d84d00, 0 2px 6px 0 rgba(0, 0, 0, .15); box-shadow: 0 1px 0 0 #d84d00, 0 2px 6px 0 rgba(0, 0, 0, .15); -webkit-transition: all .3s; -o-transition: all .3s; transition: all .3s;}.button:hover{background-color: #ffa353; -webkit-transition: all .3s; -o-transition: all .3s; transition: all .3s;}.button__search:after{content: ""; display: inline-block; width: 36px; height: 36px; background: 50% 50%/100% no-repeat url(img/plane.svg); margin-left: 2px;}.left-side,.right-side{padding: 20px; width: 100%;}.left-side{min-width: 270px; width: 270px; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-align: center; -ms-flex-align: center; align-items: center;}.right-side{display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; border-left: 1px solid black;}.button__buy{font-size: 16px; max-width: 166px; line-height: 1.25; padding-top: 5px; padding-bottom: 5px; width: 100%; font-weight: 600; font-family: inherit; text-decoration: none; margin-bottom: 15px;}.button__add{margin: 50px auto 0;}.ticket{width: 100%; max-width: 730px; margin: 30px auto; border-radius: 5px; -webkit-box-shadow: 0 1px 5px rgba(0, 0, 0, .15); box-shadow: 0 1px 5px rgba(0, 0, 0, .15); background-color: #1e3c53;}.ticket__wrapper{display: -webkit-box; display: -ms-flexbox; display: flex;}.agent{padding: 5px; text-align: center; border-bottom: 1px solid black;}.block-left,.block-right{display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between;}.block-right{text-align: right;}.city__name{font-size: 18px; font-weight: 600;}.block__ticket{margin: 50px 0;}@media(max-width: 768px){.ticket__wrapper{-ms-flex-wrap: wrap; flex-wrap: wrap;}.left-side{width: 100%;}}@media(max-width: 550px){.right-side{-ms-flex-wrap: wrap; flex-wrap: wrap;}.left-side{min-width: 100%;}.block-right, .block-left{width: 100%;}.block-left{margin-bottom: 20px;}} -------------------------------------------------------------------------------- /img/plane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const formSearch = document.querySelector(".form-search"), 2 | inputCitiesFrom = document.querySelector(".input__cities-from"), 3 | dropdownCitiesFrom = document.querySelector(".dropdown__cities-from"), 4 | inputCitiesTo = document.querySelector(".input__cities-to"), 5 | dropdownCitiesTo = document.querySelector(".dropdown__cities-to"), 6 | inputDateDepart = document.querySelector(".input__date-depart"), 7 | cheapestTicket = document.getElementById("cheapest-ticket"), 8 | otherCheapTickets = document.getElementById("other-cheap-tickets"); 9 | 10 | const CITIES_API = "http://api.travelpayouts.com/data/ru/cities.json", 11 | PROXY = "https://cors-anywhere.herokuapp.com/", 12 | API_KEY = "01b5c85b8b7e2083b53e3deecf2658f2", 13 | CALENDAR = "http://min-prices.aviasales.ru/calendar_preload", 14 | MAX_COUNT = 10; 15 | 16 | let city = []; 17 | 18 | //Функции 19 | const getData = (url, callback, reject = console.error) => { 20 | const request = new XMLHttpRequest(); 21 | request.open("GET", url); 22 | request.addEventListener("readystatechange", () => { 23 | if (request.readyState !== 4) return; 24 | if (request.status === 200) { 25 | callback(request.response); 26 | } else { 27 | reject(request.status); 28 | } 29 | }); 30 | 31 | request.send(); 32 | }; 33 | 34 | const showCity = (input, list) => { 35 | list.textContent = ""; 36 | if (!input.value) { 37 | return; 38 | } 39 | const filterCity = city.filter((item) => { 40 | const fixItem = item.name.toLowerCase(); 41 | return fixItem.startsWith(input.value.toLowerCase()); 42 | }); 43 | 44 | filterCity.forEach((item) => { 45 | const li = document.createElement("li"); 46 | li.classList.add("dropdown__city"); 47 | li.textContent = item.name; 48 | list.append(li); 49 | }); 50 | }; 51 | 52 | const selectCity = (event, input, list) => { 53 | const target = event.target; 54 | if (target.tagName.toLowerCase() === "li") { 55 | input.value = target.textContent; 56 | list.textContent = ""; 57 | } 58 | }; 59 | 60 | const getNameCity = (code) => { 61 | const objCity = city.find((item) => item.code === code); 62 | return objCity.name; 63 | }; 64 | 65 | const getDate = (date) => { 66 | return new Date(date).toLocaleString("ru", { 67 | year: "numeric", 68 | month: "long", 69 | day: "numeric", 70 | hour: "2-digit", 71 | minute: "2-digit", 72 | }); 73 | }; 74 | 75 | const getChanges = (num) => { 76 | if (num) { 77 | return num === 1 ? "C одной пересадкой" : "C двумя пересадками"; 78 | } else { 79 | return "Без пересадок"; 80 | } 81 | }; 82 | 83 | const getLinkAviasales = (data) => { 84 | let link = "https://www.aviasales.ru/search/"; 85 | link += data.origin; 86 | const date = new Date(data.depart_date); 87 | const day = date.getDate(); 88 | link += day < 10 ? "0" + day : day; 89 | const month = date.getMonth() + 1; 90 | link += month < 10 ? "0" + month : month; 91 | link += data.destination; 92 | link += "1"; 93 | return link; 94 | }; 95 | 96 | const createCard = (data) => { 97 | const ticket = document.createElement("article"); 98 | ticket.classList.add("ticket"); 99 | let deep = ""; 100 | if (data) { 101 | deep = ` 102 |

${data.gate}

103 |
104 |
105 | Купить 108 | за ${data.value} 109 |
110 |
111 |
112 |
Вылет из города 113 | ${getNameCity(data.origin)} 114 |
115 |
${getDate(data.depart_date)}
116 |
117 | 118 |
119 |
${getChanges(data.number_of_changes)}
120 |
Город назначения: 121 | ${getNameCity(data.destination)} 122 |
123 |
124 |
125 |
126 | `; 127 | } else { 128 | deep = "

К сожалению на текущую дату билетов не нашлось

"; 129 | } 130 | ticket.insertAdjacentHTML("afterbegin", deep); 131 | return ticket; 132 | }; 133 | 134 | const renderCheapDay = (cheapTicket) => { 135 | cheapestTicket.style.display = "block"; 136 | cheapestTicket.innerHTML = "

Самый дешевый билет на выбранную дату

"; 137 | const ticket = createCard(cheapTicket[0]); 138 | cheapestTicket.append(ticket); 139 | }; 140 | 141 | const renderCheapYear = (cheapTickets) => { 142 | otherCheapTickets.style.display = "block"; 143 | otherCheapTickets.innerHTML = "

Самые дешевые билеты на другие даты

"; 144 | cheapTickets.sort((a, b) => a.value - b.value); 145 | for (let i = 0; i < cheapTickets.length && i < MAX_COUNT; i++) { 146 | const ticket = createCard(cheapTickets[i]); 147 | otherCheapTickets.append(ticket); 148 | } 149 | }; 150 | 151 | const renderCheap = (data, date) => { 152 | const cheapTicketYear = JSON.parse(data).best_prices; 153 | const cheapTicketDay = cheapTicketYear.filter((item) => { 154 | return item.depart_date === date; 155 | }); 156 | 157 | renderCheapDay(cheapTicketDay); 158 | renderCheapYear(cheapTicketYear); 159 | }; 160 | 161 | //Обработчики 162 | formSearch.addEventListener("submit", (event) => { 163 | event.preventDefault(); 164 | const formData = { 165 | from: city.find((item) => inputCitiesFrom.value === item.name), 166 | to: city.find((item) => inputCitiesTo.value === item.name), 167 | when: inputDateDepart.value, 168 | }; 169 | 170 | if (formData.from && formData.to) { 171 | const requestData = `?depart_date=${formData.when}&origin=${formData.from.code}&destination=${formData.to.code}&one_way=true`; 172 | getData( 173 | PROXY + CALENDAR + requestData, 174 | (response) => { 175 | renderCheap(response, formData.when); 176 | }, 177 | (error) => { 178 | alert("В этом направлении нет рейсов"); 179 | console.error("Ошибка", error); 180 | } 181 | ); 182 | } else { 183 | alert("Введите корректное название города!"); 184 | } 185 | }); 186 | 187 | inputCitiesFrom.addEventListener("input", () => { 188 | showCity(inputCitiesFrom, dropdownCitiesFrom); 189 | }); 190 | 191 | inputCitiesTo.addEventListener("input", () => { 192 | showCity(inputCitiesTo, dropdownCitiesTo); 193 | }); 194 | 195 | dropdownCitiesFrom.addEventListener("click", (event) => { 196 | selectCity(event, inputCitiesFrom, dropdownCitiesFrom); 197 | }); 198 | 199 | dropdownCitiesTo.addEventListener("click", (event) => { 200 | selectCity(event, inputCitiesTo, dropdownCitiesTo); 201 | }); 202 | 203 | //Вызовы функций 204 | getData( 205 | PROXY + CITIES_API, 206 | (data) => { 207 | city = JSON.parse(data).filter((item) => item.name); 208 | city.sort((a, b) => { 209 | if (a.name > b.name) { 210 | return 1; 211 | } else if (a.name < b.name) { 212 | return -1; 213 | } 214 | return 0; 215 | }); 216 | }, 217 | (error) => { 218 | alert("В этом направлении нет рейсов"); 219 | console.error("Ошибка", error); 220 | } 221 | ); 222 | --------------------------------------------------------------------------------