@@ -1,13 +1,19 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Globe, Users } from "lucide-react"
|
||||
import { StarDisplay } from "@/components/star-display"
|
||||
import { SpoilerVeil } from "@/components/spoiler-veil"
|
||||
import { WatchMediumBadge } from "@/components/watch-medium-badge"
|
||||
import { EmptyState } from "@/components/empty-state"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { timeAgo } from "@/lib/date"
|
||||
import type { SocialReviewDto } from "@/features/movies"
|
||||
|
||||
export function CommunityReviews({ reviews, onShowDetail }: { reviews: { items: SocialReviewDto[] }; onShowDetail?: (review: SocialReviewDto) => void }) {
|
||||
type CommunityReviewsProps = {
|
||||
reviews: { items: SocialReviewDto[] }
|
||||
onShowDetail?: (review: SocialReviewDto) => void
|
||||
}
|
||||
|
||||
export function CommunityReviews({ reviews, onShowDetail }: CommunityReviewsProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
@@ -17,36 +23,43 @@ export function CommunityReviews({ reviews, onShowDetail }: { reviews: { items:
|
||||
<EmptyState icon={Users} title={t("movie.noReviews")} description={t("movie.beFirst")} />
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{reviews.items.map((r, i) => (
|
||||
<Card key={i} size="sm">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-1.5 text-sm">
|
||||
{r.user_display}
|
||||
{r.is_federated && <Globe className="size-3 text-muted-foreground/60" />}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-[10px]">{timeAgo(r.watched_at)}</CardDescription>
|
||||
{reviews.items.map((r, i) => {
|
||||
const veiled = !!r.spoilers
|
||||
return (
|
||||
<Card key={i} size="sm">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-1.5 text-sm">
|
||||
{r.user_display}
|
||||
{r.is_federated && <Globe className="size-3 text-muted-foreground/60" />}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-[10px]">{timeAgo(r.watched_at)}</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<StarDisplay rating={r.rating} size="xs" />
|
||||
{r.watch_medium && <WatchMediumBadge medium={r.watch_medium} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<StarDisplay rating={r.rating} size="xs" />
|
||||
{r.watch_medium && <WatchMediumBadge medium={r.watch_medium} />}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{r.comment && (
|
||||
<CardContent>
|
||||
<p
|
||||
className="text-xs text-muted-foreground"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? () => onShowDetail(r) : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail(r) : undefined}
|
||||
>{r.comment}</p>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
</CardHeader>
|
||||
{r.comment && (
|
||||
<CardContent>
|
||||
{veiled ? (
|
||||
<SpoilerVeil content={r.comment} className="text-xs text-muted-foreground" />
|
||||
) : (
|
||||
<p
|
||||
className="text-xs text-muted-foreground"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={veiled ? undefined : onShowDetail ? () => onShowDetail(r) : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail(r) : undefined}
|
||||
>{r.comment}</p>
|
||||
)}
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Link } from "@tanstack/react-router"
|
||||
import { StarDisplay } from "@/components/star-display"
|
||||
import { SpoilerVeil } from "@/components/spoiler-veil"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { posterUrl } from "@/lib/api/client"
|
||||
import type { MovieDto } from "@/lib/api/common"
|
||||
@@ -11,10 +12,13 @@ type MovieCardProps = {
|
||||
subtitle?: React.ReactNode
|
||||
variant?: "compact" | "full"
|
||||
action?: React.ReactNode
|
||||
/** Blur the comment until revealed — set for every viewer except the author. */
|
||||
spoilers?: boolean
|
||||
onShowDetail?: () => void
|
||||
}
|
||||
|
||||
export function MovieCard({ movie, rating, comment, subtitle, variant = "full", action, onShowDetail }: MovieCardProps) {
|
||||
export function MovieCard({ movie, rating, comment, subtitle, variant = "full", action, spoilers, onShowDetail }: MovieCardProps) {
|
||||
const veiled = !!spoilers && !!comment
|
||||
if (variant === "compact") {
|
||||
return (
|
||||
<Link to="/movies/$id" params={{ id: movie.id }} className="glass flex items-center gap-3 rounded-xl px-3 py-2.5 transition-colors active:bg-muted/50">
|
||||
@@ -25,13 +29,17 @@ export function MovieCard({ movie, rating, comment, subtitle, variant = "full",
|
||||
<p className="truncate text-sm font-semibold">{movie.title}</p>
|
||||
{subtitle && <p className="text-xs text-muted-foreground">{subtitle}</p>}
|
||||
{comment && (
|
||||
<p
|
||||
className="truncate text-xs text-muted-foreground/70"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? (e) => { e.preventDefault(); onShowDetail() } : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail() : undefined}
|
||||
>{comment}</p>
|
||||
veiled ? (
|
||||
<SpoilerVeil content={comment} className="truncate text-xs text-muted-foreground/70" />
|
||||
) : (
|
||||
<p
|
||||
className="truncate text-xs text-muted-foreground/70"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? (e) => { e.preventDefault(); onShowDetail() } : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail() : undefined}
|
||||
>{comment}</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{rating != null && <StarDisplay rating={rating} size="xs" />}
|
||||
@@ -51,13 +59,17 @@ export function MovieCard({ movie, rating, comment, subtitle, variant = "full",
|
||||
<p className="text-xs text-muted-foreground">{movie.release_year}{movie.director && ` · ${movie.director}`}</p>
|
||||
{rating != null && <div className="mt-1"><StarDisplay rating={rating} /></div>}
|
||||
{comment && (
|
||||
<p
|
||||
className="mt-1 line-clamp-2 text-xs text-muted-foreground"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? (e) => { e.preventDefault(); onShowDetail() } : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail() : undefined}
|
||||
>{comment}</p>
|
||||
veiled ? (
|
||||
<SpoilerVeil content={comment} className="mt-1 line-clamp-2 text-xs text-muted-foreground" />
|
||||
) : (
|
||||
<p
|
||||
className="mt-1 line-clamp-2 text-xs text-muted-foreground"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail ? (e) => { e.preventDefault(); onShowDetail() } : undefined}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail() : undefined}
|
||||
>{comment}</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{action && <div className="flex items-center" onClick={(e) => e.preventDefault()}>{action}</div>}
|
||||
|
||||
@@ -179,6 +179,7 @@ function DiaryTab({ sortBy, userId, search }: { sortBy: string; userId?: string;
|
||||
comment={e.review.comment}
|
||||
subtitle={<><TimeAgo date={e.review.watched_at} /></>}
|
||||
variant="compact"
|
||||
spoilers={e.review.spoilers}
|
||||
onShowDetail={e.review.comment ? () => setDetailEntry(e) : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TimeAgo } from "@/components/time-ago"
|
||||
import { StarDisplay } from "@/components/star-display"
|
||||
import { WatchMediumBadge } from "@/components/watch-medium-badge"
|
||||
import { EditableContextMenu } from "@/components/editable-context-menu"
|
||||
import { SpoilerVeil } from "@/components/spoiler-veil"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { posterUrl } from "@/lib/api/client"
|
||||
@@ -21,6 +22,8 @@ type ReviewCardProps = {
|
||||
}
|
||||
|
||||
export function ReviewCard({ movie, review, userName, userId, isFederated, actorUrl, onEdit, onShowDetail }: ReviewCardProps) {
|
||||
const veil = !!review.spoilers
|
||||
const showDetail = veil ? undefined : onShowDetail
|
||||
const card = (
|
||||
<Card size="sm">
|
||||
<CardContent className="flex gap-3">
|
||||
@@ -61,15 +64,19 @@ export function ReviewCard({ movie, review, userName, userId, isFederated, actor
|
||||
{review.watch_medium && <WatchMediumBadge medium={review.watch_medium} />}
|
||||
</div>
|
||||
{review.comment && (
|
||||
<p
|
||||
className="mt-1 line-clamp-2 text-xs text-muted-foreground"
|
||||
role={onShowDetail ? "button" : undefined}
|
||||
tabIndex={onShowDetail ? 0 : undefined}
|
||||
onClick={onShowDetail}
|
||||
onKeyDown={onShowDetail ? (e) => e.key === "Enter" && onShowDetail() : undefined}
|
||||
>
|
||||
{review.comment}
|
||||
</p>
|
||||
veil ? (
|
||||
<SpoilerVeil content={review.comment} className="mt-1 text-xs text-muted-foreground" />
|
||||
) : (
|
||||
<p
|
||||
className="mt-1 line-clamp-2 text-xs text-muted-foreground"
|
||||
role={showDetail ? "button" : undefined}
|
||||
tabIndex={showDetail ? 0 : undefined}
|
||||
onClick={showDetail}
|
||||
onKeyDown={showDetail ? (e) => e.key === "Enter" && showDetail() : undefined}
|
||||
>
|
||||
{review.comment}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { VisuallyHidden } from "radix-ui"
|
||||
import { Drawer, DrawerContent, DrawerTitle } from "@/components/ui/drawer"
|
||||
import { StarDisplay } from "@/components/star-display"
|
||||
import { SpoilerVeil } from "@/components/spoiler-veil"
|
||||
import { WatchMediumBadge } from "@/components/watch-medium-badge"
|
||||
import { shortDate } from "@/lib/date"
|
||||
import { posterUrl } from "@/lib/api/client"
|
||||
@@ -40,9 +41,13 @@ export function ReviewDetailSheet({ open, onOpenChange, movie, review, userName
|
||||
</div>
|
||||
|
||||
{review.comment && (
|
||||
<p className="select-text whitespace-pre-wrap text-sm leading-relaxed">
|
||||
{review.comment}
|
||||
</p>
|
||||
review.spoilers ? (
|
||||
<SpoilerVeil content={review.comment} className="text-sm leading-relaxed" />
|
||||
) : (
|
||||
<p className="select-text whitespace-pre-wrap text-sm leading-relaxed">
|
||||
{review.comment}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useTranslation } from "react-i18next"
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
import { format } from "date-fns"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { Calendar } from "@/components/ui/calendar"
|
||||
@@ -19,6 +21,8 @@ type ReviewFormFieldsProps = {
|
||||
onWatchedAtChange: (v: Date) => void
|
||||
watchMedium?: string
|
||||
onWatchMediumChange: (v: string | undefined) => void
|
||||
spoilers: boolean
|
||||
onSpoilersChange: (v: boolean) => void
|
||||
}
|
||||
|
||||
export function ReviewFormFields({
|
||||
@@ -30,6 +34,8 @@ export function ReviewFormFields({
|
||||
onWatchedAtChange,
|
||||
watchMedium,
|
||||
onWatchMediumChange,
|
||||
spoilers,
|
||||
onSpoilersChange,
|
||||
}: ReviewFormFieldsProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -42,6 +48,17 @@ export function ReviewFormFields({
|
||||
|
||||
<AutoGrowTextarea value={comment} onChange={onCommentChange} placeholder={t("logReview.commentPlaceholder")} className="mb-5" />
|
||||
|
||||
<div className="mb-5 flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="review-spoilers"
|
||||
checked={spoilers}
|
||||
onCheckedChange={(v) => onSpoilersChange(v === true)}
|
||||
/>
|
||||
<Label htmlFor="review-spoilers" className="text-xs font-normal text-muted-foreground">
|
||||
{t("review.containsSpoilers")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="mb-2 text-xs uppercase tracking-wide text-muted-foreground">{t("logReview.watchedAt")}</p>
|
||||
<Popover modal>
|
||||
|
||||
@@ -52,6 +52,7 @@ function LogMode({ open, onOpenChange }: { open: boolean; onOpenChange: (open: b
|
||||
const [comment, setComment] = useState("")
|
||||
const [watchedAt, setWatchedAt] = useState<Date>(new Date())
|
||||
const [watchMedium, setWatchMedium] = useState<string | undefined>()
|
||||
const [spoilers, setSpoilers] = useState(false)
|
||||
const logMutation = useLogReview()
|
||||
|
||||
function reset() {
|
||||
@@ -60,6 +61,7 @@ function LogMode({ open, onOpenChange }: { open: boolean; onOpenChange: (open: b
|
||||
setComment("")
|
||||
setWatchedAt(new Date())
|
||||
setWatchMedium(undefined)
|
||||
setSpoilers(false)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
@@ -79,6 +81,7 @@ function LogMode({ open, onOpenChange }: { open: boolean; onOpenChange: (open: b
|
||||
comment: comment || undefined,
|
||||
watched_at: formatWatchedAt(watchedAt),
|
||||
watch_medium: watchMedium,
|
||||
spoilers: spoilers || undefined,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
@@ -118,6 +121,8 @@ function LogMode({ open, onOpenChange }: { open: boolean; onOpenChange: (open: b
|
||||
onWatchedAtChange={setWatchedAt}
|
||||
watchMedium={watchMedium}
|
||||
onWatchMediumChange={setWatchMedium}
|
||||
spoilers={spoilers}
|
||||
onSpoilersChange={setSpoilers}
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit} disabled={!rating || logMutation.isPending} className="w-full" size="lg">
|
||||
@@ -148,6 +153,7 @@ function EditMode({
|
||||
const [watchedAt, setWatchedAt] = useState<Date>(() => parseWatchedAt(review.watched_at))
|
||||
const [dateChanged, setDateChanged] = useState(false)
|
||||
const [watchMedium, setWatchMedium] = useState<string | undefined>(review.watch_medium)
|
||||
const [spoilers, setSpoilers] = useState(review.spoilers ?? false)
|
||||
const [confirmDelete, setConfirmDelete] = useState(false)
|
||||
const editMutation = useEditReview()
|
||||
const deleteMutation = useDeleteReview()
|
||||
@@ -166,6 +172,7 @@ function EditMode({
|
||||
if (newComment !== (review.comment ?? null)) data.comment = newComment
|
||||
if (dateChanged) data.watched_at = formatWatchedAt(watchedAt, isDateOnly(review.watched_at))
|
||||
if (watchMedium !== review.watch_medium) data.watch_medium = watchMedium ?? null
|
||||
if (spoilers !== (review.spoilers ?? false)) data.spoilers = spoilers
|
||||
|
||||
if (Object.keys(data).length === 0) {
|
||||
toast.info(t("editReview.noChanges"))
|
||||
@@ -206,6 +213,8 @@ function EditMode({
|
||||
onWatchedAtChange={handleDateChange}
|
||||
watchMedium={watchMedium}
|
||||
onWatchMediumChange={setWatchMedium}
|
||||
spoilers={spoilers}
|
||||
onSpoilersChange={setSpoilers}
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit} disabled={!rating || editMutation.isPending} className="w-full" size="lg">
|
||||
|
||||
43
spa/src/components/spoiler-veil.tsx
Normal file
43
spa/src/components/spoiler-veil.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
type SpoilerVeilProps = {
|
||||
content: string
|
||||
/** Layout classes for the veiled text (line clamps, sizing, color). */
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides review text behind a blur with a reveal button — shown to every
|
||||
* viewer, the author included. Once revealed, the text stays visible.
|
||||
*/
|
||||
export function SpoilerVeil({ content, className }: SpoilerVeilProps) {
|
||||
const { t } = useTranslation()
|
||||
const [revealed, setRevealed] = useState(false)
|
||||
|
||||
if (revealed) {
|
||||
return <span className={cn("block whitespace-pre-wrap", className)}>{content}</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="relative block" aria-label={t("review.containsSpoilers")}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn("pointer-events-none block select-none blur-[5px]", className)}
|
||||
>
|
||||
{content}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="absolute inset-y-0 left-0 h-6 self-center px-2 text-[10px]"
|
||||
onClick={() => setRevealed(true)}
|
||||
>
|
||||
{t("review.showSpoilers")}
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { TrendingUp } from "lucide-react"
|
||||
import { StarDisplay } from "@/components/star-display"
|
||||
import { SpoilerVeil } from "@/components/spoiler-veil"
|
||||
import { shortDate } from "@/lib/date"
|
||||
import type { ReviewHistoryResponse } from "@/features/movies"
|
||||
|
||||
@@ -24,7 +25,11 @@ export function ViewingHistory({ history }: { history: ReviewHistoryResponse })
|
||||
<div>
|
||||
<p className="text-sm font-medium">{shortDate(v.watched_at)}</p>
|
||||
{v.comment && (
|
||||
<p className="mt-0.5 text-xs text-muted-foreground line-clamp-1">{v.comment}</p>
|
||||
v.spoilers ? (
|
||||
<SpoilerVeil content={v.comment} className="mt-0.5 text-xs text-muted-foreground line-clamp-1" />
|
||||
) : (
|
||||
<p className="mt-0.5 text-xs text-muted-foreground line-clamp-1">{v.comment}</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<StarDisplay rating={v.rating} size="xs" />
|
||||
|
||||
@@ -32,6 +32,7 @@ export const logReviewRequestSchema = z.object({
|
||||
comment: z.string().optional(),
|
||||
watched_at: z.string(),
|
||||
watch_medium: z.string().optional(),
|
||||
spoilers: z.boolean().optional(),
|
||||
})
|
||||
export type LogReviewRequest = z.infer<typeof logReviewRequestSchema>
|
||||
|
||||
@@ -40,6 +41,7 @@ export const editReviewRequestSchema = z.object({
|
||||
comment: z.string().nullable().optional(),
|
||||
watched_at: z.string().optional(),
|
||||
watch_medium: z.string().nullable().optional(),
|
||||
spoilers: z.boolean().optional(),
|
||||
})
|
||||
export type EditReviewRequest = z.infer<typeof editReviewRequestSchema>
|
||||
|
||||
|
||||
@@ -26,11 +26,13 @@ export type MovieStatsDto = z.infer<typeof movieStatsDtoSchema>
|
||||
|
||||
export const socialReviewDtoSchema = z.object({
|
||||
user_display: z.string(),
|
||||
user_id: z.string(),
|
||||
rating: z.number(),
|
||||
comment: z.string().optional(),
|
||||
watched_at: z.string(),
|
||||
is_federated: z.boolean(),
|
||||
watch_medium: z.string().optional(),
|
||||
spoilers: z.boolean().optional(),
|
||||
})
|
||||
export type SocialReviewDto = z.infer<typeof socialReviewDtoSchema>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export const reviewDtoSchema = z.object({
|
||||
comment: z.string().optional(),
|
||||
watched_at: z.string(),
|
||||
watch_medium: z.string().optional(),
|
||||
spoilers: z.boolean().optional(),
|
||||
})
|
||||
export type ReviewDto = z.infer<typeof reviewDtoSchema>
|
||||
|
||||
|
||||
@@ -353,6 +353,10 @@
|
||||
"saved": "{{title}} updated!",
|
||||
"noChanges": "No changes to save"
|
||||
},
|
||||
"review": {
|
||||
"containsSpoilers": "Contains spoilers",
|
||||
"showSpoilers": "Show"
|
||||
},
|
||||
"logReview": {
|
||||
"title": "Log Review",
|
||||
"yourRating": "Your Rating",
|
||||
|
||||
@@ -105,7 +105,10 @@ function MovieDetailPage() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
<CommunityReviews reviews={reviews} onShowDetail={setDetailReview} />
|
||||
<CommunityReviews
|
||||
reviews={reviews}
|
||||
onShowDetail={setDetailReview}
|
||||
/>
|
||||
|
||||
{history && <ViewingHistory history={history} />}
|
||||
|
||||
@@ -114,7 +117,7 @@ function MovieDetailPage() {
|
||||
open={!!detailReview}
|
||||
onOpenChange={(open) => !open && setDetailReview(null)}
|
||||
movie={movie}
|
||||
review={{ id: "", rating: detailReview.rating, comment: detailReview.comment, watched_at: detailReview.watched_at, watch_medium: detailReview.watch_medium }}
|
||||
review={{ id: "", rating: detailReview.rating, comment: detailReview.comment, watched_at: detailReview.watched_at, watch_medium: detailReview.watch_medium, spoilers: detailReview.spoilers }}
|
||||
userName={detailReview.user_display}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user