Skip to content

Commit e4fcfae

Browse files
Merge branch 'main' into feat/loading-skeleton
2 parents 448a522 + f6c75f7 commit e4fcfae

File tree

10 files changed

+913
-198
lines changed

10 files changed

+913
-198
lines changed

README.md

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,96 @@
1-
# React Verse – Free API Dashboard (Hacktoberfest)
2-
3-
An all-in-one React (Vite) dashboard showcasing multiple free, no-auth public APIs. Built as a friendly Hacktoberfest playground for first-time and seasoned contributors alike.
4-
5-
## Dashboards Implemented
6-
- Weather (wttr.in)
7-
- Cryptocurrency (CoinGecko)
8-
- Space & Astronomy (Open Notify – ISS + Astronauts)
9-
- Movies (Studio Ghibli API)
10-
- Recipes (TheMealDB)
11-
- Trivia Quiz (Open Trivia DB)
12-
- Jokes & Quotes (Official Joke API + Quotable)
13-
- Dog / Cat Images (Dog CEO + Cataas)
14-
- COVID-19 Tracker (covid19api.com)
15-
16-
## Quick Start
1+
# 🌐 ReactVerse – The Open API Playground
2+
3+
**ReactVerse** is an open-source API dashboard built with **React + Vite**, designed to help developers explore, visualize, and learn how to integrate public APIs safely and responsibly.
4+
It’s more than just a project — it’s a **community-driven learning space** for understanding how APIs work, managing data securely, and contributing meaningfully to open source.
5+
6+
Perfect for **beginners**, and **API enthusiasts** who want to get hands-on experience using real-world APIs.
7+
8+
---
9+
10+
## 🚀 What’s Inside
11+
12+
A collection of interactive dashboards that fetch and display data from **free, no-auth public APIs** — all in one unified, open-source interface.
13+
14+
| Dashboard | API Used | Description |
15+
| --------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
16+
| ☀️ Weather | [wttr.in](https://wttr.in/:help) | Get real-time weather updates and forecasts |
17+
| 💰 Cryptocurrency | [CoinGecko](https://www.coingecko.com/en/api) | Track live crypto prices and market data |
18+
| 🛰️ Space & Astronomy | [Open Notify](http://open-notify.org/Open-Notify-API/) | View ISS location and current astronauts in orbit |
19+
| 🎬 Movies | [Studio Ghibli API](https://ghibliapi.vercel.app/) | Explore Studio Ghibli’s magical movie catalog |
20+
| 🍳 Recipes | [TheMealDB](https://www.themealdb.com/api.php) | Find meal ideas and cooking inspiration |
21+
| 🎯 Trivia Quiz | [Open Trivia DB](https://opentdb.com/api_config.php) | Challenge yourself with fun trivia questions |
22+
| 😂 Jokes & Quotes | [Joke API](https://official-joke-api.appspot.com/) + [Quotable](https://github.com/lukePeavey/quotable) | Daily dose of humor and motivation |
23+
| 🐶🐱 Pets | [Dog CEO](https://dog.ceo/dog-api/) + [Cataas](https://cataas.com/#/) | Random cute dog and cat images |
24+
| 🦠 COVID-19 Tracker | [COVID19 API](https://covid19api.com/) | Track pandemic stats and trends globally |
25+
26+
---
27+
28+
## 🧠 Why ReactVerse?
29+
30+
* 🧩 **Learn by doing** — See how public APIs work and how to safely handle data fetching.
31+
* 🔐 **Promote API safety** — No API keys, no secrets, just best practices for secure integration.
32+
* 🌍 **Open to everyone** — Ideal for contributors learning React, APIs, or GitHub workflows.
33+
* 💬 **Collaborative & inclusive** — A space where you can build, break, and improve together.
34+
35+
---
36+
37+
## ⚙️ Quick Start
38+
1739
```bash
1840
npm install
1941
npm run dev
2042
```
21-
Open http://localhost:5173
22-
23-
## Tech Stack
24-
- React 18 + Vite
25-
- React Router v6
26-
- Fetch API (no extra client)
27-
- Simple CSS (custom light/dark theme)
28-
29-
## Contributing (Hacktoberfest)
30-
See `CONTRIBUTING.md` for detailed instructions, ideas, and open enhancement areas. Beginner-friendly issues will be labeled.
31-
32-
## Roadmap / TODO Highlights
33-
- Enhance UI polish, accessibility, and animations
34-
- Add search, filters, and favorites persistence per dashboard
35-
- Add charts (price trends, COVID daily trends, weather graphs)
36-
- Replace placeholder chart component with a real library
37-
- Add service worker + offline caching for selected data
38-
- Add testing (Vitest + React Testing Library)
39-
- Add type safety (TypeScript migration) – optional future step
40-
41-
## API References
42-
| Area | API | Docs |
43-
| ---- | --- | ---- |
44-
| Weather | wttr.in | https://wttr.in/:help |
45-
| Crypto | CoinGecko | https://www.coingecko.com/en/api |
46-
| Space | Open Notify | http://open-notify.org/Open-Notify-API/ |
47-
| Movies | Studio Ghibli | https://ghibliapi.vercel.app/ |
48-
| Recipes | TheMealDB | https://www.themealdb.com/api.php |
49-
| Trivia | Open Trivia DB | https://opentdb.com/api_config.php |
50-
| Jokes | Official Joke API | https://official-joke-api.appspot.com/ |
51-
| Quotes | Quotable | https://github.com/lukePeavey/quotable |
52-
| Pets | Dog CEO / Cataas | https://dog.ceo/dog-api/ / https://cataas.com/#/ |
53-
| COVID-19 | COVID19 API | https://covid19api.com/ |
54-
55-
## License
56-
MIT – see `LICENSE`.
57-
58-
## Acknowledgements
59-
Community-driven project for educational purposes. External API data © respective providers.
60-
61-
## Credits
62-
63-
Hrishikesh Dalal (https://www.hrishikeshdalal.tech/)
64-
65-
Venisha Kalola (https://www.venishakalola.tech/)
6643

44+
Then open [http://localhost:5173](http://localhost:5173) in your browser.
45+
46+
---
47+
48+
## 🧰 Tech Stack
49+
50+
* ⚛️ React 18 + Vite
51+
* 🧭 React Router v6
52+
* 🌐 Fetch API (no external client)
53+
* 🎨 Custom Light/Dark CSS Theme
54+
55+
---
56+
57+
## 🤝 Contributing
58+
59+
We welcome contributions of all levels — from design tweaks to feature enhancements!
60+
61+
* Check out `CONTRIBUTING.md` for setup instructions and issue labels.
62+
* Look for **`good first issue`** or **`help wanted`** tags to get started.
63+
* Participate in **Hacktoberfest** and grow your open-source journey with us.
64+
65+
---
66+
67+
## 🗺️ Roadmap
68+
69+
* ✨ Improve UI/UX and accessibility
70+
* 🔍 Add search, filters, and persistent favorites
71+
* 📊 Integrate charts (e.g. weather graphs, price trends)
72+
* 🧱 Introduce a real charting library (e.g. Recharts or Chart.js)
73+
* ⚙️ Add offline caching with service workers
74+
* 🧪 Include testing (Vitest + React Testing Library)
75+
* 🧾 Optional: Migrate to TypeScript for type safety
76+
77+
---
78+
79+
80+
## 🪪 License
81+
82+
MIT – see `LICENSE` for full details.
83+
84+
---
85+
86+
## 💡 Acknowledgements
87+
88+
This project is community-built for **educational and open-source learning** purposes.
89+
90+
---
91+
92+
## 👥 Credits
93+
94+
* [Hrishikesh Dalal](https://www.hrishikeshdalal.tech/)
95+
* [Venisha Kalola](https://www.venishakalola.tech/)
6796

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"dependencies": {
1212
"react": "^18.3.1",
1313
"react-dom": "^18.3.1",
14-
"react-router-dom": "^6.27.0"
14+
"react-router-dom": "^6.27.0",
15+
"leaflet": "^1.9.4",
16+
"react-leaflet": "^4.2.1"
1517
},
1618
"devDependencies": {
1719
"@vitejs/plugin-react": "^4.3.1",
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useEffect, useState } from 'react';
2+
3+
export default function DashboardControls({ onRefresh }) {
4+
const [intervalTime, setIntervalTime] = useState(null);
5+
6+
useEffect(() => {
7+
if (intervalTime) {
8+
const id = setInterval(() => {
9+
onRefresh();
10+
}, intervalTime);
11+
return () => clearInterval(id);
12+
}
13+
}, [intervalTime, onRefresh]);
14+
15+
return (
16+
<div>
17+
<button
18+
onClick={onRefresh}
19+
>
20+
Refresh
21+
</button>
22+
23+
<select
24+
onChange={(e) =>
25+
setIntervalTime(e.target.value === 'off' ? null : Number(e.target.value))
26+
}
27+
>
28+
<option value="off">Auto Refresh: Off</option>
29+
<option value={5000}>Every 5s</option>
30+
<option value={30000}>Every 30s</option>
31+
<option value={60000}>Every 1 min</option>
32+
</select>
33+
</div>
34+
);
35+
}

src/components/IssMap.jsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
2+
import L from 'leaflet';
3+
import { useEffect, useRef } from 'react';
4+
import 'leaflet/dist/leaflet.css';
5+
6+
// Default Leaflet icon fix (since CRA/Vite bundling sometimes needs explicit paths)
7+
const icon = new L.Icon({
8+
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
9+
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
10+
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
11+
iconSize: [25, 41],
12+
iconAnchor: [12, 41],
13+
popupAnchor: [1, -34],
14+
shadowSize: [41, 41]
15+
});
16+
17+
/**
18+
* IssMap component
19+
* Props:
20+
* - latitude (string or number)
21+
* - longitude (string or number)
22+
*/
23+
export default function IssMap({ latitude, longitude }) {
24+
const lat = parseFloat(latitude);
25+
const lon = parseFloat(longitude);
26+
const mapRef = useRef(null);
27+
28+
useEffect(() => {
29+
if (mapRef.current) {
30+
mapRef.current.setView([lat, lon]);
31+
}
32+
}, [lat, lon]);
33+
34+
if (Number.isNaN(lat) || Number.isNaN(lon)) return null;
35+
36+
return (
37+
<div style={{ height: '320px', width: '100%', borderRadius: 8, overflow: 'hidden' }}>
38+
<MapContainer
39+
center={[lat, lon]}
40+
zoom={3}
41+
ref={mapRef}
42+
style={{ height: '100%', width: '100%' }}
43+
scrollWheelZoom={false}
44+
>
45+
<TileLayer
46+
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
47+
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
48+
/>
49+
<Marker position={[lat, lon]} icon={icon}>
50+
<Popup>
51+
ISS Position<br />Lat: {lat.toFixed(2)} Lon: {lon.toFixed(2)}
52+
</Popup>
53+
</Marker>
54+
</MapContainer>
55+
</div>
56+
);
57+
}

src/pages/Covid.jsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* - [ ] Offline cache last fetch
1818
* - [ ] Extract service + hook (useCovidSummary, useCountryTrends)
1919
*/
20-
import { useEffect, useState } from 'react';
20+
import { useEffect, useState, useCallback } from 'react';
2121
import Loading from '../components/Loading.jsx';
2222
import ErrorMessage from '../components/ErrorMessage.jsx';
2323
import Card from '../components/Card.jsx';
@@ -28,26 +28,39 @@ export default function Covid() {
2828
const [error, setError] = useState(null);
2929
const [country, setCountry] = useState('');
3030

31-
useEffect(() => { fetchSummary(); }, []);
32-
33-
async function fetchSummary() {
31+
const fetchSummary = useCallback(async () => {
32+
if (loading) return;
3433
try {
35-
setLoading(true); setError(null);
34+
setLoading(true);
35+
setError(null);
3636
const res = await fetch('https://api.covid19api.com/summary');
3737
if (!res.ok) throw new Error('Failed to fetch');
3838
const json = await res.json();
3939
setSummary(json);
40-
} catch (e) { setError(e); } finally { setLoading(false); }
41-
}
40+
} catch (e) {
41+
setError(e);
42+
} finally {
43+
setLoading(false);
44+
}
45+
}, [loading]);
46+
47+
useEffect(() => {
48+
fetchSummary();
49+
}, []);
4250

4351
const global = summary?.Global;
4452
const countries = summary?.Countries || [];
4553
const selected = countries.find(c => c.Slug === country);
4654

4755
return (
4856
<div>
57+
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>
4958
<h2>COVID-19 Tracker</h2>
50-
{loading && <Loading />}
59+
<button onClick={fetchSummary} disabled={loading}>
60+
{loading ? 'Refreshing...' : 'Refresh'}
61+
</button>
62+
</div>
63+
{loading && !summary && <Loading />}
5164
<ErrorMessage error={error} />
5265
{global && (
5366
<Card title="Global Stats">

src/pages/Space.jsx

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* SPACE & ASTRONOMY DASHBOARD TODOs
33
* ---------------------------------
44
* Easy:
5-
* - [ ] Refresh button / auto-refresh interval selector
5+
* - [x] Refresh button / auto-refresh interval selector
66
* - [ ] Show last updated timestamp
77
* - [ ] Style astronauts list with craft grouping
88
* - [ ] Add loading skeleton or placeholder map area
@@ -21,18 +21,21 @@ import { useEffect, useState } from 'react';
2121
import Loading from '../components/Loading.jsx';
2222
import ErrorMessage from '../components/ErrorMessage.jsx';
2323
import Card from '../components/Card.jsx';
24+
import IssMap from '../components/IssMap.jsx';
25+
import DashboardControls from "../components/DashboardControls.jsx";
2426

2527
export default function Space() {
2628
const [iss, setIss] = useState(null);
2729
const [crew, setCrew] = useState([]);
2830
const [error, setError] = useState(null);
2931
const [loading, setLoading] = useState(false);
30-
31-
useEffect(() => { fetchData(); }, []);
32-
32+
const [lastUpdated, setLastUpdated] = useState(null);
33+
34+
// Fetch both ISS position + crew
3335
async function fetchData() {
3436
try {
35-
setLoading(true); setError(null);
37+
setLoading(true);
38+
setError(null);
3639
const [issRes, crewRes] = await Promise.all([
3740
fetch('http://api.open-notify.org/iss-now.json'),
3841
fetch('http://api.open-notify.org/astros.json')
@@ -42,24 +45,42 @@ export default function Space() {
4245
const crewJson = await crewRes.json();
4346
setIss(issJson);
4447
setCrew(crewJson.people || []);
45-
} catch (e) { setError(e); } finally { setLoading(false); }
48+
setLastUpdated(new Date());
49+
} catch (e) {
50+
setError(e);
51+
} finally {
52+
setLoading(false);
53+
}
4654
}
4755

56+
useEffect(() => {
57+
fetchData();
58+
}, []);
59+
60+
//leaflet map component
4861
return (
4962
<div>
5063
<h2>Space & Astronomy</h2>
64+
<DashboardControls onRefresh={fetchData} />
5165
{loading && <Loading />}
5266
<ErrorMessage error={error} />
5367
{iss && (
5468
<Card title="ISS Current Location">
5569
<p>Latitude: {iss.iss_position.latitude}</p>
5670
<p>Longitude: {iss.iss_position.longitude}</p>
57-
{/* TODO: Render map (Leaflet) with marker for ISS position */}
71+
{lastUpdated && (
72+
<p style={{ fontSize: '0.8rem', color: '#666' }}>
73+
Last updated: {lastUpdated.toLocaleTimeString()}
74+
</p>
75+
)}
76+
<IssMap latitude={iss.iss_position.latitude} longitude={iss.iss_position.longitude} />
5877
</Card>
5978
)}
6079
<Card title={`Astronauts in Space (${crew.length})`}>
6180
<ul>
62-
{crew.map(p => <li key={p.name}>{p.name}{p.craft}</li>)}
81+
{crew.map(p => (
82+
<li key={p.name}>{p.name}{p.craft}</li>
83+
))}
6384
</ul>
6485
</Card>
6586
{/* TODO: Add next ISS pass prediction form */}

0 commit comments

Comments
 (0)