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
5 changes: 4 additions & 1 deletion fake-snippets-api/lib/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,10 @@ export const publicOrgSchema = z.object({
package_count: z.number(),
created_at: z.string(),
user_permissions: z
.object({ can_manage_org: z.boolean().optional() })
.object({
can_manage_org: z.boolean().optional(),
can_manage_package: z.boolean().optional(),
})
.optional(),
})
export type PublicOrgSchema = z.infer<typeof publicOrgSchema>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { ShikiCodeViewer, SKELETON_WIDTHS } from "./ShikiCodeViewer"
import MarkdownViewer from "./markdown-viewer"
import { useGlobalStore } from "@/hooks/use-global-store"
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
import { useOrganization } from "@/hooks/use-organization"
import { Package } from "fake-snippets-api/lib/db/schema"

interface PackageFile {
package_file_id: string
Expand All @@ -29,14 +31,12 @@ interface ImportantFilesViewProps {
importantFiles?: PackageFile[]
isFetched?: boolean
onEditClicked?: (file_path?: string | null) => void
packageAuthorOwner?: string | null
aiDescription?: string
aiUsageInstructions?: string
aiReviewText?: string | null
aiReviewRequested?: boolean
onRequestAiReview?: () => void
onRequestAiDescriptionUpdate?: () => void
onLicenseFileRequested?: boolean
pkg?: Package
}

type TabType = "ai" | "ai-review" | "file"
Expand All @@ -50,31 +50,44 @@ interface TabInfo {

export default function ImportantFilesView({
importantFiles = [],
aiDescription,
aiUsageInstructions,
aiReviewText,
aiReviewRequested,
onRequestAiReview,
onRequestAiDescriptionUpdate,
isFetched = false,
onEditClicked,
packageAuthorOwner,
onLicenseFileRequested,
pkg,
}: ImportantFilesViewProps) {
const [activeTab, setActiveTab] = useState<TabInfo | null>(null)
const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
const { session: user } = useGlobalStore()

const { organization } = useOrganization(
pkg?.owner_org_id
? { orgId: String(pkg.owner_org_id) }
: pkg?.owner_github_username
? { github_handle: pkg.owner_github_username }
: {},
)

// Memoized computed values
const hasAiContent = useMemo(
() => Boolean(aiDescription || aiUsageInstructions),
[aiDescription, aiUsageInstructions],
() => Boolean(pkg?.ai_description || pkg?.ai_usage_instructions),
[pkg?.ai_description, pkg?.ai_usage_instructions],
)
const hasAiReview = useMemo(() => Boolean(aiReviewText), [aiReviewText])
const isOwner = useMemo(
() => user?.github_username === packageAuthorOwner,
[user?.github_username, packageAuthorOwner],
() => user?.github_username === pkg?.owner_github_username,
[user?.github_username, pkg?.owner_github_username],
)
const canManagePackage = useMemo(() => {
if (isOwner) return isOwner
if (organization) {
return organization.user_permissions?.can_manage_package
}
return false
}, [isOwner, organization])

// File type utilities
const isLicenseFile = useCallback((filePath: string) => {
Expand Down Expand Up @@ -141,7 +154,7 @@ export default function ImportantFilesView({
}

// Only show AI review tab if there's actual AI review content
if (hasAiReview || isOwner) {
if (hasAiReview || canManagePackage) {
tabs.push({
type: "ai-review",
filePath: null,
Expand Down Expand Up @@ -290,12 +303,13 @@ export default function ImportantFilesView({
textToCopy = aiReviewText
} else if (
activeTab?.type === "ai" &&
(aiDescription || aiUsageInstructions)
(pkg?.ai_description || pkg?.ai_usage_instructions)
) {
const parts = []
if (aiDescription) parts.push(`# Description\n\n${aiDescription}`)
if (aiUsageInstructions)
parts.push(`# Instructions\n\n${aiUsageInstructions}`)
if (pkg?.ai_description)
parts.push(`# Description\n\n${pkg?.ai_description}`)
if (pkg?.ai_usage_instructions)
parts.push(`# Instructions\n\n${pkg?.ai_usage_instructions}`)
textToCopy = parts.join("\n\n")
} else if (activeTab?.type === "file" && activeFileContent) {
textToCopy = activeFileContent
Expand All @@ -310,7 +324,7 @@ export default function ImportantFilesView({

// Render content based on active tab
const renderAiContent = useCallback(() => {
if (!aiDescription && !aiUsageInstructions) {
if (!pkg?.ai_description && !pkg?.ai_usage_instructions) {
return (
<div className="flex flex-col items-center justify-center py-8 px-4">
<div className="text-center space-y-4 max-w-md">
Expand All @@ -332,24 +346,24 @@ export default function ImportantFilesView({

return (
<div className="markdown-content">
{aiDescription && (
{pkg?.ai_description && (
<div className="mb-6">
<h3 className="font-semibold text-lg mb-2">Description</h3>
<MarkdownViewer markdownContent={aiDescription} />
<MarkdownViewer markdownContent={pkg?.ai_description} />
</div>
)}
{aiUsageInstructions && (
{pkg?.ai_usage_instructions && (
<div>
<h3 className="font-semibold text-lg mb-2">Instructions</h3>
<MarkdownViewer markdownContent={aiUsageInstructions} />
<MarkdownViewer markdownContent={pkg?.ai_usage_instructions} />
</div>
)}
</div>
)
}, [
aiDescription,
aiUsageInstructions,
isOwner,
pkg?.ai_description,
pkg?.ai_usage_instructions,
canManagePackage,
onRequestAiDescriptionUpdate,
])

Expand All @@ -369,7 +383,7 @@ export default function ImportantFilesView({
from our AI assistant.
</p>
</div>
{!isOwner ? (
{!canManagePackage ? (
<p className="text-sm text-gray-500">
Only the package owner can generate an AI review
</p>
Expand Down Expand Up @@ -413,7 +427,7 @@ export default function ImportantFilesView({
}

return <MarkdownViewer markdownContent={aiReviewText || ""} />
}, [aiReviewText, aiReviewRequested, isOwner, onRequestAiReview])
}, [aiReviewText, aiReviewRequested, canManagePackage, onRequestAiReview])

const renderFileContent = useCallback(() => {
if (!isActiveFileFetched || !activeTab?.filePath || !activeFileContent) {
Expand Down Expand Up @@ -545,7 +559,7 @@ export default function ImportantFilesView({
{((activeTab?.type === "file" && activeFileContent) ||
(activeTab?.type === "ai-review" && aiReviewText) ||
(activeTab?.type === "ai" &&
(aiDescription || aiUsageInstructions))) && (
(pkg?.ai_description || pkg?.ai_usage_instructions))) && (
<button
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md transition-all duration-300"
onClick={handleCopy}
Expand All @@ -558,17 +572,19 @@ export default function ImportantFilesView({
<span className="sr-only">Copy</span>
</button>
)}
{activeTab?.type === "ai-review" && aiReviewText && isOwner && (
<button
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
onClick={onRequestAiReview}
title="Re-request AI Review"
>
<RefreshCcwIcon className="h-4 w-4" />
<span className="sr-only">Re-request AI Review</span>
</button>
)}
{activeTab?.type === "ai" && hasAiContent && isOwner && (
{activeTab?.type === "ai-review" &&
aiReviewText &&
canManagePackage && (
<button
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
onClick={onRequestAiReview}
title="Re-request AI Review"
>
<RefreshCcwIcon className="h-4 w-4" />
<span className="sr-only">Re-request AI Review</span>
</button>
)}
{activeTab?.type === "ai" && hasAiContent && canManagePackage && (
<button
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
onClick={onRequestAiDescriptionUpdate}
Expand All @@ -578,7 +594,7 @@ export default function ImportantFilesView({
<span className="sr-only">Regenerate AI Description</span>
</button>
)}
{activeTab?.type === "file" && (
{activeTab?.type === "file" && canManagePackage && (
<button
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
onClick={() => onEditClicked?.(activeTab.filePath)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,12 @@ export default function RepoPageContent({

{/* Dynamic Content based on active view */}
{renderContent()}

{/* Important Files View - Always shown */}
<ImportantFilesView
importantFiles={importantFiles}
isFetched={arePackageFilesFetched}
pkg={packageInfo}
onEditClicked={onEditClicked}
packageAuthorOwner={packageInfo?.owner_github_username}
aiDescription={packageInfo?.ai_description ?? ""}
aiUsageInstructions={packageInfo?.ai_usage_instructions ?? ""}
aiReviewText={packageRelease?.ai_review_text ?? null}
aiReviewRequested={aiReviewRequested}
onRequestAiReview={() => {
Expand Down
15 changes: 10 additions & 5 deletions src/hooks/use-organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@ import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
export const useOrganization = ({
orgId,
orgName,
}: { orgId?: string; orgName?: string }) => {
github_handle,
}: { orgId?: string; orgName?: string; github_handle?: string }) => {
const axios = useAxios()

const orgQuery = useQuery<PublicOrgSchema, Error & { status: number }>(
Copy link
Contributor

Choose a reason for hiding this comment

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

The query key should include the github_handle parameter to maintain cache consistency. Currently, the key only includes orgId or orgName, but not the newly added parameter. Consider updating it to:

["orgs", "get", orgId || orgName || github_handle]

This ensures that queries with different GitHub handles will be properly cached as separate entries, preventing potential stale data issues when switching between different parameter combinations.

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here.

["orgs", "get", orgId || orgName],
["orgs", "get", orgId || orgName || github_handle],
async () => {
if (!orgId && !orgName) {
throw new Error("Organization ID or name is required")
if (!orgId && !orgName && !github_handle) {
throw new Error("Organization ID, name, or GitHub handle is required")
}
const params = orgId ? { org_id: orgId } : { org_name: orgName }
const params = orgId
? { org_id: orgId }
: orgName
? { org_name: orgName }
: { github_handle }
const { data } = await axios.get("/orgs/get", { params })
return data.org
},
Expand Down