diff --git a/web/app.js b/web/app.js
index a56daca..552cb3c 100644
--- a/web/app.js
+++ b/web/app.js
@@ -2,6 +2,8 @@ function app() {
return {
inboxFiles: [],
outboxFiles: [],
+ dragover: false,
+ toast: { show: false, message: '' },
async loadFiles() {
const [inbox, outbox] = await Promise.all([
fetch('/api/files?dir=inbox').then(r => r.json()),
@@ -10,28 +12,57 @@ function app() {
this.inboxFiles = inbox.files || [];
this.outboxFiles = outbox.files || [];
},
+ showToast(msg) {
+ this.toast.message = msg;
+ this.toast.show = true;
+ setTimeout(() => { this.toast.show = false; }, 2500);
+ },
async upload(event, dir) {
- const file = event.target.files[0];
- const form = new FormData();
- form.append('file', file);
- form.append('dir', dir);
- await fetch('/api/upload', {method: 'POST', body: form});
+ const files = event.target.files;
+ if (!files || !files.length) return;
+ for (const file of files) {
+ const form = new FormData();
+ form.append('file', file);
+ form.append('dir', dir);
+ await fetch('/api/upload', { method: 'POST', body: form });
+ }
+ event.target.value = '';
+ this.showToast('File uploaded');
+ this.loadFiles();
+ },
+ async uploadDropped(event, dir) {
+ const files = event.dataTransfer.files;
+ if (!files || !files.length) return;
+ for (const file of files) {
+ const form = new FormData();
+ form.append('file', file);
+ form.append('dir', dir);
+ await fetch('/api/upload', { method: 'POST', body: form });
+ }
+ this.showToast('File uploaded');
this.loadFiles();
},
download(id) {
window.location.href = '/api/files/' + id + '/download';
},
async deleteFile(id) {
- await fetch('/api/files/' + id, {method: 'DELETE'});
+ if (!confirm('Delete this file?')) return;
+ await fetch('/api/files/' + id, { method: 'DELETE' });
+ this.showToast('File deleted');
this.loadFiles();
},
async share(id) {
- const res = await fetch('/api/files/' + id + '/share', {method: 'POST'});
+ const res = await fetch('/api/files/' + id + '/share', { method: 'POST' });
const data = await res.json();
- alert('Share URL: ' + data.url);
+ try {
+ await navigator.clipboard.writeText(data.url);
+ this.showToast('Share link copied to clipboard');
+ } catch (_) {
+ alert('Share URL: ' + data.url);
+ }
},
async logout() {
- await fetch('/api/logout', {method: 'POST'});
+ await fetch('/api/logout', { method: 'POST' });
window.location.href = '/login';
}
};
diff --git a/web/index.html b/web/index.html
index fd3d1a3..4c717b7 100644
--- a/web/index.html
+++ b/web/index.html
@@ -2,40 +2,118 @@
+
yDropbox
-
+
- yDropbox
-
+ yDropbox
+
-
- Inbox
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Click to upload or drag and drop
-
+
+
+
+
+
+
+
-
- Outbox
-
-
-
-
-
-
-
+
+
+
diff --git a/web/login.html b/web/login.html
index 54b5d79..e03a82c 100644
--- a/web/login.html
+++ b/web/login.html
@@ -2,28 +2,38 @@
+
yDropbox Login
-
-
yDropbox
-
+
+
+
yDropbox
+
Enter your access token to continue
+
+
diff --git a/web/style.css b/web/style.css
index 9052c67..3140df4 100644
--- a/web/style.css
+++ b/web/style.css
@@ -1,8 +1,340 @@
-body { font-family: sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
-header { display: flex; justify-content: space-between; align-items: center; }
-section { margin: 20px 0; }
-.file-row { display: flex; gap: 10px; align-items: center; margin: 10px 0; }
-button { cursor: pointer; }
-.container { max-width: 400px; margin: 100px auto; }
-form { display: flex; flex-direction: column; gap: 10px; }
-input[type="password"] { padding: 8px; font-size: 16px; }
+*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
+
+:root {
+ --bg: #f5f6f8;
+ --surface: #fff;
+ --border: #e2e5ea;
+ --text: #1a1d23;
+ --text-secondary: #6b7280;
+ --primary: #2563eb;
+ --primary-hover: #1d4ed8;
+ --danger: #ef4444;
+ --danger-hover: #dc2626;
+ --success: #10b981;
+ --radius: 10px;
+ --shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
+ --shadow-lg: 0 4px 12px rgba(0,0,0,.08);
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.5;
+ min-height: 100vh;
+}
+
+.app {
+ max-width: 720px;
+ margin: 0 auto;
+ padding: 24px 20px 60px;
+}
+
+header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 32px;
+}
+
+header h1 {
+ font-size: 22px;
+ font-weight: 700;
+ letter-spacing: -.3px;
+ color: var(--text);
+}
+
+header h1 span {
+ color: var(--primary);
+}
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 7px 14px;
+ font-size: 13px;
+ font-weight: 500;
+ border: 1px solid var(--border);
+ border-radius: 7px;
+ background: var(--surface);
+ color: var(--text);
+ cursor: pointer;
+ transition: all .15s ease;
+ white-space: nowrap;
+}
+
+.btn:hover { background: #f0f1f3; }
+
+.btn-primary {
+ background: var(--primary);
+ color: #fff;
+ border-color: var(--primary);
+}
+
+.btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
+
+.btn-danger {
+ color: var(--danger);
+ border-color: #fecaca;
+}
+
+.btn-danger:hover { background: #fef2f2; border-color: var(--danger); }
+
+.btn-ghost {
+ border-color: transparent;
+ background: transparent;
+}
+
+.btn-ghost:hover { background: #f0f1f3; }
+
+.btn-sm { padding: 5px 10px; font-size: 12px; }
+
+.btn-logout {
+ border-color: transparent;
+ color: var(--text-secondary);
+ font-size: 13px;
+}
+
+.btn-logout:hover { color: var(--text); background: #f0f1f3; }
+
+.section {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+ margin-bottom: 20px;
+ overflow: hidden;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--border);
+}
+
+.section-header h2 {
+ font-size: 15px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.section-header .badge {
+ font-size: 11px;
+ font-weight: 600;
+ background: var(--bg);
+ color: var(--text-secondary);
+ padding: 2px 7px;
+ border-radius: 10px;
+}
+
+.section-body { padding: 0; }
+
+.drop-zone {
+ margin: 16px 20px;
+ padding: 28px 20px;
+ border: 2px dashed var(--border);
+ border-radius: 8px;
+ text-align: center;
+ cursor: pointer;
+ transition: all .2s ease;
+ position: relative;
+}
+
+.drop-zone:hover, .drop-zone.dragover {
+ border-color: var(--primary);
+ background: #eff6ff;
+}
+
+.drop-zone input[type="file"] {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ cursor: pointer;
+}
+
+.drop-zone-icon {
+ font-size: 24px;
+ margin-bottom: 6px;
+ color: var(--text-secondary);
+}
+
+.drop-zone-text {
+ font-size: 13px;
+ color: var(--text-secondary);
+}
+
+.drop-zone-text strong {
+ color: var(--primary);
+}
+
+.file-list { list-style: none; }
+
+.file-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 20px;
+ border-bottom: 1px solid #f3f4f6;
+ transition: background .1s ease;
+}
+
+.file-row:last-child { border-bottom: none; }
+.file-row:hover { background: #fafbfc; }
+
+.file-icon {
+ width: 36px;
+ height: 36px;
+ border-radius: 8px;
+ background: #eff6ff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ flex-shrink: 0;
+}
+
+.file-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.file-name {
+ font-size: 14px;
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.file-meta {
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.file-actions {
+ display: flex;
+ gap: 4px;
+ flex-shrink: 0;
+ opacity: 0;
+ transition: opacity .15s ease;
+}
+
+.file-row:hover .file-actions { opacity: 1; }
+
+.empty-state {
+ padding: 40px 20px;
+ text-align: center;
+ color: var(--text-secondary);
+}
+
+.empty-state-icon {
+ font-size: 32px;
+ margin-bottom: 8px;
+ opacity: .5;
+}
+
+.empty-state-text {
+ font-size: 14px;
+}
+
+.login-wrap {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ padding: 20px;
+}
+
+.login-card {
+ width: 100%;
+ max-width: 360px;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ box-shadow: var(--shadow-lg);
+ padding: 40px 32px;
+ text-align: center;
+}
+
+.login-card h1 {
+ font-size: 24px;
+ font-weight: 700;
+ margin-bottom: 4px;
+}
+
+.login-card h1 span { color: var(--primary); }
+
+.login-card p {
+ font-size: 14px;
+ color: var(--text-secondary);
+ margin-bottom: 28px;
+}
+
+.login-card form {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.login-card input {
+ padding: 10px 14px;
+ font-size: 14px;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ outline: none;
+ transition: border-color .15s ease;
+ width: 100%;
+}
+
+.login-card input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37,99,235,.1); }
+
+.login-card .btn-primary {
+ width: 100%;
+ padding: 10px;
+ font-size: 14px;
+ justify-content: center;
+}
+
+.login-error {
+ font-size: 13px;
+ color: var(--danger);
+ display: none;
+}
+
+.login-error.show { display: block; }
+
+.toast {
+ position: fixed;
+ bottom: 24px;
+ left: 50%;
+ transform: translateX(-50%) translateY(80px);
+ background: var(--text);
+ color: #fff;
+ padding: 10px 20px;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ box-shadow: var(--shadow-lg);
+ opacity: 0;
+ transition: all .3s ease;
+ z-index: 1000;
+ pointer-events: none;
+}
+
+.toast.show {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+}
+
+@media (max-width: 600px) {
+ .app { padding: 16px 12px 40px; }
+ .section-header { padding: 12px 16px; }
+ .file-row { padding: 10px 16px; }
+ .file-actions { opacity: 1; }
+ .drop-zone { margin: 12px 16px; }
+}