diff --git a/src/App.tsx b/src/App.tsx index 77147bf..a2cbda8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import Layout from "./Layout"; import ServiceGrid from "./components/ServiceGrid"; +import { HackerNews } from "./components/HackerNews"; import { Weather } from "./components/Weather"; import { apps } from "./data/apps"; import { privateApps } from "./data/privateApps"; @@ -14,6 +15,13 @@ export function App() {

Weather

+ +
+

+ Hacker News +

+ +

Websites diff --git a/src/components/HackerNews.tsx b/src/components/HackerNews.tsx new file mode 100644 index 0000000..5eaf7b6 --- /dev/null +++ b/src/components/HackerNews.tsx @@ -0,0 +1,51 @@ +import { useState, useEffect } from "react"; + +function timeAgo(timestamp: number): string { + const seconds = Math.floor(Date.now() / 1000 - timestamp); + + if (seconds < 60) return "1m"; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; + return `${Math.floor(seconds / 86400)}d`; +} + +export function HackerNews() { + const [stories, setStories] = useState([]); + + const [error, setError] = useState(null); + + useEffect(() => { + const fetchHackerNews = () => { + fetch(`/api/hackernews`) + .then((response) => response.json()) + .then((data) => setStories(data)); + }; + + fetchHackerNews(); + }, []); + + if (stories.length == 0) { + return
Loading stories...
; + } + + return ( +
+ +
+ ); +} diff --git a/src/components/ServiceGrid.tsx b/src/components/ServiceGrid.tsx index 02486f7..f650535 100644 --- a/src/components/ServiceGrid.tsx +++ b/src/components/ServiceGrid.tsx @@ -55,7 +55,7 @@ export default function ServiceGrid({ {service.icon && ( )} -

+

{service.name}

diff --git a/src/components/Weather.tsx b/src/components/Weather.tsx index 0d5f156..b526eb6 100644 --- a/src/components/Weather.tsx +++ b/src/components/Weather.tsx @@ -1,4 +1,37 @@ import { useState, useEffect } from "react"; +import { + Sun, + Moon, + Cloud, + CloudRain, + CloudSnow, + CloudLightning, + CloudFog, + CloudSun, + CloudMoon, +} from "lucide-react"; + +function getWeatherIcon(forecast: string, className?: string) { + const f = forecast.toLowerCase(); + if (f.includes("thunder") || f.includes("lightning")) + return ; + if (f.includes("snow") || f.includes("flurr")) + return ; + if (f.includes("rain") || f.includes("shower") || f.includes("drizzle")) + return ; + if (f.includes("fog") || f.includes("mist") || f.includes("haze")) + return ; + if (f.includes("partly cloudy")) return ; + if ( + f.includes("mostly cloudy") || + f.includes("cloud") || + f.includes("overcast") + ) + return ; + if (f.includes("sunny") || f.includes("clear")) + return ; + return ; +} export function Weather() { const [weather, setWeather] = useState<{ @@ -32,11 +65,17 @@ export function Weather() { } return ( -
-
- {weather.temperature}°{weather.temperatureUnit} +
+
+ {getWeatherIcon(weather.shortForecast, "w-12 h-12 text-zinc-300")} +
+
+ {weather.temperature}° + {weather.temperatureUnit} +
+
{weather.shortForecast}
+
-
{weather.shortForecast}
); } diff --git a/src/index.ts b/src/index.ts index d2a0d29..2d09aea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,8 @@ import index from "./index.html"; import { privateApps } from "./data/privateApps"; import { websites } from "./data/websites"; +let hnCache: { stories: any[]; fetchedAt: number } | null = null; + async function checkStatuses(items: { name: string; url: string }[]) { const results = await Promise.all( items.map(async (item) => { @@ -39,6 +41,32 @@ const server = serve({ }, }, + "/api/hackernews": { + async GET(req) { + if (hnCache && Date.now() - hnCache.fetchedAt < 5 * 60 * 1000) { + return Response.json(hnCache.stories); + } + + const ids = await fetch( + "https://hacker-news.firebaseio.com/v0/topstories.json", + ).then((res) => res.json()); + + const stories = await Promise.all( + ids + .slice(0, 5) + .map((id: number) => + fetch( + `https://hacker-news.firebaseio.com/v0/item/${id}.json`, + ).then((res) => res.json()), + ), + ); + + hnCache = { stories, fetchedAt: Date.now() }; + + return Response.json(stories); + }, + }, + "/api/weather": { async GET(req) { const url = new URL(req.url);