| | import React, { useState, useEffect } from "react"; |
| | import { apiFetch } from "../utils/apiFetch"; |
| |
|
| | interface UserInfo { |
| | connected: boolean; |
| | username: string | null; |
| | } |
| |
|
| | interface CountResponse { |
| | name: string; |
| | count: number; |
| | } |
| |
|
| | const Counter: React.FC = () => { |
| | const [userInfo, setUserInfo] = useState<UserInfo | null>(null); |
| | const [count, setCount] = useState<number>(0); |
| | const [loading, setLoading] = useState(true); |
| | const [error, setError] = useState<string | null>(null); |
| | const [incrementing, setIncrementing] = useState(false); |
| |
|
| | useEffect(() => { |
| | const fetchUserInfo = async () => { |
| | try { |
| | setLoading(true); |
| | const response = await apiFetch("/api/user"); |
| | if (!response.ok) { |
| | throw new Error(`HTTP error! status: ${response.status}`); |
| | } |
| | const data: UserInfo = await response.json(); |
| | setUserInfo(data); |
| | setError(null); |
| |
|
| | |
| | if (data.connected) { |
| | await fetchUserCount(); |
| | } |
| | } catch (err) { |
| | setError( |
| | err instanceof Error ? err.message : "Failed to fetch user info", |
| | ); |
| | setUserInfo(null); |
| | } finally { |
| | setLoading(false); |
| | } |
| | }; |
| |
|
| | fetchUserInfo(); |
| | }, []); |
| |
|
| | const fetchUserCount = async () => { |
| | try { |
| | const response = await apiFetch("/api/user/count"); |
| | if (!response.ok) { |
| | throw new Error(`HTTP error! status: ${response.status}`); |
| | } |
| | const data: CountResponse = await response.json(); |
| | setCount(data.count); |
| | } catch (err) { |
| | console.error("Failed to fetch user count:", err); |
| | setCount(0); |
| | } |
| | }; |
| |
|
| | const handleIncrement = async () => { |
| | if (!userInfo?.connected) { |
| | setError("You must be logged in to increment the counter"); |
| | return; |
| | } |
| |
|
| | try { |
| | setIncrementing(true); |
| | setError(null); |
| |
|
| | const response = await apiFetch("/api/user/count/increment", { |
| | method: "POST", |
| | }); |
| |
|
| | if (!response.ok) { |
| | throw new Error(`HTTP error! status: ${response.status}`); |
| | } |
| |
|
| | const data: CountResponse = await response.json(); |
| | setCount(data.count); |
| | } catch (err) { |
| | setError( |
| | err instanceof Error ? err.message : "Failed to increment counter", |
| | ); |
| | } finally { |
| | setIncrementing(false); |
| | } |
| | }; |
| |
|
| | |
| | if (loading) { |
| | return ( |
| | <div className="flex items-center justify-center"> |
| | <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-500"></div> |
| | <span className="ml-2 text-gray-400 text-base">Loading...</span> |
| | </div> |
| | ); |
| | } |
| |
|
| | |
| | if (!userInfo?.connected) { |
| | return ( |
| | <div className="text-center"> |
| | <p className="text-gray-400 text-base mb-4"> |
| | Please log in to use the counter |
| | </p> |
| | </div> |
| | ); |
| | } |
| |
|
| | return ( |
| | <div className="space-y-4"> |
| | {error && ( |
| | <div className="p-3 bg-red-900/20 border border-red-700 rounded-lg"> |
| | <p className="text-red-400 text-base">{error}</p> |
| | </div> |
| | )} |
| | |
| | <button |
| | onClick={handleIncrement} |
| | disabled={incrementing} |
| | className="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 disabled:from-gray-600 disabled:to-gray-700 text-white font-semibold py-4 px-8 rounded-xl border border-transparent hover:border-blue-400 transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-blue-500/50 shadow-lg hover:shadow-xl transform hover:-translate-y-1 disabled:transform-none disabled:hover:shadow-lg relative" |
| | > |
| | <div |
| | className={`flex items-center justify-center space-x-2 transition-opacity duration-200 ${incrementing ? "opacity-0" : "opacity-100"}`} |
| | > |
| | Count is {count} |
| | </div> |
| | |
| | <div |
| | className={`absolute inset-0 flex items-center justify-center space-x-2 transition-opacity duration-200 ${incrementing ? "opacity-100" : "opacity-0"}`} |
| | > |
| | <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> |
| | <span>Incrementing...</span> |
| | </div> |
| | </button> |
| | |
| | <p className="text-gray-400 text-sm"> |
| | Logged in as: {userInfo.username || "User"} |
| | </p> |
| | </div> |
| | ); |
| | }; |
| |
|
| | export default Counter; |
| |
|