Compare commits

..

No commits in common. "main" and "usefetch-to-fetch" have entirely different histories.

21 changed files with 158 additions and 440 deletions

View file

@ -1,3 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="project" />
</component>

6
.idea/tailwindcss.xml generated
View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="TailwindSettings">
<option name="lspConfiguration" value="{&#10; &quot;includeLanguages&quot;: {&#10; &quot;ftl&quot;: &quot;html&quot;,&#10; &quot;jinja&quot;: &quot;html&quot;,&#10; &quot;jinja2&quot;: &quot;html&quot;,&#10; &quot;smarty&quot;: &quot;html&quot;,&#10; &quot;tmpl&quot;: &quot;gohtml&quot;,&#10; &quot;cshtml&quot;: &quot;html&quot;,&#10; &quot;vbhtml&quot;: &quot;html&quot;,&#10; &quot;razor&quot;: &quot;html&quot;,&#10; &quot;scss&quot;: &quot;scss&quot;,&#10; &quot;html&quot;: &quot;html&quot;,&#10; &quot;javascript&quot;: &quot;javascript&quot;,&#10; &quot;typescript&quot;: &quot;typescript&quot;,&#10; &quot;css&quot;: &quot;css&quot;,&#10; &quot;vue&quot;: &quot;vue&quot;,&#10; &quot;sass&quot;: &quot;sass&quot;,&#10; &quot;twig&quot;: &quot;twig&quot;&#10; },&#10; &quot;files&quot;: {&#10; &quot;exclude&quot;: [&#10; &quot;**/.git/**&quot;,&#10; &quot;**/node_modules/**&quot;,&#10; &quot;**/.hg/**&quot;,&#10; &quot;**/.svn/**&quot;&#10; ]&#10; },&#10; &quot;emmetCompletions&quot;: false,&#10; &quot;classAttributes&quot;: [&quot;class&quot;, &quot;className&quot;, &quot;ngClass&quot;],&#10; &quot;colorDecorators&quot;: true,&#10; &quot;showPixelEquivalents&quot;: true,&#10; &quot;rootFontSize&quot;: 16,&#10; &quot;hovers&quot;: true,&#10; &quot;suggestions&quot;: true,&#10; &quot;codeActions&quot;: true,&#10; &quot;validate&quot;: true,&#10; &quot;lint&quot;: {&#10; &quot;invalidScreen&quot;: &quot;error&quot;,&#10; &quot;invalidVariant&quot;: &quot;error&quot;,&#10; &quot;invalidTailwindDirective&quot;: &quot;error&quot;,&#10; &quot;invalidApply&quot;: &quot;error&quot;,&#10; &quot;invalidConfigPath&quot;: &quot;error&quot;,&#10; &quot;cssConflict&quot;: &quot;warning&quot;,&#10; &quot;recommendedVariantOrder&quot;: &quot;warning&quot;&#10; },&#10; &quot;experimental&quot;: {&#10; &quot;configFile&quot;: null,&#10; &quot;classRegex&quot;: []&#10; }&#10;}" />
</component>
</project>

1
.idea/web.iml generated
View file

@ -4,6 +4,5 @@
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="@types/tailwindcss" level="application" />
</component>
</module>

75
.idea/workspace.xml generated
View file

@ -6,14 +6,19 @@
<component name="ChangeListManager">
<list default="true" id="5e320804-68c9-4504-97d5-d421de3438b2" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/LoadingIcon.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/LoadingIcon.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/admin/lists.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/admin/lists.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/admin/showings.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/admin/showings.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/admin/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/admin/index.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/lists/[id].vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/lists/[id].vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/lists/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/lists/index.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/schedule/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/schedule/index.vue" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ComposerSettings" synchronizationState="SYNCHRONIZE">
<component name="ComposerSettings">
<execution />
</component>
<component name="FileTemplateManagerImpl">
@ -22,7 +27,6 @@
<option value="CSS File" />
<option value="Vue Single File Component" />
<option value="TypeScript File" />
<option value="Vue Composition API Component" />
</list>
</option>
</component>
@ -56,37 +60,31 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
&quot;code.cleanup.on.save&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/tiradoe/Projects/MovieNight/movie-night-web&quot;,
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.standard&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.standard&quot;: &quot;&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;npm.dev.executor&quot;: &quot;Run&quot;,
&quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;/home/tiradoe/Projects/MovieNight/movie-night-web/src/node_modules/prettier&quot;,
&quot;rearrange.code.on.save&quot;: &quot;true&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;configurable.group.editor&quot;,
&quot;ts.external.directory.path&quot;: &quot;/home/tiradoe/Projects/MovieNight/movie-night-web/src/node_modules/typescript/lib&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
&quot;keyToStringList&quot;: {
&quot;vue.recent.templates&quot;: [
&quot;Vue Composition API Component&quot;
]
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"WebServerToolWindowFactoryState": "false",
"code.cleanup.on.save": "true",
"git-widget-placeholder": "usefetch-to-fetch",
"last_opened_file_path": "/home/tiradoe/Projects/movie-night/web/src/types",
"list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.standard": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.standard": "",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"npm.dev.executor": "Run",
"prettierjs.PrettierConfiguration.Package": "/home/tiradoe/Projects/movie-night/web/src/node_modules/prettier",
"rearrange.code.on.save": "true",
"settings.editor.selected.configurable": "settings.vue",
"ts.external.directory.path": "/home/tiradoe/Projects/movie-night/web/src/node_modules/typescript/lib",
"vue.rearranger.settings.migration": "true"
}
}</component>
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/types" />
@ -114,8 +112,8 @@
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-b26f3e71634d-JavaScript-PS-251.26094.133" />
<option value="bundled-php-predefined-a98d8de5180a-c5828cf854d9-com.jetbrains.php.sharedIndexes-PS-251.26094.133" />
<option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-PS-243.26053.13" />
<option value="bundled-php-predefined-a98d8de5180a-1ec7b7818973-com.jetbrains.php.sharedIndexes-PS-243.26053.13" />
</set>
</attachedChunks>
</component>
@ -139,13 +137,6 @@
<workItem from="1743904898331" duration="20256000" />
<workItem from="1743998844137" duration="23268000" />
<workItem from="1744430699183" duration="32152000" />
<workItem from="1745179755243" duration="40533000" />
<workItem from="1745366380362" duration="223000" />
<workItem from="1745366610986" duration="319000" />
<workItem from="1745366940811" duration="4297000" />
<workItem from="1748541588253" duration="2587000" />
<workItem from="1750288132986" duration="18089000" />
<workItem from="1750873961750" duration="644000" />
</task>
<servers />
</component>

View file

@ -2,5 +2,3 @@ Movie Night Web
=
![cinema corona list page](screenshot.png "Screenshot")
Uses filmstrip icon by momentbloom (https://www.vecteezy.com/vector-art/36659391-filmstrip)

Binary file not shown.

Binary file not shown.

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="1413.5 192.80002 108.59998 149.10001"
width="108.59998"
height="149.10001"
version="1.1"
id="svg1"
sodipodi:docname="poster-placeholder.svg"
inkscape:export-filename="poster-placeholder2.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:export-bgcolor="#ffffffff">
<inkscape:page
x="0"
y="0"
width="108.59998"
height="149.10001"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<rect
style="fill:#5c5b79;fill-opacity:1"
id="rect1"
width="108.28388"
height="148.72725"
x="1413.6864"
y="193.17278" />
<path
fill="#cccccc"
d="m 1413.5,242.5 c 0,16.6 0,33.1 0,49.7 36.2,49.7 72.4,-49.7 108.6,0 0,-16.6 0,-33.1 0,-49.7 -36.2,-49.7 -72.4,49.7 -108.6,0 z m 8.7,56.7 c -1.6,-1.3 -3.3,-2.8 -4.9,-4.6 0,-1.6 0,-3.3 0,-4.9 1.6,1.8 3.3,3.3 4.9,4.6 0,1.7 0,3.3 0,4.9 z m 0,-39.8 c -1.6,-1.3 -3.3,-2.8 -4.9,-4.6 0,-1.6 0,-3.3 0,-4.9 1.6,1.8 3.3,3.3 4.9,4.6 0,1.6 0,3.3 0,4.9 z m 9.6,44.4 c -1.6,-0.4 -3.3,-0.9 -4.9,-1.7 0,-1.6 0,-3.3 0,-4.9 1.6,0.8 3.3,1.4 4.9,1.7 0,1.7 0,3.3 0,4.9 z m 0,-39.8 c -1.6,-0.4 -3.3,-0.9 -4.9,-1.7 0,-1.6 0,-3.3 0,-4.9 1.6,0.8 3.3,1.4 4.9,1.7 0,1.7 0,3.3 0,4.9 z m 9.6,39.8 c -1.6,0.3 -3.3,0.5 -4.9,0.5 0,-1.6 0,-3.3 0,-4.9 1.6,0 3.3,-0.2 4.9,-0.5 0,1.7 0,3.3 0,4.9 z m 0,-39.8 c -1.6,0.3 -3.3,0.5 -4.9,0.5 0,-1.6 0,-3.3 0,-4.9 1.6,0 3.3,-0.2 4.9,-0.5 0,1.6 0,3.3 0,4.9 z m 9.6,36.4 c -1.6,0.8 -3.3,1.5 -4.9,2.1 0,-1.6 0,-3.3 0,-4.9 1.6,-0.6 3.3,-1.3 4.9,-2.1 0,1.7 0,3.3 0,4.9 z m 0,-39.8 c -1.6,0.8 -3.3,1.5 -4.9,2.1 0,-1.6 0,-3.3 0,-4.9 1.6,-0.6 3.3,-1.3 4.9,-2.1 0,1.6 0,3.3 0,4.9 z m 9.5,34.3 c -1.6,1.1 -3.3,2.1 -4.9,3 0,-1.6 0,-3.3 0,-4.9 1.6,-1 3.3,-2 4.9,-3 0,1.6 0,3.2 0,4.9 z m 0,-39.8 c -1.6,1.1 -3.3,2.1 -4.9,3 0,-1.6 0,-3.3 0,-4.9 1.6,-1 3.3,-2 4.9,-3 0,1.6 0,3.2 0,4.9 z m 9.6,33.3 c -1.6,1.1 -3.3,2.3 -4.9,3.4 0,-1.6 0,-3.3 0,-4.9 1.6,-1.1 3.3,-2.2 4.9,-3.4 0,1.6 0,3.2 0,4.9 z m 0,-39.8 c -1.6,1.1 -3.3,2.3 -4.9,3.4 0,-1.6 0,-3.3 0,-4.9 1.6,-1.1 3.3,-2.2 4.9,-3.4 0,1.6 0,3.2 0,4.9 z m 9.6,33.6 c -1.6,1 -3.3,2 -4.9,3.1 0,-1.6 0,-3.3 0,-4.9 1.6,-1.1 3.3,-2.1 4.9,-3.1 0,1.6 0,3.3 0,4.9 z m 0,-39.8 c -1.6,1 -3.3,2 -4.9,3.1 0,-1.6 0,-3.3 0,-4.9 1.6,-1.1 3.3,-2.1 4.9,-3.1 0,1.6 0,3.2 0,4.9 z m 9.6,35.1 c -1.6,0.6 -3.3,1.3 -4.9,2.1 0,-1.6 0,-3.3 0,-4.9 1.6,-0.8 3.3,-1.5 4.9,-2.1 0,1.7 0,3.3 0,4.9 z m 0,-39.8 c -1.6,0.6 -3.3,1.3 -4.9,2.1 0,-1.6 0,-3.3 0,-4.9 1.6,-0.8 3.3,-1.5 4.9,-2.1 0,1.7 0,3.3 0,4.9 z m 9.6,37.9 c -1.6,0 -3.3,0.2 -4.9,0.6 0,-1.6 0,-3.3 0,-4.9 1.6,-0.3 3.3,-0.5 4.9,-0.6 0,1.7 0,3.3 0,4.9 z m 0,-39.8 c -1.6,0 -3.3,0.2 -4.9,0.6 0,-1.6 0,-3.3 0,-4.9 1.6,-0.3 3.3,-0.5 4.9,-0.6 0,1.6 0,3.3 0,4.9 z m 14.2,0 c 1.6,1.2 3.3,2.7 4.9,4.5 0,1.6 0,3.3 0,4.9 -1.6,-1.8 -3.3,-3.2 -4.9,-4.5 0,-1.6 0,-3.2 0,-4.9 z m 0,39.8 c 1.6,1.2 3.3,2.7 4.9,4.5 0,1.6 0,3.3 0,4.9 -1.6,-1.8 -3.3,-3.2 -4.9,-4.5 0,-1.6 0,-3.2 0,-4.9 z m -9.6,-44.2 c 1.6,0.3 3.3,0.9 4.9,1.6 0,1.6 0,3.3 0,4.9 -1.6,-0.8 -3.3,-1.3 -4.9,-1.6 0,-1.7 0,-3.3 0,-4.9 z m 0,39.8 c 1.6,0.3 3.3,0.9 4.9,1.6 0,1.6 0,3.3 0,4.9 -1.6,-0.8 -3.3,-1.3 -4.9,-1.6 0,-1.7 0,-3.3 0,-4.9 z"
id="path1" />
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,31 +0,0 @@
<template>
<div
class="flex absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex-col"
>
<video alt="Loading" autoplay class="max-h-52" loop muted playsinline>
<source
v-if="supportsHEVC"
src="/assets/img/movie-loader.mov"
type="video/quicktime"
/>
<source v-else src="/assets/img/movie-loader.webm" type="video/webm" />
</video>
</div>
</template>
<script lang="ts" setup>
const supportsHEVC = ref(false);
const props = defineProps(["showQuote"]);
onMounted(() => {
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
// iOS devices also support HEVC
const isIOS =
/iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream;
supportsHEVC.value = isSafari || isIOS;
});
</script>
<style scoped></style>

View file

@ -1,14 +1,12 @@
<template>
<div v-show="visible" id="movie-modal" class="movie-modal movie-card">
<div class="max-w-4xl mx-auto flex flex-col px-2 sm:px-0">
<div v-show="visible" id="movie-modal" class="movie-modal movie-card p-5">
<span
class="hover-pointer font-bold self-end pr-1 sm:pr-5 pt-5 pb-5 sm:pb-0"
class="hover-pointer font-bold w-full block text-right sm:pr-5 pb-3 pt-5"
@click="toggleModal()"
>
<span class="bg-red-600 p-2 rounded">X</span>
X
</span>
<slot></slot>
</div>
<slot class=""></slot>
</div>
</template>

View file

@ -1,36 +0,0 @@
<script lang="ts" setup>
import placeholderPoster from "assets/img/poster-placeholder.svg";
const imgRef = ref<HTMLImageElement | null>(null);
const props = defineProps(["image"]);
const handleImageError = function (event: Event) {
(event.target as HTMLImageElement).classList.remove("lazyload");
(event.target as HTMLImageElement).classList.add("object-cover");
(event.target as HTMLImageElement).src = placeholderPoster;
};
watch(
() => props.image,
(newImage) => {
if (imgRef.value && newImage) {
imgRef.value.classList.add("lazyload");
imgRef.value.setAttribute("data-src", newImage);
}
},
);
</script>
<template>
<div class="aspect-[2/3] w-full text-blue-300">
<img
ref="imgRef"
:data-src="props.image"
alt="Movie Details"
class="lazyload hover-pointer w-full h-full"
@error="handleImageError"
/>
</div>
</template>
<style scoped></style>

View file

@ -1,54 +0,0 @@
<template>
<blockquote
class="flex flex-col gap-10 movie-card p-10 neon-border text-center"
>
<span>
{{ quote.quote }}
</span>
<div class="flex gap-2 italic flex-col">
<span>{{ quote.actor }}</span>
<span>{{ quote.movie }}</span>
</div>
</blockquote>
</template>
<style scoped></style>
<script lang="ts" setup>
const quotes = [
{
actor: "Darren Ewing as Arnold",
movie: "Troll 2",
quote: "They're eating her... and then they're going to eat me! OH MY GOD!",
},
{
actor: "Tommy Wiseau as Johnny",
movie: "The Room",
quote: "You're tearing me apart, Lisa!",
},
{
actor: "Arnold Schwaarzenegger as T-800",
movie: "Terminator 2: Judgment Day",
quote: "Hasta la vista, baby.",
},
{
actor: "Karolyn Grimes as Zuzu",
movie: "It's a Wonderful Life",
quote: "Every time a bell rings, an angel gets his wings.",
},
{
actor: "Pat Morita as Mr. Miyagi",
movie: "The Karate Kid",
quote: "Wax on, Wax off",
},
{
actor: "Dolph Lundgren as Drago",
movie: "Rocky 4",
quote: "I must break you.",
},
];
const randomQuote = () => quotes[Math.floor(Math.random() * quotes.length)];
const quote = ref(randomQuote());
</script>

View file

@ -1,5 +1,4 @@
<template>
<LoadingIcon v-if="loading" class="p-1 bg-gray-900 rounded-3xl" />
<form class="py-3 p-sm-0 align-items-center" @submit="findMovies">
<label class="px-0" for="search-field">Search</label>
<div class="px-0 mx-0">
@ -9,7 +8,12 @@
</form>
<ul class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-4">
<li v-for="movie in movies" class="p-1 movie-card">
<MoviePoster :image="movie.poster" @click="showModal(movie)" />
<img
:src="movie.poster"
alt="movie poster"
class="neon-border hover-pointer"
@click="showModal(movie)"
/>
<div class="p-2">
<h5 class="text-center">{{ movie.title }} ({{ movie.year }})</h5>
</div>
@ -19,9 +23,6 @@
<script lang="ts" setup>
import type { Movie } from "~/types/movie";
import "lazysizes";
const loading = ref(false);
const emit = defineEmits<{
(e: "show-modal", movie: Movie): void;
@ -35,7 +36,6 @@ const showModal = (movie: Movie) => {
const findMovies = async function (e: Event) {
let config = useRuntimeConfig();
e.preventDefault();
loading.value = true;
let searchTerm = (document.getElementById("search-field") as HTMLInputElement)
?.value;
@ -43,27 +43,28 @@ const findMovies = async function (e: Event) {
return;
}
$fetch<Movie[]>(`${config.public.apiURL}/movies/search?q=${searchTerm}`, {
const { data, error } = await useFetch<Movie[]>(
`${config.public.apiURL}/movies/search?q=${searchTerm}`,
{
method: "GET",
headers: {
"Content-type": "application/json",
Authorization: `Token ${useCookie("token").value}`,
},
})
.then((data) => {
movies.value = data;
loading.value = false;
})
.catch((err) => {
if (err.statusCode === 401) {
navigateTo("/login");
} else if (err.statusCode === 404) {
alert("No movies found");
loading.value = false;
} else {
alert("An error occurred. Please try again later.");
},
);
if (error.value) {
if (error.value.statusCode === 401) {
alert("Unauthorized");
}
} else {
if (!data.value) {
alert("No movies found.");
} else {
movies.value = data.value || [];
}
}
});
};
</script>

View file

@ -1,18 +1,22 @@
<template>
<div v-if="props.movie != null" class="sm:m-5 p-10 movie-card neon-border">
<div>
<h2 id="modal-title" class="row pb-10 text-center sm:text-left">
<h2 id="modal-title" class="row pb-3">
{{ movie.title }} ({{ movie.year }})
</h2>
<div class="grid sm:grid-cols-2 gap-5">
<div class="grid sm:grid-cols-2">
<!-- MODAL POSTER -->
<MoviePoster
:image="movie.poster"
class="max-h-72 max-w-60 sm:max-h-2xl sm:max-w-72 mx-auto sm:mx-0"
<div class="text-end">
<img
id="modal-poster"
:src="movie.poster"
alt="poster"
class="pt-5"
/>
</div>
<div class="mx-auto sm:mx-none">
<div class="pt-5">
<label class="" for="list-picker">Add To List</label><br />
<select id="list-picker" v-model="list_id" class="p-1 text-black">
<option v-for="list in lists" :value="list.id">

View file

@ -1,32 +1,23 @@
<template>
<div class="sm:m-5 p-10 movie-card neon-border">
<div>
<LoadingIcon v-if="updating" />
<h2 class="text-xl pb-10 text-center sm:text-left">
<h2 class="text-xl pb-3 text-center sm:text-left">
{{ movie.title }} ({{ movie.year }})
</h2>
<div class="sm:inline-flex sm:space-x-5">
<MoviePoster
:image="movie.poster"
class="max-h-80 max-w-60 mx-auto sm:mx-none"
<img
:src="movie.poster"
alt="movie poster"
class="mx-auto sm:mx-0 neon-border"
/>
<div class="pt-5 sm:pt-0">
<p>{{ movie.plot }}</p>
<ScheduleMovie
v-if="logged_in"
:movie="movie"
class="mt-5"
@close-modal="$emit('close-modal')"
/>
<button
v-if="logged_in"
class="my-10 btn p-2 rounded"
@click="updateMovie"
>
Refresh movie
</button>
</div>
</div>
</div>
@ -36,14 +27,10 @@
<script lang="ts" setup>
import ScheduleMovie from "~/components/forms/ScheduleMovie.vue";
const props = defineProps(["movie", "updating"]);
const emits = defineEmits(["close-modal", "update-movie"]);
const props = defineProps(["movie"]);
const emits = defineEmits(["close-modal"]);
const logged_in = ref(false);
const updateMovie = function () {
emits("update-movie");
};
onMounted(() => {
const token = useCookie("token").value;
if (token) {

View file

@ -1,14 +1,16 @@
<template>
<div class="container mx-auto">
<Navbar />
<NuxtPage class="mb-32" />
<Navbar/>
<NuxtPage/>
</div>
</template>
<script>
export default {
name: "default",
};
}
</script>
<style scoped></style>
<style scoped>
</style>

View file

@ -24,7 +24,7 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
apiURL: process.env.API_URL || "http://localhost:8000/v1",
apiURL: process.env.API_URL || "http://localhost:8000/api",
},
},

View file

@ -1,20 +1,14 @@
<template>
<LoadingIcon v-if="loading" show-quote="true" />
<div v-else class="p-5 sm:p-0">
<div v-if="list_id !== 0" class="p-5 sm:p-0">
<Modal ref="movie_modal">
<ShowMovie
v-if="modal_movie"
:movie="modal_movie"
:updating="updating"
@close-modal="closeModal"
@update-movie="updateMovie(modal_movie)"
></ShowMovie>
</Modal>
<h2 class="text-xl font-bold pb-5">{{ list.name }}</h2>
<div
v-if="movies.length > 1 && !loading"
class="grid grid-cols-2 rounded movie-card neon-border p-5"
>
<div class="grid grid-cols-2 rounded movie-card neon-border p-5">
<div>
<ul class="flex flex-row">
<li>
@ -37,27 +31,23 @@
/>
</div>
<div v-if="movies.length < 1 && !loading" class="mt-10 flex gap-5 flex-col">
No Movies Found
<MovieQuote />
</div>
<!-- MOVIE LIST -->
<ul
v-else
class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2 mt-5"
>
<li
v-for="movie in filtered_movies"
:key="movie.poster"
class="rounded movie-card neon-border flex flex-col overflow-hidden"
:key="movie.id"
class="rounded movie-card neon-border"
>
<!-- POSTER -->
<MoviePoster
:image="movie.poster"
class="flex-shrink-0"
<img
:data-src="movie.poster"
alt="movie poster"
class="lazyload p-3 movie-poster hover-pointer mx-auto"
@click="showModal(movie)"
/>
<div class="p-5 flex flex-col justify-between flex-1">
<div class="p-5 flex flex-col">
<!-- TITLE -->
<span class="font-bold text-center mb-1">{{ movie.title }}</span>
<span
@ -79,13 +69,9 @@ import "lazysizes";
import type { MovieList } from "~/types/movielist";
import type { Movie } from "~/types/movie";
import Modal from "~/components/Modal.vue";
import { useCookie } from "#app";
import { $fetch } from "ofetch";
import MoviePoster from "~/components/MoviePoster.vue";
const list_id = ref(0);
const list = defineModel<MovieList>("movie_list", { default: [] });
const loading = ref(true);
const modal_movie: Ref<Movie | null> = ref(null);
const movies = defineModel<Movie[] | []>("movies", {
default: [],
@ -96,25 +82,18 @@ const logged_in = ref(false);
const hide_scheduled = ref(false);
const getList = async function (list_id: number) {
loading.value = true;
let config = useRuntimeConfig();
let headers: any = {
"Content-type": "application/json",
};
if (typeof useCookie("token").value !== "undefined") {
headers["Authorization"] = `Token ${useCookie("token").value}`;
}
$fetch<MovieList>(`${config.public.apiURL}/lists/${list_id}`, {
method: "GET",
headers: headers,
headers: {
"Content-type": "application/json",
Authorization: `Token ${useCookie("token").value}`,
},
})
.then((data) => {
list.value = data;
movies.value = data?.movies || [];
filtered_movies.value = movies.value;
loading.value = false;
})
.catch((err) => {
if (err.statusCode === 401) {
@ -128,9 +107,9 @@ const getList = async function (list_id: number) {
};
const hideScheduled = function () {
if (hide_scheduled.value && movies.value.length > 0) {
if (hide_scheduled && movies.value.length > 0) {
let filtered = movies.value.filter((movie) => {
return !movie.has_been_scheduled;
return movie.last_watched === null;
});
if (typeof filtered != "undefined") {
filtered_movies.value = filtered;
@ -179,38 +158,6 @@ const removeMovie = async function (movie_id: string) {
}
};
const updating = ref(false);
const updateMovie = async function (movie: Movie) {
let config = useRuntimeConfig();
updating.value = true;
$fetch<Movie>(`${config.public.apiURL}/movies/${movie.id}/`, {
method: "PUT",
headers: {
"Content-type": "application/json",
Authorization: `Token ${useCookie("token").value}`,
},
body: JSON.stringify(movie),
})
.then((data) => {
modal_movie.value = data || [];
movies.value = movies.value.map((movie) => {
return movie.id === data.id ? data : movie;
});
filtered_movies.value = movies.value;
updating.value = false;
})
.catch((err) => {
if (err.statusCode === 401) {
navigateTo("/");
}
if (err.statusCode === 404) {
alert("Unable to update movie");
}
updating.value = false;
});
};
const filterMovies = function () {
if (!movie_query) {
filtered_movies.value = movies.value;

View file

@ -1,16 +1,8 @@
<template>
<div>
<LoadingIcon
v-if="loading"
class="w-full p-5 sm:p-0 max-w-2xl"
show-quote="true"
/>
<div v-if="lists.length < 1 && !loading" class="flex flex-col gap-10">
<div class="p-5 sm:p-0">
<div v-if="lists.length < 1">
<p>No lists found</p>
<MovieQuote />
</div>
<ul class="grid grid-cols-2 gap-3 mt-5">
<li v-for="list in lists" class="movie-card neon-border p-5 rounded">
<div class="grid grid-rows-2 gap-3">
@ -29,10 +21,7 @@ import type { MovieList } from "~/types/movielist";
import { useCookie } from "#app";
const lists = defineModel<MovieList[]>("movie_list", { default: [] });
const loading = ref(true);
const updateLists = async function () {
loading.value = true;
let config = useRuntimeConfig();
let headers: any = {
"Content-type": "application/json",
@ -48,7 +37,6 @@ const updateLists = async function () {
})
.then((data) => {
lists.value = data || [];
loading.value = false;
})
.catch((err) => {
if (err.statusCode === 401) {

View file

@ -1,13 +1,7 @@
<template>
<div class="p-5 sm:p-0">
<LoadingIcon v-if="loading" show-quote="true" />
<div v-else>
<div
v-if="schedule && schedule?.showings.length < 1 && !loading"
class="p-5"
>
<div v-if="schedule && schedule?.showings.length < 1" class="p-5">
<span>No Showings Found</span>
<MovieQuote />
</div>
<ul class="flex flex-col gap-5">
<li
@ -35,19 +29,16 @@
</ul>
<!-- PREVIOUS SHOWINGS -->
<LoadingIcon v-if="loadingPrevious" />
<div v-else id="previous-showings" class="p-5 mt-5 list-group">
<div id="previous-showings" class="mt-5 list-group">
<span
class="block mb-5 hover-pointer underline"
@click="getSchedule(true)"
>
Previous Showings
</span>
<span id="loader" class="hidden">Loading...</span>
<ul class="flex flex-col gap-5">
<li
v-for="showing in past_showings"
class="p-5 movie-card neon-border"
>
<li v-for="showing in past_showings" class="p-5 movie-card neon-border">
<div class="sm:grid grid-cols-2 lg:grid-cols-3">
<img
:src="showing.movie.poster"
@ -65,7 +56,6 @@
</ul>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
@ -78,8 +68,6 @@ const schedule = defineModel<Schedule>("schedule");
const past_showings = defineModel<Showing[]>("past_showings", {
default: [],
});
const loading = ref(true);
const loadingPrevious = ref(false);
const got_previous = ref(false);
const months = [
"January",
@ -104,28 +92,23 @@ const formatDate = function (date_string: string) {
};
const getSchedule = async function (previous = false) {
if (previous) loadingPrevious.value = true;
else loading.value = true;
let config = useRuntimeConfig();
if (got_previous.value) {
return false;
}
document.getElementById("loader")?.classList.toggle("hidden");
let params = "";
if (previous) params = "?past_showings=true";
let headers: any = {
"Content-type": "application/json",
};
if (typeof useCookie("token").value !== "undefined") {
headers["Authorization"] = `Token ${useCookie("token").value}`;
}
await $fetch(`${config.public.apiURL}/schedules/1${params}`, {
method: "GET",
headers: headers,
headers: {
Accept: "application/json",
"Content-type": "application/json",
Authorization: `Token ${useCookie("token").value}`,
},
})
.then((data) => {
if (previous) {
@ -133,8 +116,7 @@ const getSchedule = async function (previous = false) {
} else {
schedule.value = data;
}
loading.value = false;
loadingPrevious.value = false;
document.getElementById("loader")?.classList.toggle("hidden");
})
.catch((err) => {
switch (err.statusCode) {
@ -144,7 +126,6 @@ const getSchedule = async function (previous = false) {
break;
case 404:
alert("Unable to find schedule");
navigateTo("/");
break;
}
});

View file

@ -10,5 +10,5 @@ export type Movie = {
actors: string;
plot: string;
poster: string;
has_been_scheduled: boolean;
last_watched: string;
};