diff --git a/src/lib/components/admin/AlbumForm.svelte b/src/lib/components/admin/AlbumForm.svelte index 9af4d65..2296b26 100644 --- a/src/lib/components/admin/AlbumForm.svelte +++ b/src/lib/components/admin/AlbumForm.svelte @@ -1,197 +1,216 @@ - + goto('/admin/albums')} aria-label="Back to albums"> - 📸 {mode === 'create' ? 'New Album' : 'Edit Album'} + + + (activeTab = value)} + /> - {#if mode === 'create'} - Cancel - handleSave('draft')} - disabled={!isValid || isSaving} - > - {isSaving ? 'Saving...' : 'Save Draft'} - - handleSave('published')} - disabled={!isValid || isSaving} - > - {isSaving ? 'Publishing...' : 'Publish Album'} - - {:else} - Cancel - handleSave()} disabled={!isValid || isSaving}> - {isSaving ? 'Saving...' : 'Save Changes'} - + {#if !isLoading} + {/if} - - {#if error} - - {error} - - {/if} + + {#if isLoading} + Loading album... + {:else} + {#if error} + {error} + {/if} - - - + + + + + + - - + - - - + + + + + - - - Description - + + + + + Show in Universe + Display this album in the Universe feed + + + + + + + {#if mode === 'edit'} + + + + Photos {albumMedia.length > 0 ? `(${albumMedia.length})` : ''} + + (showBulkAlbumModal = true)}> + Manage Photos + + + {#if albumMedia.length > 0} + + {#each albumMedia as item} + + + + {/each} + + {:else} + + No photos added yet. Click "Manage Photos" to add photos to this album. + + {/if} + + {/if} + + + + + - - - - - + {/if} + +{#if album && mode === 'edit'} + +{/if} + diff --git a/src/lib/components/admin/AlbumListItem.svelte b/src/lib/components/admin/AlbumListItem.svelte index a1ab5fa..a62050b 100644 --- a/src/lib/components/admin/AlbumListItem.svelte +++ b/src/lib/components/admin/AlbumListItem.svelte @@ -18,15 +18,15 @@ date: string | null location: string | null coverPhotoId: number | null - isPhotography: boolean status: string showInUniverse: boolean publishedAt: string | null createdAt: string updatedAt: string photos: Photo[] + content?: any _count: { - photos: number + media: number } } @@ -105,7 +105,7 @@ } function getPhotoCount(): number { - return album._count?.photos || 0 + return album._count?.media || 0 } @@ -135,9 +135,10 @@ {album.title} + import { onMount } from 'svelte' + import Button from './Button.svelte' + import Input from './Input.svelte' + import LoadingSpinner from './LoadingSpinner.svelte' + + interface Album { + id: number + title: string + slug: string + _count?: { + media: number + } + } + + interface Props { + mediaId: number + currentAlbums: Album[] + onUpdate: (albums: Album[]) => void + onClose: () => void + } + + let { mediaId, currentAlbums = [], onUpdate, onClose }: Props = $props() + + // State + let albums = $state([]) + let filteredAlbums = $state([]) + let selectedAlbumIds = $state>(new Set(currentAlbums.map((a) => a.id))) + let isLoading = $state(true) + let isSaving = $state(false) + let error = $state('') + let searchQuery = $state('') + let showCreateNew = $state(false) + let newAlbumTitle = $state('') + let newAlbumSlug = $state('') + + onMount(() => { + loadAlbums() + }) + + $effect(() => { + if (searchQuery) { + filteredAlbums = albums.filter((album) => + album.title.toLowerCase().includes(searchQuery.toLowerCase()) + ) + } else { + filteredAlbums = albums + } + }) + + $effect(() => { + if (newAlbumTitle) { + // Auto-generate slug from title + newAlbumSlug = newAlbumTitle + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, '') + } + }) + + async function loadAlbums() { + try { + isLoading = true + const auth = localStorage.getItem('admin_auth') + if (!auth) return + + const response = await fetch('/api/albums', { + headers: { Authorization: `Basic ${auth}` } + }) + + if (!response.ok) { + throw new Error('Failed to load albums') + } + + const data = await response.json() + albums = data.albums || [] + filteredAlbums = albums + } catch (err) { + console.error('Failed to load albums:', err) + error = 'Failed to load albums' + } finally { + isLoading = false + } + } + + function toggleAlbum(albumId: number) { + if (selectedAlbumIds.has(albumId)) { + selectedAlbumIds.delete(albumId) + } else { + selectedAlbumIds.add(albumId) + } + selectedAlbumIds = new Set(selectedAlbumIds) + } + + async function createNewAlbum() { + if (!newAlbumTitle.trim() || !newAlbumSlug.trim()) return + + try { + isSaving = true + error = '' + const auth = localStorage.getItem('admin_auth') + if (!auth) return + + const response = await fetch('/api/albums', { + method: 'POST', + headers: { + Authorization: `Basic ${auth}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + title: newAlbumTitle.trim(), + slug: newAlbumSlug.trim(), + isPhotography: true, + status: 'draft' + }) + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.message || 'Failed to create album') + } + + const newAlbum = await response.json() + + // Add to albums list and select it + albums = [newAlbum, ...albums] + selectedAlbumIds.add(newAlbum.id) + selectedAlbumIds = new Set(selectedAlbumIds) + + // Reset form + showCreateNew = false + newAlbumTitle = '' + newAlbumSlug = '' + searchQuery = '' + } catch (err) { + error = err instanceof Error ? err.message : 'Failed to create album' + } finally { + isSaving = false + } + } + + async function handleSave() { + try { + isSaving = true + error = '' + const auth = localStorage.getItem('admin_auth') + if (!auth) return + + // Get the list of albums to add/remove + const currentAlbumIds = new Set(currentAlbums.map((a) => a.id)) + const albumsToAdd = Array.from(selectedAlbumIds).filter((id) => !currentAlbumIds.has(id)) + const albumsToRemove = currentAlbums + .filter((a) => !selectedAlbumIds.has(a.id)) + .map((a) => a.id) + + // Add to new albums + for (const albumId of albumsToAdd) { + const response = await fetch(`/api/albums/${albumId}/media`, { + method: 'POST', + headers: { + Authorization: `Basic ${auth}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ mediaIds: [mediaId] }) + }) + + if (!response.ok) { + throw new Error('Failed to add to album') + } + } + + // Remove from albums + for (const albumId of albumsToRemove) { + const response = await fetch(`/api/albums/${albumId}/media`, { + method: 'DELETE', + headers: { + Authorization: `Basic ${auth}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ mediaIds: [mediaId] }) + }) + + if (!response.ok) { + throw new Error('Failed to remove from album') + } + } + + // Get updated album list + const updatedAlbums = albums.filter((a) => selectedAlbumIds.has(a.id)) + onUpdate(updatedAlbums) + onClose() + } catch (err) { + console.error('Failed to update albums:', err) + error = 'Failed to update albums' + } finally { + isSaving = false + } + } + + // Computed + const hasChanges = $derived(() => { + const currentIds = new Set(currentAlbums.map((a) => a.id)) + if (currentIds.size !== selectedAlbumIds.size) return true + for (const id of selectedAlbumIds) { + if (!currentIds.has(id)) return true + } + return false + }) + + + + + Manage Albums + + + {#if error} + {error} + {/if} + + + {#if !showCreateNew} + + + (showCreateNew = true)} buttonSize="small"> + + + + New Album + + + + {#if isLoading} + + + Loading albums... + + {:else if filteredAlbums.length === 0} + + {searchQuery ? 'No albums found' : 'No albums available'} + + {:else} + + {#each filteredAlbums as album} + + toggleAlbum(album.id)} + /> + + {album.title} + + {album._count?.media || 0} photos + + + + {/each} + + {/if} + {:else} + + Create New Album + + + + { + showCreateNew = false + newAlbumTitle = '' + newAlbumSlug = '' + }} + disabled={isSaving} + > + Cancel + + + {isSaving ? 'Creating...' : 'Create Album'} + + + + {/if} + + + {#if !showCreateNew} + + {/if} + + + diff --git a/src/routes/admin/albums/+page.svelte b/src/routes/admin/albums/+page.svelte index 3875520..e6177f2 100644 --- a/src/routes/admin/albums/+page.svelte +++ b/src/routes/admin/albums/+page.svelte @@ -24,15 +24,15 @@ date: string | null location: string | null coverPhotoId: number | null - isPhotography: boolean status: string showInUniverse: boolean publishedAt: string | null createdAt: string updatedAt: string photos: Photo[] + content?: any _count: { - photos: number + media: number } } @@ -48,14 +48,14 @@ let activeDropdown = $state(null) // Filter state - let photographyFilter = $state('all') + let statusFilter = $state('all') let sortBy = $state('newest') // Filter options const filterOptions = [ { value: 'all', label: 'All albums' }, - { value: 'true', label: 'Photography albums' }, - { value: 'false', label: 'Regular albums' } + { value: 'published', label: 'Published' }, + { value: 'draft', label: 'Drafts' } ] const sortOptions = [ @@ -107,11 +107,11 @@ albums = data.albums || [] total = data.pagination?.total || albums.length - // Calculate album type counts + // Calculate album status counts const counts: Record = { all: albums.length, - photography: albums.filter((a) => a.isPhotography).length, - regular: albums.filter((a) => !a.isPhotography).length + published: albums.filter((a) => a.status === 'published').length, + draft: albums.filter((a) => a.status === 'draft').length } albumTypeCounts = counts @@ -129,10 +129,10 @@ let filtered = [...albums] // Apply filter - if (photographyFilter === 'true') { - filtered = filtered.filter((album) => album.isPhotography === true) - } else if (photographyFilter === 'false') { - filtered = filtered.filter((album) => album.isPhotography === false) + if (statusFilter === 'published') { + filtered = filtered.filter((album) => album.status === 'published') + } else if (statusFilter === 'draft') { + filtered = filtered.filter((album) => album.status === 'draft') } // Apply sorting @@ -289,7 +289,7 @@ {#snippet left()} - {#if photographyFilter === 'all'} + {#if statusFilter === 'all'} No albums found. Create your first album! {:else} No albums found matching the current filters. Try adjusting your filters or create a new diff --git a/src/routes/admin/albums/[id]/edit/+page.svelte b/src/routes/admin/albums/[id]/edit/+page.svelte index 3decbce..173acf5 100644 --- a/src/routes/admin/albums/[id]/edit/+page.svelte +++ b/src/routes/admin/albums/[id]/edit/+page.svelte @@ -1,48 +1,15 @@ - {album && album.title ? `${album.title} - Admin @jedmund` : 'Edit Album - Admin @jedmund'} + {album ? `Edit ${album.title}` : 'Edit Album'} - Admin @jedmund - - - {#if !isLoading && album} - - - - - - - - - - { - e.stopPropagation() - isMetadataOpen = !isMetadataOpen - }} - disabled={isSaving} - > - - - - Metadata - - - {#if isMetadataOpen && metadataButtonElement && album} - (isMetadataOpen = false)} - /> - {/if} - - - - {/if} - - - {#if isLoading} - - - - {:else if error && !album} - - {error} - Back to Albums - - {:else if album} - - {#if error} - {error} - {/if} - - - Album Details - - - - - - - - - Photos ({albumPhotos.length}) - - - - - - - Album Statistics - - - {album._count?.photos || 0} - Photos - - - {status === 'published' ? 'Published' : 'Draft'} - Status - - - {new Date(album.createdAt).toLocaleDateString()} - Created - - - - - {/if} - - - - - - - - - - +{#if isLoading} + Loading album... +{:else if error} + {error} +{:else if !album} + Album not found +{:else} + +{/if} diff --git a/src/routes/admin/albums/new/+page.svelte b/src/routes/admin/albums/new/+page.svelte index 423a76c..baf9358 100644 --- a/src/routes/admin/albums/new/+page.svelte +++ b/src/routes/admin/albums/new/+page.svelte @@ -1,498 +1,9 @@ New Album - Admin @jedmund - - - - - - - - - - - - { - e.stopPropagation() - isMetadataOpen = !isMetadataOpen - }} - disabled={isSaving} - > - - - - Metadata - - - {#if isMetadataOpen && metadataButtonElement} - {}} - onClose={() => (isMetadataOpen = false)} - /> - {/if} - - - - - - - {#if error} - {error} - {/if} - - - Album Details - - - - - - - - - Photos ({albumPhotos.length}) - - - - - - - - - - - - - +
+ No photos added yet. Click "Manage Photos" to add photos to this album. +
Loading albums...
{searchQuery ? 'No albums found' : 'No albums available'}
- {#if photographyFilter === 'all'} + {#if statusFilter === 'all'} No albums found. Create your first album! {:else} No albums found matching the current filters. Try adjusting your filters or create a new diff --git a/src/routes/admin/albums/[id]/edit/+page.svelte b/src/routes/admin/albums/[id]/edit/+page.svelte index 3decbce..173acf5 100644 --- a/src/routes/admin/albums/[id]/edit/+page.svelte +++ b/src/routes/admin/albums/[id]/edit/+page.svelte @@ -1,48 +1,15 @@ - {album && album.title ? `${album.title} - Admin @jedmund` : 'Edit Album - Admin @jedmund'} + {album ? `Edit ${album.title}` : 'Edit Album'} - Admin @jedmund - - - {#if !isLoading && album} - - - - - - - - - - { - e.stopPropagation() - isMetadataOpen = !isMetadataOpen - }} - disabled={isSaving} - > - - - - Metadata - - - {#if isMetadataOpen && metadataButtonElement && album} - (isMetadataOpen = false)} - /> - {/if} - - - - {/if} - - - {#if isLoading} - - - - {:else if error && !album} - - {error} - Back to Albums - - {:else if album} - - {#if error} - {error} - {/if} - - - Album Details - - - - - - - - - Photos ({albumPhotos.length}) - - - - - - - Album Statistics - - - {album._count?.photos || 0} - Photos - - - {status === 'published' ? 'Published' : 'Draft'} - Status - - - {new Date(album.createdAt).toLocaleDateString()} - Created - - - - - {/if} - - - - - - - - - - +{#if isLoading} +