@@ -8,76 +8,183 @@ import DefaultLayout from "@/layouts/default";
88import { ProtectedFetchToDownload } from "@/components/secured-fetch-to-download-button" ;
99import FileUpload from "@/components/file-upload/file-upload" ;
1010import { postJsonToSecuredApi } from "@/components/auth0" ;
11+ import Panel from "@/components/panel" ;
1112
1213export default function ManageDatabasePage ( ) {
1314 const { t } = useTranslation ( ) ;
14- const [ fileUpload , setFileUpload ] = useState ( null as File | null ) ;
15+ const [ fileUpload , setFileUpload ] = useState < File | null > ( null ) ;
1516 const { getAccessTokenSilently } = useAuth0 ( ) ;
16- const [ restoreDatabaseResult , setRestoreDatabaseResult ] = useState (
17- null as any | null ,
18- ) ;
17+ const [ restoreDatabaseResult , setRestoreDatabaseResult ] = useState <
18+ any | null
19+ > ( null ) ;
20+ const [ error , setError ] = useState < string | null > ( null ) ;
21+ const [ isLoading , setIsLoading ] = useState ( false ) ;
22+ const [ showConfirmDialog , setShowConfirmDialog ] = useState ( false ) ;
23+
24+ const validateBackupFile = (
25+ file : File ,
26+ ) : { valid : boolean ; error ?: string } => {
27+ // Vérifier le type MIME
28+ if ( file . type !== "application/json" ) {
29+ return { valid : false , error : t ( "error-not-json-file" ) } ;
30+ }
31+
32+ // Vérifier la taille
33+ if ( file . size > 10 * 1024 * 1024 ) {
34+ // 10MB max
35+ return { valid : false , error : t ( "error-file-too-large" ) } ;
36+ }
37+
38+ return { valid : true } ;
39+ } ;
1940
2041 const handleJsonFileUpload = async ( file : File ) => {
42+ setError ( null ) ;
43+ setIsLoading ( true ) ;
44+
2145 const reader = new FileReader ( ) ;
2246
2347 reader . onload = async ( event ) => {
2448 const jsonData = event . target ?. result ;
2549
2650 if ( jsonData ) {
2751 try {
28- const destUrl = `${ import . meta. env . API_BASE_URL } /backup/json` ;
52+ // Valider que le JSON est bien formé
53+ const parsedData = JSON . parse ( jsonData as string ) ;
54+
55+ // Vous pouvez ajouter ici une validation de la structure du JSON
2956
3057 const ret = await postJsonToSecuredApi (
3158 `${ import . meta. env . API_BASE_URL } /backup/json` ,
32- JSON . parse ( jsonData as string ) ,
59+ parsedData ,
3360 getAccessTokenSilently ,
3461 ) ;
3562
3663 setRestoreDatabaseResult ( ret ) ;
3764 } catch ( error ) {
38- console . error ( "Error parsing JSON:" , error ) ;
65+ console . error ( "Error processing JSON:" , error ) ;
66+ setError ( error instanceof Error ? error . message : t ( "error-unknown" ) ) ;
67+ } finally {
68+ setIsLoading ( false ) ;
3969 }
4070 }
4171 } ;
72+
73+ reader . onerror = ( ) => {
74+ setError ( t ( "error-reading-file" ) ) ;
75+ setIsLoading ( false ) ;
76+ } ;
77+
4278 reader . readAsText ( file ) ;
4379 } ;
4480
4581 return (
4682 < DefaultLayout >
4783 < section className = "flex flex-col items-center justify-center gap-4 py-8 md:py-10" >
48- < div className = "inline-block max-w-lg text-center justify-center" >
84+ < div className = "inline-block max-w-lg text-center justify-center mb-6 " >
4985 < h1 className = { title ( ) } > { t ( "manage-database" ) } </ h1 >
5086 </ div >
51- < p className = "text-center text-muted-foreground" >
52- { t ( "export-database-description" ) }
53- </ p >
54- < ProtectedFetchToDownload
55- putDateInFilename
56- buttonText = { t ( "export-database" ) }
57- downloadLinkText = { t ( "download-backup" ) }
58- filename = { `database-backup.json` }
59- permission = { import . meta. env . BACKUP_PERMISSION }
60- url = { `${ import . meta. env . API_BASE_URL } /backup/json` }
61- />
62- < p className = "text-center text-muted-foreground" >
63- { t ( "restore-database-description" ) }
64- </ p >
65- < FileUpload
66- accept = "application/json"
67- browseButtonText = { t ( "browse" ) }
68- resetButtonText = { t ( "reset" ) }
69- onChange = { ( files ) => {
70- setFileUpload ( files [ 0 ] ) ;
71- } }
72- />
73- { fileUpload && ! restoreDatabaseResult ?. success && (
74- < Button onPress = { async ( ) => await handleJsonFileUpload ( fileUpload ) } >
75- { t ( "restore-the-database" ) }
76- </ Button >
77- ) }
78- { restoreDatabaseResult ?. success && (
79- < div className = "text-center text-muted-foreground" >
80- < p > { t ( "restore-database-success" ) } </ p >
87+
88+ < div className = "grid gap-8 w-full max-w-xl" >
89+ { /* Section Export */ }
90+ < Panel
91+ title = { t ( "export-database" ) }
92+ description = { t ( "export-database-description" ) }
93+ >
94+ < ProtectedFetchToDownload
95+ putDateInFilename
96+ buttonText = { t ( "export-database" ) }
97+ downloadLinkText = { t ( "download-backup" ) }
98+ filename = { `database-backup.json` }
99+ permission = { import . meta. env . BACKUP_PERMISSION }
100+ url = { `${ import . meta. env . API_BASE_URL } /backup/json` }
101+ />
102+ </ Panel >
103+
104+ { /* Section Restore */ }
105+ < Panel
106+ title = { t ( "restore-database" ) }
107+ description = { t ( "restore-database-description" ) }
108+ >
109+ < div className = "space-y-4" >
110+ < FileUpload
111+ accept = "application/json"
112+ browseButtonText = { t ( "browse" ) }
113+ className = "rounded-xl"
114+ resetButtonText = { t ( "reset" ) }
115+ onChange = { ( files ) => {
116+ if ( files && files . length > 0 ) {
117+ const validation = validateBackupFile ( files [ 0 ] ) ;
118+
119+ if ( validation . valid ) {
120+ setFileUpload ( files [ 0 ] ) ;
121+ setError ( null ) ;
122+ } else {
123+ setFileUpload ( null ) ;
124+ setError ( validation . error || "" ) ;
125+ }
126+ } else {
127+ setFileUpload ( null ) ;
128+ setError ( null ) ;
129+ }
130+ setRestoreDatabaseResult ( null ) ;
131+ } }
132+ />
133+
134+ { fileUpload && ! restoreDatabaseResult ?. success && (
135+ < div className = "flex justify-center" >
136+ < Button
137+ color = "primary"
138+ onPress = { ( ) => setShowConfirmDialog ( true ) }
139+ >
140+ { t ( "restore-the-database" ) }
141+ </ Button >
142+ </ div >
143+ ) }
144+
145+ { error && (
146+ < div className = "mt-2 p-3 bg-danger-50 text-danger border border-danger-200 rounded-lg" >
147+ < p className = "text-center" > { error } </ p >
148+ </ div >
149+ ) }
150+
151+ { restoreDatabaseResult ?. success && (
152+ < div className = "mt-2 p-3 bg-success-50 text-success border border-success-200 rounded-lg" >
153+ < p className = "text-center" > { t ( "restore-database-success" ) } </ p >
154+ </ div >
155+ ) }
156+ </ div >
157+ </ Panel >
158+ </ div >
159+
160+ { /* Confirmation dialog */ }
161+ { showConfirmDialog && (
162+ < div className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" >
163+ < div className = "bg-gray-100 dark:bg-gray-700 p-6 rounded-lg max-w-md w-full" >
164+ < h3 className = "text-lg font-semibold mb-2" >
165+ { t ( "confirm-restore" ) }
166+ </ h3 >
167+ < p className = "mb-4 text-danger" > { t ( "confirm-restore-warning" ) } </ p >
168+ < div className = "flex justify-end gap-2" >
169+ < Button
170+ color = "warning"
171+ variant = "light"
172+ onPress = { ( ) => setShowConfirmDialog ( false ) }
173+ >
174+ { t ( "cancel" ) }
175+ </ Button >
176+ < Button
177+ color = "danger"
178+ isLoading = { isLoading }
179+ onPress = { async ( ) => {
180+ setShowConfirmDialog ( false ) ;
181+ await handleJsonFileUpload ( fileUpload ! ) ;
182+ } }
183+ >
184+ { t ( "confirm-restore" ) }
185+ </ Button >
186+ </ div >
187+ </ div >
81188 </ div >
82189 ) }
83190 </ section >
0 commit comments