-
diff --git a/frontend/script.js b/frontend/script.js
index 6ebaca0..7340363 100644
--- a/frontend/script.js
+++ b/frontend/script.js
@@ -34,10 +34,24 @@ async function handleDownloadPage(fileUuid) {
try {
const response = await fetch(`/api/get_download_link/${fileUuid}`);
+
+ // Check for network/connection errors
+ if (!response.ok && (response.status === 0 || response.status >= 500)) {
+ downloadStatus.textContent = 'Сервис временно недоступен, приносим наши извинения.';
+ downloadStatus.className = 'status error';
+ downloadStatus.style.display = 'block';
+ return;
+ }
+
const data = await response.json();
if (data.error || !data.result || !data.result.data) {
- downloadStatus.textContent = data.result?.comment || data.result || 'Ошибка при получении файла';
+ // Check if it's a 404 or file not found error
+ if (response.status === 404 || data.result?.comment?.includes('not found') || data.result?.comment?.includes('не найден')) {
+ downloadStatus.textContent = 'Мы не смогли ничего найти. Перепроверьте введенный адрес.';
+ } else {
+ downloadStatus.textContent = 'Мы не смогли ничего найти. Перепроверьте введенный адрес.';
+ }
downloadStatus.className = 'status error';
downloadStatus.style.display = 'block';
return;
@@ -87,12 +101,25 @@ async function handleDownloadPage(fileUuid) {
downloadStatus.style.display = 'none';
} catch (error) {
- downloadStatus.textContent = `Ошибка: ${error.message}`;
+ // Network error or other connection issues
+ downloadStatus.textContent = 'Сервис временно недоступен, приносим наши извинения.';
downloadStatus.className = 'status error';
downloadStatus.style.display = 'block';
}
}
+// Show error popup
+function showErrorPopup(message) {
+ const popup = document.getElementById('errorPopup');
+ popup.textContent = message;
+ popup.style.display = 'block';
+
+ // Auto-hide after 5 seconds
+ setTimeout(() => {
+ popup.style.display = 'none';
+ }, 5000);
+}
+
// Upload page initialization
function initUploadPage() {
const fileInput = document.getElementById('fileInput');
@@ -101,6 +128,8 @@ function initUploadPage() {
const uploadForm = document.getElementById('uploadForm');
const statusDiv = document.getElementById('status');
const maxFileSizeSpan = document.getElementById('maxFileSize');
+ const successContent = document.getElementById('successContent');
+ const uploadFormContainer = uploadForm.parentElement;
let maxFileSizeBytes = 0;
@@ -133,7 +162,8 @@ function initUploadPage() {
const file = fileInput.files[0];
if (file.size > maxFileSizeBytes && maxFileSizeBytes > 0) {
- setStatus(`Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`, 'error');
+ const errorMsg = `Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`;
+ showErrorPopup(errorMsg);
uploadButton.disabled = true;
return;
}
@@ -145,6 +175,52 @@ function initUploadPage() {
const setStatus = (message, type = '') => {
statusDiv.textContent = message;
statusDiv.className = type ? `status ${type}` : 'status';
+ // Hide status div when empty to avoid unnecessary spacing
+ if (!message || message.trim() === '') {
+ statusDiv.style.display = 'none';
+ } else {
+ statusDiv.style.display = 'block';
+ }
+ };
+
+ const showSuccessState = (fileName, fileSize, downloadUrl) => {
+ // Hide the form
+ uploadForm.style.display = 'none';
+ statusDiv.style.display = 'none';
+
+ // Show success content
+ document.getElementById('successFileName').textContent = fileName;
+ document.getElementById('successFileSize').textContent = `Размер: ${formatBytes(fileSize)}`;
+ document.getElementById('downloadLinkInput').value = downloadUrl;
+ successContent.style.display = 'block';
+
+ // Setup copy button
+ const copyButton = document.getElementById('copyLinkButton');
+ copyButton.onclick = () => {
+ const input = document.getElementById('downloadLinkInput');
+ input.select();
+ input.setSelectionRange(0, 99999); // For mobile devices
+ document.execCommand('copy');
+
+ const originalText = copyButton.textContent;
+ copyButton.textContent = 'Скопировано!';
+ copyButton.classList.add('copied');
+
+ setTimeout(() => {
+ copyButton.textContent = originalText;
+ copyButton.classList.remove('copied');
+ }, 2000);
+ };
+
+ // Setup "upload another" button
+ const uploadAnotherButton = document.getElementById('uploadAnotherButton');
+ uploadAnotherButton.onclick = () => {
+ // Reset form
+ uploadForm.style.display = 'block';
+ successContent.style.display = 'none';
+ fileInput.value = '';
+ updateButtonState();
+ };
};
dropzone.addEventListener('dragover', (event) => {
@@ -177,14 +253,14 @@ function initUploadPage() {
event.preventDefault();
if (!fileInput.files || fileInput.files.length === 0) {
- setStatus('Выберите файл для загрузки.', 'error');
+ showErrorPopup('Выберите файл для загрузки.');
return;
}
const file = fileInput.files[0];
if (maxFileSizeBytes > 0 && file.size > maxFileSizeBytes) {
- setStatus(`Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`, 'error');
+ showErrorPopup(`Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`);
return;
}
@@ -199,13 +275,22 @@ function initUploadPage() {
if (!tokenResponse.ok) {
const errorData = await tokenResponse.json().catch(() => ({}));
- throw new Error(errorData.result?.comment || `Ошибка сервера: ${tokenResponse.status}`);
+ // Extract error message without status codes
+ let errorMessage = errorData.result || 'Ошибка при загрузке файла';
+ if (typeof errorMessage === 'object' && errorMessage.comment) {
+ errorMessage = errorMessage.comment;
+ }
+ if (typeof errorMessage === 'string' && errorMessage.includes('too large')) {
+ errorMessage = 'Файл слишком большой';
+ }
+ throw new Error(errorMessage);
}
const tokenData = await tokenResponse.json();
if (tokenData.error || !tokenData.result || !tokenData.result.data) {
- throw new Error(tokenData.result?.comment || 'Ошибка при получении токена загрузки');
+ let errorMessage = tokenData.result?.comment || 'Ошибка при получении токена загрузки';
+ throw new Error(errorMessage);
}
const uploadData = tokenData.result.data;
@@ -230,17 +315,28 @@ function initUploadPage() {
});
if (!uploadResponse.ok) {
- throw new Error(`Ошибка загрузки на S3: ${uploadResponse.status}`);
+ throw new Error('Ошибка при загрузке файла на сервер');
}
// Success!
const downloadUrl = `${window.location.origin}/get/${fileUuid}`;
- setStatus(`Файл успешно загружен! UUID: ${fileUuid}. Ссылка для скачивания: ${downloadUrl}`, 'success');
+ showSuccessState(file.name, file.size, downloadUrl);
fileInput.value = '';
- updateButtonState();
} catch (error) {
- setStatus(`Не удалось загрузить файл: ${error.message}`, 'error');
+ // Extract clean error message without status codes
+ let errorMessage = error.message;
+ if (errorMessage.includes('status') || errorMessage.match(/\d{3}/)) {
+ if (errorMessage.includes('too large') || errorMessage.includes('413')) {
+ errorMessage = 'Файл слишком большой';
+ } else if (errorMessage.includes('500')) {
+ errorMessage = 'Ошибка сервера. Попробуйте позже';
+ } else {
+ errorMessage = 'Не удалось загрузить файл';
+ }
+ }
+ showErrorPopup(errorMessage);
+ setStatus('', '');
} finally {
uploadButton.disabled = false;
}
diff --git a/frontend/style.css b/frontend/style.css
index 09486a7..f9a7634 100644
--- a/frontend/style.css
+++ b/frontend/style.css
@@ -15,10 +15,19 @@ body {
padding: 24px;
}
+.page-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ max-width: 640px;
+}
+
h1 {
font-size: clamp(32px, 4vw, 48px);
- margin-bottom: 32px;
text-align: center;
+ margin: 0 0 32px 0;
+ width: 100%;
}
.upload-container {
@@ -42,6 +51,14 @@ h1 {
background: rgba(99, 102, 241, 0.04);
cursor: pointer;
position: relative;
+ display: block;
+ overflow: hidden;
+}
+
+.dropzone:focus,
+.dropzone:focus-within {
+ outline: none;
+ border: 2px dashed #6366f1;
}
.dropzone.dragover {
@@ -52,9 +69,43 @@ h1 {
.dropzone input[type="file"] {
position: absolute;
- inset: 0;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
opacity: 0;
cursor: pointer;
+ outline: none !important;
+ border: none !important;
+ box-shadow: none !important;
+ -webkit-appearance: none !important;
+ -moz-appearance: none !important;
+ appearance: none !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ background: transparent !important;
+ font-size: 0 !important;
+ line-height: 0 !important;
+ z-index: 1;
+}
+
+.dropzone input[type="file"]:focus,
+.dropzone input[type="file"]:active,
+.dropzone input[type="file"]:hover,
+.dropzone input[type="file"]:focus-visible,
+.dropzone input[type="file"]::-webkit-file-upload-button {
+ outline: none !important;
+ border: none !important;
+ box-shadow: none !important;
+ -webkit-appearance: none !important;
+ -moz-appearance: none !important;
+ appearance: none !important;
+}
+
+.dropzone input[type="file"]::-webkit-file-upload-button {
+ display: none !important;
+ visibility: hidden !important;
+ opacity: 0 !important;
}
.dropzone p {
@@ -92,12 +143,23 @@ button:disabled {
box-shadow: none;
}
+.button-container {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ margin-top: 24px;
+}
+
.status {
- min-height: 24px;
font-size: 15px;
color: #374151;
line-height: 1.5;
word-break: break-all;
+ min-height: 0;
+}
+
+.status:empty {
+ display: none;
}
.status.error {
@@ -148,3 +210,122 @@ button:disabled {
.download-button:active {
transform: translateY(0);
}
+
+/* Error popup */
+.error-popup {
+ position: fixed;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: #fee2e2;
+ border: 2px solid #dc2626;
+ border-radius: 12px;
+ padding: 16px 24px;
+ color: #991b1b;
+ font-weight: 600;
+ font-size: 15px;
+ z-index: 1000;
+ box-shadow: 0 10px 25px -5px rgba(220, 38, 38, 0.3);
+ animation: slideDown 0.3s ease-out;
+ max-width: 90%;
+ text-align: center;
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateX(-50%) translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+}
+
+/* Success content */
+.success-content {
+ animation: fadeIn 0.3s ease-in;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.download-link-container {
+ display: flex;
+ gap: 12px;
+ margin-top: 24px;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.download-link-input {
+ flex: 1;
+ min-width: 200px;
+ padding: 12px 16px;
+ border: 2px solid #e5e7eb;
+ border-radius: 8px;
+ font-size: 14px;
+ font-family: monospace;
+ background: #f9fafb;
+ color: #111827;
+ cursor: text;
+}
+
+.download-link-input:focus {
+ outline: none;
+ border-color: #6366f1;
+ background: #ffffff;
+}
+
+.copy-link-button {
+ padding: 12px 24px;
+ background: linear-gradient(135deg, #10b981, #059669);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+ white-space: nowrap;
+}
+
+.copy-link-button:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 8px 16px -4px rgba(16, 185, 129, 0.4);
+}
+
+.copy-link-button:active {
+ transform: translateY(0);
+}
+
+.copy-link-button.copied {
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
+}
+
+.upload-another-button {
+ margin-top: 24px;
+ padding: 12px 24px;
+ background: transparent;
+ color: #6366f1;
+ border: 2px solid #6366f1;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.upload-another-button:hover {
+ background: #6366f1;
+ color: white;
+ transform: translateY(-1px);
+ box-shadow: 0 8px 16px -4px rgba(99, 102, 241, 0.4);
+}