RestoreNode.tsx raw

   1  import { PowerCircleIcon } from "lucide-react";
   2  import React, { ChangeEvent, useState } from "react";
   3  import { useNavigate } from "react-router";
   4  import Loading from "src/components/Loading";
   5  import PasswordInput from "src/components/password/PasswordInput";
   6  import TwoColumnLayoutHeader from "src/components/TwoColumnLayoutHeader";
   7  import {
   8    AlertDialog,
   9    AlertDialogAction,
  10    AlertDialogCancel,
  11    AlertDialogContent,
  12    AlertDialogDescription,
  13    AlertDialogFooter,
  14    AlertDialogHeader,
  15    AlertDialogTitle,
  16    AlertDialogTrigger,
  17  } from "src/components/ui/alert-dialog";
  18  import { LoadingButton } from "src/components/ui/custom/loading-button";
  19  import { Input } from "src/components/ui/input";
  20  import { Label } from "src/components/ui/label";
  21  
  22  import { useInfo } from "src/hooks/useInfo";
  23  import { handleRequestError } from "src/utils/handleRequestError";
  24  import { isHttpMode } from "src/utils/isHttpMode";
  25  import { request } from "src/utils/request";
  26  
  27  export function RestoreNode() {
  28    const navigate = useNavigate();
  29  
  30    const [unlockPassword, setUnlockPassword] = useState("");
  31    const [file, setFile] = useState<File | null>(null);
  32  
  33    const [showAlert, setShowAlert] = useState(false);
  34    const [loading, setLoading] = useState(false);
  35    const [restored, setRestored] = useState(false);
  36    const { data: info } = useInfo(restored);
  37    const _isHttpMode = isHttpMode();
  38  
  39    React.useEffect(() => {
  40      if (restored && info?.setupCompleted) {
  41        navigate("/");
  42      }
  43    }, [info?.setupCompleted, navigate, restored]);
  44  
  45    if (restored) {
  46      return (
  47        <div className="flex flex-col gap-5 items-center">
  48          <TwoColumnLayoutHeader
  49            title="Restart your Hub"
  50            pageTitle="Restart your Hub"
  51            description="Alby Hub needs to restart to finish restoring your node"
  52          />
  53          <PowerCircleIcon className="w-32 h-32" />
  54          <p className="max-w-sm text-center">
  55            If you're running in a cloud VM or linux service, your Alby Hub will
  56            restart automatically. Otherwise, please manually restart your Alby
  57            Hub to finish the restore process.
  58          </p>
  59          <div className="flex items-center gap-2 text-muted-foreground">
  60            <Loading /> <p>Waiting for restart...</p>
  61          </div>
  62        </div>
  63      );
  64    }
  65  
  66    const onSubmit = (e: React.FormEvent) => {
  67      e.preventDefault();
  68      setShowAlert(true);
  69    };
  70  
  71    const restoreNode = async () => {
  72      try {
  73        setLoading(true);
  74  
  75        if (_isHttpMode) {
  76          const formData = new FormData();
  77          formData.append("unlockPassword", unlockPassword);
  78          if (file !== null) {
  79            formData.append("backup", file);
  80          }
  81          await request("/api/restore", {
  82            method: "POST",
  83            body: formData,
  84          });
  85        } else {
  86          await request("/api/restore", {
  87            method: "POST",
  88            body: JSON.stringify({
  89              unlockPassword,
  90            }),
  91          });
  92        }
  93  
  94        setRestored(true);
  95      } catch (error) {
  96        handleRequestError("Failed to restore backup", error);
  97      } finally {
  98        setShowAlert(false);
  99        setLoading(false);
 100      }
 101    };
 102  
 103    const handleChangeFile = (e: ChangeEvent<HTMLInputElement>) => {
 104      const files = e.currentTarget.files;
 105      if (files) {
 106        setFile(files[0]);
 107      }
 108    };
 109  
 110    return (
 111      <form
 112        onSubmit={onSubmit}
 113        className="flex flex-col gap-5 mx-auto max-w-2xl text-sm"
 114      >
 115        <TwoColumnLayoutHeader
 116          // TODO: Show different message in wails mode
 117          title="Import Wallet from Migration File"
 118          pageTitle="Import Wallet from Migration File"
 119          description="Upload your encrypted wallet migration file."
 120        />
 121        <div className="grid gap-2">
 122          <Label htmlFor="password">Unlock Password</Label>
 123          <PasswordInput
 124            onChange={setUnlockPassword}
 125            value={unlockPassword}
 126            placeholder="Unlock Password"
 127          />
 128        </div>
 129        {_isHttpMode && (
 130          <div className="grid gap-2">
 131            <Label htmlFor="backup">Migration File</Label>
 132            <Input
 133              type="file"
 134              required
 135              id="backup"
 136              name="backup"
 137              accept=".bkp"
 138              onChange={handleChangeFile}
 139              className="cursor-pointer pt-2"
 140            />
 141          </div>
 142        )}
 143        <AlertDialog open={showAlert}>
 144          <AlertDialogTrigger asChild>
 145            <LoadingButton type="submit" loading={loading}>
 146              Import Wallet
 147            </LoadingButton>
 148          </AlertDialogTrigger>
 149          <AlertDialogContent>
 150            <AlertDialogHeader>
 151              <AlertDialogTitle>
 152                Restore Node from Migration File
 153              </AlertDialogTitle>
 154              <AlertDialogDescription>
 155                <div>
 156                  <p>
 157                    As part of the node restore process your Alby Hub will be shut
 158                    down.
 159                  </p>
 160                  <p className="mt-4">
 161                    If you're running in a cloud VM or linux service, your Alby
 162                    Hub will restart automatically. Otherwise, please manually
 163                    restart your Alby Hub to finish the restore process.
 164                  </p>
 165                </div>
 166              </AlertDialogDescription>
 167            </AlertDialogHeader>
 168            <AlertDialogFooter>
 169              <AlertDialogCancel onClick={() => setShowAlert(false)}>
 170                Cancel
 171              </AlertDialogCancel>
 172              <AlertDialogAction onClick={restoreNode}>
 173                Continue
 174              </AlertDialogAction>
 175            </AlertDialogFooter>
 176          </AlertDialogContent>
 177        </AlertDialog>
 178      </form>
 179    );
 180  }
 181