11import { createFileRoute , Link } from '@tanstack/react-router'
2- import { CheckCircle2 , History , ArrowLeft , Calendar , Hash , Settings , Palette , ExternalLink , Globe } from 'lucide-react'
2+ import { CheckCircle2 , History , ArrowLeft , Calendar , Hash , Settings , Palette , ExternalLink , Globe , FileCode , Loader2 } from 'lucide-react'
3+ import { useCallback , useRef , useState , useEffect } from 'react'
34
45import { Badge } from '@/components/ui/badge'
56import { Button } from '@/components/ui/button'
@@ -15,6 +16,64 @@ export const Route = createFileRoute('/history/$id')({
1516 params : { path : { id } } ,
1617 } )
1718
19+ const [ pythonDownloadStatus , setPythonDownloadStatus ] = useState < 'idle' | 'loading' | 'success' | 'failed' > ( 'idle' )
20+ const pythonDownloadTimeoutRef = useRef < number | null > ( null )
21+
22+ const clearPythonStatusTimeout = useCallback ( ( ) => {
23+ if ( pythonDownloadTimeoutRef . current !== null ) {
24+ window . clearTimeout ( pythonDownloadTimeoutRef . current )
25+ pythonDownloadTimeoutRef . current = null
26+ }
27+ } , [ ] )
28+
29+ const schedulePythonStatusReset = useCallback ( ( ) => {
30+ clearPythonStatusTimeout ( )
31+ pythonDownloadTimeoutRef . current = window . setTimeout ( ( ) => {
32+ setPythonDownloadStatus ( 'idle' )
33+ pythonDownloadTimeoutRef . current = null
34+ } , 2000 )
35+ } , [ clearPythonStatusTimeout ] )
36+
37+ const handleDownloadPythonScript = useCallback ( async ( ) => {
38+ if ( ! data ?. result ?. length ) return
39+ try {
40+ setPythonDownloadStatus ( 'loading' )
41+ clearPythonStatusTimeout ( )
42+ const searchParams = new URLSearchParams ( )
43+ if ( data . footprint ) {
44+ searchParams . set ( 'footprint' , data . footprint )
45+ }
46+ data . result . forEach ( ( value : number ) => {
47+ searchParams . append ( 'numbers' , String ( value ) )
48+ } )
49+ const response = await fetch ( `/api/random-generator/verify-python-code?${ searchParams . toString ( ) } ` , {
50+ credentials : 'include' ,
51+ } )
52+ if ( ! response . ok ) {
53+ throw new Error ( 'Failed to download script' )
54+ }
55+ const blob = await response . blob ( )
56+ const url = URL . createObjectURL ( blob )
57+ const anchor = document . createElement ( 'a' )
58+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' )
59+ anchor . href = url
60+ anchor . download = `check_${ timestamp } .py`
61+ anchor . click ( )
62+ URL . revokeObjectURL ( url )
63+ setPythonDownloadStatus ( 'success' )
64+ } catch ( error ) {
65+ setPythonDownloadStatus ( 'failed' )
66+ } finally {
67+ schedulePythonStatusReset ( )
68+ }
69+ } , [ data , clearPythonStatusTimeout , schedulePythonStatusReset ] )
70+
71+ useEffect ( ( ) => {
72+ return ( ) => {
73+ clearPythonStatusTimeout ( )
74+ }
75+ } , [ clearPythonStatusTimeout ] )
76+
1877
1978 if ( isServerPending ) {
2079 return (
@@ -140,15 +199,36 @@ export const Route = createFileRoute('/history/$id')({
140199 < CheckCircle2 className = "h-4 w-4 text-primary" />
141200 < span className = "font-medium text-foreground" > Криптографическая подпись</ span >
142201 </ div >
143- < Button asChild variant = "outline" size = "sm" >
144- < Link to = "/verify" search = { { footprint : data . footprint , numbers : data . result . join ( ',' ) } } >
145- Проверить подпись
146- </ Link >
147- </ Button >
202+ < div className = "flex gap-2" >
203+ < Button asChild variant = "outline" size = "sm" >
204+ < Link to = "/verify" search = { { footprint : data . footprint , numbers : data . result . join ( ',' ) } } >
205+ Проверить подпись
206+ </ Link >
207+ </ Button >
208+ < Button
209+ variant = "outline"
210+ size = "sm"
211+ onClick = { handleDownloadPythonScript }
212+ disabled = { pythonDownloadStatus === 'loading' }
213+ >
214+ { pythonDownloadStatus === 'loading' ? (
215+ < Loader2 className = "mr-2 h-4 w-4 animate-spin" aria-hidden />
216+ ) : (
217+ < FileCode className = "mr-2 h-4 w-4" aria-hidden />
218+ ) }
219+ Python-скрипт
220+ </ Button >
221+ </ div >
148222 </ div >
149223 < code className = "block rounded-md border border-border/60 bg-background px-3 py-2 text-xs break-all" >
150224 { data . footprint }
151225 </ code >
226+ { pythonDownloadStatus === 'success' && (
227+ < p className = "text-xs text-green-600" > Скрипт check.py сохранён.</ p >
228+ ) }
229+ { pythonDownloadStatus === 'failed' && (
230+ < p className = "text-xs text-red-600" > Не удалось скачать скрипт. Попробуйте ещё раз.</ p >
231+ ) }
152232 </ div >
153233 ) }
154234 </ CardContent >
0 commit comments