Skip to content

Commit b4a827e

Browse files
author
Le Tuan Kiet
committed
[RTT-04] Implate UI Weather APP
1 parent 2044716 commit b4a827e

File tree

11 files changed

+29049
-1270
lines changed

11 files changed

+29049
-1270
lines changed

package-lock.json

Lines changed: 27418 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@types/react-dom": "^18.0.0",
1616
"@types/uuid": "^8.3.4",
1717
"axios": "^0.27.2",
18+
"lodash": "^4.17.21",
1819
"react": "^18.2.0",
1920
"react-dom": "^18.2.0",
2021
"react-scripts": "5.0.1",
@@ -46,5 +47,8 @@
4647
"last 1 firefox version",
4748
"last 1 safari version"
4849
]
50+
},
51+
"devDependencies": {
52+
"@types/lodash": "^4.14.186"
4953
}
5054
}

src/App.css

Lines changed: 192 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,203 @@
1-
.App {
1+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
2+
*{
3+
margin: 0;
4+
padding: 0;
5+
box-sizing: border-box;
6+
font-family: 'Poppins', sans-serif;
7+
}
8+
body{
9+
display: flex;
10+
align-items: center;
11+
justify-content: center;
12+
min-height: 100vh;
13+
background: #43AFFC;
14+
}
15+
::selection{
16+
color: #fff;
17+
background: #43AFFC;
18+
}
19+
.wrapper{
20+
width: 400px;
21+
background: #fff;
22+
border-radius: 7px;
23+
box-shadow: 7px 7px 20px rgba(0, 0, 0, 0.05);
24+
}
25+
.wrapper header{
26+
display: flex;
27+
font-size: 21px;
28+
font-weight: 500;
29+
color: #43AFFC;
30+
padding: 16px 15px;
31+
align-items: center;
32+
border-bottom: 1px solid #ccc;
33+
}
34+
header i{
35+
font-size: 0em;
36+
cursor: pointer;
37+
margin-right: 8px;
38+
}
39+
.wrapper.active header i{
40+
margin-left: 5px;
41+
font-size: 30px;
42+
}
43+
.wrapper .input-part{
44+
padding: 20px 25px 30px;
45+
}
46+
.wrapper.active .input-part{
47+
display: none;
48+
}
49+
.input-part .info-txt{
50+
display: none;
51+
font-size: 17px;
252
text-align: center;
53+
padding: 12px 10px;
54+
border-radius: 7px;
55+
margin-bottom: 15px;
356
}
4-
5-
.App-logo {
6-
height: 40vmin;
7-
pointer-events: none;
57+
.input-part .info-txt.error{
58+
color: #721c24;
59+
display: block;
60+
background: #f8d7da;
61+
border: 1px solid #f5c6cb;
862
}
9-
10-
@media (prefers-reduced-motion: no-preference) {
11-
.App-logo {
12-
animation: App-logo-spin infinite 20s linear;
13-
}
63+
.input-part .info-txt.pending{
64+
color: #0c5460;
65+
display: block;
66+
background: #d1ecf1;
67+
border: 1px solid #bee5eb;
1468
}
15-
16-
.App-header {
17-
background-color: #282c34;
18-
min-height: 100vh;
69+
.input-part :where(input, button){
70+
width: 100%;
71+
height: 55px;
72+
border: none;
73+
outline: none;
74+
font-size: 18px;
75+
border-radius: 7px;
76+
}
77+
.input-part input{
78+
text-align: center;
79+
padding: 0 15px;
80+
border: 1px solid #ccc;
81+
}
82+
.input-part input:is(:focus, :valid){
83+
border: 2px solid #43AFFC;
84+
}
85+
.input-part input::placeholder{
86+
color: #bfbfbf;
87+
}
88+
.input-part .separator{
89+
height: 1px;
90+
width: 100%;
91+
margin: 25px 0;
92+
background: #ccc;
93+
position: relative;
1994
display: flex;
20-
flex-direction: column;
2195
align-items: center;
2296
justify-content: center;
23-
font-size: calc(10px + 2vmin);
24-
color: white;
2597
}
26-
27-
.App-link {
28-
color: #61dafb;
98+
.separator::before{
99+
content: "or";
100+
color: #b3b3b3;
101+
font-size: 19px;
102+
padding: 0 15px;
103+
background: #fff;
104+
}
105+
.input-part button{
106+
color: #fff;
107+
cursor: pointer;
108+
background: #43AFFC;
109+
transition: 0.3s ease;
110+
}
111+
.input-part button:hover{
112+
background: #1d9ffc;
29113
}
30114

31-
@keyframes App-logo-spin {
32-
from {
33-
transform: rotate(0deg);
34-
}
35-
to {
36-
transform: rotate(360deg);
37-
}
115+
.wrapper .weather-part{
116+
display: none;
117+
margin: 30px 0 0;
118+
align-items: center;
119+
justify-content: center;
120+
flex-direction: column;
121+
}
122+
.wrapper.active .weather-part{
123+
display: flex;
124+
}
125+
.weather-part img{
126+
max-width: 125px;
127+
}
128+
.weather-part .temp{
129+
display: flex;
130+
font-weight: 500;
131+
font-size: 72px;
132+
}
133+
.weather-part .temp .numb{
134+
font-weight: 600;
135+
}
136+
.weather-part .temp .deg{
137+
font-size: 40px;
138+
display: block;
139+
margin: 10px 5px 0 0;
38140
}
141+
.weather-part .weather{
142+
font-size: 21px;
143+
text-align: center;
144+
margin: -5px 20px 15px;
145+
}
146+
.weather-part .location{
147+
display: flex;
148+
font-size: 19px;
149+
padding: 0 20px;
150+
text-align: center;
151+
margin-bottom: 30px;
152+
align-items: flex-start;
153+
}
154+
.location i{
155+
font-size: 22px;
156+
margin: 4px 5px 0 0;
157+
}
158+
.weather-part .bottom-details{
159+
display: flex;
160+
width: 100%;
161+
justify-content: space-between;
162+
border-top: 1px solid #ccc;
163+
}
164+
.bottom-details .column{
165+
display: flex;
166+
width: 100%;
167+
padding: 15px 0;
168+
align-items: center;
169+
justify-content: center;
170+
}
171+
.column i{
172+
color: #5DBBFF;
173+
font-size: 40px;
174+
}
175+
.column.humidity{
176+
border-left: 1px solid #ccc;
177+
}
178+
.column .details{
179+
margin-left: 3px;
180+
}
181+
.details .temp, .humidity span{
182+
font-size: 18px;
183+
font-weight: 500;
184+
margin-top: -3px;
185+
}
186+
.details .temp .deg{
187+
margin: 0;
188+
font-size: 17px;
189+
padding: 0 2px 0 1px;
190+
}
191+
.column .details p{
192+
font-size: 14px;
193+
margin-top: -6px;
194+
}
195+
.humidity i{
196+
font-size: 37px;
197+
}
198+
199+
.text-error{
200+
color:red;
201+
text-align: center;
202+
margin-top: 1rem;
203+
}

src/App.tsx

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,93 @@
1-
import './App.css';
1+
import { debounce } from "@mui/material";
2+
import { useEffect, useReducer } from "react";
3+
import services from "./api/index";
4+
import "./App.css";
5+
import {
6+
ACTION_TYPE,
7+
apiReducer,
8+
INITIAL_STATE
9+
} from "./utils/reducers/WeatherReducers";
210

3-
function App() {
11+
function App() {
12+
const [state, dispatch] = useReducer(apiReducer, INITIAL_STATE);
413

14+
useEffect(() => {}, [state]);
15+
16+
const handleSearchWithCityName = debounce((nameCity: string) => {
17+
getInfoWithCityName(nameCity);
18+
}, 1000);
19+
20+
const getInfoWithCityName = async (nameCity: string) => {
21+
const formRequest = {
22+
q: nameCity,
23+
appid: process.env.REACT_APP_KEY || "",
24+
};
25+
26+
dispatch({ type: ACTION_TYPE.FETCH_START });
27+
28+
await services.weather
29+
.index(formRequest)
30+
.then(({data:weatherInfo}) => {
31+
dispatch({ type: ACTION_TYPE.FETCH_SUCCESS, payload: weatherInfo });
32+
})
33+
.catch((e: ApiError) => {
34+
dispatch({ type: ACTION_TYPE.FETCH_FAILED, errorMsg: e.message });
35+
});
36+
};
537

638
return (
739
<div className="App">
8-
<header className="App-header">
9-
<p>
10-
Edit <code>src/App.tsx</code> and save to reload.
11-
</p>
12-
<a
13-
className="App-link"
14-
href="https://reactjs.org"
15-
target="_blank"
16-
rel="noopener noreferrer"
17-
>
18-
Learn React
19-
</a>
20-
</header>
40+
<div className="wrapper">
41+
<header>
42+
<i className="bx bx-left-arrow-alt"></i>Weather App
43+
</header>
44+
<section className="input-part">
45+
<p className="info-txt"></p>
46+
<div className="content">
47+
<input
48+
type="text"
49+
spellCheck="false"
50+
placeholder="Enter city name"
51+
onChange={(e) => handleSearchWithCityName(e.target.value)}
52+
required
53+
/>
54+
<div className="separator"></div>
55+
<button disabled={state.isLoading}>Get Device Location</button>
56+
{state.isError && <p className="text-error">{state.errorMsg}</p>}
57+
</div>
58+
</section>
59+
<section className="weather-part">
60+
<img src="" alt="Weather Icon" />
61+
<div className="temp">
62+
<span className="numb">_</span>
63+
<span className="deg">°</span>C
64+
</div>
65+
<div className="weather">_ _</div>
66+
<div className="location">
67+
<i className="bx bx-map"></i>
68+
<span>_, _</span>
69+
</div>
70+
<div className="bottom-details">
71+
<div className="column feels">
72+
<i className="bx bxs-thermometer"></i>
73+
<div className="details">
74+
<div className="temp">
75+
<span className="numb-2">_</span>
76+
<span className="deg">°</span>C
77+
</div>
78+
<p>Feels like</p>
79+
</div>
80+
</div>
81+
<div className="column humidity">
82+
<i className="bx bxs-droplet-half"></i>
83+
<div className="details">
84+
<span>_</span>
85+
<p>Humidity</p>
86+
</div>
87+
</div>
88+
</div>
89+
</section>
90+
</div>
2191
</div>
2292
);
2393
}

src/api/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/* eslint-disable import/no-anonymous-default-export */
2-
// import API from "../utils/constants/api";
3-
// import RestService from "../utils/service/api";
2+
import API from "../utils/constants/api";
3+
import RestService from "../utils/service/api";
44

55
export default {
66
// EXAMPLE: Your need delete it
7-
// weather: new RestService<any, { q: string; appid: string } | { a: string }>(
8-
// API.WEATHER
9-
// ),
7+
weather: new RestService<ApiResponse, { q: string; appid: string }>(
8+
API.WEATHER
9+
),
1010
};

src/utils/constants/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/* eslint-disable import/no-anonymous-default-export */
22
export enum API_STATUS {
33
HTTP_404_NOT_FOUND = 400,
4+
HTTP_500_SERVER = 500,
45
}
56

67
//TODO: you need delete when integrate API
78
export default {
8-
HELLO: "hello",
99
WEATHER: "weather",
1010
};

0 commit comments

Comments
 (0)