{/* Logo and primary navigation */}
@@ -42,6 +36,6 @@ export function Navbar(user: { name: string, email: string, image?: string }) {
-
+
);
}
diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx
index 9aad8e6..b19dc92 100644
--- a/components/app-sidebar.tsx
+++ b/components/app-sidebar.tsx
@@ -57,7 +57,7 @@ export function AppSidebar({ userProject, allProjects, user }: {
navSecondary: [
{
title: "Support",
- url: "/suuport",
+ url: "/support",
icon: (
diff --git a/components/nav-main.tsx b/components/nav-main.tsx
index 31da167..d03511e 100644
--- a/components/nav-main.tsx
+++ b/components/nav-main.tsx
@@ -1,5 +1,7 @@
"use client"
+import Link from "next/link"
+import { usePathname } from "next/navigation"
import {
Collapsible,
CollapsibleContent,
@@ -34,46 +36,50 @@ export function NavMain({
}[]
}[]
}) {
+ const pathname = usePathname()
+
return (
{title}
- {items.map((item) => (
-
-
-
-
- {item.icon}
- {item.title}
-
-
- {item.items?.length ? (
- <>
-
-
-
- Toggle
-
-
-
-
- {item.items?.map((subItem) => (
-
-
-
- {subItem.title}
-
-
-
- ))}
-
-
- >
- ) : null}
-
-
- ))}
+ {items.map((item) => {
+ const isActive = pathname === item.url || pathname.startsWith(`${item.url}/`)
+ return (
+
+
+
+
+ {item.icon}
+ {item.title}
+
+
+ {item.items?.length ? (
+ <>
+
+
+
+ Toggle
+
+
+
+
+ {item.items?.map((subItem) => (
+
+
+
+ {subItem.title}
+
+
+
+ ))}
+
+
+ >
+ ) : null}
+
+
+ )
+ })}
)
diff --git a/components/nav-secondary.tsx b/components/nav-secondary.tsx
index 3afbe30..13abf29 100644
--- a/components/nav-secondary.tsx
+++ b/components/nav-secondary.tsx
@@ -1,6 +1,7 @@
"use client"
import * as React from "react"
+import Link from "next/link"
import {
SidebarGroup,
@@ -27,10 +28,10 @@ export function NavSecondary({
{items.map((item) => (
-
+
{item.icon}
{item.title}
-
+
))}
diff --git a/db/schema.ts b/db/schema.ts
index 6c8656e..1dd9a20 100644
--- a/db/schema.ts
+++ b/db/schema.ts
@@ -150,7 +150,11 @@ export const feature = pgTable("feature", {
.references((): AnyPgColumn => comment.id, { onDelete: "set null" }),
createdAt: timestamp("created_at", {withTimezone:true}).defaultNow().notNull(),
-}, (t) => [index("project_feature_idx").on(t.projectId)])
+}, (t) => [
+ index("project_feature_idx").on(t.projectId),
+ index("feature_project_status_created_idx").on(t.projectId, t.status, t.createdAt),
+ index("feature_project_upvotes_idx").on(t.projectId, t.upvotesCount),
+])
export const upvote = pgTable(
"upvotes",
@@ -248,7 +252,9 @@ export const changelogs = pgTable("changelogs", {
content: text("content").notNull(), // HTML from Tiptap
category: changelogCategoryEnum("category").default("new_feature").notNull(),
createdAt: timestamp("created_at", {withTimezone:true}).defaultNow().notNull(),
-});
+}, (t) => [
+ index("changelogs_project_created_idx").on(t.projectId, t.createdAt),
+]);
export const widgetDailyStats = pgTable("widget_daily_stats", {
projectId: text("project_id").notNull(),
@@ -286,6 +292,7 @@ export const featureTags = pgTable("feature_tags", {
.references(() => tag.id, { onDelete: "cascade" }),
}, (t) => [
primaryKey({ columns: [t.featureId, t.tagId] }),
+ index("feature_tags_tag_idx").on(t.tagId),
]);
export const featureTagsRelations = relations(featureTags, ({ one }) => ({
diff --git a/drizzle/0033_quick_perf_indexes.sql b/drizzle/0033_quick_perf_indexes.sql
new file mode 100644
index 0000000..22e5b94
--- /dev/null
+++ b/drizzle/0033_quick_perf_indexes.sql
@@ -0,0 +1,4 @@
+CREATE INDEX IF NOT EXISTS "feature_project_status_created_idx" ON "feature" USING btree ("project_id","status","created_at");
+CREATE INDEX IF NOT EXISTS "feature_project_upvotes_idx" ON "feature" USING btree ("project_id","upvotes_count");
+CREATE INDEX IF NOT EXISTS "feature_tags_tag_idx" ON "feature_tags" USING btree ("tag_id");
+CREATE INDEX IF NOT EXISTS "changelogs_project_created_idx" ON "changelogs" USING btree ("project_id","created_at");
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 63cb279..bff51a0 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -232,6 +232,13 @@
"when": 1773921732525,
"tag": "0032_dashing_rafael_vega",
"breakpoints": true
+ },
+ {
+ "idx": 33,
+ "version": "7",
+ "when": 1773998257390,
+ "tag": "0033_quick_perf_indexes",
+ "breakpoints": true
}
]
-}
\ No newline at end of file
+}
diff --git a/lib/server/session.ts b/lib/server/session.ts
new file mode 100644
index 0000000..3505a41
--- /dev/null
+++ b/lib/server/session.ts
@@ -0,0 +1,9 @@
+import { cache } from "react"
+import { headers } from "next/headers"
+import { auth } from "@/lib/auth"
+
+export const getServerSession = cache(async () => {
+ return auth.api.getSession({
+ headers: await headers(),
+ })
+})
diff --git a/next.config.ts b/next.config.ts
index 8af9749..d375843 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,23 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- /* config options here */
+ poweredByHeader: false,
+ compress: true,
+ experimental: {
+ optimizePackageImports: [
+ "lucide-react",
+ "@tiptap/react",
+ "@tiptap/starter-kit",
+ "@tiptap/extension-link",
+ "@tiptap/extension-image",
+ "@tiptap/extension-placeholder",
+ "@tiptap/extension-highlight",
+ "@tiptap/extension-underline",
+ "@tiptap/extension-text-align",
+ "react-icons",
+ "recharts",
+ ],
+ },
async headers() {
return [
{