mirror of
https://github.com/mmahdium/TBW.git
synced 2026-08-14 16:22:48 +03:30
Improve UI and clean the details card
This commit is contained in:
@@ -89,7 +89,10 @@ const alreadyAdded = computed(() => store.mediaList.some((media) => media.Id ===
|
|||||||
<!-- Actions row -->
|
<!-- Actions row -->
|
||||||
<div class="card-actions flex items-center justify-between mt-auto">
|
<div class="card-actions flex items-center justify-between mt-auto">
|
||||||
<!-- Type badge -->
|
<!-- Type badge -->
|
||||||
<span class="badge badge-outline text-xs font-medium px-2 py-1">
|
<span
|
||||||
|
class="badge badge-outline text-xs font-medium px-2 py-1"
|
||||||
|
:class="props.media.Adult ? 'badge-error' : ''"
|
||||||
|
>
|
||||||
{{ props.media.MediaType === 'movie' ? 'Movie' : 'Show' }}
|
{{ props.media.MediaType === 'movie' ? 'Movie' : 'Show' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MovieDetailsType } from '@/types/Movie'
|
import type { MovieDetailsType } from '@/types/Movie'
|
||||||
import type { TvSeriesDetailsType } from '@/types/TvSeries'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useMediaStore } from '@/stores/media'
|
import { useMediaStore } from '@/stores/media'
|
||||||
import type { MediaType } from '@/types/Media'
|
import type { MediaType } from '@/types/Media'
|
||||||
@@ -8,38 +7,26 @@ import ImageWithFallback from './ImageWithFallback.vue'
|
|||||||
import MediaTypeBadge from './MediaTypeBadge.vue'
|
import MediaTypeBadge from './MediaTypeBadge.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: 'movie' | 'tv'
|
|
||||||
media?: MediaType
|
media?: MediaType
|
||||||
movie?: MovieDetailsType | null
|
movie?: MovieDetailsType | null
|
||||||
tvSeries?: TvSeriesDetailsType | null
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const store = useMediaStore()
|
const store = useMediaStore()
|
||||||
|
|
||||||
const alreadyAdded = computed(() =>
|
const alreadyAdded = computed(() =>
|
||||||
props.type === 'movie' && props.movie
|
props.movie ? store.mediaList.some((m: MediaType) => m.Id === props.movie!.Id) : false,
|
||||||
? store.mediaList.some((m: MediaType) => m.Id === props.movie!.Id)
|
|
||||||
: props.type === 'tv' && props.tvSeries
|
|
||||||
? store.mediaList.some((ts: MediaType) => ts.Id === props.tvSeries!.Id)
|
|
||||||
: false,
|
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- Error -->
|
|
||||||
<!-- <ErrorAlert
|
|
||||||
v-if="props.type === 'movie' && props.movie?.Error"
|
|
||||||
:message="props.movie.ErrorMessage"
|
|
||||||
/> -->
|
|
||||||
|
|
||||||
<!-- Hero -->
|
<!-- Hero -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full bg-white/70 backdrop-blur-md border border-gray-200/60 shadow-md rounded-xl p-8 transition"
|
class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full bg-white/70 border border-gray-200/60 rounded-xl p-8 transition"
|
||||||
v-motion-fade-visible-once
|
v-motion-fade-visible-once
|
||||||
>
|
>
|
||||||
<!-- Poster -->
|
<!-- Poster -->
|
||||||
<ImageWithFallback
|
<ImageWithFallback
|
||||||
:src="type === 'movie' ? props.movie!.PosterPath : props.tvSeries!.PosterPath"
|
:src="props.movie!.PosterPath"
|
||||||
alt="Poster"
|
alt="Poster"
|
||||||
size="w500"
|
size="w500"
|
||||||
class="shrink-0 w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
|
class="shrink-0 w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
|
||||||
@@ -48,51 +35,29 @@ const alreadyAdded = computed(() =>
|
|||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h1 class="text-4xl font-bold text-gray-800 mb-2">
|
<h1 class="text-4xl font-bold text-gray-800 mb-2">
|
||||||
<template v-if="props.type === 'movie'">
|
{{ props.movie!.Title }}
|
||||||
{{ props.movie!.Title }}
|
<span class="text-gray-400 text-lg font-normal"
|
||||||
<span class="text-gray-400 text-lg font-normal"
|
>({{ props.movie!.ReleaseDate?.slice(0, 4) }})</span
|
||||||
>({{ props.movie!.ReleaseDate?.slice(0, 4) }})</span
|
>
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ props.tvSeries!.Name }}
|
|
||||||
<span class="text-gray-400 text-lg font-normal"
|
|
||||||
>({{ props.tvSeries!.FirstAirDate?.slice(0, 4) }})</span
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p v-if="props.type === 'movie' && props.movie!.Tagline" class="italic text-gray-500 mb-2">
|
<p v-if="props.movie!.Tagline" class="italic text-gray-500 mb-2">
|
||||||
{{ props.movie!.Tagline }}
|
{{ props.movie!.Tagline }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="props.type === 'tv' && props.tvSeries!.Tagline" class="italic text-gray-500 mb-2">
|
|
||||||
{{ props.tvSeries!.Tagline }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="text-gray-600 leading-relaxed mb-6">
|
<p class="text-gray-600 leading-relaxed mb-6">
|
||||||
{{ props.type === 'movie' ? props.movie!.Overview : props.tvSeries!.Overview }}
|
{{ props.movie!.Overview }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Badges -->
|
<!-- Badges -->
|
||||||
<div class="flex flex-wrap gap-2 mb-6">
|
<div class="flex flex-wrap gap-2 mb-6">
|
||||||
<MediaTypeBadge
|
<MediaTypeBadge v-for="g in props.movie!.Genres" :key="g.id" :text="g.name" />
|
||||||
v-for="g in props.type === 'movie' ? props.movie!.Genres : props.tvSeries!.Genres"
|
<MediaTypeBadge :text="`Runtime: ${props.movie!.Runtime} min`" />
|
||||||
:key="g.id"
|
|
||||||
:text="g.name"
|
|
||||||
/>
|
|
||||||
<MediaTypeBadge
|
|
||||||
v-if="props.type === 'movie'"
|
|
||||||
:text="`Runtime: ${props.movie!.Runtime} min`"
|
|
||||||
/>
|
|
||||||
<MediaTypeBadge
|
|
||||||
v-if="props.type === 'tv'"
|
|
||||||
:text="`Seasons: ${props.tvSeries!.NumberOfSeasons}`"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="card-actions" v-auto-animate>
|
<div class="card-actions" v-auto-animate>
|
||||||
<template v-if="(props.movie && props.media) || (props.tvSeries && props.media)">
|
<template v-if="props.movie && props.media">
|
||||||
<button
|
<button
|
||||||
v-if="!alreadyAdded"
|
v-if="!alreadyAdded"
|
||||||
class="btn px-6 bg-linear-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300"
|
class="btn px-6 bg-linear-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300"
|
||||||
@@ -113,8 +78,8 @@ const alreadyAdded = computed(() =>
|
|||||||
:to="{
|
:to="{
|
||||||
name: 'watch',
|
name: 'watch',
|
||||||
params: {
|
params: {
|
||||||
name: props.type === 'movie' ? props.movie!.Title : props.tvSeries!.Name,
|
name: props.movie!.Title,
|
||||||
id: props.type === 'movie' ? props.movie!.Id : props.tvSeries!.Id,
|
id: props.movie!.Id,
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
Executable
+109
@@ -0,0 +1,109 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useMediaStore } from '@/stores/media'
|
||||||
|
import type { MediaType } from '@/types/Media'
|
||||||
|
import ImageWithFallback from './ImageWithFallback.vue'
|
||||||
|
import MediaTypeBadge from './MediaTypeBadge.vue'
|
||||||
|
import type { TvSeriesDetailsType } from '@/types/TvSeries'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
media?: MediaType
|
||||||
|
tvSeries?: TvSeriesDetailsType | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = useMediaStore()
|
||||||
|
|
||||||
|
const alreadyAdded = computed(() =>
|
||||||
|
props.tvSeries ? store.mediaList.some((m: MediaType) => m.Id === props.tvSeries!.Id) : false,
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Hero -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full bg-white/70 border border-gray-200/60 rounded-xl p-8 transition"
|
||||||
|
v-motion-fade-visible-once
|
||||||
|
>
|
||||||
|
<!-- Poster -->
|
||||||
|
<ImageWithFallback
|
||||||
|
:src="props.tvSeries!.PosterPath"
|
||||||
|
alt="Poster"
|
||||||
|
size="w500"
|
||||||
|
class="shrink-0 w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Text -->
|
||||||
|
<div class="flex-1">
|
||||||
|
<h1 class="text-4xl font-bold text-gray-800 mb-2">
|
||||||
|
{{ props.tvSeries!.Name }}
|
||||||
|
<span class="text-gray-400 text-lg font-normal"
|
||||||
|
>({{ props.tvSeries!.FirstAirDate?.slice(0, 4) }})</span
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p v-if="props.tvSeries!.Tagline" class="italic text-gray-500 mb-2">
|
||||||
|
{{ props.tvSeries!.Tagline }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="text-gray-600 leading-relaxed mb-6">
|
||||||
|
{{ props.tvSeries!.Overview }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Badges -->
|
||||||
|
<div class="flex flex-wrap gap-2 mb-6">
|
||||||
|
<MediaTypeBadge v-for="g in props.tvSeries!.Genres" :key="g.id" :text="g.name" />
|
||||||
|
<MediaTypeBadge :text="`Seasons: ${props.tvSeries!.NumberOfSeasons}`" />
|
||||||
|
<MediaTypeBadge :text="`Episodes: ${props.tvSeries!.NumberOfEpisodes}`" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="card-actions" v-auto-animate>
|
||||||
|
<template v-if="props.tvSeries && props.media">
|
||||||
|
<button
|
||||||
|
v-if="!alreadyAdded"
|
||||||
|
class="btn px-6 bg-linear-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300"
|
||||||
|
@click="store.addMedia(props.media)"
|
||||||
|
>
|
||||||
|
Add to list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="btn px-6 bg-linear-to-r from-red-50 to-red-100 border border-red-200 text-red-600 hover:from-red-100 hover:to-red-200"
|
||||||
|
@click="store.removeMedia(props.media.Id)"
|
||||||
|
>
|
||||||
|
Remove from library
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- <RouterLink
|
||||||
|
:to="{
|
||||||
|
name: 'watch',
|
||||||
|
params: {
|
||||||
|
name: props.tvSeries!.Name,
|
||||||
|
id: props.tvSeries!.Id,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn relative flex items-center gap-2 px-6 bg-linear-to-r from-indigo-500 to-violet-500 text-white border-0 shadow-md hover:opacity-90 transition"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M6.5 5.5a1 1 0 0 1 1.52-.85l6 4.5a1 1 0 0 1 0 1.7l-6 4.5A1 1 0 0 1 6.5 14.5v-9z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>Watch</span>
|
||||||
|
<span class="badge badge-sm badge-secondary absolute -top-2 -right-2">Beta</span>
|
||||||
|
</button>
|
||||||
|
</RouterLink> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ErrorAlert from '@/components/alerts/ErrorAlert.vue'
|
import ErrorAlert from '@/components/alerts/ErrorAlert.vue'
|
||||||
import MediaDetails from '@/components/MediaDetails.vue'
|
import MovieDetails from '@/components/MovieDetails.vue'
|
||||||
|
import TVSeriesDetails from '@/components/TVSeriesDetails.vue'
|
||||||
import { getMovieDetails, getSeriesDetails } from '@/lib/api'
|
import { getMovieDetails, getSeriesDetails } from '@/lib/api'
|
||||||
import { mapMovieDetailsToMedia, mapTvSeriesDetailsToMedia } from '@/types/MediaMap'
|
import { mapMovieDetailsToMedia, mapTvSeriesDetailsToMedia } from '@/types/MediaMap'
|
||||||
import type { MovieDetailsType } from '@/types/Movie'
|
import type { MovieDetailsType } from '@/types/Movie'
|
||||||
@@ -57,17 +58,23 @@ onMounted(async () => {
|
|||||||
<ErrorAlert v-if="errorMessage" :message="errorMessage" />
|
<ErrorAlert v-if="errorMessage" :message="errorMessage" />
|
||||||
|
|
||||||
<!-- Loading -->
|
<!-- Loading -->
|
||||||
<div v-else-if="isLoading" class="flex justify-center items-center">
|
<!-- Loading -->
|
||||||
|
<div v-else-if="isLoading" class="flex justify-center items-center min-h-screen">
|
||||||
<span class="loading loading-ring loading-lg text-primary"></span>
|
<span class="loading loading-ring loading-lg text-primary"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Details -->
|
<!-- Details -->
|
||||||
<MediaDetails
|
<MovieDetails
|
||||||
v-else-if="movieDetails || tvSeriesDetails"
|
v-else-if="movieDetails"
|
||||||
:type="route.params.type === 'movie' ? 'movie' : 'tv'"
|
|
||||||
:media="media"
|
:media="media"
|
||||||
:movie="movieDetails"
|
:movie="movieDetails"
|
||||||
:tv-series="tvSeriesDetails"
|
v-motion-fade-visible-once
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TVSeriesDetails
|
||||||
|
v-else-if="tvSeriesDetails"
|
||||||
|
:media="media"
|
||||||
|
:tvSeries="tvSeriesDetails"
|
||||||
v-motion-fade-visible-once
|
v-motion-fade-visible-once
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user