Skip to content
Snippets Groups Projects
Commit 41b720fc authored by Aiyar, Tejas (UG - Comp Sci & Elec Eng)'s avatar Aiyar, Tejas (UG - Comp Sci & Elec Eng)
Browse files

Merge branch 'test' into 'main'

Push to Main

See merge request !5
parents 3394492a 319dd2fb
No related branches found
No related tags found
1 merge request!5Push to Main
Showing
with 3275 additions and 0 deletions
.DS_Store 0 → 100644
File added
VITE_TMDB_API_KEY=42259df77843511296d8096fa29e08a8
VITE_TMDB_BASE=https://api.themoviedb.org/3
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"recommendations": ["Vue.volar"]
}
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="./src/assets/Dark_Mode.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SceneIt</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
This diff is collapsed.
{
"name": "movie-group-8",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.2.0",
"@tailwindcss/vite": "^4.0.14",
"axios": "^1.9.0",
"firebase": "^11.4.0",
"movie-group-8": "file:",
"tailwindcss": "^4.0.14",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.2.0"
}
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
<script setup>
import { RouterLink, RouterView } from 'vue-router';
import Navbar from './components/Navbar.vue';
</script>
<template>
<Navbar />
<div class="z-10 bg-neutral-900">
<RouterView />
</div>
</template>
<style scoped>
</style>
movie-group-8/src/assets/Dark_Mode.png

88.5 KiB

movie-group-8/src/assets/Light_Mode.png

82.3 KiB

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 190.24 81.52"><defs><style>.cls-1{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" y1="40.76" x2="190.24" y2="40.76" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#90cea1"/><stop offset="0.56" stop-color="#3cbec9"/><stop offset="1" stop-color="#00b3e5"/></linearGradient></defs><title>Asset 2</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M105.67,36.06h66.9A17.67,17.67,0,0,0,190.24,18.4h0A17.67,17.67,0,0,0,172.57.73h-66.9A17.67,17.67,0,0,0,88,18.4h0A17.67,17.67,0,0,0,105.67,36.06Zm-88,45h76.9A17.67,17.67,0,0,0,112.24,63.4h0A17.67,17.67,0,0,0,94.57,45.73H17.67A17.67,17.67,0,0,0,0,63.4H0A17.67,17.67,0,0,0,17.67,81.06ZM10.41,35.42h7.8V6.92h10.1V0H.31v6.9h10.1Zm28.1,0h7.8V8.25h.1l9,27.15h6l9.3-27.15h.1V35.4h7.8V0H66.76l-8.2,23.1h-.1L50.31,0H38.51ZM152.43,55.67a15.07,15.07,0,0,0-4.52-5.52,18.57,18.57,0,0,0-6.68-3.08,33.54,33.54,0,0,0-8.07-1h-11.7v35.4h12.75a24.58,24.58,0,0,0,7.55-1.15A19.34,19.34,0,0,0,148.11,77a16.27,16.27,0,0,0,4.37-5.5,16.91,16.91,0,0,0,1.63-7.58A18.5,18.5,0,0,0,152.43,55.67ZM145,68.6A8.8,8.8,0,0,1,142.36,72a10.7,10.7,0,0,1-4,1.82,21.57,21.57,0,0,1-5,.55h-4.05v-21h4.6a17,17,0,0,1,4.67.63,11.66,11.66,0,0,1,3.88,1.87A9.14,9.14,0,0,1,145,59a9.87,9.87,0,0,1,1,4.52A11.89,11.89,0,0,1,145,68.6Zm44.63-.13a8,8,0,0,0-1.58-2.62A8.38,8.38,0,0,0,185.63,64a10.31,10.31,0,0,0-3.17-1v-.1a9.22,9.22,0,0,0,4.42-2.82,7.43,7.43,0,0,0,1.68-5,8.42,8.42,0,0,0-1.15-4.65,8.09,8.09,0,0,0-3-2.72,12.56,12.56,0,0,0-4.18-1.3,32.84,32.84,0,0,0-4.62-.33h-13.2v35.4h14.5a22.41,22.41,0,0,0,4.72-.5,13.53,13.53,0,0,0,4.28-1.65,9.42,9.42,0,0,0,3.1-3,8.52,8.52,0,0,0,1.2-4.68A9.39,9.39,0,0,0,189.66,68.47ZM170.21,52.72h5.3a10,10,0,0,1,1.85.18,6.18,6.18,0,0,1,1.7.57,3.39,3.39,0,0,1,1.22,1.13,3.22,3.22,0,0,1,.48,1.82,3.63,3.63,0,0,1-.43,1.8,3.4,3.4,0,0,1-1.12,1.2,4.92,4.92,0,0,1-1.58.65,7.51,7.51,0,0,1-1.77.2h-5.65Zm11.72,20a3.9,3.9,0,0,1-1.22,1.3,4.64,4.64,0,0,1-1.68.7,8.18,8.18,0,0,1-1.82.2h-7v-8h5.9a15.35,15.35,0,0,1,2,.15,8.47,8.47,0,0,1,2.05.55,4,4,0,0,1,1.57,1.18,3.11,3.11,0,0,1,.63,2A3.71,3.71,0,0,1,181.93,72.72Z"/></g></g></svg>
\ No newline at end of file
movie-group-8/src/assets/default_avatar.png

38.2 KiB

movie-group-8/src/assets/home_poster.png

1.85 MiB

movie-group-8/src/assets/review_poster.png

2.35 MiB

movie-group-8/src/assets/tmdb_poster.png

1.31 MiB

<template>
<div class="bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg p-4 flex gap-4 items-start">
<!-- Clickable poster -->
<router-link
:to="`/films/${movie.id}`"
class="shrink-0 hover:opacity-90 transition"
>
<img
v-if="movie.poster_path"
:src="'https://image.tmdb.org/t/p/w154' + movie.poster_path"
alt="Poster"
class="w-24 h-36 object-cover rounded"
/>
</router-link>
<div class="flex-1">
<!-- Clickable title -->
<router-link
:to="`/films/${movie.id}`"
class="text-lg font-semibold text-gray-800 dark:text-white hover:underline block"
>
{{ movie.title }}
</router-link>
<p class="text-sm text-gray-600 dark:text-neutral-400">{{ movie.vote_average }}</p>
<!-- Interactive status dropdown -->
<label class="text-xs text-gray-500 dark:text-gray-400 block mt-2">
Status:
<select
v-model="selectedStatus"
@change="emitChange"
class="ml-2 bg-white dark:bg-neutral-700 border border-gray-300 dark:border-neutral-600 rounded px-2 py-1 text-sm"
>
<option value="planned">Planned</option>
<option value="watched">Watched</option>
</select>
</label>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
movie: Object,
})
const emit = defineEmits(['status-changed'])
const selectedStatus = ref(props.movie.status)
const emitChange = () => {
emit('status-changed', props.movie.id, selectedStatus.value)
}
</script>
\ No newline at end of file
<template>
<div class="movie-list">
<div v-if="!movies.length" class="no-results">
<slot name="empty">No movies found.</slot>
</div>
<ul v-else class="movies">
<li v-for="movie in movies" :key="movie.id" class="movie-item">
<router-link
:to="{ name: 'FilmDetails', params: { id: movie.id } }"
class="movie-link"
>
<img
v-if="movie.poster_path"
:src="getPosterUrl(movie.poster_path)"
:alt="`${movie.title} poster`"
class="poster"
/>
<div class="details">
<h2 class="title">{{ movie.title }}</h2>
<p class="release-date">{{ formatDate(movie.release_date) }}</p>
<p class="overview">{{ movie.overview || 'No overview available.' }}</p>
</div>
</router-link>
</li>
</ul>
</div>
</template>
<script setup>
const props = defineProps({
movies: {
type: Array,
required: true
}
})
// TMDB image base URL from Vite env or fallback
const IMAGE_BASE = import.meta.env.VITE_TMDB_IMAGE_BASE || 'https://image.tmdb.org/t/p/w200'
/**
* Build full poster URL
*/
function getPosterUrl(path) {
return `${IMAGE_BASE}${path}`
}
/**
* Format a date string to a more readable form
*/
function formatDate(dateString) {
if (!dateString) return 'Unknown'
const options = { year: 'numeric', month: 'long', day: 'numeric' }
return new Date(dateString).toLocaleDateString(undefined, options)
}
</script>
<style scoped>
.movie-list {
display: flex;
flex-direction: column;
}
.no-results {
text-align: center;
color: #666;
font-style: italic;
}
.movies {
list-style: none;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.movie-item {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
}
.poster {
width: 100%;
object-fit: cover;
aspect-ratio: 2 / 3;
}
.details {
padding: 0.75rem;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.title {
font-size: 1.1rem;
margin: 0 0 0.5rem;
}
.release-date {
font-size: 0.875rem;
color: #888;
margin: 0 0 0.5rem;
}
.overview {
font-size: 0.9rem;
color: #444;
flex-grow: 1;
}
</style>
<template>
<nav class="fixed top-0 left-0 right-0 min-w-11/12 mx-6 bg-neutral-800 text-white shadow-md px-4 flex justify-between items-center z-50 rounded-2xl mt-3">
<!-- Logo -->
<div class="flex items-center">
<RouterLink to="/">
<img class="h-14 w-auto" src="../assets/Dark_Mode.png" alt="Logo" />
</RouterLink>
</div>
<!-- Search Bar (Perfectly Centered) -->
<div class="absolute left-1/2 transform -translate-x-1/2 w-full max-w-md">
<input
v-model="searchQuery"
type="text"
placeholder="Search..."
class="w-full px-4 py-2 pr-10 rounded-full bg-neutral-700 text-white placeholder-gray-400 focus:ring-2 focus:ring-white focus:outline-none"
/>
<!-- Search Icon inside input -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="size-6 absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">
<path stroke-linecap="round" stroke-linejoin="round"
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
</div>
<!-- Navigation & Profile Section -->
<div class="flex items-center space-x-4 ml-auto">
<!-- Navigation Links -->
<div class="flex space-x-3">
<RouterLink
v-for="item in filteredNavigation"
:key="item.name"
:to="item.href"
class="px-3 py-2 rounded-md text-sm font-medium transition hover:bg-neutral-700"
:class="{ 'bg-neutral-900': item.href === currentRoute }">
{{ item.name }}
</RouterLink>
<RouterLink
v-if="!isLoggedIn"
to="/login"
class="px-4 py-2 rounded-xl bg-blue-600 text-white font-semibold text-sm transition hover:bg-blue-700 shadow-md">
Login
</RouterLink>
</div>
<!-- Profile Dropdown (Only show if logged in) -->
<Menu as="div" class="relative" v-if="isLoggedIn">
<div>
<MenuButton class="flex rounded-full bg-gray-800 text-sm focus:ring-1 focus:ring-white focus:ring-offset-2">
<img class="h-8 w-8 rounded-full" :src="userPhotoURL" alt="User Avatar" />
</MenuButton>
</div>
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
<MenuItems class="absolute right-0 mt-2 w-48 bg-neutral-800 rounded-md shadow-lg ring-1 ring-black/5 py-1">
<MenuItem v-slot="{ active }">
<RouterLink to="/profile" :class="[active ? 'bg-neutral-700' : '', 'block px-4 py-2 text-sm text-white']">Your Profile</RouterLink>
</MenuItem>
<MenuItem v-slot="{ active }">
<RouterLink to="/settings" :class="[active ? 'bg-neutral-700' : '', 'block px-4 py-2 text-sm text-white']">Settings</RouterLink>
</MenuItem>
<MenuItem @click="handleSignOut" v-slot="{ active }">
<a href="#" @click.prevent="handleSignOut" :class="[active ? 'bg-neutral-700' : '', 'block px-4 py-2 text-sm text-white']">Sign out</a>
</MenuItem>
</MenuItems>
</transition>
</Menu>
</div>
</nav>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getAuth, onAuthStateChanged, signOut } from 'firebase/auth';
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
import defaultAvatar from '@/assets/default_avatar.png'
// Firebase Authentication instance
const auth = getAuth();
const route = useRoute();
const router = useRouter();
const isLoggedIn = ref(false);
const userPhotoURL = ref('');
const navigation = ref([
{ name: 'Films', href: '/films', authRequired: true },
{ name: 'Watchlist', href: '/watchlist', authRequired: true },
{ name: 'Top Rated', href: '/top-rated', authRequired: true },
{ name: 'Social', href: '/social', authRequired: true },
]);
// Compute the current route to match against the navigation links
const currentRoute = computed(() => route.path);
// Filter navigation based on auth state
const filteredNavigation = computed(() => {
return navigation.value.filter(item => {
if (item.authRequired && !isLoggedIn.value) return false; // Hide if user is not logged in
if (item.guestOnly && isLoggedIn.value) return false; // Hide if user is logged in
return true;
});
});
// Check authentication state
onMounted(() => {
onAuthStateChanged(auth, (user) => {
isLoggedIn.value = !!user; // Sets to true if user exists, false otherwise
if(user && user.photoURL) {
userPhotoURL.value = user.photoURL;
} else {
userPhotoURL.value = defaultAvatar;
}
});
});
// Sign-out function
const handleSignOut = () => {
signOut(auth)
.then(() => {
isLoggedIn.value = false; // Ensure UI updates correctly
router.push('/'); // Redirect to home
})
.catch((error) => {
console.error("Error signing out:", error);
});
};
</script>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment