11"use client" ;
2- import {
3- Dialog ,
4- DialogContent ,
5- DialogHeader ,
6- DialogTitle ,
7- DialogTrigger ,
8- } from "@/components/ui/dialog" ;
9-
10- import { Button } from "@/components/ui/button" ;
11- import { Label } from "@/components/ui/label" ;
12- import { Progress } from "@/components/ui/progress" ;
13- import { auth , useSession } from "@/lib/auth" ;
14- import { APIError } from "better-auth/api" ;
15- import { useEffect , useMemo , useState } from "react" ;
16- import { InputWithPrefix } from "@/components/input-prefix" ;
17- import { Code } from "@/components/code" ;
18- import { toast } from "sonner" ;
19- import { ClockAlertIcon } from "lucide-react" ;
2+ import { useSession } from "@/lib/auth" ;
203import { useTRPC } from "@/lib/trpc/client" ;
214import { useQuery } from "@tanstack/react-query" ;
225
@@ -28,7 +11,7 @@ export function Telegram() {
2811 return user . telegramUsername && user . telegramId ? (
2912 < ShowTelegram username = { user . telegramUsername } userId = { user . telegramId } />
3013 ) : (
31- < LinkTelegram />
14+ < > < />
3215 ) ;
3316}
3417
@@ -52,235 +35,3 @@ function ShowTelegram({
5235 </ >
5336 ) ;
5437}
55-
56- function LinkTelegram ( ) {
57- const [ open , setOpen ] = useState ( false ) ;
58- const { refetch } = useSession ( ) ;
59- function handleComplete ( ) {
60- toast . success ( "Telegram link completed!" ) ;
61- refetch ( ) ;
62- localStorage . removeItem ( "linktg" ) ;
63- setOpen ( false ) ;
64- }
65- return (
66- < Dialog open = { open } onOpenChange = { setOpen } >
67- < DialogTrigger asChild >
68- < Button size = "sm" variant = "secondary" >
69- Link
70- </ Button >
71- </ DialogTrigger >
72- < DialogContent >
73- < DialogHeader >
74- < DialogTitle > Link your Telegram</ DialogTitle >
75- < div className = "flex min-h-40 flex-col justify-center" >
76- < Form onComplete = { handleComplete } />
77- </ div >
78- </ DialogHeader >
79- </ DialogContent >
80- </ Dialog >
81- ) ;
82- }
83-
84- function getSaved ( ) {
85- const saved = localStorage . getItem ( "linktg" ) ;
86- if ( ! saved ) return null ;
87- const { username, code, ttl, startTime } = JSON . parse ( saved ) as {
88- username : string ;
89- code : string ;
90- ttl : number ;
91- startTime : number ;
92- } ;
93-
94- const leftTime = ttl - ( Date . now ( ) - startTime ) / 1000 ;
95- if ( leftTime <= 0 ) {
96- localStorage . removeItem ( "linktg" ) ;
97- return null ;
98- }
99-
100- return { username, leftTime, code, ttl } ;
101- }
102-
103- function Form ( { onComplete } : { onComplete : ( ) => void } ) {
104- const saved = useMemo ( ( ) => getSaved ( ) , [ ] ) ;
105- const [ username , setUsername ] = useState < string > ( saved ?. username ?? "" ) ;
106- const [ code , setCode ] = useState < string | null > ( saved ?. code ?? null ) ;
107- const [ ttl , setTTL ] = useState < number | null > ( saved ?. leftTime ?? null ) ;
108- const [ expired , setExpired ] = useState < boolean > ( false ) ;
109-
110- async function handleSubmit (
111- e :
112- | React . FormEvent < HTMLFormElement >
113- | React . MouseEvent < HTMLButtonElement , MouseEvent > ,
114- ) {
115- e . stopPropagation ( ) ;
116- e . preventDefault ( ) ;
117- if ( username . length === 0 ) return ;
118- const res = await auth . telegram . link . start ( {
119- telegramUsername : username ,
120- } ) ;
121-
122- if ( res . error ) return console . error ( "custom error" , res . error ) ;
123- if ( res . data instanceof APIError )
124- return console . error ( "better-auth APIError" , res . data ) ;
125- setExpired ( false ) ;
126- setCode ( res . data . code ) ;
127- setTTL ( res . data . ttl ) ;
128-
129- localStorage . setItem (
130- "linktg" ,
131- JSON . stringify ( {
132- code : res . data . code ,
133- ttl : res . data . ttl ,
134- startTime : Date . now ( ) ,
135- username,
136- } ) ,
137- ) ;
138- }
139-
140- function reset ( ) {
141- setTTL ( null ) ;
142- setCode ( null ) ;
143- setExpired ( false ) ;
144- setUsername ( "" ) ;
145- localStorage . removeItem ( "linktg" ) ;
146- }
147-
148- useEffect ( ( ) => {
149- if ( ! code || ! ttl ) return ;
150- // eslint-disable-next-line @typescript-eslint/no-misused-promises
151- const interval = setInterval ( async ( ) => {
152- const res = await auth . telegram . link . verify ( { query : { code } } ) ;
153-
154- if ( res . error ) return console . error ( "custom error" , res . error ) ;
155- if ( res . data instanceof APIError )
156- return console . error ( "better-auth APIError" , res . data ) ;
157-
158- if ( res . data . verified ) {
159- clearInterval ( interval ) ;
160- onComplete ( ) ;
161- return ;
162- }
163-
164- if ( res . data . expired ) {
165- setExpired ( true ) ;
166- localStorage . removeItem ( "linktg" ) ;
167- setTTL ( null ) ;
168- setCode ( null ) ;
169- clearInterval ( interval ) ;
170- return ;
171- }
172-
173- console . log ( "polling... not verified or expired" ) ;
174- } , 5000 ) ;
175- return ( ) => clearInterval ( interval ) ;
176- } , [ code , onComplete , ttl ] ) ;
177-
178- if ( expired )
179- return (
180- < div className = "flex flex-col items-center gap-4 py-8" >
181- < ClockAlertIcon size = { 64 } />
182- < p > Code Expired!</ p >
183- < Button onClick = { handleSubmit } > Regenerate</ Button >
184- </ div >
185- ) ;
186-
187- return code && ttl ? (
188- < div className = "flex flex-col items-center gap-4 pt-8 pb-4" >
189- < p className = "flex items-center justify-between gap-4 text-4xl" >
190- { code . split ( "" ) . map ( ( c , i ) => (
191- < span key = { i } > { c } </ span >
192- ) ) }
193- </ p >
194- < Timer
195- ttl = { saved ?. ttl ?? ttl }
196- timeLeft = { ttl }
197- onEnd = { ( ) => setExpired ( true ) }
198- />
199-
200- < div className = "flex items-center gap-2" >
201- < Button variant = "outline" >
202- < a
203- aria-label = "open telegram bot"
204- href = "tg://resolve?domain=pn_ts_dev_bot"
205- >
206- Start the bot
207- </ a >
208- </ Button >
209- < p > and then send</ p >
210- < Code copyOnClick > /link</ Code >
211- </ div >
212-
213- < p className = "text-foreground/30 pt-2 text-xs" >
214- Having troubles with this code?{ " " }
215- < button className = "underline" onClick = { reset } >
216- Click to reset
217- </ button >
218- </ p >
219- </ div >
220- ) : (
221- < form onSubmit = { handleSubmit } className = "flex flex-col items-center gap-4" >
222- < div className = "flex items-center gap-4" >
223- < Label htmlFor = "username" > Username</ Label >
224- < InputWithPrefix
225- prefix = "@"
226- id = "username"
227- autoComplete = "off"
228- min = { 1 }
229- value = { username }
230- pattern = "^[^@]*"
231- onChange = { ( e ) => setUsername ( e . target . value . replaceAll ( "@" , "" ) ) }
232- placeholder = "example"
233- />
234- </ div >
235- < Button type = "submit" > Inizia</ Button >
236- </ form >
237- ) ;
238- }
239-
240- function Timer ( {
241- ttl,
242- timeLeft : pTimeLeft ,
243- onEnd,
244- } : {
245- ttl : number ;
246- timeLeft : number ;
247- onEnd : ( ) => void ;
248- } ) {
249- const ttlMs = ttl * 1000 ;
250- const [ timeLeft , setTimeLeft ] = useState < number > ( pTimeLeft * 1000 ) ;
251-
252- const percentage = ( timeLeft / ttlMs ) * 100 ;
253- useEffect ( ( ) => {
254- if ( timeLeft === 0 ) return ;
255- const interval = setInterval ( ( ) => {
256- setTimeLeft ( ( prevTime ) => {
257- if ( prevTime <= 100 ) {
258- clearInterval ( interval ) ;
259- onEnd ( ) ;
260- return 0 ;
261- }
262- return timeLeft - 100 ;
263- } ) ;
264- } , 100 ) ;
265-
266- return ( ) => clearInterval ( interval ) ;
267- } , [ onEnd , timeLeft ] ) ;
268-
269- return (
270- < >
271- < Progress
272- value = { percentage }
273- className = "h-2 w-64"
274- indicatorClassname = {
275- timeLeft < 31_000 ? "bg-red-600 dark:bg-red-400" : "bg-primary"
276- }
277- />
278- < span className = "text-foreground/70 text-sm" >
279- Time left:
280- < span className = "inline-block w-10 text-end" >
281- { Math . floor ( timeLeft / 1000 ) } s
282- </ span >
283- </ span >
284- </ >
285- ) ;
286- }
0 commit comments