mirror of
				https://github.com/mmahdium/TBW.git
				synced 2025-11-04 09:09:24 +01:00 
			
		
		
		
	UI improvements and code simplification
This commit is contained in:
		
							
								
								
									
										26
									
								
								src/components/AddMoreCard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/components/AddMoreCard.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
<script setup lang="ts"></script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <router-link
 | 
			
		||||
    to="/add"
 | 
			
		||||
    class="card relative w-full h-full flex items-center justify-center aspect-[2/3] bg-white/40 backdrop-blur-md border-2 border-dashed border-gray-300 rounded-lg shadow-sm hover:shadow-md hover:border-gray-400 transition-all duration-300"
 | 
			
		||||
    v-motion-fade-visible-once
 | 
			
		||||
  >
 | 
			
		||||
    <!-- Plus icon -->
 | 
			
		||||
    <div
 | 
			
		||||
      class="flex flex-col items-center justify-center text-gray-400 hover:text-gray-600 transition"
 | 
			
		||||
    >
 | 
			
		||||
      <svg
 | 
			
		||||
        xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
        class="w-16 h-16 mb-2"
 | 
			
		||||
        fill="none"
 | 
			
		||||
        viewBox="0 0 24 24"
 | 
			
		||||
        stroke="currentColor"
 | 
			
		||||
        stroke-width="2"
 | 
			
		||||
      >
 | 
			
		||||
        <path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
 | 
			
		||||
      </svg>
 | 
			
		||||
      <span class="text-lg font-semibold">Add More</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </router-link>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -5,13 +5,11 @@ import type { MovieType } from '@/types/Movie'
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  movie: MovieType
 | 
			
		||||
  loading: boolean | undefined
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{ (e: 'loaded', id: string): void }>()
 | 
			
		||||
 | 
			
		||||
const store = useMoviesStore()
 | 
			
		||||
const imageLoadFailed = ref(false)
 | 
			
		||||
const loaded = ref(false)
 | 
			
		||||
 | 
			
		||||
const alreadyAdded = computed(() =>
 | 
			
		||||
  store.movieList.some((movie) => movie.imdbID === props.movie.imdbID),
 | 
			
		||||
@@ -26,7 +24,7 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
    <!-- Poster -->
 | 
			
		||||
    <router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
 | 
			
		||||
      <figure class="overflow-hidden flex items-center justify-center aspect-[2/3] bg-gray-50">
 | 
			
		||||
        <span v-if="props.loading" class="loading loading-ring loading-lg text-primary"></span>
 | 
			
		||||
        <span v-if="!loaded" class="loading loading-ring loading-lg text-primary"></span>
 | 
			
		||||
 | 
			
		||||
        <div v-else-if="imageLoadFailed" class="flex items-center justify-center">
 | 
			
		||||
          <svg
 | 
			
		||||
@@ -46,15 +44,15 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <img
 | 
			
		||||
          v-show="!props.loading && !imageLoadFailed"
 | 
			
		||||
          v-show="loaded && !imageLoadFailed"
 | 
			
		||||
          :src="props.movie.Poster"
 | 
			
		||||
          :alt="props.movie.Title"
 | 
			
		||||
          class="object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
 | 
			
		||||
          @load="emit('loaded', props.movie.imdbID)"
 | 
			
		||||
          @load="loaded = true"
 | 
			
		||||
          @error="
 | 
			
		||||
            () => {
 | 
			
		||||
              imageLoadFailed = true
 | 
			
		||||
              emit('loaded', props.movie.imdbID)
 | 
			
		||||
              loaded = true
 | 
			
		||||
            }
 | 
			
		||||
          "
 | 
			
		||||
        />
 | 
			
		||||
@@ -73,14 +71,16 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
      </router-link>
 | 
			
		||||
 | 
			
		||||
      <div class="card-actions justify-end mt-auto">
 | 
			
		||||
        <button v-motion-fade-visible-once
 | 
			
		||||
        <button
 | 
			
		||||
          v-motion-fade-visible-once
 | 
			
		||||
          v-if="!alreadyAdded"
 | 
			
		||||
          class="btn btn-sm px-4 bg-gradient-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300 hover:text-gray-900 transition"
 | 
			
		||||
          @click="store.addMovie(props.movie)"
 | 
			
		||||
        >
 | 
			
		||||
          Add
 | 
			
		||||
        </button>
 | 
			
		||||
        <button v-motion-fade-visible-once
 | 
			
		||||
        <button
 | 
			
		||||
          v-motion-fade-visible-once
 | 
			
		||||
          v-else
 | 
			
		||||
          class="btn btn-sm px-4 bg-gradient-to-r from-red-50 to-red-100 border border-red-200 text-red-600 hover:from-red-100 hover:to-red-200 hover:text-red-700 transition"
 | 
			
		||||
          @click="store.removeMovie(props.movie.imdbID)"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import AddMoreCard from './AddMoreCard.vue'
 | 
			
		||||
import MovieCard from './MovieCard.vue'
 | 
			
		||||
import type { MovieType } from '@/types/Movie'
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  movies: MovieType[]
 | 
			
		||||
  loadingImages: Record<string, boolean>
 | 
			
		||||
  loadingMore: boolean
 | 
			
		||||
  isSearch: boolean
 | 
			
		||||
}>()
 | 
			
		||||
@@ -30,11 +30,10 @@ const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): voi
 | 
			
		||||
      class="grid gap-4 grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
 | 
			
		||||
    >
 | 
			
		||||
      <li v-for="movie in props.movies" :key="movie.imdbID" v-auto-animate>
 | 
			
		||||
        <MovieCard
 | 
			
		||||
          :movie="movie"
 | 
			
		||||
          :loading="props.loadingImages[movie.imdbID]"
 | 
			
		||||
          @loaded="emit('loaded', $event)"
 | 
			
		||||
        />
 | 
			
		||||
        <MovieCard :movie="movie" />
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <AddMoreCard />
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import { ref } from 'vue'
 | 
			
		||||
const props = defineProps<{ modelValue: string }>()
 | 
			
		||||
const emit = defineEmits<{ (e: 'update:modelValue', value: string): void; (e: 'submit'): void }>()
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
const localQuery = ref(props.modelValue)
 | 
			
		||||
 | 
			
		||||
function onSubmit() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
@import 'tailwindcss';
 | 
			
		||||
@plugin "daisyui" {
 | 
			
		||||
  themes:
 | 
			
		||||
    lofi --default
 | 
			
		||||
  themes: lofi --default;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import { useSearchPageStore } from '@/stores/movies'
 | 
			
		||||
const movies = ref<MovieType[]>()
 | 
			
		||||
const seachError = ref<string>('')
 | 
			
		||||
const searchQuery = ref('')
 | 
			
		||||
const loadingImages = ref<Record<string, boolean>>({})
 | 
			
		||||
const searchPage = ref(1)
 | 
			
		||||
const isSearching = ref(false)
 | 
			
		||||
const isLoadingMore = ref(false)
 | 
			
		||||
@@ -28,19 +27,9 @@ async function searchMovie() {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    seachError.value = ''
 | 
			
		||||
    const previouslyLoaded = Object.keys(loadingImages.value).filter((id) =>
 | 
			
		||||
      result.Search.some((m: MovieType) => m.imdbID === id),
 | 
			
		||||
    )
 | 
			
		||||
    movies.value = result.Search
 | 
			
		||||
 | 
			
		||||
    state.setState(searchPage.value, searchQuery.value, result.Search)
 | 
			
		||||
 | 
			
		||||
    for (const m of result.Search) {
 | 
			
		||||
      if (previouslyLoaded.includes(m.imdbID)) {
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      loadingImages.value[m.imdbID] = true
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    movies.value = []
 | 
			
		||||
    console.error(error)
 | 
			
		||||
@@ -57,9 +46,6 @@ async function loadMore() {
 | 
			
		||||
    const result = await loadMoreMovies(searchQuery.value, searchPage.value)
 | 
			
		||||
    movies.value?.push(...result.Search)
 | 
			
		||||
    state.setState(searchPage.value, searchQuery.value, movies.value ? movies.value : [])
 | 
			
		||||
    for (const m of result.Search) {
 | 
			
		||||
      loadingImages.value[m.imdbID] = true
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    searchPage.value = 1
 | 
			
		||||
    seachError.value = (error as Error).message
 | 
			
		||||
@@ -68,10 +54,6 @@ async function loadMore() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleLoaded(id: string) {
 | 
			
		||||
  loadingImages.value[id] = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (
 | 
			
		||||
    state.searchQuery !== '' &&
 | 
			
		||||
@@ -122,9 +104,7 @@ watch(searchQuery, () => {
 | 
			
		||||
    <MovieList
 | 
			
		||||
      v-else-if="movies && movies.length > 0"
 | 
			
		||||
      :movies="movies"
 | 
			
		||||
      :loading-images="loadingImages"
 | 
			
		||||
      :loading-more="isLoadingMore"
 | 
			
		||||
      @loaded="handleLoaded"
 | 
			
		||||
      @loadMore="loadMore"
 | 
			
		||||
      :is-search="true"
 | 
			
		||||
    />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,22 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useMoviesStore } from '@/stores/movies'
 | 
			
		||||
import MovieList from '@/components/MovieList.vue'
 | 
			
		||||
import { onMounted, ref } from 'vue'
 | 
			
		||||
 | 
			
		||||
const loadingImages = ref<Record<string, boolean>>({})
 | 
			
		||||
 | 
			
		||||
const store = useMoviesStore()
 | 
			
		||||
 | 
			
		||||
function handleLoaded(id: string) {
 | 
			
		||||
  loadingImages.value[id] = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  loadingImages.value = Object.fromEntries(store.movieList.map((movie) => [movie.imdbID, true]))
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container mx-auto px-4 py-8" v-motion-fade-visible-once>
 | 
			
		||||
  <div class="container mx-auto px-4 py-12" v-motion-fade-visible-once>
 | 
			
		||||
    <h1
 | 
			
		||||
      class="text-4xl font-extrabold text-center mb-10 bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
 | 
			
		||||
    >
 | 
			
		||||
      Movie Library
 | 
			
		||||
    </h1>
 | 
			
		||||
    <MovieList
 | 
			
		||||
      v-auto-animate
 | 
			
		||||
      v-if="store.movieList"
 | 
			
		||||
      :movies="store.movieList"
 | 
			
		||||
      :loading-images="loadingImages"
 | 
			
		||||
      :loading-more="false"
 | 
			
		||||
      @loaded="handleLoaded"
 | 
			
		||||
      :is-search="false"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user