Compare commits
2 Commits
fc2d41ee97
...
8687d2c444
| Author | SHA1 | Date | |
|---|---|---|---|
| 8687d2c444 | |||
| 16a3025929 |
@@ -9,31 +9,42 @@ const ActivityHeatmap = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
// Initialize default empty data
|
||||||
// 1. Fetch GitHub Data (using proxy)
|
let githubData = { contributions: [] };
|
||||||
const githubRes = await fetch('https://github-contributions-api.jogruber.de/v4/MatissJurevics');
|
let giteaData = [];
|
||||||
const githubJson = await githubRes.json();
|
|
||||||
|
|
||||||
// 2. Fetch Gitea Data
|
try {
|
||||||
|
// 1. Fetch GitHub Data (Safe Fetch)
|
||||||
|
try {
|
||||||
|
const githubRes = await fetch('https://github-contributions-api.jogruber.de/v4/MatissJurevics');
|
||||||
|
if (githubRes.ok) {
|
||||||
|
githubData = await githubRes.json();
|
||||||
|
} else {
|
||||||
|
console.warn("GitHub API fetch failed:", githubRes.status);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("GitHub API error:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fetch Gitea Data (Safe Fetch)
|
||||||
|
try {
|
||||||
const giteaRes = await fetch('/api/gitea/api/v1/users/Matiss/heatmap');
|
const giteaRes = await fetch('/api/gitea/api/v1/users/Matiss/heatmap');
|
||||||
const giteaJson = await giteaRes.json(); // Array of { timestamp, contributions }
|
if (giteaRes.ok) {
|
||||||
|
giteaData = await giteaRes.json();
|
||||||
|
} else {
|
||||||
|
console.warn("Gitea API fetch failed:", giteaRes.status);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Gitea API error:", e);
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Process & Merge
|
// 3. Process & Merge
|
||||||
const processData = () => {
|
const processData = () => {
|
||||||
const merged = new Map();
|
const merged = new Map();
|
||||||
|
|
||||||
// Initialize with GitHub data (usually last year)
|
|
||||||
// The proxy returns 'contributions' array for the last year usually?
|
|
||||||
// Actually correct structure from jogruber api is { total: {}, contributions: [ { date, count, level } ] }
|
|
||||||
// But let's check what it returns specifically or handle 'years' object.
|
|
||||||
// Usually structure val: { yearly: [] , total: {} }
|
|
||||||
|
|
||||||
// Let's rely on standard logic: get last 365 days.
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const oneYearAgo = new Date();
|
const oneYearAgo = new Date();
|
||||||
oneYearAgo.setDate(today.getDate() - 365);
|
oneYearAgo.setDate(today.getDate() - 365);
|
||||||
|
|
||||||
// Helper to normalize date string YYYY-MM-DD
|
|
||||||
const toKey = (date) => date.toISOString().split('T')[0];
|
const toKey = (date) => date.toISOString().split('T')[0];
|
||||||
|
|
||||||
// Initialize map with empty days
|
// Initialize map with empty days
|
||||||
@@ -42,11 +53,8 @@ const ActivityHeatmap = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill GitHub
|
// Fill GitHub
|
||||||
// The API usually returns 'contributions' list.
|
if (githubData && githubData.contributions) {
|
||||||
// If structure is complex, we might need adjustments, but let's assume flat list available or extractable.
|
githubData.contributions.forEach(day => {
|
||||||
// Actually, jogruber V4 returns: { total: {}, contributions: [ { date, count, level } ... ] }
|
|
||||||
if (githubJson.contributions) {
|
|
||||||
githubJson.contributions.forEach(day => {
|
|
||||||
if (merged.has(day.date)) {
|
if (merged.has(day.date)) {
|
||||||
const curr = merged.get(day.date);
|
const curr = merged.get(day.date);
|
||||||
curr.github = day.count;
|
curr.github = day.count;
|
||||||
@@ -56,9 +64,8 @@ const ActivityHeatmap = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill Gitea
|
// Fill Gitea
|
||||||
// Gitea heatmap endpoint returns array of { timestamp: unix_timestamp, contributions: count }
|
if (Array.isArray(giteaData)) {
|
||||||
if (Array.isArray(giteaJson)) {
|
giteaData.forEach(item => {
|
||||||
giteaJson.forEach(item => {
|
|
||||||
const d = new Date(item.timestamp * 1000);
|
const d = new Date(item.timestamp * 1000);
|
||||||
const key = toKey(d);
|
const key = toKey(d);
|
||||||
if (merged.has(key)) {
|
if (merged.has(key)) {
|
||||||
@@ -75,8 +82,8 @@ const ActivityHeatmap = () => {
|
|||||||
const processed = processData();
|
const processed = processData();
|
||||||
setData(processed);
|
setData(processed);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error fetching activity:", err);
|
console.error("Critical error in ActivityHeatmap:", err);
|
||||||
setError(err.message);
|
// setError(err.message); // Suppress global error to show partial data
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -95,7 +102,7 @@ const ActivityHeatmap = () => {
|
|||||||
svg.selectAll("*").remove();
|
svg.selectAll("*").remove();
|
||||||
|
|
||||||
const g = svg.append("g")
|
const g = svg.append("g")
|
||||||
.attr("transform", `translate(${width / 2}, ${height / 4})`); // Center top-ish
|
.attr("transform", `translate(220, 100)`); // Moved up to prevent cutoff
|
||||||
|
|
||||||
// Isometric projection
|
// Isometric projection
|
||||||
// x grid runs diagonally right-down, y grid runs diagonally left-down
|
// x grid runs diagonally right-down, y grid runs diagonally left-down
|
||||||
@@ -238,22 +245,23 @@ const ActivityHeatmap = () => {
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '600px', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#0a0a0a', color: '#ff4d00' }}>
|
<div style={{ height: '600px', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#0a0a0a', color: '#ff4d00', flexDirection: 'column' }}>
|
||||||
DATA FLUX ERROR
|
<div>DATA FLUX ERROR</div>
|
||||||
|
<div style={{ fontSize: '0.8rem', marginTop: '10px', color: '#888' }}>{error}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section style={{
|
<section style={{
|
||||||
height: '600px',
|
width: '100%',
|
||||||
background: '#0a0a0a',
|
height: '100%',
|
||||||
color: '#e4e4e4',
|
color: '#e4e4e4',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: '40px 20px',
|
padding: '0',
|
||||||
overflow: 'hidden'
|
overflow: 'hidden'
|
||||||
}}>
|
}}>
|
||||||
<h2 className="uppercase" style={{ fontSize: '1.5rem', marginBottom: '10px', color: '#888' }}>
|
<h2 className="uppercase" style={{ fontSize: '1.5rem', marginBottom: '10px', color: '#888' }}>
|
||||||
@@ -268,7 +276,12 @@ const ActivityHeatmap = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<svg ref={svgRef} width="1000" height="600" style={{ maxWidth: '100%', height: 'auto', overflow: 'visible' }} />
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
viewBox="0 0 1000 600"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
style={{ width: '100%', height: 'auto', overflow: 'visible' }}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,10 +18,9 @@ const Footer = () => {
|
|||||||
<div>
|
<div>
|
||||||
<h4 className="uppercase" style={{ marginBottom: '20px', color: '#666' }}>Socials</h4>
|
<h4 className="uppercase" style={{ marginBottom: '20px', color: '#666' }}>Socials</h4>
|
||||||
<ul style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
<ul style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||||
<li><a href="#">Instagram</a></li>
|
<li><a href="https://instagram.com/matiss.j20">Instagram</a></li>
|
||||||
<li><a href="#">Twitter / X</a></li>
|
<li><a href="https://www.linkedin.com/in/matiss-jurevics-121162240/">LinkedIn</a></li>
|
||||||
<li><a href="#">LinkedIn</a></li>
|
<li><a href="https://github.com/MatissJurevics">GitHub</a></li>
|
||||||
<li><a href="#">GitHub</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
192
src/components/GitHistory.jsx
Normal file
192
src/components/GitHistory.jsx
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const GitHistory = () => {
|
||||||
|
const [events, setEvents] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchHistory = async () => {
|
||||||
|
try {
|
||||||
|
// 1. Fetch GitHub Events
|
||||||
|
const githubPromise = fetch('https://api.github.com/users/MatissJurevics/events/public?per_page=5')
|
||||||
|
.then(async res => {
|
||||||
|
if (!res.ok) throw new Error('GitHub Fetch Failed');
|
||||||
|
const data = await res.json();
|
||||||
|
return data.map(evt => ({
|
||||||
|
source: 'github',
|
||||||
|
id: evt.id,
|
||||||
|
type: evt.type,
|
||||||
|
date: new Date(evt.created_at),
|
||||||
|
repo: evt.repo.name.replace('MatissJurevics/', ''),
|
||||||
|
url: `https://github.com/${evt.repo.name}`,
|
||||||
|
raw: evt
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn('GitHub history error:', err);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Fetch Gitea Commits (Manual Aggregation)
|
||||||
|
// Since 'events' endpoint is missing, we fetch repos -> recent commits
|
||||||
|
const giteaPromise = async () => {
|
||||||
|
try {
|
||||||
|
const reposRes = await fetch('/api/gitea/api/v1/users/Matiss/repos');
|
||||||
|
if (!reposRes.ok) throw new Error('Gitea Repos Fetch Failed');
|
||||||
|
const repos = await reposRes.json();
|
||||||
|
|
||||||
|
// Fetch commits for each repo (limit 3 per repo to save requests/bandwidth)
|
||||||
|
const commitPromises = repos.map(async repo => {
|
||||||
|
try {
|
||||||
|
const commitsRes = await fetch(`/api/gitea/api/v1/repos/Matiss/${repo.name}/commits?limit=3`);
|
||||||
|
if (!commitsRes.ok) return [];
|
||||||
|
const commits = await commitsRes.json();
|
||||||
|
return commits.map(c => ({
|
||||||
|
source: 'gitea',
|
||||||
|
id: c.sha,
|
||||||
|
type: 'PushEvent', // Simulate PushEvent
|
||||||
|
date: new Date(c.commit.author.date),
|
||||||
|
repo: repo.name,
|
||||||
|
url: c.html_url,
|
||||||
|
message: c.commit.message,
|
||||||
|
raw: c
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const commitsArrays = await Promise.all(commitPromises);
|
||||||
|
return commitsArrays.flat();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Gitea history error:', err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [githubEvents, giteaEvents] = await Promise.all([githubPromise, giteaPromise()]);
|
||||||
|
const allEvents = [...githubEvents, ...giteaEvents];
|
||||||
|
|
||||||
|
// Sort by Date Descending
|
||||||
|
allEvents.sort((a, b) => b.date - a.date);
|
||||||
|
|
||||||
|
// Slice top 10
|
||||||
|
setEvents(allEvents.slice(0, 10));
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("History fetch error:", err);
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchHistory();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getEventMessage = (event) => {
|
||||||
|
if (event.source === 'gitea') {
|
||||||
|
// Clean message (first line)
|
||||||
|
const msg = event.message.split('\n')[0];
|
||||||
|
return `Cm: ${msg}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitHub Logic
|
||||||
|
const e = event.raw;
|
||||||
|
switch (e.type) {
|
||||||
|
case 'PushEvent':
|
||||||
|
return `Pushed to ${event.repo}`;
|
||||||
|
case 'CreateEvent':
|
||||||
|
return `Created ${e.payload.ref_type} in ${event.repo}`;
|
||||||
|
case 'WatchEvent':
|
||||||
|
return `Starred ${event.repo}`;
|
||||||
|
case 'PullRequestEvent':
|
||||||
|
return `${e.payload.action} PR in ${event.repo}`;
|
||||||
|
default:
|
||||||
|
return `${e.type} on ${event.repo}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (date) => {
|
||||||
|
return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSourceIcon = (source) => {
|
||||||
|
if (source === 'github') return <span style={{ width: '8px', height: '8px', background: '#2da44e', display: 'inline-block', marginRight: '8px', borderRadius: '50%' }}></span>
|
||||||
|
if (source === 'gitea') return <span style={{ width: '8px', height: '8px', background: '#ff4d00', display: 'inline-block', marginRight: '8px', borderRadius: '50%' }}></span>
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#666', fontFamily: 'monospace' }}>
|
||||||
|
LOADING HISTORY...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ff4d00', fontFamily: 'monospace' }}>
|
||||||
|
ERROR LOAD HISTORY
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
color: '#e4e4e4',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '0',
|
||||||
|
position: 'relative'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
maxWidth: '600px',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}>
|
||||||
|
<h2 className="uppercase" style={{ fontSize: '3rem', marginBottom: '40px', lineHeight: 1, textAlign: 'center' }}>
|
||||||
|
Git <br /> History
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="hide-scrollbar" style={{ display: 'flex', flexDirection: 'column', gap: '20px', maxHeight: '500px', overflowY: 'auto', paddingRight: '10px' }}>
|
||||||
|
{events.map(event => (
|
||||||
|
<div key={event.id} style={{
|
||||||
|
borderLeft: `2px solid ${event.source === 'gitea' ? '#ff4d00' : '#333'}`,
|
||||||
|
paddingLeft: '20px',
|
||||||
|
transition: 'border-color 0.3s ease',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.borderColor = event.source === 'gitea' ? '#ff6600' : '#2da44e'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.borderColor = event.source === 'gitea' ? '#ff4d00' : '#333'}
|
||||||
|
>
|
||||||
|
<p className="mono" style={{ fontSize: '0.8rem', color: '#666', marginBottom: '5px' }}>
|
||||||
|
{getSourceIcon(event.source)}
|
||||||
|
{formatDate(event.date)}
|
||||||
|
</p>
|
||||||
|
<a href={event.url} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||||
|
<p style={{ fontSize: '1.1rem', fontWeight: 'bold', lineHeight: '1.4' }}>
|
||||||
|
{getEventMessage(event)}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: '40px', textAlign: 'center' }}>
|
||||||
|
<a href="https://git.mati.ss/Matiss" target="_blank" rel="noopener noreferrer" className="mono" style={{ color: '#ff4d00', textDecoration: 'none', fontSize: '0.9rem' }}>
|
||||||
|
VIEW FULL PROFILE →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GitHistory;
|
||||||
47
src/components/GitSection.jsx
Normal file
47
src/components/GitSection.jsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import GitHistory from './GitHistory';
|
||||||
|
import ActivityHeatmap from './ActivityHeatmap';
|
||||||
|
|
||||||
|
const GitSection = () => {
|
||||||
|
return (
|
||||||
|
<section style={{
|
||||||
|
background: '#0a0a0a',
|
||||||
|
color: '#e4e4e4',
|
||||||
|
padding: '40px 20px',
|
||||||
|
position: 'relative'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
maxWidth: '1400px',
|
||||||
|
width: '100%',
|
||||||
|
margin: '0 auto',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '40px',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start' // Align top
|
||||||
|
}}>
|
||||||
|
{/* Left: Github History List */}
|
||||||
|
<div style={{
|
||||||
|
flex: '1',
|
||||||
|
minWidth: '350px',
|
||||||
|
maxWidth: '500px'
|
||||||
|
}}>
|
||||||
|
<GitHistory />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: Activity Heatmap 3D */}
|
||||||
|
<div style={{
|
||||||
|
flex: '2',
|
||||||
|
minWidth: '500px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}>
|
||||||
|
<ActivityHeatmap />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GitSection;
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
const GithubHistory = () => {
|
|
||||||
const [events, setEvents] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchEvents = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://api.github.com/users/MatissJurevics/events/public?per_page=5');
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch GitHub history');
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
setEvents(data);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchEvents();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getEventMessage = (event) => {
|
|
||||||
switch (event.type) {
|
|
||||||
case 'PushEvent':
|
|
||||||
return `Pushed to ${event.repo.name.replace('MatissJurevics/', '')}`;
|
|
||||||
case 'CreateEvent':
|
|
||||||
return `Created ${event.payload.ref_type} in ${event.repo.name.replace('MatissJurevics/', '')}`;
|
|
||||||
case 'WatchEvent':
|
|
||||||
return `Starred ${event.repo.name.replace('MatissJurevics/', '')}`;
|
|
||||||
case 'PullRequestEvent':
|
|
||||||
return `${event.payload.action} PR in ${event.repo.name.replace('MatissJurevics/', '')}`;
|
|
||||||
default:
|
|
||||||
return `${event.type} on ${event.repo.name.replace('MatissJurevics/', '')}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
|
||||||
const date = new Date(dateString);
|
|
||||||
return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#666', fontFamily: 'monospace' }}>
|
|
||||||
LOADING HISTORY...
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ff4d00', fontFamily: 'monospace' }}>
|
|
||||||
ERROR LOAD GITHUB DATA
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section style={{
|
|
||||||
height: '600px',
|
|
||||||
background: '#0a0a0a',
|
|
||||||
color: '#e4e4e4',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: '60px 20px',
|
|
||||||
position: 'relative'
|
|
||||||
}}>
|
|
||||||
<div style={{
|
|
||||||
maxWidth: '600px', // Narrower container for list
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}}>
|
|
||||||
<h2 className="uppercase" style={{ fontSize: '3rem', marginBottom: '40px', lineHeight: 1, textAlign: 'center' }}>
|
|
||||||
Github <br /> History
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
|
||||||
{events.map(event => (
|
|
||||||
<div key={event.id} style={{
|
|
||||||
borderLeft: '2px solid #333',
|
|
||||||
paddingLeft: '20px',
|
|
||||||
transition: 'border-color 0.3s ease',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => e.currentTarget.style.borderColor = '#ff4d00'}
|
|
||||||
onMouseLeave={(e) => e.currentTarget.style.borderColor = '#333'}
|
|
||||||
>
|
|
||||||
<p className="mono" style={{ fontSize: '0.8rem', color: '#666', marginBottom: '5px' }}>
|
|
||||||
{formatDate(event.created_at)}
|
|
||||||
</p>
|
|
||||||
<a href={`https://github.com/${event.repo.name}`} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none', color: 'inherit' }}>
|
|
||||||
<p style={{ fontSize: '1.2rem', fontWeight: 'bold' }}>
|
|
||||||
{getEventMessage(event)}
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div style={{ marginTop: '40px', textAlign: 'center' }}>
|
|
||||||
<a href="https://github.com/MatissJurevics" target="_blank" rel="noopener noreferrer" className="mono" style={{ color: '#ff4d00', textDecoration: 'none', fontSize: '0.9rem' }}>
|
|
||||||
VIEW FULL PROFILE →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GithubHistory;
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import WhereAmI from './WhereAmI';
|
import WhereAmI from './WhereAmI';
|
||||||
import GithubHistory from './GithubHistory';
|
import GitSection from './GitSection';
|
||||||
import ActivityHeatmap from './ActivityHeatmap';
|
|
||||||
|
|
||||||
const InfoTabs = () => {
|
const InfoTabs = () => {
|
||||||
const [activeTab, setActiveTab] = useState('location');
|
const [activeTab, setActiveTab] = useState('location');
|
||||||
@@ -34,12 +33,12 @@ const InfoTabs = () => {
|
|||||||
Where Am I?
|
Where Am I?
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('github')}
|
onClick={() => setActiveTab('git')}
|
||||||
style={{
|
style={{
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderBottom: activeTab === 'github' ? '2px solid #ff4d00' : '2px solid transparent',
|
borderBottom: activeTab === 'git' ? '2px solid #ff4d00' : '2px solid transparent',
|
||||||
color: activeTab === 'github' ? '#fff' : '#666',
|
color: activeTab === 'git' ? '#fff' : '#666',
|
||||||
padding: '10px 20px',
|
padding: '10px 20px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
@@ -48,32 +47,14 @@ const InfoTabs = () => {
|
|||||||
transition: 'all 0.3s ease'
|
transition: 'all 0.3s ease'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
History
|
Git Activity
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('activity')}
|
|
||||||
style={{
|
|
||||||
background: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
borderBottom: activeTab === 'activity' ? '2px solid #ff4d00' : '2px solid transparent',
|
|
||||||
color: activeTab === 'activity' ? '#fff' : '#666',
|
|
||||||
padding: '10px 20px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '0.9rem',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: '0.1em',
|
|
||||||
transition: 'all 0.3s ease'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Activity 3D
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Area */}
|
{/* Content Area */}
|
||||||
<div>
|
<div>
|
||||||
{activeTab === 'location' && <WhereAmI />}
|
{activeTab === 'location' && <WhereAmI />}
|
||||||
{activeTab === 'github' && <GithubHistory />}
|
{activeTab === 'git' && <GitSection />}
|
||||||
{activeTab === 'activity' && <ActivityHeatmap />}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ a {
|
|||||||
color: #646cff;
|
color: #646cff;
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #535bf2;
|
color: #535bf2;
|
||||||
}
|
}
|
||||||
@@ -46,9 +47,11 @@ button {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.25s;
|
transition: border-color 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
border-color: #646cff;
|
border-color: #646cff;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:focus,
|
button:focus,
|
||||||
button:focus-visible {
|
button:focus-visible {
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
@@ -59,9 +62,11 @@ button:focus-visible {
|
|||||||
color: #213547;
|
color: #213547;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #747bff;
|
color: #747bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,3 +40,15 @@ ul {
|
|||||||
canvas {
|
canvas {
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Utility to hide scrollbar */
|
||||||
|
.hide-scrollbar {
|
||||||
|
-ms-overflow-style: none !important;
|
||||||
|
/* IE and Edge */
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
/* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite' // HMR Trigger
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
|
|||||||
Reference in New Issue
Block a user