🏷️ Search for custom tailnet name offers with keywords.
1import { type FC, useEffect, useState } from 'react';
2
3import AlertModal from '$components/AlertModal';
4import EligibilityModal from '$components/EligibilityModal';
5import Footer from '$components/Footer';
6import LoadingOverlay from '$components/LoadingOverlay';
7
8import { useEligibility } from '$hooks/useEligibility';
9import { useInputValidation } from '$hooks/useInputValidation';
10import { useModal } from '$hooks/useModal';
11import { useStatus } from '$hooks/useStatus';
12import { useTailnetNames } from '$hooks/useTailnetNames';
13import { useTimer } from '$hooks/useTimer';
14import { useTokens } from '$hooks/useTokens';
15import { useWords } from '$hooks/useWords';
16
17import MainScreen from '$screens/MainScreen';
18import TailnetWordsScreen from '$screens/TailnetWords';
19import TokenListScreen from '$screens/TokenList';
20import WordListScreen from '$screens/WordList';
21
22const App: FC = () => {
23 const [inputValue, setInputValue] = useState('');
24 const [error, _setError] = useState<string | null>(null);
25 const [showAlert, setShowAlert] = useState(false);
26 const [alertMessage, setAlertMessage] = useState('');
27 const [claimedToken, setClaimedToken] = useState<string | null>(null);
28 const [screen, setScreen] = useState<
29 'main' | 'words' | 'tokens' | 'wordlist'
30 >('main');
31 const [isTransitioning, setIsTransitioning] = useState(false);
32
33 const handleShowWordsScreen = () => {
34 setIsTransitioning(true);
35 setScreen('words');
36 };
37 const handleShowTokensScreen = () => {
38 setIsTransitioning(true);
39 setScreen('tokens');
40 };
41 const handleBackToWords = () => {
42 setIsTransitioning(true);
43 setScreen('words');
44 };
45 const handleBackToMain = () => {
46 setIsTransitioning(true);
47 setScreen('main');
48 };
49 const handleShowWordListScreen = () => {
50 setIsTransitioning(true);
51 setScreen('wordlist');
52 };
53
54 const { status, setStatus, timer, loading, setLoading } = useTimer();
55 const { handleStart, handleStop } = useStatus(setStatus);
56 const { tails, scales } = useWords();
57 const {
58 tailnetNames,
59 setTailnetNames,
60 handleAddTailnet,
61 handleRemoveTailnet,
62 } = useTailnetNames(tails, scales, setStatus, inputValue, setInputValue);
63 const [
64 tokens,
65 setTokens,
66 { handleClaimToken, handleRemoveToken, error: claimTokenError },
67 ] = useTokens({
68 setLoading,
69 setAlertMessage,
70 setShowAlert,
71 setClaimedToken,
72 });
73
74 const { eligibility, eligibilityLoading } = useEligibility();
75 const { alertModalRef, alertCloseBtnRef, handleAlertCloseModal } = useModal(
76 showAlert,
77 setShowAlert,
78 setAlertMessage,
79 claimedToken,
80 setClaimedToken,
81 setTokens,
82 setTailnetNames,
83 );
84 const isInputValid = useInputValidation(
85 inputValue,
86 tails,
87 scales,
88 tailnetNames,
89 );
90
91 useEffect(() => {
92 if (isTransitioning) {
93 const timer = setTimeout(() => setIsTransitioning(false), 300);
94 return () => clearTimeout(timer);
95 }
96 }, [isTransitioning]);
97
98 return (
99 <div className="flex flex-col min-w-[600px] max-w-lg min-h-[600px] bg-background overflow-hidden mx-auto h-full">
100 <div className="flex-1 relative flex flex-col h-full">
101 <div className="flex-1 relative overflow-x-hidden overflow-y-auto [&::-webkit-scrollbar]:hidden">
102 <div
103 className={`absolute inset-0 transition-all duration-500 bg-background ${
104 screen === 'main'
105 ? 'translate-x-0 opacity-100 pointer-events-auto'
106 : 'translate-x-[-100%] opacity-0 pointer-events-none'
107 }`}
108 >
109 <MainScreen
110 onShowWords={handleShowWordsScreen}
111 onShowTokens={handleShowTokensScreen}
112 status={status}
113 timer={timer}
114 handleStart={handleStart}
115 handleStop={handleStop}
116 tailnetNames={tailnetNames}
117 tokens={tokens}
118 />
119 </div>
120
121 <div
122 className={`absolute inset-0 transition-all duration-500 bg-background ${
123 screen === 'words'
124 ? 'translate-x-0 opacity-100 pointer-events-auto'
125 : screen === 'main'
126 ? 'translate-x-[100%] opacity-0 pointer-events-none'
127 : 'translate-x-[-100%] opacity-0 pointer-events-none'
128 }`}
129 >
130 <TailnetWordsScreen
131 tailnetNames={tailnetNames}
132 handleAddTailnet={handleAddTailnet}
133 handleRemoveTailnet={handleRemoveTailnet}
134 inputValue={inputValue}
135 setInputValue={setInputValue}
136 isInputValid={!!isInputValid}
137 loading={loading}
138 onBack={handleBackToMain}
139 error={error}
140 tails={tails}
141 scales={scales}
142 onShowWordList={handleShowWordListScreen}
143 />
144 </div>
145
146 <div
147 className={`absolute inset-0 transition-all duration-500 bg-background ${
148 screen === 'tokens'
149 ? 'translate-x-0 opacity-100 pointer-events-auto'
150 : 'translate-x-[100%] opacity-0 pointer-events-none'
151 }`}
152 >
153 <TokenListScreen
154 tokens={tokens}
155 handleClaimToken={handleClaimToken}
156 handleRemoveToken={handleRemoveToken}
157 loading={loading}
158 error={claimTokenError}
159 onBack={handleBackToMain}
160 />
161 </div>
162
163 <div
164 className={`absolute inset-0 transition-all duration-500 bg-background ${
165 screen === 'wordlist'
166 ? 'translate-x-0 opacity-100 pointer-events-auto'
167 : 'translate-x-[100%] opacity-0 pointer-events-none'
168 }`}
169 >
170 <WordListScreen
171 tails={tails}
172 scales={scales}
173 onBack={handleBackToWords}
174 />
175 </div>
176
177 <LoadingOverlay
178 eligibilityLoading={eligibilityLoading}
179 loading={loading}
180 />
181 <EligibilityModal
182 eligibility={eligibility}
183 eligibilityLoading={eligibilityLoading}
184 />
185 <AlertModal
186 alertCloseBtnRef={alertCloseBtnRef}
187 alertMessage={alertMessage}
188 alertModalRef={alertModalRef}
189 handleAlertCloseModal={handleAlertCloseModal}
190 showAlert={showAlert}
191 />
192 </div>
193
194 <Footer />
195 </div>
196 </div>
197 );
198};
199
200export default App;