Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 46 additions & 52 deletions apps/portal/src/app/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function Header() {
const pathname = usePathname();

return (
<header className="flex w-full flex-col gap-2 border-b bg-background p-4 xl:pb-0 lg:px-8 overflow-hidden">
<header className="flex w-full flex-col gap-2 border-b p-4 xl:pb-0 lg:px-8 lg:pt-5 overflow-hidden bg-card">
{/* Top row */}
<div className="container flex items-center justify-between gap-6">
<div className="flex items-center gap-2">
Expand All @@ -230,7 +230,7 @@ export function Header() {
</Link>
</div>

<div className="flex items-center gap-3">
<div className="flex items-center gap-4">
<div className="hidden xl:block">
<Link
className="text-foreground"
Expand Down Expand Up @@ -269,13 +269,13 @@ export function Header() {
</div>

{/* Bottom row - hidden on mobile */}
<div className="container hidden items-center justify-between gap-6 xl:flex mt-1">
<div className="container hidden items-start justify-between gap-6 xl:flex mt-1">
<nav className="flex grow gap-5">
<ul className="flex flex-row items-center gap-0 mb-1">
<ul className="flex flex-row items-center gap-0 mb-1.5">
{links.map((link) => {
return (
<li
className="flex items-center py-2 relative px-2.5 hover:text-foreground"
className="flex items-center relative"
key={link.href}
onClick={() => {
setShowBurgerMenu(false);
Expand All @@ -284,57 +284,51 @@ export function Header() {
setShowBurgerMenu(false);
}}
>
<NavLink href={link.href} name={link.name} />
<NavLink
href={link.href}
name={link.name}
className="py-2.5 px-3 hover:bg-accent rounded-lg hover:text-foreground font-normal"
/>
{pathname?.startsWith(link.href) && (
<div className="bg-violet-700 h-[2px] inset-x-0 rounded-full absolute -bottom-1" />
<div className="bg-foreground h-[2px] inset-x-0 rounded-full absolute -bottom-1.5" />
)}
Comment on lines 292 to 294
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix active-state matching to avoid false positives (e.g., "/reference" vs "/references").

Current startsWith checks will incorrectly mark parent routes active. Use exact or segment-aware matching.

Apply these diffs:

-                  {pathname?.startsWith(link.href) && (
+                  {(pathname === link.href || pathname?.startsWith(`${link.href}/`)) && (
                     <div className="bg-foreground h-[2px] inset-x-0 rounded-full absolute -bottom-1.5" />
                   )}
-        pathname?.startsWith(props.href)
-          ? "text-foreground"
-          : "text-muted-foreground",
+        pathname === props.href || pathname?.startsWith(`${props.href}/`)
+          ? "text-foreground"
+          : "text-muted-foreground",

Also applies to: 525-531

🤖 Prompt for AI Agents
In apps/portal/src/app/Header.tsx around lines 292-294 (and similarly lines
525-531), the current active-state uses pathname?.startsWith(link.href) which
creates false positives like "/reference" matching "/references"; change the
condition to a segment-aware check such as normalizing trailing slashes and
using (pathname === link.href || pathname.startsWith(link.href + '/')) so the
link is only active for exact match or deeper nested routes, and apply the same
change to the other occurrence.

</li>
);
})}
</ul>
</nav>

<div className="flex items-center gap-3">
<div className="px-1">
<DropdownLinks
category="AI"
links={aiLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
</div>
<div className="px-1">
<DropdownLinks
category="SDKs"
links={sdkLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
</div>
<div className="px-1">
<DropdownLinks
category="APIs"
links={apisLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
</div>

<div className="px-1">
<DropdownLinks
category="Tools"
links={toolLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
</div>
<div className="flex items-center">
<DropdownLinks
category="AI"
links={aiLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
<DropdownLinks
category="SDKs"
links={sdkLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
<DropdownLinks
category="APIs"
links={apisLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>

<div className="px-1">
<DropdownLinks
category="Support"
links={supportLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
</div>
<DropdownLinks
category="Tools"
links={toolLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>
<DropdownLinks
category="Support"
links={supportLinks}
onLinkClick={() => setShowBurgerMenu(false)}
/>

<NavLink
href="/changelog"
className="px-3 py-2.5 hover:bg-accent hover:text-foreground rounded-lg"
name="Changelog"
onClick={() => {
setShowBurgerMenu(false);
Expand Down Expand Up @@ -448,31 +442,29 @@ function DropdownLinks(props: {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="inline-flex items-center gap-1 p-0 font-medium text-muted-foreground text-sm hover:bg-transparent hover:text-foreground"
className="inline-flex items-center gap-1.5 font-normal text-muted-foreground text-sm hover:bg-accent hover:text-foreground rounded-lg px-3 py-2.5"
variant="ghost"
>
{props.category}
<ChevronDownIcon className="size-4 text-muted-foreground opacity-70" />
<ChevronDownIcon className="size-3.5 text-muted-foreground opacity-70" />
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent
className="flex flex-col gap-1 bg-card p-3"
style={{
minWidth: "150px",
}}
className="flex flex-col gap-1 bg-card p-1 rounded-xl min-w-[200px]"
sideOffset={14}
>
{props.links.map((info) => {
return (
<DropdownMenuItem asChild key={info.name}>
<div
className={clsx(
"relative flex cursor-pointer gap-2 font-medium text-foreground",
"relative flex cursor-pointer gap-3 font-medium text-foreground !rounded-lg px-3 py-2",
"hover:bg-accent",
)}
>
{info.icon && (
<info.icon className="size-5 text-foreground" />
<info.icon className="size-5 text-muted-foreground" />
)}
<Link
className="before:absolute before:inset-0"
Expand Down Expand Up @@ -524,6 +516,7 @@ function NavLink(props: {
name: string;
onClick?: () => void;
icon?: React.FC<{ className?: string }>;
className?: string;
}) {
const pathname = usePathname();
return (
Expand All @@ -534,6 +527,7 @@ function NavLink(props: {
? "text-foreground"
: "text-muted-foreground",
props.icon ? "flex flex-row gap-3" : "",
props.className,
)}
href={props.href}
onClick={props.onClick}
Expand Down
2 changes: 1 addition & 1 deletion apps/portal/src/app/changelog/[slug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default function Layout(props: { children: React.ReactNode }) {
return (
<main className="container max-w-3xl py-10" data-noindex>
<main className="container max-w-4xl py-10" data-noindex>
{props.children}
</main>
);
Expand Down
4 changes: 2 additions & 2 deletions apps/portal/src/app/changelog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default async function Page(props: {

return (
<div className="changelog-page">
<Button asChild>
<Button asChild variant="ghost">
<Link
className="-translate-x-1/4 !p-2 !text-muted-foreground hover:!text-foreground mb-4 bg-transparent"
href="/changelog"
Expand Down Expand Up @@ -64,7 +64,7 @@ export default async function Page(props: {
})}
</div>

<div className="mb-8 border-t-2" />
<div className="mb-8 border-t border-dashed" />

{ReactHtmlParser(data.html || "", {
transform,
Expand Down
6 changes: 2 additions & 4 deletions apps/portal/src/app/changelog/components/Author.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ export function Author(props: { name: string; profileImage?: string | null }) {
{props.profileImage && (
<img
alt=""
className="size-8 rounded-[50%] border"
className="size-6 rounded-[50%] border"
src={props.profileImage}
/>
)}
<span className="font-medium text-muted-foreground text-sm">
{props.name}
</span>
<span className="text-muted-foreground text-sm">{props.name}</span>
</div>
);
}
11 changes: 0 additions & 11 deletions apps/portal/src/app/changelog/components/ChangeLogIndexTOC.tsx

This file was deleted.

12 changes: 3 additions & 9 deletions apps/portal/src/app/changelog/components/RenderData.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { formatDistance } from "date-fns";
import { formatDate } from "date-fns";
import { cn } from "../../../lib/utils";

export function RenderDate(props: { iso: string; className?: string }) {
return (
<time
className={cn(
"font-medium text-muted-foreground text-sm",
props.className,
)}
dateTime={props.iso}
>
{formatDistance(new Date(props.iso), new Date())} ago
<time className={cn("text-sm", props.className)} dateTime={props.iso}>
{formatDate(new Date(props.iso), "MMM d, yyyy")}
</time>
);
}
87 changes: 41 additions & 46 deletions apps/portal/src/app/changelog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { createMetadata } from "@doc";
import GithubSlugger from "github-slugger";
import { PlusIcon } from "lucide-react";
import Link from "next/link";
import ReactHtmlParser from "react-html-parser";
import { Heading } from "@/components/Document";
import { Author } from "./components/Author";
import { ChangelogIndexTOC } from "./components/ChangeLogIndexTOC";
import { RenderDate } from "./components/RenderData";
import { fetchChangeLogs, fetchPost } from "./ghost";
import { transform } from "./utils/transform";
Expand All @@ -23,12 +20,15 @@ export const metadata = createMetadata({

export default async function Page() {
return (
<main className="container" data-noindex>
<div className="flex justify-between gap-16">
<div className="w-full max-w-[850px] grow-0 ">
<PageContent />
<main data-noindex>
<div className="border-b py-12 border-dashed">
<div className="container">
<h1 className="font-semibold text-4xl tracking-tight">Changelog</h1>
</div>
<ChangelogIndexTOC />
</div>

<div className="container">
<PageContent />
</div>
</main>
);
Expand All @@ -39,53 +39,48 @@ async function PageContent() {
const slugger = new GithubSlugger();

return (
<div className="changelog-page py-6">
<h1 className="mb-10 font-semibold text-3xl tracking-tighter">
Changelog
</h1>

<div className="flex flex-col gap-10 xl:border-l-2 xl:pl-12">
<div className="changelog-page overflow-hidden">
<div className="flex flex-col gap-10 xl:border-l border-dashed xl:pl-16 max-w-4xl ml-auto pb-20">
{posts.map((post) => {
return (
<div className="relative pb-10" key={post.id}>
<div className="mb-2 flex items-center gap-5">
<div className="relative pt-10" key={post.id}>
<div className="-left-16 -translate-x-full absolute top-12 hidden xl:flex gap-5 ml-3 items-center">
{post.published_at && (
<RenderDate className="text-base" iso={post.published_at} />
<div className="hidden xl:block text-sm text-muted-foreground">
<RenderDate iso={post.published_at} />
</div>
)}
<div className="flex gap-5">
{post.authors?.map((author) => {
return (
<Author
key={author.id}
name={author.name || ""}
profileImage={author.profile_image}
/>
);
})}
<div className="size-6 rounded-[50%] border bg-background flex items-center justify-center">
<div className="size-2 bg-blue-500 rounded-full" />
</div>
</div>

<div className="-left-12 -translate-x-1/2 absolute top-12 hidden size-7 items-center justify-center rounded-[50%] bg-foreground md:size-10 xl:flex">
<PlusIcon className="size-6 text-background" />
</div>

<div>
<Heading
anchorClassName="[&>a]:hidden m-0 border-b pb-5 mb-5"
anchorId={slugger.slug(post.title || "")}
// eslint-disable-next-line tailwindcss/no-custom-classname
className="changelog-title"
level={2}
>
<Link
className="!text-foreground font-bold text-3xl tracking-tighter hover:underline md:text-4xl xl:text-5xl"
href={`/changelog/${post.slug}`}
>
{post.title}
</Link>
</Heading>
{post.published_at && (
<div className="xl:hidden inline-flex rounded-full border px-3 py-2 mb-4 text-muted-foreground text-xs bg-card">
<RenderDate iso={post.published_at} />
</div>
)}

<RenderChangelogContent slug={post.slug} />
<div className="space-y-3 mb-5">
<Heading
anchorClassName="[&>a]:hidden m-0"
anchorId={slugger.slug(post.title || "")}
// eslint-disable-next-line tailwindcss/no-custom-classname
className="changelog-title"
level={2}
>
<Link
className="!text-foreground font-semibold text-3xl md:text-4xl tracking-tight hover:underline !leading-tight"
href={`/changelog/${post.slug}`}
>
{post.title}
</Link>
</Heading>
</div>
<div className="text-muted-foreground">
<RenderChangelogContent slug={post.slug} />
</div>
</div>
</div>
);
Expand Down
Loading
Loading