jedmund-svelte/src/routes/admin/test-upload/+page.svelte

371 lines
7 KiB
Svelte

<script lang="ts">
import AdminPage from '$lib/components/admin/AdminPage.svelte'
import Editor from '$lib/components/admin/Editor.svelte'
import type { JSONContent } from '@tiptap/core'
let testContent = $state<JSONContent>({
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Test the image upload functionality:'
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: '1. Try pasting an image from clipboard (Cmd+V)'
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: '2. Click the image placeholder below to upload'
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: '3. Drag and drop an image onto the placeholder'
}
]
},
{
type: 'image-placeholder'
}
]
})
let uploadedImages = $state<Array<{ url: string; timestamp: string }>>([])
function handleEditorChange(content: JSONContent) {
testContent = content
// Extract images from content
const images: Array<{ url: string; timestamp: string }> = []
function extractImages(node: any) {
if (node.type === 'image' && node.attrs?.src) {
images.push({
url: node.attrs.src,
timestamp: new Date().toLocaleTimeString()
})
}
if (node.content) {
node.content.forEach(extractImages)
}
}
if (content.content) {
content.content.forEach(extractImages)
}
uploadedImages = images
}
// Check local uploads directory
let localUploadsExist = $state(false)
async function checkLocalUploads() {
try {
const auth = localStorage.getItem('admin_auth')
if (!auth) return
const response = await fetch('/api/media?limit=10', {
headers: { Authorization: `Basic ${auth}` }
})
if (response.ok) {
const data = await response.json()
localUploadsExist = data.media.some((m: any) => m.url.includes('/local-uploads/'))
}
} catch (error) {
console.error('Failed to check uploads:', error)
}
}
// Check on mount and when images change
$effect(() => {
checkLocalUploads()
})
// Re-check when new images are uploaded
$effect(() => {
if (uploadedImages.length > 0) {
setTimeout(checkLocalUploads, 500) // Small delay to ensure DB is updated
}
})
</script>
<AdminPage>
<header slot="header">
<h1>Upload Test</h1>
<a href="/admin/projects" class="back-link">← Back to Projects</a>
</header>
<div class="test-container">
<div class="info-section">
<h2>Image Upload Test</h2>
<p>This page helps you test that image uploads are working correctly.</p>
{#if localUploadsExist}
<div class="status success">✅ Local uploads directory is configured</div>
{:else}
<div class="status warning">⚠️ No local uploads found yet</div>
{/if}
<div class="instructions">
<h3>How to test:</h3>
<ol>
<li>Copy an image to your clipboard</li>
<li>Click in the editor below and paste (Cmd+V)</li>
<li>Or click the image placeholder to browse files</li>
<li>Or drag and drop an image onto the placeholder</li>
</ol>
</div>
</div>
<div class="editor-section">
<h3>Editor with Image Upload</h3>
<Editor
bind:data={testContent}
onChange={handleEditorChange}
placeholder="Start typing or paste an image..."
minHeight={400}
/>
</div>
{#if uploadedImages.length > 0}
<div class="results-section">
<h3>Uploaded Images</h3>
<div class="image-grid">
{#each uploadedImages as image}
<div class="uploaded-image">
<img src={image.url} alt="Uploaded" />
<div class="image-info">
<span class="timestamp">{image.timestamp}</span>
<code class="url">{image.url}</code>
{#if image.url.includes('/local-uploads/')}
<span class="badge local">Local</span>
{:else if image.url.includes('cloudinary')}
<span class="badge cloud">Cloudinary</span>
{:else}
<span class="badge">Unknown</span>
{/if}
</div>
</div>
{/each}
</div>
</div>
{/if}
<div class="json-section">
<h3>Editor Content (JSON)</h3>
<pre>{JSON.stringify(testContent, null, 2)}</pre>
</div>
</div>
</AdminPage>
<style lang="scss">
header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
h1 {
font-size: 1.75rem;
font-weight: 700;
margin: 0;
color: $grey-10;
}
.back-link {
color: $grey-40;
text-decoration: none;
&:hover {
color: $grey-20;
}
}
}
.test-container {
max-width: 800px;
margin: 0 auto;
width: 100%;
box-sizing: border-box;
}
.info-section {
background: white;
padding: $unit-4x;
border-radius: $unit-2x;
margin-bottom: $unit-4x;
box-sizing: border-box;
overflow: hidden;
h2 {
margin: 0 0 $unit-2x;
}
p {
color: $grey-30;
margin-bottom: $unit-3x;
}
}
.status {
padding: $unit-2x $unit-3x;
border-radius: $unit;
margin-bottom: $unit-3x;
&.success {
background: #e6f7e6;
color: #2d662d;
}
&.warning {
background: #fff4e6;
color: #996600;
}
}
.instructions {
h3 {
font-size: 1rem;
margin-bottom: $unit-2x;
}
ol {
margin: 0;
padding-left: $unit-4x;
li {
margin-bottom: $unit;
color: $grey-30;
}
}
}
.editor-section {
background: white;
padding: $unit-4x;
border-radius: $unit-2x;
margin-bottom: $unit-4x;
box-sizing: border-box;
overflow: hidden;
h3 {
margin: 0 0 $unit-3x;
}
}
.results-section {
background: white;
padding: $unit-4x;
border-radius: $unit-2x;
margin-bottom: $unit-4x;
box-sizing: border-box;
overflow: hidden;
h3 {
margin: 0 0 $unit-3x;
}
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: $unit-3x;
}
.uploaded-image {
border: 1px solid $grey-80;
border-radius: $unit;
overflow: hidden;
img {
width: 100%;
height: 150px;
object-fit: cover;
}
.image-info {
padding: $unit-2x;
background: $grey-95;
.timestamp {
display: block;
font-size: 0.875rem;
color: $grey-40;
margin-bottom: $unit;
}
.url {
display: block;
font-size: 0.75rem;
word-break: break-all;
color: $grey-30;
margin-bottom: $unit;
overflow-wrap: break-word;
max-width: 100%;
}
.badge {
display: inline-block;
padding: 2px 8px;
font-size: 0.75rem;
border-radius: 4px;
&.local {
background: #e6f0ff;
color: #0066cc;
}
&.cloud {
background: #f0e6ff;
color: #6600cc;
}
}
}
}
.json-section {
background: white;
padding: $unit-4x;
border-radius: $unit-2x;
box-sizing: border-box;
overflow: hidden;
h3 {
margin: 0 0 $unit-3x;
}
pre {
background: $grey-95;
padding: $unit-3x;
border-radius: $unit;
overflow-x: auto;
overflow-y: auto;
max-height: 400px;
font-size: 0.875rem;
line-height: 1.5;
margin: 0;
color: $grey-10;
white-space: pre-wrap;
word-wrap: break-word;
}
}
</style>