Repository: Significant-Gravitas/AutoGPT
Pull request #9819: feat(frontend): Wallet top-up and auto-refill
Author: login: kcze
Labels: name: size/xl
name: platform/frontend
description: AutoGPT Platform - Front end
name: platform/backend
description: AutoGPT Platform - Back end
Created: 2025-04-15T10:56:13Z | Merged: 2025-04-18T10:06:14Z

### Changes 🏗️

- Add top-up and auto-refill tabs in the Wallet
- Add shadcn `tabs` component
- Disable increase/decrease spinner buttons on number inputs across Platform (moved css from `customnode.css` to `globals.css`

### 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] Incorrect values are detected properly
  - [x] Top-up works
  - [x] Setting auto-refill works




Diff:
diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json
index be7c5b88c8b1..58bfd05d7e0c 100644
--- a/autogpt_platform/frontend/package.json
+++ b/autogpt_platform/frontend/package.json
@@ -42,6 +42,7 @@
     "@radix-ui/react-separator": "^1.1.0",
     "@radix-ui/react-slot": "^1.1.0",
     "@radix-ui/react-switch": "^1.1.1",
+    "@radix-ui/react-tabs": "^1.1.4",
     "@radix-ui/react-toast": "^1.2.5",
     "@radix-ui/react-tooltip": "^1.1.7",
     "@sentry/nextjs": "^9",
diff --git a/autogpt_platform/frontend/src/app/globals.css b/autogpt_platform/frontend/src/app/globals.css
index c4247abe5f94..ae5eb2807675 100644
--- a/autogpt_platform/frontend/src/app/globals.css
+++ b/autogpt_platform/frontend/src/app/globals.css
@@ -144,3 +144,13 @@
     text-wrap: balance;
   }
 }
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+  -webkit-appearance: none;
+  margin: 0;
+}
+
+input[type="number"] {
+  -moz-appearance: textfield;
+}
diff --git a/autogpt_platform/frontend/src/app/profile/(user)/credits/page.tsx b/autogpt_platform/frontend/src/app/profile/(user)/credits/page.tsx
index b3852c246503..132b6d83defa 100644
--- a/autogpt_platform/frontend/src/app/profile/(user)/credits/page.tsx
+++ b/autogpt_platform/frontend/src/app/profile/(user)/credits/page.tsx
@@ -118,8 +118,7 @@ export default function CreditsPage() {
             {topupStatus === "success" && (
               <span className="text-green-500">
                 Your payment was successful. Your credits will be updated
-                shortly. You can click the refresh icon 🔄 in case it is not
-                updated.
+                shortly. Try refreshing the page in case it is not updated.
               </span>
             )}
             {topupStatus === "cancel" && (
diff --git a/autogpt_platform/frontend/src/components/agptui/Wallet.tsx b/autogpt_platform/frontend/src/components/agptui/Wallet.tsx
index d4111365e7b7..9b588d48af35 100644
--- a/autogpt_platform/frontend/src/components/agptui/Wallet.tsx
+++ b/autogpt_platform/frontend/src/components/agptui/Wallet.tsx
@@ -14,6 +14,7 @@ import { useOnboarding } from "../onboarding/onboarding-provider";
 import { useCallback, useEffect, useRef } from "react";
 import { cn } from "@/lib/utils";
 import * as party from "party-js";
+import WalletRefill from "./WalletRefill";
 
 export default function Wallet() {
   const { credits, formatCredits, fetchCredits } = useCredits({
@@ -86,26 +87,31 @@ export default function Wallet() {
           "rounded-xl border-zinc-200 bg-zinc-50 shadow-[0_3px_3px] shadow-zinc-300",
         )}
       >
-        <div>
-          <div className="mx-1 flex items-center justify-between border-b border-zinc-300 pb-2">
-            <span className="font-poppins font-medium text-zinc-900">
-              Your wallet
-            </span>
-            <div className="flex items-center font-inter text-sm font-semibold text-violet-700">
-              <div className="rounded-lg bg-violet-100 px-3 py-2">
-                Wallet{" "}
-                <span className="font-semibold">{formatCredits(credits)}</span>
-              </div>
-              <PopoverClose>
-                <X className="ml-[2.8rem] h-5 w-5 text-zinc-800 hover:text-foreground" />
-              </PopoverClose>
+        {/* Header */}
+        <div className="mx-1 flex items-center justify-between border-b border-zinc-300 pb-2">
+          <span className="font-poppins font-medium text-zinc-900">
+            Your wallet
+          </span>
+          <div className="flex items-center font-inter text-sm font-semibold text-violet-700">
+            <div className="rounded-lg bg-violet-100 px-3 py-2">
+              Wallet{" "}
+              <span className="font-semibold">{formatCredits(credits)}</span>
             </div>
+            <PopoverClose>
+              <X className="ml-[2.8rem] h-5 w-5 text-zinc-800 hover:text-foreground" />
+            </PopoverClose>
           </div>
-          <p className="mx-1 mt-3 font-inter text-xs text-muted-foreground text-zinc-400">
+        </div>
+        <ScrollArea className="max-h-[85vh] overflow-y-auto">
+          {/* Top ups */}
+          <WalletRefill />
+          {/* Tasks */}
+          <p className="mx-1 mt-4 font-sans text-xs font-medium text-violet-700">
+            Onboarding tasks
+          </p>
+          <p className="mx-1 my-1 font-sans text-xs font-normal text-zinc-500">
             Complete the following tasks to earn more credits!
           </p>
-        </div>
-        <ScrollArea className="max-h-[80vh] overflow-y-auto">
           <TaskGroups />
         </ScrollArea>
       </PopoverContent>
diff --git a/autogpt_platform/frontend/src/components/agptui/WalletRefill.tsx b/autogpt_platform/frontend/src/components/agptui/WalletRefill.tsx
new file mode 100644
index 000000000000..756df29bc033
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/agptui/WalletRefill.tsx
@@ -0,0 +1,265 @@
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/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,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormMessage,
+} from "@/components/ui/form";
+import { Input } from "../ui/input";
+import Link from "next/link";
+import { useToast, useToastOnFail } from "../ui/use-toast";
+import useCredits from "@/hooks/useCredits";
+import { useCallback, useEffect, useState } from "react";
+
+const topUpSchema = z.object({
+  amount: z
+    .number({ coerce: true, invalid_type_error: "Enter top-up amount" })
+    .min(5, "Top-ups start at $5. Please enter a higher amount."),
+});
+
+const autoRefillSchema = z
+  .object({
+    threshold: z
+      .number({ coerce: true, invalid_type_error: "Enter min. balance" })
+      .min(
+        5,
+        "Looks like your balance is too low for auto-refill. Try $5 or more.",
+      ),
+    refillAmount: z
+      .number({ coerce: true, invalid_type_error: "Enter top-up amount" })
+      .min(5, "Top-ups start at $5. Please enter a higher amount."),
+  })
+  .refine((data) => data.refillAmount >= data.threshold, {
+    message:
+      "Your refill amount must be equal to or greater than the balance you entered above.",
+    path: ["refillAmount"],
+  });
+
+export default function WalletRefill() {
+  const { toast } = useToast();
+  const toastOnFail = useToastOnFail();
+  const { requestTopUp, autoTopUpConfig, updateAutoTopUpConfig } = useCredits({
+    fetchInitialAutoTopUpConfig: true,
+  });
+  const [isLoading, setIsLoading] = useState(false);
+
+  const topUpForm = useForm<z.infer<typeof topUpSchema>>({
+    resolver: zodResolver(topUpSchema),
+  });
+  const autoRefillForm = useForm<z.infer<typeof autoRefillSchema>>({
+    resolver: zodResolver(autoRefillSchema),
+  });
+
+  console.log("autoRefillForm");
+
+  // Pre-fill the auto-refill form with existing values
+  useEffect(() => {
+    const values = autoRefillForm.getValues();
+    if (
+      autoTopUpConfig &&
+      autoTopUpConfig.amount > 0 &&
+      autoTopUpConfig.threshold > 0 &&
+      !autoRefillForm.getFieldState("threshold").isTouched &&
+      !autoRefillForm.getFieldState("refillAmount").isTouched
+    ) {
+      autoRefillForm.setValue("threshold", autoTopUpConfig.threshold / 100);
+      autoRefillForm.setValue("refillAmount", autoTopUpConfig.amount / 100);
+    }
+  }, [autoTopUpConfig, autoRefillForm]);
+
+  const submitTopUp = useCallback(
+    async (data: z.infer<typeof topUpSchema>) => {
+      setIsLoading(true);
+      await requestTopUp(data.amount * 100).catch(
+        toastOnFail("request top-up"),
+      );
+      setIsLoading(false);
+    },
+    [requestTopUp, toastOnFail],
+  );
+
+  const submitAutoTopUpConfig = useCallback(
+    async (data: z.infer<typeof autoRefillSchema>) => {
+      setIsLoading(true);
+      await updateAutoTopUpConfig(data.refillAmount * 100, data.threshold * 100)
+        .then(() => {
+          toast({ title: "Auto top-up config updated! 🎉" });
+        })
+        .catch(toastOnFail("update auto top-up config"));
+      setIsLoading(false);
+    },
+    [updateAutoTopUpConfig, toast, toastOnFail],
+  );
+
+  return (
+    <div className="mx-1 border-b border-zinc-300">
+      <p className="mx-0 mt-4 font-sans text-xs font-medium text-violet-700">
+        Add credits to your balance
+      </p>
+      <p className="mx-0 my-1 font-sans text-xs font-normal text-zinc-500">
+        Choose a one-time top-up or set up automatic refills
+      </p>
+      <Tabs
+        defaultValue="top-up"
+        className="mb-6 mt-4 flex w-full flex-col items-center"
+      >
+        <TabsList className="mx-auto">
+          <TabsTrigger value="top-up">One-time top up</TabsTrigger>
+          <TabsTrigger value="auto-refill">Auto-refill</TabsTrigger>
+        </TabsList>
+        <div className="mt-4 w-full rounded-lg px-5 outline outline-1 outline-offset-2 outline-zinc-200">
+          <TabsContent value="top-up" className="flex flex-col">
+            <div className="mt-2 justify-start font-sans text-sm font-medium leading-snug text-zinc-900">
+              One-time top-up
+            </div>
+            <div className="mt-1 justify-start font-sans text-xs font-normal leading-tight text-zinc-500">
+              Enter an amount (min. $5) and add credits instantly.
+            </div>
+            <Form {...topUpForm}>
+              <form onSubmit={topUpForm.handleSubmit(submitTopUp)}>
+                <FormField
+                  control={topUpForm.control}
+                  name="amount"
+                  render={({ field }) => (
+                    <FormItem className="mb-6 mt-4">
+                      <FormLabel className="font-sans text-sm font-medium leading-snug text-zinc-800">
+                        Amount
+                      </FormLabel>
+                      <FormControl>
+                        <>
+                          <Input
+                            className={cn(
+                              "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",
+                            )}
+                            type="number"
+                            step="1"
+                            {...field}
+                          />
+                          <span className="absolute left-10 -translate-y-9 text-sm text-zinc-500">
+                            $
+                          </span>
+                        </>
+                      </FormControl>
+                      <FormMessage className="mt-2 font-sans text-xs font-normal leading-tight" />
+                    </FormItem>
+                  )}
+                />
+                <button
+                  className={cn(
+                    "mb-2 inline-flex h-10 w-24 items-center justify-center rounded-3xl bg-zinc-800 px-4 py-2",
+                    "font-sans text-sm font-medium leading-snug text-white",
+                    "transition-colors duration-200 hover:bg-zinc-700 disabled:bg-zinc-500",
+                  )}
+                  type="submit"
+                  disabled={isLoading}
+                >
+                  Top up
+                </button>
+              </form>
+            </Form>
+          </TabsContent>
+          <TabsContent value="auto-refill" className="flex flex-col">
+            <div className="justify-start font-sans text-sm font-medium leading-snug text-zinc-900">
+              Auto-refill
+            </div>
+            <div className="mt-1 justify-start font-sans text-xs font-normal leading-tight text-zinc-500">
+              Choose a one-time top-up or set up automatic refills.
+            </div>
+
+            <Form {...autoRefillForm}>
+              <form
+                onSubmit={autoRefillForm.handleSubmit(submitAutoTopUpConfig)}
+              >
+                <FormField
+                  control={autoRefillForm.control}
+                  name="threshold"
+                  render={({ field }) => (
+                    <FormItem className="mb-6 mt-4">
+                      <FormLabel className="font-sans text-sm font-medium leading-snug text-zinc-800">
+                        Refill when balance drops below:
+                      </FormLabel>
+                      <FormControl>
+                        <>
+                          <Input
+                            className={cn(
+                              "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",
+                            )}
+                            type="number"
+                            step="1"
+                            {...field}
+                          />
+                          <span className="absolute left-10 -translate-y-9 text-sm text-zinc-500">
+                            $
+                          </span>
+                        </>
+                      </FormControl>
+                      <FormMessage className="mt-2 font-sans text-xs font-normal leading-tight" />
+                    </FormItem>
+                  )}
+                />
+                <FormField
+                  control={autoRefillForm.control}
+                  name="refillAmount"
+                  render={({ field }) => (
+                    <FormItem className="mb-6">
+                      <FormLabel className="font-sans text-sm font-medium leading-snug text-zinc-800">
+                        Add this amount:
+                      </FormLabel>
+                      <FormControl>
+                        <>
+                          <Input
+                            className={cn(
+                              "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",
+                            )}
+                            type="number"
+                            step="1"
+                            {...field}
+                          />
+                          <span className="absolute left-10 -translate-y-9 text-sm text-zinc-500">
+                            $
+                          </span>
+                        </>
+                      </FormControl>
+                      <FormMessage className="mt-2 font-sans text-xs font-normal leading-tight" />
+                    </FormItem>
+                  )}
+                />
+                <button
+                  className={cn(
+                    "mb-4 inline-flex h-10 w-40 items-center justify-center rounded-3xl bg-zinc-800 px-4 py-2",
+                    "font-sans text-sm font-medium leading-snug text-white",
+                    "transition-colors duration-200 hover:bg-zinc-700 disabled:bg-zinc-500",
+                  )}
+                  type="submit"
+                  disabled={isLoading}
+                >
+                  Enable Auto-refill
+                </button>
+              </form>
+            </Form>
+          </TabsContent>
+          <div className="mb-3 justify-start font-sans text-xs font-normal leading-tight">
+            <span className="text-zinc-500">
+              To update your billing details, head to{" "}
+            </span>
+            <Link
+              href="/profile/credits"
+              className="cursor-pointer text-zinc-800 underline"
+            >
+              Billing settings
+            </Link>
+          </div>
+        </div>
+      </Tabs>
+    </div>
+  );
+}
diff --git a/autogpt_platform/frontend/src/components/customnode.css b/autogpt_platform/frontend/src/components/customnode.css
index 8e4ed0c87df2..e12a10d0927b 100644
--- a/autogpt_platform/frontend/src/components/customnode.css
+++ b/autogpt_platform/frontend/src/components/customnode.css
@@ -147,13 +147,3 @@
 .custom-switch {
   padding-left: 2px;
 }
-
-input[type="number"]::-webkit-outer-spin-button,
-input[type="number"]::-webkit-inner-spin-button {
-  -webkit-appearance: none;
-  margin: 0;
-}
-
-input[type="number"] {
-  -moz-appearance: textfield;
-}
diff --git a/autogpt_platform/frontend/src/components/ui/tabs.tsx b/autogpt_platform/frontend/src/components/ui/tabs.tsx
new file mode 100644
index 000000000000..2090bc42e59d
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/ui/tabs.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+
+import { cn } from "@/lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.List
+    ref={ref}
+    className={cn(
+      "inline-flex h-10 w-80 items-center justify-center rounded-3xl bg-gray-100 p-[5px] text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400",
+      className,
+    )}
+    {...props}
+  />
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex flex-1 items-start justify-center gap-2.5 whitespace-nowrap rounded-2xl px-3 py-2 text-center font-sans text-xs font-medium leading-tight text-gray-500 ring-offset-white transition-all",
+      "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+      "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-subtle",
+      className,
+    )}
+    {...props}
+  />
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Content
+    ref={ref}
+    className={cn(
+      "mt-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300",
+      className,
+    )}
+    {...props}
+  />
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
+
+<div className="inline-flex h-10 w-80 items-start justify-start rounded-3xl bg-gray-100 p-[5px]">
+  <div
+    data-state="selected"
+    className="flex flex-1 items-start justify-start gap-2.5 rounded-2xl bg-background px-3 py-1.5 shadow-subtle"
+  >
+    <div className="flex-1 justify-start text-center font-['Geist'] text-xs font-medium leading-tight text-foreground">
+      \ One-time top up
+    </div>
+  </div>
+  <div
+    data-state="unselected"
+    className="flex flex-1 items-start justify-start gap-2.5 rounded-sm px-3 py-1.5"
+  >
+    <div className="flex-1 justify-start text-center font-['Geist'] text-xs font-medium leading-tight text-gray-500">
+      Auto-refill
+    </div>
+  </div>
+</div>;
diff --git a/autogpt_platform/frontend/src/hooks/useCredits.ts b/autogpt_platform/frontend/src/hooks/useCredits.ts
index a4836cd19b11..43d7423e4c87 100644
--- a/autogpt_platform/frontend/src/hooks/useCredits.ts
+++ b/autogpt_platform/frontend/src/hooks/useCredits.ts
@@ -131,9 +131,7 @@ export default function useCredits({
     }
     const value = Math.abs(credit);
     const sign = credit < 0 ? "-" : "";
-    const precision =
-      2 - (value % 100 === 0 ? 1 : 0) - (value % 10 === 0 ? 1 : 0);
-    return `${sign}$${(value / 100).toFixed(precision)}`;
+    return `${sign}$${(value / 100).toFixed(2)}`;
   }, []);
 
   return {
diff --git a/autogpt_platform/frontend/tailwind.config.ts b/autogpt_platform/frontend/tailwind.config.ts
index c5e7d3aae976..49d487c6a8c5 100644
--- a/autogpt_platform/frontend/tailwind.config.ts
+++ b/autogpt_platform/frontend/tailwind.config.ts
@@ -117,6 +117,9 @@ const config = {
         // Add a full radius for pill-shaped buttons
         full: "9999px",
       },
+      boxShadow: {
+        subtle: "0px 1px 2px 0px rgba(0,0,0,0.05)",
+      },
       keyframes: {
         "accordion-down": {
           from: { height: "0" },
diff --git a/autogpt_platform/frontend/yarn.lock b/autogpt_platform/frontend/yarn.lock
index 249f0bce6202..44eaf93c4c84 100644
--- a/autogpt_platform/frontend/yarn.lock
+++ b/autogpt_platform/frontend/yarn.lock
@@ -2158,6 +2158,11 @@
   resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.1.tgz#fc169732d755c7fbad33ba8d0cd7fd10c90dc8e3"
   integrity sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==
 
+"@radix-ui/primitive@1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.2.tgz#83f415c4425f21e3d27914c12b3272a32e3dae65"
+  integrity sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==
+
 "@radix-ui/react-alert-dialog@^1.1.5":
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.6.tgz#52187fdaa5110ed6749e75974e90c3505f788c5d"
@@ -2225,11 +2230,26 @@
     "@radix-ui/react-primitive" "2.0.2"
     "@radix-ui/react-slot" "1.1.2"
 
+"@radix-ui/react-collection@1.1.3":
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.3.tgz#cfd46dcea5a8ab064d91798feeb46faba4032930"
+  integrity sha512-mM2pxoQw5HJ49rkzwOs7Y6J4oYH22wS8BfK2/bBxROlI4xuR0c4jEenQP63LlTlDkO6Buj2Vt+QYAYcOgqtrXA==
+  dependencies:
+    "@radix-ui/react-compose-refs" "1.1.2"
+    "@radix-ui/react-context" "1.1.2"
+    "@radix-ui/react-primitive" "2.0.3"
+    "@radix-ui/react-slot" "1.2.0"
+
 "@radix-ui/react-compose-refs@1.1.1":
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec"
   integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==
 
+"@radix-ui/react-compose-refs@1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
+  integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
+
 "@radix-ui/react-context-menu@^2.2.5":
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.6.tgz#752fd1d91f92bba287ef2b558770f4ca7d74523e"
@@ -2247,6 +2267,11 @@
   resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a"
   integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==
 
+"@radix-ui/react-context@1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
+  integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
+
 "@radix-ui/react-dialog@1.1.6", "@radix-ui/react-dialog@^1.1.2":
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz#65b4465e99ad900f28a98eed9a94bb21ec644bf7"
@@ -2272,6 +2297,11 @@
   resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc"
   integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==
 
+"@radix-ui/react-direction@1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz#39e5a5769e676c753204b792fbe6cf508e550a14"
+  integrity sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==
+
 "@radix-ui/react-dismissable-layer@1.1.5":
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz#96dde2be078c694a621e55e047406c58cd5fe774"
@@ -2322,6 +2352,13 @@
   dependencies:
     "@radix-ui/react-use-layout-effect" "1.1.0"
 
+"@radix-ui/react-id@1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.1.tgz#1404002e79a03fe062b7e3864aa01e24bd1471f7"
+  integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==
+  dependencies:
+    "@radix-ui/react-use-layout-effect" "1.1.1"
+
 "@radix-ui/react-label@^2.1.0":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.1.2.tgz#994a5d815c2ff46e151410ae4e301f1b639f9971"
@@ -2406,6 +2443,14 @@
     "@radix-ui/react-compose-refs" "1.1.1"
     "@radix-ui/react-use-layout-effect" "1.1.0"
 
+"@radix-ui/react-presence@1.1.3":
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.3.tgz#ce3400caec9892ceb862f96ddaa2add080c09b90"
+  integrity sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==
+  dependencies:
+    "@radix-ui/react-compose-refs" "1.1.2"
+    "@radix-ui/react-use-layout-effect" "1.1.1"
+
 "@radix-ui/react-primitive@2.0.2", "@radix-ui/react-primitive@^2.0.0":
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz#ac8b7854d87b0d7af388d058268d9a7eb64ca8ef"
@@ -2413,6 +2458,13 @@
   dependencies:
     "@radix-ui/react-slot" "1.1.2"
 
+"@radix-ui/react-primitive@2.0.3":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.3.tgz#13c654dc4754558870a9c769f6febe5980a1bad8"
+  integrity sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==
+  dependencies:
+    "@radix-ui/react-slot" "1.2.0"
+
 "@radix-ui/react-radio-group@^1.2.1":
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz#f60f58179cce716ccdb5c3d53a2eca97e4efd520"
@@ -2444,6 +2496,21 @@
     "@radix-ui/react-use-callback-ref" "1.1.0"
     "@radix-ui/react-use-controllable-state" "1.1.0"
 
+"@radix-ui/react-roving-focus@1.1.3":
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.3.tgz#c992b9d30c795f5f5a668853db8f4a6e07b7284d"
+  integrity sha512-ufbpLUjZiOg4iYgb2hQrWXEPYX6jOLBbR27bDyAff5GYMRrCzcze8lukjuXVUQvJ6HZe8+oL+hhswDcjmcgVyg==
+  dependencies:
+    "@radix-ui/primitive" "1.1.2"
+    "@radix-ui/react-collection" "1.1.3"
+    "@radix-ui/react-compose-refs" "1.1.2"
+    "@radix-ui/react-context" "1.1.2"
+    "@radix-ui/react-direction" "1.1.1"
+    "@radix-ui/react-id" "1.1.1"
+    "@radix-ui/react-primitive" "2.0.3"
+    "@radix-ui/react-use-callback-ref" "1.1.1"
+    "@radix-ui/react-use-controllable-state" "1.1.1"
+
 "@radix-ui/react-scroll-area@^1.2.1":
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz#6a9a7897add739ce84b517796ee345d495893d3f"
@@ -2500,6 +2567,13 @@
   dependencies:
     "@radix-ui/react-compose-refs" "1.1.1"
 
+"@radix-ui/react-slot@1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.0.tgz#57727fc186ddb40724ccfbe294e1a351d92462ba"
+  integrity sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==
+  dependencies:
+    "@radix-ui/react-compose-refs" "1.1.2"
+
 "@radix-ui/react-switch@^1.1.1":
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.1.3.tgz#cb6386909d1d3f65a2b81a3b15da8c91d18f49b0"
@@ -2513,6 +2587,20 @@
     "@radix-ui/react-use-previous" "1.1.0"
     "@radix-ui/react-use-size" "1.1.0"
 
+"@radix-ui/react-tabs@^1.1.4":
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.4.tgz#2e43f3ef3450143281e7c1491da1e5d7941b9826"
+  integrity sha512-fuHMHWSf5SRhXke+DbHXj2wVMo+ghVH30vhX3XVacdXqDl+J4XWafMIGOOER861QpBx1jxgwKXL2dQnfrsd8MQ==
+  dependencies:
+    "@radix-ui/primitive" "1.1.2"
+    "@radix-ui/react-context" "1.1.2"
+    "@radix-ui/react-direction" "1.1.1"
+    "@radix-ui/react-id" "1.1.1"
+    "@radix-ui/react-presence" "1.1.3"
+    "@radix-ui/react-primitive" "2.0.3"
+    "@radix-ui/react-roving-focus" "1.1.3"
+    "@radix-ui/react-use-controllable-state" "1.1.1"
+
 "@radix-ui/react-toast@^1.2.5":
   version "1.2.6"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.2.6.tgz#f8d4bb2217851d221d700ac48fbe866b35023361"
@@ -2554,6 +2642,11 @@
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
   integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==
 
+"@radix-ui/react-use-callback-ref@1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz#62a4dba8b3255fdc5cc7787faeac1c6e4cc58d40"
+  integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==
+
 "@radix-ui/react-use-controllable-state@1.1.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0"
@@ -2561,6 +2654,13 @@
   dependencies:
     "@radix-ui/react-use-callback-ref" "1.1.0"
 
+"@radix-ui/react-use-controllable-state@1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.1.tgz#ec9c572072a6f269df7435c1652fbeebabe0f0c1"
+  integrity sha512-YnEXIy8/ga01Y1PN0VfaNH//MhA91JlEGVBDxDzROqwrAtG5Yr2QGEPz8A/rJA3C7ZAHryOYGaUv8fLSW2H/mg==
+  dependencies:
+    "@radix-ui/react-use-callback-ref" "1.1.1"
+
 "@radix-ui/react-use-escape-keydown@1.1.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754"
@@ -2573,6 +2673,11 @@
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
   integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
 
+"@radix-ui/react-use-layout-effect@1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e"
+  integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
+
 "@radix-ui/react-use-previous@1.1.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c"


Reviews:
user: login: majdyz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T15:02:44Z
user: login: majdyz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T15:03:06Z
user: login: majdyz
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T15:03:14Z
user: login: kcze
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T08:40:44Z
user: login: kcze
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T08:48:36Z
user: login: majdyz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T09:37:01Z

Review comments:
diff_hunk: @@ -73,7 +74,7 @@ export default function Wallet() {
         >
           Wallet{" "}
           <span className="text-sm font-semibold">
-            {formatCredits(credits)}
+            ${credits ? (credits / 100).toFixed(2) : "-"}
path: autogpt_platform/frontend/src/components/agptui/Wallet.tsx
user: login: majdyz
body: Why do we change it? and why don't we change formatCredits instead ?
created_at: 2025-04-17T15:02:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,76 @@
+"use client";
+
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+
+import { cn } from "@/lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.List
+    ref={ref}
+    className={cn(
+      "inline-flex h-10 w-80 items-center justify-center rounded-3xl bg-gray-100 p-[5px] text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400",
+      className,
+    )}
+    {...props}
+  />
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex flex-1 items-start justify-center gap-2.5 whitespace-nowrap rounded-2xl px-3 py-2 text-center font-sans text-xs font-medium leading-tight text-gray-500 ring-offset-white transition-all",
+      "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+      "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-[0px_1px_2px_0px_rgba(0,0,0,0.05)]",
path: autogpt_platform/frontend/src/components/ui/tabs.tsx
user: login: majdyz
body: The shadow might need some alias :D
created_at: 2025-04-17T15:03:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,7 +74,7 @@ export default function Wallet() {
         >
           Wallet{" "}
           <span className="text-sm font-semibold">
-            {formatCredits(credits)}
+            ${credits ? (credits / 100).toFixed(2) : "-"}
path: autogpt_platform/frontend/src/components/agptui/Wallet.tsx
user: login: kcze
body: right, I updated `formatCredits` and used it instead
created_at: 2025-04-18T08:40:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,76 @@
+"use client";
+
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+
+import { cn } from "@/lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.List
+    ref={ref}
+    className={cn(
+      "inline-flex h-10 w-80 items-center justify-center rounded-3xl bg-gray-100 p-[5px] text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400",
+      className,
+    )}
+    {...props}
+  />
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex flex-1 items-start justify-center gap-2.5 whitespace-nowrap rounded-2xl px-3 py-2 text-center font-sans text-xs font-medium leading-tight text-gray-500 ring-offset-white transition-all",
+      "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+      "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-[0px_1px_2px_0px_rgba(0,0,0,0.05)]",
path: autogpt_platform/frontend/src/components/ui/tabs.tsx
user: login: kcze
body: I aliased it `shadow-subtle`!
created_at: 2025-04-18T08:48:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-04-15T10:56:30Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *auto-gpt-docs-dev* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 4e3983619c74484ed31ecb0df442dacabbad8cea |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/auto-gpt-docs-dev/deploys/680211cf6dafde00081c57ba |
author_association: NONE
user: login: deepsource-io[bot]
created_at: 2025-04-15T10:56:31Z
body: <div><p>Here's the code health analysis summary for commits <code>8ea6432..4e39836</code>. <a href="https://app.deepsource.com/gh/Significant-Gravitas/AutoGPT/run/7610aa10-7ebc-4875-a17f-90a50f656ff8/">View details on DeepSource</a>&nbsp;↗.</p><h3>Analysis Summary</h3><table><thead><tr><th>Analyzer</th><th>Status</th><th>Summary</th><th>Link</th></tr></thead><tbody><tr><td><img src="https://static.deepsource.com/analyzer_logos/javascript.svg" alt="DeepSource JavaScript Logo" width="16px" height="16px"/><strong>JavaScript</strong></td><td><span>✅&nbsp;</span><span>Success</span></td><td><div>❗ 9 occurences introduced</div><div>🎯 1 occurence resolved</div></td><td><a href="https://app.deepsource.com/gh/Significant-Gravitas/AutoGPT/run/7610aa10-7ebc-4875-a17f-90a50f656ff8/javascript/">View Check</a>&nbsp;↗</td></tr><tr><td><img src="https://static.deepsource.com/analyzer_logos/python.svg" alt="DeepSource Python Logo" width="16px" height="16px"/><strong>Python</strong></td><td><span>✅&nbsp;</span><span>Success</span></td><td></td><td><a href="https://app.deepsource.com/gh/Significant-Gravitas/AutoGPT/run/7610aa10-7ebc-4875-a17f-90a50f656ff8/python/">View Check</a>&nbsp;↗</td></tr></tbody></table><hr/><blockquote><div>💡 If you’re a repository administrator, you can configure the quality gates from the <a href="https://app.deepsource.com/gh/Significant-Gravitas/AutoGPT/settings/reporting">settings</a>.</div></blockquote></div>

author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-15T10:56:47Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *auto-gpt-docs* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 4e3983619c74484ed31ecb0df442dacabbad8cea |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/auto-gpt-docs/deploys/680211cfa5e5d40008302dd4 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-9819--auto-gpt-docs.netlify.app](https://deploy-preview-9819--auto-gpt-docs.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTk4MTktLWF1dG8tZ3B0LWRvY3MubmV0bGlmeS5hcHAifQ.WmbRNXePOSnilc_IcWq0hNVEamO9Mq8ETGsibXjdUqA)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [auto-gpt-docs Preview](https://deploy-preview-9819--auto-gpt-docs.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/auto-gpt-docs/configuration/notifications#deploy-webhooks)._
author_association: NONE