diff --git a/app/Http/Controllers/Api/ArtworkInteractionController.php b/app/Http/Controllers/Api/ArtworkInteractionController.php
index bc8a6d26..d063df88 100644
--- a/app/Http/Controllers/Api/ArtworkInteractionController.php
+++ b/app/Http/Controllers/Api/ArtworkInteractionController.php
@@ -20,11 +20,11 @@ final class ArtworkInteractionController extends Controller
$this->toggleSimple(
request: $request,
- table: 'user_favorites',
+ table: 'artwork_favourites',
keyColumns: ['user_id', 'artwork_id'],
keyValues: ['user_id' => (int) $request->user()->id, 'artwork_id' => $artworkId],
- insertPayload: ['created_at' => now()],
- requiredTable: 'user_favorites'
+ insertPayload: ['created_at' => now(), 'updated_at' => now()],
+ requiredTable: 'artwork_favourites'
);
$this->syncArtworkStats($artworkId);
@@ -154,8 +154,8 @@ final class ArtworkInteractionController extends Controller
return;
}
- $favorites = Schema::hasTable('user_favorites')
- ? (int) DB::table('user_favorites')->where('artwork_id', $artworkId)->count()
+ $favorites = Schema::hasTable('artwork_favourites')
+ ? (int) DB::table('artwork_favourites')->where('artwork_id', $artworkId)->count()
: 0;
$likes = Schema::hasTable('artwork_likes')
@@ -167,23 +167,22 @@ final class ArtworkInteractionController extends Controller
[
'favorites' => $favorites,
'rating_count' => $likes,
- 'updated_at' => now(),
]
);
}
private function statusPayload(int $viewerId, int $artworkId): array
{
- $isFavorited = Schema::hasTable('user_favorites')
- ? DB::table('user_favorites')->where('user_id', $viewerId)->where('artwork_id', $artworkId)->exists()
+ $isFavorited = Schema::hasTable('artwork_favourites')
+ ? DB::table('artwork_favourites')->where('user_id', $viewerId)->where('artwork_id', $artworkId)->exists()
: false;
$isLiked = Schema::hasTable('artwork_likes')
? DB::table('artwork_likes')->where('user_id', $viewerId)->where('artwork_id', $artworkId)->exists()
: false;
- $favorites = Schema::hasTable('user_favorites')
- ? (int) DB::table('user_favorites')->where('artwork_id', $artworkId)->count()
+ $favorites = Schema::hasTable('artwork_favourites')
+ ? (int) DB::table('artwork_favourites')->where('artwork_id', $artworkId)->count()
: 0;
$likes = Schema::hasTable('artwork_likes')
diff --git a/resources/js/components/artwork/ArtworkActionBar.jsx b/resources/js/components/artwork/ArtworkActionBar.jsx
index b961d720..98a9ad87 100644
--- a/resources/js/components/artwork/ArtworkActionBar.jsx
+++ b/resources/js/components/artwork/ArtworkActionBar.jsx
@@ -1,4 +1,5 @@
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
+import { createPortal } from 'react-dom'
function formatCount(value) {
const n = Number(value || 0)
@@ -64,17 +65,147 @@ function FlagIcon() {
)
}
+/* ── Report Modal ──────────────────────────────────────────────────────────── */
+const REPORT_REASONS = [
+ 'Inappropriate content',
+ 'Copyright violation',
+ 'Spam or misleading',
+ 'Offensive or abusive',
+]
+
+function ReportModal({ open, onClose, onSubmit, submitting }) {
+ const [selected, setSelected] = useState('')
+ const [details, setDetails] = useState('')
+ const backdropRef = useRef(null)
+ const inputRef = useRef(null)
+
+ // Reset & focus when opening
+ useEffect(() => {
+ if (open) {
+ setSelected('')
+ setDetails('')
+ const t = setTimeout(() => inputRef.current?.focus(), 80)
+ return () => clearTimeout(t)
+ }
+ }, [open])
+
+ // Close on Escape
+ useEffect(() => {
+ if (!open) return
+ const handler = (e) => { if (e.key === 'Escape') onClose() }
+ window.addEventListener('keydown', handler)
+ return () => window.removeEventListener('keydown', handler)
+ }, [open, onClose])
+
+ if (!open) return null
+
+ const trimmedDetails = details.trim()
+ const canSubmit = selected.length > 0 && trimmedDetails.length >= 10 && !submitting
+ const fullReason = `${selected}: ${trimmedDetails}`
+
+ return createPortal(
+
{ if (e.target === backdropRef.current) onClose() }}
+ className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4"
+ >
+
+ {/* Header */}
+
+
Report Artwork
+
+
+
+ {/* Body */}
+
+ {/* Step 1 — pick a reason */}
+
+
+
+ {REPORT_REASONS.map((r) => (
+
+ ))}
+
+
+
+ {/* Step 2 — describe & prove */}
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
,
+ document.body
+ )
+}
+
export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStatsChange }) {
- const [liked, setLiked] = useState(Boolean(artwork?.viewer?.is_liked))
const [favorited, setFavorited] = useState(Boolean(artwork?.viewer?.is_favorited))
const [downloading, setDownloading] = useState(false)
const [reporting, setReporting] = useState(false)
+ const [reported, setReported] = useState(false)
+ const [reportOpen, setReportOpen] = useState(false)
const [copied, setCopied] = useState(false)
useEffect(() => {
- setLiked(Boolean(artwork?.viewer?.is_liked))
setFavorited(Boolean(artwork?.viewer?.is_favorited))
- }, [artwork?.id, artwork?.viewer?.is_liked, artwork?.viewer?.is_favorited])
+ }, [artwork?.id, artwork?.viewer?.is_favorited])
const fallbackUrl = artwork?.thumbs?.xl?.url || artwork?.thumbs?.lg?.url || artwork?.file?.url || '#'
const shareUrl = canonicalUrl || artwork?.canonical_url || (typeof window !== 'undefined' ? window.location.href : '#')
@@ -132,15 +263,6 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats
}
}
- const onToggleLike = async () => {
- const nextState = !liked
- setLiked(nextState)
- try {
- await postInteraction(`/api/artworks/${artwork.id}/like`, { state: nextState })
- onStatsChange?.({ likes: nextState ? 1 : -1 })
- } catch { setLiked(!nextState) }
- }
-
const onToggleFavorite = async () => {
const nextState = !favorited
setFavorited(nextState)
@@ -162,16 +284,22 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats
} catch { /* noop */ }
}
- const onReport = async () => {
+ const openReport = () => {
+ if (reported) return
+ setReportOpen(true)
+ }
+
+ const submitReport = async (reason) => {
if (reporting) return
setReporting(true)
try {
- await postInteraction(`/api/artworks/${artwork.id}/report`, { reason: 'Reported from artwork page' })
+ await postInteraction(`/api/artworks/${artwork.id}/report`, { reason })
+ setReported(true)
+ setReportOpen(false)
} catch { /* noop */ }
finally { setReporting(false) }
}
- const likeCount = formatCount(stats?.likes ?? artwork?.stats?.likes ?? 0)
const favCount = formatCount(stats?.favorites ?? artwork?.stats?.favorites ?? 0)
const viewCount = formatCount(stats?.views ?? artwork?.stats?.views ?? 0)
@@ -179,35 +307,19 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats
<>
{/* ── Desktop centered bar ────────────────────────────────────── */}
- {/* Like stat pill */}
+ {/* Favourite (heart) stat pill */}
-
- {/* Favorite/bookmark stat pill */}
-
@@ -232,12 +344,17 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats
{/* Download button */}
@@ -258,31 +375,16 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats
-
-
@@ -300,9 +402,14 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats
@@ -319,6 +426,14 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats
+
+ {/* Report modal */}
+ setReportOpen(false)}
+ onSubmit={submitReport}
+ submitting={reporting}
+ />
>
)
}