import { Typewriter } from "./Typewriter"; import { useBars } from "~/context/bars"; import { onMount, createEffect, createSignal, Show, For, onCleanup } from "solid-js"; import { api } from "~/lib/api"; import { insertSoftHyphens } from "~/lib/client-utils"; import GitHub from "./icons/GitHub"; import LinkedIn from "./icons/LinkedIn"; import { RecentCommits } from "./RecentCommits"; import { ActivityHeatmap } from "./ActivityHeatmap"; import { DarkModeToggle } from "./DarkModeToggle"; import { SkeletonBox, SkeletonText } from "./SkeletonLoader"; import { env } from "~/env/client"; function formatDomainName(url: string): string { const domain = url.split("://")[1]?.split(":")[0] ?? url; const withoutWww = domain.replace(/^www\./i, ""); return withoutWww.charAt(0).toUpperCase() + withoutWww.slice(1); } interface GitCommit { sha: string; message: string; author: string; date: string; repo: string; url: string; } interface ContributionDay { date: string; count: number; } export function RightBarContent() { const { setLeftBarVisible } = useBars(); const [githubCommits, setGithubCommits] = createSignal([]); const [giteaCommits, setGiteaCommits] = createSignal([]); const [githubActivity, setGithubActivity] = createSignal( [] ); const [giteaActivity, setGiteaActivity] = createSignal([]); const [loading, setLoading] = createSignal(true); const handleLinkClick = () => { if (typeof window !== "undefined" && window.innerWidth < 768) { setLeftBarVisible(false); } }; onMount(() => { // Fetch all data client-side only to avoid hydration mismatch const fetchData = async () => { try { const [ghCommits, gtCommits, ghActivity, gtActivity] = await Promise.all([ api.gitActivity.getGitHubCommits .query({ limit: 3 }) .catch(() => []), api.gitActivity.getGiteaCommits.query({ limit: 3 }).catch(() => []), api.gitActivity.getGitHubActivity.query().catch(() => []), api.gitActivity.getGiteaActivity.query().catch(() => []) ]); setGithubCommits(ghCommits); setGiteaCommits(gtCommits); setGithubActivity(ghActivity); setGiteaActivity(gtActivity); } catch (error) { console.error("Failed to fetch git activity:", error); } finally { setLoading(false); } }; // Defer API calls to next tick to allow initial render to complete first setTimeout(() => { fetchData(); }, 0); }); return (
{/* Git Activity Section */}
); } export function LeftBar() { const { setLeftBarSize, leftBarSize, leftBarVisible, setLeftBarVisible } = useBars(); let ref: HTMLDivElement | undefined; let actualWidth = 0; const [recentPosts, setRecentPosts] = createSignal( undefined ); const [userInfo, setUserInfo] = createSignal<{ email: string | null; isAuthenticated: boolean; } | null>(null); const [isMounted, setIsMounted] = createSignal(false); const [signOutLoading, setSignOutLoading] = createSignal(false); const handleLinkClick = () => { if (typeof window !== "undefined" && window.innerWidth < 768) { setLeftBarVisible(false); } }; const handleSignOut = async () => { setSignOutLoading(true); try { await api.auth.signOut.mutate(); window.location.href = "/"; } catch (error) { console.error("Sign out failed:", error); setSignOutLoading(false); } }; onMount(() => { setIsMounted(true); if (ref) { const updateSize = () => { actualWidth = ref?.offsetWidth || 0; setLeftBarSize(leftBarVisible() ? actualWidth : 0); }; updateSize(); const resizeObserver = new ResizeObserver((entries) => { requestAnimationFrame(() => { actualWidth = ref?.offsetWidth || 0; setLeftBarSize(leftBarVisible() ? actualWidth : 0); }); }); resizeObserver.observe(ref); // Focus trap for accessibility on mobile const handleKeyDown = (e: KeyboardEvent) => { const isMobile = window.innerWidth < 768; if (!isMobile || !leftBarVisible()) return; if (e.key === "Tab") { const focusableElements = ref?.querySelectorAll( 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])' ); if (!focusableElements || focusableElements.length === 0) return; const firstElement = focusableElements[0] as HTMLElement; const lastElement = focusableElements[ focusableElements.length - 1 ] as HTMLElement; if (e.shiftKey) { // Shift+Tab - going backwards if (document.activeElement === firstElement) { e.preventDefault(); lastElement.focus(); } } else { // Tab - going forwards if (document.activeElement === lastElement) { e.preventDefault(); firstElement.focus(); } } } }; ref.addEventListener("keydown", handleKeyDown); onCleanup(() => { resizeObserver.disconnect(); ref?.removeEventListener("keydown", handleKeyDown); }); } const fetchData = async () => { try { const posts = await api.blog.getRecentPosts.query(); setRecentPosts(posts as any[]); } catch (error) { console.error("Failed to fetch recent posts:", error); setRecentPosts([]); } try { const response = await fetch("/api/trpc/user.getProfile", { method: "GET" }); if (response.ok) { const result = await response.json(); if (result.result?.data) { setUserInfo({ email: result.result.data.email, isAuthenticated: true }); } else { setUserInfo({ email: null, isAuthenticated: false }); } } else { setUserInfo({ email: null, isAuthenticated: false }); } } catch (error) { console.error("Failed to fetch user info:", error); setUserInfo({ email: null, isAuthenticated: false }); } }; setTimeout(() => { fetchData(); }, 0); }); createEffect(() => { setLeftBarSize(leftBarVisible() ? actualWidth : 0); }); return ( ); } export function RightBar() { const { setRightBarSize, rightBarSize, rightBarVisible } = useBars(); let ref: HTMLDivElement | undefined; let actualWidth = 0; onMount(() => { if (ref) { const updateSize = () => { actualWidth = ref?.offsetWidth || 0; setRightBarSize(rightBarVisible() ? actualWidth : 0); }; updateSize(); const resizeObserver = new ResizeObserver((entries) => { requestAnimationFrame(() => { actualWidth = ref?.offsetWidth || 0; setRightBarSize(rightBarVisible() ? actualWidth : 0); }); }); resizeObserver.observe(ref); return () => { resizeObserver.disconnect(); }; } }); createEffect(() => { setRightBarSize(rightBarVisible() ? actualWidth : 0); }); return ( ); }