SubwalletCreated.tsx raw

   1  import {
   2    AlertCircleIcon,
   3    AlertTriangleIcon,
   4    CopyIcon,
   5    ExternalLinkIcon,
   6    MonitorIcon,
   7    RadioIcon,
   8    SmartphoneIcon,
   9    TriangleAlertIcon,
  10    UserIcon,
  11    ZapIcon,
  12  } from "lucide-react";
  13  import React from "react";
  14  import { useLocation, useNavigate } from "react-router";
  15  import AppHeader from "src/components/AppHeader";
  16  import ExternalLink from "src/components/ExternalLink";
  17  import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
  18  import { AppleIcon } from "src/components/icons/Apple";
  19  import { PlayStoreIcon } from "src/components/icons/PlayStore";
  20  import { ZapStoreIcon } from "src/components/icons/ZapStore";
  21  import { IsolatedAppTopupDialog } from "src/components/IsolatedAppTopupDialog";
  22  import QRCode from "src/components/QRCode";
  23  import {
  24    Accordion,
  25    AccordionContent,
  26    AccordionItem,
  27    AccordionTrigger,
  28  } from "src/components/ui/accordion";
  29  import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert";
  30  import { Button } from "src/components/ui/button";
  31  import {
  32    Card,
  33    CardContent,
  34    CardDescription,
  35    CardFooter,
  36    CardHeader,
  37    CardTitle,
  38  } from "src/components/ui/card";
  39  import { ExternalLinkButton } from "src/components/ui/custom/external-link-button";
  40  import { InputWithAdornment } from "src/components/ui/custom/input-with-adornment";
  41  import { LinkButton } from "src/components/ui/custom/link-button";
  42  import { LoadingButton } from "src/components/ui/custom/loading-button";
  43  import { Input } from "src/components/ui/input";
  44  import { Label } from "src/components/ui/label";
  45  import {
  46    Popover,
  47    PopoverContent,
  48    PopoverTrigger,
  49  } from "src/components/ui/popover";
  50  import { Textarea } from "src/components/ui/textarea";
  51  import { UpgradeDialog } from "src/components/UpgradeDialog";
  52  import { useAlbyMe } from "src/hooks/useAlbyMe";
  53  import { useApp } from "src/hooks/useApp";
  54  import { useCreateLightningAddress } from "src/hooks/useCreateLightningAddress";
  55  import { useNodeConnectionInfo } from "src/hooks/useNodeConnectionInfo";
  56  import { copyToClipboard } from "src/lib/clipboard";
  57  import { ConnectAppCard } from "src/screens/apps/ConnectAppCard";
  58  import { CreateAppResponse } from "src/types";
  59  
  60  export function SubwalletCreated() {
  61    const { data: nodeConnectionInfo } = useNodeConnectionInfo();
  62  
  63    const { state } = useLocation();
  64    const navigate = useNavigate();
  65    const createAppResponse = state as CreateAppResponse | undefined;
  66    const { data: app } = useApp(createAppResponse?.id, true);
  67    const [intendedLightningAddress, setIntendedLightningAddress] =
  68      React.useState(
  69        createAppResponse?.name
  70          .toLowerCase()
  71          .trim()
  72          .replace(/\s+/g, "_")
  73          .replace(/[^a-z0-9_]/g, "") || ""
  74      );
  75  
  76    const { data: albyMe } = useAlbyMe();
  77  
  78    const { createLightningAddress, creatingLightningAddress } =
  79      useCreateLightningAddress(createAppResponse?.id);
  80  
  81    const [step, setStep] = React.useState(1);
  82  
  83    if (!createAppResponse?.pairingUri) {
  84      navigate("/");
  85      return null;
  86    }
  87  
  88    const name = createAppResponse.name;
  89    let connectionSecret = createAppResponse.pairingUri;
  90    if (app?.metadata?.lud16) {
  91      connectionSecret += `&lud16=${app.metadata.lud16}`;
  92    }
  93  
  94    const albyAccountUrl = `https://getalby.com/nwc/new#${connectionSecret}`;
  95    const valueTag = `<podcast:value type="lightning" method="keysend">
  96      <podcast:valueRecipient name="${name}" type="node" address="${nodeConnectionInfo?.pubkey}" customKey="696969"  customValue="${app?.id}" split="100"/>
  97    </podcast:value>`;
  98  
  99    return (
 100      <div className="grid gap-5">
 101        <AppHeader
 102          pageTitle={`Connect ${name}`}
 103          title={`Connect ${name}`}
 104          description=""
 105        />
 106        <div className="max-w-lg">
 107          <div className="flex flex-col col-span-3 gap-5 items-start">
 108            {step === 1 && app && (
 109              <div className="grid gap-5">
 110                <div>
 111                  Configure this sub-wallet by creating a lightning address and
 112                  topping it up. That way it's ready to use as soon as it's
 113                  connected.
 114                </div>
 115                <div className="grid gap-3">
 116                  {!app.metadata?.lud16 && (
 117                    <Card>
 118                      <CardHeader>
 119                        <CardTitle>Lightning address</CardTitle>
 120                        <CardDescription>
 121                          Create a lightning address for this sub-wallet
 122                        </CardDescription>
 123                      </CardHeader>
 124                      <CardContent>
 125                        <InputWithAdornment
 126                          type="text"
 127                          value={intendedLightningAddress}
 128                          onChange={(e) =>
 129                            setIntendedLightningAddress(e.target.value)
 130                          }
 131                          required
 132                          autoComplete="off"
 133                          endAdornment={
 134                            <span className="mr-1 text-muted-foreground text-xs">
 135                              @getalby.com
 136                            </span>
 137                          }
 138                        />
 139                      </CardContent>
 140                      <CardFooter className="flex flex-row justify-end">
 141                        {!albyMe?.subscription.plan_code ? (
 142                          <UpgradeDialog>
 143                            <Button size="sm" variant="secondary">
 144                              Create Lightning Address
 145                            </Button>
 146                          </UpgradeDialog>
 147                        ) : (
 148                          <LoadingButton
 149                            loading={creatingLightningAddress}
 150                            onClick={() =>
 151                              createLightningAddress(intendedLightningAddress)
 152                            }
 153                            size="sm"
 154                            variant="secondary"
 155                          >
 156                            Create Lightning Address
 157                          </LoadingButton>
 158                        )}
 159                      </CardFooter>
 160                    </Card>
 161                  )}
 162                  {app.metadata?.lud16 && (
 163                    <Card>
 164                      <CardHeader>
 165                        <CardTitle>Lightning address</CardTitle>
 166                        <CardDescription>
 167                          Your lightning address for this sub-account
 168                        </CardDescription>
 169                      </CardHeader>
 170                      <CardContent>
 171                        <p className="font-semibold">{app.metadata.lud16}</p>
 172                      </CardContent>
 173                      <CardFooter className="flex flex-row justify-end">
 174                        <Button
 175                          onClick={() => {
 176                            if (app.metadata?.lud16) {
 177                              copyToClipboard(app.metadata.lud16);
 178                            }
 179                          }}
 180                          size="sm"
 181                          variant="secondary"
 182                        >
 183                          Copy
 184                        </Button>
 185                      </CardFooter>
 186                    </Card>
 187                  )}
 188                  <Card>
 189                    <CardHeader>
 190                      <CardTitle>{name}</CardTitle>
 191                      <CardDescription>
 192                        Balance:{" "}
 193                        <FormattedBitcoinAmount amountMsat={app.balanceMsat} />
 194                      </CardDescription>
 195                    </CardHeader>
 196                    <CardFooter className="flex flex-row justify-end">
 197                      <IsolatedAppTopupDialog appId={app.id}>
 198                        <Button size="sm" variant="secondary">
 199                          Top Up
 200                        </Button>
 201                      </IsolatedAppTopupDialog>
 202                    </CardFooter>
 203                  </Card>
 204                  <Button onClick={() => setStep(2)}>Next</Button>
 205                </div>
 206              </div>
 207            )}
 208            {step === 2 && (
 209              <div className="grid gap-3">
 210                <div>
 211                  Select which apps to connect to this sub-wallet — whether for
 212                  yourself or someone you're inviting. Connect to as many services
 213                  as you want:
 214                </div>
 215                <Alert variant="destructive">
 216                  <AlertCircleIcon className="h-4 w-4" />
 217                  <AlertTitle>Important</AlertTitle>
 218                  <AlertDescription className="inline">
 219                    For your security, these connection details are only visible
 220                    now and{" "}
 221                    <span className="font-semibold">
 222                      cannot be retrieved later
 223                    </span>
 224                    . If needed, you can store them in a password manager for
 225                    future reference.
 226                  </AlertDescription>
 227                </Alert>
 228                <Accordion type="single" collapsible defaultValue="other">
 229                  <AccordionItem value="other">
 230                    <AccordionTrigger>
 231                      <div className="flex items-center gap-2">
 232                        <ZapIcon className="h-4 w-4" />
 233                        Connect Your App
 234                      </div>
 235                    </AccordionTrigger>
 236                    <AccordionContent>
 237                      {app && (
 238                        <div className="flex justify-center">
 239                          <ConnectAppCard
 240                            app={app}
 241                            pairingUri={connectionSecret}
 242                          />
 243                        </div>
 244                      )}
 245                    </AccordionContent>
 246                  </AccordionItem>
 247                  <AccordionItem value="mobile">
 248                    <AccordionTrigger>
 249                      <div className="flex items-center gap-2">
 250                        <SmartphoneIcon className="h-4 w-4" />
 251                        Alby Go
 252                      </div>
 253                    </AccordionTrigger>
 254                    <AccordionContent>
 255                      <ul className="flex flex-col gap-1 list-inside list-decimal mb-6">
 256                        <li>
 257                          Download Alby Go from the app store
 258                          <div className="flex flex-row gap-3 my-2">
 259                            <Popover>
 260                              <Button variant="outline" asChild>
 261                                <PopoverTrigger>
 262                                  <PlayStoreIcon />
 263                                  Play Store
 264                                </PopoverTrigger>
 265                              </Button>
 266                              <PopoverContent className="flex flex-col items-center gap-3">
 267                                <QRCode value="https://play.google.com/store/apps/details?id=com.getalby.mobile" />
 268                                <ExternalLinkButton
 269                                  variant="link"
 270                                  to="https://play.google.com/store/apps/details?id=com.getalby.mobile"
 271                                >
 272                                  Open
 273                                  <ExternalLinkIcon />
 274                                </ExternalLinkButton>
 275                              </PopoverContent>
 276                            </Popover>
 277                            <Popover>
 278                              <Button variant="outline" asChild>
 279                                <PopoverTrigger>
 280                                  <AppleIcon />
 281                                  Apple App Store
 282                                </PopoverTrigger>
 283                              </Button>
 284                              <PopoverContent className="flex flex-col items-center gap-3">
 285                                <QRCode value="https://apps.apple.com/us/app/alby-go/id6471335774" />
 286                                <ExternalLinkButton
 287                                  variant="link"
 288                                  to="https://apps.apple.com/us/app/alby-go/id6471335774"
 289                                >
 290                                  Open
 291                                  <ExternalLinkIcon />
 292                                </ExternalLinkButton>
 293                              </PopoverContent>
 294                            </Popover>
 295                            <Popover>
 296                              <Button variant="outline" asChild>
 297                                <PopoverTrigger>
 298                                  <ZapStoreIcon />
 299                                  Zapstore
 300                                </PopoverTrigger>
 301                              </Button>
 302                              <PopoverContent className="flex flex-col items-center gap-3">
 303                                <div className="text-center text-xs text-muted-foreground">
 304                                  Install Zapstore on your Android device and
 305                                  search for{" "}
 306                                  <span className="font-semibold">Alby Go</span>
 307                                </div>
 308                                <QRCode value="https://zapstore.dev" />
 309                                <ExternalLinkButton
 310                                  variant="link"
 311                                  to="https://zapstore.dev"
 312                                >
 313                                  Open
 314                                  <ExternalLinkIcon />
 315                                </ExternalLinkButton>
 316                              </PopoverContent>
 317                            </Popover>
 318                          </div>
 319                        </li>
 320                        <li>Open Alby Go and scan this QR code</li>
 321                      </ul>
 322                      {app && (
 323                        <ConnectAppCard app={app} pairingUri={connectionSecret} />
 324                      )}
 325                    </AccordionContent>
 326                  </AccordionItem>
 327                  <AccordionItem value="account">
 328                    <AccordionTrigger>
 329                      <div className="flex items-center gap-2">
 330                        <UserIcon className="h-4 w-4" />
 331                        Alby Account
 332                      </div>
 333                    </AccordionTrigger>
 334                    <AccordionContent>
 335                      <ul className="flex flex-col gap-1 list-inside list-decimal mb-6">
 336                        <li>
 337                          If the recipient doesn't have an Alby Account yet, ask
 338                          them to create an Alby Account on getalby.com or send
 339                          them an{" "}
 340                          <ExternalLink
 341                            to="https://getalby.com/invite_codes"
 342                            className="underline"
 343                          >
 344                            invite
 345                          </ExternalLink>
 346                        </li>
 347                        <li>
 348                          Send your recipient this connection link after they've
 349                          created an Alby account. The link will automatically
 350                          connect the wallet to their account — no extra steps
 351                          needed.
 352                        </li>
 353                      </ul>
 354                      <div className="flex gap-2">
 355                        <Input
 356                          disabled
 357                          readOnly
 358                          type="password"
 359                          value={albyAccountUrl}
 360                        />
 361                        <Button
 362                          onClick={() => copyToClipboard(albyAccountUrl)}
 363                          variant="outline"
 364                        >
 365                          <CopyIcon />
 366                          Copy URL
 367                        </Button>
 368                      </div>
 369                      <Alert className="mt-5">
 370                        <AlertTriangleIcon />
 371                        <AlertTitle>
 372                          Use separate browsers or profiles when managing multiple
 373                          Alby accounts
 374                        </AlertTitle>
 375                        <AlertDescription>
 376                          Opening a connection URL while logged into a different
 377                          Alby account will override your currently connected
 378                          wallet.
 379                        </AlertDescription>
 380                      </Alert>
 381                    </AccordionContent>
 382                  </AccordionItem>
 383                  <AccordionItem value="extension">
 384                    <AccordionTrigger>
 385                      <div className="flex items-center gap-2">
 386                        <MonitorIcon className="h-4 w-4" />
 387                        Alby Browser Extension
 388                      </div>
 389                    </AccordionTrigger>
 390                    <AccordionContent>
 391                      <ul className="list-inside list-decimal flex flex-col gap-1 mb-6">
 392                        <li>
 393                          Install Alby Browser Extension from{" "}
 394                          <ExternalLink
 395                            className="underline"
 396                            to="https://chromewebstore.google.com/detail/alby-bitcoin-wallet-for-l/iokeahhehimjnekafflcihljlcjccdbe"
 397                          >
 398                            Chrome Web Store
 399                          </ExternalLink>{" "}
 400                          or{" "}
 401                          <ExternalLink
 402                            className="underline"
 403                            to="https://addons.mozilla.org/en-US/firefox/addon/alby/"
 404                          >
 405                            Firefox Add-ons
 406                          </ExternalLink>
 407                        </li>
 408                        <li>
 409                          In the extension, choose{" "}
 410                          <span className="font-medium">
 411                            Bring Your Own Wallet
 412                          </span>{" "}
 413                          while connecting a wallet
 414                        </li>
 415                        <li>
 416                          Choose{" "}
 417                          <span className="font-medium">
 418                            Nostr Wallet Connect
 419                          </span>{" "}
 420                          option
 421                        </li>
 422                        <li>
 423                          Copy & paste the connection secret below to connect the
 424                          sub-wallet to the extension.
 425                        </li>
 426                      </ul>
 427                      <div className="flex gap-2">
 428                        <Input
 429                          disabled
 430                          readOnly
 431                          type="password"
 432                          value={connectionSecret}
 433                        />
 434                        <Button
 435                          onClick={() => copyToClipboard(connectionSecret)}
 436                          variant="outline"
 437                        >
 438                          <CopyIcon />
 439                          Copy
 440                        </Button>
 441                      </div>
 442                    </AccordionContent>
 443                  </AccordionItem>
 444                  <AccordionItem value="podcasting">
 445                    <AccordionTrigger>
 446                      <div className="flex items-center gap-2">
 447                        <RadioIcon className="h-4 w-4" />
 448                        Podcasting 2.0
 449                      </div>
 450                    </AccordionTrigger>
 451                    <AccordionContent>
 452                      <p className="text-muted-foreground text-sm mb-5">
 453                        To allow receiving podcasting 2.0 payments to the
 454                        sub-wallet, make sure to share the podcast:value tag that
 455                        needs to be added to recipient's RSS feed:
 456                      </p>
 457                      <div className="flex flex-col gap-2">
 458                        <div className="grid gap-1.5">
 459                          <Label htmlFor="podcastValueTag">
 460                            podcast:value tag
 461                          </Label>
 462                          <Textarea
 463                            id="podcastValueTag"
 464                            readOnly
 465                            className="h-32"
 466                            value={valueTag}
 467                          />
 468                        </div>
 469                        <Button
 470                          onClick={() => copyToClipboard(valueTag)}
 471                          variant="outline"
 472                        >
 473                          <CopyIcon />
 474                          Copy
 475                        </Button>
 476                        <Alert>
 477                          <AlertTitle className="flex flex-row gap-2">
 478                            <TriangleAlertIcon className="w-4 h-4" />
 479                            Make sure you also connect other options
 480                          </AlertTitle>
 481                          <AlertDescription>
 482                            This connection will allow them to receive payments
 483                            from their podcast audience. To allow them to spend
 484                            the funds, make sure they are connected to other
 485                            options like Alby Go or Alby Account.
 486                          </AlertDescription>
 487                        </Alert>
 488                      </div>
 489                    </AccordionContent>
 490                  </AccordionItem>
 491                </Accordion>
 492                <div className="flex gap-2">
 493                  <Button onClick={() => setStep(1)} variant="secondary">
 494                    Back
 495                  </Button>
 496                  <LinkButton to="/sub-wallets">Finish</LinkButton>
 497                </div>
 498              </div>
 499            )}
 500          </div>
 501        </div>
 502      </div>
 503    );
 504  }
 505