From 6e38e2d270b651c2fa1c406bfedb0e7750fafa0d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 27 Mar 2026 18:35:12 +0100 Subject: [PATCH] refactor: replace useQuery with useAuthQuery for improved authentication handling - Updated multiple components to utilize useAuthQuery instead of useQuery for fetching user-related data, enhancing authentication management. - Adjusted balance and subscription queries across various components including InitUser, ManageSubscription, PricingCards, CreditDisplay, and others to ensure consistent authentication checks. - Improved overall code maintainability by centralizing authentication logic in the new hook. --- components/billing/manage-subscription.tsx | 4 ++-- components/billing/pricing-cards.tsx | 4 ++-- components/canvas/canvas.tsx | 8 +++---- components/canvas/credit-display.tsx | 7 +++--- components/canvas/nodes/prompt-node.tsx | 5 ++-- components/dashboard/credit-overview.tsx | 8 +++---- components/dashboard/recent-transactions.tsx | 4 ++-- components/init-user.tsx | 9 ++++---- hooks/use-auth-query.ts | 24 ++++++++++++++++++++ 9 files changed, 48 insertions(+), 25 deletions(-) create mode 100644 hooks/use-auth-query.ts diff --git a/components/billing/manage-subscription.tsx b/components/billing/manage-subscription.tsx index 654f2b5..8175a75 100644 --- a/components/billing/manage-subscription.tsx +++ b/components/billing/manage-subscription.tsx @@ -1,6 +1,6 @@ "use client"; -import { useQuery } from "convex/react"; +import { useAuthQuery } from "@/hooks/use-auth-query"; import { ExternalLink } from "lucide-react"; import { Badge } from "@/components/ui/badge"; @@ -19,7 +19,7 @@ const TIER_LABELS: Record = { }; export function ManageSubscription() { - const subscription = useQuery(api.credits.getSubscription); + const subscription = useAuthQuery(api.credits.getSubscription); const tier = normalizeTier(subscription?.tier); return ( diff --git a/components/billing/pricing-cards.tsx b/components/billing/pricing-cards.tsx index dbbd89e..467ff18 100644 --- a/components/billing/pricing-cards.tsx +++ b/components/billing/pricing-cards.tsx @@ -1,6 +1,6 @@ "use client"; -import { useQuery } from "convex/react"; +import { useAuthQuery } from "@/hooks/use-auth-query"; import { Check } from "lucide-react"; import { Badge } from "@/components/ui/badge"; @@ -18,7 +18,7 @@ import { msg } from "@/lib/toast-messages"; const TIER_ORDER = ["free", "starter", "pro", "max"] as const; export function PricingCards() { - const subscription = useQuery(api.credits.getSubscription); + const subscription = useAuthQuery(api.credits.getSubscription); const currentTier = normalizeTier(subscription?.tier); async function handleCheckout(polarProductId: string) { diff --git a/components/canvas/canvas.tsx b/components/canvas/canvas.tsx index 7f3e631..47dbad3 100644 --- a/components/canvas/canvas.tsx +++ b/components/canvas/canvas.tsx @@ -236,10 +236,11 @@ function CanvasInner({ canvasId }: CanvasInnerProps) { const { resolvedTheme } = useTheme(); const { data: session, isPending: isSessionPending } = authClient.useSession(); const { isLoading: isAuthLoading, isAuthenticated } = useConvexAuth(); - const shouldSkipCanvasQueries = isAuthLoading || !isAuthenticated; + const shouldSkipCanvasQueries = + isSessionPending || isAuthLoading || !isAuthenticated; const convexAuthUserProbe = useQuery( api.auth.safeGetAuthUser, - isAuthLoading ? "skip" : {}, + shouldSkipCanvasQueries ? "skip" : {}, ); useEffect(() => { @@ -376,7 +377,6 @@ function CanvasInner({ canvasId }: CanvasInnerProps) { // ─── Convex → Lokaler State Sync ────────────────────────────── useEffect(() => { if (!convexNodes || isDragging.current) return; - // eslint-disable-next-line react-hooks/set-state-in-effect setNodes((previousNodes) => { const incomingNodes = withResolvedCompareData(convexNodes.map(convexNodeToRF), edges); return mergeNodesPreservingLocalState(previousNodes, incomingNodes); @@ -385,7 +385,6 @@ function CanvasInner({ canvasId }: CanvasInnerProps) { useEffect(() => { if (!convexEdges) return; - // eslint-disable-next-line react-hooks/set-state-in-effect setEdges((prev) => { const tempEdges = prev.filter((e) => e.className === "temp"); const mapped = convexEdges.map(convexEdgeToRF); @@ -398,7 +397,6 @@ function CanvasInner({ canvasId }: CanvasInnerProps) { useEffect(() => { if (isDragging.current) return; - // eslint-disable-next-line react-hooks/set-state-in-effect setNodes((nds) => withResolvedCompareData(nds, edges)); }, [edges]); diff --git a/components/canvas/credit-display.tsx b/components/canvas/credit-display.tsx index 325fdf6..bde0c22 100644 --- a/components/canvas/credit-display.tsx +++ b/components/canvas/credit-display.tsx @@ -1,6 +1,7 @@ "use client"; -import { useMutation, useQuery } from "convex/react"; +import { useMutation } from "convex/react"; +import { useAuthQuery } from "@/hooks/use-auth-query"; import { api } from "@/convex/_generated/api"; import { Coins } from "lucide-react"; import { toast } from "@/lib/toast"; @@ -27,8 +28,8 @@ const showTestCreditGrant = process.env.NEXT_PUBLIC_ALLOW_TEST_CREDIT_GRANT === "true"; export function CreditDisplay() { - const balance = useQuery(api.credits.getBalance); - const subscription = useQuery(api.credits.getSubscription); + const balance = useAuthQuery(api.credits.getBalance); + const subscription = useAuthQuery(api.credits.getSubscription); const grantTestCredits = useMutation(api.credits.grantTestCredits); if (balance === undefined || subscription === undefined) { diff --git a/components/canvas/nodes/prompt-node.tsx b/components/canvas/nodes/prompt-node.tsx index 915594d..4f07e78 100644 --- a/components/canvas/nodes/prompt-node.tsx +++ b/components/canvas/nodes/prompt-node.tsx @@ -9,7 +9,8 @@ import { type NodeProps, type Node, } from "@xyflow/react"; -import { useMutation, useAction, useQuery } from "convex/react"; +import { useMutation, useAction } from "convex/react"; +import { useAuthQuery } from "@/hooks/use-auth-query"; import { api } from "@/convex/_generated/api"; import type { Id } from "@/convex/_generated/dataModel"; import BaseNodeWrapper from "./base-node-wrapper"; @@ -108,7 +109,7 @@ export default function PromptNode({ const dataRef = useRef(data); dataRef.current = data; - const balance = useQuery(api.credits.getBalance); + const balance = useAuthQuery(api.credits.getBalance); const creditCost = getModel(DEFAULT_MODEL_ID)?.creditCost ?? 4; const availableCredits = diff --git a/components/dashboard/credit-overview.tsx b/components/dashboard/credit-overview.tsx index f985795..6123d2c 100644 --- a/components/dashboard/credit-overview.tsx +++ b/components/dashboard/credit-overview.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect } from "react"; -import { useQuery } from "convex/react"; +import { useAuthQuery } from "@/hooks/use-auth-query"; import { CreditCard } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; @@ -43,9 +43,9 @@ const LOW_CREDITS_THRESHOLD = 20; export function CreditOverview() { const router = useRouter(); - const balance = useQuery(api.credits.getBalance); - const subscription = useQuery(api.credits.getSubscription); - const usageStats = useQuery(api.credits.getUsageStats); + const balance = useAuthQuery(api.credits.getBalance); + const subscription = useAuthQuery(api.credits.getSubscription); + const usageStats = useAuthQuery(api.credits.getUsageStats); useEffect(() => { if (balance === undefined) return; diff --git a/components/dashboard/recent-transactions.tsx b/components/dashboard/recent-transactions.tsx index 3a38909..9e41468 100644 --- a/components/dashboard/recent-transactions.tsx +++ b/components/dashboard/recent-transactions.tsx @@ -1,6 +1,6 @@ "use client"; -import { useQuery } from "convex/react"; +import { useAuthQuery } from "@/hooks/use-auth-query"; import { Activity, Coins } from "lucide-react"; import { Badge } from "@/components/ui/badge"; @@ -45,7 +45,7 @@ function truncatedDescription(text: string, maxLen = 40) { // --------------------------------------------------------------------------- export function RecentTransactions() { - const transactions = useQuery(api.credits.getRecentTransactions, { + const transactions = useAuthQuery(api.credits.getRecentTransactions, { limit: 10, }); diff --git a/components/init-user.tsx b/components/init-user.tsx index c433c43..b47ea8e 100644 --- a/components/init-user.tsx +++ b/components/init-user.tsx @@ -1,7 +1,8 @@ "use client"; import { authClient } from "@/lib/auth-client"; -import { useMutation, useQuery } from "convex/react"; +import { useMutation } from "convex/react"; +import { useAuthQuery } from "@/hooks/use-auth-query"; import { api } from "@/convex/_generated/api"; import { useEffect, useRef } from "react"; import { toast } from "@/lib/toast"; @@ -14,10 +15,8 @@ import { msg } from "@/lib/toast-messages"; */ export function InitUser() { const { data: session } = authClient.useSession(); - const balance = useQuery( - api.credits.getBalance, - session?.user ? {} : "skip" - ); + + const balance = useAuthQuery(api.credits.getBalance); const initBalance = useMutation(api.credits.initBalance); const initStartedRef = useRef(false); diff --git a/hooks/use-auth-query.ts b/hooks/use-auth-query.ts new file mode 100644 index 0000000..bca57ea --- /dev/null +++ b/hooks/use-auth-query.ts @@ -0,0 +1,24 @@ +"use client"; + +import { useConvexAuth, useQuery } from "convex/react"; +import type { FunctionReference, FunctionArgs, FunctionReturnType } from "convex/server"; + +/** + * Wrapper um `useQuery` der automatisch `"skip"` nutzt wenn der + * Convex-Auth-Token noch nicht bereit ist. Verhindert "Unauthenticated"-Fehler + * bei Queries die `requireAuth` nutzen. + * + * Nutzt nur `isAuthenticated` (nicht `isLoading`) als Guard — wenn ein + * `initialToken` vom SSR vorhanden ist, springt `isAuthenticated` sofort + * auf `true` ohne Loading-Phase, sodass Queries ohne Verzögerung feuern. + */ +export function useAuthQuery>( + query: F, + ...args: [] | [FunctionArgs | "skip"] +): FunctionReturnType | undefined { + const { isAuthenticated } = useConvexAuth(); + + const shouldSkip = !isAuthenticated || args[0] === "skip"; + + return useQuery(query, shouldSkip ? "skip" : (args[0] ?? ({} as FunctionArgs))); +}