mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
Generated by Spark: A snippet card can render stuff - see if we can make it more robust
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Copy, Pencil, Trash, Check, ArrowsOut, SplitVertical } from '@phosphor-icons/react'
|
||||
import { Copy, Pencil, Trash, Check, ArrowsOut, SplitVertical, WarningCircle } from '@phosphor-icons/react'
|
||||
import { Snippet, LANGUAGE_COLORS } from '@/lib/types'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
@@ -16,16 +16,101 @@ interface SnippetCardProps {
|
||||
|
||||
export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: SnippetCardProps) {
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const [hasRenderError, setHasRenderError] = useState(false)
|
||||
|
||||
const handleCopy = () => {
|
||||
const handleCopy = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
onCopy(snippet.code)
|
||||
setIsCopied(true)
|
||||
setTimeout(() => setIsCopied(false), 2000)
|
||||
}
|
||||
|
||||
const truncatedCode = snippet.code.length > 200
|
||||
? snippet.code.slice(0, 200) + '...'
|
||||
: snippet.code
|
||||
const handleEdit = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
onEdit(snippet)
|
||||
}
|
||||
|
||||
const handleDelete = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
onDelete(snippet.id)
|
||||
}
|
||||
|
||||
const safeSnippet = useMemo(() => {
|
||||
try {
|
||||
const title = snippet?.title ?? 'Untitled Snippet'
|
||||
const description = snippet?.description ?? ''
|
||||
const code = snippet?.code ?? ''
|
||||
const language = snippet?.language ?? 'Other'
|
||||
const updatedAt = snippet?.updatedAt ?? Date.now()
|
||||
|
||||
const truncatedCode = code.length > 200
|
||||
? code.slice(0, 200) + '...'
|
||||
: code
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
code,
|
||||
truncatedCode,
|
||||
language,
|
||||
updatedAt,
|
||||
hasPreview: snippet?.hasPreview ?? false,
|
||||
isTruncated: code.length > 200
|
||||
}
|
||||
} catch (err) {
|
||||
setHasRenderError(true)
|
||||
return {
|
||||
title: 'Error Loading Snippet',
|
||||
description: 'This snippet contains invalid data',
|
||||
code: '',
|
||||
truncatedCode: '',
|
||||
language: 'Other',
|
||||
updatedAt: Date.now(),
|
||||
hasPreview: false,
|
||||
isTruncated: false
|
||||
}
|
||||
}
|
||||
}, [snippet])
|
||||
|
||||
if (hasRenderError) {
|
||||
return (
|
||||
<Card className="group relative overflow-hidden border-destructive/50 bg-destructive/5">
|
||||
<div className="p-5 space-y-3">
|
||||
<div className="flex items-center gap-3 text-destructive">
|
||||
<WarningCircle className="h-5 w-5 shrink-0" weight="fill" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-lg truncate">
|
||||
{safeSnippet.title}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Unable to render this snippet
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleEdit}
|
||||
className="gap-2"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
Try to Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleDelete}
|
||||
className="gap-2 text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -38,16 +123,16 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-lg text-foreground truncate mb-1">
|
||||
{snippet.title}
|
||||
{safeSnippet.title}
|
||||
</h3>
|
||||
{snippet.description && (
|
||||
{safeSnippet.description && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{snippet.description}
|
||||
{safeSnippet.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 shrink-0 items-start">
|
||||
{snippet.hasPreview && (
|
||||
{safeSnippet.hasPreview && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-accent/30 bg-accent/10 text-accent text-xs px-2 py-1 gap-1"
|
||||
@@ -60,10 +145,10 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"border font-medium text-xs px-2 py-1",
|
||||
LANGUAGE_COLORS[snippet.language] || LANGUAGE_COLORS['Other']
|
||||
LANGUAGE_COLORS[safeSnippet.language] || LANGUAGE_COLORS['Other']
|
||||
)}
|
||||
>
|
||||
{snippet.language}
|
||||
{safeSnippet.language}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,18 +156,27 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
|
||||
<div
|
||||
className="relative rounded-md border border-border bg-secondary/30 p-3 overflow-hidden cursor-pointer hover:border-accent/50 transition-colors"
|
||||
onClick={() => onView(snippet)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
onView(snippet)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<pre className="text-sm text-foreground/90 line-clamp-6">
|
||||
<code className="font-mono">{truncatedCode}</code>
|
||||
<pre className="text-sm text-foreground/90 line-clamp-6 whitespace-pre-wrap break-words">
|
||||
<code className="font-mono">{safeSnippet.truncatedCode}</code>
|
||||
</pre>
|
||||
{snippet.code.length > 200 && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-secondary/30 to-transparent" />
|
||||
{safeSnippet.isTruncated && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-secondary/30 to-transparent pointer-events-none" />
|
||||
)}
|
||||
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 gap-2 bg-background/80 backdrop-blur-sm"
|
||||
className="h-8 gap-2 bg-background/80 backdrop-blur-sm pointer-events-none"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<ArrowsOut className="h-4 w-4" />
|
||||
View
|
||||
@@ -92,7 +186,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(snippet.updatedAt).toLocaleDateString()}
|
||||
{new Date(safeSnippet.updatedAt).toLocaleDateString()}
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
@@ -100,6 +194,7 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
|
||||
size="sm"
|
||||
onClick={handleCopy}
|
||||
className="h-8 w-8 p-0 hover:bg-accent/20 hover:text-accent"
|
||||
aria-label="Copy code"
|
||||
>
|
||||
{isCopied ? (
|
||||
<Check className="h-4 w-4 text-accent" weight="bold" />
|
||||
@@ -110,16 +205,18 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onEdit(snippet)}
|
||||
onClick={handleEdit}
|
||||
className="h-8 w-8 p-0 hover:bg-primary/20 hover:text-primary"
|
||||
aria-label="Edit snippet"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onDelete(snippet.id)}
|
||||
onClick={handleDelete}
|
||||
className="h-8 w-8 p-0 hover:bg-destructive/20 hover:text-destructive"
|
||||
aria-label="Delete snippet"
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user