Less
diff --git a/src/components/Bars.tsx b/src/components/Bars.tsx
index 670a903..04a0734 100644
--- a/src/components/Bars.tsx
+++ b/src/components/Bars.tsx
@@ -1,6 +1,13 @@
import { Typewriter } from "./Typewriter";
import { useBars } from "~/context/bars";
-import { onMount, createEffect, createSignal, Show, For } from "solid-js";
+import {
+ onMount,
+ createEffect,
+ createSignal,
+ Show,
+ For,
+ onCleanup
+} from "solid-js";
import { api } from "~/lib/api";
import { TerminalSplash } from "./TerminalSplash";
import { insertSoftHyphens } from "~/lib/client-utils";
@@ -9,6 +16,7 @@ import LinkedIn from "./icons/LinkedIn";
import { RecentCommits } from "./RecentCommits";
import { ActivityHeatmap } from "./ActivityHeatmap";
import { DarkModeToggle } from "./DarkModeToggle";
+import { SkeletonBox, SkeletonText } from "./SkeletonLoader";
interface GitCommit {
sha: string;
@@ -33,25 +41,35 @@ export function RightBarContent() {
const [giteaActivity, setGiteaActivity] = createSignal
([]);
const [loading, setLoading] = createSignal(true);
- onMount(async () => {
+ onMount(() => {
// Fetch all data client-side only to avoid hydration mismatch
- 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(() => [])
- ]);
+ 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);
- }
+ 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 (
@@ -166,43 +184,11 @@ export function LeftBar() {
}
};
- onMount(async () => {
+ onMount(() => {
// 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 });
- }
-
+ // Setup ResizeObserver FIRST (synchronous) - this allows bar sizing to happen immediately
if (ref) {
const updateSize = () => {
actualWidth = ref?.offsetWidth || 0;
@@ -282,13 +268,54 @@ export function LeftBar() {
ref.addEventListener("touchend", handleTouchEnd, { passive: true });
ref.addEventListener("keydown", handleKeyDown);
- return () => {
+ onCleanup(() => {
resizeObserver.disconnect();
ref?.removeEventListener("touchstart", handleTouchStart);
ref?.removeEventListener("touchend", handleTouchEnd);
ref?.removeEventListener("keydown", handleKeyDown);
- };
+ });
}
+
+ // Fetch data asynchronously AFTER sizing setup (non-blocking)
+ const fetchData = async () => {
+ // 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 });
+ }
+ };
+
+ // Defer API calls to next tick to allow initial render/sizing to complete first
+ setTimeout(() => {
+ fetchData();
+ }, 0);
});
// Update size when visibility changes
@@ -365,7 +392,23 @@ export function LeftBar() {
Recent Posts
+ }
+ >
+
+
}
>
@@ -55,7 +77,7 @@ export const RecentCommits: Component<{
href={commit.url}
target="_blank"
rel="noreferrer"
- class="hover:bg-surface0 group block rounded-md p-2 transition-all duration-200 ease-in-out hover:scale-[1.02]"
+ class="hover:bg-surface0 group block w-52 rounded-md p-2 transition-all duration-200 ease-in-out hover:scale-[1.02]"
>
{
+ if (isServer) return;
+
+ const interval = setInterval(() => {
+ setShowing((prev) => (prev + 1) % spinnerChars.length);
+ }, 50);
+
+ onCleanup(() => {
+ clearInterval(interval);
+ });
+ });
+
+ return () => spinnerChars[showing()];
+}
+
+export function SkeletonBox(props: SkeletonProps) {
+ const spinner = useSpinner();
+
+ return (
+
+ {spinner()}
+
+ );
+}
+
+export function SkeletonText(props: SkeletonProps) {
+ const spinner = useSpinner();
+
+ return (
+
+ {spinner()}
+
+ );
+}
+
+export function SkeletonCircle(props: SkeletonProps) {
+ const spinner = useSpinner();
+
+ return (
+
+ {spinner()}
+
+ );
+}