├── aviasales-master.zip
└── aviasales-master
├── src
├── constants.js
├── components
│ ├── TicketItem.vue
│ ├── FilterTransfer.vue
│ └── Tabs.vue
├── main.js
├── router
│ └── index.js
├── views
│ ├── Tickets.vue
│ ├── Header.vue
│ └── Content.vue
├── App.vue
└── assets
│ └── Logo.svg
├── babel.config.js
├── aviasales_frontend-master.zip
├── aviasales_frontend-master
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── components
│ │ ├── Logo
│ │ │ ├── Logo.js
│ │ │ └── Logo.svg
│ │ ├── TicketList
│ │ │ ├── TicketList.module.scss
│ │ │ └── TicketList.js
│ │ ├── Alert
│ │ │ ├── Alert.module.scss
│ │ │ └── Alert.js
│ │ ├── FilterList
│ │ │ ├── FilterList.module.scss
│ │ │ └── FilterList.js
│ │ ├── ThemeSwitcher
│ │ │ ├── ThemeSwitcher.js
│ │ │ └── ThemeSwitcher.module.scss
│ │ ├── Sorting
│ │ │ ├── Sorting.js
│ │ │ └── Sorting.module.scss
│ │ ├── Checkbox
│ │ │ ├── Checkbox.js
│ │ │ └── Checkbox.module.scss
│ │ └── Ticket
│ │ │ ├── Ticket.module.scss
│ │ │ └── Ticket.js
│ ├── App.js
│ ├── pages
│ │ ├── Search.module.scss
│ │ ├── testData.json
│ │ └── Search.js
│ ├── index.js
│ ├── context
│ │ └── ThemeContext
│ │ │ ├── ThemeContext.js
│ │ │ └── ThemeColors.js
│ ├── index.scss
│ └── serviceWorker.js
├── package.json
├── README.md
└── server.md
├── README.md
├── package.json
└── public
└── index.html
/aviasales-master.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ooopsa27/Avia2/HEAD/aviasales-master.zip
--------------------------------------------------------------------------------
/aviasales-master/src/constants.js:
--------------------------------------------------------------------------------
1 | export const getTicketsUrl = 'https://front-test.beta.aviasales.ru/tickets';
--------------------------------------------------------------------------------
/aviasales-master/src/components/TicketItem.vue:
--------------------------------------------------------------------------------
1 |
2 | Билет вникуда
3 |
--------------------------------------------------------------------------------
/aviasales-master/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ooopsa27/Avia2/HEAD/aviasales-master/aviasales_frontend-master.zip
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ooopsa27/Avia2/HEAD/aviasales-master/aviasales_frontend-master/public/favicon.ico
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ooopsa27/Avia2/HEAD/aviasales-master/aviasales_frontend-master/public/logo192.png
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ooopsa27/Avia2/HEAD/aviasales-master/aviasales_frontend-master/public/logo512.png
--------------------------------------------------------------------------------
/aviasales-master/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 |
5 | createApp(App).use(router).mount('#app')
6 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Logo/Logo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import logoSvg from "./Logo.svg";
3 |
4 | export const Logo = () => (
5 | <>
6 |
7 | >
8 | );
9 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/TicketList/TicketList.module.scss:
--------------------------------------------------------------------------------
1 | .TicketList {
2 | li {
3 | margin-bottom: 20px;
4 |
5 | &:last-child {
6 | margin-bottom: 0;
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/aviasales-master/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 |
3 | const routes = []
4 |
5 | const router = createRouter({
6 | history: createWebHistory(process.env.BASE_URL),
7 | routes
8 | })
9 |
10 | export default router
11 |
--------------------------------------------------------------------------------
/aviasales-master/src/views/Tickets.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/aviasales-master/src/views/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/aviasales-master/src/components/FilterTransfer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Все
4 | Без пересадок
5 | 1 пересадка
6 | 2 пересадки
7 | 3 пересадки
8 |
9 |
--------------------------------------------------------------------------------
/aviasales-master/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
20 |
--------------------------------------------------------------------------------
/aviasales-master/README.md:
--------------------------------------------------------------------------------
1 | # aviasales
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Customize configuration
19 | See [Configuration Reference](https://cli.vuejs.org/config/).
20 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Alert/Alert.module.scss:
--------------------------------------------------------------------------------
1 | .Alert {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | flex-direction: column;
6 |
7 | span {
8 | text-align: center;
9 | }
10 |
11 | button {
12 | margin-top: 10px;
13 | color: var(--body-text);
14 | font-size: 15px;
15 | line-height: 20px;
16 | border-bottom: 1px solid;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Alert/Alert.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/accessible-emoji */
2 | import React from "react";
3 | import classes from "./Alert.module.scss";
4 |
5 | export const Alert = () => {
6 | return (
7 |
8 | 😟 Что-то пошло не так
9 | window.location.reload()}>
10 | Обновить страницу
11 |
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/FilterList/FilterList.module.scss:
--------------------------------------------------------------------------------
1 | .FilterList {
2 | min-width: 232px;
3 | background: var(--filter-background);
4 | box-shadow: 0px 2px 8px var(--box-shadow);
5 | border-radius: 5px;
6 | padding-bottom: 10px;
7 | }
8 |
9 | .Title {
10 | text-transform: uppercase;
11 | letter-spacing: 0.5px;
12 | font-style: normal;
13 | font-weight: 600;
14 | font-size: 12px;
15 | padding: 20px 20px 10px 20px;
16 | }
17 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/TicketList/TicketList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Ticket } from "../Ticket/Ticket";
3 | import classes from "./TicketList.module.scss";
4 |
5 | export const TicketList = ({ tickets }) => {
6 | return (
7 |
8 | {tickets.map((item, i) => (
9 |
10 |
11 |
12 | ))}
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/aviasales-master/src/components/Tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Самый дешёвый
4 | Самый быстрый
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/aviasales-master/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aviasales",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "core-js": "^3.6.5",
11 | "vue": "^3.0.0",
12 | "vue-router": "^4.0.0-0"
13 | },
14 | "devDependencies": {
15 | "@vue/cli-plugin-babel": "~4.5.0",
16 | "@vue/cli-plugin-router": "~4.5.0",
17 | "@vue/cli-service": "~4.5.0",
18 | "@vue/compiler-sfc": "^3.0.0"
19 | },
20 | "browserslist": [
21 | "> 1%",
22 | "last 2 versions",
23 | "not dead"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Search } from "./pages/Search";
3 | import { Logo } from "./components/Logo/Logo";
4 | import { ThemeSwitcher } from "./components/ThemeSwitcher/ThemeSwitcher";
5 |
6 | function App() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 | );
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/pages/Search.module.scss:
--------------------------------------------------------------------------------
1 | .appInformer {
2 | margin: 0 0 20px 0;
3 |
4 | @media screen and (min-width: 630px) {
5 | margin: 0 20px 0 0;
6 | }
7 | }
8 |
9 | .appContentInner {
10 | display: flex;
11 | justify-content: center;
12 | flex-direction: column;
13 | align-items: center;
14 |
15 | @media screen and (min-width: 630px) {
16 | flex-direction: row;
17 | align-items: flex-start;
18 | }
19 | }
20 |
21 | .appContent {
22 | display: flex;
23 | flex-direction: column;
24 | width: 100%;
25 | max-width: 502px;
26 | }
27 |
28 | .sorting {
29 | margin-bottom: 20px;
30 | }
31 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/aviasales-master/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.scss";
4 | import { ThemeProvider } from "./context/ThemeContext/ThemeContext";
5 | import App from "./App";
6 | import * as serviceWorker from "./serviceWorker";
7 |
8 | ReactDOM.render(
9 |
10 |
11 |
12 |
13 | ,
14 | document.getElementById("root")
15 | );
16 |
17 | // If you want your app to work offline and load faster, you can change
18 | // unregister() to register() below. Note this comes with some pitfalls.
19 | // Learn more about service workers: https://bit.ly/CRA-PWA
20 | serviceWorker.unregister();
21 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/ThemeSwitcher/ThemeSwitcher.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import classes from "./ThemeSwitcher.module.scss";
3 | import { ThemeContext } from "../../context/ThemeContext/ThemeContext";
4 |
5 | export const ThemeSwitcher = () => {
6 | const { dark, toggle } = useContext(ThemeContext);
7 |
8 | return (
9 |
10 | toggle()}
16 | />
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage": "https://victoriaZubareva.github.io/aviasales_frontend",
3 | "name": "aviasales",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.3.2",
9 | "@testing-library/user-event": "^7.1.2",
10 | "gh-pages": "^2.2.0",
11 | "node-sass": "^4.13.1",
12 | "react": "^16.12.0",
13 | "react-dom": "^16.12.0",
14 | "react-scripts": "3.4.0"
15 | },
16 | "scripts": {
17 | "predeploy": "npm run build",
18 | "deploy": "gh-pages -d build",
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Sorting/Sorting.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import classes from "./Sorting.module.scss";
3 |
4 | export const Sorting = ({ sorts }) => {
5 | const [selectTub, setSelectTub] = useState("low price");
6 |
7 | const paramSorting = [
8 | { value: "Самый дешевый", id: "low price" },
9 | { value: "Самый быстрый", id: "fastest" }
10 | ];
11 |
12 | const checkTub = id => {
13 | if (id === selectTub) {
14 | return;
15 | }
16 | setSelectTub(id);
17 | sorts.sortTabs(id);
18 | };
19 |
20 | return (
21 |
22 |
23 | {paramSorting.map(elem => (
24 | checkTub(elem.id)}
32 | >
33 | {elem.value}
34 |
35 | ))}
36 |
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Checkbox/Checkbox.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import classes from "./Checkbox.module.scss";
3 |
4 | export const Checkbox = props => {
5 | const { item, checked } = props;
6 |
7 | let [isChecked, setIsChecked] = useState(item.isChecked);
8 |
9 | // отслеживаем -- если изменился isChecked, то обновим состояние
10 | useEffect(() => {
11 | setIsChecked(props.item.isChecked);
12 | }, [props.item.isChecked]);
13 |
14 | const toggleIsChecked = () => {
15 | setIsChecked(!isChecked);
16 | checked(item.id, !isChecked);
17 | };
18 |
19 | return (
20 |
21 |
22 |
23 |
27 |
31 |
32 | {item.value}
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Sorting/Sorting.module.scss:
--------------------------------------------------------------------------------
1 | .SortingTabs {
2 | height: 50px;
3 | display: flex;
4 | flex-wrap: nowrap;
5 | }
6 |
7 | .SortingTab {
8 | display: flex;
9 | cursor: pointer;
10 | letter-spacing: 0.5px;
11 | text-transform: uppercase;
12 | font-weight: 600;
13 | font-size: 12px;
14 | line-height: 20px;
15 | background: var(--sorting-tab-background);
16 | border: 1px solid var(--sorting-tab-border);
17 | flex: 1 0 auto;
18 | align-items: center;
19 | justify-content: center;
20 |
21 | &_active {
22 | background: var(--sorting-tab-background-active);
23 | color: var(--sorting-tab-text-active);
24 | border: 1px solid var(--sorting-tab-background-active);
25 | margin-right: 1px;
26 | }
27 |
28 | &:first-child {
29 | border-radius: 5px 0 0 5px;
30 | &:hover {
31 | z-index: 2;
32 | }
33 | }
34 |
35 | &:last-child {
36 | border-radius: 0 5px 5px 0;
37 | }
38 |
39 | &:not(:first-child) {
40 | margin-left: -1px;
41 | }
42 |
43 | &:hover:not(.SortingTab_active) {
44 | border: 1px solid var(--sorting-tab-border-hover);
45 | background: var(--sorting-tab-background-hover);
46 | transition: all 0.3s;
47 | color: var(--sorting-tab-text-hover);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Ticket/Ticket.module.scss:
--------------------------------------------------------------------------------
1 | .ticket {
2 | max-width: 502px;
3 | min-width: 300px;
4 | background: var(--ticket-background);
5 | box-shadow: 0px 2px 8px var(--box-shadow);
6 | border-radius: 5px;
7 | padding: 20px;
8 | }
9 | .ticketHeader {
10 | display: flex;
11 | justify-content: space-between;
12 | margin-bottom: 20px;
13 |
14 | div {
15 | flex: 2;
16 | &:last-child {
17 | flex: 1;
18 | }
19 | }
20 | }
21 |
22 | .ticketPrice {
23 | font-size: 24px;
24 | color: var(--ticket-price);
25 | font-weight: 600;
26 | align-self: center;
27 | }
28 |
29 | // body
30 |
31 | .ticketBody {
32 | display: flex;
33 | justify-content: space-between;
34 | flex-direction: column;
35 | }
36 |
37 | .bodyRow {
38 | margin-bottom: 10px;
39 | display: flex;
40 | justify-content: space-between;
41 |
42 | div {
43 | flex: 1;
44 | }
45 |
46 | &:last-child {
47 | margin-bottom: 0px;
48 | }
49 | }
50 |
51 | .titleRow {
52 | font-weight: 600;
53 | font-size: 12px;
54 | letter-spacing: 0.5px;
55 | text-transform: uppercase;
56 | color: var(--ticket-text-subtitle);
57 | display: block;
58 | line-height: 18px;
59 | }
60 |
61 | .textRow {
62 | display: block;
63 | font-weight: 600;
64 | font-size: 14px;
65 | line-height: 21px;
66 | }
67 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/context/ThemeContext/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import React, { useLayoutEffect, useState } from "react";
2 | import { darkTheme, lightTheme } from "./ThemeColors";
3 |
4 | export const ThemeContext = React.createContext({
5 | dark: false,
6 | toggle: () => {}
7 | });
8 |
9 | export const ThemeProvider = ({ children }) => {
10 | // текущая тема
11 | const [dark, setDark] = useState(false);
12 |
13 | // устанавливаем стили css
14 | const applyTheme = theme => {
15 | const root = document.getElementsByTagName("html")[0];
16 | root.style.cssText = theme.join(";");
17 | };
18 |
19 | // обновляем стили синхронно
20 | useLayoutEffect(() => {
21 | // устанавливаем последнюю выбранную пользователем тему
22 | const lastTheme = window.localStorage.getItem("darkTheme");
23 |
24 | if (lastTheme === "true") {
25 | setDark(true);
26 | applyTheme(darkTheme);
27 | } else {
28 | setDark(false);
29 | applyTheme(lightTheme);
30 | }
31 | }, [dark]);
32 |
33 | const toggle = () => {
34 | setDark(!dark);
35 | window.localStorage.setItem("darkTheme", !dark);
36 | };
37 |
38 | return (
39 |
45 | {children}
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Checkbox/Checkbox.module.scss:
--------------------------------------------------------------------------------
1 | .Item {
2 | display: flex;
3 | height: 40px;
4 | align-items: center;
5 | cursor: pointer;
6 |
7 | &:hover {
8 | background-color: var(--filter-hover-item);
9 | transition: background-color 0.3s;
10 | }
11 | }
12 |
13 | .ItemText {
14 | font-size: 13px;
15 | line-height: 20px;
16 | }
17 |
18 | input[type="checkbox"] {
19 | display: none;
20 | pointer-events: none;
21 | }
22 |
23 | .Checkbox {
24 | width: 20px;
25 | stroke-width: 5;
26 | margin: 10px 10px 10px 20px;
27 |
28 | &__box {
29 | stroke: var(--checkbox-box);
30 | stroke-dasharray: 320;
31 | stroke-dashoffset: 0;
32 | fill: rgba(0, 0, 0, 0);
33 | }
34 |
35 | &__check {
36 | stroke: var(--checkbox-box-check);
37 | stroke-dasharray: 70;
38 | stroke-dashoffset: 70;
39 | fill: none;
40 | }
41 | }
42 |
43 | input:checked + svg {
44 | .Checkbox__box {
45 | stroke: var(--checkbox-box-check);
46 | animation: animBorder 0.3s linear forwards;
47 | }
48 | .Checkbox__check {
49 | stroke-dashoffset: 0;
50 | transition: stroke-dashoffset 0.4s linear;
51 | }
52 | }
53 |
54 | .reverse {
55 | .Checkbox__box {
56 | stroke-dashoffset: 0;
57 | transition: stroke-dashoffset 0.3s linear;
58 | }
59 | .Checkbox__check {
60 | stroke-dashoffset: 70;
61 | transition: stroke-dashoffset 0.3s linear;
62 | }
63 | }
64 |
65 | @keyframes animBorder {
66 | from {
67 | stroke-dashoffset: 320;
68 | }
69 | to {
70 | stroke-dashoffset: 0;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/README.md:
--------------------------------------------------------------------------------
1 | Что получилось: https://victoriazubareva.github.io/aviasales_frontend/
2 |
3 | # Тестовое задание Aviasales ([frontend](https://aviasales.recruitee.com/o/frontend-developer-js-coffeescript-react%C2%A0redux--aviasalesru))
4 |
5 | Основной frontend проект нашей команды — это страница выдачи билетов со множеством фильтров, настроек и, собственно, билетов.
6 | Проект написан на React, поэтому тестовое задание приближено к ежедневным задачам.
7 |
8 | Перед тобой упрощенный макет нашего проекта — список билетов, фильтры и сортировка. Также у нас есть небольшой сервер для тестового задания, который работает схоже с нашим основным backend движком и реализует технику long polling для передачи пачек билетов. Тебе необходимо реализовать клиент, который будет получать случайно сгенерированные билеты от сервера и отрисует интерфейс согласно макету в Figma. Достаточно будет отрендерить 5 первых билетов соотвествующих выбранным фильрам и сортировки.
9 |
10 | ## Условия
11 |
12 | - Используй React
13 | - Используй TS или JS
14 | - Работоспособность в актуальной версии Google Chrome
15 | - Остальное на твоё усмотрение
16 |
17 | ## Документация по работе с сервером: [Здесь](https://github.com/victoriaZubareva/aviasales_frontend/blob/master/server.md)
18 |
19 | ## Макет
20 |
21 | https://www.figma.com/file/4fQe1lEbo4DARjvNtaU0uJ/Aviasales-test-task
22 |
23 | Залогинься, чтобы видеть CSS-свойства элементов
24 |
25 | 
26 |
27 | Удачи! Если будут какие-то вопросы, пиши – добавим уточнения в репу.
28 |
29 | P.S.: Картинки авиакомпаний можешь брать с нашего CDN: `//pics.avs.io/99/36/{IATA_CODE_HERE}.png`
30 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/ThemeSwitcher/ThemeSwitcher.module.scss:
--------------------------------------------------------------------------------
1 | .Switcher {
2 | position: absolute;
3 | right: 10px;
4 | }
5 |
6 | .SwitcherInput {
7 | display: none;
8 | }
9 |
10 | .SwitcherLabel {
11 | display: inline-block;
12 | position: relative;
13 | background-color: #83d8ff;
14 | height: 30px;
15 | width: 50px;
16 | border-radius: 18px;
17 | cursor: pointer;
18 | transition: background-image 0.2s cubic-bezier(0.445, 0.05, 0.55, 0.95);
19 | background-image: url("https://www.aviasales.ru/helios-assets/day-f57cce95d54a4169fca3f8536fc8bb16.svg");
20 | }
21 |
22 | .SwitcherToggle {
23 | display: inline-block;
24 | position: relative;
25 | z-index: 1;
26 | top: 4px;
27 | left: 4px;
28 | border-radius: 50px;
29 | transform: rotate(-45deg);
30 | transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
31 | background-color: #ff8742;
32 | background-image: url("https://www.aviasales.ru/helios-assets/sun-85fc2c3910b661d20b962851179ad09f.svg");
33 | width: 22px;
34 | height: 22px;
35 | box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.25),
36 | inset 0 0 10px 0 hsla(0, 0%, 100%, 0.5);
37 | }
38 |
39 | .SwitcherInput:checked + .SwitcherLabel {
40 | background-image: url("https://www.aviasales.ru/helios-assets/night-81c7c4b13c053594c9ac00efe3aea7bf.svg");
41 | }
42 |
43 | .SwitcherInput:checked + .SwitcherLabel .SwitcherToggle {
44 | background-color: #eff9ff;
45 | background-image: url("https://www.aviasales.ru/helios-assets/moon-8540e558b4839650675fbb94bc126f33.svg");
46 | box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.25),
47 | inset 0 -3px 6px 0 rgba(0, 0, 0, 0.15);
48 | transform: translate3d(20px, 0, 0) rotate(0);
49 | }
50 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/context/ThemeContext/ThemeColors.js:
--------------------------------------------------------------------------------
1 | export const lightTheme = [
2 | "--background-app: #f3f7fa",
3 | "--body-text: #4A4A4A",
4 | "--box-shadow: rgba(0, 0, 0, 0.1)",
5 | "--filter-background: #fff",
6 | "--filter-hover-item: #F1FCFF",
7 | "--filter-text: #4A4A4A",
8 | "--checkbox-box: rgba(154, 187, 206, 1)",
9 | "--checkbox-box-check: #2196F3",
10 | "--sorting-tab-background: #fff",
11 | "--sorting-tab-background-active: #2196F3",
12 | "--sorting-tab-background-hover: #F1FCFF",
13 | "--sorting-tab-text: #4A4A4A",
14 | "--sorting-tab-text-active: #fff",
15 | "--sorting-tab-text-hover: #2196F3",
16 | "--sorting-tab-border: #DFE5EC",
17 | "--sorting-tab-border-hover: #2196F3",
18 | "--ticket-background: #fff",
19 | "--ticket-price: #2196F3",
20 | "--ticket-text-subtitle: #A0B0B9",
21 | "--ticket-text: #4A4A4A"
22 | ];
23 |
24 | export const darkTheme = [
25 | "--background-app: #05263f",
26 | "--body-text: #fff",
27 | "--box-shadow: rgba(0, 0, 0, 0.1)",
28 | "--filter-background: #1e3c52",
29 | "--filter-hover-item: #355064",
30 | "--filter-text: #fff",
31 | "--checkbox-box: rgba(154, 187, 206, 1)",
32 | "--checkbox-box-check: #2196F3",
33 | "--sorting-tab-background: #1e3c52",
34 | "--sorting-tab-background-active: #2196f3",
35 | "--sorting-tab-background-hover: #031b2c",
36 | "--sorting-tab-text: #fff",
37 | "--sorting-tab-text-active: #fff",
38 | "--sorting-tab-text-hover: #fff",
39 | "--sorting-tab-border: #000",
40 | "--sorting-tab-border-hover: #000",
41 | "--ticket-background: #1e3c52",
42 | "--ticket-price: #f57c00",
43 | "--ticket-text-subtitle: #A0B0B9",
44 | "--ticket-text: #fff"
45 | ];
46 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/server.md:
--------------------------------------------------------------------------------
1 | # Описание взаимодействия с сервером
2 |
3 | Схема работы проста: сначала необходимо инициировать поиск на сервере и получить идентификатор поиска (`searchId`). Далее, с полученным `searchId`, ты делаешь запросы для получения неотсортированных списков билетов. Обрати внимание, что билеты прилетают пачками, которые необходимо агрегировать, фильтровать и сортировать согласно выбранным в интерфейсе параметрам. Для усложнения задачи, сервер может на один из запросов ответить ошибкой.
4 |
5 | ## Получение `searchId`
6 |
7 | Просто отправь GET-запрос на `https://front-test.beta.aviasales.ru/search` и получи его.
8 |
9 | Пример:
10 |
11 | Request: `https://front-test.beta.aviasales.ru/search`
12 |
13 | Response: `{"searchId":"4niyd"}`
14 |
15 | ## Получение пачки билетов
16 |
17 | Отправляй GET-запросы на `https://front-test.beta.aviasales.ru/tickets` и передай searchId полученный из запроса выше GET-параметром.
18 |
19 | Пример:
20 |
21 | Request: `https://front-test.beta.aviasales.ru/tickets?searchId=4niyd`
22 |
23 | Response: `{tickets: [], stop: false}`
24 |
25 | ## Обработка завершения поиска
26 |
27 | Поиск считается завершенным, когда в очередном ответе от сервера придёт значение `{stop: true}`.
28 |
29 | Пример:
30 |
31 | Request: `https://front-test.beta.aviasales.ru/tickets?searchId=4niyd`
32 |
33 | Response: `{tickets: [], stop: true}`
34 |
35 | ## Структура билета
36 |
37 | В списке `tickets` будут лежать билеты следующей структуры:
38 |
39 | ```typescript
40 | interface Ticket {
41 | // Цена в рублях
42 | price: number
43 | // Код авиакомпании (iata)
44 | carrier: string
45 | // Массив перелётов.
46 | // В тестовом задании это всегда поиск "туда-обратно" значит состоит из двух элементов
47 | segments: [
48 | {
49 | // Код города (iata)
50 | origin: string
51 | // Код города (iata)
52 | destination: string
53 | // Дата и время вылета туда
54 | date: string
55 | // Массив кодов (iata) городов с пересадками
56 | stops: string[]
57 | // Общее время перелёта в минутах
58 | duration: number
59 | },
60 | {
61 | // Код города (iata)
62 | origin: string
63 | // Код города (iata)
64 | destination: string
65 | // Дата и время вылета обратно
66 | date: string
67 | // Массив кодов (iata) городов с пересадками
68 | stops: string[]
69 | // Общее время перелёта в минутах
70 | duration: number
71 | }
72 | ]
73 | }
74 | ```
75 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/index.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --background-app: #f3f7fa;
3 | --body-text: #4A4A4A;
4 | --box-shadow: rgba(0, 0, 0, 0.1);
5 |
6 | --filter-background: #fff;
7 | --filter-hover-item: #F1FCFF;
8 | --filter-text: #4A4A4A;
9 |
10 | --checkbox-box: rgba(154, 187, 206, 1);
11 | --checkbox-box-check: #2196F3;
12 |
13 | --sorting-tab-background: #fff;
14 | --sorting-tab-background-active: #2196F3;
15 | --sorting-tab-background-hover: #F1FCFF;
16 | --sorting-tab-text: #4A4A4A;
17 | --sorting-tab-text-active: #fff;
18 | --sorting-tab-text-hover: #2196F3;
19 | --sorting-tab-border: #DFE5EC;
20 | --sorting-tab-border-hover: #2196F3;
21 |
22 | --ticket-background: #fff;
23 | --ticket-price: #2196F3;
24 | --ticket-text-subtitle: #A0B0B9;
25 | --ticket-text: #4A4A4A;
26 | }
27 |
28 | *, *:before, *:after {
29 | box-sizing: border-box;
30 | }
31 |
32 | a {
33 | text-decoration: none;
34 | cursor: pointer;
35 | }
36 |
37 | button {
38 | background-color: transparent;
39 | border-width: 0;
40 | padding: 0;
41 | cursor: pointer;
42 | outline: none;
43 | }
44 | figure {
45 | margin: 0;
46 | }
47 | input::-moz-focus-inner {
48 | border: 0;
49 | padding: 0;
50 | margin: 0;
51 | }
52 | ul,
53 | ol,
54 | dd {
55 | margin: 0;
56 | padding: 0;
57 | list-style: none;
58 | }
59 | h1,
60 | h2,
61 | h3,
62 | h4,
63 | h5,
64 | h6 {
65 | margin: 0;
66 | }
67 | p {
68 | margin: 0;
69 | }
70 | cite {
71 | font-style: normal;
72 | }
73 |
74 | .app {
75 | background: var(--background-app);
76 | padding: 20px 10px;
77 |
78 | &__inner {
79 | max-width: 1280px;
80 | margin: 0 auto;
81 | position: relative;
82 | }
83 |
84 | &__section {
85 | max-width: 800px;
86 | margin: 0 auto;
87 | }
88 |
89 | @media screen and (min-width: 630px) {
90 | padding: 55px 20px;
91 | }
92 | }
93 |
94 | .navbar {
95 | display: flex;
96 | align-items: center;
97 | justify-content: center;
98 | }
99 |
100 | body {
101 | margin: 0;
102 | font-family: -apple-system, BlinkMacSystemFont, "Open Sans", "Roboto",
103 | "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
104 | sans-serif;
105 | -webkit-font-smoothing: antialiased;
106 | -moz-osx-font-smoothing: grayscale;
107 | color: var(--body-text);
108 | transition: all .3s ease;
109 | }
110 |
111 | section {
112 | min-height: 100vh;
113 | }
114 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/FilterList/FilterList.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Checkbox } from "../Checkbox/Checkbox";
3 | import classes from "./FilterList.module.scss";
4 |
5 | export const FilterList = props => {
6 | const { hiddenTickets, sortNonStop, sortStops } = props.sortTickets;
7 |
8 | const [transfers, setTransfers] = useState([
9 | { value: "Все", id: "checkedAll", isChecked: true },
10 | { value: "Без пересадок", id: "nonStop", isChecked: true },
11 | { value: "1 пересадка", id: 1, isChecked: true },
12 | { value: "2 пересадки", id: 2, isChecked: true },
13 | { value: "3 пересадки", id: 3, isChecked: true }
14 | ]);
15 |
16 | /* универсальная функция которой будем переключать чекбоксы */
17 | const checkID = (id, isChecked) => {
18 | if (isChecked) {
19 | const newChecked = transfers.map(item =>
20 | item.id === id ? { ...item, isChecked: true } : { ...item }
21 | );
22 | setTransfers(newChecked);
23 | } else {
24 | const newChecked = transfers.map(item => {
25 | if (item.id === id || item.id === "checkedAll") {
26 | return { ...item, isChecked: false };
27 | } else {
28 | return { ...item };
29 | }
30 | });
31 | setTransfers(newChecked);
32 | }
33 | };
34 |
35 | const changeIsChecked = (id, isChecked) => {
36 | // если выбраны "все" билеты, меняем их isChecked
37 | if (id === "checkedAll") {
38 | if (isChecked === true) {
39 | const newChecked = transfers.map(item => ({
40 | ...item,
41 | isChecked: true
42 | }));
43 | setTransfers(newChecked);
44 | } else {
45 | const newChecked = transfers.map(item => ({
46 | ...item,
47 | isChecked: false
48 | }));
49 | setTransfers(newChecked);
50 | }
51 | hiddenTickets(isChecked);
52 | }
53 |
54 | // если выбрано 'Без пересадок'
55 | else if (id === "nonStop") {
56 | checkID(id, isChecked);
57 | sortNonStop(isChecked);
58 | }
59 | // все остальные варианты пересадок
60 | else {
61 | checkID(id, isChecked);
62 | sortStops(id, isChecked);
63 | }
64 | };
65 |
66 | return (
67 |
68 |
Количество пересадок
69 |
70 | {transfers.map(item => {
71 | return (
72 |
73 |
74 |
75 | );
76 | })}
77 |
78 |
79 | );
80 | };
81 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/pages/testData.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "price": 76945,
4 | "carrier": "MH",
5 | "segments": [
6 | {
7 | "origin": "MOW",
8 | "destination": "HKT",
9 | "date": "2020-03-27T00:57:00.000Z",
10 | "stops": ["AUH"],
11 | "duration": 1541
12 | },
13 | {
14 | "origin": "MOW",
15 | "destination": "HKT",
16 | "date": "2020-04-16T15:41:00.000Z",
17 | "stops": [],
18 | "duration": 1097
19 | }
20 | ]
21 | },
22 | {
23 | "price": 48435,
24 | "carrier": "TG",
25 | "segments": [
26 | {
27 | "origin": "MOW",
28 | "destination": "HKT",
29 | "date": "2020-03-27T12:04:00.000Z",
30 | "stops": [],
31 | "duration": 803
32 | },
33 | {
34 | "origin": "MOW",
35 | "destination": "HKT",
36 | "date": "2020-04-15T21:23:00.000Z",
37 | "stops": ["BKK", "AUH"],
38 | "duration": 944
39 | }
40 | ]
41 | },
42 | {
43 | "price": 99919,
44 | "carrier": "FV",
45 | "segments": [
46 | {
47 | "origin": "MOW",
48 | "destination": "HKT",
49 | "date": "2020-03-27T15:38:00.000Z",
50 | "stops": ["SIN", "DXB", "HKG"],
51 | "duration": 1373
52 | },
53 | {
54 | "origin": "MOW",
55 | "destination": "HKT",
56 | "date": "2020-04-16T13:07:00.000Z",
57 | "stops": ["HKG", "AUH"],
58 | "duration": 869
59 | }
60 | ]
61 | },
62 | {
63 | "price": 72145,
64 | "carrier": "S7",
65 | "segments": [
66 | {
67 | "origin": "MOW",
68 | "destination": "HKT",
69 | "date": "2020-03-27T14:36:00.000Z",
70 | "stops": [],
71 | "duration": 1433
72 | },
73 | {
74 | "origin": "MOW",
75 | "destination": "HKT",
76 | "date": "2020-04-16T04:10:00.000Z",
77 | "stops": [],
78 | "duration": 1049
79 | }
80 | ]
81 | },
82 | {
83 | "price": 73132,
84 | "carrier": "SU",
85 | "segments": [
86 | {
87 | "origin": "MOW",
88 | "destination": "HKT",
89 | "date": "2020-03-27T11:07:00.000Z",
90 | "stops": ["SIN", "IST", "SHA"],
91 | "duration": 1835
92 | },
93 | {
94 | "origin": "MOW",
95 | "destination": "HKT",
96 | "date": "2020-04-16T01:03:00.000Z",
97 | "stops": ["IST", "SIN", "HKG"],
98 | "duration": 1331
99 | }
100 | ]
101 | }
102 | ]
103 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Ticket/Ticket.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import classes from "./Ticket.module.scss";
3 |
4 | export const Ticket = ({ ticket }) => {
5 | const picAvs = "http://pics.avs.io/99/36/";
6 |
7 | // вырезаем время вылета + получаем время прилета
8 | const splitTimeOut = (str, dur) => {
9 | const [, time] = str.slice(0, -1).split("T");
10 | const [h, m] = time.split(":");
11 | const sum = Number(h) * 60 + Number(m) + dur;
12 | const newH =
13 | sum / 60 >= 24 ? Math.floor(sum / 60 - 24) : Math.floor(sum / 60);
14 | const newM = sum % 60;
15 | const formH = newH < 10 ? `0${newH}` : newH;
16 | const formM = newM < 10 ? `0${newM}` : newM;
17 |
18 | return `${h}:${m} - ${formH}:${formM}`;
19 | }
20 |
21 | // переводим длительность перелета(минуты) в формат 00:00
22 | const durationFunc = number => {
23 | const hours = Math.round(number / 60);
24 | const minutes = number % 60;
25 | return `${hours}ч : ${minutes}м`;
26 | }
27 |
28 | return (
29 |
30 |
31 |
32 | {`${ticket.price.toLocaleString()} P`}
33 |
34 |
35 |
36 |
37 |
38 |
39 | {ticket.segments.map((item, i) => (
40 |
41 |
42 |
43 | {item.origin} – {item.destination}
44 |
45 |
46 | {splitTimeOut(item.date, item.duration)}
47 |
48 |
49 |
50 | В пути
51 |
52 | {durationFunc(item.duration)}
53 |
54 |
55 |
56 |
57 | {item.stops.length > 0
58 | ? `${item.stops.length} пересадк${
59 | item.stops.length === 1 ? "a" : "и"
60 | }`
61 | : "Без пересадок"}
62 |
63 | {item.stops.join(", ")}
64 |
65 |
66 | ))}
67 |
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/aviasales-master/src/views/Content.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/pages/Search.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import classes from "./Search.module.scss";
3 | import { FilterList } from "../components/FilterList/FilterList";
4 | import { TicketList } from "../components/TicketList/TicketList";
5 | import { Sorting } from "../components/Sorting/Sorting";
6 | import { Alert } from "../components/Alert/Alert";
7 |
8 | import data from './testData.json' // тестовые данные
9 |
10 | export const Search = () => {
11 | const [tickets, setTickets] = useState([]); // неотсортированный список билетов
12 | const [hasError, setHasError] = useState(false);
13 | const [cloneTickets, setCloneTickets] = useState(null); // массив билетов который будем сортировать по пересадкам
14 | const [selectTub, setSelectTub] = useState("low price"); // таб для автоматической сортировки билетов (цена/скорость)
15 |
16 | // отсортированные по всем параметрам билеты для отрисовки
17 | const [sortTickets, setSortTickets] = useState(null);
18 |
19 | useEffect(() => {
20 | //*** закомментировала сервер aviasales, использую сохраненный json ***/
21 | // fetch("https://front-test.beta.aviasales.ru/search")
22 | // .then(res => res.json())
23 | // .then(({ searchId }) =>
24 | // fetch(
25 | // `https://front-test.beta.aviasales.ru/tickets?searchId=${searchId}`
26 | // )
27 | // )
28 | // .then(res => res.json())
29 | // .then(({ tickets }) => {
30 | // setTickets(tickets.slice(0, 5));
31 | // setCloneTickets(tickets.slice(0, 5));
32 | // })
33 | // .catch(() => setHasError(true));
34 | const tickets = data
35 | setTickets(tickets);
36 | setCloneTickets(tickets);
37 | }, []);
38 |
39 | useEffect(() => {
40 | function setSortTabs() {
41 | if (!cloneTickets) return;
42 | if (selectTub === "low price") {
43 | const newArr = [...cloneTickets].sort((a, b) =>
44 | a.price > b.price ? 1 : -1
45 | );
46 | setSortTickets(newArr);
47 | }
48 | if (selectTub === "fastest") {
49 | const newArr = [...cloneTickets].sort((a, b) => {
50 | const firstTicket = a.segments.reduce(
51 | (acc, i) => i.duration + acc,
52 | 0
53 | );
54 | const secondTicket = b.segments.reduce(
55 | (acc, i) => i.duration + acc,
56 | 0
57 | );
58 |
59 | if (firstTicket > secondTicket) {
60 | return 1;
61 | } else if (firstTicket < secondTicket) {
62 | return -1;
63 | } else {
64 | return 0;
65 | }
66 | });
67 | setSortTickets(newArr);
68 | }
69 | }
70 | setSortTabs();
71 | }, [cloneTickets, selectTub]);
72 |
73 | // скрыть/показать все билеты
74 | const hiddenTickets = isChecked => {
75 | isChecked ? setCloneTickets(tickets) : setCloneTickets([]);
76 | };
77 |
78 | // билеты без пересадок
79 | const sortNonStop = isChecked => {
80 | if (isChecked) {
81 | const newArr = tickets.filter(item => {
82 | return item.segments.every(i => i.stops.length === 0); // every вернет true, если ВСЕ маршруты без пересадок
83 | });
84 | setCloneTickets([...cloneTickets, ...newArr]); // вернем в state новый массив с билетами без пересадок
85 | } else {
86 | const newArr = cloneTickets.filter(item => {
87 | return item.segments.some(i => i.stops.length > 0); // оставим только те билеты, у которых хотя бы один маршрут с пересадкой
88 | });
89 | setCloneTickets(newArr);
90 | }
91 | };
92 |
93 | /* универсальная функция которой будем сортировать билеты
94 | в зависимости от количества пересадок */
95 | const sortStops = (numb, isChecked) => {
96 | if (isChecked) {
97 | const newArr = tickets.filter(item => {
98 | // вернет 'true' если хотя бы один маршрут в билете будет с заданным числом пересадок
99 | const sort1 = item.segments.some(i => i.stops.length === numb);
100 | // вернет 'true' если каждый маршрут меньше или равняется заданному числу пересадок
101 | const sort2 = item.segments.every(i => i.stops.length <= numb);
102 | return sort1 && sort2;
103 | });
104 | setCloneTickets([...cloneTickets, ...newArr]);
105 | } else {
106 | const newArr = cloneTickets.filter(item => {
107 | const sort1 = item.segments.some(i => i.stops.length > numb);
108 | const sort2 = item.segments.every(i => i.stops.length < numb);
109 | return sort1 || sort2;
110 | });
111 | setCloneTickets(newArr);
112 | }
113 | };
114 |
115 | // функция для изменения state 'selectTub' для сортировки по табам
116 | const sortTabs = id => {
117 | setSelectTub(id);
118 | };
119 |
120 | return (
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | {sortTickets ? (
130 |
131 | ) : hasError ? (
132 |
133 | ) : null}
134 |
135 |
136 | );
137 | };
138 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/aviasales-master/src/assets/Logo.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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/aviasales-master/aviasales_frontend-master/src/components/Logo/Logo.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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------