refactor: final touches
This commit is contained in:
16
README.md
16
README.md
@@ -1,12 +1,16 @@
|
||||
## PDF to text
|
||||
|
||||
The goal of this project is to create a small web interface that allows the user to upload a PDF file and get the text content of the PDF.
|
||||
|
||||
Features:
|
||||
|
||||
- A small web interface allowing to upload a PDF file
|
||||
- An output containing the text content of the PDF
|
||||
|
||||
## Getting Started
|
||||
|
||||
To ruun the development server:
|
||||
To run the development server:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- edge cases
|
||||
- tests
|
||||
|
||||
@@ -1,115 +1,20 @@
|
||||
import { responseSchema } from '@/utils/data';
|
||||
import axios from 'axios';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from './Button';
|
||||
import { useState } from 'react';
|
||||
import { Content } from './Content';
|
||||
import { Form } from './Form';
|
||||
|
||||
export default function Dashboard() {
|
||||
const [clearing, setClearing] = useState(false);
|
||||
const [file, setFile] = useState<File>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
const [parsedText, setParsedText] = useState<string>('');
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
setFile(e.target.files[0]);
|
||||
} else {
|
||||
setFile(undefined);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
setClearing(false);
|
||||
setFile(undefined);
|
||||
setParsedText('');
|
||||
}, []);
|
||||
|
||||
const validateFile = useCallback((file: File) => {
|
||||
if (file.type !== 'application/pdf') {
|
||||
setError(
|
||||
"The file you've selected is not a PDF file. Please select a PDF file."
|
||||
);
|
||||
setLoading(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This limit is caused by the project being deployed on Vercel using a free tier, with limited resources
|
||||
if (file.size > 1024 * 1024) {
|
||||
setError('The file must be smaller than 1MB.');
|
||||
setLoading(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const uploadFile = useCallback(async () => {
|
||||
if (!file) return;
|
||||
|
||||
if (!validateFile(file)) return;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('File', file);
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/parse', formData);
|
||||
const validatedData = responseSchema.safeParse(response.data);
|
||||
|
||||
if (!validatedData.success) {
|
||||
setError('There was an error parsing the file. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
setParsedText(validatedData.data.text);
|
||||
setClearing(true);
|
||||
} catch {
|
||||
setError('Internal server error. Please try again later.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [file, validateFile]);
|
||||
|
||||
const handleUpload = useCallback(() => {
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
setParsedText('');
|
||||
|
||||
uploadFile();
|
||||
}, [uploadFile]);
|
||||
|
||||
function renderForm() {
|
||||
if (clearing) {
|
||||
return (
|
||||
<>
|
||||
<p>File: {file?.name}</p>
|
||||
<Button label='Use a different file' onClick={handleClear} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<input type='file' accept='.pdf' onChange={handleFileChange} />
|
||||
<Button
|
||||
label='Upload and parse'
|
||||
onClick={handleUpload}
|
||||
disabled={!file}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='dashboard'>
|
||||
<p>This is a tool to extract the text from a PDF file</p>
|
||||
<div className='form'> {renderForm()}</div>
|
||||
<Form
|
||||
setLoading={setLoading}
|
||||
setError={setError}
|
||||
setParsedText={setParsedText}
|
||||
/>
|
||||
<Content loading={loading} error={error} text={parsedText} />
|
||||
</div>
|
||||
);
|
||||
|
||||
112
components/Form.tsx
Normal file
112
components/Form.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { responseSchema } from '@/utils/data';
|
||||
import axios from 'axios';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from './Button';
|
||||
|
||||
interface FormProps {
|
||||
setLoading: (loading: boolean) => void;
|
||||
setError: (error: string | undefined) => void;
|
||||
setParsedText: (parsedText: string) => void;
|
||||
}
|
||||
|
||||
export function Form({ setLoading, setError, setParsedText }: FormProps) {
|
||||
const [clearing, setClearing] = useState(false);
|
||||
const [file, setFile] = useState<File>();
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
setFile(e.target.files[0]);
|
||||
} else {
|
||||
setFile(undefined);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
setClearing(false);
|
||||
setFile(undefined);
|
||||
setParsedText('');
|
||||
}, []);
|
||||
|
||||
const validateFile = useCallback((file: File) => {
|
||||
if (file.type !== 'application/pdf') {
|
||||
setError(
|
||||
"The file you've selected is not a PDF file. Please select a PDF file."
|
||||
);
|
||||
setLoading(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This limit is caused by the project being deployed on Vercel using a free tier, with limited resources for the cloud functions (it can be commented out if running locally)
|
||||
if (file.size > 1024 * 1024) {
|
||||
setError('The file must be smaller than 1MB.');
|
||||
setLoading(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const uploadFile = useCallback(async () => {
|
||||
if (!file) return;
|
||||
|
||||
if (!validateFile(file)) return;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('File', file);
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/parse', formData);
|
||||
const validatedData = responseSchema.safeParse(response.data);
|
||||
|
||||
if (!validatedData.success) {
|
||||
setError('There was an error parsing the file. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
setParsedText(validatedData.data.text);
|
||||
} catch {
|
||||
setError('Internal server error. Please try again later.');
|
||||
} finally {
|
||||
setClearing(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [file, validateFile]);
|
||||
|
||||
const handleUpload = useCallback(() => {
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
setParsedText('');
|
||||
|
||||
uploadFile();
|
||||
}, [uploadFile]);
|
||||
|
||||
function renderForm() {
|
||||
if (clearing) {
|
||||
return (
|
||||
<>
|
||||
<p>File: {file?.name}</p>
|
||||
<Button label='Use a different file' onClick={handleClear} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<input type='file' accept='.pdf' onChange={handleFileChange} />
|
||||
<Button
|
||||
label='Upload and parse'
|
||||
onClick={handleUpload}
|
||||
disabled={!file}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className='form'> {renderForm()}</div>;
|
||||
}
|
||||
Reference in New Issue
Block a user