add delete

This commit is contained in:
songtianlun 2025-08-03 13:25:06 +08:00
parent 19661710c1
commit 6fecf02a46
4 changed files with 277 additions and 51 deletions

View File

@ -198,6 +198,11 @@ export default function StudioPage() {
router.push(`/studio/${id}`) router.push(`/studio/${id}`)
} }
const handleDeletePrompt = (id: string) => {
// Remove the prompt from the local state
setPrompts(prev => prev.filter(p => p.id !== id))
}
const handleUpdatePermissions = async (promptId: string, newPermissions: 'private' | 'public') => { const handleUpdatePermissions = async (promptId: string, newPermissions: 'private' | 'public') => {
if (!user) return if (!user) return
@ -753,6 +758,7 @@ export default function StudioPage() {
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
onClose={handleEditModalClose} onClose={handleEditModalClose}
onSave={handleEditModalSave} onSave={handleEditModalSave}
onDelete={handleDeletePrompt}
userId={user?.id || ''} userId={user?.id || ''}
/> />
)} )}
@ -764,6 +770,8 @@ export default function StudioPage() {
onClose={handleDetailModalClose} onClose={handleDetailModalClose}
onEdit={handleEditPrompt} onEdit={handleEditPrompt}
onDebug={handleDebugPrompt} onDebug={handleDebugPrompt}
onDelete={handleDeletePrompt}
userId={user?.id}
/> />
{/* Bulk Add Modal */} {/* Bulk Add Modal */}

View File

@ -7,11 +7,13 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea' import { Textarea } from '@/components/ui/textarea'
import { LoadingSpinner } from '@/components/ui/loading-spinner' import { LoadingSpinner } from '@/components/ui/loading-spinner'
import { DeleteConfirmationDialog } from '@/components/ui/delete-confirmation-dialog'
import { import {
X, X,
Save, Save,
Tag, Tag,
Plus Plus,
Trash2
} from 'lucide-react' } from 'lucide-react'
interface Prompt { interface Prompt {
@ -29,6 +31,7 @@ interface EditPromptModalProps {
isOpen: boolean isOpen: boolean
onClose: () => void onClose: () => void
onSave: (updatedPrompt: Prompt) => void onSave: (updatedPrompt: Prompt) => void
onDelete?: (id: string) => void
userId: string userId: string
} }
@ -37,6 +40,7 @@ export function EditPromptModal({
isOpen, isOpen,
onClose, onClose,
onSave, onSave,
onDelete,
userId userId
}: EditPromptModalProps) { }: EditPromptModalProps) {
const t = useTranslations('studio') const t = useTranslations('studio')
@ -48,6 +52,8 @@ export function EditPromptModal({
const [newTag, setNewTag] = useState('') const [newTag, setNewTag] = useState('')
const [isSaving, setIsSaving] = useState(false) const [isSaving, setIsSaving] = useState(false)
const [availableTags, setAvailableTags] = useState<string[]>([]) const [availableTags, setAvailableTags] = useState<string[]>([])
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
@ -114,6 +120,29 @@ export function EditPromptModal({
} }
} }
const handleDelete = async () => {
if (!onDelete) return
setIsDeleting(true)
try {
const response = await fetch(`/api/prompts/${prompt.id}?userId=${userId}`, {
method: 'DELETE'
})
if (response.ok) {
onDelete(prompt.id)
setIsDeleteDialogOpen(false)
onClose()
} else {
console.error('Failed to delete prompt')
}
} catch (error) {
console.error('Error deleting prompt:', error)
} finally {
setIsDeleting(false)
}
}
const handleKeyPress = (e: React.KeyboardEvent) => { const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
handleSave() handleSave()
@ -258,28 +287,54 @@ export function EditPromptModal({
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-end space-x-2 p-6 border-t border-border"> <div className="flex items-center justify-between p-6 border-t border-border">
<Button {/* Delete button on the left */}
variant="outline" {onDelete && (
onClick={onClose} <Button
disabled={isSaving} variant="outline"
> onClick={() => setIsDeleteDialogOpen(true)}
{tCommon('cancel')} disabled={isSaving || isDeleting}
</Button> className="flex items-center space-x-2 text-destructive hover:text-destructive hover:bg-destructive/10"
<Button >
onClick={handleSave} <Trash2 className="h-4 w-4" />
disabled={!name.trim() || isSaving} <span>{tCommon('delete')}</span>
className="flex items-center space-x-2" </Button>
> )}
{isSaving ? (
<LoadingSpinner size="sm" /> {/* Save and Cancel buttons on the right */}
) : ( <div className="flex items-center space-x-2 ml-auto">
<Save className="h-4 w-4" /> <Button
)} variant="outline"
<span>{tCommon('save')}</span> onClick={onClose}
</Button> disabled={isSaving || isDeleting}
>
{tCommon('cancel')}
</Button>
<Button
onClick={handleSave}
disabled={!name.trim() || isSaving || isDeleting}
className="flex items-center space-x-2"
>
{isSaving ? (
<LoadingSpinner size="sm" />
) : (
<Save className="h-4 w-4" />
)}
<span>{tCommon('save')}</span>
</Button>
</div>
</div> </div>
</div> </div>
{/* Delete Confirmation Dialog */}
<DeleteConfirmationDialog
isOpen={isDeleteDialogOpen}
onClose={() => setIsDeleteDialogOpen(false)}
onConfirm={handleDelete}
title={t('deletePrompt')}
itemName={prompt.name}
isDeleting={isDeleting}
/>
</div> </div>
) )
} }

View File

@ -1,8 +1,10 @@
'use client' 'use client'
import { useState } from 'react'
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { X, Edit, Play, Calendar, Tag } from 'lucide-react' import { DeleteConfirmationDialog } from '@/components/ui/delete-confirmation-dialog'
import { X, Edit, Play, Calendar, Tag, Trash2 } from 'lucide-react'
interface Prompt { interface Prompt {
id: string id: string
@ -23,6 +25,8 @@ interface PromptDetailModalProps {
onClose: () => void onClose: () => void
onEdit: (id: string) => void onEdit: (id: string) => void
onDebug: (id: string) => void onDebug: (id: string) => void
onDelete?: (id: string) => void
userId?: string
} }
export function PromptDetailModal({ export function PromptDetailModal({
@ -30,11 +34,16 @@ export function PromptDetailModal({
isOpen, isOpen,
onClose, onClose,
onEdit, onEdit,
onDebug onDebug,
onDelete,
userId
}: PromptDetailModalProps) { }: PromptDetailModalProps) {
const t = useTranslations('studio') const t = useTranslations('studio')
const tCommon = useTranslations('common') const tCommon = useTranslations('common')
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const formatDate = (dateString: string) => { const formatDate = (dateString: string) => {
return new Intl.DateTimeFormat('default', { return new Intl.DateTimeFormat('default', {
year: 'numeric', year: 'numeric',
@ -45,6 +54,29 @@ export function PromptDetailModal({
}).format(new Date(dateString)) }).format(new Date(dateString))
} }
const handleDelete = async () => {
if (!prompt || !userId || !onDelete) return
setIsDeleting(true)
try {
const response = await fetch(`/api/prompts/${prompt.id}?userId=${userId}`, {
method: 'DELETE'
})
if (response.ok) {
onDelete(prompt.id)
setIsDeleteDialogOpen(false)
onClose()
} else {
console.error('Failed to delete prompt')
}
} catch (error) {
console.error('Error deleting prompt:', error)
} finally {
setIsDeleting(false)
}
}
if (!isOpen || !prompt) return null if (!isOpen || !prompt) return null
return ( return (
@ -147,36 +179,61 @@ export function PromptDetailModal({
</div> </div>
{/* Footer Actions */} {/* Footer Actions */}
<div className="flex items-center justify-end space-x-3 p-6 border-t border-border"> <div className="flex items-center justify-between p-6 border-t border-border">
<Button {/* Delete button on the left */}
variant="outline" {onDelete && userId && (
onClick={onClose} <Button
> variant="outline"
{tCommon('close')} onClick={() => setIsDeleteDialogOpen(true)}
</Button> className="flex items-center space-x-2 text-destructive hover:text-destructive hover:bg-destructive/10"
<Button >
variant="outline" <Trash2 className="h-4 w-4" />
onClick={() => { <span>{tCommon('delete')}</span>
onEdit(prompt.id) </Button>
onClose() )}
}}
className="flex items-center space-x-2" {/* Other actions on the right */}
> <div className="flex items-center space-x-3 ml-auto">
<Edit className="h-4 w-4" /> <Button
<span>{tCommon('edit')}</span> variant="outline"
</Button> onClick={onClose}
<Button >
onClick={() => { {tCommon('close')}
onDebug(prompt.id) </Button>
onClose() <Button
}} variant="outline"
className="flex items-center space-x-2" onClick={() => {
> onEdit(prompt.id)
<Play className="h-4 w-4" /> onClose()
<span>{t('debugPrompt')}</span> }}
</Button> className="flex items-center space-x-2"
>
<Edit className="h-4 w-4" />
<span>{tCommon('edit')}</span>
</Button>
<Button
onClick={() => {
onDebug(prompt.id)
onClose()
}}
className="flex items-center space-x-2"
>
<Play className="h-4 w-4" />
<span>{t('debugPrompt')}</span>
</Button>
</div>
</div> </div>
</div> </div>
{/* Delete Confirmation Dialog */}
<DeleteConfirmationDialog
isOpen={isDeleteDialogOpen}
onClose={() => setIsDeleteDialogOpen(false)}
onConfirm={handleDelete}
title={t('deletePrompt')}
itemName={prompt?.name}
isDeleting={isDeleting}
/>
</div> </div>
) )
} }

View File

@ -0,0 +1,106 @@
'use client'
import { useTranslations } from 'next-intl'
import { Button } from '@/components/ui/button'
import { LoadingSpinner } from '@/components/ui/loading-spinner'
import { AlertTriangle, X } from 'lucide-react'
interface DeleteConfirmationDialogProps {
isOpen: boolean
onClose: () => void
onConfirm: () => void
title: string
description?: string
itemName?: string
isDeleting?: boolean
}
export function DeleteConfirmationDialog({
isOpen,
onClose,
onConfirm,
title,
description,
itemName,
isDeleting = false
}: DeleteConfirmationDialogProps) {
const t = useTranslations('studio')
const tCommon = useTranslations('common')
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50"
onClick={onClose}
/>
{/* Dialog */}
<div className="relative bg-background border border-border rounded-lg shadow-lg max-w-md w-full mx-4">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-border">
<div className="flex items-center space-x-3">
<div className="flex-shrink-0 w-10 h-10 bg-destructive/10 rounded-full flex items-center justify-center">
<AlertTriangle className="h-5 w-5 text-destructive" />
</div>
<h2 className="text-lg font-semibold text-foreground">
{title}
</h2>
</div>
<Button
variant="ghost"
size="sm"
onClick={onClose}
className="h-8 w-8 p-0"
disabled={isDeleting}
>
<X className="h-4 w-4" />
</Button>
</div>
{/* Content */}
<div className="p-6 space-y-4">
<div className="text-sm text-muted-foreground">
{description || t('confirmDelete')}
</div>
{itemName && (
<div className="bg-muted rounded-lg p-3">
<div className="text-sm font-medium text-foreground">
{itemName}
</div>
</div>
)}
<div className="text-sm text-destructive font-medium">
{t('deleteWarning')}
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-end space-x-3 p-6 border-t border-border">
<Button
variant="outline"
onClick={onClose}
disabled={isDeleting}
>
{tCommon('cancel')}
</Button>
<Button
variant="destructive"
onClick={onConfirm}
disabled={isDeleting}
className="flex items-center space-x-2"
>
{isDeleting ? (
<LoadingSpinner size="sm" />
) : null}
<span>{tCommon('delete')}</span>
</Button>
</div>
</div>
</div>
)
}