Add Wakatime component and integrate with About section

This commit is contained in:
암냥 2025-04-02 07:30:00 +09:00
commit ae90712b97
5 changed files with 125 additions and 29 deletions

View file

@ -6,6 +6,7 @@ import Contact from "@/components/Home/Contact";
import Project from "@/components/Home/Project";
import "./index.css";
import Wakatime from "./components/Home/Wakatime";
export function App() {
useEffect(() => {
@ -66,6 +67,9 @@ export function App() {
<div id="about" className="section">
<About />
</div>
<div id="wakatime" className="section">
<Wakatime />
</div>
<div id="project" className="section">
<Project />
</div>

View file

@ -1,13 +1,17 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
export default function About() {
const [time, setTime] = useState<string>("");
const [wakatime, setWakatime] = useState<any>();
const [time, setTime] = useState<number>(0);
const [post, setPost] = useState<any>({});
const [age, setAge] = useState<number>(0);
const [totalSeconds, setTotalSeconds] = useState<number>(0);
const [isVisible, setIsVisible] = useState<boolean>(false);
const AboutRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Calculate age based on reference date (2010-11-08)
const referenceDate = new Date(2010, 11, 8); // November is 10 because months are 0-indexed
// 나이 계산
const referenceDate = new Date(2010, 10, 8); // 2010년 11월 8일 (0-indexed)
const currentDate = new Date();
let calculatedAge = currentDate.getFullYear() - referenceDate.getFullYear();
if (currentDate < new Date(currentDate.getFullYear(), referenceDate.getMonth(), referenceDate.getDate())) {
@ -17,32 +21,57 @@ export default function About() {
}, []);
useEffect(() => {
fetch("https://api.imnya.ng/rss", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
// 블로그 데이터 가져오기
fetch("https://api.imnya.ng/rss")
.then(response => response.json())
.then(data => {
if (data) {
setPost(data[0] || {});
} else {
console.error("Error: data is undefined");
}
if (data) setPost(data[0] || {});
})
.catch(error => console.error("Error fetching posts:", error));
}, []);
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date().toLocaleTimeString('en-US', { timeZone: 'Asia/Seoul', hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }));
}, 1);
return () => clearInterval(interval);
// Intersection Observer로 isVisible 상태 변경
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
}
},
{ threshold: 0.1 }
);
if (AboutRef.current) observer.observe(AboutRef.current);
return () => {
if (AboutRef.current) observer.unobserve(AboutRef.current);
};
}, []);
useEffect(() => {
// Wakatime 데이터 가져오기 (한 번만 실행)
fetch("https://api.imnya.ng/wakatime")
.then(response => response.json())
.then(data => {
if (data) {
const roundedSeconds = Math.round(data.data.total_seconds); // 반올림
setTotalSeconds(roundedSeconds);
setWakatime(data.data);
}
})
.catch(error => console.error("Error fetching Wakatime data:", error));
}, []);
useEffect(() => {
// isVisible이 true일 때 time 증가
if (isVisible && time < totalSeconds) {
const timer = setTimeout(() => setTime(prevTime => prevTime + 1), time === 0 ? 300 : 0);
return () => clearTimeout(timer);
}
}, [isVisible, time, totalSeconds]);
return (
<div className="w-full h-screen flex flex-col items-center justify-center">
<div className="w-full h-screen flex flex-col items-center justify-center" ref={AboutRef}>
<div className="w-full md:w-[50%] p-4">
<h1 className="text-2xl font-bold">🤔 About</h1>
<div className="flex items-start justify-center flex-col p-2 mt-2 w-full">
@ -51,15 +80,21 @@ export default function About() {
<h1> <strong className="font-black"></strong> </h1>
<h1> <strong className="font-black"></strong>.</h1>
</div>
<div className="mt-8">
<p>{age} </p>
<p> </p>
<p> </p>
<p> .</p>
<h1 className="mt-4 text-foreground">In South Korea : <span className="tnum text-muted-foreground">{time}</span></h1>
<h1 className="text-foreground"> : <a href={post.link} className="text-muted-foreground">{post.title}</a></h1>
<p>{age} </p>
<p> </p>
<p> </p>
<p> .</p>
<br/>
<a className="mt-4 text-foreground" href="https://wakatime.com/@imnyang" target="_blank" rel="noopener noreferrer">
Wakatime Mar 18th ~ : <span className="tnum text-muted-foreground">{time}</span>s
</a>
<h1 className="text-foreground">
: <a href={post.link} className="text-muted-foreground">{post.title}</a>
</h1>
</div>
</div>
</div>

View file

@ -0,0 +1,40 @@
import React, { useEffect, useState } from "react";
export default function Wakatime() {
const [wakatime, setWakatime] = useState<any>();
useEffect(() => {
// Wakatime 데이터 가져오기 (한 번만 실행)
fetch("https://api.imnya.ng/wakatime")
.then(response => response.json())
.then(data => {
if (data) {
setWakatime(data.data);
}
})
.catch(error => console.error("Error fetching Wakatime data:", error));
}, []);
return (
<div className="w-full h-screen flex flex-col items-center justify-center">
<div className="w-full md:w-[50%] p-4">
<a className="text-2xl font-bold" href="https://wakatime.com/@imnyang" target="_blank" rel="noopener noreferrer">🍝 Wakatime</a>
<p>Dashboards for developers</p>
<br />
{wakatime && wakatime.languages && (
<div>
<p> : {(wakatime.human_readable_total)}</p>
<p> : {wakatime.human_readable_daily_average}</p>
<br />
<p> :</p>
<ul>
{wakatime.languages.slice(0, 3).map((language: any, index: number) => (
<li key={index}>{index+1}. {language.name}: {language.percent}%</li>
))}
</ul>
</div>
)}
</div>
</div>
)
}

View file

@ -1,13 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!--
이번 패치하면서 들은 곡
Laur - Gears of Fate [AWC 2025 Finals Tiebreaker]
https://www.youtube.com/watch?v=-bnrmxa2dW0
-->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="./favicon.ico" />
<title>남현석 | :two_hearts:</title>
<meta name="description" content="항상 탐구하고 연구하는 평범한 학생 개발자입니다." />
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4588517451789913" crossorigin="anonymous"></script>
<script type="module" src="./frontend.tsx" async></script>
<script type="module" src="./frontend.tsx" async></script>'
</head>
<body>
<div id="root"></div>