Skip to content

Commit 33c52e0

Browse files
committed
Try to dockerize the app
1 parent f065585 commit 33c52e0

26 files changed

Lines changed: 452 additions & 150 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data

Frontend/solarWatch/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Build Stage
2+
FROM node:18-alpine AS build
3+
WORKDIR /app
4+
COPY package*.json ./
5+
RUN npm i
6+
COPY . .
7+
RUN npm run build
8+
9+
# Production Stage
10+
FROM nginx:stable-alpine AS production
11+
COPY --from=build /app/dist /usr/share/nginx/html
12+
COPY nginx.conf /etc/nginx/conf.d/default.conf
13+
EXPOSE 80
14+
CMD ["nginx", "-g", "daemon off;"]

Frontend/solarWatch/nginx.conf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
server {
2+
listen 80;
3+
server_name localhost;
4+
root /usr/share/nginx/html;
5+
index index.html;
6+
7+
location / {
8+
try_files $uri $uri/ /index.html;
9+
}
10+
11+
location /api {
12+
proxy_pass http://backend:8081;
13+
proxy_http_version 1.1;
14+
proxy_set_header Upgrade $http_upgrade;
15+
proxy_set_header Connection 'upgrade';
16+
proxy_set_header Host $host;
17+
proxy_cache_bypass $http_upgrade;
18+
}
19+
}
Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default function CityList({ cities }) {
22
return (
3-
<div className="w-full overflow-x-auto whitespace-nowrap p-4 bg-base-200 rounded-box shadow flex">
3+
<div className="w-full whitespace-nowrap p-4 rounded-box ">
44
{cities.map((city, i) => {
55
const backgroundImage = city.pic
66
? `url(${city.pic})`
@@ -9,32 +9,22 @@ export default function CityList({ cities }) {
99
return (
1010
<div
1111
key={i}
12-
className="inline-block w-64 h-80 mr-4 bg-base-100 rounded-box shadow bg-cover bg-center relative text-white"
12+
className="inline-block w-64 h-80 mr-4 rounded-box shadow bg-cover bg-center relative text-white mt-6"
1313
style={{ backgroundImage }}
1414
>
1515
<div className="absolute inset-0 rounded-box p-4 flex flex-col justify-end" style={{ backgroundColor: "rgba(0, 0, 0, 0.4)" }}>
1616
<div className="font-bold text-lg">{city.city}, {city.country}</div>
1717
{city.solarData?.[0] && (
1818
<div className="text-sm">
19-
<p><strong>Date:</strong> {city.solarData[0].date}</p>
20-
<p><strong>Sunrise:</strong> {city.solarData[0].sunrise}</p>
21-
<p><strong>Sunset:</strong> {city.solarData[0].sunset}</p>
19+
<p><strong>Date:</strong> {city.solarData[city.solarData.length-1].date}</p>
20+
<p><strong>Sunrise:</strong> {city.solarData[city.solarData.length-1].sunrise}</p>
21+
<p><strong>Sunset:</strong> {city.solarData[city.solarData.length-1].sunset}</p>
2222
</div>
2323
)}
2424
</div>
2525
</div>
2626
);
2727
})}
2828
</div>
29-
// <div>
30-
// <h3 className="text-lg font-semibold mb-2">Previous Searches:</h3>
31-
// <ul className="space-y-2">
32-
// {cities.map((city, i) => (
33-
// <div key={i} className="border h-10 flex items-center p-3">
34-
// <div>{city.city}</div>
35-
// </div>
36-
// ))}
37-
// </ul>
38-
// </div>
3929
);
4030
}

Frontend/solarWatch/src/components/Globe.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function GlobeViewer({ city }) {
77
useEffect(() => {
88
console.log("City data:", city);
99
if (city && city.lat && city.lon) {
10-
globeEl.current.pointOfView({ lat: city.lat, lng: city.lon, altitude: 1.5 }, 1000);
10+
globeEl.current.pointOfView({ lat: city.lat, lng: city.lon, altitude: 0.75}, 1000);
1111
}
1212
}, [city]);
1313

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { useEffect, useState } from "react";
2+
3+
const Notification = ({ type = "info", message, onClose }) => {
4+
const [progress, setProgress] = useState(100);
5+
6+
useEffect(() => {
7+
const interval = setInterval(() => {
8+
setProgress(prev => {
9+
if (prev <= 0) {
10+
clearInterval(interval);
11+
onClose();
12+
return 0;
13+
}
14+
return prev - 5;
15+
});
16+
}, 100);
17+
18+
return () => clearInterval(interval);
19+
}, [onClose]);
20+
21+
const styles = {
22+
info: {
23+
background: "#dbeafe",
24+
color: "#1e3a8a",
25+
bar: "#3b82f6",
26+
},
27+
error: {
28+
background: "#fee2e2",
29+
color: "#7f1d1d",
30+
bar: "#ef4444",
31+
},
32+
success: {
33+
background: "#d1fae5",
34+
color: "#065f46",
35+
bar: "#10b981",
36+
},
37+
};
38+
39+
const { background, color, bar } = styles[type] || styles.info;
40+
41+
return (
42+
<div
43+
className="fixed top-4 right-4 w-80 shadow-lg rounded-lg p-4 z-50 transition-all duration-300"
44+
style={{ background, color }}
45+
>
46+
<div className="text-sm font-medium">{message}</div>
47+
<div className="h-1 mt-2 bg-gray-300 rounded overflow-hidden">
48+
<div
49+
className="h-full transition-all"
50+
style={{
51+
width: `${progress}%`,
52+
backgroundColor: bar,
53+
}}
54+
/>
55+
</div>
56+
</div>
57+
);
58+
};
59+
60+
export default Notification;

Frontend/solarWatch/src/components/Searchbar/Searchbar.jsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import React from "react";
2-
import { useState } from "react";
1+
import React, { useState } from 'react';
32

4-
function Searchbar({ recommendations, setQuery, query, handleSearch }) {
3+
const Searchbar = ({
4+
query,
5+
setQuery,
6+
recommendations,
7+
selectedDate,
8+
setSelectedDate,
9+
handleSearch,
10+
}) => {
511
const [filtered, setFiltered] = useState([]);
612
const [showSuggestions, setShowSuggestions] = useState(false);
713

814
return (
9-
<div className="flex justify-center w-full px-4 mt-10">
10-
<div className="relative w-full max-w-2xl backdrop-blur-md bg-white/10 border border-white/20 rounded-xl p-2 shadow-lg">
15+
<div className="flex items-center w-full px-4 mt-10 gap-4 z-50">
16+
<div className="relative flex items-center w-full max-w-2xl backdrop-blur-md bg-white/10 border border-white/20 rounded-xl p-2 shadow-lg mx-auto">
17+
{/* Search Input */}
1118
<input
1219
type="text"
13-
className="w-full px-4 py-2 rounded-lg focus:outline-none text-white"
20+
className="flex-1 px-4 py-2 rounded-lg bg-transparent focus:outline-none text-white placeholder-white/70"
1421
value={query}
1522
onChange={(e) => {
1623
const value = e.target.value;
@@ -26,8 +33,9 @@ function Searchbar({ recommendations, setQuery, query, handleSearch }) {
2633
placeholder="Search for a city..."
2734
/>
2835

36+
{/* Suggestions Dropdown */}
2937
{showSuggestions && filtered.length > 0 && (
30-
<ul className="absolute top-full left-0 w-full mt-1 bg-white text-black border border-gray-300 rounded-lg shadow-lg z-50 max-h-60 overflow-y-auto">
38+
<ul className="absolute top-full left-0 w-full mt-1 bg-white text-black border border-gray-300 rounded-lg shadow-lg z-60 max-h-60 overflow-y-auto">
3139
{filtered.map((city, index) => (
3240
<li
3341
key={index}
@@ -43,15 +51,24 @@ function Searchbar({ recommendations, setQuery, query, handleSearch }) {
4351
</ul>
4452
)}
4553

54+
{/* Date Picker */}
55+
<input
56+
type="date"
57+
value={selectedDate}
58+
onChange={(e) => setSelectedDate(e.target.value)}
59+
className="bg-white/20 text-white px-3 py-1 rounded-lg border border-white/30 focus:outline-none ml-2"
60+
/>
61+
62+
{/* Search Button */}
4663
<button
47-
onClick={handleSearch}
48-
className="absolute right-2 top-1/2 -translate-y-1/2 bg-blue-600 hover:bg-blue-700 text-white font-semibold px-4 py-1 rounded-lg transition"
64+
onClick={() => handleSearch()}
65+
className="ml-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold px-4 py-1 rounded-lg transition"
4966
>
5067
Search
5168
</button>
5269
</div>
5370
</div>
5471
);
55-
}
72+
};
5673

57-
export default Searchbar;
74+
export default Searchbar;

Frontend/solarWatch/src/pages/Content.jsx

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,71 @@ import { fetchData } from "../utils";
33
import Globe from "../components/Globe";
44
import CityList from "../components/CityList";
55
import Searchbar from "../components/Searchbar/Searchbar";
6+
import Notification from "../components/Notification";
67

78
export default function Content() {
89
const [recommendations, setRecommendations] = useState([]);
910
const [query, setQuery] = useState("");
11+
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
1012
const [searchResults, setSearchResults] = useState([]);
1113
const [selectedCity, setSelectedCity] = useState(null);
1214
const jwt = localStorage.getItem("jwt");
15+
const [notification, setNotification] = useState(null);
1316

1417
useEffect(() => {
1518
const loadRecommendations = async () => {
1619
const data = await fetchData("report/cityNames", "GET", null, jwt);
17-
console.log("🚀 ~ loadRecommendations ~ data:", data)
20+
console.log("🚀 ~ loadRecommendations ~ data:", data);
1821
if (data) setRecommendations(data);
1922
};
2023
loadRecommendations();
21-
}, []);
24+
}, [searchResults]);
2225

2326
const handleSearch = async () => {
24-
const result = await fetchData(`report?city=${query}`, "GET", null, jwt);
25-
if (result) {
26-
setSearchResults((prev) => [result, ...prev]);
27-
setSelectedCity(result);
27+
if (!query || !selectedDate) return;
28+
29+
try {
30+
const result = await fetchData(
31+
`report?city=${query}&date=${selectedDate}`,
32+
"GET",
33+
null,
34+
jwt
35+
);
36+
if (result) {
37+
setSearchResults((prev) => [result, ...prev]);
38+
setSelectedCity(result);
39+
}
40+
} catch (error) {
41+
setNotification({ type: "error", message: error.message });
2842
}
2943
};
3044

3145
return (
32-
<div className="p-6 w-full flex flex-col gap-4 z-10 h-screen box-border">
33-
<Searchbar recommendations={recommendations} setQuery={setQuery} query={query} handleSearch={handleSearch}></Searchbar>
34-
<div className="w-full overflow-x-auto">
35-
<CityList cities={searchResults} />
36-
</div>
37-
38-
<div className="flex-1 min-h-0 flex justify-center items-center">
39-
<Globe city={selectedCity} />
40-
</div>
41-
</div>
42-
46+
<div className="p-6 w-full flex flex-col gap-4 z-10 h-screen box-border">
47+
<Searchbar
48+
recommendations={recommendations}
49+
setQuery={setQuery}
50+
query={query}
51+
selectedDate={selectedDate}
52+
setSelectedDate={setSelectedDate}
53+
handleSearch={handleSearch}
54+
/>
55+
<div className="flex">
56+
<div className="w-full overflow-x-auto backdrop-blur-md bg-white/10 border border-white/20 basis-6/12">
57+
<h1 className="text-center text-3xl my-2 sticky left-0">Previous Searches</h1>
58+
<CityList cities={searchResults} />
59+
</div>
60+
<div className="flex-1 min-h-0 flex justify-center items-center">
61+
<Globe city={selectedCity} />
62+
</div>
63+
</div>
64+
{notification && (
65+
<Notification
66+
type={notification.type}
67+
message={notification.message}
68+
onClose={() => setNotification(null)}
69+
/>
70+
)}
71+
</div>
4372
);
44-
}
73+
}

Frontend/solarWatch/src/pages/Login.jsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
import React, { useState } from "react";
33
import { fetchData } from "../utils";
44
import { useNavigate } from "react-router-dom";
5+
import Notification from "../components/Notification";
56

67
const Login = () => {
78
const [form, setForm] = useState({
89
username: "",
910
password: "",
1011
});
1112

13+
const [notification, setNotification] = useState(null);
14+
1215
const navigate = useNavigate();
1316

1417
const handleChange = (e) => {
@@ -21,19 +24,37 @@ const Login = () => {
2124
try {
2225
const response = await fetchData("user/signin", "POST", form);
2326
console.log("Login response:", response);
24-
27+
2528
if (response?.jwt) {
2629
localStorage.setItem("jwt", response.jwt);
30+
setNotification({
31+
type: "success",
32+
message: "You Logged in",
33+
});
34+
35+
setTimeout(() => {
36+
navigate("/content");
37+
}, 3000);
38+
} else {
39+
setNotification({
40+
type: "error",
41+
message: "Login failed. Please check your credentials.",
42+
});
2743
}
28-
29-
navigate("/");
3044
} catch (error) {
31-
console.error("Login failed:", error);
32-
alert("Login failed. Please check your credentials.");
45+
console.error("Login error:", error);
46+
setNotification({
47+
type: "error",
48+
message:
49+
error.status === 401
50+
? "Invalid username or password."
51+
: "Something went wrong. Please try again.",
52+
});
3353
}
3454
};
3555

3656
return (
57+
<>
3758
<div className="w-100 z-10 flex items-center justify-center bg-white/50 rounded-[2rem] text-black">
3859
<div className="bg-white p-8 rounded-2xl shadow-xl w-full max-w-md">
3960
<h2 className="text-2xl font-bold mb-6 text-center text-black">Login</h2>
@@ -75,6 +96,14 @@ const Login = () => {
7596
</form>
7697
</div>
7798
</div>
99+
{notification && (
100+
<Notification
101+
type={notification.type}
102+
message={notification.message}
103+
onClose={() => setNotification(null)}
104+
/>
105+
)}
106+
</>
78107
);
79108
};
80109

0 commit comments

Comments
 (0)