Files
platform/frontend-v2/src/routes/(app)/upload/+page.svelte
Yusuf Suleman 07cbec89c4 feat: quick screenshot upload at /upload
- Drop zone: drag and drop files
- Paste: Ctrl+V pastes clipboard screenshots directly
- Browse: file picker button
- Saves to platform/screenshots/ with timestamp filename
- Mounted as volume in frontend container
- Accessible from any device at dash.quadjourney.com/upload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:31:13 -05:00

118 lines
3.8 KiB
Svelte

<script lang="ts">
let uploading = $state(false);
let lastFile = $state('');
let dragOver = $state(false);
async function upload(file: File) {
uploading = true;
const fd = new FormData();
fd.append('file', file);
try {
const res = await fetch('/upload', { method: 'POST', body: fd, credentials: 'include' });
const data = await res.json();
lastFile = data.filename || '';
} catch { lastFile = 'Error'; }
uploading = false;
}
function handleDrop(e: DragEvent) {
e.preventDefault();
dragOver = false;
const file = e.dataTransfer?.files[0];
if (file) upload(file);
}
function handlePaste(e: ClipboardEvent) {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
if (file) upload(file);
break;
}
}
}
function handleFileInput(e: Event) {
const input = e.target as HTMLInputElement;
if (input.files?.[0]) upload(input.files[0]);
}
</script>
<svelte:window onpaste={handlePaste} />
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="upload-page"
ondragover={(e) => { e.preventDefault(); dragOver = true; }}
ondragleave={() => dragOver = false}
ondrop={handleDrop}
>
<div class="upload-zone" class:dragover={dragOver}>
{#if uploading}
<div class="upload-status">Uploading...</div>
{:else if lastFile}
<div class="upload-done">
<div class="upload-check">Saved</div>
<div class="upload-filename">{lastFile}</div>
<button class="upload-another" onclick={() => lastFile = ''}>Upload another</button>
</div>
{:else}
<div class="upload-prompt">
<div class="upload-icon">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
</div>
<div class="upload-text">Drop a file, paste a screenshot, or click to upload</div>
<label class="upload-btn">
Browse files
<input type="file" accept="image/*,.pdf,.txt,.md" onchange={handleFileInput} hidden />
</label>
</div>
{/if}
</div>
</div>
<style>
.upload-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 80vh;
padding: 24px;
}
.upload-zone {
width: 100%;
max-width: 480px;
min-height: 280px;
border: 2px dashed rgba(35,26,17,0.15);
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
transition: all 200ms;
background: rgba(255,252,248,0.5);
}
.upload-zone.dragover {
border-color: rgba(179,92,50,0.4);
background: rgba(255,248,242,0.8);
}
.upload-prompt { text-align: center; }
.upload-icon { color: #8c7b69; margin-bottom: 16px; }
.upload-text { color: #5c5046; font-size: 1rem; margin-bottom: 16px; line-height: 1.5; }
.upload-btn {
display: inline-block; padding: 10px 24px; border-radius: 999px;
background: #1e1812; color: white; font-size: 0.88rem; font-weight: 600;
cursor: pointer; transition: opacity 160ms;
}
.upload-btn:hover { opacity: 0.9; }
.upload-status { font-size: 1.1rem; color: #5c5046; }
.upload-done { text-align: center; }
.upload-check { font-size: 1.2rem; font-weight: 700; color: #059669; margin-bottom: 8px; }
.upload-filename { font-size: 0.88rem; color: #5c5046; font-family: var(--mono); margin-bottom: 16px; }
.upload-another {
padding: 8px 18px; border-radius: 999px; border: 1px solid rgba(35,26,17,0.12);
background: none; color: #5c5046; font-size: 0.85rem; cursor: pointer;
}
</style>