diff --git a/warehouse/static/js/site.js b/warehouse/static/js/site.js
index a2de36a4dc1618e4724a40ccf80ea10184dba1e7..c0f4560c868b7f3898544d03920a2fff44e9644b 100644
--- a/warehouse/static/js/site.js
+++ b/warehouse/static/js/site.js
@@ -1,11 +1,59 @@
+let active_uploads = 0;
+
 document.addEventListener('DOMContentLoaded', function() {
 	function submit_on_change(event) {
-		event.target.closest('form').submit();
+		const form = event.target.closest('form');
+		const form_data = new FormData();
+		form_data.append("csrf_token", form.querySelector('input[name="csrf_token"]').value);
+		form_data.append("file", event.target.files[0])
+		const alertContainer = document.getElementById('alertContainer');
+		const toastContainer = document.querySelector('.toast-container');
+		const tmp = document.createElement("div");
+		tmp.innerHTML = `
+			<div class="toast align-items-center text-bg-primary border-0 mb-2" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false">
+					<div class="toast-body">
+						<div class="spinner-border spinner-border-sm" role="status"></div>
+						Uploading photo ...
+					</div>
+			</div>
+		`;
+		const toastElement = tmp.querySelector('div');
+		toastContainer.append(toastElement);
+		const toast = new bootstrap.Toast(toastElement);
+		toast.show();
+		active_uploads += 1;
+		fetch(form.action, {
+			method: "POST",
+			credentials: "include",
+			body: form_data,
+		}).then((response) => {
+			if (!response.ok) {
+				throw new Error("Upload error");
+			}
+			toast.hide();
+			const tmp = document.createElement("div");
+			tmp.innerHTML = '<div class="alert alert-success" role="alert">Photo added</div>';
+			alertContainer.append(tmp.querySelector('div'));
+			active_uploads -= 1;
+		}).catch((error) => {
+			toast.hide();
+			const tmp = document.createElement("div");
+			tmp.innerHTML = '<div class="alert alert-danger" role="alert">Photo upload failed!';
+			alertContainer.prepend(tmp.querySelector('div'));
+			active_uploads -= 1;
+		});
 	}
 
 	const buttons = document.querySelectorAll('.submit-on-change');
 	buttons.forEach((button) => {
-		console.log(button);
 		button.addEventListener('change', submit_on_change);
 	});
+
+	window.addEventListener('beforeunload', (event) => {
+		if (!active_uploads) {
+			return;
+		}
+		event.preventDefault();
+		event.returnValue = '';
+	});
 })
diff --git a/warehouse/templates/layout.html b/warehouse/templates/layout.html
index a9f7e4c6cec644f5ef5f53cfe0c1579e6d58a91c..15cd603e3a3a00ce4953273c21290e5057f5e360 100644
--- a/warehouse/templates/layout.html
+++ b/warehouse/templates/layout.html
@@ -5,7 +5,7 @@
 		<meta name="viewport" content="width=device-width, initial-scale=1">
 		<link href="{{ url_for('static', filename="css/bootstrap.min.css") }}" rel="stylesheet">
 		<script src="{{ url_for('static', filename="js/bootstrap.bundle.min.js") }}"></script>
-		<script src="{{ url_for('static', filename="js/site.js") }}"></script>
+		<script src="{{ url_for('static', filename="js/site.js", v=1) }}"></script>
 		<title>Warehouse</title>
 	</head>
 	<body>
@@ -28,13 +28,15 @@
 			</div>
 		</nav>
 		<div class="col-md-8 py-2 px-3 mx-auto">
+		<div id="alertContainer">
 		{% for category, message in get_flashed_messages(with_categories=true) %}
-		<div class="col-12">
 			<div class="alert alert-{{ 'danger' if category == 'error' else 'warning' if category == 'warning' else 'primary' }}" role="alert">{{ message }}</div>
-		</div>
 		{% endfor %}
+		</div>
 		{% block body %}
 		{% endblock %}
 		</div>
+		<div class="toast-container position-fixed bottom-0 end-0 p-3">
+		</div>
 	</body>
 </html>