fix: disable vaul repositionInputs to fix iOS keyboard in drawers
Some checks failed
CI / Check / Test (push) Failing after 6m35s
Some checks failed
CI / Check / Test (push) Failing after 6m35s
This commit is contained in:
17
spa/src/components/back-button.tsx
Normal file
17
spa/src/components/back-button.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useRouter } from "@tanstack/react-router"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { ArrowLeft } from "lucide-react"
|
||||||
|
|
||||||
|
export function BackButton() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => router.history.back()}
|
||||||
|
className="inline-flex items-center gap-1 text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="size-4" /> {t("common.back")}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import type { MovieSelection } from "@/components/search-overlay"
|
|||||||
import { useLogReview } from "@/hooks/use-diary"
|
import { useLogReview } from "@/hooks/use-diary"
|
||||||
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"
|
||||||
|
|
||||||
type LogSheetProps = {
|
type LogSheetProps = {
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -48,6 +49,7 @@ export function LogSheet({ open, onOpenChange }: LogSheetProps) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
hapticMedium()
|
||||||
toast.success(t("logReview.logged", { title: movie.title }))
|
toast.success(t("logReview.logged", { title: movie.title }))
|
||||||
handleClose()
|
handleClose()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ type MovieCardProps = {
|
|||||||
comment?: string
|
comment?: string
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
variant?: "compact" | "full"
|
variant?: "compact" | "full"
|
||||||
|
action?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MovieCard({ movie, rating, comment, subtitle, variant = "full" }: MovieCardProps) {
|
export function MovieCard({ movie, rating, comment, subtitle, variant = "full", action }: MovieCardProps) {
|
||||||
if (variant === "compact") {
|
if (variant === "compact") {
|
||||||
return (
|
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">
|
<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">
|
||||||
@@ -41,6 +42,7 @@ export function MovieCard({ movie, rating, comment, subtitle, variant = "full" }
|
|||||||
{rating != null && <div className="mt-1"><StarDisplay rating={rating} /></div>}
|
{rating != null && <div className="mt-1"><StarDisplay rating={rating} /></div>}
|
||||||
{comment && <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">{comment}</p>}
|
{comment && <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">{comment}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
{action && <div className="flex items-center" onClick={(e) => e.preventDefault()}>{action}</div>}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
import { Globe } from "lucide-react"
|
import { Globe } from "lucide-react"
|
||||||
|
import { timeAgo } from "@/lib/date"
|
||||||
import { StarDisplay } from "@/components/star-display"
|
import { StarDisplay } from "@/components/star-display"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { posterUrl } from "@/lib/api/client"
|
import { posterUrl } from "@/lib/api/client"
|
||||||
@@ -32,7 +33,7 @@ export function ReviewCard({ movie, review, userName, userId, isFederated }: Rev
|
|||||||
)}
|
)}
|
||||||
{isFederated && <Globe className="size-3 text-muted-foreground/60" />}
|
{isFederated && <Globe className="size-3 text-muted-foreground/60" />}
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<span>{review.watched_at.slice(0, 10)}</span>
|
<span>{timeAgo(review.watched_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Link to="/movies/$id" params={{ id: movie.id }} className="font-semibold hover:underline">
|
<Link to="/movies/$id" params={{ id: movie.id }} className="font-semibold hover:underline">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Star } from "lucide-react"
|
import { Star } from "lucide-react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { hapticLight } from "@/lib/haptics"
|
||||||
|
|
||||||
type StarRatingProps = {
|
type StarRatingProps = {
|
||||||
value: number
|
value: number
|
||||||
@@ -16,7 +17,7 @@ export function StarRating({ value, onChange, size = "lg" }: StarRatingProps) {
|
|||||||
<button
|
<button
|
||||||
key={star}
|
key={star}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onChange(star)}
|
onClick={() => { hapticLight(); onChange(star) }}
|
||||||
className="transition-transform active:scale-90"
|
className="transition-transform active:scale-90"
|
||||||
>
|
>
|
||||||
<Star
|
<Star
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useDrag } from "@use-gesture/react"
|
|||||||
import { Trash2 } from "lucide-react"
|
import { Trash2 } from "lucide-react"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { ConfirmDialog } from "@/components/confirm-dialog"
|
import { ConfirmDialog } from "@/components/confirm-dialog"
|
||||||
|
import { hapticMedium } from "@/lib/haptics"
|
||||||
|
|
||||||
type SwipeToDeleteProps = {
|
type SwipeToDeleteProps = {
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
@@ -31,6 +32,7 @@ export function SwipeToDelete({
|
|||||||
setOffsetX(Math.min(0, Math.max(mx, -100)))
|
setOffsetX(Math.min(0, Math.max(mx, -100)))
|
||||||
} else {
|
} else {
|
||||||
if (mx < -60) {
|
if (mx < -60) {
|
||||||
|
hapticMedium()
|
||||||
setRevealed(true)
|
setRevealed(true)
|
||||||
setOffsetX(-80)
|
setOffsetX(-80)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { Drawer as DrawerPrimitive } from "vaul"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function Drawer({
|
function Drawer({
|
||||||
|
repositionInputs = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
||||||
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
return <DrawerPrimitive.Root data-slot="drawer" repositionInputs={repositionInputs} {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function DrawerTrigger({
|
function DrawerTrigger({
|
||||||
|
|||||||
19
spa/src/lib/date.ts
Normal file
19
spa/src/lib/date.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { formatDistanceToNow, parseISO, format } from "date-fns"
|
||||||
|
|
||||||
|
export function timeAgo(dateStr: string): string {
|
||||||
|
try {
|
||||||
|
const date = dateStr.includes("T") ? parseISO(dateStr) : new Date(dateStr.replace(" ", "T"))
|
||||||
|
return formatDistanceToNow(date, { addSuffix: true })
|
||||||
|
} catch {
|
||||||
|
return dateStr.slice(0, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shortDate(dateStr: string): string {
|
||||||
|
try {
|
||||||
|
const date = dateStr.includes("T") ? parseISO(dateStr) : new Date(dateStr.replace(" ", "T"))
|
||||||
|
return format(date, "MMM d, yyyy")
|
||||||
|
} catch {
|
||||||
|
return dateStr.slice(0, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
7
spa/src/lib/haptics.ts
Normal file
7
spa/src/lib/haptics.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function hapticLight() {
|
||||||
|
navigator?.vibrate?.(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hapticMedium() {
|
||||||
|
navigator?.vibrate?.(20)
|
||||||
|
}
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
import { QueryClient } from "@tanstack/react-query"
|
import { QueryClient, type Mutation } from "@tanstack/react-query"
|
||||||
|
import { toast } from "sonner"
|
||||||
import { ApiError } from "@/lib/api/client"
|
import { ApiError } from "@/lib/api/client"
|
||||||
|
|
||||||
|
function onMutationError(error: Error, _vars: unknown, _ctx: unknown, mutation: Mutation) {
|
||||||
|
if (mutation.options.onError) return
|
||||||
|
const msg = error instanceof ApiError ? `Error ${error.status}` : "Something went wrong"
|
||||||
|
toast.error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
export const queryClient = new QueryClient({
|
export const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
@@ -13,5 +20,8 @@ export const queryClient = new QueryClient({
|
|||||||
return failureCount < 2
|
return failureCount < 2
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mutations: {
|
||||||
|
onError: onMutationError as never,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,7 +67,8 @@
|
|||||||
"removeFromWatchlist": "Remove from watchlist?",
|
"removeFromWatchlist": "Remove from watchlist?",
|
||||||
"queueEmpty": "Queue empty",
|
"queueEmpty": "Queue empty",
|
||||||
"queueEmptyDesc": "Movies from Jellyfin/Plex appear here",
|
"queueEmptyDesc": "Movies from Jellyfin/Plex appear here",
|
||||||
"deleteReview": "Delete this review?"
|
"deleteReview": "Delete this review?",
|
||||||
|
"addedToWatchlist": "Added to watchlist"
|
||||||
},
|
},
|
||||||
"diary": {
|
"diary": {
|
||||||
"title": "Diary",
|
"title": "Diary",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useCallback, useState } from "react"
|
import { useCallback, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Clapperboard, Film, Inbox, Plus } from "lucide-react"
|
import { Clapperboard, Film, Inbox, Plus, RefreshCw } from "lucide-react"
|
||||||
import { ReviewCard } from "@/components/review-card"
|
import { ReviewCard } from "@/components/review-card"
|
||||||
import { MovieCard } from "@/components/movie-card"
|
import { MovieCard } from "@/components/movie-card"
|
||||||
import { EmptyState } from "@/components/empty-state"
|
import { EmptyState } from "@/components/empty-state"
|
||||||
@@ -13,6 +13,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { StarRating } from "@/components/star-rating"
|
import { StarRating } from "@/components/star-rating"
|
||||||
import { useAuth } from "@/components/auth-provider"
|
import { useAuth } from "@/components/auth-provider"
|
||||||
|
import { useQueryClient } from "@tanstack/react-query"
|
||||||
import { useInfiniteActivityFeed, useDeleteReview } from "@/hooks/use-diary"
|
import { useInfiniteActivityFeed, useDeleteReview } from "@/hooks/use-diary"
|
||||||
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"
|
||||||
@@ -52,6 +53,8 @@ function HomePage() {
|
|||||||
function FeedTab() {
|
function FeedTab() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { auth } = useAuth()
|
const { auth } = useAuth()
|
||||||
|
const qc = useQueryClient()
|
||||||
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
const [sortBy, setSortBy] = useState("date")
|
const [sortBy, setSortBy] = useState("date")
|
||||||
const feedSortOptions = [
|
const feedSortOptions = [
|
||||||
{ value: "date", label: t("feed.sortLatest") },
|
{ value: "date", label: t("feed.sortLatest") },
|
||||||
@@ -67,7 +70,19 @@ function FeedTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-end">
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="size-8"
|
||||||
|
onClick={async () => {
|
||||||
|
setRefreshing(true)
|
||||||
|
await qc.refetchQueries({ queryKey: ["activity-feed"] })
|
||||||
|
setRefreshing(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RefreshCw className={`size-4 ${refreshing ? "animate-spin" : ""}`} />
|
||||||
|
</Button>
|
||||||
<Select value={sortBy} onValueChange={setSortBy}>
|
<Select value={sortBy} onValueChange={setSortBy}>
|
||||||
<SelectTrigger className="w-36">
|
<SelectTrigger className="w-36">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router"
|
import { createFileRoute, Link } from "@tanstack/react-router"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { ArrowLeft, Bookmark, BookmarkCheck, Globe, Star, TrendingUp, User, Users } from "lucide-react"
|
import { Bookmark, BookmarkCheck, Globe, Star, TrendingUp, User, Users } from "lucide-react"
|
||||||
|
import { BackButton } from "@/components/back-button"
|
||||||
import { StarDisplay } from "@/components/star-display"
|
import { StarDisplay } from "@/components/star-display"
|
||||||
import { RatingHistogram } from "@/components/rating-histogram"
|
import { RatingHistogram } from "@/components/rating-histogram"
|
||||||
import { EmptyState } from "@/components/empty-state"
|
import { EmptyState } from "@/components/empty-state"
|
||||||
@@ -9,6 +10,7 @@ import { Button } from "@/components/ui/button"
|
|||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { posterUrl, tmdbProfileUrl } from "@/lib/api/client"
|
import { posterUrl, tmdbProfileUrl } from "@/lib/api/client"
|
||||||
|
import { timeAgo, shortDate } from "@/lib/date"
|
||||||
import { useMovie, useMovieHistory, useMovieProfile } from "@/hooks/use-movies"
|
import { useMovie, useMovieHistory, useMovieProfile } from "@/hooks/use-movies"
|
||||||
import {
|
import {
|
||||||
useWatchlistStatus,
|
useWatchlistStatus,
|
||||||
@@ -36,9 +38,7 @@ function MovieDetailPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5 p-4">
|
<div className="space-y-5 p-4">
|
||||||
<Link to="/" className="inline-flex items-center gap-1 text-sm text-muted-foreground">
|
<BackButton />
|
||||||
<ArrowLeft className="size-4" /> {t("common.back")}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<HeroSection movie={movie} stats={stats} movieId={id} tagline={profile?.tagline} />
|
<HeroSection movie={movie} stats={stats} movieId={id} tagline={profile?.tagline} />
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ function MovieDetailPage() {
|
|||||||
{r.user_display}
|
{r.user_display}
|
||||||
{r.is_federated && <Globe className="size-3 text-muted-foreground/60" />}
|
{r.is_federated && <Globe className="size-3 text-muted-foreground/60" />}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-[10px]">{r.watched_at.slice(0, 10)}</CardDescription>
|
<CardDescription className="text-[10px]">{timeAgo(r.watched_at)}</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<StarDisplay rating={r.rating} size="xs" />
|
<StarDisplay rating={r.rating} size="xs" />
|
||||||
</div>
|
</div>
|
||||||
@@ -145,7 +145,7 @@ function MovieDetailPage() {
|
|||||||
{history.viewings.map((v) => (
|
{history.viewings.map((v) => (
|
||||||
<div key={v.id} className="flex items-center justify-between rounded-xl bg-card p-3">
|
<div key={v.id} className="flex items-center justify-between rounded-xl bg-card p-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">{v.watched_at}</p>
|
<p className="text-sm font-medium">{shortDate(v.watched_at)}</p>
|
||||||
{v.comment && (
|
{v.comment && (
|
||||||
<p className="mt-0.5 text-xs text-muted-foreground line-clamp-1">{v.comment}</p>
|
<p className="mt-0.5 text-xs text-muted-foreground line-clamp-1">{v.comment}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { ArrowLeft, Film, User } from "lucide-react"
|
import { Film, User } from "lucide-react"
|
||||||
|
import { BackButton } from "@/components/back-button"
|
||||||
import { MovieCard } from "@/components/movie-card"
|
import { MovieCard } from "@/components/movie-card"
|
||||||
import { EmptyState } from "@/components/empty-state"
|
import { EmptyState } from "@/components/empty-state"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
@@ -23,9 +24,7 @@ function PersonDetailPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 p-4">
|
<div className="space-y-4 p-4">
|
||||||
<Link to="/" className="inline-flex items-center gap-1 text-sm text-muted-foreground">
|
<BackButton />
|
||||||
<ArrowLeft className="size-4" /> {t("common.back")}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="size-16 flex-shrink-0 overflow-hidden rounded-full bg-muted">
|
<div className="size-16 flex-shrink-0 overflow-hidden rounded-full bg-muted">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useCallback, useState } from "react"
|
import { useCallback, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Search as SearchIcon, Film, Users } from "lucide-react"
|
import { Bookmark, Search as SearchIcon, Film, Users } from "lucide-react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { MovieCard } from "@/components/movie-card"
|
import { MovieCard } from "@/components/movie-card"
|
||||||
import { PersonRow } from "@/components/person-row"
|
import { PersonRow } from "@/components/person-row"
|
||||||
@@ -10,6 +11,8 @@ import { InfiniteScroll } from "@/components/infinite-scroll"
|
|||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { useInfiniteSearch } from "@/hooks/use-search"
|
import { useInfiniteSearch } from "@/hooks/use-search"
|
||||||
import { useDebounce } from "@/hooks/use-debounce"
|
import { useDebounce } from "@/hooks/use-debounce"
|
||||||
|
import { useAddToWatchlist } from "@/hooks/use-watchlist"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
export const Route = createFileRoute("/_app/search")({
|
export const Route = createFileRoute("/_app/search")({
|
||||||
component: SearchPage,
|
component: SearchPage,
|
||||||
@@ -17,6 +20,7 @@ export const Route = createFileRoute("/_app/search")({
|
|||||||
|
|
||||||
function SearchPage() {
|
function SearchPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const addToWatchlist = useAddToWatchlist()
|
||||||
const [query, setQuery] = useState("")
|
const [query, setQuery] = useState("")
|
||||||
const debouncedQuery = useDebounce(query, 300)
|
const debouncedQuery = useDebounce(query, 300)
|
||||||
const {
|
const {
|
||||||
@@ -87,6 +91,21 @@ function SearchPage() {
|
|||||||
genres: hit.genres,
|
genres: hit.genres,
|
||||||
}}
|
}}
|
||||||
variant="full"
|
variant="full"
|
||||||
|
action={
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="size-8 text-muted-foreground"
|
||||||
|
onClick={() => {
|
||||||
|
addToWatchlist.mutate(
|
||||||
|
{ movie_id: hit.movie_id },
|
||||||
|
{ onSuccess: () => toast.success(t("feed.addedToWatchlist")) },
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Bookmark className="size-4" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { ArrowLeft, UserCheck, UserPlus } from "lucide-react"
|
import { UserCheck, UserPlus } from "lucide-react"
|
||||||
|
import { BackButton } from "@/components/back-button"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { ProfileView, ProfileSkeleton } from "@/components/profile-view"
|
import { ProfileView, ProfileSkeleton } from "@/components/profile-view"
|
||||||
import { useAuth } from "@/components/auth-provider"
|
import { useAuth } from "@/components/auth-provider"
|
||||||
@@ -28,9 +29,7 @@ function UserProfilePage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Link to="/" className="mb-4 inline-flex items-center gap-1 text-sm text-muted-foreground">
|
<div className="mb-4"><BackButton /></div>
|
||||||
<ArrowLeft className="size-4" /> {t("common.back")}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<ProfileView
|
<ProfileView
|
||||||
data={data}
|
data={data}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router"
|
import { createFileRoute, Link } from "@tanstack/react-router"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { ArrowLeft, Star, Users } from "lucide-react"
|
import { Star, Users } from "lucide-react"
|
||||||
|
import { BackButton } from "@/components/back-button"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
@@ -26,9 +27,7 @@ function WrapUpReportPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 p-4">
|
<div className="space-y-4 p-4">
|
||||||
<Link to="/profile" className="inline-flex items-center gap-1 text-sm text-muted-foreground">
|
<BackButton />
|
||||||
<ArrowLeft className="size-4" /> {t("profile.title")}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{/* Hero */}
|
{/* Hero */}
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user