refactor: replace button text changes with toast notifications
- Update all admin forms to use toast messages - Remove temporary "Saving..." button text changes - Remove inline error/success message displays - Keep buttons disabled during operations - Show loading, success, and error toasts appropriately Updated components: - AlbumForm: Save operations with descriptive messages - StatusDropdown: Remove loading text from buttons - MediaDetailsModal: Save, delete, and copy operations - ProjectForm: Create and update operations - EssayForm: Publish and save draft operations - SimplePostForm: Create and update posts - PhotoPostForm: Publish photo posts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1a155e5657
commit
e305bf15ef
7 changed files with 76 additions and 101 deletions
|
|
@ -10,6 +10,7 @@
|
||||||
import SmartImage from '../SmartImage.svelte'
|
import SmartImage from '../SmartImage.svelte'
|
||||||
import EnhancedComposer from './EnhancedComposer.svelte'
|
import EnhancedComposer from './EnhancedComposer.svelte'
|
||||||
import { authenticatedFetch } from '$lib/admin-auth'
|
import { authenticatedFetch } from '$lib/admin-auth'
|
||||||
|
import { toast } from '$lib/stores/toast'
|
||||||
import type { Album } from '@prisma/client'
|
import type { Album } from '@prisma/client'
|
||||||
import type { JSONContent } from '@tiptap/core'
|
import type { JSONContent } from '@tiptap/core'
|
||||||
|
|
||||||
|
|
@ -34,8 +35,6 @@
|
||||||
// State
|
// State
|
||||||
let isLoading = $state(mode === 'edit')
|
let isLoading = $state(mode === 'edit')
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let error = $state('')
|
|
||||||
let successMessage = $state('')
|
|
||||||
let validationErrors = $state<Record<string, string>>({})
|
let validationErrors = $state<Record<string, string>>({})
|
||||||
let showBulkAlbumModal = $state(false)
|
let showBulkAlbumModal = $state(false)
|
||||||
let albumMedia = $state<any[]>([])
|
let albumMedia = $state<any[]>([])
|
||||||
|
|
@ -132,14 +131,14 @@
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
if (!validateForm()) {
|
if (!validateForm()) {
|
||||||
error = 'Please fix the validation errors'
|
toast.error('Please fix the validation errors')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingToastId = toast.loading(`${mode === 'edit' ? 'Saving' : 'Creating'} album...`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
error = ''
|
|
||||||
successMessage = ''
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
title: formData.title,
|
title: formData.title,
|
||||||
|
|
@ -172,6 +171,9 @@
|
||||||
|
|
||||||
const savedAlbum = await response.json()
|
const savedAlbum = await response.json()
|
||||||
|
|
||||||
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.success(`Album ${mode === 'edit' ? 'saved' : 'created'} successfully!`)
|
||||||
|
|
||||||
if (mode === 'create') {
|
if (mode === 'create') {
|
||||||
goto(`/admin/albums/${savedAlbum.id}/edit`)
|
goto(`/admin/albums/${savedAlbum.id}/edit`)
|
||||||
} else if (mode === 'edit' && album) {
|
} else if (mode === 'edit' && album) {
|
||||||
|
|
@ -180,10 +182,12 @@
|
||||||
populateFormData(savedAlbum)
|
populateFormData(savedAlbum)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error =
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.error(
|
||||||
err instanceof Error
|
err instanceof Error
|
||||||
? err.message
|
? err.message
|
||||||
: `Failed to ${mode === 'edit' ? 'save' : 'create'} album`
|
: `Failed to ${mode === 'edit' ? 'save' : 'create'} album`
|
||||||
|
)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
@ -252,10 +256,6 @@
|
||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
<div class="loading">Loading album...</div>
|
<div class="loading">Loading album...</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#if error}
|
|
||||||
<div class="error-message">{error}</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="tab-panels">
|
<div class="tab-panels">
|
||||||
<!-- Metadata Panel -->
|
<!-- Metadata Panel -->
|
||||||
<div class="panel content-wrapper" class:active={activeTab === 'metadata'}>
|
<div class="panel content-wrapper" class:active={activeTab === 'metadata'}>
|
||||||
|
|
@ -464,16 +464,6 @@
|
||||||
color: $grey-40;
|
color: $grey-40;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
|
||||||
background-color: #fee;
|
|
||||||
color: #d33;
|
|
||||||
padding: $unit-3x;
|
|
||||||
border-radius: $unit;
|
|
||||||
margin-bottom: $unit-4x;
|
|
||||||
max-width: 700px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-section {
|
.form-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import Editor from './Editor.svelte'
|
import Editor from './Editor.svelte'
|
||||||
import Button from './Button.svelte'
|
import Button from './Button.svelte'
|
||||||
import Input from './Input.svelte'
|
import Input from './Input.svelte'
|
||||||
|
import { toast } from '$lib/stores/toast'
|
||||||
import type { JSONContent } from '@tiptap/core'
|
import type { JSONContent } from '@tiptap/core'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -24,8 +25,6 @@
|
||||||
// State
|
// State
|
||||||
let isLoading = $state(false)
|
let isLoading = $state(false)
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let error = $state('')
|
|
||||||
let successMessage = $state('')
|
|
||||||
let activeTab = $state('metadata')
|
let activeTab = $state('metadata')
|
||||||
let showPublishMenu = $state(false)
|
let showPublishMenu = $state(false)
|
||||||
|
|
||||||
|
|
@ -80,14 +79,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
error = 'Title is required'
|
toast.error('Title is required')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingToastId = toast.loading(`${mode === 'edit' ? 'Saving' : 'Creating'} essay...`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
error = ''
|
|
||||||
successMessage = ''
|
|
||||||
|
|
||||||
const auth = localStorage.getItem('admin_auth')
|
const auth = localStorage.getItem('admin_auth')
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
|
|
@ -121,16 +120,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedPost = await response.json()
|
const savedPost = await response.json()
|
||||||
successMessage = `Essay ${mode === 'edit' ? 'saved' : 'created'} successfully!`
|
|
||||||
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.success(`Essay ${mode === 'edit' ? 'saved' : 'created'} successfully!`)
|
||||||
|
|
||||||
setTimeout(() => {
|
if (mode === 'create') {
|
||||||
successMessage = ''
|
goto(`/admin/posts/${savedPost.id}/edit`)
|
||||||
if (mode === 'create') {
|
}
|
||||||
goto(`/admin/posts/${savedPost.id}/edit`)
|
|
||||||
}
|
|
||||||
}, 1500)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = `Failed to ${mode === 'edit' ? 'save' : 'create'} essay`
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.error(`Failed to ${mode === 'edit' ? 'save' : 'create'} essay`)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
@ -196,7 +195,7 @@
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<div class="save-actions">
|
<div class="save-actions">
|
||||||
<Button variant="primary" onclick={handleSave} disabled={isSaving} class="save-button">
|
<Button variant="primary" onclick={handleSave} disabled={isSaving} class="save-button">
|
||||||
{isSaving ? 'Saving...' : status === 'published' ? 'Save' : 'Save Draft'}
|
{status === 'published' ? 'Save' : 'Save Draft'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import AlbumSelector from './AlbumSelector.svelte'
|
import AlbumSelector from './AlbumSelector.svelte'
|
||||||
import AlbumIcon from '$icons/album.svg?component'
|
import AlbumIcon from '$icons/album.svg?component'
|
||||||
import { authenticatedFetch } from '$lib/admin-auth'
|
import { authenticatedFetch } from '$lib/admin-auth'
|
||||||
|
import { toast } from '$lib/stores/toast'
|
||||||
import type { Media } from '@prisma/client'
|
import type { Media } from '@prisma/client'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -22,8 +23,6 @@
|
||||||
let description = $state('')
|
let description = $state('')
|
||||||
let isPhotography = $state(false)
|
let isPhotography = $state(false)
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let error = $state('')
|
|
||||||
let successMessage = $state('')
|
|
||||||
|
|
||||||
// Usage tracking state
|
// Usage tracking state
|
||||||
let usage = $state<
|
let usage = $state<
|
||||||
|
|
@ -51,8 +50,6 @@
|
||||||
if (media) {
|
if (media) {
|
||||||
description = media.description || ''
|
description = media.description || ''
|
||||||
isPhotography = media.isPhotography || false
|
isPhotography = media.isPhotography || false
|
||||||
error = ''
|
|
||||||
successMessage = ''
|
|
||||||
showExif = false
|
showExif = false
|
||||||
loadUsage()
|
loadUsage()
|
||||||
// Only load albums for images
|
// Only load albums for images
|
||||||
|
|
@ -109,8 +106,6 @@
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
description = ''
|
description = ''
|
||||||
isPhotography = false
|
isPhotography = false
|
||||||
error = ''
|
|
||||||
successMessage = ''
|
|
||||||
isOpen = false
|
isOpen = false
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
@ -118,9 +113,10 @@
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
if (!media) return
|
if (!media) return
|
||||||
|
|
||||||
|
const loadingToastId = toast.loading('Saving changes...')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
error = ''
|
|
||||||
|
|
||||||
const response = await authenticatedFetch(`/api/media/${media.id}`, {
|
const response = await authenticatedFetch(`/api/media/${media.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|
@ -139,14 +135,17 @@
|
||||||
|
|
||||||
const updatedMedia = await response.json()
|
const updatedMedia = await response.json()
|
||||||
onUpdate(updatedMedia)
|
onUpdate(updatedMedia)
|
||||||
successMessage = 'Media updated successfully!'
|
|
||||||
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.success('Media updated successfully!')
|
||||||
|
|
||||||
// Auto-close after success
|
// Auto-close after success
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
handleClose()
|
handleClose()
|
||||||
}, 1500)
|
}, 1500)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = 'Failed to update media. Please try again.'
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.error('Failed to update media. Please try again.')
|
||||||
console.error('Failed to update media:', err)
|
console.error('Failed to update media:', err)
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
@ -161,9 +160,10 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingToastId = toast.loading('Deleting media...')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
error = ''
|
|
||||||
|
|
||||||
const response = await authenticatedFetch(`/api/media/${media.id}`, {
|
const response = await authenticatedFetch(`/api/media/${media.id}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
|
|
@ -173,11 +173,15 @@
|
||||||
throw new Error('Failed to delete media')
|
throw new Error('Failed to delete media')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.success('Media deleted successfully')
|
||||||
|
|
||||||
// Close modal and let parent handle the deletion
|
// Close modal and let parent handle the deletion
|
||||||
handleClose()
|
handleClose()
|
||||||
// Note: Parent component should refresh the media list
|
// Note: Parent component should refresh the media list
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = 'Failed to delete media. Please try again.'
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.error('Failed to delete media. Please try again.')
|
||||||
console.error('Failed to delete media:', err)
|
console.error('Failed to delete media:', err)
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
@ -189,16 +193,10 @@
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(media.url)
|
.writeText(media.url)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
successMessage = 'URL copied to clipboard!'
|
toast.success('URL copied to clipboard!')
|
||||||
setTimeout(() => {
|
|
||||||
successMessage = ''
|
|
||||||
}, 2000)
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
error = 'Failed to copy URL'
|
toast.error('Failed to copy URL')
|
||||||
setTimeout(() => {
|
|
||||||
error = ''
|
|
||||||
}, 2000)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -548,15 +546,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-right">
|
<div class="footer-right">
|
||||||
{#if error}
|
|
||||||
<span class="error-text">{error}</span>
|
|
||||||
{/if}
|
|
||||||
{#if successMessage}
|
|
||||||
<span class="success-text">{successMessage}</span>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Button variant="primary" onclick={handleSave} disabled={isSaving}>
|
<Button variant="primary" onclick={handleSave} disabled={isSaving}>
|
||||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1035,16 +1026,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $unit-2x;
|
gap: $unit-2x;
|
||||||
|
|
||||||
.error-text {
|
|
||||||
color: $red-60;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-text {
|
|
||||||
color: #16a34a; // green-600 equivalent
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import Input from './Input.svelte'
|
import Input from './Input.svelte'
|
||||||
import ImageUploader from './ImageUploader.svelte'
|
import ImageUploader from './ImageUploader.svelte'
|
||||||
import Editor from './Editor.svelte'
|
import Editor from './Editor.svelte'
|
||||||
|
import { toast } from '$lib/stores/toast'
|
||||||
import type { JSONContent } from '@tiptap/core'
|
import type { JSONContent } from '@tiptap/core'
|
||||||
import type { Media } from '@prisma/client'
|
import type { Media } from '@prisma/client'
|
||||||
|
|
||||||
|
|
@ -24,7 +25,6 @@
|
||||||
|
|
||||||
// State
|
// State
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let error = $state('')
|
|
||||||
let status = $state<'draft' | 'published'>(initialData?.status || 'draft')
|
let status = $state<'draft' | 'published'>(initialData?.status || 'draft')
|
||||||
|
|
||||||
// Form data
|
// Form data
|
||||||
|
|
@ -81,18 +81,19 @@
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!featuredImage) {
|
if (!featuredImage) {
|
||||||
error = 'Please upload a photo for this post'
|
toast.error('Please upload a photo for this post')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!title.trim()) {
|
if (!title.trim()) {
|
||||||
error = 'Please enter a title for this post'
|
toast.error('Please enter a title for this post')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingToastId = toast.loading(`${status === 'published' ? 'Publishing' : 'Saving'} photo post...`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
error = ''
|
|
||||||
|
|
||||||
// Get editor content
|
// Get editor content
|
||||||
let editorContent = content
|
let editorContent = content
|
||||||
|
|
@ -145,6 +146,9 @@
|
||||||
|
|
||||||
const savedPost = await response.json()
|
const savedPost = await response.json()
|
||||||
|
|
||||||
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.success(`Photo post ${status === 'published' ? 'published' : 'saved'} successfully!`)
|
||||||
|
|
||||||
// Redirect to posts list or edit page
|
// Redirect to posts list or edit page
|
||||||
if (mode === 'create') {
|
if (mode === 'create') {
|
||||||
goto(`/admin/posts/${savedPost.id}/edit`)
|
goto(`/admin/posts/${savedPost.id}/edit`)
|
||||||
|
|
@ -152,7 +156,8 @@
|
||||||
goto('/admin/posts')
|
goto('/admin/posts')
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = `Failed to ${mode === 'edit' ? 'update' : 'create'} photo post`
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.error(`Failed to ${mode === 'edit' ? 'update' : 'create'} photo post`)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
@ -192,7 +197,7 @@
|
||||||
onclick={handlePublish}
|
onclick={handlePublish}
|
||||||
disabled={!featuredImage || !title.trim()}
|
disabled={!featuredImage || !title.trim()}
|
||||||
>
|
>
|
||||||
{isSaving ? 'Publishing...' : 'Publish'}
|
Publish
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
import Button from './Button.svelte'
|
import Button from './Button.svelte'
|
||||||
import StatusDropdown from './StatusDropdown.svelte'
|
import StatusDropdown from './StatusDropdown.svelte'
|
||||||
import { projectSchema } from '$lib/schemas/project'
|
import { projectSchema } from '$lib/schemas/project'
|
||||||
|
import { toast } from '$lib/stores/toast'
|
||||||
import type { Project, ProjectFormData } from '$lib/types/project'
|
import type { Project, ProjectFormData } from '$lib/types/project'
|
||||||
import { defaultProjectFormData } from '$lib/types/project'
|
import { defaultProjectFormData } from '$lib/types/project'
|
||||||
|
|
||||||
|
|
@ -24,8 +25,6 @@
|
||||||
// State
|
// State
|
||||||
let isLoading = $state(mode === 'edit')
|
let isLoading = $state(mode === 'edit')
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let error = $state('')
|
|
||||||
let successMessage = $state('')
|
|
||||||
let activeTab = $state('metadata')
|
let activeTab = $state('metadata')
|
||||||
let validationErrors = $state<Record<string, string>>({})
|
let validationErrors = $state<Record<string, string>>({})
|
||||||
|
|
||||||
|
|
@ -117,14 +116,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateForm()) {
|
if (!validateForm()) {
|
||||||
error = 'Please fix the validation errors'
|
toast.error('Please fix the validation errors')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingToastId = toast.loading(`${mode === 'edit' ? 'Saving' : 'Creating'} project...`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
error = ''
|
|
||||||
successMessage = ''
|
|
||||||
|
|
||||||
const auth = localStorage.getItem('admin_auth')
|
const auth = localStorage.getItem('admin_auth')
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
|
|
@ -173,16 +172,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedProject = await response.json()
|
const savedProject = await response.json()
|
||||||
successMessage = `Project ${mode === 'edit' ? 'saved' : 'created'} successfully!`
|
|
||||||
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.success(`Project ${mode === 'edit' ? 'saved' : 'created'} successfully!`)
|
||||||
|
|
||||||
setTimeout(() => {
|
if (mode === 'create') {
|
||||||
successMessage = ''
|
goto(`/admin/projects/${savedProject.id}/edit`)
|
||||||
if (mode === 'create') {
|
}
|
||||||
goto(`/admin/projects/${savedProject.id}/edit`)
|
|
||||||
}
|
|
||||||
}, 1500)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = `Failed to ${mode === 'edit' ? 'save' : 'create'} project`
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.error(`Failed to ${mode === 'edit' ? 'save' : 'create'} project`)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import Editor from './Editor.svelte'
|
import Editor from './Editor.svelte'
|
||||||
import Button from './Button.svelte'
|
import Button from './Button.svelte'
|
||||||
import Input from './Input.svelte'
|
import Input from './Input.svelte'
|
||||||
|
import { toast } from '$lib/stores/toast'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
postType: 'post'
|
postType: 'post'
|
||||||
|
|
@ -23,7 +24,6 @@
|
||||||
|
|
||||||
// State
|
// State
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let error = $state('')
|
|
||||||
let status = $state<'draft' | 'published'>(initialData?.status || 'draft')
|
let status = $state<'draft' | 'published'>(initialData?.status || 'draft')
|
||||||
|
|
||||||
// Form data
|
// Form data
|
||||||
|
|
@ -53,19 +53,20 @@
|
||||||
|
|
||||||
async function handleSave(publishStatus: 'draft' | 'published') {
|
async function handleSave(publishStatus: 'draft' | 'published') {
|
||||||
if (isOverLimit) {
|
if (isOverLimit) {
|
||||||
error = 'Post is too long'
|
toast.error('Post is too long')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// For link posts, URL is required
|
// For link posts, URL is required
|
||||||
if (linkUrl && !linkUrl.trim()) {
|
if (linkUrl && !linkUrl.trim()) {
|
||||||
error = 'Link URL is required'
|
toast.error('Link URL is required')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingToastId = toast.loading(`${publishStatus === 'published' ? 'Publishing' : 'Saving'} post...`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
error = ''
|
|
||||||
|
|
||||||
const auth = localStorage.getItem('admin_auth')
|
const auth = localStorage.getItem('admin_auth')
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
|
|
@ -104,10 +105,14 @@
|
||||||
|
|
||||||
const savedPost = await response.json()
|
const savedPost = await response.json()
|
||||||
|
|
||||||
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.success(`Post ${publishStatus === 'published' ? 'published' : 'saved'} successfully!`)
|
||||||
|
|
||||||
// Redirect back to posts list after creation
|
// Redirect back to posts list after creation
|
||||||
goto('/admin/posts')
|
goto('/admin/posts')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = `Failed to ${mode === 'edit' ? 'save' : 'create'} post`
|
toast.dismiss(loadingToastId)
|
||||||
|
toast.error(`Failed to ${mode === 'edit' ? 'save' : 'create'} post`)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
@ -146,16 +151,12 @@
|
||||||
onclick={() => handleSave('published')}
|
onclick={() => handleSave('published')}
|
||||||
disabled={isSaving || !hasContent() || (postType === 'microblog' && isOverLimit)}
|
disabled={isSaving || !hasContent() || (postType === 'microblog' && isOverLimit)}
|
||||||
>
|
>
|
||||||
{isSaving ? 'Posting...' : 'Post'}
|
Post
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="composer-container">
|
<div class="composer-container">
|
||||||
{#if error}
|
|
||||||
<div class="error-message">{error}</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="composer">
|
<div class="composer">
|
||||||
{#if postType === 'microblog'}
|
{#if postType === 'microblog'}
|
||||||
<div class="post-composer">
|
<div class="post-composer">
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
onclick={handlePrimaryAction}
|
onclick={handlePrimaryAction}
|
||||||
disabled={disabled || isLoading}
|
disabled={disabled || isLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? `${primaryAction.label.replace(/e$/, 'ing')}...` : primaryAction.label}
|
{primaryAction.label}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{#if hasDropdownContent}
|
{#if hasDropdownContent}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue