Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WEATHER_API_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9wZmEuZm9yZWNhLmNvbVwvYXV0aG9yaXplXC90b2tlbiIsImlhdCI6MTY2MjA0OTA3MiwiZXhwIjo5OTk5OTk5OTk5LCJuYmYiOjE2NjIwNDkwNzIsImp0aSI6ImE4NDRkZjZmZjA2MWY3N2UiLCJzdWIiOiJjb25jZW5jdWMiLCJmbXQiOiJYRGNPaGpDNDArQUxqbFlUdGpiT2lBPT0ifQ.myDTy0LZS__4k-Gjjz4jP_5ljikTDP4vEsrKzWC79oM
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ package-lock.json
/build/*
# Editor/IDE
.idea
packages/api/etc/
packages/api/etc/

# api keys
.env
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,21 @@ You are about to create weather application with location detection.

### Full description

Use weather API: https://developer.foreca.com/#Forecasts. Note that this API is **NOT** public. Your task is to create
front-end for that API. Your application should not have any login buttons to authenticate against weather api. It
should happen silently for the user. You need to create next parts (every page has a layout with a header, footer and
Use weather API: https://developer.foreca.com/#Forecasts. Note that this API is **NOT** public. Your task is to create
front-end for that API. Your application should not have any login buttons to authenticate against weather api. It
should happen silently for the user. You need to create next parts (every page has a layout with a header, footer and
navigation panel):

1. Main page with weather for the current location.
2. List page (may also be a flyout or any other kind of menu), where user can see the weather for all cities in the World
(all available cities) sorted by country and city title.
3. Page with details about chosen (in a list from previous point) city.
3. Page with details about chosen (in a list from previous point) city.
4. Info page where user can see some information about the service.
5. Feedback page with a form for feedback on your site: please, create the form in survey style with some simple questions.
Implement form submitting mechanism and save it in localStorage. For now don't bother yourself saving form data in DB.
6. Implement Dark/Light mode for your app. There should be some sort of switcher that changes current view.


### P.S.

There is a branch <name>_<surname> created for you in original repository. Your task is to fork from this repo and work
There is a branch <name>\_<surname> created for you in original repository. Your task is to fork from this repo and work
in your branches there. At the end of the day, you should create PRs against your branches in original repo.
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = function (api) {
['@babel/preset-react', { runtime: 'automatic' }]
];
const plugins = [
'@babel/transform-react-constant-elements',
'@babel/transform-react-constant-elements',
'transform-react-pure-class-to-function',
'@babel/plugin-transform-runtime',
'react-hot-loader/babel',
Expand Down
85 changes: 85 additions & 0 deletions backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const express = require('express');
const cors = require('cors');
const axios = require('axios');
require('dotenv').config();
const app = express();

app.use(cors());

const foreca = axios.create({
baseURL: 'https://pfa.foreca.com/api/v1',
headers: {
authorization: `Bearer ${process.env.WEATHER_API_KEY}`
}
});

app.get('/', (req, res) => {
res.send('Homepage');
});

app.get('/get-city', (req, res) => {
foreca.get(`location/${req.query.latitude},${req.query.latitude}`).then(function (response) {
res.json({
country: response.data.country,
city: response.data.name,
latitude: response.data.lat,
longitude: response.data.lon
});
});
});

app.get('/get-city-coords', (req, res) => {
foreca.get(`location/search/${req.query.query}`).then(function (response) {
res.json({
country: response.data.locations[0].country,
city: response.data.locations[0].name,
latitude: response.data.locations[0].lat,
longitude: response.data.locations[0].lon
});
});
});

app.get('/get-current-weather', (req, res) => {
foreca
.get(`current/location=${req.query.latitude},${req.query.longitude}`)
.then(function (response) {
res.json({
country: req.query.country,
city: req.query.city,
symbol: response.data.current.symbol,
temperature: response.data.current.temperature,
relHumidity: response.data.current.relHumidity,
windSpeed: response.data.current.windSpeed,
cloudiness: response.data.current.cloudiness
});
});
});

app.get('/get-detail-weather', (req, res) => {
foreca
.get(
`forecast/daily/location=${req.query.latitude},${req.query.longitude}&dataset=full&periods=8`
)
.then(function (response) {
let detailWeather = () => {
return response.data.forecast.map(item => {
return {
date: item.date,
symbol: item.symbol,
minTemp: item.minTemp,
maxTemp: item.maxTemp,
minRelHumidity: item.minRelHumidity,
maxRelHumidity: item.maxRelHumidity,
windSpeed: item.maxWindSpeed,
cloudiness: item.cloudiness
};
});
};

res.send(detailWeather());
});
});

app.listen(8000, () => {
console.log('server is runnings');
});
22 changes: 18 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"start": "npm run format && cross-env NODE_ENV=development webpack-dev-server --open",
"start": "concurrently \"npm:start:backend\" \"npm:start:frontend\"",
"start:backend": "nodemon backend.js",
"start:frontend": "npm run format && cross-env NODE_ENV=development webpack-dev-server --open",
"build": "cross-env NODE_ENV=production webpack",
"test": "jest --coverage",
"format": "prettier --write ."
Expand All @@ -18,18 +20,29 @@
"npm": ">=3"
},
"dependencies": {
"@reduxjs/toolkit": "^1.8.3",
"axios": "0.21.4",
"concurrently": "^7.3.0",
"core-js": "3.2.1",
"cors": "^2.8.5",
"country-state-city": "^3.0.1",
"dotenv": "^16.0.2",
"express": "^4.18.1",
"history": "4.10.1",
"nodemon": "^2.0.19",
"npm-run-all": "^4.1.5",
"parallel": "^1.2.0",
"prop-types": "15.7.2",
"ramda": "0.26.1",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-geolocated": "^4.0.3",
"react-redux": "7.2.2",
"react-router-dom": "5.2.0",
"react-router-dom": "^6.3.0",
"redux": "4.0.5",
"redux-thunk": "2.3.0",
"reselect": "4.0.0"
"reselect": "4.0.0",
"webpack-php-loader": "^0.5.0"
},
"resolutions": {
"babel-core": "7.0.0-bridge.0"
Expand Down Expand Up @@ -62,6 +75,7 @@
"connect-history-api-fallback": "1.6.0",
"cross-env": "6.0.3",
"css-loader": "3.2.0",
"dotenv-webpack": "^8.0.0",
"file-loader": "4.2.0",
"html-webpack-plugin": "3.2.0",
"husky": "3.0.8",
Expand All @@ -70,13 +84,13 @@
"jsdom": "15.1.1",
"koa-connect": "2.0.1",
"mini-css-extract-plugin": "0.8.0",
"node-sass": "4.14.1",
"npm-check-updates": "3.1.24",
"optimize-css-assets-webpack-plugin": "5.0.3",
"prettier": "2.2.1",
"pretty-quick": "1.11.1",
"react-hot-loader": "4.13.0",
"redux-immutable-state-invariant": "2.1.0",
"sass": "1.52.3",
"sass-loader": "8.0.0",
"script-ext-html-webpack-plugin": "2.1.4",
"style-loader": "1.0.0",
Expand Down
9 changes: 9 additions & 0 deletions src/Api/createWeatherApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TOKEN, MAIN_URL } from '../Config/constants';
const axios = require('axios').default;

export default axios.create({
baseURL: MAIN_URL,
headers: {
authorization: `Bearer ${TOKEN}`
}
});
14 changes: 14 additions & 0 deletions src/Api/getCity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const axios = require('axios');
import { backendURL } from '../Config/constants';

const getCity = async coords => {
const options = {
method: 'GET',
url: backendURL + '/get-city',
params: coords
};

return await axios.request(options).then(response => response.data);
};

export default getCity;
14 changes: 14 additions & 0 deletions src/Api/getCityCoords.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const axios = require('axios');
import { backendURL } from '../Config/constants';

const getCityCoords = async query => {
const options = {
method: 'GET',
url: backendURL + '/get-city-coords',
params: { query: query }
};

return await axios.request(options).then(response => response.data);
};

export default getCityCoords;
14 changes: 14 additions & 0 deletions src/Api/getCurrentWeather.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const axios = require('axios');
import { backendURL } from '../Config/constants';

const getCurrentWeather = async coords => {
const options = {
method: 'GET',
url: backendURL + '/get-current-weather',
params: coords
};

return await axios.request(options).then(response => response.data);
};

export default getCurrentWeather;
14 changes: 14 additions & 0 deletions src/Api/getDetailWeather.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const axios = require('axios');
import { backendURL } from '../Config/constants';

const getDetailWeather = async coords => {
const options = {
method: 'GET',
url: backendURL + '/get-detail-weather',
params: coords
};

return await axios.request(options).then(response => response.data);
};

export default getDetailWeather;
5 changes: 0 additions & 5 deletions src/App.jsx

This file was deleted.

3 changes: 0 additions & 3 deletions src/App.test.js

This file was deleted.

62 changes: 62 additions & 0 deletions src/Components/CitiesForm/CitiesForm.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.form {
margin-top: 30px;
margin-right: 50px;
width: 350px;
display: flex;
flex-wrap: wrap;
}
.selectWrap {
margin-bottom: 20px;
position: relative;
width: 100%;
}
.selectWrap:after {
content: '';
position: absolute;
right: 25px;
top: 19px;
width: 13px;
height: 13px;
transform: rotate(45deg);
border: 3px solid var(--text-primary);
border-top: none;
border-left: none;
}
.select {
cursor: pointer;
border: 3px solid #3cc2ff;
border-radius: 30px;
display: inline-flex;
padding: 0 20px;
height: 54px;
line-height: 48px;
width: 100%;
}
.inputWrap {
width: 100%;
border: 3px solid #3cc2ff;
border-radius: 30px;
display: inline-flex;
padding: 6px;
padding-left: 20px;
}
.input {
padding-right: 20px;
width: 100%;
}
.btn {
display: inline-block;
padding: 0 20px;
border-radius: 20px;
height: 40px;
line-height: 40px;
font-weight: 500;
font-size: 20px;
text-align: center;
background-color: #3cc2ff;
color: #fff;
}
.btn:hover {
transition: 0.2s;
background-color: #3095cb;
}
Loading