first commit

This commit is contained in:
2025-10-20 17:10:51 +03:30
commit 42b184d9e9
35 changed files with 4701 additions and 0 deletions

110
src/views/AddView.vue Normal file
View 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
View 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
View 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
View 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>

View 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>