Show store logo in sidebar with Amplified by Forte branding
Sidebar header now loads the store logo from the files API and displays it scaled to fit. Below the logo: "Amplified by Forte" in subtle text. Falls back to store name as text if no logo is uploaded. Logo fetched via authenticated request with blob URL and proper cleanup.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { createFileRoute, Outlet, Link, redirect, useRouter } from '@tanstack/react-router'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect } from 'react'
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { api } from '@/lib/api-client'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
import { myPermissionsOptions } from '@/api/rbac'
|
||||
import { Avatar } from '@/components/shared/avatar-upload'
|
||||
@@ -17,6 +19,49 @@ export const Route = createFileRoute('/_authenticated')({
|
||||
component: AuthenticatedLayout,
|
||||
})
|
||||
|
||||
function StoreLogo() {
|
||||
const token = useAuthStore((s) => s.token)
|
||||
const [src, setSrc] = useState<string | null>(null)
|
||||
|
||||
const { data: storeData } = useQuery(queryOptions({
|
||||
queryKey: ['store'],
|
||||
queryFn: () => api.get<{ id: string; name: string }>('/v1/store'),
|
||||
enabled: !!token,
|
||||
}))
|
||||
|
||||
const { data: filesData } = useQuery(queryOptions({
|
||||
queryKey: ['files', 'company', storeData?.id ?? ''],
|
||||
queryFn: () => api.get<{ data: { id: string; path: string }[] }>('/v1/files', { entityType: 'company', entityId: storeData?.id }),
|
||||
enabled: !!storeData?.id,
|
||||
}))
|
||||
|
||||
const logoFile = filesData?.data?.find((f) => f.path.includes('/logo-'))
|
||||
|
||||
useEffect(() => {
|
||||
if (!logoFile || !token) { setSrc(null); return }
|
||||
let cancelled = false
|
||||
let blobUrl: string | null = null
|
||||
async function load() {
|
||||
try {
|
||||
const res = await fetch(`/v1/files/serve/${logoFile!.path}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
if (!res.ok || cancelled) return
|
||||
const blob = await res.blob()
|
||||
if (!cancelled) { blobUrl = URL.createObjectURL(blob); setSrc(blobUrl) }
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
load()
|
||||
return () => { cancelled = true; if (blobUrl) URL.revokeObjectURL(blobUrl) }
|
||||
}, [logoFile?.path, token])
|
||||
|
||||
if (src) {
|
||||
return <img src={src} alt={storeData?.name ?? 'Store'} className="max-h-10 max-w-[180px] object-contain" />
|
||||
}
|
||||
|
||||
return <h2 className="text-lg font-semibold text-sidebar-foreground">{storeData?.name ?? 'Forte'}</h2>
|
||||
}
|
||||
|
||||
function NavLink({ to, icon, label }: { to: string; icon: React.ReactNode; label: string }) {
|
||||
return (
|
||||
<Link
|
||||
@@ -64,8 +109,9 @@ function AuthenticatedLayout() {
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="flex">
|
||||
<nav className="w-56 border-r border-border bg-sidebar min-h-screen flex flex-col">
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-semibold text-sidebar-foreground">Forte</h2>
|
||||
<div className="p-4 space-y-1">
|
||||
<StoreLogo />
|
||||
<p className="text-[10px] text-sidebar-foreground/40 tracking-wide">Amplified by <span className="font-semibold text-sidebar-foreground/60">Forte</span></p>
|
||||
</div>
|
||||
|
||||
{/* Sidebar links use `as any` on search because TanStack Router
|
||||
|
||||
Reference in New Issue
Block a user