fix(frontend): prevent Wallet rendering twice (#11282)

## Changes 🏗️

The `<Wallet />` was being rendered twice ( one hidden with CSS `hidden`
) because of the Navbar layout, which caused logic issues within the
wallet. I changed to render it conditionally via Javascript instance,
which is always better practice than use `hidden` specially for
components with actual logic.

I also moved the component files closer to where it is used ( in the
navbar ).

I have a Cursor plugin that removes imports when unused, but annoyingly
re-organizes them, hence the changes around that...

## Checklist 📋

### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Login
  - [x] There is only 1 Wallet in the DOM
This commit is contained in:
Ubbe
2025-10-29 18:09:13 +04:00
committed by GitHub
parent 4922f88851
commit 749341100b
5 changed files with 74 additions and 57 deletions

View File

@@ -1,13 +1,13 @@
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useTurnstile } from "@/hooks/useTurnstile";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { environment } from "@/services/environment";
import { loginFormSchema, LoginProvider } from "@/types/auth";
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import z from "zod";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { environment } from "@/services/environment";
export function useLoginPage() {
const { supabase, user, isUserLoading } = useSupabase();

View File

@@ -1,14 +1,16 @@
"use client";
import { useGetV2GetUserProfile } from "@/app/api/__generated__/endpoints/store/store";
import { IconAutoGPTLogo, IconType } from "@/components/__legacy__/ui/icons";
import Wallet from "../../../../app/(no-navbar)/onboarding/components/Wallet/Wallet";
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { getAccountMenuItems, loggedInLinks, loggedOutLinks } from "../helpers";
import { AccountMenu } from "./AccountMenu/AccountMenu";
import { AgentActivityDropdown } from "./AgentActivityDropdown/AgentActivityDropdown";
import { LoginButton } from "./LoginButton";
import { MobileNavBar } from "./MobileNavbar/MobileNavBar";
import { NavbarLink } from "./NavbarLink";
import { getAccountMenuItems, loggedInLinks, loggedOutLinks } from "../helpers";
import { useGetV2GetUserProfile } from "@/app/api/__generated__/endpoints/store/store";
import { AgentActivityDropdown } from "./AgentActivityDropdown/AgentActivityDropdown";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { Wallet } from "./Wallet/Wallet";
interface NavbarViewProps {
isLoggedIn: boolean;
@@ -16,6 +18,10 @@ interface NavbarViewProps {
export const NavbarView = ({ isLoggedIn }: NavbarViewProps) => {
const { user } = useSupabase();
const breakpoint = useBreakpoint();
const isSmallScreen = breakpoint === "sm" || breakpoint === "base";
const dynamicMenuItems = getAccountMenuItems(user?.role);
const { data: profile } = useGetV2GetUserProfile({
query: {
select: (res) => (res.status === 200 ? res.data : null),
@@ -23,21 +29,29 @@ export const NavbarView = ({ isLoggedIn }: NavbarViewProps) => {
},
});
const dynamicMenuItems = getAccountMenuItems(user?.role);
return (
<>
<nav className="sticky top-0 z-40 inline-flex h-[60px] w-full items-center border border-white/50 bg-[#f3f4f6]/20 p-3 backdrop-blur-[26px]">
{/* Left section */}
<div className="hidden flex-1 items-center gap-3 md:flex md:gap-5">
{isLoggedIn
? loggedInLinks.map((link) => (
<NavbarLink key={link.name} name={link.name} href={link.href} />
))
: loggedOutLinks.map((link) => (
<NavbarLink key={link.name} name={link.name} href={link.href} />
))}
</div>
{!isSmallScreen ? (
<div className="flex flex-1 items-center gap-3 gap-5">
{isLoggedIn
? loggedInLinks.map((link) => (
<NavbarLink
key={link.name}
name={link.name}
href={link.href}
/>
))
: loggedOutLinks.map((link) => (
<NavbarLink
key={link.name}
name={link.name}
href={link.href}
/>
))}
</div>
) : null}
{/* Centered logo */}
<div className="static h-auto w-[4.5rem] md:absolute md:left-1/2 md:top-1/2 md:w-[5.5rem] md:-translate-x-1/2 md:-translate-y-1/2">
@@ -45,11 +59,11 @@ export const NavbarView = ({ isLoggedIn }: NavbarViewProps) => {
</div>
{/* Right section */}
{isLoggedIn ? (
<div className="hidden flex-1 items-center justify-end gap-4 md:flex">
{isLoggedIn && !isSmallScreen ? (
<div className="flex flex-1 items-center justify-end gap-4">
<div className="flex items-center gap-4">
<AgentActivityDropdown />
{profile && <Wallet />}
{profile && <Wallet key={profile.username} />}
<AccountMenu
userName={profile?.username}
userEmail={profile?.name}
@@ -58,17 +72,17 @@ export const NavbarView = ({ isLoggedIn }: NavbarViewProps) => {
/>
</div>
</div>
) : (
) : !isLoggedIn ? (
<div className="flex w-full items-center justify-end">
<LoginButton />
</div>
)}
) : null}
{/* <ThemeToggle /> */}
</nav>
{/* Mobile Navbar - Adjust positioning */}
<>
{isLoggedIn ? (
<div className="fixed right-0 top-2 z-50 flex items-center gap-0 md:hidden">
{isLoggedIn && isSmallScreen ? (
<div className="fixed right-0 top-2 z-50 flex items-center gap-0">
<Wallet />
<MobileNavBar
userName={profile?.username}

View File

@@ -1,25 +1,25 @@
"use client";
import useCredits from "@/hooks/useCredits";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/__legacy__/ui/popover";
import { X } from "lucide-react";
import { Text } from "@/components/atoms/Text/Text";
import { PopoverClose } from "@radix-ui/react-popover";
import { TaskGroups } from "@/app/(no-navbar)/onboarding/components/Wallet/components/WalletTaskGroups";
import { ScrollArea } from "../../../../../components/__legacy__/ui/scroll-area";
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import * as party from "party-js";
import WalletRefill from "./components/WalletRefill";
import useCredits from "@/hooks/useCredits";
import { OnboardingStep } from "@/lib/autogpt-server-api";
import { cn } from "@/lib/utils";
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { storage, Key as StorageKey } from "@/services/storage/local-storage";
import { WalletIcon } from "@phosphor-icons/react";
import { useGetFlag, Flag } from "@/services/feature-flags/use-get-flag";
import { PopoverClose } from "@radix-ui/react-popover";
import { X } from "lucide-react";
import * as party from "party-js";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ScrollArea } from "../../../../../components/__legacy__/ui/scroll-area";
import WalletRefill from "./components/WalletRefill";
import { TaskGroups } from "./components/WalletTaskGroups";
export interface Task {
id: OnboardingStep;
@@ -39,7 +39,7 @@ export interface TaskGroup {
tasks: Task[];
}
export default function Wallet() {
export function Wallet() {
const { state, updateState } = useOnboarding();
const isPaymentEnabled = useGetFlag(Flag.ENABLE_PLATFORM_PAYMENT);

View File

@@ -1,13 +1,3 @@
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/__legacy__/ui/tabs";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import {
Form,
FormControl,
@@ -16,14 +6,24 @@ import {
FormLabel,
FormMessage,
} from "@/components/__legacy__/ui/form";
import { Input } from "../../../../../../components/__legacy__/ui/input";
import Link from "next/link";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/__legacy__/ui/tabs";
import { Input } from "@/components/atoms/Input/Input";
import {
useToast,
useToastOnFail,
} from "../../../../../../components/molecules/Toast/use-toast";
} from "@/components/molecules/Toast/use-toast";
import useCredits from "@/hooks/useCredits";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
const topUpSchema = z.object({
amount: z
@@ -148,6 +148,8 @@ export default function WalletRefill() {
"mt-2 rounded-3xl border-0 bg-white py-2 pl-6 pr-4 font-sans outline outline-1 outline-zinc-300",
"focus:outline-2 focus:outline-offset-0 focus:outline-violet-700",
)}
label="Amount"
id="amount"
type="number"
step="1"
{...field}
@@ -204,6 +206,8 @@ export default function WalletRefill() {
)}
type="number"
step="1"
label="Refill when balance drops below:"
id="threshold"
{...field}
/>
<span className="absolute left-10 -translate-y-9 text-sm text-zinc-500">
@@ -232,6 +236,8 @@ export default function WalletRefill() {
)}
type="number"
step="1"
label="Add this amount:"
id="refillAmount"
{...field}
/>
<span className="absolute left-10 -translate-y-9 text-sm text-zinc-500">

View File

@@ -1,12 +1,9 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { ChevronDown, Check, BadgeQuestionMark } from "lucide-react";
import { cn } from "@/lib/utils";
import * as party from "party-js";
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
import {
Task,
TaskGroup,
} from "@/app/(no-navbar)/onboarding/components/Wallet/Wallet";
import { BadgeQuestionMark, Check, ChevronDown } from "lucide-react";
import * as party from "party-js";
import { useCallback, useEffect, useRef, useState } from "react";
import { Task, TaskGroup } from "../Wallet";
interface Props {
groups: TaskGroup[];