mirror of
https://github.com/mmahdium/TBW.git
synced 2025-12-21 13:13:55 +01:00
first commit
This commit is contained in:
110
src/views/AddView.vue
Normal file
110
src/views/AddView.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { loadMoreMovies, searchMovies } from '@/lib/api'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import SearchBar from '@/components/SearchBar.vue'
|
||||
import MovieList from '@/components/MovieList.vue'
|
||||
import type { MovieType } from '@/types/Movie'
|
||||
import ErrorAlert from '@/components/alerts/ErrorAlert.vue'
|
||||
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)
|
||||
|
||||
const state = useSearchPageStore()
|
||||
|
||||
async function searchMovie() {
|
||||
try {
|
||||
isSearching.value = true
|
||||
searchPage.value = 1
|
||||
const result = await searchMovies(searchQuery.value)
|
||||
if (!result.Response) {
|
||||
seachError.value = result.ErrorMessage
|
||||
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) {
|
||||
console.error(error)
|
||||
seachError.value = (error as Error).message
|
||||
} finally {
|
||||
isSearching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function loadMore() {
|
||||
try {
|
||||
isLoadingMore.value = true
|
||||
searchPage.value++
|
||||
const result = await loadMoreMovies(searchQuery.value, searchPage.value)
|
||||
movies.value.push(...result.Search)
|
||||
state.setState(searchPage.value, searchQuery.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
|
||||
} finally {
|
||||
isLoadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleLoaded(id: string) {
|
||||
loadingImages.value[id] = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (state.searchQuery !== '' && state.searchPage !== 0 && state.movieList.length > 0) {
|
||||
searchQuery.value = state.searchQuery
|
||||
searchPage.value = state.searchPage
|
||||
movies.value = state.movieList
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-3xl font-bold text-center mb-8">Add a Movie</h1>
|
||||
|
||||
<SearchBar v-model="searchQuery" @submit="searchMovie" />
|
||||
|
||||
<div v-if="isSearching" class="flex justify-center my-12">
|
||||
<span class="loading loading-ring loading-lg"></span>
|
||||
</div>
|
||||
|
||||
<MovieList
|
||||
v-else-if="movies && movies.length > 0"
|
||||
:movies="movies"
|
||||
:loading-images="loadingImages"
|
||||
:loading-more="isLoadingMore"
|
||||
@loaded="handleLoaded"
|
||||
@loadMore="loadMore"
|
||||
:is-search="true"
|
||||
/>
|
||||
|
||||
<p v-else class="text-center text-gray-500">Search for a movie to add it to your list</p>
|
||||
|
||||
<div class="flex justify-center pt-2">
|
||||
<ErrorAlert v-if="seachError" :message="seachError" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
41
src/views/DetailsView.vue
Normal file
41
src/views/DetailsView.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import MovieDetails from '@/components/MovieDetails.vue'
|
||||
import { getMovie } from '@/lib/api'
|
||||
import type { MovieDetailsType } from '@/types/Movie'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const movie = ref<MovieDetailsType>()
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await getMovie(route.params.id as string)
|
||||
movie.value = response
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
movie.value = {
|
||||
Title: '',
|
||||
Year: '',
|
||||
imdbID: '',
|
||||
Type: '',
|
||||
Poster: '',
|
||||
Plot: '',
|
||||
Language: '',
|
||||
Country: '',
|
||||
imdbRating: '',
|
||||
Error: true,
|
||||
ErrorMessage: (error as Error).message,
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<MovieDetails :movie="movie" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
40
src/views/HomeView.vue
Normal file
40
src/views/HomeView.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<h1 class="text-5xl font-bold text-center mb-12">
|
||||
Welcome to <br /><span class="text-6xl font-bold text-center gradient-text"
|
||||
>To Be Watched</span
|
||||
>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-center mb-12">
|
||||
A simple and beautiful movie list app built with Vue 3 and Tailwind CSS (daisyUI).
|
||||
</p>
|
||||
<RouterLink to="/add" class="btn btn-primary btn-lg">Get Started</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.gradient-text {
|
||||
background: linear-gradient(to right, black 0%, red 50%, blue 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-size: 200% auto;
|
||||
animation: gradient 3s ease infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: left center;
|
||||
}
|
||||
100% {
|
||||
background-position: right center;
|
||||
}
|
||||
50% {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
32
src/views/ListView.vue
Normal file
32
src/views/ListView.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<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">
|
||||
<MovieList
|
||||
v-if="store.movieList"
|
||||
:movies="store.movieList"
|
||||
:loading-images="loadingImages"
|
||||
:loading-more="false"
|
||||
@loaded="handleLoaded"
|
||||
:is-search="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
11
src/views/NotFoundView.vue
Normal file
11
src/views/NotFoundView.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-center">Not found</h1>
|
||||
<router-link to="/">Home</router-link>
|
||||
<div class="container mx-auto px-4 py-8"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user