initial commit

This commit is contained in:
Edward Tirado Jr 2026-02-16 19:12:00 -06:00
commit 869be69d67
42 changed files with 11444 additions and 0 deletions

36
app/app.vue Normal file
View file

@ -0,0 +1,36 @@
<script lang="ts" setup>
</script>
<template>
<div>
<NuxtRouteAnnouncer/>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</div>
</template>
<style>
body {
background-color: #f5f5f5;
}
/* sm */
@media (min-width: 640px) {
}
/* md */
@media (min-width: 768px) {
}
/* lg */
@media (min-width: 1024px) {
}
/* xl */
@media (min-width: 1280px) {
}
/* 2xl */
@media (min-width: 1536px) {
}
</style>

71
app/assets/css/reset.css Normal file
View file

@ -0,0 +1,71 @@
/* Source: https://www.joshwcomeau.com/css/custom-css-reset */
/* 1. Use a more-intuitive box-sizing model */
*, *::before, *::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
*:not(dialog) {
margin: 0;
}
/* 3. Enable keyword animations */
@media (prefers-reduced-motion: no-preference) {
html {
interpolate-size: allow-keywords;
}
}
body {
/* 4. Add accessible line-height */
line-height: 1.5;
/* 5. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 6. Improve media defaults */
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/* 7. Inherit fonts for form controls */
input, button, textarea, select {
font: inherit;
}
/* 8. Avoid text overflows */
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/* 9. Improve line wrapping */
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
/*
10. Create a root stacking context
*/
#root, #__next {
isolation: isolate;
}
/* Extra CSS reset*/
/* Remove default list padding */
ul, ol {
padding-left: 0;
list-style: none;
}
/* Remove default link styles */
a {
color: inherit;
text-decoration: none;
}

View file

@ -0,0 +1,4 @@
:root {
--color-primary: #000;
--color-surface: #fff;
}

View file

@ -0,0 +1,48 @@
<?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>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<circle cx="32" cy="32" r="32" fill="#ccc"/>
<circle cx="32" cy="24" r="10" fill="#fff"/>
<path d="M12 56c0-11 9-20 20-20s20 9 20 20" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

View file

@ -0,0 +1,11 @@
<script lang="ts" setup>
</script>
<template>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,18 @@
<script lang="ts" setup>
const props = defineProps<{
title: string
}>()
</script>
<template>
<h2>{{ props.title }}</h2>
</template>
<style scoped>
h1 {
font-size: 2rem;
font-weight: bold;
margin-bottom: 1rem;
}
</style>

View file

@ -0,0 +1,32 @@
<script lang="ts" setup>
</script>
<template>
<form>
<label for="list_name">Add List</label>
<div>
<input class="" name="list_name"
placeholder="List Name"
type="text">
<button>Add</button>
</div>
</form>
</template>
<style scoped>
button {
background-color: #4caf50;
color: white;
padding: .5rem 1rem;
border: none;
border-radius: 0 4px 4px 0;
}
input {
padding: .5rem;
border: 1px solid #ccc;
border-right: none;
border-radius: 4px 0 0 4px;
}
</style>

View file

@ -0,0 +1,25 @@
<script lang="ts" setup>
</script>
<template>
<form class="password-form">
<div class="form-group">
<label for="current-password">Current Password</label>
<input id="current-password" type="password"/>
</div>
<div class="form-group">
<label for="new-password">New Password</label>
<input id="new-password" type="password"/>
</div>
</form>
</template>
<style scoped>
.form-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
</style>

View file

@ -0,0 +1,36 @@
<script lang="ts" setup>
</script>
<template>
<form class="profile-form">
<div class="form-group">
<label for="profile-first-name">First Name</label>
<input id="profile-first-name" type="text"/>
</div>
<div class="form-group">
<label for="profile-last-name">Last Name</label>
<input id="profile-last-name" type="text"/>
</div>
<div class="form-group">
<label for="profile-location">Location</label>
<input id="profile-location" type="text"/>
</div>
<div class="form-group">
<label for="profile-pic">Profile Picture</label>
<input id="profile-pic" type="file"/>
</div>
</form>
</template>
<style scoped>
.form-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
</style>

72
app/components/header.vue Normal file
View file

@ -0,0 +1,72 @@
<script lang="ts" setup>
</script>
<template>
<nav class="header">
<span class="logo">
<NuxtLink to="/">
Movie Night
</NuxtLink>
</span>
<ul class="links">
<li>
<NuxtLink to="/lists">Lists</NuxtLink>
</li>
<!-- <li>
<NuxtLink to="/schedule">Schedule</NuxtLink>
</li>
-->
<li>
<ProfileMenu/>
</li>
</ul>
</nav>
</template>
<style scoped>
.header {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
font: bold 1.5rem sans-serif;
justify-content: center;
margin-bottom: 2rem;
}
.links {
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
list-style: none;
}
.links li {
cursor: pointer;
}
/* sm */
@media (min-width: 640px) {
.header {
justify-content: space-between;
flex-direction: row;
}
}
/* md */
@media (min-width: 768px) {
}
/* lg */
@media (min-width: 1024px) {
}
/* xl */
@media (min-width: 1280px) {
}
/* 2xl */
@media (min-width: 1536px) {
}
</style>

View file

@ -0,0 +1,136 @@
<script lang="ts" setup>
import type {List} from "~/types/list";
import {type ListSettings} from "~/types/list-settings";
const emit = defineEmits(['back-to-list'])
const props = defineProps<{
list: List
}>()
const listSettings: ListSettings = {
listName: 'My List',
isPublic: true,
collaborators: [
{id: 1, name: 'Ed', role: 3},
{id: 2, name: 'Bob', role: 2}
],
roles: [
{id: 1, name: 'Viewer'},
{id: 2, name: 'Editor'},
{id: 3, name: 'Admin'}
]
}
</script>
<template>
<div class="settings-header">
<div @click="$emit('back-to-list')">
<Icon name="solar:arrow-left-linear"/>
<span>Back to List</span>
</div>
</div>
<ul class="settings-list">
<li class="list-setting">
<label for="list-name-input">List Name</label>
<div>
<input id="list-name-input" :value="listSettings.listName" type="text"/>
<button>Save</button>
</div>
</li>
<li class="list-setting">
<div>
<label for="make-list-public">Make list public</label>
<input id="make-list-public" :checked="listSettings.isPublic" type="checkbox"/>
</div>
</li>
<li class="list-setting collaborator-list">
<span>Collaborators</span>
<details>
<summary>Permission levels</summary>
<ul>
<li>Viewer: Can view the list, but cannot make any changes.</li>
<li>Editor: Can add/remove movies from the list.</li>
<li>Admin: Can make any changes to the list including deleting it. Can also invite other users to collaborate
on
this list.
</li>
</ul>
</details>
<ul class="collaborators">
<li v-for="collaborator in listSettings.collaborators" :key="collaborator.id">
<span>{{ collaborator.name }}</span>
<select v-model="collaborator.role">
<option
v-for="role in listSettings.roles"
:value="role.id"
>
{{ role.name }}
</option>
</select>
</li>
</ul>
</li>
<li class="list-setting">
<label for="invite-collaborators-input">Invite Collaborators</label>
<textarea name="invite-collaborators-input" type="text"></textarea>
</li>
<li class="list-setting">
<label for="delete-list-button">Delete List</label>
<button name="delete-list-button">Delete</button>
</li>
</ul>
</template>
<style scoped>
.collaborator-list {
gap: 1rem;
}
.collaborators li {
display: flex;
justify-content: space-between;
}
.list-setting {
display: flex;
flex-direction: column;
gap: 1rem;
}
.settings-header {
display: flex;
justify-content: space-between;
padding: 1rem 0;
cursor: pointer;
}
.settings-header > div {
display: flex;
align-items: center;
}
.settings-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.settings-list > li {
display: flex;
border: gray 1px solid;
padding: 1rem;
}
details ul > li {
padding: 1rem;
border: black 1px solid;
}
details ul > li:not(:last-child) {
border-bottom: none;
}
</style>

View file

@ -0,0 +1,96 @@
<script lang="ts" setup>
import {type Movie} from "~/types/movie";
import posterPlaceholder from "~/assets/img/poster-placeholder.svg";
const props = defineProps<{
movies: Movie[]
}>()
const filteredMovies = ref<Movie[]>(props.movies);
const searchQuery = ref('');
const emit = defineEmits<{
'movie-clicked': [movie: Movie],
'add-movie': []
}>()
const movieSearch = () => {
filteredMovies.value = props.movies.filter(
movie => movie.title.toLowerCase().includes(searchQuery.value.toLowerCase())
);
}
</script>
<template>
<div>
<div class="list-controls-container">
<div class="list-controls">
<input v-model="searchQuery" placeholder="Search Movies" type="text" @keyup="movieSearch"/>
<Icon
class="list-controls-icon"
name="solar:filter-bold"
size="24"
title="Filter Movies"
/>
<Icon
class="list-controls-icon"
name="solar:sort-vertical-linear"
size="24"
title="Sort Movies"
/>
</div>
<button @click="emit('add-movie')">Add Movie</button>
</div>
<div v-if="filteredMovies.length === 0" class="movie-quote">
<span class="quote">"You complete me."</span>
<span class="attribution">Tom Cruise, Jerry Maguire</span>
</div>
<ul v-else class="movie-list">
<li v-for="movie in filteredMovies" :key="movie.id" class="movie" @click="emit('movie-clicked', movie)">
<img
alt=""
class="movie-poster"
src="http://fart.fart"
@error="(e) => (e.target as HTMLImageElement).src = posterPlaceholder"
/>
<span class="movie-title">{{ movie.title }}</span>
</li>
</ul>
</div>
</template>
<style scoped>
.list-controls-container {
display: flex;
justify-content: space-between;
gap: 1rem;
margin: 1rem 0;
}
.list-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
.movie-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(max(140px, 20%), 1fr));
gap: 1rem;
}
.movie {
display: flex;
flex-direction: column;
cursor: pointer;
}
.movie-quote {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
margin: 5rem 0;
}
</style>

View file

@ -0,0 +1,38 @@
<script lang="ts" setup>
import type {Movie} from "~/types/movie";
defineProps<{
selectedMovie: Movie;
}>();
</script>
<template>
<div class="movie-details">
<img :alt="selectedMovie!.title" :src="selectedMovie!.poster" class="movie-poster"/>
<h2 class="movie-title">{{ selectedMovie!.title }}</h2>
</div>
</template>
<style scoped>
.movie-details {
display: flex;
flex-direction: column;
padding: 2rem;
}
.movie-poster {
width: 100%;
}
@media (max-width: 767px) {
.movie-details {
align-items: center;
}
.movie-poster {
margin: 0 auto;
}
}
</style>

View file

@ -0,0 +1,79 @@
<script lang="ts" setup>
const dropdownOpen = ref(false)
const profileMenu = ref<HTMLElement | null>(null)
function toggleDropdown() {
dropdownOpen.value = !dropdownOpen.value
}
function onClickOutside(e: MouseEvent) {
if (profileMenu.value && !profileMenu.value.contains(e.target as Node)) {
dropdownOpen.value = false
}
}
onMounted(() => document.addEventListener('click', onClickOutside))
onUnmounted(() => document.removeEventListener('click', onClickOutside))
</script>
<template>
<div ref="profileMenu" class="profile-menu">
<button class="profile-pic" @click="toggleDropdown">
<img alt="Profile" src="~/assets/img/profile-placeholder.svg"/>
</button>
<ul v-if="dropdownOpen" class="dropdown">
<li>
<NuxtLink to="/account" @click="dropdownOpen = false">Account</NuxtLink>
</li>
<li>Log Out</li>
</ul>
</div>
</template>
<style scoped>
.profile-menu {
position: relative;
}
.profile-pic {
background: none;
border: none;
padding: 0;
cursor: pointer;
display: flex;
align-items: center;
}
.profile-pic img {
width: 2rem;
height: 2rem;
border-radius: 50%;
}
.dropdown {
position: absolute;
right: 0;
top: 100%;
margin-top: 0.5rem;
background: white;
border: 1px solid #ccc;
border-radius: 0.5rem;
list-style: none;
padding: 0.25rem 0;
min-width: 8rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 10;
}
.dropdown li {
padding: 0.5rem 1rem;
font-size: 1rem;
white-space: nowrap;
}
.dropdown li:hover {
background: #f0f0f0;
}
</style>

View file

@ -0,0 +1,85 @@
<script lang="ts" setup>
defineProps<{
open: boolean
}>()
const emit = defineEmits<{
close: []
}>()
</script>
<template>
<Transition name="slideout-backdrop">
<div v-if="open" class="backdrop" @click="emit('close')"/>
</Transition>
<Transition name="slideout">
<div v-if="open" class="panel">
<button class="close-button" @click="emit('close')">&times;</button>
<slot/>
</div>
</Transition>
</template>
<style scoped>
.backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 100;
}
.panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 400px;
background: var(--color-surface, #fff);
z-index: 101;
overflow-y: auto;
padding: 1rem;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.2);
}
.close-button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: inherit;
line-height: 1;
padding: 0.25rem 0.5rem;
}
@media (max-width: 767px) {
.panel {
width: 100%;
}
}
/* Backdrop transitions */
.slideout-backdrop-enter-active,
.slideout-backdrop-leave-active {
transition: opacity 0.3s ease;
}
.slideout-backdrop-enter-from,
.slideout-backdrop-leave-to {
opacity: 0;
}
/* Panel transitions */
.slideout-enter-active,
.slideout-leave-active {
transition: transform 0.3s ease;
}
.slideout-enter-from,
.slideout-leave-to {
transform: translateX(100%);
}
</style>

19
app/layouts/default.vue Normal file
View file

@ -0,0 +1,19 @@
<script lang="ts" setup>
</script>
<template>
<div class="container">
<Header/>
<slot/>
</div>
</template>
<style scoped>
.container {
margin: 0 auto;
padding: 1rem 5rem;
max-width: 1280px;
}
</style>

54
app/pages/account.vue Normal file
View file

@ -0,0 +1,54 @@
<script lang="ts" setup>
import PageTitle from "~/components/common/page-title.vue";
import PasswordResetForm from "~/components/forms/password-reset-form.vue";
import ProfileForm from "~/components/forms/profile-form.vue";
</script>
<template>
<PageTitle title="Account Settings"/>
<div class="password-settings settings-section">
<h2>Reset Password</h2>
<PasswordResetForm/>
</div>
<div class="profile-settings settings-section">
<div class="profile-header">
<h2>Profile</h2>
<span class="public-profile-link">View Public Profile</span>
</div>
<ProfileForm/>
</div>
</template>
<style scoped>
.profile-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.public-profile-link {
color: #007bff;
text-decoration: underline;
cursor: pointer;
}
form {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 1rem 0;
}
.settings-section {
margin: 2rem 0;
}
</style>

39
app/pages/index.vue Normal file
View file

@ -0,0 +1,39 @@
<script lang="ts" setup>
import PageTitle from "~/components/common/page-title.vue";
const user = "Eddie";
const welcomeMessage = computed(() => `Welcome, ${user}!`)
</script>
<template>
<PageTitle :title="welcomeMessage"/>
<div class="content">
<div>
<h2>Next up</h2>
<span>Nothing Scheduled :(</span>
</div>
<div>
<h2>Activity Feed</h2>
<ul>
<li>Ed created a list: "Best of 2023"</li>
<li>Ed added a movie to "Best of 2023": "The Dark Knight"</li>
<li>Ed added a movie to "Best of 2023": "The Dark Knight Rises"</li>
</ul>
</div>
</div>
</template>
<style scoped>
.page-title {
margin: 1rem 0;
}
.content {
display: flex;
flex-direction: column;
gap: 2rem;
}
</style>

70
app/pages/lists/[id].vue Normal file
View file

@ -0,0 +1,70 @@
<script lang="ts" setup>
import PageTitle from "~/components/common/page-title.vue";
import {type Movie} from "~/types/movie";
import {type List} from "~/types/list";
import MovieDetails from "~/components/panels/movie-details.vue";
const settingsActive = ref(false);
const movieSearchActive = ref(false);
const toggleSettings = () => settingsActive.value = !settingsActive.value
const toggleMovieSearch = () => movieSearchActive.value = !movieSearchActive.value
const selectedMovie = ref<Movie | null>(null);
const list: List = {
id: 1,
name: 'List Name',
isPublic: true,
listSettings: {
listName: 'List Name',
isPublic: true,
collaborators: [],
roles: []
}
};
const movies: Movie[] = []
</script>
<template>
<div class="page-header">
<PageTitle title="List Name"/>
<Icon class="settings-icon" name="solar:settings-bold" @click="toggleSettings"/>
</div>
<ListSettings
v-if="settingsActive"
:list="list"
v-on:back-to-list="toggleSettings"
/>
<MovieList
v-else
:movies="movies"
@movie-clicked="selectedMovie = $event"
@add-movie="toggleMovieSearch"
/>
<SlideoutPanel :open="!!selectedMovie" @close="selectedMovie = null">
<MovieDetails v-if="selectedMovie" :selectedMovie="selectedMovie"/>
</SlideoutPanel>
<SlideoutPanel :open="movieSearchActive" class="movie-search-panel"
@close="movieSearchActive = false">
<p>Movie Search</p>
</SlideoutPanel>
</template>
<style scoped>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.settings-icon {
cursor: pointer;
font-size: 1.5rem;
}
</style>

41
app/pages/lists/index.vue Normal file
View file

@ -0,0 +1,41 @@
<script lang="ts" setup>
import PageTitle from "~/components/common/page-title.vue";
import CreateListForm from "~/components/forms/create-list-form.vue";
</script>
<template>
<PageTitle title="Lists"/>
<div class="content">
<CreateListForm/>
<div class="w-full flex flex-col gap-5">
<h2 class="text-2xl font-bold">Your Lists</h2>
<ul class="w-full flex flex-col gap-3">
<li class="flex justify-between items-center p-4 bg-gray-700/50 rounded-lg hover:bg-gray-600/50 transition-colors">
<NuxtLink to="lists/1">List 1</NuxtLink>
</li>
</ul>
</div>
<div class="w-full flex flex-col gap-5">
<h2 class="text-2xl font-bold">Shared With You</h2>
<ul class="w-full flex flex-col gap-3">
<li class="flex justify-between items-center p-4 bg-gray-700/50 rounded-lg hover:bg-gray-600/50 transition-colors">
<NuxtLink to="lists/2">Bob's List</NuxtLink>
</li>
</ul>
</div>
</div>
</template>
<style scoped>
.content {
display: flex;
flex-direction: column;
gap: 2rem;
}
</style>

View file

@ -0,0 +1,6 @@
export type Collaborator = {
id: number,
name: string
role: number
}

View file

@ -0,0 +1,9 @@
import type {Collaborator} from "~/types/collaborator";
import type {Role} from "~/types/role";
export type ListSettings = {
listName: string,
isPublic: boolean,
collaborators: Collaborator[],
roles: Role[]
}

9
app/types/list.ts Normal file
View file

@ -0,0 +1,9 @@
import {type ListSettings} from "~/types/list-settings";
export type List = {
id: number,
name: string
isPublic: boolean
listSettings: ListSettings
}

14
app/types/movie.ts Normal file
View file

@ -0,0 +1,14 @@
export type Movie = {
id: number,
title: string
imdb_id: string
director: string
actors: string
plot: string
genre: string
mpaa_rating: string
critic_scores: string
poster: string
added_by: number
}

5
app/types/role.ts Normal file
View file

@ -0,0 +1,5 @@
export type Role = {
id: number,
name: string
}