Skip to content
Open
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
89 changes: 47 additions & 42 deletions app/[owner]/[repo]/[postNumber]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { CommentThreadClient } from "./comment-thread-client"
import { PostComposer } from "./post-composer"
import { PostHeader } from "./post-header"
import { PostMetadataProvider } from "./post-metadata-context"
import { PrivatePostGate } from "./private-post-gate"

const githubCompareSchema = z.object({
ahead_by: z.number(),
Expand Down Expand Up @@ -156,6 +157,7 @@ export default async function PostPage({
authorId: posts.authorId,
createdAt: posts.createdAt,
updatedAt: posts.updatedAt,
visibility: posts.visibility,
gitContexts: posts.gitContexts,
category: {
id: categories.id,
Expand Down Expand Up @@ -322,48 +324,51 @@ export default async function PostPage({
.find((c) => c.authorId.startsWith("llm_"))?.authorId

return (
<PostMetadataProvider
archivedRefs={archivedRefs}
authorId={post.authorId}
categories={repoCategories}
initialCategory={category?.id ? category : null}
initialGitContext={gitContext}
initialTitle={post.title}
owner={owner}
postId={post.id}
repo={repo}
staleInfo={staleInfo}
>
<Container>
<div className="min-h-body-min-height">
<PostHeader owner={owner} postNumber={postNumber} repo={repo} />

<div className="mt-8 space-y-4">
<CommentThreadClient
askingOptions={askingOptions}
authorsById={authorsById}
commentNumbers={commentNumbers}
comments={postComments}
mentions={postMentions}
owner={owner}
reactions={postReactions}
repo={repo}
rootCommentId={post.rootCommentId}
/>
<PrivatePostGate authorId={post.authorId} visibility={post.visibility}>
<PostMetadataProvider
archivedRefs={archivedRefs}
authorId={post.authorId}
categories={repoCategories}
initialCategory={category?.id ? category : null}
initialGitContext={gitContext}
initialTitle={post.title}
owner={owner}
postId={post.id}
repo={repo}
staleInfo={staleInfo}
visibility={post.visibility}
>
<Container>
<div className="min-h-body-min-height">
<PostHeader owner={owner} postNumber={postNumber} repo={repo} />

<div className="mt-8 space-y-4">
<CommentThreadClient
askingOptions={askingOptions}
authorsById={authorsById}
commentNumbers={commentNumbers}
comments={postComments}
mentions={postMentions}
owner={owner}
reactions={postReactions}
repo={repo}
rootCommentId={post.rootCommentId}
/>
</div>
</div>
</div>

<hr className="divider-md my-14 h-px border-0" />
<p className="relative -top-14 left-1/2 max-w-max -translate-x-1/2 -translate-y-1/2 bg-background px-2 text-sm">
END OF POST
</p>

<PostComposer
askingOptions={askingOptions}
defaultLlmId={lastLlmAuthorId}
postId={post.id}
/>
</Container>
</PostMetadataProvider>

<hr className="divider-md my-14 h-px border-0" />
<p className="relative -top-14 left-1/2 max-w-max -translate-x-1/2 -translate-y-1/2 bg-background px-2 text-sm">
END OF POST
</p>

<PostComposer
askingOptions={askingOptions}
defaultLlmId={lastLlmAuthorId}
postId={post.id}
/>
</Container>
</PostMetadataProvider>
</PrivatePostGate>
)
}
1 change: 1 addition & 0 deletions app/[owner]/[repo]/[postNumber]/post-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function PostComposer({
}}
options={{ asking: askingOptions }}
placeholder="Follow up"
showVisibility={false}
storageKey={`post-composer:${postId}:${threadCommentId ?? "main"}`}
/>
</div>
Expand Down
29 changes: 19 additions & 10 deletions app/[owner]/[repo]/[postNumber]/post-header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { ChevronRight, TagIcon } from "lucide-react"
import { ChevronRight, LockIcon, TagIcon } from "lucide-react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { type ReactNode, useTransition } from "react"
Expand All @@ -24,7 +24,8 @@ export function PostHeader({
repo: string
postNumber: number
}) {
const { title, category, gitContext, archivedRefs } = usePostMetadata()
const { title, category, gitContext, archivedRefs, visibility } =
usePostMetadata()
const hasArchivedRefs = archivedRefs.length > 0

return (
Expand All @@ -50,14 +51,22 @@ export function PostHeader({
)}
</div>

{typeof title === "string" ? (
<Title className="mt-1">{title || `Post #${postNumber}`}</Title>
) : (
<h1 className="relative mt-1 overflow-hidden font-medium text-2xl text-muted-foreground">
<span>Generating title...</span>
<span className="absolute inset-0 -translate-x-full animate-[shimmer_2s_infinite] bg-linear-to-r from-transparent via-white/20 to-transparent" />
</h1>
)}
<div className="mt-1 flex items-center gap-2">
{typeof title === "string" ? (
<Title>{title || `Post #${postNumber}`}</Title>
) : (
<h1 className="relative overflow-hidden font-medium text-2xl text-muted-foreground">
<span>Generating title...</span>
<span className="absolute inset-0 -translate-x-full animate-[shimmer_2s_infinite] bg-linear-to-r from-transparent via-white/20 to-transparent" />
</h1>
)}
{visibility === "private" && (
<span className="flex items-center gap-1 bg-faint px-1.5 py-0.5 font-medium text-background text-xs uppercase">
<LockIcon className="size-3" />
Private
</span>
)}
</div>

{gitContext ? (
<div className="mt-2 flex items-center gap-4 text-sm">
Expand Down
4 changes: 4 additions & 0 deletions app/[owner]/[repo]/[postNumber]/post-metadata-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type PostMetadata = {
categories: Category[]
gitContext: GitContextData | null
staleInfo: StaleInfo
visibility: "private" | null
isPolling: boolean
archivedRefs: string[]
selectedRef: string | null
Expand All @@ -51,6 +52,7 @@ export function PostMetadataProvider({
initialCategory,
initialGitContext,
staleInfo,
visibility,
archivedRefs,
categories,
children,
Expand All @@ -63,6 +65,7 @@ export function PostMetadataProvider({
initialCategory: Category | null
initialGitContext: GitContextData | null
staleInfo: StaleInfo
visibility: "private" | null
archivedRefs: string[]
categories: Category[]
children: React.ReactNode
Expand Down Expand Up @@ -139,6 +142,7 @@ export function PostMetadataProvider({
categories,
gitContext,
staleInfo,
visibility,
isPolling,
archivedRefs,
selectedRef,
Expand Down
53 changes: 53 additions & 0 deletions app/[owner]/[repo]/[postNumber]/private-post-gate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client"

import { LockIcon } from "lucide-react"
import type { ReactNode } from "react"
import { Container } from "@/components/container"
import { authClient } from "@/lib/auth-client"

type PrivatePostGateProps = {
visibility: "private" | null
authorId: string
children: ReactNode
}

export function PrivatePostGate({
visibility,
authorId,
children,
}: PrivatePostGateProps) {
const { data: auth, isPending } = authClient.useSession()
const userId = auth?.user?.id

if (visibility !== "private") {
return <>{children}</>
}

if (isPending) {
return (
<Container>
<div className="flex min-h-body-min-height items-center justify-center">
<p className="text-muted">Loading...</p>
</div>
</Container>
)
}

if (!userId || userId !== authorId) {
return (
<Container>
<div className="flex min-h-body-min-height flex-col items-center justify-center gap-4">
<LockIcon className="size-12 text-faint" />
<div className="text-center">
<h1 className="font-medium text-lg">Private Post</h1>
<p className="mt-1 text-muted text-sm">
This post is private and only visible to its author.
</p>
</div>
</div>
</Container>
)
}

return <>{children}</>
}
1 change: 1 addition & 0 deletions app/[owner]/[repo]/category/[categorySlug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default async function CategoryPage({
authorUsername: comments.authorUsername,
rootCommentId: posts.rootCommentId,
createdAt: posts.createdAt,
visibility: posts.visibility,
commentCount: sql<number>`(
SELECT COUNT(*) FROM comments WHERE comments.post_id = ${posts.id}
)`.as("comment_count"),
Expand Down
4 changes: 3 additions & 1 deletion app/[owner]/[repo]/new-post-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function NewPostComposer({
onAskingChange={(asking) => {
localStorage.setItem(PREFERRED_LLM_KEY, asking.id)
}}
onSubmit={async ({ value, options }) => {
onSubmit={async ({ value, visibility, options }) => {
const result = await createPost({
owner,
repo,
Expand All @@ -46,13 +46,15 @@ export function NewPostComposer({
},
seekingAnswerFrom: options.asking.id,
categoryId,
visibility: visibility === "private" ? "private" : null,
})
router.push(`/${owner}/${repo}/${result.postNumber}`)
}}
options={{
asking: askingOptions,
}}
placeholder="Ask or search"
showVisibility
storageKey={`new-post-composer:${owner}:${repo}`}
/>
)
Expand Down
1 change: 1 addition & 0 deletions app/[owner]/[repo]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default async function RepoPage({
authorUsername: comments.authorUsername,
rootCommentId: posts.rootCommentId,
createdAt: posts.createdAt,
visibility: posts.visibility,
commentCount: sql<number>`(
SELECT COUNT(*) FROM comments WHERE comments.post_id = ${posts.id}
)`.as("comment_count"),
Expand Down
19 changes: 16 additions & 3 deletions app/[owner]/[repo]/repo-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import type { InferSelectModel } from "drizzle-orm"
import { useRouter } from "next/navigation"
import { useEffect, useState } from "react"
import { useEffect, useMemo, useState } from "react"
import { Composer, type ComposerProps } from "@/components/composer"
import { createPost } from "@/lib/actions/posts"
import { authClient } from "@/lib/auth-client"
import type { categories } from "@/lib/db/schema"
import { RepoPostsSection } from "./repo-posts-section"

Expand All @@ -19,6 +20,7 @@ type PostListItem = {
authorUsername: string | null
rootCommentId: string | null
createdAt: number
visibility: "private" | null
commentCount: number
reactionCount: number
}
Expand All @@ -45,6 +47,8 @@ export function RepoContent({
const router = useRouter()
const [searchQuery, setSearchQuery] = useState("")
const [defaultLlmId, setDefaultLlmId] = useState<string | undefined>()
const { data: auth } = authClient.useSession()
const userId = auth?.user?.id

useEffect(() => {
const saved = localStorage.getItem(PREFERRED_LLM_KEY)
Expand All @@ -53,6 +57,13 @@ export function RepoContent({
}
}, [askingOptions])

const filteredPosts = useMemo(() => {
return posts.filter((post) => {
if (post.visibility !== "private") return true
return userId && post.authorId === userId
})
}, [posts, userId])

return (
<>
<div className="mb-8">
Expand All @@ -63,7 +74,7 @@ export function RepoContent({
localStorage.setItem(PREFERRED_LLM_KEY, asking.id)
}}
onChange={setSearchQuery}
onSubmit={async ({ value, options }) => {
onSubmit={async ({ value, visibility, options }) => {
const result = await createPost({
owner,
repo,
Expand All @@ -74,13 +85,15 @@ export function RepoContent({
},
seekingAnswerFrom: options.asking.id,
categoryId,
visibility: visibility === "private" ? "private" : null,
})
router.push(`/${owner}/${repo}/${result.postNumber}`)
}}
options={{
asking: askingOptions,
}}
placeholder="Ask or search"
showVisibility
storageKey={`new-post-composer:${owner}:${repo}`}
/>
</div>
Expand All @@ -89,7 +102,7 @@ export function RepoContent({
categoriesById={categoriesById}
categoryId={categoryId}
owner={owner}
posts={posts}
posts={filteredPosts}
repo={repo}
searchQuery={searchQuery}
/>
Expand Down
Loading