Update package dependencies and enhance application structure
- Added several new dependencies related to the Applesauce library, including 'applesauce-accounts', 'applesauce-common', 'applesauce-core', 'applesauce-loaders', 'applesauce-relay', and 'applesauce-signers', all at version 5.1.0. - Updated the development script in package.json to specify a port for Vite and added new seed scripts for profiles and activity. - Removed outdated image files from the public directory to clean up unused assets. - Enhanced the App.vue structure by integrating shared components like AppHeader and AuthModal for improved user experience. - Refactored ContentDetailModal and MobileNav components to support new features and improve usability. These changes improve the overall functionality and maintainability of the application while ensuring it utilizes the latest libraries for better performance.
This commit is contained in:
@@ -5,21 +5,74 @@
|
||||
<!-- Logo + Navigation (Left Side) -->
|
||||
<div class="flex items-center gap-10">
|
||||
<router-link to="/">
|
||||
<img src="/assets/images/logo.svg" alt="IndeedHub" class="h-10 ml-2 md:ml-0" />
|
||||
<img src="/assets/images/logo-desktop.svg" alt="IndeeHub" class="h-8 md:h-10 ml-2 md:ml-0" />
|
||||
</router-link>
|
||||
|
||||
<!-- Navigation - Desktop -->
|
||||
<nav v-if="showNav" class="hidden md:flex items-center gap-3">
|
||||
<router-link to="/" :class="isRoute('/') ? 'nav-button-active' : 'nav-button'">Films</router-link>
|
||||
<router-link to="/library" :class="isRoute('/library') ? 'nav-button-active' : 'nav-button'">My List</router-link>
|
||||
<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>
|
||||
|
||||
<!-- Algorithm Filters -->
|
||||
<button
|
||||
v-for="algo in algorithms"
|
||||
:key="algo.id"
|
||||
@click="setAlgorithm(algo.id)"
|
||||
:class="activeAlgorithm === algo.id ? 'nav-button-active' : 'nav-button'"
|
||||
>
|
||||
{{ algo.label }}
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Right Side Actions -->
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Sign In Button (if not authenticated) -->
|
||||
<!-- Nostr Login (persona switcher or extension) -->
|
||||
<div v-if="!nostrLoggedIn" class="hidden md:flex items-center gap-2">
|
||||
<!-- Persona Switcher (dev) -->
|
||||
<div class="relative persona-dropdown">
|
||||
<button @click="togglePersonaMenu" class="nav-button px-3 py-2 text-xs">
|
||||
Persona
|
||||
<svg class="w-3 h-3 ml-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
<div v-if="personaMenuOpen" class="profile-menu absolute right-0 mt-2 w-52">
|
||||
<div class="floating-glass-header py-2 rounded-xl">
|
||||
<div class="px-3 py-1 text-xs text-white/40 uppercase tracking-wider">Test Personas</div>
|
||||
<button
|
||||
v-for="persona in testPersonas"
|
||||
:key="persona.pubkey"
|
||||
@click="handlePersonaLogin(persona)"
|
||||
class="profile-menu-item flex items-center gap-3 px-4 py-2 w-full text-left"
|
||||
>
|
||||
<img :src="`https://robohash.org/${persona.pubkey}.png`" class="w-6 h-6 rounded-full" :alt="persona.name" />
|
||||
<span>{{ persona.name }}</span>
|
||||
</button>
|
||||
<div class="border-t border-white/10 my-1"></div>
|
||||
<div class="px-3 py-1 text-xs text-white/40 uppercase tracking-wider">Tastemakers</div>
|
||||
<button
|
||||
v-for="persona in tastemakerPersonas"
|
||||
:key="persona.pubkey"
|
||||
@click="handlePersonaLogin(persona)"
|
||||
class="profile-menu-item flex items-center gap-3 px-4 py-2 w-full text-left"
|
||||
>
|
||||
<img :src="`https://robohash.org/${persona.pubkey}.png`" class="w-6 h-6 rounded-full" :alt="persona.name" />
|
||||
<span>{{ persona.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Extension Login -->
|
||||
<button @click="handleExtensionLogin" class="nav-button px-3 py-2 text-xs">
|
||||
Extension
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sign In (app auth, if not Nostr logged in) -->
|
||||
<button
|
||||
v-if="!isAuthenticated && showAuth"
|
||||
v-if="!isAuthenticated && showAuth && !nostrLoggedIn"
|
||||
@click="$emit('openAuth')"
|
||||
class="hidden md:block hero-play-button px-4 py-2 text-sm"
|
||||
>
|
||||
@@ -33,22 +86,24 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Profile Dropdown (authenticated only) -->
|
||||
<div v-if="isAuthenticated" class="hidden md:block relative profile-dropdown">
|
||||
<!-- Active Nostr Account -->
|
||||
<div v-if="nostrLoggedIn" class="hidden md:block relative profile-dropdown">
|
||||
<button
|
||||
@click="toggleDropdown"
|
||||
class="profile-button flex items-center gap-2"
|
||||
>
|
||||
<div class="profile-avatar">
|
||||
<span>{{ userInitials }}</span>
|
||||
</div>
|
||||
<span class="text-white text-sm font-medium">{{ userName }}</span>
|
||||
<img
|
||||
v-if="nostrActivePubkey"
|
||||
:src="`https://robohash.org/${nostrActivePubkey}.png`"
|
||||
class="w-8 h-8 rounded-full"
|
||||
alt="Avatar"
|
||||
/>
|
||||
<span class="text-white text-sm font-medium">{{ nostrActiveName }}</span>
|
||||
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': dropdownOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div v-if="dropdownOpen" class="profile-menu absolute right-0 mt-2 w-48">
|
||||
<div class="floating-glass-header py-2 rounded-xl">
|
||||
<button @click="navigateTo('/profile')" class="profile-menu-item flex items-center gap-3 px-4 py-2.5 w-full text-left">
|
||||
@@ -74,12 +129,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile User Avatar + Name -->
|
||||
<!-- Fallback: App-auth profile (when Nostr not logged in but app auth is) -->
|
||||
<div v-else-if="isAuthenticated" class="hidden md:block relative profile-dropdown">
|
||||
<button @click="toggleDropdown" class="profile-button flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-orange-500 to-pink-500 flex items-center justify-center text-xs font-bold text-white">
|
||||
{{ userInitials }}
|
||||
</div>
|
||||
<span class="text-white text-sm font-medium">{{ userName }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile -->
|
||||
<div class="md:hidden flex items-center gap-2 mr-2">
|
||||
<div v-if="isAuthenticated" class="profile-avatar">
|
||||
<span>{{ userInitials }}</span>
|
||||
</div>
|
||||
<span v-if="isAuthenticated" class="text-white text-sm font-medium">{{ userName }}</span>
|
||||
<img
|
||||
v-if="nostrLoggedIn && nostrActivePubkey"
|
||||
:src="`https://robohash.org/${nostrActivePubkey}.png`"
|
||||
class="w-7 h-7 rounded-full"
|
||||
alt="Avatar"
|
||||
/>
|
||||
<span v-if="nostrLoggedIn" class="text-white text-sm font-medium">{{ nostrActiveName }}</span>
|
||||
<template v-else-if="isAuthenticated">
|
||||
<div class="w-7 h-7 rounded-full bg-gradient-to-br from-orange-500 to-pink-500 flex items-center justify-center text-xs font-bold text-white">
|
||||
{{ userInitials }}
|
||||
</div>
|
||||
<span class="text-white text-sm font-medium">{{ userName }}</span>
|
||||
</template>
|
||||
<button v-else-if="showAuth" @click="$emit('openAuth')" class="text-white text-sm font-medium">Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,6 +166,10 @@
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAuth } from '../composables/useAuth'
|
||||
import { useAccounts } from '../composables/useAccounts'
|
||||
import { useContentDiscovery } from '../composables/useContentDiscovery'
|
||||
|
||||
type Persona = { name: string; nsec: string; pubkey: string }
|
||||
|
||||
interface Props {
|
||||
showNav?: boolean
|
||||
@@ -113,11 +191,40 @@ defineEmits<Emits>()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { user, isAuthenticated, logout } = useAuth()
|
||||
const { user, isAuthenticated, logout: appLogout } = useAuth()
|
||||
const {
|
||||
isLoggedIn: nostrLoggedIn,
|
||||
activePubkey: nostrActivePubkey,
|
||||
activeName: nostrActiveName,
|
||||
testPersonas,
|
||||
tastemakerPersonas,
|
||||
loginWithExtension,
|
||||
loginWithPersona,
|
||||
logout: nostrLogout,
|
||||
} = useAccounts()
|
||||
|
||||
const {
|
||||
activeAlgorithm,
|
||||
algorithms,
|
||||
setAlgorithm: _setAlgorithm,
|
||||
} = useContentDiscovery()
|
||||
|
||||
/**
|
||||
* When a filter is clicked, navigate to Films page if not already there,
|
||||
* then apply the filter.
|
||||
*/
|
||||
function setAlgorithm(id: string) {
|
||||
_setAlgorithm(id as any)
|
||||
if (route.path !== '/') {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const personaMenuOpen = ref(false)
|
||||
|
||||
const userInitials = computed(() => {
|
||||
if (nostrActiveName.value) return nostrActiveName.value[0].toUpperCase()
|
||||
if (!user.value?.legalName) return 'U'
|
||||
const names = user.value.legalName.split(' ')
|
||||
return names.length > 1
|
||||
@@ -126,6 +233,7 @@ const userInitials = computed(() => {
|
||||
})
|
||||
|
||||
const userName = computed(() => {
|
||||
if (nostrActiveName.value) return nostrActiveName.value
|
||||
return user.value?.legalName?.split(' ')[0] || 'Guest'
|
||||
})
|
||||
|
||||
@@ -133,8 +241,31 @@ function isRoute(path: string): boolean {
|
||||
return route.path === path
|
||||
}
|
||||
|
||||
/** Navigate to Films and clear any active filter */
|
||||
function handleFilmsClick() {
|
||||
if (activeAlgorithm.value) {
|
||||
_setAlgorithm(activeAlgorithm.value as any) // toggle off
|
||||
}
|
||||
if (route.path !== '/') {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear active filter (used when navigating to My List) */
|
||||
function clearFilter() {
|
||||
if (activeAlgorithm.value) {
|
||||
_setAlgorithm(activeAlgorithm.value as any) // toggle off
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDropdown() {
|
||||
dropdownOpen.value = !dropdownOpen.value
|
||||
personaMenuOpen.value = false
|
||||
}
|
||||
|
||||
function togglePersonaMenu() {
|
||||
personaMenuOpen.value = !personaMenuOpen.value
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
|
||||
function navigateTo(path: string) {
|
||||
@@ -142,17 +273,31 @@ function navigateTo(path: string) {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
async function handlePersonaLogin(persona: Persona) {
|
||||
personaMenuOpen.value = false
|
||||
await loginWithPersona(persona)
|
||||
}
|
||||
|
||||
async function handleExtensionLogin() {
|
||||
await loginWithExtension()
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
await logout()
|
||||
nostrLogout()
|
||||
await appLogout()
|
||||
dropdownOpen.value = false
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const dropdown = document.querySelector('.profile-dropdown')
|
||||
const personaDropdown = document.querySelector('.persona-dropdown')
|
||||
if (dropdown && !dropdown.contains(event.target as Node)) {
|
||||
dropdownOpen.value = false
|
||||
}
|
||||
if (personaDropdown && !personaDropdown.contains(event.target as Node)) {
|
||||
personaMenuOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user