summaryrefslogtreecommitdiff
path: root/static/app.js
blob: bb09c86e90f9e9a2225445f59a34f72cdd8c43aa (plain)
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';
  }
});