-
Notifications
You must be signed in to change notification settings - Fork 61
/
Copy pathTurnstile.tsx
117 lines (104 loc) · 4.42 KB
/
Turnstile.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"use client";
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { MdInfo } from "react-icons/md";
import { Button } from "@akashnetwork/ui/components";
import { Turnstile as ReactTurnstile, TurnstileInstance } from "@marsidev/react-turnstile";
import axios, { AxiosError } from "axios";
import { motion } from "framer-motion";
import { firstValueFrom, Subject } from "rxjs";
import { browserEnvConfig } from "@src/config/browser-env.config";
import { managedWalletHttpService } from "@src/services/managed-wallet-http/managed-wallet-http.service";
type TurnstileStatus = "uninitialized" | "solved" | "interactive" | "expired" | "error" | "dismissed" | "retrying";
const VISIBILITY_STATUSES: TurnstileStatus[] = ["interactive", "error", "retrying"];
export const Turnstile: FC = () => {
const turnstileRef = useRef<TurnstileInstance>();
const [status, setStatus] = useState<TurnstileStatus>("uninitialized");
const isVisible = useMemo(() => !!browserEnvConfig.NEXT_PUBLIC_TURNSTILE_ENABLED && VISIBILITY_STATUSES.includes(status), [status]);
const dismissedSubject = useRef(new Subject<void>());
useEffect(() => {
const interceptorId = managedWalletHttpService.interceptors.response.use(
response => response,
async (error: AxiosError) => {
const request = error?.request;
if ((!request?.status || request?.status > 400) && turnstileRef.current) {
turnstileRef.current?.render();
turnstileRef.current.execute();
const response = await Promise.race([turnstileRef.current.getResponsePromise(), firstValueFrom(dismissedSubject.current.asObservable())]);
if (response) {
return axios(error.config!);
}
}
return Promise.reject(error);
}
);
return () => {
managedWalletHttpService.interceptors.response.eject(interceptorId);
};
}, []);
const resetWidget = useCallback(() => {
turnstileRef.current?.remove();
turnstileRef.current?.render();
turnstileRef.current?.execute();
}, []);
return browserEnvConfig.NEXT_PUBLIC_TURNSTILE_ENABLED ? (
<>
<motion.div
className="fixed inset-0 z-[101] flex content-center items-center justify-center bg-white bg-opacity-90"
initial={{ opacity: 0 }}
animate={{ opacity: isVisible ? 1 : 0 }}
style={{ pointerEvents: isVisible ? "auto" : "none" }}
transition={{
duration: 0.3,
delay: isVisible ? 0 : status === "dismissed" ? 0 : 1
}}
>
<div className="flex flex-col items-center">
<h3 className="mb-2 text-2xl font-bold">We are verifying you are a human. This may take a few seconds</h3>
<p className="mb-8">Reviewing the security of your connection before proceeding</p>
<div className="h-[66px]">
<ReactTurnstile
ref={turnstileRef}
siteKey={browserEnvConfig.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
options={{ execution: "execute" }}
onError={() => setStatus("error")}
onExpire={() => setStatus("expired")}
onSuccess={() => setStatus("solved")}
onBeforeInteractive={() => setStatus("interactive")}
/>
</div>
{status === "error" && <p className="text-red-600">Some error occurred</p>}
<motion.div
className="flex flex-col items-center"
initial={{ opacity: 0 }}
animate={{ opacity: isVisible ? 1 : 0 }}
style={{ pointerEvents: isVisible ? "auto" : "none" }}
transition={{
duration: 0.3,
delay: isVisible ? (status === "error" ? 0 : 5) : 1
}}
>
<div className="my-8">
<Button onClick={resetWidget} className="mr-4">
Retry
</Button>
<Button
onClick={() => {
setStatus("dismissed");
dismissedSubject.current.next();
turnstileRef.current?.remove();
}}
variant="link"
>
Dismiss
</Button>
</div>
<p>
<MdInfo className="mr-1 inline text-xl text-muted-foreground" />
<small>dismissing the check might result into some features not working properly</small>
</p>
</motion.div>
</div>
</motion.div>
</>
) : null;
};