import { Typewriter } from "./Typewriter";
import { useBars } from "~/context/bars";
import {
onMount,
createEffect,
createSignal,
createResource,
Show,
For,
Suspense,
createMemo
} from "solid-js";
import { api } from "~/lib/api";
import { TerminalSplash } from "./TerminalSplash";
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";
export function RightBarContent() {
const [githubCommits] = createResource(async () => {
try {
return await api.gitActivity.getGitHubCommits.query({ limit: 3 });
} catch (error) {
console.error("Failed to fetch GitHub commits:", error);
return [];
}
});
const [giteaCommits] = createResource(async () => {
try {
return await api.gitActivity.getGiteaCommits.query({ limit: 3 });
} catch (error) {
console.error("Failed to fetch Gitea commits:", error);
return [];
}
});
const [githubActivity] = createResource(async () => {
try {
return await api.gitActivity.getGitHubActivity.query();
} catch (error) {
console.error("Failed to fetch GitHub activity:", error);
return [];
}
});
const [giteaActivity] = createResource(async () => {
try {
return await api.gitActivity.getGiteaActivity.query();
} catch (error) {
console.error("Failed to fetch Gitea activity:", error);
return [];
}
});
return (
{/* Git Activity Section */}
}>
);
}
export function LeftBar() {
const { setLeftBarSize, leftBarSize, leftBarVisible, setLeftBarVisible } =
useBars();
let ref: HTMLDivElement | undefined;
let actualWidth = 0;
let touchStartX = 0;
let touchStartY = 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 handleSignOut = async () => {
setSignOutLoading(true);
try {
await api.auth.signOut.mutate();
window.location.href = "/";
} catch (error) {
console.error("Sign out failed:", error);
setSignOutLoading(false);
}
};
onMount(async () => {
// Mark as mounted to avoid hydration mismatch
setIsMounted(true);
// Fetch recent posts only on client side to avoid hydration mismatch
try {
const posts = await api.blog.getRecentPosts.query();
setRecentPosts(posts as any[]);
} catch (error) {
console.error("Failed to fetch recent posts:", error);
setRecentPosts([]);
}
// Fetch user info client-side only to avoid hydration mismatch
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 });
}
if (ref) {
const updateSize = () => {
actualWidth = ref?.offsetWidth || 0;
setLeftBarSize(leftBarVisible() ? actualWidth : 0);
};
updateSize();
const resizeObserver = new ResizeObserver((entries) => {
// Use requestAnimationFrame to avoid ResizeObserver loop error
requestAnimationFrame(() => {
actualWidth = ref?.offsetWidth || 0;
setLeftBarSize(leftBarVisible() ? actualWidth : 0);
});
});
resizeObserver.observe(ref);
// Swipe-to-dismiss gesture on sidebar itself (mobile only)
const handleTouchStart = (e: TouchEvent) => {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
};
const handleTouchEnd = (e: TouchEvent) => {
const isMobile = window.innerWidth < 768;
if (!isMobile) return; // Only allow dismiss on mobile
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const deltaX = touchEndX - touchStartX;
const deltaY = touchEndY - touchStartY;
// Only trigger if horizontal swipe is dominant
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Swipe left to dismiss (at least 50px)
if (deltaX < -50 && leftBarVisible()) {
setLeftBarVisible(false);
}
}
};
// 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("touchstart", handleTouchStart, { passive: true });
ref.addEventListener("touchend", handleTouchEnd, { passive: true });
ref.addEventListener("keydown", handleKeyDown);
return () => {
resizeObserver.disconnect();
ref?.removeEventListener("touchstart", handleTouchStart);
ref?.removeEventListener("touchend", handleTouchEnd);
ref?.removeEventListener("keydown", handleKeyDown);
};
}
});
// Update size when visibility changes
createEffect(() => {
setLeftBarSize(leftBarVisible() ? actualWidth : 0);
});
// Auto-focus first element when sidebar opens on mobile
createEffect(() => {
const isMobile = window.innerWidth < 768;
if (leftBarVisible() && isMobile && ref) {
const firstFocusable = ref.querySelector(
"a[href], button:not([disabled]), input:not([disabled])"
) as HTMLElement;
if (firstFocusable) {
// Small delay to ensure animation has started
setTimeout(() => firstFocusable.focus(), 100);
}
}
});
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 (
);
}