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<{
 | 
					const props = defineProps<{
 | 
				
			||||||
  movie: MovieType
 | 
					  movie: MovieType
 | 
				
			||||||
  loading: boolean | undefined
 | 
					 | 
				
			||||||
}>()
 | 
					}>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits<{ (e: 'loaded', id: string): void }>()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const store = useMoviesStore()
 | 
					const store = useMoviesStore()
 | 
				
			||||||
const imageLoadFailed = ref(false)
 | 
					const imageLoadFailed = ref(false)
 | 
				
			||||||
 | 
					const loaded = ref(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const alreadyAdded = computed(() =>
 | 
					const alreadyAdded = computed(() =>
 | 
				
			||||||
  store.movieList.some((movie) => movie.imdbID === props.movie.imdbID),
 | 
					  store.movieList.some((movie) => movie.imdbID === props.movie.imdbID),
 | 
				
			||||||
@@ -26,7 +24,7 @@ const alreadyAdded = computed(() =>
 | 
				
			|||||||
    <!-- Poster -->
 | 
					    <!-- Poster -->
 | 
				
			||||||
    <router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
 | 
					    <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">
 | 
					      <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">
 | 
					        <div v-else-if="imageLoadFailed" class="flex items-center justify-center">
 | 
				
			||||||
          <svg
 | 
					          <svg
 | 
				
			||||||
@@ -46,15 +44,15 @@ const alreadyAdded = computed(() =>
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <img
 | 
					        <img
 | 
				
			||||||
          v-show="!props.loading && !imageLoadFailed"
 | 
					          v-show="loaded && !imageLoadFailed"
 | 
				
			||||||
          :src="props.movie.Poster"
 | 
					          :src="props.movie.Poster"
 | 
				
			||||||
          :alt="props.movie.Title"
 | 
					          :alt="props.movie.Title"
 | 
				
			||||||
          class="object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
 | 
					          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="
 | 
					          @error="
 | 
				
			||||||
            () => {
 | 
					            () => {
 | 
				
			||||||
              imageLoadFailed = true
 | 
					              imageLoadFailed = true
 | 
				
			||||||
              emit('loaded', props.movie.imdbID)
 | 
					              loaded = true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          "
 | 
					          "
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
@@ -73,14 +71,16 @@ const alreadyAdded = computed(() =>
 | 
				
			|||||||
      </router-link>
 | 
					      </router-link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="card-actions justify-end mt-auto">
 | 
					      <div class="card-actions justify-end mt-auto">
 | 
				
			||||||
        <button v-motion-fade-visible-once
 | 
					        <button
 | 
				
			||||||
 | 
					          v-motion-fade-visible-once
 | 
				
			||||||
          v-if="!alreadyAdded"
 | 
					          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"
 | 
					          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)"
 | 
					          @click="store.addMovie(props.movie)"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          Add
 | 
					          Add
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
        <button v-motion-fade-visible-once
 | 
					        <button
 | 
				
			||||||
 | 
					          v-motion-fade-visible-once
 | 
				
			||||||
          v-else
 | 
					          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"
 | 
					          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)"
 | 
					          @click="store.removeMovie(props.movie.imdbID)"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import AddMoreCard from './AddMoreCard.vue'
 | 
				
			||||||
import MovieCard from './MovieCard.vue'
 | 
					import MovieCard from './MovieCard.vue'
 | 
				
			||||||
import type { MovieType } from '@/types/Movie'
 | 
					import type { MovieType } from '@/types/Movie'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
  movies: MovieType[]
 | 
					  movies: MovieType[]
 | 
				
			||||||
  loadingImages: Record<string, boolean>
 | 
					 | 
				
			||||||
  loadingMore: boolean
 | 
					  loadingMore: boolean
 | 
				
			||||||
  isSearch: 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"
 | 
					      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>
 | 
					      <li v-for="movie in props.movies" :key="movie.imdbID" v-auto-animate>
 | 
				
			||||||
        <MovieCard
 | 
					        <MovieCard :movie="movie" />
 | 
				
			||||||
          :movie="movie"
 | 
					      </li>
 | 
				
			||||||
          :loading="props.loadingImages[movie.imdbID]"
 | 
					      <li>
 | 
				
			||||||
          @loaded="emit('loaded', $event)"
 | 
					        <AddMoreCard />
 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </li>
 | 
					      </li>
 | 
				
			||||||
    </ul>
 | 
					    </ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import { ref } from 'vue'
 | 
				
			|||||||
const props = defineProps<{ modelValue: string }>()
 | 
					const props = defineProps<{ modelValue: string }>()
 | 
				
			||||||
const emit = defineEmits<{ (e: 'update:modelValue', value: string): void; (e: 'submit'): void }>()
 | 
					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)
 | 
					const localQuery = ref(props.modelValue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onSubmit() {
 | 
					function onSubmit() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
 | 
					import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routes: RouteRecordRaw[] = [
 | 
					const routes: RouteRecordRaw[] = [
 | 
				
			||||||
  { 
 | 
					  {
 | 
				
			||||||
    path: '/:pathMatch(.*)*', 
 | 
					    path: '/:pathMatch(.*)*',
 | 
				
			||||||
    name: 'NotFound', 
 | 
					    name: 'NotFound',
 | 
				
			||||||
    component: () => import('@/views/NotFoundView.vue'),
 | 
					    component: () => import('@/views/NotFoundView.vue'),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
@import 'tailwindcss';
 | 
					@import 'tailwindcss';
 | 
				
			||||||
@plugin "daisyui" {
 | 
					@plugin "daisyui" {
 | 
				
			||||||
  themes:
 | 
					  themes: lofi --default;
 | 
				
			||||||
    lofi --default
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,6 @@ import { useSearchPageStore } from '@/stores/movies'
 | 
				
			|||||||
const movies = ref<MovieType[]>()
 | 
					const movies = ref<MovieType[]>()
 | 
				
			||||||
const seachError = ref<string>('')
 | 
					const seachError = ref<string>('')
 | 
				
			||||||
const searchQuery = ref('')
 | 
					const searchQuery = ref('')
 | 
				
			||||||
const loadingImages = ref<Record<string, boolean>>({})
 | 
					 | 
				
			||||||
const searchPage = ref(1)
 | 
					const searchPage = ref(1)
 | 
				
			||||||
const isSearching = ref(false)
 | 
					const isSearching = ref(false)
 | 
				
			||||||
const isLoadingMore = ref(false)
 | 
					const isLoadingMore = ref(false)
 | 
				
			||||||
@@ -28,19 +27,9 @@ async function searchMovie() {
 | 
				
			|||||||
      return
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    seachError.value = ''
 | 
					    seachError.value = ''
 | 
				
			||||||
    const previouslyLoaded = Object.keys(loadingImages.value).filter((id) =>
 | 
					 | 
				
			||||||
      result.Search.some((m: MovieType) => m.imdbID === id),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    movies.value = result.Search
 | 
					    movies.value = result.Search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.setState(searchPage.value, searchQuery.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) {
 | 
					  } catch (error) {
 | 
				
			||||||
    movies.value = []
 | 
					    movies.value = []
 | 
				
			||||||
    console.error(error)
 | 
					    console.error(error)
 | 
				
			||||||
@@ -57,9 +46,6 @@ async function loadMore() {
 | 
				
			|||||||
    const result = await loadMoreMovies(searchQuery.value, searchPage.value)
 | 
					    const result = await loadMoreMovies(searchQuery.value, searchPage.value)
 | 
				
			||||||
    movies.value?.push(...result.Search)
 | 
					    movies.value?.push(...result.Search)
 | 
				
			||||||
    state.setState(searchPage.value, searchQuery.value, movies.value ? movies.value : [])
 | 
					    state.setState(searchPage.value, searchQuery.value, movies.value ? movies.value : [])
 | 
				
			||||||
    for (const m of result.Search) {
 | 
					 | 
				
			||||||
      loadingImages.value[m.imdbID] = true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    searchPage.value = 1
 | 
					    searchPage.value = 1
 | 
				
			||||||
    seachError.value = (error as Error).message
 | 
					    seachError.value = (error as Error).message
 | 
				
			||||||
@@ -68,10 +54,6 @@ async function loadMore() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleLoaded(id: string) {
 | 
					 | 
				
			||||||
  loadingImages.value[id] = false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
  if (
 | 
					  if (
 | 
				
			||||||
    state.searchQuery !== '' &&
 | 
					    state.searchQuery !== '' &&
 | 
				
			||||||
@@ -122,9 +104,7 @@ watch(searchQuery, () => {
 | 
				
			|||||||
    <MovieList
 | 
					    <MovieList
 | 
				
			||||||
      v-else-if="movies && movies.length > 0"
 | 
					      v-else-if="movies && movies.length > 0"
 | 
				
			||||||
      :movies="movies"
 | 
					      :movies="movies"
 | 
				
			||||||
      :loading-images="loadingImages"
 | 
					 | 
				
			||||||
      :loading-more="isLoadingMore"
 | 
					      :loading-more="isLoadingMore"
 | 
				
			||||||
      @loaded="handleLoaded"
 | 
					 | 
				
			||||||
      @loadMore="loadMore"
 | 
					      @loadMore="loadMore"
 | 
				
			||||||
      :is-search="true"
 | 
					      :is-search="true"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,30 +1,22 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useMoviesStore } from '@/stores/movies'
 | 
					import { useMoviesStore } from '@/stores/movies'
 | 
				
			||||||
import MovieList from '@/components/MovieList.vue'
 | 
					import MovieList from '@/components/MovieList.vue'
 | 
				
			||||||
import { onMounted, ref } from 'vue'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const loadingImages = ref<Record<string, boolean>>({})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const store = useMoviesStore()
 | 
					const store = useMoviesStore()
 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleLoaded(id: string) {
 | 
					 | 
				
			||||||
  loadingImages.value[id] = false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					 | 
				
			||||||
  loadingImages.value = Object.fromEntries(store.movieList.map((movie) => [movie.imdbID, true]))
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<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
 | 
					    <MovieList
 | 
				
			||||||
      v-auto-animate
 | 
					      v-auto-animate
 | 
				
			||||||
      v-if="store.movieList"
 | 
					      v-if="store.movieList"
 | 
				
			||||||
      :movies="store.movieList"
 | 
					      :movies="store.movieList"
 | 
				
			||||||
      :loading-images="loadingImages"
 | 
					 | 
				
			||||||
      :loading-more="false"
 | 
					      :loading-more="false"
 | 
				
			||||||
      @loaded="handleLoaded"
 | 
					 | 
				
			||||||
      :is-search="false"
 | 
					      :is-search="false"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user