162 lines
4.9 KiB
TypeScript
162 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import Image from "next/image";
|
|
|
|
// 1. Define the TypeScript interface for a GitHub User object
|
|
interface GitHubUser {
|
|
id: number;
|
|
login: string;
|
|
avatar_url: string;
|
|
html_url: string;
|
|
}
|
|
|
|
const URL = "https://api.github.com/users";
|
|
|
|
const FetchGitUsers = () => {
|
|
// 2. Add types to state hooks
|
|
const [users, setUsers] = useState<GitHubUser[]>([]);
|
|
const [loading, setLoading] = useState<boolean>(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
// 3. Native Web API AbortController for fetch cleanup
|
|
const controller = new AbortController();
|
|
|
|
const fetchData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const response = await fetch(URL, { signal: controller.signal });
|
|
console.log(response);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP Error! Status: ${response.status}`);
|
|
}
|
|
|
|
const data: GitHubUser[] = await response.json();
|
|
setUsers(data);
|
|
} catch (err: unknown) {
|
|
// Ignore abort errors caused by component unmounting
|
|
if (err instanceof Error && err.name === "AbortError") return;
|
|
|
|
if (err instanceof Error) {
|
|
setError(err.message);
|
|
} else {
|
|
setError("An unexpected error occurred.");
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchData();
|
|
|
|
// 4. Clean up ongoing fetch if component unmounts
|
|
return () => {
|
|
controller.abort();
|
|
};
|
|
}, []);
|
|
|
|
// DaisyUI Loading Spinner State
|
|
if (loading) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center min-h-[50vh] gap-3">
|
|
<span className="loading loading-spinner loading-lg text-primary"></span>
|
|
<p className="text-base-content/70 text-sm font-medium">
|
|
Loading GitHub Users...
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// DaisyUI Alert Error State
|
|
if (error) {
|
|
return (
|
|
<div className="max-w-md mx-auto my-8">
|
|
<div className="alert alert-error shadow-lg">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="stroke-current shrink-0 h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
<span>{error}</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="container mx-auto p-6 max-w-5xl">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between border-b border-base-300 pb-4 mb-8">
|
|
<div>
|
|
<h2 className="text-3xl font-black text-base-content tracking-tight">
|
|
GitHub Users
|
|
</h2>
|
|
<p className="text-sm text-base-content/60 mt-1">
|
|
Fetched directly from GitHub API
|
|
</p>
|
|
</div>
|
|
<div className="badge badge-primary badge-outline font-semibold">
|
|
{users.length} Users
|
|
</div>
|
|
</div>
|
|
|
|
{/* Users Grid */}
|
|
<ul className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
|
{users.map((user) => {
|
|
const { id, login, avatar_url, html_url } = user;
|
|
return (
|
|
<li key={id}>
|
|
{/* DaisyUI Card Component */}
|
|
<div className="card bg-base-100 shadow-md border border-base-200 hover:shadow-xl transition-all duration-200">
|
|
<figure className="px-6 pt-6">
|
|
{/* DaisyUI Avatar Container */}
|
|
<div className="avatar">
|
|
<div className="w-24 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2">
|
|
{/* <img src={avatar_url} alt={`${login}'s avatar`} /> */}
|
|
<Image
|
|
src={avatar_url}
|
|
alt={`${login}'s avatar`}
|
|
width={600}
|
|
height={600}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</figure>
|
|
<div className="card-body items-center text-center p-5">
|
|
<h3 className="card-title text-lg font-bold capitalize text-base-content">
|
|
{login}
|
|
</h3>
|
|
<div className="card-actions mt-3 w-full">
|
|
<a
|
|
href={html_url}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="btn btn-primary btn-sm btn-block font-medium"
|
|
>
|
|
View Profile
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default FetchGitUsers;
|