feat(layout): re-enable "Back to hub" with a sticky sheet footer #128
11 changed files with 183 additions and 149 deletions
feat(layout): re-enable "Back to hub" with a sticky sheet footer
Reverses the events-only hide (c037d45) now that the link has a real
home. Three parts:
- Add a @brand-hub-logo alias (brandHubLogoAliasEntry) resolving to the
brand's primary/global logo — the HUB's logo, never the per-standalone
@brand-app-logo. Wired into all 9 app vite configs since the shared
ProfileSheetContent renders it.
- Restructure the profile sheet into a fixed-height flex column: a
flex-1 min-h-0 overflow-y-auto scroll region over a shrink-0 footer,
so "Back to hub" + the log-in/out bar stay pinned to the bottom while
the identity/preferences area scrolls.
- Move the edit-profile Dialog out of the flex root (it portals to body,
so it's not part of the sheet flow).
Logo bumped to w-8 h-8, centered, with tightened footer padding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit
2cc8e34b9d
|
|
@ -66,9 +66,7 @@ const npubPreview = computed(() => {
|
||||||
return value.length > 24 ? `${value.slice(0, 12)}…${value.slice(-8)}` : value
|
return value.length > 24 ? `${value.slice(0, 12)}…${value.slice(-8)}` : value
|
||||||
})
|
})
|
||||||
|
|
||||||
// "Back to hub" is hidden for the events-only launch (see template).
|
const hubRootUrl = computed(() => import.meta.env.VITE_HUB_ROOT_URL || '/')
|
||||||
// Re-enable this alongside the commented-out link below when the hub ships.
|
|
||||||
// const hubRootUrl = computed(() => import.meta.env.VITE_HUB_ROOT_URL || '/')
|
|
||||||
|
|
||||||
const copiedField = ref<string | null>(null)
|
const copiedField = ref<string | null>(null)
|
||||||
async function copyToClipboard(text: string, field: string) {
|
async function copyToClipboard(text: string, field: string) {
|
||||||
|
|
@ -105,156 +103,166 @@ async function onLogout() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SheetHeader>
|
<!-- Fill the sheet exactly (h-full) so the footer stays stuck to the
|
||||||
<SheetTitle>{{ t('common.nav.profile') }}</SheetTitle>
|
bottom while only the region above it scrolls. The sheet host already
|
||||||
<SheetDescription v-if="isAuthenticated">
|
has overflow-y-auto, but with an exact-fit child it never triggers —
|
||||||
{{ t('common.nav.profileDescription') }}
|
the inner flex-1 region owns the scroll instead. -->
|
||||||
</SheetDescription>
|
<div class="flex h-full flex-col">
|
||||||
<SheetDescription v-else>
|
<!-- Scrollable region: everything above the pinned footer. min-h-0 lets
|
||||||
{{ t('common.nav.profileLoggedOutDescription') }}
|
this flex child shrink below its content height so it can scroll. -->
|
||||||
</SheetDescription>
|
<div class="flex-1 min-h-0 overflow-y-auto">
|
||||||
</SheetHeader>
|
<SheetHeader>
|
||||||
|
<SheetTitle>{{ t('common.nav.profile') }}</SheetTitle>
|
||||||
|
<SheetDescription v-if="isAuthenticated">
|
||||||
|
{{ t('common.nav.profileDescription') }}
|
||||||
|
</SheetDescription>
|
||||||
|
<SheetDescription v-else>
|
||||||
|
{{ t('common.nav.profileLoggedOutDescription') }}
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
<!-- Identity card (logged in) — summary with an inline edit (pencil)
|
<!-- Identity card (logged in) — summary with an inline edit (pencil)
|
||||||
button that opens the profile form. -->
|
button that opens the profile form. -->
|
||||||
<div v-if="isAuthenticated" class="mt-4 rounded-lg border bg-muted/30 p-3 space-y-4">
|
<div v-if="isAuthenticated" class="mt-4 rounded-lg border bg-muted/30 p-3 space-y-4">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<Avatar class="h-12 w-12 shrink-0">
|
<Avatar class="h-12 w-12 shrink-0">
|
||||||
<AvatarImage v-if="pictureUrl" :src="pictureUrl" :alt="displayName ?? ''" />
|
<AvatarImage v-if="pictureUrl" :src="pictureUrl" :alt="displayName ?? ''" />
|
||||||
<AvatarFallback>{{ fallbackInitial || '?' }}</AvatarFallback>
|
<AvatarFallback>{{ fallbackInitial || '?' }}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="text-sm font-medium truncate">{{ displayName || user?.username }}</p>
|
<p class="text-sm font-medium truncate">{{ displayName || user?.username }}</p>
|
||||||
<p v-if="displayName && user?.username" class="text-xs text-muted-foreground truncate">
|
<p v-if="displayName && user?.username" class="text-xs text-muted-foreground truncate">
|
||||||
@{{ user.username }}
|
@{{ user.username }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="h-8 w-8 shrink-0 self-start text-muted-foreground"
|
class="h-8 w-8 shrink-0 self-start text-muted-foreground"
|
||||||
:aria-label="t('common.nav.editProfile', 'Edit profile')"
|
:aria-label="t('common.nav.editProfile', 'Edit profile')"
|
||||||
@click="editProfileOpen = true"
|
@click="editProfileOpen = true"
|
||||||
>
|
|
||||||
<Pencil class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Identifier rows: full-width value with a corner-offset "legend"
|
|
||||||
badge straddling the top border (fieldset-legend pattern). The
|
|
||||||
value gets the entire row so long bech32 / username@domain
|
|
||||||
strings have room to render. -->
|
|
||||||
<div class="space-y-3 pt-1">
|
|
||||||
<!-- Lightning Address — this is also the NIP-05 in this stack,
|
|
||||||
but the @username above already signals the NIP-05. -->
|
|
||||||
<button
|
|
||||||
v-if="lightningAddress"
|
|
||||||
type="button"
|
|
||||||
class="relative w-full rounded-md border bg-background/60 px-3 pt-3 pb-2 text-left hover:bg-background transition-colors min-w-0"
|
|
||||||
:aria-label="t('common.nav.copyLightning', 'Copy Lightning address')"
|
|
||||||
@click="copyToClipboard(lightningAddress, 'lightning')"
|
|
||||||
>
|
|
||||||
<span class="absolute -top-2 left-2 inline-flex items-center gap-1 rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground leading-none">
|
|
||||||
<Zap class="w-3 h-3 text-yellow-500 fill-yellow-500" />
|
|
||||||
{{ t('common.nav.lightning', 'Lightning') }}
|
|
||||||
</span>
|
|
||||||
<span class="block truncate pr-6 text-xs font-mono">{{ lightningAddress }}</span>
|
|
||||||
<component
|
|
||||||
:is="copiedField === 'lightning' ? Check : Copy"
|
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- npub — copy the full bech32 even though we display a preview. -->
|
|
||||||
<button
|
|
||||||
v-if="npub"
|
|
||||||
type="button"
|
|
||||||
class="relative w-full rounded-md border bg-background/60 px-3 pt-3 pb-2 text-left hover:bg-background transition-colors min-w-0"
|
|
||||||
:aria-label="t('common.nav.copyNpub', 'Copy npub')"
|
|
||||||
@click="copyToClipboard(npub, 'npub')"
|
|
||||||
>
|
|
||||||
<span class="absolute -top-2 left-2 inline-flex items-center rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground leading-none">
|
|
||||||
{{ t('common.nav.npub', 'npub') }}
|
|
||||||
</span>
|
|
||||||
<span class="block truncate pr-6 text-xs font-mono">{{ npubPreview }}</span>
|
|
||||||
<component
|
|
||||||
:is="copiedField === 'npub' ? Check : Copy"
|
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- App-specific nav items (rendered by callers like StandaloneMenu) -->
|
|
||||||
<slot name="app-nav" />
|
|
||||||
|
|
||||||
<!-- Cross-app links + global preferences (always visible, auth or not) -->
|
|
||||||
<div class="mt-4">
|
|
||||||
<!-- "Back to hub" hidden for the events-only launch — re-enable when
|
|
||||||
the hub ships. When re-enabled, show the HUB's brand-kit logo
|
|
||||||
(the brand's primary/global logo, or a hub-specific override) —
|
|
||||||
NOT the per-standalone @brand-app-logo, which resolves to this
|
|
||||||
standalone's own logo. This needs a hub-logo alias (e.g.
|
|
||||||
@brand-hub-logo = resolveAppLogo for the hub) added in
|
|
||||||
vite-branding.ts + the standalone vite configs.
|
|
||||||
<a
|
|
||||||
:href="hubRootUrl"
|
|
||||||
class="flex items-center justify-between gap-3 px-3 py-3 hover:bg-accent rounded-md transition-colors"
|
|
||||||
:aria-label="t('common.nav.backToHub')"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<img src="@brand-hub-logo" :alt="t('common.nav.backToHub')" class="w-5 h-5 shrink-0" />
|
|
||||||
<span class="text-sm font-medium">{{ t('common.nav.backToHub') }}</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<PreferencesRow layout="list" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Logged-out: prominent log-in CTA -->
|
|
||||||
<div v-if="!isAuthenticated" class="mt-6">
|
|
||||||
<Separator class="mb-4" />
|
|
||||||
<Button class="w-full" @click="goLogin">
|
|
||||||
<LogIn class="mr-2 h-4 w-4" />
|
|
||||||
{{ t('common.nav.login') }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Logged-in: log-out button stays visible without opening the edit popup. -->
|
|
||||||
<div v-else class="mt-6">
|
|
||||||
<Separator class="mb-4" />
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger as-child>
|
|
||||||
<Button variant="destructive" class="w-full">
|
|
||||||
<LogOut class="mr-2 h-4 w-4" />
|
|
||||||
{{ t('common.nav.logOut', 'Log out') }}
|
|
||||||
</Button>
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>
|
|
||||||
Log out of {{ user?.username || 'your account' }}?
|
|
||||||
</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
{{ t('common.nav.logOutConfirmDescription', "You'll need to sign in again to access your wallet, post in the forum, place orders, or use any feature that needs your account.") }}
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>{{ t('common.nav.cancel', 'Cancel') }}</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
|
||||||
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
||||||
@click="onLogout"
|
|
||||||
>
|
>
|
||||||
{{ t('common.nav.logOut', 'Log out') }}
|
<Pencil class="h-4 w-4" />
|
||||||
</AlertDialogAction>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</div>
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
<!-- Identifier rows: full-width value with a corner-offset "legend"
|
||||||
|
badge straddling the top border (fieldset-legend pattern). The
|
||||||
|
value gets the entire row so long bech32 / username@domain
|
||||||
|
strings have room to render. -->
|
||||||
|
<div class="space-y-3 pt-1">
|
||||||
|
<!-- Lightning Address — this is also the NIP-05 in this stack,
|
||||||
|
but the @username above already signals the NIP-05. -->
|
||||||
|
<button
|
||||||
|
v-if="lightningAddress"
|
||||||
|
type="button"
|
||||||
|
class="relative w-full rounded-md border bg-background/60 px-3 pt-3 pb-2 text-left hover:bg-background transition-colors min-w-0"
|
||||||
|
:aria-label="t('common.nav.copyLightning', 'Copy Lightning address')"
|
||||||
|
@click="copyToClipboard(lightningAddress, 'lightning')"
|
||||||
|
>
|
||||||
|
<span class="absolute -top-2 left-2 inline-flex items-center gap-1 rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground leading-none">
|
||||||
|
<Zap class="w-3 h-3 text-yellow-500 fill-yellow-500" />
|
||||||
|
{{ t('common.nav.lightning', 'Lightning') }}
|
||||||
|
</span>
|
||||||
|
<span class="block truncate pr-6 text-xs font-mono">{{ lightningAddress }}</span>
|
||||||
|
<component
|
||||||
|
:is="copiedField === 'lightning' ? Check : Copy"
|
||||||
|
class="absolute right-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- npub — copy the full bech32 even though we display a preview. -->
|
||||||
|
<button
|
||||||
|
v-if="npub"
|
||||||
|
type="button"
|
||||||
|
class="relative w-full rounded-md border bg-background/60 px-3 pt-3 pb-2 text-left hover:bg-background transition-colors min-w-0"
|
||||||
|
:aria-label="t('common.nav.copyNpub', 'Copy npub')"
|
||||||
|
@click="copyToClipboard(npub, 'npub')"
|
||||||
|
>
|
||||||
|
<span class="absolute -top-2 left-2 inline-flex items-center rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground leading-none">
|
||||||
|
{{ t('common.nav.npub', 'npub') }}
|
||||||
|
</span>
|
||||||
|
<span class="block truncate pr-6 text-xs font-mono">{{ npubPreview }}</span>
|
||||||
|
<component
|
||||||
|
:is="copiedField === 'npub' ? Check : Copy"
|
||||||
|
class="absolute right-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- App-specific nav items (rendered by callers like StandaloneMenu) -->
|
||||||
|
<slot name="app-nav" />
|
||||||
|
|
||||||
|
<!-- Cross-app links + global preferences (always visible, auth or not) -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<PreferencesRow layout="list" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pinned footer: stays stuck to the bottom of the sheet (shrink-0);
|
||||||
|
"Back to hub" sits directly above the log-in/out bar. -->
|
||||||
|
<div class="shrink-0 pt-1">
|
||||||
|
<!-- "Back to hub" shows the HUB's brand-kit logo (the brand's
|
||||||
|
primary/global logo via @brand-hub-logo) — NOT the per-standalone
|
||||||
|
@brand-app-logo, which resolves to this standalone's own logo. -->
|
||||||
|
<a
|
||||||
|
:href="hubRootUrl"
|
||||||
|
class="flex items-center justify-center gap-3 px-3 py-1.5 hover:bg-accent rounded-md transition-colors"
|
||||||
|
:aria-label="t('common.nav.backToHub')"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<img src="@brand-hub-logo" :alt="t('common.nav.backToHub')" class="w-8 h-8 shrink-0" />
|
||||||
|
<span class="text-sm font-medium">{{ t('common.nav.backToHub') }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Logged-out: prominent log-in CTA -->
|
||||||
|
<div v-if="!isAuthenticated">
|
||||||
|
<Separator class="mb-2" />
|
||||||
|
<Button class="w-full" @click="goLogin">
|
||||||
|
<LogIn class="mr-2 h-4 w-4" />
|
||||||
|
{{ t('common.nav.login') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Logged-in: log-out button stays visible without opening the edit popup. -->
|
||||||
|
<div v-else>
|
||||||
|
<Separator class="mb-2" />
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger as-child>
|
||||||
|
<Button variant="destructive" class="w-full">
|
||||||
|
<LogOut class="mr-2 h-4 w-4" />
|
||||||
|
{{ t('common.nav.logOut', 'Log out') }}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Log out of {{ user?.username || 'your account' }}?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{{ t('common.nav.logOutConfirmDescription', "You'll need to sign in again to access your wallet, post in the forum, place orders, or use any feature that needs your account.") }}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{{ t('common.nav.cancel', 'Cancel') }}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
@click="onLogout"
|
||||||
|
>
|
||||||
|
{{ t('common.nav.logOut', 'Log out') }}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Edit-profile popup (pencil button in the identity card) — the full
|
<!-- Edit-profile popup (pencil button in the identity card) — the full
|
||||||
form lives here so the sheet stays scannable. -->
|
form lives here so the sheet stays scannable. Outside the flex root:
|
||||||
|
its content portals to <body>, so it's not part of the sheet flow. -->
|
||||||
<Dialog v-model:open="editProfileOpen">
|
<Dialog v-model:open="editProfileOpen">
|
||||||
<DialogContent class="max-w-md max-h-[90vh] overflow-y-auto overflow-x-hidden">
|
<DialogContent class="max-w-md max-h-[90vh] overflow-y-auto overflow-x-hidden">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,22 @@ export function brandAppLogoAliasEntry(app?: string) {
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hub-logo alias entry. Resolves `@brand-hub-logo` to the brand's
|
||||||
|
* primary/global logo (the hub's logo), independent of which standalone
|
||||||
|
* is building. Unlike {@link brandAppLogoAliasEntry}, this never takes an
|
||||||
|
* `app` argument — the "Back to hub" link in every standalone must point
|
||||||
|
* at the HUB's logo, not the current standalone's own logo. Wire it into
|
||||||
|
* every vite.<app>.config.ts that builds ProfileSheetContent.vue.
|
||||||
|
*/
|
||||||
|
export function brandHubLogoAliasEntry() {
|
||||||
|
const resolved = resolveAppLogo()
|
||||||
|
return {
|
||||||
|
find: /^@brand-hub-logo(\?.*)?$/,
|
||||||
|
replacement: `${resolved}$1`,
|
||||||
|
} as const
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional brand banner — a wide lockup (logo + wordmark in one image)
|
* Optional brand banner — a wide lockup (logo + wordmark in one image)
|
||||||
* that replaces the logo + app-name pair in a standalone's header.
|
* that replaces the logo + app-name pair in a standalone's header.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
function chatHtmlPlugin(): Plugin {
|
function chatHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -107,6 +107,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('chat'),
|
brandAppLogoAliasEntry('chat'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/chat-app/app.config.ts', import.meta.url)) },
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/chat-app/app.config.ts', import.meta.url)) },
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig } from 'vite'
|
||||||
import Inspect from 'vite-plugin-inspect'
|
import Inspect from 'vite-plugin-inspect'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
//
|
//
|
||||||
|
|
@ -53,6 +53,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare alias.
|
// with optional `?url` query) doesn't get shadowed by the bare alias.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry(),
|
brandAppLogoAliasEntry(),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
brandAppBannerAliasEntry,
|
brandAppBannerAliasEntry,
|
||||||
brandAppLogoAliasEntry,
|
brandAppLogoAliasEntry,
|
||||||
brandAssetsPlugin,
|
brandAssetsPlugin,
|
||||||
|
brandHubLogoAliasEntry,
|
||||||
brandManifestName,
|
brandManifestName,
|
||||||
resolveAppBanner,
|
resolveAppBanner,
|
||||||
} from './vite-branding'
|
} from './vite-branding'
|
||||||
|
|
@ -137,6 +138,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('events'),
|
brandAppLogoAliasEntry('events'),
|
||||||
brandAppBannerAliasEntry('events'),
|
brandAppBannerAliasEntry('events'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
function forumHtmlPlugin(): Plugin {
|
function forumHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -107,6 +107,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('forum'),
|
brandAppLogoAliasEntry('forum'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/forum-app/app.config.ts', import.meta.url)) },
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/forum-app/app.config.ts', import.meta.url)) },
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin to rewrite dev server requests to libra.html
|
* Plugin to rewrite dev server requests to libra.html
|
||||||
|
|
@ -114,6 +114,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('libra'),
|
brandAppLogoAliasEntry('libra'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @rollup/plugin-alias is first-match-wins.
|
// ORDER MATTERS — @rollup/plugin-alias is first-match-wins.
|
||||||
// The more specific @/app.config remap must precede the @ prefix
|
// The more specific @/app.config remap must precede the @ prefix
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
function marketHtmlPlugin(): Plugin {
|
function marketHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -107,6 +107,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('market'),
|
brandAppLogoAliasEntry('market'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/market-app/app.config.ts', import.meta.url)) },
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/market-app/app.config.ts', import.meta.url)) },
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
function restaurantHtmlPlugin(): Plugin {
|
function restaurantHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -114,6 +114,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('restaurant'),
|
brandAppLogoAliasEntry('restaurant'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/restaurant-app/app.config.ts', import.meta.url)) },
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/restaurant-app/app.config.ts', import.meta.url)) },
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
function tasksHtmlPlugin(): Plugin {
|
function tasksHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -107,6 +107,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('tasks'),
|
brandAppLogoAliasEntry('tasks'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/tasks-app/app.config.ts', import.meta.url)) },
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/tasks-app/app.config.ts', import.meta.url)) },
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin, brandHubLogoAliasEntry } from './vite-branding'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin to rewrite dev server requests to wallet.html
|
* Plugin to rewrite dev server requests to wallet.html
|
||||||
|
|
@ -113,6 +113,7 @@ export default defineConfig(({ mode }) => ({
|
||||||
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
alias: [
|
alias: [
|
||||||
brandAppLogoAliasEntry('wallet'),
|
brandAppLogoAliasEntry('wallet'),
|
||||||
|
brandHubLogoAliasEntry(),
|
||||||
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/wallet-app/app.config.ts', import.meta.url)) },
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/wallet-app/app.config.ts', import.meta.url)) },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue