mirror of
https://github.com/mmahdium/TBW.git
synced 2026-08-14 16:22:48 +03:30
UI improvements and code simplification
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
+3
-3
@@ -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
-2
@@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
+6
-14
@@ -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