37 Commits

Author SHA1 Message Date
43262846a5 feat: implement weather fetching API and update frontend components for improved user experience 2025-08-03 19:10:20 +02:00
b6b6146ad0 Merge branch 'development' into debian12fullstack 2025-08-03 17:27:10 +02:00
784822fa9a removed loading state and fixed bug with that 2025-08-03 03:27:32 +02:00
0da92ead3c feat: add lucide-react icons to components and update dependencies 2025-08-02 22:53:47 +02:00
16ea1aa4aa removed test changes 2025-08-02 15:01:18 +02:00
905dd6ad22 fix: remove period from API key update message for consistency 2025-08-02 14:59:18 +02:00
e67dce4113 docs: update README to clarify auto deployment process 2025-08-02 14:56:03 +02:00
5bc7bea4a1 removed test changes from the commit before.
- It works!
2025-08-02 14:50:34 +02:00
59e6e2d9d7 added test changes 2025-08-02 14:49:26 +02:00
65c7170acf fix: improve error handling in auto-pull script 2025-08-02 14:43:41 +02:00
2d1bbeb827 added auto pull 2025-08-02 14:38:10 +02:00
f6d29a279a Merge branch 'main' into debian12
merged bug fix by first loading in to the page
2025-08-02 02:30:17 +02:00
f4fffb73ff bug fix: update default unit in ChangePreferences component from celsius to metric 2025-08-02 02:28:36 +02:00
e22fa64e69 fix: adjust server configuration in vite.config.ts for improved host settings 2025-08-02 02:24:13 +02:00
c15d0869c6 Update frontend/src/utils/apiFunc.ts 2025-08-02 02:18:46 +02:00
bbbc8b9edd Update frontend/vite.config.ts 2025-08-02 02:16:48 +02:00
72754f5949 Update frontend/vite.config.ts 2025-08-02 02:13:04 +02:00
c1e96eb7d2 Update docker-compose.yml 2025-08-02 02:10:32 +02:00
ae410bda2b Update frontend/vite.config.ts 2025-08-02 02:09:59 +02:00
400d77cd5a Update docker-compose.yml 2025-08-02 02:07:26 +02:00
5708bfa1b3 changed README.md accordingly 2025-08-02 01:55:20 +02:00
71a29ad9de Merge branch 'main' into debian12
updated host version to 1.0.1
2025-08-02 01:55:05 +02:00
d98fab004f fix: correct numbering in installation instructions for clarity 2025-08-01 21:49:23 +02:00
e56e998467 docs: enhance installation instructions and clarify Docker usage in README 2025-08-01 21:43:02 +02:00
ee6469379f added docker functionality 2025-08-01 21:23:58 +02:00
62094299d4 fix: change button type to submit in ChangeAPI component and update temperature unit logic in WeatherData component 2025-08-01 20:25:03 +02:00
dbbca57f59 fix: update README to clarify web version availability and hosted version details 2025-08-01 19:51:50 +02:00
97b5190442 docs: update version information in README to reflect current development status 2025-08-01 19:49:59 +02:00
a2ee1b3d1f chore: remove detailed usage instructions and features from README for web-only version 2025-08-01 19:48:39 +02:00
7d91eb64eb Merge branch 'v1.0.0-local' into debian12 2025-08-01 19:45:26 +02:00
d8bbc7e62a fix: add missing newlines for improved markdown formatting in README 2025-08-01 19:43:44 +02:00
11e6e1684c Refactor code structure for improved readability and maintainability 2025-08-01 19:43:11 +02:00
a3df60178b changed ports 2025-08-01 01:09:58 +02:00
8341e50dc8 feat: enhance ChangeAPI and ChangePreferences components; add "API key update" functionality and save preferences feature 2025-08-01 01:06:58 +02:00
6f3d945213 added readme 2025-07-31 18:04:50 +02:00
f4881be087 added preferences card 2025-07-31 17:55:47 +02:00
3ecac5a854 refactor: comment out frontend service configuration in docker-compose.yml 2025-07-31 17:38:06 +02:00
21 changed files with 406 additions and 122 deletions

View File

@@ -0,0 +1,15 @@
# Weather App
This version is only meant for publishing on the web. It is not meant for local development or use.
You can find the web version of the Weather App at [https://weather.the1s.de](https://weather.the1s.de).
## Version
Currently hosted version: **1.0.1**
## Dev info
All changes that are made to this branch (`debian12`) will be automatically deployed to the [web version](https://weather.the1s.de) of the Weather App.
This is done by using an auto pull script (`auto-pull.sh`) that runs on the server where the web version is hosted.

6
auto-pull.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
cd /pfad/zu/deinem/repo
while true; do
git pull || echo "git pull failed"
sleep 10
done

63
backend/routes/api.js Normal file
View File

@@ -0,0 +1,63 @@
import { Router } from "express";
import dotenv from "dotenv";
const router = Router();
dotenv.config();
router.get("/fetchWeather", async (req, res) => {
const city = req.query.city;
const units = req.query.units;
const apiKey = process.env.API_KEY;
try {
const locationResponse = await fetch(
`https://api.openweathermap.org/geo/1.0/direct?q=${city}&appid=${apiKey}`
);
if (!locationResponse.ok) {
return res.status(404).json({
error: "Error fetching location data. (Error: x32)",
success: "false",
data: null,
});
}
const location = await locationResponse.json();
if (!location || !location[0]) {
return res.status(404).json({
error: "Location not found.",
success: "false",
data: null,
});
}
const lat = location[0].lat;
const lon = location[0].lon;
const weatherResponse = await fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=${units}`
);
if (!weatherResponse.ok) {
return res.status(500).json({
error: "Unexpected error! (Error: x33)",
success: "false",
data: null,
});
}
const weather = await weatherResponse.json();
res.status(200).json({
error: "false",
success: "Weather data fetched successfully!",
data: weather,
});
} catch (err) {
res.status(500).json({
error: "Server error",
success: "false",
data: null,
});
}
});
export default router;

View File

@@ -1,7 +1,7 @@
import express from "express";
import cors from "cors";
import env from "dotenv";
env.config();
import apiRouter from "./routes/api.js";
const app = express();
const port = 7001;
@@ -10,6 +10,8 @@ app.use(express.urlencoded({ extended: true }));
app.set("view engine", "ejs");
app.use(express.json());
app.use("/api", apiRouter);
app.get("/", (req, res) => {
res.render("index.ejs", { title: port });
});

View File

@@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Backend | <%= title %></title>
</head>
<body>
You have reached the backend views index page.
</body>
</head>
<body>
<h1>You have reached the backend views index page!</h1>
<p>Currently, there is nothing to display!</p>
</body>
</html>

View File

@@ -1,29 +1,26 @@
services:
frontend:
container_name: frontend
build: ./frontend
ports:
- "7002:7002"
networks:
- proxynet
environment:
- CHOKIDAR_USEPOLLING=true
volumes:
- ./frontend:/app
- /app/node_modules
restart: unless-stopped
# frontend:
# container_name: weather-frontend
# build: ./frontend
# ports:
# - "7002:7002"
# # networks:
# # - proxynet
# environment:
# - CHOKIDAR_USEPOLLING=true
# volumes:
# - ./frontend:/app
# - /app/node_modules
# restart: unless-stopped
backend:
container_name: backend
container_name: weather-backend
build: ./backend
ports:
- "7001:7001"
networks:
- proxynet
volumes:
- ./backend:/bikelane-backend
- ./backend:/app
- /app/node_modules
restart: unless-stopped
networks:
proxynet:
external: true
#networks:
# proxynet:
# external: true

View File

@@ -9,4 +9,4 @@ COPY . .
EXPOSE 7002
CMD ["npm", "start"]
CMD ["npm", "run", "dev"]

View File

@@ -5,7 +5,7 @@
<link
rel="icon"
type="image/svg+xml"
href="./src/assets/cloud-sun-fill.svg"
href="./src/assets/cloud-sun-fill.png"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Weather App</title>

View File

@@ -12,6 +12,7 @@
"bootstrap-icons": "^1.13.1",
"js-cookie": "^3.0.5",
"jscookie": "^1.1.0",
"lucide-react": "^0.536.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-toastify": "^11.0.5",
@@ -3221,6 +3222,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.536.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.536.0.tgz",
"integrity": "sha512-2PgvNa9v+qz4Jt/ni8vPLt4jwoFybXHuubQT8fv4iCW5TjDxkbZjNZZHa485ad73NSEn/jdsEtU57eE1g+ma8A==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",

View File

@@ -14,6 +14,7 @@
"bootstrap-icons": "^1.13.1",
"js-cookie": "^3.0.5",
"jscookie": "^1.1.0",
"lucide-react": "^0.536.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-toastify": "^11.0.5",

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,22 +1,29 @@
import { useState } from "react";
import React from "react";
import { changeAPIcookie } from "../utils/changeAPIcookie";
import { KeyRound } from "lucide-react";
interface Props {
currentAPIKey: string;
onClose: () => void;
}
const ChangeAPI: React.FC<Props> = ({ currentAPIKey }) => {
const ChangeAPI: React.FC<Props> = ({ currentAPIKey, onClose }) => {
const [apiKey, setApiKey] = useState(currentAPIKey);
const handleUpdate = () => {
changeAPIcookie(apiKey);
onClose();
};
return (
<div className="w-full">
<h2 className="text-2xl font-bold mb-2 text-blue-700">Change API Key</h2>
<p className="mb-2 text-gray-600">
<h4 className="mb-2 font-semibold text-gray-600">
Update your API key to fetch weather data.
</p>
<div className="mb-6 flex items-center gap-2 text-gray-600">
<span>We are using</span>
</h4>
<div className="mb-6 flex items-center gap-2 font-style: italic text-gray-500 flex-wrap text-">
<p>We are using</p>
<a
href="https://openweathermap.org/api"
className="text-blue-600 font-semibold underline hover:text-blue-800"
@@ -25,26 +32,29 @@ const ChangeAPI: React.FC<Props> = ({ currentAPIKey }) => {
>
OpenWeatherMap
</a>
<span>API for fetching weather data.</span>
<p>API for fetching weather data.</p>
</div>
<form className="flex flex-col gap-4">
<label htmlFor="apiKey" className="font-medium text-gray-700">
API Key:
</label>
<input
type="text"
id="apiKey"
name="apiKey"
placeholder="Enter your API key"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
required
className="border border-blue-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 bg-blue-50 text-blue-900 font-mono"
/>
<div className="flex items-center gap-2 w-full">
<KeyRound size={32} />
<input
type="text"
id="apiKey"
name="apiKey"
placeholder="Enter your API key"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
required
className="border border-blue-300 rounded-xl px-6 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 bg-blue-50 text-blue-900 font-mono w-full"
/>
</div>
<button
type="button"
type="submit"
className="bg-gradient-to-r from-blue-600 to-blue-400 text-white font-bold px-6 py-3 rounded-xl shadow-lg hover:from-blue-700 hover:to-blue-500 transition-all"
onClick={() => changeAPIcookie(apiKey)}
onClick={handleUpdate}
>
Update API Key
</button>

View File

@@ -0,0 +1,89 @@
import React, { useState } from "react";
import { myToast } from "../utils/toastify";
import { Settings2, SunMoon, Thermometer } from "lucide-react";
const getInitialTheme = () => localStorage.getItem("theme") || "light";
const getInitialUnit = () => localStorage.getItem("unit") || "metric";
interface Props {
onClose: () => void;
}
const ChangePreferences: React.FC<Props> = ({ onClose }) => {
const [unit, setUnit] = useState(getInitialUnit());
const [theme, setTheme] = useState(getInitialTheme());
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
localStorage.setItem("unit", unit);
localStorage.setItem("theme", theme);
myToast("Preferences saved successfully!", "success");
onClose();
};
// Theme-Optionen so sortieren, dass die aktuelle Auswahl oben steht
const themeOptions =
theme === "dark"
? [
{ value: "dark", label: "Dark" },
{ value: "light", label: "Light" },
]
: [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
];
return (
<div className="w-full max-w-md mx-auto bg-white rounded-2xl shadow-2xl border border-blue-200 p-8">
<h2 className="text-3xl font-extrabold mb-6 text-blue-700 flex items-center gap-2">
<Settings2 />
Preferences
</h2>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Unit */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2 items-center gap-2">
<Thermometer />
Temperature Unit
</label>
<select
className="cursor-pointer w-full border-2 border-blue-200 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400 transition"
value={unit}
onChange={(e) => setUnit(e.target.value)}
>
<option value="metric">Celsius (°C)</option>
<option value="imperial">Imperial (°F)</option>
<option value="standard">Standard (K)</option>
</select>
</div>
{/* Theme */}
<div>
<label className="cursor-pointer block text-sm font-semibold text-gray-700 mb-2 items-center gap-2">
<SunMoon />
Theme
</label>
<select
className="cursor-pointer w-full border-2 border-blue-200 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400 transition"
value={theme}
onChange={(e) => setTheme(e.target.value)}
>
{themeOptions.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
{/* Submit */}
<button
type="submit"
className="cursor-pointer w-full bg-gradient-to-r from-blue-600 to-blue-400 text-white py-3 rounded-xl font-bold text-lg shadow-lg hover:from-blue-700 hover:to-blue-500 transition-all focus:outline-none focus:ring-2 focus:ring-blue-400"
>
Save Preferences
</button>
</form>
</div>
);
};
export default ChangePreferences;

View File

@@ -1,11 +1,15 @@
import React from "react";
import ChangeAPI from "./ChangeAPI";
import ChangePreferences from "./ChangePreferences";
import { myToast } from "../utils/toastify";
import { useState } from "react";
import Cookies from "js-cookie";
import logo from "../assets/cloud-sun-fill.svg";
import logo from "../assets/cloud-sun-fill.png";
import { Github, KeyRound, Settings2 } from "lucide-react";
const Header: React.FC = () => {
const [apiCard, setApiCard] = useState(false);
const [preferencesCard, setPreferencesCard] = useState(false);
const apiKey = Cookies.get("apiKey") || "";
return (
@@ -18,26 +22,42 @@ const Header: React.FC = () => {
className="w-10 h-10 drop-shadow-lg"
/>
<h1 className="text-4xl font-extrabold tracking-wide drop-shadow-lg">
Weather App
Weather App - <strong>Web</strong>
</h1>
</div>
<button
className="bg-white text-blue-700 font-bold px-6 py-3 rounded-xl shadow-lg hover:bg-blue-100 transition-all border border-blue-200"
onClick={() => setApiCard(true)}
>
<span className="flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
strokeWidth="2"
viewBox="0 0 24 24"
>
<path d="M12 4v16m8-8H4" />
</svg>
Set API Key
</span>
</button>
<div className="flex items-center gap-4">
{" "}
{/* Added container for buttons */}
<button
className="bg-gray-200 text-gray-400 font-bold px-6 py-3 rounded-xl shadow-lg border border-gray-300 flex items-center gap-2 cursor-not-allowed opacity-60"
onClick={() => myToast("You don't need to set an API Key!", "info")}
>
<span className="flex items-center gap-2">
<KeyRound />
Set API Key
</span>
</button>
<button
className="cursor-pointer bg-white text-blue-700 font-bold px-6 py-3 rounded-xl shadow-lg hover:bg-blue-100 transition-all border border-blue-200 flex items-center gap-2"
onClick={() => setPreferencesCard(true)}
>
<span className="flex items-center gap-2">
<Settings2 />
Preferences
</span>
</button>
<a
href="https://github.com/theis-js/weather-app/wiki"
target="_blank"
rel="noopener noreferrer"
>
<button className="cursor-help bg-white text-blue-700 font-bold px-6 py-3 rounded-xl shadow-lg hover:bg-blue-100 transition-all border border-blue-200 flex items-center gap-2">
<span className="flex items-center gap-2">
<Github /> Docs
</span>
</button>
</a>
</div>
</header>
{apiCard && (
<div className="fixed inset-0 bg-gray-900 bg-opacity-60 flex items-center justify-center z-50 transition-all">
@@ -49,7 +69,25 @@ const Header: React.FC = () => {
>
&times;
</button>
<ChangeAPI currentAPIKey={apiKey} />
<ChangeAPI
currentAPIKey={apiKey}
onClose={() => setApiCard(false)}
/>
</div>
</div>
)}
{preferencesCard && (
<div className="fixed inset-0 bg-gray-900 bg-opacity-60 flex items-center justify-center z-50 transition-all">
<div className="bg-white rounded-2xl shadow-2xl p-10 w-full max-w-md relative border-2 border-blue-200">
<button
className="absolute top-4 right-4 text-gray-400 hover:text-blue-600 text-2xl font-bold"
onClick={() => setPreferencesCard(false)}
aria-label="Close"
>
&times;
</button>
<ChangePreferences onClose={() => setPreferencesCard(false)} />
</div>
</div>
)}

View File

@@ -1,6 +1,5 @@
import React from "react";
import sunriseIcon from "../assets/icons/sunrise-fill.svg";
import sunsetIcon from "../assets/icons/sunset-fill.svg";
import { Sunrise, Sunset } from "lucide-react";
const WeatherData: React.FC = () => {
const weatherRaw = localStorage.getItem("weather");
@@ -19,7 +18,27 @@ const WeatherData: React.FC = () => {
{weatherData?.name}
</h2>
<p className="text-5xl font-extrabold mb-2 text-blue-900">
{weatherData?.main?.temp} °C
{weatherData?.main?.temp}{" "}
{localStorage.getItem("unit") === "metric"
? "°C"
: localStorage.getItem("unit") === "imperial"
? "°F"
: localStorage.getItem("unit") === "standard"
? "K"
: "°C"}
</p>
<p className="flex items-center justify-center gap-2">
{weatherData?.sys?.country && (
<>
<img
src={`https://flagcdn.com/${weatherData.sys.country.toLowerCase()}.svg`}
alt={weatherData.sys.country}
width={24}
height={18}
/>
<span>{weatherData.sys.country}</span>
</>
)}
</p>
<p className="text-lg mb-4 capitalize text-blue-600">
{weatherData?.weather?.[0]?.description}
@@ -33,7 +52,7 @@ const WeatherData: React.FC = () => {
)}
<div className="flex flex-col items-center gap-4 mb-2">
<div className="flex items-center gap-3">
<img src={sunriseIcon} alt="Sunrise Icon" className="w-7 h-7" />
<Sunrise />
<span className="text-base text-blue-700 font-semibold">
Sunrise:{" "}
{weatherData?.sys?.sunrise
@@ -45,7 +64,7 @@ const WeatherData: React.FC = () => {
</span>
</div>
<div className="flex items-center gap-3">
<img src={sunsetIcon} alt="Sunset Icon" className="w-7 h-7" />
<Sunset />
<span className="text-base text-blue-700 font-semibold">
Sunset:{" "}
{weatherData?.sys?.sunset

View File

@@ -1,17 +1,15 @@
import React from "react";
import { useState } from "react";
import { fetchWeather } from "../utils/apiFunc";
import Cookies from "js-cookie";
import { toast } from "react-toastify";
import WeatherData from "./WeatherData";
import { useEffect } from "react";
import { X } from "lucide-react";
const WeatherCard: React.FC = () => {
const [city, setCity] = useState("");
const [loading, setLoading] = useState(false);
const [weatherData, setWeatherData] = useState(false);
console.log(loading); // only for better reading because a syntax error would appear
const getAPIKey = () => Cookies.get("apiKey") || "";
const getUnit = () => localStorage.getItem("unit") || "metric";
const handleCityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setCity(event.target.value);
@@ -19,12 +17,13 @@ const WeatherCard: React.FC = () => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoading(true);
setWeatherData(false);
toast
.promise(fetchWeather(city, getAPIKey(), "metric"), {
.promise(fetchWeather(city, getUnit()), {
pending: "Fetching weather data...",
success: "Weather data loaded successfully!",
error: "Error loading weather data! (Check console for details)",
error:
"Failed to load weather data. Please check your entered city name. (Error: x4040)",
})
.then(() => {
if (localStorage.getItem("weather")) {
@@ -32,7 +31,6 @@ const WeatherCard: React.FC = () => {
} else {
setWeatherData(false);
}
setLoading(false);
});
};
@@ -53,6 +51,9 @@ const WeatherCard: React.FC = () => {
<p className="mb-2 text-gray-600">
Current weather will be displayed here.
</p>
<p className="mb-2 text-gray-600">
<strong>You don't need an API Key!</strong>
</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-4 mt-4">
<label htmlFor="city" className="font-medium text-gray-700">
Enter City:
@@ -67,12 +68,27 @@ const WeatherCard: React.FC = () => {
required
className="border border-blue-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 bg-blue-50 text-blue-900 font-mono"
/>
<button
type="submit"
className="bg-gradient-to-r from-blue-600 to-blue-400 text-white font-bold px-6 py-3 rounded-xl shadow-lg hover:from-blue-700 hover:to-blue-500 transition-all"
>
Get Weather
</button>
<div className="cursor-pointer flex items-center gap-2">
<button
type="submit"
className="cursor-pointer flex-1 bg-gradient-to-r from-blue-600 to-blue-400 text-white font-bold px-4 py-3 rounded-xl shadow-lg hover:from-blue-700 hover:to-blue-500 transition-all"
>
Get Weather
</button>
{weatherData && (
<button
type="button"
onClick={() => {
setWeatherData(false);
localStorage.removeItem("weather");
}}
className="cursor-pointer flex-shrink-0 bg-red-500 hover:bg-red-600 text-white rounded-xl p-3 shadow-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-red-400"
aria-label="Close weather data"
>
<X />
</button>
)}
</div>
</form>
{weatherData && <WeatherData />}
</div>

View File

@@ -1,30 +1,30 @@
export type units = "metric" | "imperial";
import { myToast } from "./toastify";
export const fetchWeather = async (
city: string,
apiKey: string,
units: units
) => {
// Get location data
const location = await fetch(
`http://api.openweathermap.org/geo/1.0/direct?q=${city}&appid=${apiKey}`
).then((response) => {
if (response.status === 401) {
console.error(
"You are not authorized to access this resource. Please check your API key."
);
} else if (response.ok) {
return response.json();
} else {
console.error("Error fetching location data: ", response.statusText);
export const fetchWeather = async (city: string, units: string) => {
try {
const response = await fetch(
`http://localhost:7001/api/fetchWeather?city=${encodeURIComponent(
city
)}&units=${encodeURIComponent(units)}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const responseData = await response.json();
if (!response.ok) {
myToast(responseData.error, "error");
return;
}
});
const lat = location[0].lat;
const lon = location[0].lon;
// Get weather data
const weather = await fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=${units}`
).then((response) => response.json());
localStorage.setItem("weather", JSON.stringify(weather));
localStorage.setItem("weather", JSON.stringify(responseData.data));
return;
} catch (error) {
const errorMsg = JSON.stringify(error);
myToast(errorMsg, "error");
return null;
}
};

View File

@@ -2,6 +2,18 @@ import Cookies from "js-cookie";
import { myToast } from "./toastify";
export const changeAPIcookie = (newApiKey: string) => {
let apiKey15 = newApiKey.slice(0, 15);
Cookies.set("apiKey", newApiKey);
myToast("API key updated successfully!", "success");
if (Cookies.get("apiKey") === newApiKey) {
myToast(
"API key updated successfully!" +
" " +
"Your new API key: " +
apiKey15 +
"...",
"success"
);
} else {
myToast("Failed to update API key. (Error: x30)", "error");
}
};

View File

@@ -1,12 +1,17 @@
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
server: {
server: {
host: "0.0.0.0",
allowedHosts: ["weather.the1s.de"],
port: 7002,
watch: {
usePolling: true,
watch: { usePolling: true },
hmr: {
host: "weather.the1s.de",
port: 7002,
protocol: "wss",
},
},
});