SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
2.2 KB · 58 lines tsx
Raw Blame History
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