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:
35
src/App.vue
35
src/App.vue
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -175,7 +167,6 @@
|
|||||||
<div class="text-white/50 text-lg mb-6">Your list is empty</div>
|
<div class="text-white/50 text-lg mb-6">Your list is empty</div>
|
||||||
<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 ===== -->
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user