SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
2.5 KB · 64 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import Link from "next/link";4import { usePathname, useRouter } from "next/navigation";5import { ArrowRight, Sparkles } from "lucide-react";6import { useApp } from "@/components/app/store";7import { useApi } from "@/lib/client/api";8import type { PublicConnection } from "@/lib/client/types";910const SS_REDIRECTED = "polyllm:onboarding-redirected";11const LS_DONE_PREFIX = "polyllm:onboarding-done:";1213/**14 * First-run gate. Mounted by the chat empty state (and safe anywhere inside <AppProvider>).15 *16 * A user who has never completed onboarding and has no provider connected is sent to17 * `/app/onboarding` once per browser session; afterwards this renders a small "Finish setup" link18 * so nobody is trapped in a redirect loop. Completion is stored server-side19 * (`users.onboarding_completed_at`) by the onboarding page.20 */21export function Onboarding() {22  const { user } = useApp();23  const router = useRouter();24  const pathname = usePathname();25  // Same SWR key as the store → deduped, but gives us `isLoading` so we never redirect on an empty first render.26  const providers = useApi<{ connections: PublicConnection[] }>("/api/providers");27  const [redirected, setRedirected] = React.useState<boolean | null>(null);2829  const eligible = user.onboardingCompletedAt === null && !providers.isLoading && (providers.data?.connections.length ?? 0) === 0;3031  React.useEffect(() => {32    if (!eligible || pathname.startsWith("/app/onboarding")) return;33    let doneLocally = false;34    let already = false;35    try {36      doneLocally = window.localStorage.getItem(LS_DONE_PREFIX + user.id) === "1";37      already = window.sessionStorage.getItem(SS_REDIRECTED) === "1";38    } catch {39      /* storage unavailable */40    }41    if (doneLocally) return;42    if (already) {43      // eslint-disable-next-line react-hooks/set-state-in-effect44      setRedirected(true);45      return;46    }47    try {48      window.sessionStorage.setItem(SS_REDIRECTED, "1");49    } catch {50      /* ignore */51    }52    router.replace("/app/onboarding");53  }, [eligible, pathname, router, user.id]);5455  if (!eligible || !redirected) return null;56  return (57    <Link href="/app/onboarding" className="mb-5 inline-flex min-h-[40px] items-center gap-2 rounded-full bg-accent-soft px-3.5 text-[13px] font-medium text-accent transition-colors hover:bg-accent/20">58      <Sparkles className="size-3.5" aria-hidden />59      Finish setting up PolyLLM60      <ArrowRight className="size-3.5" aria-hidden />61    </Link>62  );63}64