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
|
const MAX_BYTES = 10 * 1024 * 1024;
const ALLOWED_EXTENSIONS = /\.(jpe?g|png|heic)$/i;
const form = document.querySelector('#upload-form');
const input = document.querySelector('#image-input');
const dropZone = document.querySelector('#drop-zone');
const fileRow = document.querySelector('#file-row');
const fileName = document.querySelector('#file-name');
const fileSize = document.querySelector('#file-size');
const preview = document.querySelector('#preview');
const removeButton = document.querySelector('#remove-button');
const submitButton = document.querySelector('#submit-button');
const status = document.querySelector('#status');
let previewUrl = null;
function setStatus(message = '', type = '') {
status.textContent = message;
status.className = `status ${type}`.trim();
}
function clearSelection() {
input.value = '';
fileRow.hidden = true;
dropZone.hidden = false;
submitButton.disabled = true;
if (previewUrl) URL.revokeObjectURL(previewUrl);
previewUrl = null;
preview.removeAttribute('src');
}
function validate(file) {
if (!file) return 'Choose an image to continue.';
if (!ALLOWED_EXTENSIONS.test(file.name)) return 'Choose a JPG, PNG, or HEIC image.';
if (file.size === 0) return 'The selected image is empty.';
if (file.size > MAX_BYTES) return 'The image must be 10 MB or smaller.';
return '';
}
function showFile(file) {
const error = validate(file);
if (error) {
clearSelection();
setStatus(error, 'error');
return;
}
setStatus();
fileName.textContent = file.name;
fileSize.textContent = `${(file.size / 1024 / 1024).toFixed(2)} MB`;
if (previewUrl) URL.revokeObjectURL(previewUrl);
previewUrl = URL.createObjectURL(file);
preview.src = previewUrl;
fileRow.hidden = false;
dropZone.hidden = true;
submitButton.disabled = false;
}
input.addEventListener('change', () => showFile(input.files[0]));
removeButton.addEventListener('click', () => { clearSelection(); setStatus(); });
for (const eventName of ['dragenter', 'dragover']) {
dropZone.addEventListener(eventName, (event) => {
event.preventDefault();
dropZone.classList.add('is-dragging');
});
}
for (const eventName of ['dragleave', 'drop']) {
dropZone.addEventListener(eventName, (event) => {
event.preventDefault();
dropZone.classList.remove('is-dragging');
});
}
dropZone.addEventListener('drop', (event) => {
const files = event.dataTransfer.files;
if (files.length !== 1) {
setStatus('Drop exactly one image.', 'error');
return;
}
const transfer = new DataTransfer();
transfer.items.add(files[0]);
input.files = transfer.files;
showFile(files[0]);
});
form.addEventListener('submit', async (event) => {
event.preventDefault();
const file = input.files[0];
const error = validate(file);
if (error) { setStatus(error, 'error'); return; }
submitButton.disabled = true;
submitButton.querySelector('span').textContent = 'Submitting…';
setStatus('Securely sending your image…');
const body = new FormData();
body.append('image', file);
try {
const response = await fetch(window.location.pathname, { method: 'POST', body });
const result = await response.json();
if (!response.ok) throw new Error(result.message || 'Upload failed.');
clearSelection();
setStatus(`${result.message} Receipt: ${result.receipt}`, 'success');
} catch (error) {
submitButton.disabled = false;
setStatus(error.message || 'Upload failed. Please try again.', 'error');
} finally {
submitButton.querySelector('span').textContent = 'Submit image';
}
});
|