Gate My List behind auth modal instead of in-page prompt

- Clicking My List when not logged in now opens the auth modal
  directly instead of navigating to a page with a sign-in button
- After successful login, auto-redirects to /library (My List)
- Works on both desktop header and mobile tab bar
- App.vue tracks a pending redirect path so the post-login
  navigation happens seamlessly
- Direct URL navigation to /library when not logged in also
  triggers the modal and redirects back to Films

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dorian
2026-02-12 12:41:12 +00:00
parent 04d80f545e
commit ba4e37813d
4 changed files with 72 additions and 25 deletions

View File

@@ -1,19 +1,19 @@
<template> <template>
<div id="app" class="min-h-screen"> <div id="app" class="min-h-screen">
<!-- Shared Header --> <!-- Shared Header -->
<AppHeader @openAuth="showAuthModal = true" /> <AppHeader @openAuth="handleOpenAuth" />
<!-- Route Content --> <!-- Route Content -->
<RouterView @openAuth="showAuthModal = true" /> <RouterView @openAuth="handleOpenAuth" />
<!-- Mobile Navigation (hidden on desktop) --> <!-- Mobile Navigation (hidden on desktop) -->
<MobileNav /> <MobileNav @openAuth="handleOpenAuth" />
<!-- Auth Modal (shared across all views) --> <!-- Auth Modal (shared across all views) -->
<AuthModal <AuthModal
:isOpen="showAuthModal" :isOpen="showAuthModal"
@close="showAuthModal = false" @close="handleAuthClose"
@success="showAuthModal = false" @success="handleAuthSuccess"
/> />
<!-- Toast Notifications --> <!-- Toast Notifications -->
@@ -23,14 +23,39 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from './stores/auth' import { useAuthStore } from './stores/auth'
import AppHeader from './components/AppHeader.vue' import AppHeader from './components/AppHeader.vue'
import AuthModal from './components/AuthModal.vue' import AuthModal from './components/AuthModal.vue'
import MobileNav from './components/MobileNav.vue' import MobileNav from './components/MobileNav.vue'
import ToastContainer from './components/ToastContainer.vue' import ToastContainer from './components/ToastContainer.vue'
const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
const showAuthModal = ref(false) const showAuthModal = ref(false)
const pendingRedirect = ref<string | null>(null)
/**
* Open the auth modal, optionally storing a redirect path
* to navigate to after successful login.
*/
function handleOpenAuth(redirect?: string) {
pendingRedirect.value = redirect || null
showAuthModal.value = true
}
function handleAuthClose() {
showAuthModal.value = false
pendingRedirect.value = null
}
function handleAuthSuccess() {
showAuthModal.value = false
if (pendingRedirect.value) {
router.push(pendingRedirect.value)
pendingRedirect.value = null
}
}
onMounted(async () => { onMounted(async () => {
// Initialize authentication on app mount // Initialize authentication on app mount

View File

@@ -11,7 +11,7 @@
<!-- Navigation - Desktop --> <!-- Navigation - Desktop -->
<nav v-if="showNav" class="hidden md:flex items-center gap-3"> <nav v-if="showNav" class="hidden md:flex items-center gap-3">
<button @click="handleFilmsClick" :class="isRoute('/') && !activeAlgorithm ? 'nav-button-active' : 'nav-button'">Films</button> <button @click="handleFilmsClick" :class="isRoute('/') && !activeAlgorithm ? 'nav-button-active' : 'nav-button'">Films</button>
<router-link to="/library" :class="isRoute('/library') && !activeAlgorithm ? 'nav-button-active' : 'nav-button'" @click="clearFilter">My List</router-link> <button @click="handleMyListClick" :class="isRoute('/library') && !activeAlgorithm ? 'nav-button-active' : 'nav-button'">My List</button>
<!-- Inline Algorithm Buttons (xl+ screens where they fit) --> <!-- Inline Algorithm Buttons (xl+ screens where they fit) -->
<button <button
@@ -223,7 +223,7 @@ interface Props {
} }
interface Emits { interface Emits {
(e: 'openAuth'): void (e: 'openAuth', redirect?: string): void
} }
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
@@ -232,7 +232,7 @@ withDefaults(defineProps<Props>(), {
showAuth: true, showAuth: true,
}) })
defineEmits<Emits>() const emit = defineEmits<Emits>()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@@ -310,11 +310,21 @@ function handleFilmsClick() {
} }
} }
/** Clear active filter (used when navigating to My List) */ /**
function clearFilter() { * Navigate to My List if logged in, otherwise open the auth modal
* with a redirect so the user lands on My List after login.
*/
function handleMyListClick() {
if (activeAlgorithm.value) { if (activeAlgorithm.value) {
_setAlgorithm(activeAlgorithm.value as any) // toggle off _setAlgorithm(activeAlgorithm.value as any) // toggle off
} }
if (!isAuthenticated.value && !nostrLoggedIn.value) {
emit('openAuth', '/library')
return
}
if (route.path !== '/library') {
router.push('/library')
}
} }
function toggleDropdown() { function toggleDropdown() {

View File

@@ -93,9 +93,15 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useContentDiscovery, type AlgorithmId } from '../composables/useContentDiscovery' import { useContentDiscovery, type AlgorithmId } from '../composables/useContentDiscovery'
import { useAuth } from '../composables/useAuth'
import { useAccounts } from '../composables/useAccounts'
const emit = defineEmits<{ (e: 'openAuth', redirect?: string): void }>()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const { isAuthenticated } = useAuth()
const { isLoggedIn: isNostrLoggedIn } = useAccounts()
const { const {
activeAlgorithm, activeAlgorithm,
@@ -107,6 +113,7 @@ const {
const showFilterSheet = ref(false) const showFilterSheet = ref(false)
const isOnFilmsPage = computed(() => route.path === '/') const isOnFilmsPage = computed(() => route.path === '/')
const isLoggedInAnywhere = computed(() => isAuthenticated.value || isNostrLoggedIn.value)
const navigate = (path: string) => { const navigate = (path: string) => {
router.push(path) router.push(path)
@@ -126,11 +133,18 @@ function handleFilmsClick() {
navigate('/') navigate('/')
} }
/** Navigate to My List and clear any active filter */ /**
* Navigate to My List if logged in, otherwise open the auth modal
* with a redirect so the user lands on My List after login.
*/
function handleMyListClick() { function handleMyListClick() {
if (isFilterActive.value) { if (isFilterActive.value) {
setAlgorithm(activeAlgorithm.value!) // toggle off setAlgorithm(activeAlgorithm.value!) // toggle off
} }
if (!isLoggedInAnywhere.value) {
emit('openAuth', '/library')
return
}
navigate('/library') navigate('/library')
} }

View File

@@ -78,16 +78,8 @@
<section class="relative pt-8 pb-20 px-4"> <section class="relative pt-8 pb-20 px-4">
<div class="mx-auto space-y-12"> <div class="mx-auto space-y-12">
<!-- ===== MY LIST TAB ===== --> <!-- ===== MY LIST TAB (only rendered when logged in) ===== -->
<template v-if="isMyListTab"> <template v-if="isMyListTab && isLoggedInAnywhere">
<!-- Not logged in -->
<div v-if="!isLoggedInAnywhere" class="text-center py-16">
<div class="text-white/50 text-lg mb-6">Sign in to save films to your list</div>
<button @click="$emit('openAuth')" class="hero-play-button inline-block">Sign In</button>
</div>
<!-- Logged in: library content -->
<template v-else>
<!-- Continue Watching --> <!-- Continue Watching -->
<div v-if="continueWatching.length > 0" class="content-row"> <div v-if="continueWatching.length > 0" class="content-row">
<h2 class="content-row-title text-xl md:text-2xl font-bold text-white mb-6 px-4 uppercase">Continue Watching</h2> <h2 class="content-row-title text-xl md:text-2xl font-bold text-white mb-6 px-4 uppercase">Continue Watching</h2>
@@ -176,7 +168,6 @@
<router-link to="/" class="hero-play-button inline-block text-decoration-none">Browse Films</router-link> <router-link to="/" class="hero-play-button inline-block text-decoration-none">Browse Films</router-link>
</div> </div>
</template> </template>
</template>
<!-- ===== FILMS TAB: Filtered Grid ===== --> <!-- ===== FILMS TAB: Filtered Grid ===== -->
<template v-else-if="isFilterActive"> <template v-else-if="isFilterActive">
@@ -274,7 +265,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue' import { ref, onMounted, computed, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import ContentRow from '../components/ContentRow.vue' import ContentRow from '../components/ContentRow.vue'
import SplashIntro from '../components/SplashIntro.vue' import SplashIntro from '../components/SplashIntro.vue'
import ContentDetailModal from '../components/ContentDetailModal.vue' import ContentDetailModal from '../components/ContentDetailModal.vue'
@@ -287,9 +278,10 @@ import { useContentDiscovery } from '../composables/useContentDiscovery'
import { indeeHubFilms, bitcoinFilms, documentaries } from '../data/indeeHubFilms' import { indeeHubFilms, bitcoinFilms, documentaries } from '../data/indeeHubFilms'
import type { Content } from '../types/content' import type { Content } from '../types/content'
const emit = defineEmits<{ (e: 'openAuth'): void }>() const emit = defineEmits<{ (e: 'openAuth', redirect?: string): void }>()
const route = useRoute() const route = useRoute()
const router = useRouter()
const contentStore = useContentStore() const contentStore = useContentStore()
const { isAuthenticated, hasActiveSubscription } = useAuth() const { isAuthenticated, hasActiveSubscription } = useAuth()
const { isLoggedIn: isNostrLoggedIn } = useAccounts() const { isLoggedIn: isNostrLoggedIn } = useAccounts()
@@ -369,8 +361,14 @@ function loadDummyLibrary() {
rentedContent.value = documentaries.slice(0, 2) rentedContent.value = documentaries.slice(0, 2)
} }
// Load library data when the tab becomes active and user is logged in // If someone navigates directly to /library without being logged in,
// open the auth modal and redirect back to Films.
watch([isMyListTab, isLoggedInAnywhere], ([onListTab, loggedIn]) => { watch([isMyListTab, isLoggedInAnywhere], ([onListTab, loggedIn]) => {
if (onListTab && !loggedIn) {
emit('openAuth', '/library')
router.replace('/')
return
}
if (onListTab && loggedIn) { if (onListTab && loggedIn) {
loadDummyLibrary() loadDummyLibrary()
} }