SubwalletList.tsx raw

   1  import {
   2    CirclePlusIcon,
   3    HelpCircleIcon,
   4    InfoIcon,
   5    ShieldCheckIcon,
   6    SparklesIcon,
   7    TriangleAlertIcon,
   8  } from "lucide-react";
   9  import { useRef, useState } from "react";
  10  import AppHeader from "src/components/AppHeader";
  11  import AppCard from "src/components/connections/AppCard";
  12  import { CustomPagination } from "src/components/CustomPagination";
  13  import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
  14  import FormattedFiatAmount from "src/components/FormattedFiatAmount";
  15  import Loading from "src/components/Loading";
  16  import ResponsiveButton from "src/components/ResponsiveButton";
  17  import ResponsiveLinkButton from "src/components/ResponsiveLinkButton";
  18  import {
  19    Alert,
  20    AlertAction,
  21    AlertDescription,
  22    AlertTitle,
  23  } from "src/components/ui/alert";
  24  import { Button } from "src/components/ui/button";
  25  import {
  26    Card,
  27    CardContent,
  28    CardHeader,
  29    CardTitle,
  30  } from "src/components/ui/card";
  31  import { ExternalLinkButton } from "src/components/ui/custom/external-link-button";
  32  import { LinkButton } from "src/components/ui/custom/link-button";
  33  import { UpgradeDialog } from "src/components/UpgradeDialog";
  34  import { LIST_APPS_LIMIT, MAX_FREE_SUBWALLETS } from "src/constants";
  35  import { useAlbyMe } from "src/hooks/useAlbyMe";
  36  import { useApps } from "src/hooks/useApps";
  37  import { useBalances } from "src/hooks/useBalances";
  38  import { useInfo } from "src/hooks/useInfo";
  39  import { SubwalletIntro } from "src/screens/subwallets/SubwalletIntro";
  40  
  41  export function SubwalletList() {
  42    const { data: info } = useInfo();
  43    const [page, setPage] = useState(1);
  44    const appsListRef = useRef<HTMLDivElement>(null);
  45    const { data: subwalletAppsData } = useApps(
  46      undefined,
  47      page,
  48      {
  49        subWallets: true,
  50      },
  51      "created_at"
  52    );
  53    const { data: albyMe, error: albyMeError } = useAlbyMe();
  54    const { data: balances } = useBalances();
  55  
  56    const handlePageChange = (page: number) => {
  57      setPage(page);
  58      appsListRef.current?.scrollIntoView({
  59        behavior: "smooth",
  60        block: "start",
  61      });
  62    };
  63  
  64    if (
  65      !info ||
  66      !subwalletAppsData ||
  67      !balances ||
  68      (info.albyAccountConnected && !albyMe && !albyMeError)
  69    ) {
  70      return <Loading />;
  71    }
  72  
  73    const subwalletApps = subwalletAppsData.apps;
  74  
  75    if (!subwalletAppsData.totalCount) {
  76      return <SubwalletIntro />;
  77    }
  78  
  79    const subwalletTotalAmountMsat = subwalletAppsData.totalBalanceMsat || 0;
  80    const isSufficientlyBacked =
  81      subwalletTotalAmountMsat <= balances.lightning.totalSpendableMsat;
  82  
  83    return (
  84      <div className="grid gap-3">
  85        <AppHeader
  86          title="Sub-wallets"
  87          pageTitle="Sub-wallets"
  88          description="Create sub-wallets for yourself, friends, family or coworkers"
  89          contentRight={
  90            <>
  91              <ExternalLinkButton
  92                to="https://guides.getalby.com/user-guide/alby-hub/sub-wallets"
  93                variant="outline"
  94                size="icon"
  95              >
  96                <HelpCircleIcon className="size-4" />
  97              </ExternalLinkButton>
  98              {!albyMe?.subscription.plan_code &&
  99              subwalletAppsData.totalCount >= MAX_FREE_SUBWALLETS ? (
 100                <UpgradeDialog>
 101                  <ResponsiveButton icon={CirclePlusIcon} text="New Sub-wallet" />
 102                </UpgradeDialog>
 103              ) : (
 104                <ResponsiveLinkButton
 105                  to="/sub-wallets/new"
 106                  icon={CirclePlusIcon}
 107                  text="New Sub-wallet"
 108                />
 109              )}
 110            </>
 111          }
 112        />
 113  
 114        {!albyMe?.subscription.plan_code &&
 115          subwalletAppsData.totalCount >= MAX_FREE_SUBWALLETS && (
 116            <Alert>
 117              <InfoIcon />
 118              <AlertTitle>Need more Sub-wallets?</AlertTitle>
 119              <AlertDescription>
 120                Upgrade to Pro for unlimited sub-wallets.
 121              </AlertDescription>
 122              <AlertAction>
 123                <UpgradeDialog>
 124                  <Button size="sm">
 125                    <SparklesIcon />
 126                    Upgrade
 127                  </Button>
 128                </UpgradeDialog>
 129              </AlertAction>
 130            </Alert>
 131          )}
 132  
 133        {!isSufficientlyBacked && (
 134          <Alert variant="warning">
 135            <TriangleAlertIcon />
 136            <AlertTitle>
 137              Sub-wallets you manage are insufficiently backed
 138            </AlertTitle>
 139            <AlertDescription className="flex flex-row gap-3">
 140              There's not enough bitcoin in your lightning balance to honor all
 141              balances of sub-wallets under your management. Increase your
 142              lightning balance by opening a channel or review your channel
 143              statuses to back them up again.
 144              <LinkButton to="/wallet/receive" variant="secondary">
 145                Deposit Bitcoin
 146              </LinkButton>
 147            </AlertDescription>
 148          </Alert>
 149        )}
 150  
 151        <div className="flex flex-col sm:flex-row flex-wrap gap-3 slashed-zero">
 152          <Card className="flex flex-1 flex-col">
 153            <CardHeader className="pb-2">
 154              <CardTitle className="text-lg">
 155                Total Balance of Sub-wallets
 156              </CardTitle>
 157            </CardHeader>
 158            <CardContent className="grow">
 159              <div className="mb-1">
 160                <span className="text-2xl font-medium balance sensitive">
 161                  <FormattedBitcoinAmount amountMsat={subwalletTotalAmountMsat} />
 162                </span>
 163              </div>
 164              <FormattedFiatAmount amountSat={subwalletTotalAmountMsat / 1000} />
 165            </CardContent>
 166          </Card>
 167          <Card className="flex flex-1 flex-col">
 168            <CardHeader className="pb-2">
 169              <CardTitle className="text-lg">Number of Sub-wallets</CardTitle>
 170            </CardHeader>
 171            <CardContent className="grow flex flex-col gap-4">
 172              <div className="flex flex-col gap-2">
 173                <span className="text-2xl font-medium">
 174                  {subwalletAppsData.totalCount} /{" "}
 175                  {albyMe?.subscription.plan_code ? "∞" : MAX_FREE_SUBWALLETS}
 176                </span>
 177                {isSufficientlyBacked ? (
 178                  <div className="flex items-center text-positive-foreground text-sm">
 179                    <ShieldCheckIcon className="size-4 mr-2" />
 180                    <span className="text-sm font-medium">Fully backed</span>
 181                  </div>
 182                ) : (
 183                  <div className="flex items-center text-warning-foreground text-sm">
 184                    <TriangleAlertIcon className="size-4 mr-2" />
 185                    <span className="text-sm font-medium">
 186                      Insufficiently backed
 187                    </span>
 188                  </div>
 189                )}
 190              </div>
 191            </CardContent>
 192          </Card>
 193        </div>
 194        <div className="mt-8">
 195          <h3 className="font-semibold text-2xl mb-4">Managed Sub-wallets</h3>
 196          <div
 197            ref={appsListRef}
 198            className="grid grid-cols-1 lg:grid-cols-2 gap-3 items-stretch"
 199          >
 200            {subwalletApps.map((app, index) => (
 201              <AppCard key={index} app={app} />
 202            ))}
 203          </div>
 204        </div>
 205  
 206        <CustomPagination
 207          limit={LIST_APPS_LIMIT}
 208          totalCount={subwalletAppsData.totalCount}
 209          page={page}
 210          handlePageChange={handlePageChange}
 211        />
 212      </div>
 213    );
 214  }
 215