TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1"use client";23import { useEffect, useState } from "react";4import { useSession, useToasts } from "@/lib/store";5import type { PublicUser, UserSettings, WalletView } from "@spinza/shared";6import { AnimatePresence, motion } from "framer-motion";7import { cn } from "@/lib/utils";8import { SessionReminder } from "./session-reminder";910export function Providers({ children, session }: { children: React.ReactNode; session: { user: PublicUser; wallet: WalletView; settings: UserSettings } | null }) {11 const hydrate = useSession((s) => s.hydrate);12 // Hydrate synchronously on first render so the first paint already knows the session.13 useState(() => {14 hydrate(session);15 return true;16 });17 useEffect(() => {18 hydrate(session);19 }, [session, hydrate]);20 return (21 <>22 {children}23 <Toaster />24 <SessionReminder />25 </>26 );27}2829function Toaster() {30 const toasts = useToasts((s) => s.toasts);31 const dismiss = useToasts((s) => s.dismiss);32 return (33 <div className="pointer-events-none fixed inset-x-0 top-0 flex flex-col items-center gap-2 px-4" style={{ zIndex: "var(--z-toast)", paddingTop: "calc(var(--safe-top) + 12px)" }}>34 <AnimatePresence>35 {toasts.map((t) => (36 <motion.button37 key={t.id}38 layout39 initial={{ opacity: 0, y: -16, scale: 0.96 }}40 animate={{ opacity: 1, y: 0, scale: 1 }}41 exit={{ opacity: 0, y: -10, scale: 0.96 }}42 onClick={() => dismiss(t.id)}43 className={cn(44 "pointer-events-auto glass w-full max-w-sm rounded-md px-4 py-3 text-left shadow-2xl",45 t.tone === "success" && "border-success/40",46 t.tone === "danger" && "border-danger/40",47 t.tone === "credit" && "border-accent/40",48 )}49 >50 <div className={cn("text-sm font-semibold", t.tone === "credit" && "text-credit", t.tone === "danger" && "text-danger", t.tone === "success" && "text-success")}>{t.title}</div>51 {t.description ? <div className="mt-0.5 text-[13px] text-fg-2">{t.description}</div> : null}52 </motion.button>53 ))}54 </AnimatePresence>55 </div>56 );57}58