SetupFinish.tsx raw
1 import React, { useEffect } from "react";
2 import { useNavigate } from "react-router";
3 import { toast } from "sonner";
4 import Container from "src/components/Container";
5 import LottieLoading from "src/components/LottieLoading";
6 import { Button } from "src/components/ui/button";
7 import { useInfo } from "src/hooks/useInfo";
8 import { saveAuthToken } from "src/lib/auth";
9 import useSetupStore from "src/state/SetupStore";
10 import { AuthTokenResponse, SetupNodeInfo } from "src/types";
11 import { handleRequestError } from "src/utils/handleRequestError";
12 import { request } from "src/utils/request";
13
14 let lastStartupErrorTime: string;
15 export function SetupFinish() {
16 const navigate = useNavigate();
17 const { data: info } = useInfo(true); // poll the info endpoint to auto-redirect when app is running
18
19 const [loading, setLoading] = React.useState(false);
20 const [connectionError, setConnectionError] = React.useState(false);
21 const hasFetchedRef = React.useRef(false);
22
23 const startupError = info?.startupError;
24 const startupErrorTime = info?.startupErrorTime;
25
26 React.useEffect(() => {
27 // lastStartupErrorTime check is required because user may leave page and come back
28 // after re-configuring settings
29 if (
30 startupError &&
31 startupErrorTime &&
32 startupErrorTime !== lastStartupErrorTime
33 ) {
34 lastStartupErrorTime = startupErrorTime;
35 toast.error("Failed to start", {
36 description: startupError,
37 });
38 setLoading(false);
39 setConnectionError(true);
40 }
41 }, [startupError, startupErrorTime]);
42
43 useEffect(() => {
44 if (!loading) {
45 return;
46 }
47 const timer = setTimeout(() => {
48 // SetupRedirect takes care of redirection once info.running is true
49 // if it still didn't redirect after 30 seconds, we show an error
50 // Typically initial startup should complete in less than 10 seconds.
51 setLoading(false);
52 setConnectionError(true);
53 }, 30000);
54
55 return () => {
56 clearTimeout(timer);
57 };
58 }, [loading]);
59
60 useEffect(() => {
61 if (!info) {
62 return;
63 }
64 // ensure setup call is only called once
65 if (hasFetchedRef.current) {
66 return;
67 }
68 hasFetchedRef.current = true;
69
70 (async () => {
71 setLoading(true);
72 const succeeded = await finishSetup(
73 useSetupStore.getState().nodeInfo,
74 useSetupStore.getState().unlockPassword
75 );
76 // only setup call is successful as start is async
77 if (!succeeded) {
78 setLoading(false);
79 setConnectionError(true);
80 }
81 })();
82 }, [navigate, info]);
83
84 if (connectionError) {
85 return (
86 <>
87 <title>Connection Failed · Alby Hub</title>
88 <Container>
89 <div className="flex flex-col gap-5 text-center items-center">
90 <div className="grid gap-2">
91 <h1 className="font-semibold text-lg">Connection Failed</h1>
92 <p>Please check your node configuration and try again.</p>
93 </div>
94 <Button
95 onClick={() => {
96 navigate(-1);
97 }}
98 >
99 Try again
100 </Button>
101 </div>
102 </Container>
103 </>
104 );
105 }
106
107 return (
108 <>
109 <title>Setting up... · Alby Hub</title>
110 <Container>
111 <div className="flex flex-col gap-5 justify-center text-center">
112 <LottieLoading size={400} />
113 <h1 className="font-semibold text-lg font-headline">
114 Setting up your Hub...
115 </h1>
116 </div>
117 </Container>
118 </>
119 );
120 }
121
122 const finishSetup = async (
123 nodeInfo: SetupNodeInfo,
124 unlockPassword: string
125 ): Promise<boolean> => {
126 try {
127 await request("/api/setup", {
128 method: "POST",
129 headers: {
130 "Content-Type": "application/json",
131 },
132 body: JSON.stringify({
133 ...nodeInfo,
134 unlockPassword,
135 }),
136 });
137
138 const authTokenResponse = await request<AuthTokenResponse>("/api/start", {
139 method: "POST",
140 headers: {
141 "Content-Type": "application/json",
142 },
143 body: JSON.stringify({
144 unlockPassword,
145 }),
146 });
147 if (authTokenResponse) {
148 saveAuthToken(authTokenResponse.token);
149 }
150 return true;
151 } catch (error) {
152 handleRequestError("Failed to connect", error);
153 return false;
154 }
155 };
156