feat(hub): ghost auth-required tiles + dock swaps Profile↔Log in
Two related UX hardening tweaks for the unauthenticated case. 1. Module.authRequired flag Tiles for modules with no public view (wallet, chat, castle) are now ghosted out for unauthenticated visitors — same visual treatment we already apply to "coming soon" tiles (opacity 60, cursor not-allowed, non-anchored). This prevents an unauth user from clicking through to a standalone that will instantly bounce them to /login (per the strict guards in those apps). Implementation: hubLink() returns null when authRequired && !isAuthenticated, which already triggers the existing non-link render branch. No new visual treatment to design. Public modules (forum, market, tasks, activities) and the restaurant placeholder are unaffected. 2. Bottom-dock Profile↔Log-in swap When logged in, the first dock slot opens the Profile sheet (existing behaviour). When logged out it now renders a plain Log-In button that pushes /login on the hub itself. Avoids showing a "Profile" affordance to a user who has no profile yet. Both changes localised to src/pages/Hub.vue. No other files touched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
772c57fd85
commit
b80ad24ae2
1 changed files with 20 additions and 6 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuth } from '@/composables/useAuthService'
|
import { useAuth } from '@/composables/useAuthService'
|
||||||
import { useTheme } from '@/components/theme-provider'
|
import { useTheme } from '@/components/theme-provider'
|
||||||
import { useLocale } from '@/composables/useLocale'
|
import { useLocale } from '@/composables/useLocale'
|
||||||
|
|
@ -7,7 +8,7 @@ import { toast } from 'vue-sonner'
|
||||||
import {
|
import {
|
||||||
Castle, ListTodo, Newspaper, MessageCircle, Wallet, CalendarDays,
|
Castle, ListTodo, Newspaper, MessageCircle, Wallet, CalendarDays,
|
||||||
Store, UtensilsCrossed,
|
Store, UtensilsCrossed,
|
||||||
User as UserIcon, Sun, Moon, Monitor, Globe, Coins,
|
User as UserIcon, LogIn, Sun, Moon, Monitor, Globe, Coins,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import {
|
import {
|
||||||
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
|
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
|
||||||
|
|
@ -19,6 +20,7 @@ import {
|
||||||
} from '@/components/ui/sheet'
|
} from '@/components/ui/sheet'
|
||||||
import ProfileSettings from '@/modules/base/components/ProfileSettings.vue'
|
import ProfileSettings from '@/modules/base/components/ProfileSettings.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const { isAuthenticated } = useAuth()
|
const { isAuthenticated } = useAuth()
|
||||||
const { theme, setTheme, currentTheme } = useTheme()
|
const { theme, setTheme, currentTheme } = useTheme()
|
||||||
const { currentLocale, locales, setLocale } = useLocale()
|
const { currentLocale, locales, setLocale } = useLocale()
|
||||||
|
|
@ -31,6 +33,8 @@ interface Module {
|
||||||
glow: string
|
glow: string
|
||||||
envKey?: string
|
envKey?: string
|
||||||
status?: string
|
status?: string
|
||||||
|
/** When true, the tile is ghosted out unless the user is logged in. */
|
||||||
|
authRequired?: boolean
|
||||||
/** Unread count for the corner badge. Wire to real data via #32. */
|
/** Unread count for the corner badge. Wire to real data via #32. */
|
||||||
unread?: number
|
unread?: number
|
||||||
}
|
}
|
||||||
|
|
@ -39,12 +43,12 @@ interface Module {
|
||||||
const modules: Module[] = [
|
const modules: Module[] = [
|
||||||
{ label: 'Restaurant', chakra: 'Muladhara', icon: UtensilsCrossed, bgClass: '', glow: 'rgba(255,80,80,0.5)', status: 'coming soon' },
|
{ label: 'Restaurant', chakra: 'Muladhara', icon: UtensilsCrossed, bgClass: '', glow: 'rgba(255,80,80,0.5)', status: 'coming soon' },
|
||||||
{ label: 'Market', chakra: 'Muladhara', icon: Store, bgClass: '', glow: 'rgba(255,80,80,0.5)', envKey: 'VITE_HUB_MARKET_URL', status: 'alpha' },
|
{ label: 'Market', chakra: 'Muladhara', icon: Store, bgClass: '', glow: 'rgba(255,80,80,0.5)', envKey: 'VITE_HUB_MARKET_URL', status: 'alpha' },
|
||||||
{ label: 'Wallet', chakra: 'Manipura', icon: Wallet, bgClass: '', glow: 'rgba(255,200,0,0.5)', envKey: 'VITE_HUB_WALLET_URL', status: 'alpha' },
|
{ label: 'Wallet', chakra: 'Manipura', icon: Wallet, bgClass: '', glow: 'rgba(255,200,0,0.5)', envKey: 'VITE_HUB_WALLET_URL', status: 'alpha', authRequired: true },
|
||||||
{ label: 'Activities', chakra: 'Swadhisthana', icon: CalendarDays, bgClass: '', glow: 'rgba(255,165,0,0.5)', envKey: 'VITE_HUB_ACTIVITIES_URL', status: 'beta' },
|
{ label: 'Activities', chakra: 'Swadhisthana', icon: CalendarDays, bgClass: '', glow: 'rgba(255,165,0,0.5)', envKey: 'VITE_HUB_ACTIVITIES_URL', status: 'beta' },
|
||||||
{ label: 'Chat', chakra: 'Anahata', icon: MessageCircle, bgClass: '', glow: 'rgba(0,200,80,0.5)', envKey: 'VITE_HUB_CHAT_URL', status: 'alpha' },
|
{ label: 'Chat', chakra: 'Anahata', icon: MessageCircle, bgClass: '', glow: 'rgba(0,200,80,0.5)', envKey: 'VITE_HUB_CHAT_URL', status: 'alpha', authRequired: true },
|
||||||
{ label: 'Forum', chakra: 'Vishuddha', icon: Newspaper, bgClass: '', glow: 'rgba(60,120,255,0.5)', envKey: 'VITE_HUB_FORUM_URL', status: 'alpha' },
|
{ label: 'Forum', chakra: 'Vishuddha', icon: Newspaper, bgClass: '', glow: 'rgba(60,120,255,0.5)', envKey: 'VITE_HUB_FORUM_URL', status: 'alpha' },
|
||||||
{ label: 'Tasks', chakra: 'Ajna', icon: ListTodo, bgClass: '', glow: 'rgba(99,80,200,0.5)', envKey: 'VITE_HUB_TASKS_URL', status: 'alpha' },
|
{ label: 'Tasks', chakra: 'Ajna', icon: ListTodo, bgClass: '', glow: 'rgba(99,80,200,0.5)', envKey: 'VITE_HUB_TASKS_URL', status: 'alpha' },
|
||||||
{ label: 'Castle', chakra: 'Sahasrara', icon: Castle, bgClass: '', glow: 'rgba(160,80,220,0.5)', envKey: 'VITE_HUB_CASTLE_URL', status: 'beta' },
|
{ label: 'Castle', chakra: 'Sahasrara', icon: Castle, bgClass: '', glow: 'rgba(160,80,220,0.5)', envKey: 'VITE_HUB_CASTLE_URL', status: 'beta', authRequired: true },
|
||||||
]
|
]
|
||||||
// Crown at top, root at bottom
|
// Crown at top, root at bottom
|
||||||
const orderedModules = computed(() => [...modules].reverse())
|
const orderedModules = computed(() => [...modules].reverse())
|
||||||
|
|
@ -53,6 +57,8 @@ const token = computed(() => localStorage.getItem('lnbits_access_token') || '')
|
||||||
|
|
||||||
function hubLink(m: Module): string | null {
|
function hubLink(m: Module): string | null {
|
||||||
if (!m.envKey) return null
|
if (!m.envKey) return null
|
||||||
|
// Auth-only modules (wallet, chat, castle) are ghosted when not logged in.
|
||||||
|
if (m.authRequired && !isAuthenticated.value) return null
|
||||||
const url = import.meta.env[m.envKey] as string | undefined
|
const url = import.meta.env[m.envKey] as string | undefined
|
||||||
if (!url) return null
|
if (!url) return null
|
||||||
if (isAuthenticated.value && token.value) {
|
if (isAuthenticated.value && token.value) {
|
||||||
|
|
@ -134,8 +140,8 @@ function notImplemented() {
|
||||||
style="padding-bottom: env(safe-area-inset-bottom)"
|
style="padding-bottom: env(safe-area-inset-bottom)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-around h-14 max-w-lg mx-auto">
|
<div class="flex items-center justify-around h-14 max-w-lg mx-auto">
|
||||||
<!-- Profile -->
|
<!-- Profile (when logged in) / Log in (when not) -->
|
||||||
<Sheet v-model:open="showProfile">
|
<Sheet v-if="isAuthenticated" v-model:open="showProfile">
|
||||||
<SheetTrigger as-child>
|
<SheetTrigger as-child>
|
||||||
<button class="flex flex-col items-center justify-center gap-0.5 flex-1 h-full text-muted-foreground hover:text-foreground transition-colors">
|
<button class="flex flex-col items-center justify-center gap-0.5 flex-1 h-full text-muted-foreground hover:text-foreground transition-colors">
|
||||||
<UserIcon class="w-5 h-5" />
|
<UserIcon class="w-5 h-5" />
|
||||||
|
|
@ -152,6 +158,14 @@ function notImplemented() {
|
||||||
</div>
|
</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="flex flex-col items-center justify-center gap-0.5 flex-1 h-full text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
@click="router.push('/login')"
|
||||||
|
>
|
||||||
|
<LogIn class="w-5 h-5" />
|
||||||
|
<span class="text-[10px] font-medium">Log in</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- Theme -->
|
<!-- Theme -->
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue