feat(04-02): thumbnail PostCard e hint Unsplash in OutputReview

- PostCard: thumbnail 80x56px della cover image quando keyword e' URL
  - Rilevamento con startsWith('http')
  - object-cover, loading=lazy, onError nasconde se URL non valido
  - Posizionato dopo cover_title e prima dei metadati secondari
- OutputReview: hint discreto Unsplash sotto il box info edit inline
  - Visibile solo se unsplash_api_key_configured === false
  - Link a /impostazioni con stile amber discreto
  - Scompare automaticamente dopo configurazione Unsplash
  - Usa Link da react-router-dom (pattern codebase)
This commit is contained in:
Michele
2026-03-09 08:16:55 +01:00
parent d537c03706
commit f154f1b2f6
2 changed files with 30 additions and 2 deletions

View File

@@ -181,6 +181,19 @@ export function PostCard({
{post.cover_title} {post.cover_title}
</p> </p>
{/* Thumbnail cover image (solo se keyword e' un URL reale) */}
{post.cover_image_keyword.startsWith('http') && (
<div className="mt-2 mb-1">
<img
src={post.cover_image_keyword}
alt="Cover preview"
loading="lazy"
className="w-20 h-14 object-cover rounded-md border border-stone-700"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
/>
</div>
)}
{/* Metadati secondari */} {/* Metadati secondari */}
{slot && ( {slot && (
<div className="flex flex-wrap gap-x-3 gap-y-0.5 mt-2"> <div className="flex flex-wrap gap-x-3 gap-y-0.5 mt-2">

View File

@@ -12,8 +12,8 @@
import { Download, Loader2, RefreshCw } from 'lucide-react' import { Download, Loader2, RefreshCw } from 'lucide-react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom' import { Link, useParams } from 'react-router-dom'
import { useDownloadEditedCsv, useJobResults } from '../api/hooks' import { useDownloadEditedCsv, useJobResults, useSettingsStatus } from '../api/hooks'
import { PostCard } from '../components/PostCard' import { PostCard } from '../components/PostCard'
import type { PostResult } from '../types' import type { PostResult } from '../types'
@@ -21,6 +21,7 @@ export function OutputReview() {
const { jobId } = useParams<{ jobId: string }>() const { jobId } = useParams<{ jobId: string }>()
const { data: jobData, isLoading, error } = useJobResults(jobId ?? null) const { data: jobData, isLoading, error } = useJobResults(jobId ?? null)
const downloadMutation = useDownloadEditedCsv() const downloadMutation = useDownloadEditedCsv()
const { data: settingsStatus } = useSettingsStatus()
// Stato locale dei post — viene aggiornato da edit inline e rigenerazione // Stato locale dei post — viene aggiornato da edit inline e rigenerazione
const [localResults, setLocalResults] = useState<PostResult[]>([]) const [localResults, setLocalResults] = useState<PostResult[]>([])
@@ -156,6 +157,20 @@ export function OutputReview() {
Le modifiche saranno incluse nel CSV scaricato. Le modifiche saranno incluse nel CSV scaricato.
</div> </div>
{/* Hint Unsplash — visibile solo se non configurato */}
{settingsStatus && !settingsStatus.unsplash_api_key_configured && (
<div className="px-4 py-2 rounded-lg bg-stone-800/30 border border-stone-700/50 text-xs text-stone-600 flex items-center gap-2 flex-wrap">
<span>Le colonne immagine contengono keyword testuali.</span>
<Link
to="/impostazioni"
className="text-amber-500/70 hover:text-amber-400 underline underline-offset-2 transition-colors"
>
Configura Unsplash
</Link>
<span>per URL immagini reali nel CSV.</span>
</div>
)}
{/* Griglia post */} {/* Griglia post */}
<div className="space-y-3"> <div className="space-y-3">
{localResults.length === 0 ? ( {localResults.length === 0 ? (