mirror of
				https://github.com/mmahdium/TBW.git
				synced 2025-11-04 09:09:24 +01:00 
			
		
		
		
	UI improvements
This commit is contained in:
		@@ -19,12 +19,21 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="card bg-base-100 shadow-sm hover:shadow-md transition rounded-sm w-full h-full">
 | 
			
		||||
  <div
 | 
			
		||||
    class="card relative w-full h-full overflow-hidden
 | 
			
		||||
           bg-white/70 backdrop-blur-md border border-gray-200/60
 | 
			
		||||
           shadow-md hover:shadow-xl transition-all duration-300"
 | 
			
		||||
    v-motion-fade-visible-once
 | 
			
		||||
  >
 | 
			
		||||
    <!-- Poster -->
 | 
			
		||||
    <router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
 | 
			
		||||
      <figure v-motion-fade-visible-once
 | 
			
		||||
        class="overflow-hidden flex items-center justify-center bg-gray-100 aspect-[2/3] rounded-t-sm"
 | 
			
		||||
      <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-xl text-primary"></span>
 | 
			
		||||
        <span
 | 
			
		||||
          v-if="props.loading"
 | 
			
		||||
          class="loading loading-ring loading-lg text-primary"
 | 
			
		||||
        ></span>
 | 
			
		||||
 | 
			
		||||
        <div v-else-if="imageLoadFailed" class="flex items-center justify-center">
 | 
			
		||||
          <svg
 | 
			
		||||
@@ -33,7 +42,7 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
            viewBox="0 0 24 24"
 | 
			
		||||
            stroke-width="1.5"
 | 
			
		||||
            stroke="currentColor"
 | 
			
		||||
            class="w-12 h-12 text-gray-400"
 | 
			
		||||
            class="w-12 h-12 text-gray-300"
 | 
			
		||||
          >
 | 
			
		||||
            <path
 | 
			
		||||
              stroke-linecap="round"
 | 
			
		||||
@@ -47,7 +56,7 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
          v-show="!props.loading && !imageLoadFailed"
 | 
			
		||||
          :src="props.movie.Poster"
 | 
			
		||||
          :alt="props.movie.Title"
 | 
			
		||||
          class="object-cover w-full h-full"
 | 
			
		||||
          class="object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
 | 
			
		||||
          @load="emit('loaded', props.movie.imdbID)"
 | 
			
		||||
          @error="
 | 
			
		||||
            () => {
 | 
			
		||||
@@ -59,21 +68,40 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
      </figure>
 | 
			
		||||
    </router-link>
 | 
			
		||||
 | 
			
		||||
    <!-- Body -->
 | 
			
		||||
    <div class="card-body p-4 flex flex-col">
 | 
			
		||||
      <router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
 | 
			
		||||
        <h2 class="card-title text-lg">{{ props.movie.Title }}</h2>
 | 
			
		||||
        <p class="text-sm text-gray-500">{{ props.movie.Year }}</p>
 | 
			
		||||
        <h2
 | 
			
		||||
          class="card-title text-base font-semibold
 | 
			
		||||
                 bg-gradient-to-r from-gray-700 to-gray-500
 | 
			
		||||
                 bg-clip-text text-transparent"
 | 
			
		||||
        >
 | 
			
		||||
          {{ props.movie.Title }}
 | 
			
		||||
        </h2>
 | 
			
		||||
        <p class="text-sm text-gray-400">{{ props.movie.Year }}</p>
 | 
			
		||||
      </router-link>
 | 
			
		||||
 | 
			
		||||
      <div class="card-actions justify-end mt-auto">
 | 
			
		||||
        <button
 | 
			
		||||
          v-if="!alreadyAdded"
 | 
			
		||||
          class="btn btn-sm btn-primary"
 | 
			
		||||
          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-else class="btn btn-sm btn-error" @click="store.removeMovie(props.movie.imdbID)">
 | 
			
		||||
        <button
 | 
			
		||||
          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)"
 | 
			
		||||
        >
 | 
			
		||||
          Delete
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ import { useMoviesStore } from '@/stores/movies'
 | 
			
		||||
const props = defineProps<{ movie: MovieDetailsType | undefined }>()
 | 
			
		||||
 | 
			
		||||
const imageLoaded = ref(false)
 | 
			
		||||
 | 
			
		||||
const store = useMoviesStore()
 | 
			
		||||
 | 
			
		||||
const alreadyAdded = computed(() =>
 | 
			
		||||
@@ -16,57 +15,87 @@ const alreadyAdded = computed(() =>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <ErrorAlert class="" v-if="props.movie?.Error" :message="props.movie.ErrorMessage" />
 | 
			
		||||
  <ErrorAlert v-if="props.movie?.Error" :message="props.movie.ErrorMessage" />
 | 
			
		||||
 | 
			
		||||
  <div v-else class="hero bg-base-200 min-h-screen rounded-md">
 | 
			
		||||
    <!-- Loading state for hero content -->
 | 
			
		||||
    <div v-if="props.movie === undefined" class="hero-content">
 | 
			
		||||
  <div
 | 
			
		||||
    v-else
 | 
			
		||||
    class="min-h-screen flex items-center justify-center px-6 py-12
 | 
			
		||||
           bg-gradient-to-b from-gray-50 to-white"
 | 
			
		||||
  >
 | 
			
		||||
    <!-- Loading state -->
 | 
			
		||||
    <div v-if="props.movie === undefined" class="flex justify-center items-center">
 | 
			
		||||
      <span class="loading loading-ring loading-lg text-primary"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Actual hero content -->
 | 
			
		||||
    <div v-else class="hero bg-base-200 min-h-screen rounded-md" v-motion-fade-visible-once>
 | 
			
		||||
      <div class="hero-content flex-col lg:flex-row gap-12">
 | 
			
		||||
        <!-- Poster -->
 | 
			
		||||
        <figure class="flex-shrink-0">
 | 
			
		||||
          <span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
 | 
			
		||||
          <img
 | 
			
		||||
            v-show="imageLoaded"
 | 
			
		||||
            :src="props.movie.Poster"
 | 
			
		||||
            alt="Poster"
 | 
			
		||||
            class="w-full max-w-lg rounded-lg shadow-2xl"
 | 
			
		||||
            @load="imageLoaded = true"
 | 
			
		||||
          />
 | 
			
		||||
        </figure>
 | 
			
		||||
    <!-- Hero content -->
 | 
			
		||||
    <div
 | 
			
		||||
      v-else
 | 
			
		||||
      class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full
 | 
			
		||||
             bg-white/70 backdrop-blur-md border border-gray-200/60
 | 
			
		||||
             shadow-md rounded-xl p-8 transition"
 | 
			
		||||
      v-motion-fade-visible-once
 | 
			
		||||
    >
 | 
			
		||||
      <!-- Poster -->
 | 
			
		||||
      <figure class="flex-shrink-0">
 | 
			
		||||
        <span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
 | 
			
		||||
        <img
 | 
			
		||||
          v-show="imageLoaded"
 | 
			
		||||
          :src="props.movie.Poster"
 | 
			
		||||
          alt="Poster"
 | 
			
		||||
          class="w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
 | 
			
		||||
          @load="imageLoaded = true"
 | 
			
		||||
        />
 | 
			
		||||
      </figure>
 | 
			
		||||
 | 
			
		||||
        <!-- Text -->
 | 
			
		||||
        <div class="max-w-2xl">
 | 
			
		||||
          <h1 class="text-5xl font-bold">
 | 
			
		||||
            {{ props.movie.Title }}
 | 
			
		||||
            <span class="text-gray-500 text-sm">({{ props.movie.Year }})</span>
 | 
			
		||||
          </h1>
 | 
			
		||||
          <p class="py-6">{{ props.movie.Plot }}</p>
 | 
			
		||||
          <div class="flex flex-wrap gap-2">
 | 
			
		||||
            <span class="badge badge-dash badge-primary">
 | 
			
		||||
              Language: {{ props.movie.Language }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="badge badge-dash badge-secondary">
 | 
			
		||||
              Country: {{ props.movie.Country }}
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="badge badge-dash badge-info">
 | 
			
		||||
              IMDB Rating: {{ props.movie.imdbRating }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div v-if="!alreadyAdded" class="card-actions mt-4">
 | 
			
		||||
            <button class="btn -sm btn-primary" @click="store.addMovie(props.movie)">
 | 
			
		||||
              Add to list
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div v-else class="card-actions mt-4">
 | 
			
		||||
            <button class="btn btn-error" @click="store.removeMovie(props.movie.imdbID)">
 | 
			
		||||
              Remove from list
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
      <!-- Text -->
 | 
			
		||||
      <div class="flex-1">
 | 
			
		||||
        <h1 class="text-4xl font-bold text-gray-800 mb-4">
 | 
			
		||||
          {{ props.movie.Title }}
 | 
			
		||||
          <span class="text-gray-400 text-lg font-normal">({{ props.movie.Year }})</span>
 | 
			
		||||
        </h1>
 | 
			
		||||
 | 
			
		||||
        <p class="text-gray-600 leading-relaxed mb-6">{{ props.movie.Plot }}</p>
 | 
			
		||||
 | 
			
		||||
        <!-- Badges -->
 | 
			
		||||
        <div class="flex flex-wrap gap-2 mb-6">
 | 
			
		||||
          <span
 | 
			
		||||
            class="px-3 py-1 rounded-md text-sm
 | 
			
		||||
                   bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
 | 
			
		||||
          >
 | 
			
		||||
            Language: {{ props.movie.Language }}
 | 
			
		||||
          </span>
 | 
			
		||||
          <span
 | 
			
		||||
            class="px-3 py-1 rounded-md text-sm
 | 
			
		||||
                   bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
 | 
			
		||||
          >
 | 
			
		||||
            Country: {{ props.movie.Country }}
 | 
			
		||||
          </span>
 | 
			
		||||
          <span
 | 
			
		||||
            class="px-3 py-1 rounded-md text-sm
 | 
			
		||||
                   bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
 | 
			
		||||
          >
 | 
			
		||||
            IMDB Rating: {{ props.movie.imdbRating }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Actions -->
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          <button
 | 
			
		||||
            v-if="!alreadyAdded"
 | 
			
		||||
            class="btn px-6 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"
 | 
			
		||||
            @click="store.addMovie(props.movie)"
 | 
			
		||||
          >
 | 
			
		||||
            Add to list
 | 
			
		||||
          </button>
 | 
			
		||||
          <button
 | 
			
		||||
            v-else
 | 
			
		||||
            class="btn px-6 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"
 | 
			
		||||
            @click="store.removeMovie(props.movie.imdbID)"
 | 
			
		||||
          >
 | 
			
		||||
            Remove from list
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,19 +10,24 @@ const props = defineProps<{
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): void }>()
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <p v-if="props.movies.length === 0" class="text-center text-gray-500 py-12">
 | 
			
		||||
    <p
 | 
			
		||||
      v-if="props.movies.length === 0"
 | 
			
		||||
      class="text-center text-gray-500 py-12 bg-gray-50/60 rounded-lg border border-gray-200"
 | 
			
		||||
    >
 | 
			
		||||
      No movies found.<br />
 | 
			
		||||
      <RouterLink to="/add" class="text-black font-bold">Add a movie</RouterLink>
 | 
			
		||||
      <RouterLink to="/add" class="text-gray-700 font-semibold hover:text-gray-900 transition">
 | 
			
		||||
        Add a movie
 | 
			
		||||
      </RouterLink>
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <ul v-auto-animate
 | 
			
		||||
    <ul
 | 
			
		||||
      v-auto-animate
 | 
			
		||||
      v-else
 | 
			
		||||
      class="grid gap-6 grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
 | 
			
		||||
      class="grid gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 lg:gap-8"
 | 
			
		||||
    >
 | 
			
		||||
      <li v-for="movie in props.movies" :key="movie.imdbID" v-auto-animate>
 | 
			
		||||
        <MovieCard
 | 
			
		||||
@@ -33,19 +38,13 @@ const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): voi
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <div
 | 
			
		||||
      v-if="props.isSearch && props.movies.length > 0"
 | 
			
		||||
      class="flex justify-center mt-8"
 | 
			
		||||
    >
 | 
			
		||||
    <div v-if="props.isSearch && props.movies.length > 0" class="flex justify-center mt-8">
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn btn-outline btn-secondary"
 | 
			
		||||
        class="btn px-6 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 disabled:opacity-50"
 | 
			
		||||
        @click="emit('loadMore')"
 | 
			
		||||
        :disabled="props.loadingMore"
 | 
			
		||||
      >
 | 
			
		||||
        <span
 | 
			
		||||
          v-if="props.loadingMore"
 | 
			
		||||
          class="loading loading-spinner loading-xs mr-2"
 | 
			
		||||
        ></span>
 | 
			
		||||
        <span v-if="props.loadingMore" class="loading loading-spinner loading-xs mr-2"></span>
 | 
			
		||||
        Load more
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,22 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="navbar bg-base-100 shadow-sm sticky top-0 z-10">
 | 
			
		||||
  <div
 | 
			
		||||
    class="navbar sticky top-0 z-20
 | 
			
		||||
           bg-white/70 backdrop-blur-md border-b border-gray-200/60
 | 
			
		||||
           shadow-sm transition"
 | 
			
		||||
  >
 | 
			
		||||
    <!-- Left -->
 | 
			
		||||
    <div class="navbar-start">
 | 
			
		||||
      <!-- Mobile dropdown -->
 | 
			
		||||
      <div class="dropdown">
 | 
			
		||||
        <div tabindex="0" role="button" class="btn btn-ghost lg:hidden" aria-label="Menu">
 | 
			
		||||
        <div
 | 
			
		||||
          tabindex="0"
 | 
			
		||||
          role="button"
 | 
			
		||||
          class="btn btn-ghost lg:hidden"
 | 
			
		||||
          aria-label="Menu"
 | 
			
		||||
        >
 | 
			
		||||
          <svg
 | 
			
		||||
            xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
            class="h-5 w-5"
 | 
			
		||||
            class="h-6 w-6 text-gray-600"
 | 
			
		||||
            fill="none"
 | 
			
		||||
            viewBox="0 0 24 24"
 | 
			
		||||
            stroke="currentColor"
 | 
			
		||||
@@ -20,24 +31,44 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <ul
 | 
			
		||||
          tabindex="-1"
 | 
			
		||||
          class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow"
 | 
			
		||||
          class="menu menu-sm dropdown-content
 | 
			
		||||
                 mt-3 w-52 p-2 rounded-lg shadow-md
 | 
			
		||||
                 bg-white/80 backdrop-blur-md border border-gray-200/60"
 | 
			
		||||
        >
 | 
			
		||||
          <li><RouterLink class="text-xl" to="/">Home</RouterLink></li>
 | 
			
		||||
          <li><RouterLink class="text-xl" to="/list">List</RouterLink></li>
 | 
			
		||||
          <li><RouterLink class="text-xl" to="/add">Add</RouterLink></li>
 | 
			
		||||
          <li><RouterLink class="text-gray-700 hover:text-gray-900" to="/">Home</RouterLink></li>
 | 
			
		||||
          <li><RouterLink class="text-gray-700 hover:text-gray-900" to="/list">List</RouterLink></li>
 | 
			
		||||
          <li><RouterLink class="text-gray-700 hover:text-gray-900" to="/add">Add</RouterLink></li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
      <RouterLink class="btn btn-ghost text-xl" to="/">To Vue</RouterLink>
 | 
			
		||||
 | 
			
		||||
      <!-- Logo -->
 | 
			
		||||
      <RouterLink
 | 
			
		||||
        class="btn btn-ghost normal-case text-2xl font-bold
 | 
			
		||||
               bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
 | 
			
		||||
        to="/"
 | 
			
		||||
      >
 | 
			
		||||
        To Vue
 | 
			
		||||
      </RouterLink>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Center (desktop menu) -->
 | 
			
		||||
    <div class="navbar-center hidden lg:flex">
 | 
			
		||||
      <ul class="menu menu-horizontal px-1">
 | 
			
		||||
        <li><RouterLink class="text-xl" to="/">Home</RouterLink></li>
 | 
			
		||||
        <li><RouterLink class="text-xl" to="/list">List</RouterLink></li>
 | 
			
		||||
        <li><RouterLink class="text-xl" to="/add">Add</RouterLink></li>
 | 
			
		||||
      <ul class="menu menu-horizontal px-1 space-x-4">
 | 
			
		||||
        <li><RouterLink class="text-gray-700 hover:text-gray-900 transition" to="/">Home</RouterLink></li>
 | 
			
		||||
        <li><RouterLink class="text-gray-700 hover:text-gray-900 transition" to="/list">List</RouterLink></li>
 | 
			
		||||
        <li><RouterLink class="text-gray-700 hover:text-gray-900 transition" to="/add">Add</RouterLink></li>
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Right -->
 | 
			
		||||
    <div class="navbar-end">
 | 
			
		||||
      <RouterLink class="btn" to="/add">Get Started</RouterLink>
 | 
			
		||||
      <RouterLink
 | 
			
		||||
        class="btn px-5 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"
 | 
			
		||||
        to="/add"
 | 
			
		||||
      >
 | 
			
		||||
        Get Started
 | 
			
		||||
      </RouterLink>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ 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() {
 | 
			
		||||
@@ -13,30 +12,51 @@ function onSubmit() {
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <form @submit.prevent="onSubmit">
 | 
			
		||||
    <div class="join flex justify-center mb-8">
 | 
			
		||||
      <label class="input join-item">
 | 
			
		||||
        <svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
 | 
			
		||||
          <g
 | 
			
		||||
            stroke-linejoin="round"
 | 
			
		||||
            stroke-linecap="round"
 | 
			
		||||
            stroke-width="2.5"
 | 
			
		||||
            fill="none"
 | 
			
		||||
            stroke="currentColor"
 | 
			
		||||
          >
 | 
			
		||||
            <circle cx="11" cy="11" r="8"></circle>
 | 
			
		||||
            <path d="m21 21-4.3-4.3"></path>
 | 
			
		||||
          </g>
 | 
			
		||||
  <form @submit.prevent="onSubmit" class="flex justify-center mb-8">
 | 
			
		||||
    <div
 | 
			
		||||
      class="flex items-center w-full max-w-md
 | 
			
		||||
             bg-white/70 backdrop-blur-md border border-gray-200/60
 | 
			
		||||
             shadow-sm hover:shadow-md transition
 | 
			
		||||
             rounded-lg overflow-hidden"
 | 
			
		||||
    >
 | 
			
		||||
      <!-- Icon -->
 | 
			
		||||
      <span class="pl-3 text-gray-400">
 | 
			
		||||
        <svg
 | 
			
		||||
          class="h-5 w-5"
 | 
			
		||||
          xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
          viewBox="0 0 24 24"
 | 
			
		||||
          fill="none"
 | 
			
		||||
          stroke="currentColor"
 | 
			
		||||
          stroke-width="2"
 | 
			
		||||
          stroke-linecap="round"
 | 
			
		||||
          stroke-linejoin="round"
 | 
			
		||||
        >
 | 
			
		||||
          <circle cx="11" cy="11" r="8" />
 | 
			
		||||
          <line x1="21" y1="21" x2="16.65" y2="16.65" />
 | 
			
		||||
        </svg>
 | 
			
		||||
        <input
 | 
			
		||||
          type="search"
 | 
			
		||||
          placeholder="Search for a movie"
 | 
			
		||||
          required
 | 
			
		||||
          :value="props.modelValue"
 | 
			
		||||
          @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
 | 
			
		||||
        />
 | 
			
		||||
      </label>
 | 
			
		||||
      <!-- <button class="btn btn-neutral join-item">Search</button> -->
 | 
			
		||||
      </span>
 | 
			
		||||
 | 
			
		||||
      <!-- Input -->
 | 
			
		||||
      <input
 | 
			
		||||
        type="search"
 | 
			
		||||
        placeholder="Search for a movie"
 | 
			
		||||
        required
 | 
			
		||||
        :value="props.modelValue"
 | 
			
		||||
        @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
 | 
			
		||||
        class="flex-1 px-3 py-2 bg-transparent outline-none text-gray-700 placeholder-gray-400"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <!-- Optional button -->
 | 
			
		||||
      <!--
 | 
			
		||||
      <button
 | 
			
		||||
        type="submit"
 | 
			
		||||
        class="px-4 py-2 bg-gradient-to-r from-gray-100 to-gray-200
 | 
			
		||||
               border-l border-gray-200 text-gray-600 hover:text-gray-800
 | 
			
		||||
               transition"
 | 
			
		||||
      >
 | 
			
		||||
        Search
 | 
			
		||||
      </button>
 | 
			
		||||
      -->
 | 
			
		||||
    </div>
 | 
			
		||||
  </form>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -102,15 +102,24 @@ watch(searchQuery, () => {
 | 
			
		||||
</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>
 | 
			
		||||
  <div class="container mx-auto px-4 py-12" v-motion-fade-visible-once>
 | 
			
		||||
    <!-- Title -->
 | 
			
		||||
    <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"
 | 
			
		||||
    >
 | 
			
		||||
      Add a Movie
 | 
			
		||||
    </h1>
 | 
			
		||||
 | 
			
		||||
    <!-- Search bar -->
 | 
			
		||||
    <SearchBar v-model="searchQuery" @submit="searchMovie" />
 | 
			
		||||
 | 
			
		||||
    <div v-if="isSearching" class="flex justify-center my-12">
 | 
			
		||||
      <span class="loading loading-ring loading-lg"></span>
 | 
			
		||||
    <!-- Loading spinner -->
 | 
			
		||||
    <div v-if="isSearching" class="flex justify-center my-16">
 | 
			
		||||
      <span class="loading loading-ring loading-lg text-primary"></span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Movie list -->
 | 
			
		||||
    <MovieList
 | 
			
		||||
      v-else-if="movies && movies.length > 0"
 | 
			
		||||
      :movies="movies"
 | 
			
		||||
@@ -121,10 +130,19 @@ watch(searchQuery, () => {
 | 
			
		||||
      :is-search="true"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <p v-else class="text-center text-gray-500">Search for a movie to add it to your list</p>
 | 
			
		||||
    <!-- Empty state -->
 | 
			
		||||
    <p
 | 
			
		||||
      v-else
 | 
			
		||||
      class="text-center text-gray-500 mt-16
 | 
			
		||||
             bg-gray-50/60 border border-gray-200 rounded-lg py-12"
 | 
			
		||||
    >
 | 
			
		||||
      Search for a movie to add it to your list
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <div class="flex justify-center pt-2">
 | 
			
		||||
    <!-- Error alert -->
 | 
			
		||||
    <div class="flex justify-center pt-4">
 | 
			
		||||
      <ErrorAlert v-if="seachError" :message="seachError" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ onMounted(async () => {
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container mx-auto px-4 py-8">
 | 
			
		||||
    <MovieDetails :movie="movie" />
 | 
			
		||||
    <MovieDetails :movie="movie" v-motion-fade-visible-once />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,53 @@
 | 
			
		||||
<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
 | 
			
		||||
        >
 | 
			
		||||
  <div class="container mx-auto px-4 py-16" v-motion-fade-visible-once>
 | 
			
		||||
    <div class="flex flex-col items-center justify-center text-center">
 | 
			
		||||
      <h1 class="text-5xl font-bold mb-8 leading-tight">
 | 
			
		||||
        Welcome to <br />
 | 
			
		||||
        <span class="text-6xl font-extrabold 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 class="text-lg text-gray-600 max-w-2xl 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>
 | 
			
		||||
 | 
			
		||||
      <RouterLink
 | 
			
		||||
        to="/add"
 | 
			
		||||
        class="btn px-8 py-3 text-lg font-medium
 | 
			
		||||
               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"
 | 
			
		||||
      >
 | 
			
		||||
        Get Started
 | 
			
		||||
      </RouterLink>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.gradient-text {
 | 
			
		||||
  background: linear-gradient(to right, black 0%, red 50%, blue 100%);
 | 
			
		||||
  background: linear-gradient(
 | 
			
		||||
    90deg,
 | 
			
		||||
    #6b7280 0%,   /* gray-500 */
 | 
			
		||||
    #818cf8 50%,  /* indigo-400 */
 | 
			
		||||
    #22d3ee 100%  /* cyan-400 */
 | 
			
		||||
  );
 | 
			
		||||
  -webkit-background-clip: text;
 | 
			
		||||
  -webkit-text-fill-color: transparent;
 | 
			
		||||
  background-size: 200% auto;
 | 
			
		||||
  animation: gradient 3s ease infinite alternate;
 | 
			
		||||
  animation: gradientShift 6s ease infinite alternate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes gradient {
 | 
			
		||||
@keyframes gradientShift {
 | 
			
		||||
  0% {
 | 
			
		||||
    background-position: left center;
 | 
			
		||||
  }
 | 
			
		||||
  100% {
 | 
			
		||||
    background-position: right center;
 | 
			
		||||
  }
 | 
			
		||||
  50% {
 | 
			
		||||
    animation-play-state: paused;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ onMounted(() => {
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container mx-auto px-4 py-8">
 | 
			
		||||
  <div class="container mx-auto px-4 py-8" v-motion-fade-visible-once>
 | 
			
		||||
    <MovieList
 | 
			
		||||
      v-auto-animate
 | 
			
		||||
      v-if="store.movieList"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,28 @@
 | 
			
		||||
<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
 | 
			
		||||
    class="min-h-screen flex flex-col items-center justify-center bg-gradient-to-b from-gray-50 to-white px-4"
 | 
			
		||||
  >
 | 
			
		||||
    <!-- Big 404 -->
 | 
			
		||||
    <h1
 | 
			
		||||
      class="text-8xl font-extrabold mb-4 bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
 | 
			
		||||
    >
 | 
			
		||||
      404
 | 
			
		||||
    </h1>
 | 
			
		||||
 | 
			
		||||
    <!-- Message -->
 | 
			
		||||
    <p class="text-xl text-gray-600 mb-8 text-center">
 | 
			
		||||
      Oops! The page you’re looking for doesn’t exist.
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
    <!-- Button back home -->
 | 
			
		||||
    <RouterLink
 | 
			
		||||
      to="/"
 | 
			
		||||
      class="btn px-6 py-3 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"
 | 
			
		||||
    >
 | 
			
		||||
      Back to Home
 | 
			
		||||
    </RouterLink>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user