blob: 7d8f838407c74231cfc18e507ea075649af3466f (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
import { useState, useRef } from 'react';
interface JsonUploadModalProps {
isOpen: boolean;
onClose: () => void;
// has to be any as this is a dynamic trackerm with dynamic score formats
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onUpload: (data: any) => void;
game: string;
}
const JsonUploadModal = ({ isOpen, onClose, onUpload, game }: JsonUploadModalProps) => {
const [file, setFile] = useState<File | null>(null);
const [error, setError] = useState<string>('');
const [isLoading, setIsLoading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
if (!isOpen) return null;
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
setError('');
if (selectedFile) {
if (!selectedFile.name.endsWith('.json')) {
setError('Please select a JSON file');
return;
}
setFile(selectedFile);
}
};
const handleUpload = async () => {
if (!file) {
setError('Please select a file to upload');
return;
}
setIsLoading(true);
setError('');
try {
const text = await file.text();
const data = JSON.parse(text);
onUpload(data);
setFile(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
onClose();
} catch (err) {
if (err instanceof SyntaxError) {
setError('Invalid JSON file format');
} else {
setError('Failed to process file. Please try again.');
}
} finally {
setIsLoading(false);
}
};
const handleClose = () => {
setFile(null);
setError('');
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
onClose();
};
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity"
onClick={handleClose}
/>
{/* Modal */}
<div className="flex min-h-full items-center justify-center p-4">
<div className="relative bg-slate-900 rounded-lg border border-slate-700 w-full max-w-md p-6 shadow-xl">
{/* Header */}
<div className="mb-6">
<h3 className="text-xl font-bold text-white mb-2">Import {game} Data</h3>
<p className="text-slate-400 text-sm">Upload your game data in JSON format</p>
</div>
{/* File Upload Area */}
<div className="mb-6">
<label
htmlFor="file-upload"
className="relative block w-full rounded-lg border-2 border-dashed border-slate-600 p-12 text-center hover:border-violet-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-violet-500 focus-within:ring-offset-2 focus-within:ring-offset-slate-900 transition-colors cursor-pointer"
>
<svg
className="mx-auto h-12 w-12 text-slate-400"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span className="mt-2 block text-sm font-medium text-slate-300">
{file ? file.name : 'Click to upload or drag and drop'}
</span>
<span className="mt-1 block text-xs text-slate-500">JSON files only</span>
<input
ref={fileInputRef}
id="file-upload"
name="file-upload"
type="file"
className="sr-only"
accept=".json"
onChange={handleFileChange}
/>
</label>
</div>
{/* Error Message */}
{error && (
<div className="mb-6 rounded-md bg-red-500/10 border border-red-500/20 p-3">
<p className="text-sm text-red-400">{error}</p>
</div>
)}
{/* Actions */}
<div className="flex space-x-3">
<button
onClick={handleClose}
className="flex-1 border border-slate-600 hover:border-violet-500 text-slate-300 hover:text-white py-2 px-4 rounded-md font-medium transition-colors"
disabled={isLoading}
>
Cancel
</button>
<button
onClick={handleUpload}
className="flex-1 bg-violet-600 hover:bg-violet-700 text-white py-2 px-4 rounded-md font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
disabled={!file || isLoading}
>
{isLoading ? (
<span className="flex items-center justify-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Processing...
</span>
) : (
'Upload'
)}
</button>
</div>
</div>
</div>
</div>
);
};
export default JsonUploadModal;
|