Khushal-kreeda
fix: ui enhancements
826a975
import React, { useState, useRef } from "react";
import Papa from "papaparse";
import { ApiService } from "../utils/apiService";
import { config, debugLog } from "../utils/config";
const Step2 = ({
uploadedFile,
setUploadedFile,
s3Link,
setS3Link,
fileMetadata,
setFileMetadata,
stepNumber,
stepTitle,
stepIcon,
enabled = true,
// Add API key props for validation
apiKey,
isApiKeyValid,
}) => {
const [outputFormat, setOutputFormat] = useState("tabular");
const [isUploading, setIsUploading] = useState(false);
const [csvData, setCsvData] = useState(null);
const [dragOver, setDragOver] = useState(false);
const fileInputRef = useRef(null);
const handleFileUpload = async (file) => {
// Prevent action if step is disabled
if (!enabled) {
debugLog("File upload attempted but step is disabled");
return;
}
if (!file) {
alert("No file selected");
return;
}
if (!file.name.endsWith(".csv")) {
alert("Please upload a CSV file. Only CSV files are supported.");
return;
}
// Check file size against configured limit
const maxSizeBytes = config.maxFileSizeMB * 1024 * 1024;
if (file.size > maxSizeBytes) {
alert(
`File size (${(file.size / 1024 / 1024).toFixed(
2
)}MB) exceeds maximum allowed size of ${config.maxFileSizeMB}MB`
);
return;
}
// Check if file is empty
if (file.size === 0) {
alert(
"The selected file is empty. Please choose a valid CSV file with data."
);
return;
}
setUploadedFile(file);
debugLog("File selected for upload", {
name: file.name,
size: file.size,
type: file.type,
});
// Initialize file metadata with file size
setFileMetadata({
fileSizeBytes: file.size,
sourceFileRows: 0, // Will be updated after CSV parsing
});
// Parse CSV for preview
Papa.parse(file, {
complete: (results) => {
if (results.errors && results.errors.length > 0) {
debugLog("CSV parsing warnings", results.errors);
}
if (!results.data || results.data.length === 0) {
alert(
"The CSV file appears to be empty or invalid. Please check your file and try again."
);
setUploadedFile(null);
setFileMetadata({ fileSizeBytes: 0, sourceFileRows: 0 });
return;
}
setCsvData(results.data);
// Update file metadata with row count
setFileMetadata({
fileSizeBytes: file.size,
sourceFileRows: results.data.length,
});
debugLog("CSV parsed for preview", {
rows: results.data.length,
columns: results.data[0] ? Object.keys(results.data[0]).length : 0,
});
},
header: true,
skipEmptyLines: true,
error: (error) => {
debugLog("CSV parsing error", error);
alert("Error parsing CSV file. Please ensure it's a valid CSV format.");
setUploadedFile(null);
},
});
// Upload to S3 using ApiService
setIsUploading(true);
try {
debugLog("Starting file upload to S3");
// Use the ApiService with retry logic
const result = await ApiService.retryRequest(async () => {
return await ApiService.uploadFileToS3(file);
});
// Handle different response structures
const s3Url =
result.s3_link || result.link || result.publicUrl || result.url;
setS3Link(s3Url);
debugLog("File uploaded successfully", {
s3Link: s3Url,
result: result,
});
} catch (error) {
debugLog("File upload failed", error);
// More specific error messages
let errorMessage = "Failed to upload file. Please try again.";
if (error.message.includes("credentials")) {
errorMessage =
"AWS credentials are not configured properly. Please check your .env.local file and restart the application.";
} else if (error.message.includes("bucket")) {
errorMessage =
"Storage bucket configuration issue. Please check your S3 bucket name in .env.local file.";
} else if (error.message.includes("size")) {
errorMessage = `File size exceeds the maximum limit of ${config.maxFileSizeMB}MB.`;
} else if (
error.message.includes("network") ||
error.message.includes("fetch") ||
error.message.includes("CORS")
) {
errorMessage =
"Network/CORS error. This might be due to S3 bucket CORS configuration or network connectivity issues.";
} else if (error.message.includes("AccessDenied")) {
errorMessage =
"Access denied to S3 bucket. Please check your AWS permissions.";
}
alert(errorMessage);
setUploadedFile(null);
setCsvData(null);
} finally {
setIsUploading(false);
}
};
const handleDrop = (e) => {
// Prevent action if step is disabled
if (!enabled) return;
e.preventDefault();
setDragOver(false);
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFileUpload(files[0]);
}
};
const handleDragOver = (e) => {
e.preventDefault();
setDragOver(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
setDragOver(false);
};
const renderDataPreview = () => {
if (!csvData || csvData.length === 0) return null;
// Show more rows for better preview (up to 20 rows)
const previewData = csvData.slice(0, 20);
const columns = Object.keys(previewData[0]);
return (
<div className="data-preview">
<h4>
πŸ“Š Data Preview ({csvData.length} rows total, showing first{" "}
{previewData.length})
</h4>
<div className="step2-data-table-container">
<table className="data-table-scrollable">
<thead>
<tr>
{columns.map((col, index) => (
<th key={index}>{col}</th>
))}
</tr>
</thead>
<tbody>
{previewData.map((row, index) => (
<tr key={index}>
{columns.map((col, colIndex) => (
<td key={colIndex} title={row[col]}>
{row[col]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{csvData.length > 20 && (
<p className="preview-note">
Showing first 20 rows of {csvData.length} total rows
</p>
)}
</div>
);
};
return (
<div className="step-container fade-in">
<div className="step-header">
<h2>
<span className="step-number">{stepNumber}</span>
{stepIcon} {stepTitle}
</h2>
<p>
Choose your output format and upload a CSV file for analysis and
synthetic data generation.
</p>
</div>
<div className="step-body">
{!enabled && (
<div
className="status-message warning"
style={{ marginBottom: "1.5rem" }}
>
<div className="status-message-icon">⚠️</div>
<div className="status-message-content">
<h4>API Key Required</h4>
<p>
Please complete Step 1 (API Key Validation) before uploading
files.
</p>
</div>
</div>
)}
<div className="form-group output-format-section">
<label>Output Format</label>
<div className="output-options">
<div
className={`output-option ${
outputFormat === "tabular" ? "selected" : ""
}`}
onClick={() => setOutputFormat("tabular")}
>
<h4>πŸ“Š Tabular</h4>
<p>CSV format with structured rows and columns</p>
</div>
<div className="output-option disabled" title="Coming Soon">
<h4>πŸ“„ JSONL (Coming Soon)</h4>
<p>JSON Lines format for advanced use cases</p>
</div>
</div>
</div>
<div className="form-group file-upload-section">
<label>πŸ“€ Upload Source File</label>
{uploadedFile && s3Link ? (
<div className="file-uploaded">
<div className="file-info">
<div className="file-icon">πŸ“Š</div>
<div className="file-details">
<h4>{uploadedFile.name}</h4>
<p>Successfully uploaded and ready for processing</p>
<p
style={{
fontSize: "0.75rem",
opacity: 0.8,
marginTop: "0.25rem",
}}
>
Size: {(uploadedFile.size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
<button
className="btn btn-secondary"
onClick={() => {
setUploadedFile(null);
setS3Link("");
setCsvData(null);
setFileMetadata({ fileSizeBytes: 0, sourceFileRows: 0 });
}}
style={{ marginLeft: "auto" }}
>
πŸ”„ Change File
</button>
</div>
</div>
) : (
<div
className={`file-upload-area ${dragOver ? "drag-over" : ""}`}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onClick={() => fileInputRef.current?.click()}
>
<input
ref={fileInputRef}
type="file"
accept=".csv"
onChange={(e) => handleFileUpload(e.target.files[0])}
/>
{isUploading ? (
<div>
<div className="spinner"></div>
<div className="file-upload-text">Uploading your file...</div>
<div className="file-upload-subtext">
Please wait while we process your data
</div>
</div>
) : (
<div>
<div className="file-upload-icon">πŸ“Š</div>
<div className="file-upload-text">
Drop your CSV file here
</div>
<div className="file-upload-subtext">
or click to browse β€’ Max {config.maxFileSizeMB}MB β€’ CSV
files only
</div>
</div>
)}
</div>
)}
</div>
{renderDataPreview()}
</div>
</div>
);
};
export default Step2;