Skip to content

Commit

Permalink
feat(auth): improve turnstile widget UX
Browse files Browse the repository at this point in the history
  • Loading branch information
ygrishajev committed Jan 17, 2025
1 parent f4e3d76 commit b2962e4
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 13 deletions.
57 changes: 46 additions & 11 deletions apps/deploy-web/src/components/turnstile/Turnstile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,74 @@ import { Turnstile as ReactTurnstile, TurnstileInstance } from "@marsidev/react-
import classnames from "classnames";

import { browserEnvConfig } from "@src/config/browser-env.config";
import { useWhen } from "@src/hooks/useWhen";

type TurnstileStatus = "uninitialized" | "solved" | "interactive" | "expired" | "error" | "dismissed";
type TurnstileStatus = "uninitialized" | "solved" | "interactive" | "expired" | "error" | "dismissed" | "retrying";

const VISIBILITY_STATUSES: TurnstileStatus[] = ["uninitialized", "interactive", "error"];
const VISIBILITY_STATUSES: TurnstileStatus[] = ["interactive", "error", "retrying"];

export const Turnstile: FC = () => {
const turnstileRef = useRef<TurnstileInstance>();
const [status, setStatus] = useState<TurnstileStatus>("uninitialized");
const [isTimingOut, setIsTimingOut] = useState(false);
const isVisible = useMemo(() => !!browserEnvConfig.NEXT_PUBLIC_TURNSTILE_ENABLED && VISIBILITY_STATUSES.includes(status), [status]);
const [isVisible, setIsVisible] = useState(false);
const [shouldHide, setShouldHide] = useState(true);
const hasActions = useMemo(() => isTimingOut || status === "error", [isTimingOut, status]);

useEffect(() => {
if (isVisible) {
const timeout = setTimeout(() => {
setIsTimingOut(true);
}, 5000);
if (!browserEnvConfig.NEXT_PUBLIC_TURNSTILE_ENABLED) {
setIsVisible(false);
}

return () => clearTimeout(timeout);
if (VISIBILITY_STATUSES.includes(status)) {
setIsVisible(true);
setShouldHide(false);
} else if (isVisible && status === "dismissed") {
setShouldHide(true);
} else if (isVisible) {
setTimeout(() => setShouldHide(true), 1000);
}
}, [isVisible]);
}, [isVisible, status]);

useWhen(isVisible, () => {
setTimeout(() => setIsTimingOut(true), 5000);
});

useWhen(shouldHide, () => {
setTimeout(() => setIsVisible(false), 400);
});

return browserEnvConfig.NEXT_PUBLIC_TURNSTILE_ENABLED ? (
<>
<div className={classnames({ hidden: !isVisible }, "fixed inset-0 z-[101] flex content-center items-center justify-center bg-white bg-opacity-90")}>
<div
className={classnames(
{
"opacity-0": shouldHide,
"opacity-100": !shouldHide,
visible: isVisible,
invisible: !isVisible
},
"fixed inset-0 z-[101] flex content-center items-center justify-center bg-white bg-opacity-90 transition-opacity duration-300"
)}
>
<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}
onError={() => setStatus("error")}
onError={() =>
setStatus(prevStatus => {
if (prevStatus === "retrying") {
return "error";
}

turnstileRef.current?.reset();

return "retrying";
})
}
onExpire={() => setStatus("expired")}
onSuccess={() => setStatus("solved")}
onBeforeInteractive={() => setStatus("interactive")}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b2962e4

Please sign in to comment.