Skip to content
Open
165 changes: 107 additions & 58 deletions apps/sim/app/(home)/components/navbar/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import Image from 'next/image'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
import { GithubOutlineIcon } from '@/components/icons'
import { useSession } from '@/lib/auth/auth-client'
import { cn } from '@/lib/core/utils/cn'
import {
BlogDropdown,
Expand Down Expand Up @@ -40,6 +42,12 @@ interface NavbarProps {

export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps) {
const brand = getBrandConfig()
const searchParams = useSearchParams()
const { data: session, isPending: isSessionPending } = useSession()
const isAuthenticated = Boolean(session?.user?.id)
const isBrowsingHome = searchParams.has('home')
const useHomeLinks = isAuthenticated || isBrowsingHome
const logoHref = useHomeLinks ? '/?home' : '/'
const [activeDropdown, setActiveDropdown] = useState<DropdownId>(null)
const [hoveredLink, setHoveredLink] = useState<string | null>(null)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
Expand Down Expand Up @@ -92,7 +100,7 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
itemScope
itemType='https://schema.org/SiteNavigationElement'
>
<Link href='/' className={LOGO_CELL} aria-label={`${brand.name} home`} itemProp='url'>
<Link href={logoHref} className={LOGO_CELL} aria-label={`${brand.name} home`} itemProp='url'>
<span itemProp='name' className='sr-only'>
{brand.name}
</span>
Expand Down Expand Up @@ -121,7 +129,9 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
{!logoOnly && (
<>
<ul className='mt-[0.75px] hidden lg:flex'>
{NAV_LINKS.map(({ label, href, external, icon, dropdown }) => {
{NAV_LINKS.map(({ label, href: rawHref, external, icon, dropdown }) => {
const href =
useHomeLinks && rawHref.startsWith('/#') ? `/?home${rawHref.slice(1)}` : rawHref
const hasDropdown = !!dropdown
const isActive = hasDropdown && activeDropdown === dropdown
const isThisHovered = hoveredLink === label
Expand Down Expand Up @@ -206,21 +216,38 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps

<div className='hidden flex-1 lg:block' />

<div className='hidden items-center gap-[8px] pr-[80px] pl-[20px] lg:flex'>
<Link
href='/login'
className='inline-flex h-[30px] items-center rounded-[5px] border border-[#3d3d3d] px-[9px] text-[#ECECEC] text-[13.5px] transition-colors hover:bg-[#2A2A2A]'
aria-label='Log in'
>
Log in
</Link>
<Link
href='/signup'
className='inline-flex h-[30px] items-center gap-[7px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] px-[9px] text-[13.5px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
aria-label='Get started with Sim'
>
Get started
</Link>
<div
className={cn(
'hidden items-center gap-[8px] pr-[80px] pl-[20px] lg:flex',
isSessionPending && 'invisible'
)}
>
{isAuthenticated ? (
<Link
href='/workspace'
className='inline-flex h-[30px] items-center gap-[7px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] px-[9px] text-[13.5px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
aria-label='Go to app'
>
Go to App
</Link>
) : (
<>
<Link
href='/login'
className='inline-flex h-[30px] items-center rounded-[5px] border border-[#3d3d3d] px-[9px] text-[#ECECEC] text-[13.5px] transition-colors hover:bg-[#2A2A2A]'
aria-label='Log in'
>
Log in
</Link>
<Link
href='/signup'
className='inline-flex h-[30px] items-center gap-[7px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] px-[9px] text-[13.5px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
aria-label='Get started with Sim'
>
Get started
</Link>
</>
)}
</div>

<div className='flex flex-1 items-center justify-end pr-[20px] lg:hidden'>
Expand All @@ -242,30 +269,34 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
)}
>
<ul className='flex flex-col'>
{NAV_LINKS.map(({ label, href, external }) => (
<li key={label} className='border-[#2A2A2A] border-b'>
{external ? (
<a
href={href}
target='_blank'
rel='noopener noreferrer'
className='flex items-center justify-between px-[20px] py-[14px] text-[#ECECEC] transition-colors active:bg-[#2A2A2A]'
onClick={() => setMobileMenuOpen(false)}
>
{label}
<ExternalArrowIcon />
</a>
) : (
<Link
href={href}
className='flex items-center px-[20px] py-[14px] text-[#ECECEC] transition-colors active:bg-[#2A2A2A]'
onClick={() => setMobileMenuOpen(false)}
>
{label}
</Link>
)}
</li>
))}
{NAV_LINKS.map(({ label, href: rawHref, external }) => {
const href =
useHomeLinks && rawHref.startsWith('/#') ? `/?home${rawHref.slice(1)}` : rawHref
return (
<li key={label} className='border-[#2A2A2A] border-b'>
{external ? (
<a
href={href}
target='_blank'
rel='noopener noreferrer'
className='flex items-center justify-between px-[20px] py-[14px] text-[#ECECEC] transition-colors active:bg-[#2A2A2A]'
onClick={() => setMobileMenuOpen(false)}
>
{label}
<ExternalArrowIcon />
</a>
) : (
<Link
href={href}
className='flex items-center px-[20px] py-[14px] text-[#ECECEC] transition-colors active:bg-[#2A2A2A]'
onClick={() => setMobileMenuOpen(false)}
>
{label}
</Link>
)}
</li>
)
})}
<li className='border-[#2A2A2A] border-b'>
<a
href='https://github.com/simstudioai/sim'
Expand All @@ -280,23 +311,41 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
</li>
</ul>

<div className='mt-auto flex flex-col gap-[10px] p-[20px]'>
<Link
href='/login'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[#3d3d3d] text-[#ECECEC] text-[14px] transition-colors active:bg-[#2A2A2A]'
onClick={() => setMobileMenuOpen(false)}
aria-label='Log in'
>
Log in
</Link>
<Link
href='/signup'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] text-[14px] text-black transition-colors active:bg-[#E0E0E0]'
onClick={() => setMobileMenuOpen(false)}
aria-label='Get started with Sim'
>
Get started
</Link>
<div
className={cn(
'mt-auto flex flex-col gap-[10px] p-[20px]',
isSessionPending && 'invisible'
)}
>
{isAuthenticated ? (
<Link
href='/workspace'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] text-[14px] text-black transition-colors active:bg-[#E0E0E0]'
onClick={() => setMobileMenuOpen(false)}
aria-label='Go to app'
>
Go to App
</Link>
) : (
<>
<Link
href='/login'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[#3d3d3d] text-[#ECECEC] text-[14px] transition-colors active:bg-[#2A2A2A]'
onClick={() => setMobileMenuOpen(false)}
aria-label='Log in'
>
Log in
</Link>
<Link
href='/signup'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] text-[14px] text-black transition-colors active:bg-[#E0E0E0]'
onClick={() => setMobileMenuOpen(false)}
aria-label='Get started with Sim'
>
Get started
</Link>
</>
)}
</div>
</div>
</>
Expand Down
2 changes: 0 additions & 2 deletions apps/sim/app/(landing)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export default async function Page({ params }: { params: Promise<{ slug: string
sizes='(max-width: 768px) 100vw, 450px'
priority
itemProp='image'
unoptimized
/>
</div>
</div>
Expand Down Expand Up @@ -144,7 +143,6 @@ export default async function Page({ params }: { params: Promise<{ slug: string
className='h-[160px] w-full object-cover'
sizes='(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw'
loading='lazy'
unoptimized
/>
<div className='p-3'>
<div className='mb-1 text-[#999] text-xs'>
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/(landing)/blog/authors/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export default async function AuthorPage({ params }: { params: Promise<{ id: str
width={600}
height={315}
className='h-[160px] w-full object-cover transition-transform group-hover:scale-[1.02]'
unoptimized
/>
<div className='p-3'>
<div className='mb-1 text-[#999] text-xs'>
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/(landing)/blog/post-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export function PostGrid({ posts }: { posts: Post[] }) {
src={p.ogImage}
alt={p.title}
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
unoptimized
priority={index < 6}
loading={index < 6 ? undefined : 'lazy'}
fill
Expand Down
114 changes: 73 additions & 41 deletions apps/sim/app/(landing)/components/nav/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { createLogger } from '@sim/logger'
import { ArrowRight, ChevronRight } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useRouter, useSearchParams } from 'next/navigation'
import { GithubIcon } from '@/components/icons'
import { useSession } from '@/lib/auth/auth-client'
import { isHosted } from '@/lib/core/config/feature-flags'
import { getFormattedGitHubStars } from '@/app/(landing)/actions/github'
import { useBrandConfig } from '@/ee/whitelabeling'
Expand All @@ -26,6 +27,12 @@ export default function Nav({ hideAuthButtons = false, variant = 'landing' }: Na
const router = useRouter()
const brand = useBrandConfig()
const buttonClass = useBrandedButtonClass()
const searchParams = useSearchParams()
const { data: session, isPending: isSessionPending } = useSession()
const isAuthenticated = Boolean(session?.user?.id)
const isBrowsingHome = searchParams.has('home')
const useHomeLinks = isAuthenticated || isBrowsingHome
const logoHref = useHomeLinks ? '/?home' : '/'

useEffect(() => {
if (variant !== 'landing') return
Expand Down Expand Up @@ -72,7 +79,7 @@ export default function Nav({ hideAuthButtons = false, variant = 'landing' }: Na
</li>
<li>
<Link
href='/?from=nav#pricing'
href={useHomeLinks ? '/?home#pricing' : '/#pricing'}
className='text-[16px] text-muted-foreground transition-colors hover:text-foreground'
scroll={true}
>
Expand Down Expand Up @@ -124,7 +131,7 @@ export default function Nav({ hideAuthButtons = false, variant = 'landing' }: Na
itemType='https://schema.org/SiteNavigationElement'
>
<div className='flex items-center gap-[34px]'>
<Link href='/?from=nav' aria-label={`${brand.name} home`} itemProp='url'>
<Link href={logoHref} aria-label={`${brand.name} home`} itemProp='url'>
<span itemProp='name' className='sr-only'>
{brand.name} Home
</span>
Expand Down Expand Up @@ -162,45 +169,70 @@ export default function Nav({ hideAuthButtons = false, variant = 'landing' }: Na

{/* Auth Buttons - show only when hosted, regardless of variant */}
{!hideAuthButtons && isHosted && (
<div className='flex items-center justify-center gap-[16px] pt-[1.5px]'>
<button
onClick={handleLoginClick}
onMouseEnter={() => setIsLoginHovered(true)}
onMouseLeave={() => setIsLoginHovered(false)}
className='group hidden text-[#2E2E2E] text-[16px] transition-colors hover:text-foreground md:block'
type='button'
aria-label='Log in to your account'
>
<span className='flex items-center gap-1'>
Log in
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
{isLoginHovered ? (
<ArrowRight className='h-4 w-4' aria-hidden='true' />
) : (
<ChevronRight className='h-4 w-4' aria-hidden='true' />
)}
</span>
</span>
</button>
<Link
href='/signup'
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={`${buttonClass} group inline-flex items-center justify-center gap-2 rounded-[10px] py-[6px] pr-[10px] pl-[12px] text-[15px] text-white transition-all`}
aria-label='Get started with Sim - Sign up for free'
prefetch={true}
>
<span className='flex items-center gap-1'>
Get started
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
{isHovered ? (
<ArrowRight className='h-4 w-4' aria-hidden='true' />
) : (
<ChevronRight className='h-4 w-4' aria-hidden='true' />
)}
<div
className={`flex items-center justify-center gap-[16px] pt-[1.5px]${isSessionPending ? ' invisible' : ''}`}
>
{isAuthenticated ? (
<Link
href='/workspace'
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={`${buttonClass} group inline-flex items-center justify-center gap-2 rounded-[10px] py-[6px] pr-[10px] pl-[12px] text-[15px] text-white transition-all`}
aria-label='Go to app'
>
<span className='flex items-center gap-1'>
Go to App
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
{isHovered ? (
<ArrowRight className='h-4 w-4' aria-hidden='true' />
) : (
<ChevronRight className='h-4 w-4' aria-hidden='true' />
)}
</span>
</span>
</span>
</Link>
</Link>
) : (
<>
<button
onClick={handleLoginClick}
onMouseEnter={() => setIsLoginHovered(true)}
onMouseLeave={() => setIsLoginHovered(false)}
className='group hidden text-[#2E2E2E] text-[16px] transition-colors hover:text-foreground md:block'
type='button'
aria-label='Log in to your account'
>
<span className='flex items-center gap-1'>
Log in
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
{isLoginHovered ? (
<ArrowRight className='h-4 w-4' aria-hidden='true' />
) : (
<ChevronRight className='h-4 w-4' aria-hidden='true' />
)}
</span>
</span>
</button>
<Link
href='/signup'
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={`${buttonClass} group inline-flex items-center justify-center gap-2 rounded-[10px] py-[6px] pr-[10px] pl-[12px] text-[15px] text-white transition-all`}
aria-label='Get started with Sim - Sign up for free'
prefetch={true}
>
<span className='flex items-center gap-1'>
Get started
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
{isHovered ? (
<ArrowRight className='h-4 w-4' aria-hidden='true' />
) : (
<ChevronRight className='h-4 w-4' aria-hidden='true' />
)}
</span>
</span>
</Link>
</>
)}
</div>
)}
</nav>
Expand Down
Loading