edit sheet delete btn, fix medium icons, 44px touch targets, fix drag leak on drawer close
Some checks failed
CI / Check / Test (push) Failing after 8m44s
Some checks failed
CI / Check / Test (push) Failing after 8m44s
This commit is contained in:
@@ -6,7 +6,9 @@ import { Button } from "@/components/ui/button"
|
|||||||
import { ReviewFormFields } from "@/components/review-form-fields"
|
import { ReviewFormFields } from "@/components/review-form-fields"
|
||||||
import { SearchOverlay } from "@/components/search-overlay"
|
import { SearchOverlay } from "@/components/search-overlay"
|
||||||
import type { MovieSelection } from "@/components/search-overlay"
|
import type { MovieSelection } from "@/components/search-overlay"
|
||||||
import { useLogReview, useEditReview } from "@/features/diary"
|
import { useLogReview, useEditReview, useDeleteReview } from "@/features/diary"
|
||||||
|
import { ConfirmDialog } from "@/components/confirm-dialog"
|
||||||
|
import { Trash2 } from "lucide-react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { posterUrl } from "@/lib/api/client"
|
import { posterUrl } from "@/lib/api/client"
|
||||||
import { hapticMedium } from "@/lib/haptics"
|
import { hapticMedium } from "@/lib/haptics"
|
||||||
@@ -146,7 +148,9 @@ function EditMode({
|
|||||||
const [watchedAt, setWatchedAt] = useState<Date>(() => parseLocalDate(review.watched_at))
|
const [watchedAt, setWatchedAt] = useState<Date>(() => parseLocalDate(review.watched_at))
|
||||||
const [dateChanged, setDateChanged] = useState(false)
|
const [dateChanged, setDateChanged] = useState(false)
|
||||||
const [watchMedium, setWatchMedium] = useState<string | undefined>(review.watch_medium)
|
const [watchMedium, setWatchMedium] = useState<string | undefined>(review.watch_medium)
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false)
|
||||||
const editMutation = useEditReview()
|
const editMutation = useEditReview()
|
||||||
|
const deleteMutation = useDeleteReview()
|
||||||
|
|
||||||
function handleDateChange(d: Date) {
|
function handleDateChange(d: Date) {
|
||||||
setWatchedAt(d)
|
setWatchedAt(d)
|
||||||
@@ -207,6 +211,32 @@ function EditMode({
|
|||||||
<Button onClick={handleSubmit} disabled={!rating || editMutation.isPending} className="w-full" size="lg">
|
<Button onClick={handleSubmit} disabled={!rating || editMutation.isPending} className="w-full" size="lg">
|
||||||
{editMutation.isPending ? t("editReview.saving") : t("editReview.save")}
|
{editMutation.isPending ? t("editReview.saving") : t("editReview.save")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="mt-2 w-full text-destructive hover:text-destructive"
|
||||||
|
onClick={() => setConfirmDelete(true)}
|
||||||
|
disabled={deleteMutation.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-1.5 size-4" />
|
||||||
|
{t("editReview.delete", { defaultValue: "Delete review" })}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
open={confirmDelete}
|
||||||
|
onOpenChange={setConfirmDelete}
|
||||||
|
title={t("diary.deleteReview", { defaultValue: "Delete review?" })}
|
||||||
|
description={`${movie.title} — ${review.watched_at.slice(0, 10)}`}
|
||||||
|
onConfirm={() =>
|
||||||
|
deleteMutation.mutate(review.id, {
|
||||||
|
onSuccess: () => {
|
||||||
|
hapticMedium()
|
||||||
|
toast.success(t("editReview.deleted", { defaultValue: "Review deleted", title: movie.title }))
|
||||||
|
onOpenChange(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@@ -8,21 +8,25 @@ type StarRatingProps = {
|
|||||||
size?: "sm" | "md" | "lg"
|
size?: "sm" | "md" | "lg"
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizes = { sm: "size-5", md: "size-8", lg: "size-10" }
|
const iconSizes = { sm: "size-5", md: "size-7", lg: "size-9" }
|
||||||
|
const buttonSizes = { sm: "size-8", md: "size-10", lg: "size-11" }
|
||||||
|
|
||||||
export function StarRating({ value, onChange, size = "lg" }: StarRatingProps) {
|
export function StarRating({ value, onChange, size = "lg" }: StarRatingProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-0.5">
|
||||||
{[1, 2, 3, 4, 5].map((star) => (
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
<button
|
<button
|
||||||
key={star}
|
key={star}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => { hapticLight(); onChange(star) }}
|
onClick={() => { hapticLight(); onChange(star) }}
|
||||||
className="transition-transform active:scale-90"
|
className={cn(
|
||||||
|
"flex items-center justify-center rounded-md transition-transform active:scale-90",
|
||||||
|
buttonSizes[size],
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Star
|
<Star
|
||||||
className={cn(
|
className={cn(
|
||||||
sizes[size],
|
iconSizes[size],
|
||||||
star <= value
|
star <= value
|
||||||
? "fill-amber-500 text-amber-500 aero-star-filled"
|
? "fill-amber-500 text-amber-500 aero-star-filled"
|
||||||
: "text-muted-foreground/30",
|
: "text-muted-foreground/30",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function SwipeToDelete({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ axis: "x", filterTaps: true, pointer: { capture: true } },
|
{ axis: "x", filterTaps: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
function handleDeleteTap() {
|
function handleDeleteTap() {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function WatchMediumPicker({ value, onChange }: WatchMediumPickerProps) {
|
|||||||
<p className="mb-2 text-xs uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
{t("watchMedium.label")}
|
{t("watchMedium.label")}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-2">
|
||||||
{WATCH_MEDIUMS.map(({ value: val, icon: Icon, labelKey }) => {
|
{WATCH_MEDIUMS.map(({ value: val, icon: Icon, labelKey }) => {
|
||||||
const selected = value === val
|
const selected = value === val
|
||||||
return (
|
return (
|
||||||
@@ -28,14 +28,14 @@ export function WatchMediumPicker({ value, onChange }: WatchMediumPickerProps) {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-8",
|
"size-11",
|
||||||
selected && "border-[var(--aero-primary)] bg-[var(--aero-primary)] text-white shadow-[0_0_8px_var(--aero-primary-glow)]",
|
selected && "border-[var(--aero-primary)] bg-[var(--aero-primary)] text-white shadow-[0_0_8px_var(--aero-primary-glow)]",
|
||||||
)}
|
)}
|
||||||
aria-label={t(labelKey)}
|
aria-label={t(labelKey)}
|
||||||
aria-pressed={selected}
|
aria-pressed={selected}
|
||||||
onClick={() => onChange(selected ? undefined : val)}
|
onClick={() => onChange(selected ? undefined : val)}
|
||||||
>
|
>
|
||||||
<Icon className="size-4" />
|
<Icon className="size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent sideOffset={4}>{t(labelKey)}</TooltipContent>
|
<TooltipContent sideOffset={4}>{t(labelKey)}</TooltipContent>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { LucideIcon } from "lucide-react"
|
import type { LucideIcon } from "lucide-react"
|
||||||
import {
|
import {
|
||||||
|
Cast,
|
||||||
Clapperboard,
|
Clapperboard,
|
||||||
Tv,
|
|
||||||
Radio,
|
|
||||||
Disc3,
|
Disc3,
|
||||||
Download,
|
Download,
|
||||||
Server,
|
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
|
Server,
|
||||||
|
Tv,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
|
||||||
export type WatchMediumDef = {
|
export type WatchMediumDef = {
|
||||||
@@ -17,8 +17,8 @@ export type WatchMediumDef = {
|
|||||||
|
|
||||||
export const WATCH_MEDIUMS: WatchMediumDef[] = [
|
export const WATCH_MEDIUMS: WatchMediumDef[] = [
|
||||||
{ value: "cinema", icon: Clapperboard, labelKey: "watchMedium.cinema" },
|
{ value: "cinema", icon: Clapperboard, labelKey: "watchMedium.cinema" },
|
||||||
{ value: "streaming", icon: Tv, labelKey: "watchMedium.streaming" },
|
{ value: "streaming", icon: Cast, labelKey: "watchMedium.streaming" },
|
||||||
{ value: "tv", icon: Radio, labelKey: "watchMedium.tv" },
|
{ value: "tv", icon: Tv, labelKey: "watchMedium.tv" },
|
||||||
{ value: "physical_media", icon: Disc3, labelKey: "watchMedium.physicalMedia" },
|
{ value: "physical_media", icon: Disc3, labelKey: "watchMedium.physicalMedia" },
|
||||||
{ value: "download", icon: Download, labelKey: "watchMedium.download" },
|
{ value: "download", icon: Download, labelKey: "watchMedium.download" },
|
||||||
{ value: "media_server", icon: Server, labelKey: "watchMedium.mediaServer" },
|
{ value: "media_server", icon: Server, labelKey: "watchMedium.mediaServer" },
|
||||||
|
|||||||
Reference in New Issue
Block a user