mirror of
https://github.com/mmahdium/TBW.git
synced 2026-08-14 16:22:48 +03:30
UI improvements
This commit is contained in:
@@ -19,12 +19,21 @@ const alreadyAdded = computed(() =>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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 } }">
|
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
|
||||||
<figure v-motion-fade-visible-once
|
<figure
|
||||||
class="overflow-hidden flex items-center justify-center bg-gray-100 aspect-[2/3] rounded-t-sm"
|
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">
|
<div v-else-if="imageLoadFailed" class="flex items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
@@ -33,7 +42,7 @@ const alreadyAdded = computed(() =>
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
class="w-12 h-12 text-gray-400"
|
class="w-12 h-12 text-gray-300"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
@@ -47,7 +56,7 @@ const alreadyAdded = computed(() =>
|
|||||||
v-show="!props.loading && !imageLoadFailed"
|
v-show="!props.loading && !imageLoadFailed"
|
||||||
:src="props.movie.Poster"
|
:src="props.movie.Poster"
|
||||||
:alt="props.movie.Title"
|
: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)"
|
@load="emit('loaded', props.movie.imdbID)"
|
||||||
@error="
|
@error="
|
||||||
() => {
|
() => {
|
||||||
@@ -59,21 +68,40 @@ const alreadyAdded = computed(() =>
|
|||||||
</figure>
|
</figure>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
<div class="card-body p-4 flex flex-col">
|
<div class="card-body p-4 flex flex-col">
|
||||||
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
|
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
|
||||||
<h2 class="card-title text-lg">{{ props.movie.Title }}</h2>
|
<h2
|
||||||
<p class="text-sm text-gray-500">{{ props.movie.Year }}</p>
|
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>
|
</router-link>
|
||||||
|
|
||||||
<div class="card-actions justify-end mt-auto">
|
<div class="card-actions justify-end mt-auto">
|
||||||
<button
|
<button
|
||||||
v-if="!alreadyAdded"
|
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)"
|
@click="store.addMovie(props.movie)"
|
||||||
>
|
>
|
||||||
Add
|
Add
|
||||||
</button>
|
</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
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useMoviesStore } from '@/stores/movies'
|
|||||||
const props = defineProps<{ movie: MovieDetailsType | undefined }>()
|
const props = defineProps<{ movie: MovieDetailsType | undefined }>()
|
||||||
|
|
||||||
const imageLoaded = ref(false)
|
const imageLoaded = ref(false)
|
||||||
|
|
||||||
const store = useMoviesStore()
|
const store = useMoviesStore()
|
||||||
|
|
||||||
const alreadyAdded = computed(() =>
|
const alreadyAdded = computed(() =>
|
||||||
@@ -16,57 +15,87 @@ const alreadyAdded = computed(() =>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<div
|
||||||
<!-- Loading state for hero content -->
|
v-else
|
||||||
<div v-if="props.movie === undefined" class="hero-content">
|
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>
|
<span class="loading loading-ring loading-lg text-primary"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actual hero content -->
|
<!-- Hero content -->
|
||||||
<div v-else class="hero bg-base-200 min-h-screen rounded-md" v-motion-fade-visible-once>
|
<div
|
||||||
<div class="hero-content flex-col lg:flex-row gap-12">
|
v-else
|
||||||
<!-- Poster -->
|
class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full
|
||||||
<figure class="flex-shrink-0">
|
bg-white/70 backdrop-blur-md border border-gray-200/60
|
||||||
<span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
|
shadow-md rounded-xl p-8 transition"
|
||||||
<img
|
v-motion-fade-visible-once
|
||||||
v-show="imageLoaded"
|
>
|
||||||
:src="props.movie.Poster"
|
<!-- Poster -->
|
||||||
alt="Poster"
|
<figure class="flex-shrink-0">
|
||||||
class="w-full max-w-lg rounded-lg shadow-2xl"
|
<span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
|
||||||
@load="imageLoaded = true"
|
<img
|
||||||
/>
|
v-show="imageLoaded"
|
||||||
</figure>
|
: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 -->
|
<!-- Text -->
|
||||||
<div class="max-w-2xl">
|
<div class="flex-1">
|
||||||
<h1 class="text-5xl font-bold">
|
<h1 class="text-4xl font-bold text-gray-800 mb-4">
|
||||||
{{ props.movie.Title }}
|
{{ props.movie.Title }}
|
||||||
<span class="text-gray-500 text-sm">({{ props.movie.Year }})</span>
|
<span class="text-gray-400 text-lg font-normal">({{ props.movie.Year }})</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="py-6">{{ props.movie.Plot }}</p>
|
|
||||||
<div class="flex flex-wrap gap-2">
|
<p class="text-gray-600 leading-relaxed mb-6">{{ props.movie.Plot }}</p>
|
||||||
<span class="badge badge-dash badge-primary">
|
|
||||||
Language: {{ props.movie.Language }}
|
<!-- Badges -->
|
||||||
</span>
|
<div class="flex flex-wrap gap-2 mb-6">
|
||||||
<span class="badge badge-dash badge-secondary">
|
<span
|
||||||
Country: {{ props.movie.Country }}
|
class="px-3 py-1 rounded-md text-sm
|
||||||
</span>
|
bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||||
<span class="badge badge-dash badge-info">
|
>
|
||||||
IMDB Rating: {{ props.movie.imdbRating }}
|
Language: {{ props.movie.Language }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
<span
|
||||||
<div v-if="!alreadyAdded" class="card-actions mt-4">
|
class="px-3 py-1 rounded-md text-sm
|
||||||
<button class="btn -sm btn-primary" @click="store.addMovie(props.movie)">
|
bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||||
Add to list
|
>
|
||||||
</button>
|
Country: {{ props.movie.Country }}
|
||||||
</div>
|
</span>
|
||||||
<div v-else class="card-actions mt-4">
|
<span
|
||||||
<button class="btn btn-error" @click="store.removeMovie(props.movie.imdbID)">
|
class="px-3 py-1 rounded-md text-sm
|
||||||
Remove from list
|
bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||||
</button>
|
>
|
||||||
</div>
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,19 +10,24 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): void }>()
|
const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): void }>()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<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 />
|
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>
|
</p>
|
||||||
|
|
||||||
<ul v-auto-animate
|
<ul
|
||||||
|
v-auto-animate
|
||||||
v-else
|
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>
|
<li v-for="movie in props.movies" :key="movie.imdbID" v-auto-animate>
|
||||||
<MovieCard
|
<MovieCard
|
||||||
@@ -33,19 +38,13 @@ const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): voi
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div
|
<div v-if="props.isSearch && props.movies.length > 0" class="flex justify-center mt-8">
|
||||||
v-if="props.isSearch && props.movies.length > 0"
|
|
||||||
class="flex justify-center mt-8"
|
|
||||||
>
|
|
||||||
<button
|
<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')"
|
@click="emit('loadMore')"
|
||||||
:disabled="props.loadingMore"
|
:disabled="props.loadingMore"
|
||||||
>
|
>
|
||||||
<span
|
<span v-if="props.loadingMore" class="loading loading-spinner loading-xs mr-2"></span>
|
||||||
v-if="props.loadingMore"
|
|
||||||
class="loading loading-spinner loading-xs mr-2"
|
|
||||||
></span>
|
|
||||||
Load more
|
Load more
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+44
-13
@@ -1,11 +1,22 @@
|
|||||||
<template>
|
<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">
|
<div class="navbar-start">
|
||||||
|
<!-- Mobile dropdown -->
|
||||||
<div class="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
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-5 w-5"
|
class="h-6 w-6 text-gray-600"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -20,24 +31,44 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
tabindex="-1"
|
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-gray-700 hover:text-gray-900" to="/">Home</RouterLink></li>
|
||||||
<li><RouterLink class="text-xl" to="/list">List</RouterLink></li>
|
<li><RouterLink class="text-gray-700 hover:text-gray-900" 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="/add">Add</RouterLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
|
<!-- Center (desktop menu) -->
|
||||||
<div class="navbar-center hidden lg:flex">
|
<div class="navbar-center hidden lg:flex">
|
||||||
<ul class="menu menu-horizontal px-1">
|
<ul class="menu menu-horizontal px-1 space-x-4">
|
||||||
<li><RouterLink class="text-xl" to="/">Home</RouterLink></li>
|
<li><RouterLink class="text-gray-700 hover:text-gray-900 transition" to="/">Home</RouterLink></li>
|
||||||
<li><RouterLink class="text-xl" to="/list">List</RouterLink></li>
|
<li><RouterLink class="text-gray-700 hover:text-gray-900 transition" 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 transition" to="/add">Add</RouterLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Right -->
|
||||||
<div class="navbar-end">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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() {
|
||||||
@@ -13,30 +12,51 @@ function onSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="onSubmit">
|
<form @submit.prevent="onSubmit" class="flex justify-center mb-8">
|
||||||
<div class="join flex justify-center mb-8">
|
<div
|
||||||
<label class="input join-item">
|
class="flex items-center w-full max-w-md
|
||||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
bg-white/70 backdrop-blur-md border border-gray-200/60
|
||||||
<g
|
shadow-sm hover:shadow-md transition
|
||||||
stroke-linejoin="round"
|
rounded-lg overflow-hidden"
|
||||||
stroke-linecap="round"
|
>
|
||||||
stroke-width="2.5"
|
<!-- Icon -->
|
||||||
fill="none"
|
<span class="pl-3 text-gray-400">
|
||||||
stroke="currentColor"
|
<svg
|
||||||
>
|
class="h-5 w-5"
|
||||||
<circle cx="11" cy="11" r="8"></circle>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path d="m21 21-4.3-4.3"></path>
|
viewBox="0 0 24 24"
|
||||||
</g>
|
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>
|
</svg>
|
||||||
<input
|
</span>
|
||||||
type="search"
|
|
||||||
placeholder="Search for a movie"
|
<!-- Input -->
|
||||||
required
|
<input
|
||||||
:value="props.modelValue"
|
type="search"
|
||||||
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
placeholder="Search for a movie"
|
||||||
/>
|
required
|
||||||
</label>
|
:value="props.modelValue"
|
||||||
<!-- <button class="btn btn-neutral join-item">Search</button> -->
|
@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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+24
-6
@@ -102,15 +102,24 @@ watch(searchQuery, () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-12" v-motion-fade-visible-once>
|
||||||
<h1 class="text-3xl font-bold text-center mb-8">Add a Movie</h1>
|
<!-- 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" />
|
<SearchBar v-model="searchQuery" @submit="searchMovie" />
|
||||||
|
|
||||||
<div v-if="isSearching" class="flex justify-center my-12">
|
<!-- Loading spinner -->
|
||||||
<span class="loading loading-ring loading-lg"></span>
|
<div v-if="isSearching" class="flex justify-center my-16">
|
||||||
|
<span class="loading loading-ring loading-lg text-primary"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Movie list -->
|
||||||
<MovieList
|
<MovieList
|
||||||
v-else-if="movies && movies.length > 0"
|
v-else-if="movies && movies.length > 0"
|
||||||
:movies="movies"
|
:movies="movies"
|
||||||
@@ -121,10 +130,19 @@ watch(searchQuery, () => {
|
|||||||
:is-search="true"
|
: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" />
|
<ErrorAlert v-if="seachError" :message="seachError" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<MovieDetails :movie="movie" />
|
<MovieDetails :movie="movie" v-motion-fade-visible-once />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
+28
-15
@@ -1,40 +1,53 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-16" v-motion-fade-visible-once>
|
||||||
<div class="flex flex-col items-center justify-center">
|
<div class="flex flex-col items-center justify-center text-center">
|
||||||
<h1 class="text-5xl font-bold text-center mb-12">
|
<h1 class="text-5xl font-bold mb-8 leading-tight">
|
||||||
Welcome to <br /><span class="text-6xl font-bold text-center gradient-text"
|
Welcome to <br />
|
||||||
>To Be Watched</span
|
<span class="text-6xl font-extrabold gradient-text">
|
||||||
>
|
To Be Watched
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="text-xl text-center mb-12">
|
<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).
|
A simple and beautiful movie list app built with Vue 3 and Tailwind CSS (daisyUI).
|
||||||
</p>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.gradient-text {
|
.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-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-size: 200% auto;
|
background-size: 200% auto;
|
||||||
animation: gradient 3s ease infinite alternate;
|
animation: gradientShift 6s ease infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes gradient {
|
@keyframes gradientShift {
|
||||||
0% {
|
0% {
|
||||||
background-position: left center;
|
background-position: left center;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background-position: right center;
|
background-position: right center;
|
||||||
}
|
}
|
||||||
50% {
|
|
||||||
animation-play-state: paused;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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
|
<MovieList
|
||||||
v-auto-animate
|
v-auto-animate
|
||||||
v-if="store.movieList"
|
v-if="store.movieList"
|
||||||
|
|||||||
@@ -1,10 +1,28 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div
|
||||||
<h1 class="text-center">Not found</h1>
|
class="min-h-screen flex flex-col items-center justify-center bg-gradient-to-b from-gray-50 to-white px-4"
|
||||||
<router-link to="/">Home</router-link>
|
>
|
||||||
<div class="container mx-auto px-4 py-8"></div>
|
<!-- 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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user