@tscircuit/fake-snippets 0.0.109 → 0.0.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/bun-formatcheck.yml +2 -2
- package/.github/workflows/bun-pver-release.yml +3 -3
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +2 -2
- package/.github/workflows/update-snapshots.yml +1 -1
- package/README.md +4 -0
- package/api/generated-index.js +37 -3
- package/biome.json +2 -1
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +32 -3
- package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
- package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
- package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
- package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
- package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
- package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +151 -0
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
- package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
- package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
- package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
- package/bun.lock +361 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1313 -639
- package/dist/index.d.ts +313 -6
- package/dist/index.js +328 -24
- package/dist/schema.d.ts +290 -1
- package/dist/schema.js +54 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +219 -4
- package/fake-snippets-api/lib/db/schema.ts +63 -1
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
- package/fake-snippets-api/lib/public-mapping/public-map-org.ts +33 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
- package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
- package/fake-snippets-api/routes/api/orgs/create.ts +48 -0
- package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
- package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
- package/fake-snippets-api/routes/api/orgs/list_members.ts +60 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +118 -0
- package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
- package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
- package/fake-snippets-api/routes/api/packages/create.ts +57 -10
- package/fake-snippets-api/routes/api/packages/get.ts +23 -0
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
- package/fake-snippets-api/routes/api/packages/list.ts +29 -2
- package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
- package/package.json +25 -19
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +27 -8
- package/src/ContextProviders.tsx +25 -2
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +281 -247
- package/src/components/DownloadButtonAndMenu.tsx +17 -5
- package/src/components/FileSidebar.tsx +11 -17
- package/src/components/Footer.tsx +8 -9
- package/src/components/Header.tsx +19 -32
- package/src/components/Header2.tsx +16 -32
- package/src/components/HeaderDropdown.tsx +13 -8
- package/src/components/HeaderLogin.tsx +43 -15
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- package/src/components/PackageSearchResults.tsx +1 -1
- package/src/components/PrefetchPageLink.tsx +7 -1
- package/src/components/ProfileRouter.tsx +32 -0
- package/src/components/SearchComponent.tsx +12 -8
- package/src/components/SentryNotFoundReporter.tsx +44 -0
- package/src/components/UserCard.tsx +80 -0
- package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
- package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
- package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
- package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
- package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
- package/src/components/dialogs/import-component-dialog.tsx +16 -9
- package/src/components/dialogs/import-package-dialog.tsx +3 -2
- package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
- package/src/components/organization/OrganizationCard.tsx +206 -0
- package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
- package/src/components/organization/OrganizationHeader.tsx +154 -0
- package/src/components/organization/OrganizationMembers.tsx +146 -0
- package/src/components/package-port/CodeAndPreview.tsx +15 -12
- package/src/components/package-port/CodeEditor.tsx +4 -30
- package/src/components/package-port/CodeEditorHeader.tsx +123 -61
- package/src/components/package-port/EditorNav.tsx +32 -49
- package/src/components/preview/ConnectedPackagesList.tsx +8 -8
- package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
- package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
- package/src/components/ui/tree-view.tsx +6 -3
- package/src/hooks/use-add-org-member-mutation.ts +51 -0
- package/src/hooks/use-create-org-mutation.ts +38 -0
- package/src/hooks/use-create-package-mutation.ts +3 -0
- package/src/hooks/use-current-package-release.ts +4 -3
- package/src/hooks/use-download-zip.ts +2 -2
- package/src/hooks/use-global-store.ts +6 -4
- package/src/hooks/use-hydration.ts +30 -0
- package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
- package/src/hooks/use-list-org-members.ts +27 -0
- package/src/hooks/use-list-user-orgs.ts +25 -0
- package/src/hooks/use-org-by-github-handle.ts +26 -0
- package/src/hooks/use-org.ts +24 -0
- package/src/hooks/use-organization.ts +42 -0
- package/src/hooks/use-package-as-snippet.ts +4 -2
- package/src/hooks/use-package-builds.ts +6 -2
- package/src/hooks/use-package-files.ts +5 -3
- package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
- package/src/hooks/use-package-release-images.ts +105 -0
- package/src/hooks/use-package-release.ts +2 -2
- package/src/hooks/use-package-stars.ts +80 -4
- package/src/hooks/use-preview-images.ts +6 -3
- package/src/hooks/use-remove-org-member-mutation.ts +32 -0
- package/src/hooks/use-update-ai-description-mutation.ts +42 -0
- package/src/hooks/use-update-org-mutation.ts +41 -0
- package/src/hooks/use-warn-user-on-page-change.ts +71 -4
- package/src/hooks/useFileManagement.ts +51 -22
- package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
- package/src/lib/download-fns/download-kicad-files.ts +22 -11
- package/src/lib/download-fns/download-step.ts +12 -0
- package/src/lib/normalize-svg-for-tile.ts +50 -0
- package/src/lib/posthog.ts +11 -9
- package/src/lib/react-query-api-failure-tracking.ts +148 -0
- package/src/lib/sentry.ts +14 -0
- package/src/lib/templates/blank-circuit-board-template.ts +0 -4
- package/src/lib/ts-lib-cache.ts +122 -7
- package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
- package/src/lib/utils/findTargetFile.ts +45 -10
- package/src/lib/utils/isComponentExported.ts +2 -1
- package/src/main.tsx +2 -1
- package/src/pages/create-organization.tsx +169 -0
- package/src/pages/dashboard.tsx +38 -6
- package/src/pages/datasheet.tsx +1 -1
- package/src/pages/datasheets.tsx +3 -3
- package/src/pages/editor.tsx +4 -6
- package/src/pages/landing.tsx +6 -6
- package/src/pages/latest.tsx +3 -0
- package/src/pages/organization-profile.tsx +199 -0
- package/src/pages/organization-settings.tsx +569 -0
- package/src/pages/package-editor.tsx +21 -21
- package/src/pages/preview-release.tsx +75 -145
- package/src/pages/quickstart.tsx +159 -123
- package/src/pages/release-detail.tsx +119 -31
- package/src/pages/search.tsx +197 -57
- package/src/pages/settings-redirect.tsx +44 -0
- package/src/pages/trending.tsx +29 -20
- package/src/pages/user-profile.tsx +58 -7
- package/src/pages/user-settings.tsx +161 -0
- package/src/pages/view-package.tsx +30 -16
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
- package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
- package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
- package/src/components/PageSearchComponent.tsx +0 -148
- package/src/pages/package-builds.tsx +0 -33
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { useParams } from "wouter"
|
|
3
|
+
import { useOrgByGithubHandle } from "@/hooks/use-org-by-github-handle"
|
|
4
|
+
import { OrganizationProfilePageContent } from "@/pages/organization-profile"
|
|
5
|
+
import { UserProfilePage } from "@/pages/user-profile"
|
|
6
|
+
import NotFoundPage from "@/pages/404"
|
|
7
|
+
import { FullPageLoader } from "@/App"
|
|
8
|
+
|
|
9
|
+
const ProfileRouter: React.FC = () => {
|
|
10
|
+
const { username } = useParams()
|
|
11
|
+
const {
|
|
12
|
+
data: organization,
|
|
13
|
+
isLoading,
|
|
14
|
+
error,
|
|
15
|
+
} = useOrgByGithubHandle(username || null)
|
|
16
|
+
|
|
17
|
+
if (!username) {
|
|
18
|
+
return <NotFoundPage heading="Username Not Provided" />
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isLoading) {
|
|
22
|
+
return <FullPageLoader />
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (organization && !organization.is_personal_org && !error) {
|
|
26
|
+
return <OrganizationProfilePageContent org={organization} />
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <UserProfilePage />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default ProfileRouter
|
|
@@ -5,7 +5,7 @@ import React, { useEffect, useRef, useState } from "react"
|
|
|
5
5
|
import { useQuery } from "react-query"
|
|
6
6
|
import { Alert } from "./ui/alert"
|
|
7
7
|
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
8
|
-
import {
|
|
8
|
+
import { Link } from "wouter"
|
|
9
9
|
import { CircuitBoard } from "lucide-react"
|
|
10
10
|
import { cn } from "@/lib/utils"
|
|
11
11
|
|
|
@@ -42,9 +42,9 @@ const LinkWithNewTabHandling = ({
|
|
|
42
42
|
)
|
|
43
43
|
}
|
|
44
44
|
return (
|
|
45
|
-
<
|
|
45
|
+
<Link onClick={onClick} className={className} href={href}>
|
|
46
46
|
{children}
|
|
47
|
-
</
|
|
47
|
+
</Link>
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -60,7 +60,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
60
60
|
const resultsRef = useRef<HTMLDivElement>(null)
|
|
61
61
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
62
62
|
const [location, setLocation] = useLocation()
|
|
63
|
-
const
|
|
63
|
+
const apiBaseUrl = useApiBaseUrl()
|
|
64
64
|
|
|
65
65
|
const { data: searchResults, isLoading } = useQuery(
|
|
66
66
|
["packageSearch", searchQuery],
|
|
@@ -137,10 +137,13 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
137
137
|
<Input
|
|
138
138
|
autoComplete="off"
|
|
139
139
|
spellCheck={false}
|
|
140
|
+
autoCorrect="off"
|
|
141
|
+
autoCapitalize="off"
|
|
140
142
|
ref={inputRef}
|
|
141
143
|
type="search"
|
|
144
|
+
aria-autocomplete="none"
|
|
142
145
|
placeholder="Search"
|
|
143
|
-
className="pl-4 focus:border-blue-500 placeholder-gray-400 text-sm"
|
|
146
|
+
className="pl-4 focus:border-blue-500 placeholder-gray-400 text-sm select-none"
|
|
144
147
|
value={searchQuery}
|
|
145
148
|
onChange={(e) => {
|
|
146
149
|
setSearchQuery(e.target.value)
|
|
@@ -195,10 +198,10 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
195
198
|
{showResults && searchResults && (
|
|
196
199
|
<div
|
|
197
200
|
ref={resultsRef}
|
|
198
|
-
className="absolute top-full md:left-0 right-0 mt-2 bg-white shadow-lg rounded-md z-50 w-80 max-h-screen overflow-y-auto overflow-x-visible"
|
|
201
|
+
className="absolute top-full md:left-0 right-0 no-scrollbar mt-2 bg-white shadow-lg rounded-md z-50 w-80 max-h-screen overflow-y-auto overflow-x-visible"
|
|
199
202
|
>
|
|
200
203
|
{searchResults.length > 0 ? (
|
|
201
|
-
<ul className="divide-y divide-gray-200">
|
|
204
|
+
<ul className="divide-y divide-gray-200 no-scrollbar">
|
|
202
205
|
{searchResults.map((pkg: any, index: number) => (
|
|
203
206
|
<li
|
|
204
207
|
key={pkg.package_id}
|
|
@@ -222,8 +225,9 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
222
225
|
>
|
|
223
226
|
<div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm bg-gray-50 border flex items-center justify-center">
|
|
224
227
|
<img
|
|
225
|
-
src={`${
|
|
228
|
+
src={`${apiBaseUrl}/packages/images/${pkg.name}/pcb.svg`}
|
|
226
229
|
alt={`PCB preview for ${pkg.name}`}
|
|
230
|
+
draggable={false}
|
|
227
231
|
className="w-12 h-12 object-contain p-1 scale-[4] rotate-45"
|
|
228
232
|
onError={(e) => {
|
|
229
233
|
e.currentTarget.style.display = "none"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react"
|
|
2
|
+
import { Sentry } from "@/lib/sentry"
|
|
3
|
+
|
|
4
|
+
type NotFoundContext = "package" | "package_release"
|
|
5
|
+
|
|
6
|
+
type SentryNotFoundReporterProps = {
|
|
7
|
+
context: NotFoundContext
|
|
8
|
+
slug: string
|
|
9
|
+
status?: number
|
|
10
|
+
message?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const SentryNotFoundReporter = ({
|
|
14
|
+
context,
|
|
15
|
+
slug,
|
|
16
|
+
status,
|
|
17
|
+
message,
|
|
18
|
+
}: SentryNotFoundReporterProps) => {
|
|
19
|
+
const hasLoggedRef = useRef(false)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (typeof window === "undefined") return
|
|
23
|
+
if (typeof slug !== "string" || slug.length === 0) return
|
|
24
|
+
if (typeof status === "number" && status !== 404) return
|
|
25
|
+
if (hasLoggedRef.current) return
|
|
26
|
+
|
|
27
|
+
hasLoggedRef.current = true
|
|
28
|
+
|
|
29
|
+
Sentry.captureMessage(`package:view:${context}-not-found`, {
|
|
30
|
+
level: "warning",
|
|
31
|
+
tags: {
|
|
32
|
+
slug,
|
|
33
|
+
},
|
|
34
|
+
extra: {
|
|
35
|
+
status,
|
|
36
|
+
message,
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
}, [context, slug, status, message])
|
|
40
|
+
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default SentryNotFoundReporter
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Link } from "wouter"
|
|
3
|
+
import { Account } from "fake-snippets-api/lib/db/schema"
|
|
4
|
+
import { User } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
export interface UserCardProps {
|
|
7
|
+
/** The account data to display */
|
|
8
|
+
account: Account | Omit<Account, "account_id">
|
|
9
|
+
/** Whether to render the card with a link to the user profile page */
|
|
10
|
+
withLink?: boolean
|
|
11
|
+
/** Custom class name for the card container */
|
|
12
|
+
className?: string
|
|
13
|
+
/** Custom onClick handler */
|
|
14
|
+
onClick?: (account: Account | Omit<Account, "account_id">) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const UserCard: React.FC<UserCardProps> = ({
|
|
18
|
+
account,
|
|
19
|
+
withLink = true,
|
|
20
|
+
className = "",
|
|
21
|
+
onClick,
|
|
22
|
+
}) => {
|
|
23
|
+
const handleClick = () => {
|
|
24
|
+
if (onClick) {
|
|
25
|
+
onClick(account)
|
|
26
|
+
} else if (!withLink) {
|
|
27
|
+
window.location.href = `/${account.github_username}`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cardContent = (
|
|
32
|
+
<div
|
|
33
|
+
className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 cursor-pointer ${className}`}
|
|
34
|
+
onClick={!withLink ? handleClick : undefined}
|
|
35
|
+
>
|
|
36
|
+
<div className="flex items-start gap-4">
|
|
37
|
+
<div className="w-16 h-16 flex-shrink-0 rounded-md overflow-hidden bg-gray-50 border flex items-center justify-center">
|
|
38
|
+
<img
|
|
39
|
+
src={`https://github.com/${account.github_username}.png`}
|
|
40
|
+
alt={`${account.github_username} avatar`}
|
|
41
|
+
className="object-cover h-full w-full transition-transform duration-300 hover:scale-110"
|
|
42
|
+
onError={(e) => {
|
|
43
|
+
const target = e.target as HTMLImageElement
|
|
44
|
+
target.style.display = "none"
|
|
45
|
+
target.nextElementSibling?.classList.remove("hidden")
|
|
46
|
+
target.nextElementSibling?.classList.add("flex")
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
<div className="hidden items-center justify-center h-full w-full">
|
|
50
|
+
<User className="w-6 h-6 text-gray-300" />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="flex-1 min-w-0 flex flex-col justify-center my-auto">
|
|
54
|
+
<div className="flex justify-between items-start">
|
|
55
|
+
<h2 className="text-md font-semibold truncate pr-[30px]">
|
|
56
|
+
<span className="text-gray-900">{account.github_username}</span>
|
|
57
|
+
</h2>
|
|
58
|
+
</div>
|
|
59
|
+
<p className="text-sm text-gray-500 truncate max-w-xs">
|
|
60
|
+
@{account.github_username}
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if (withLink) {
|
|
68
|
+
return (
|
|
69
|
+
<Link
|
|
70
|
+
key={account.github_username}
|
|
71
|
+
href={`/${account.github_username}`}
|
|
72
|
+
onClick={onClick ? () => onClick(account) : undefined}
|
|
73
|
+
>
|
|
74
|
+
{cardContent}
|
|
75
|
+
</Link>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return cardContent
|
|
80
|
+
}
|
|
@@ -14,7 +14,7 @@ export interface BuildStatusProps {
|
|
|
14
14
|
|
|
15
15
|
export const BuildStatus = ({ step, packageReleaseId }: BuildStatusProps) => {
|
|
16
16
|
const { author, packageName } = useParams()
|
|
17
|
-
const href = `/${author}/${packageName}/
|
|
17
|
+
const href = `/${author}/${packageName}/releases/${packageReleaseId}`
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<Link href={href} className="flex items-center gap-2">
|
|
@@ -16,6 +16,8 @@ import { ShikiCodeViewer, SKELETON_WIDTHS } from "./ShikiCodeViewer"
|
|
|
16
16
|
import MarkdownViewer from "./markdown-viewer"
|
|
17
17
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
18
18
|
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
19
|
+
import { useOrganization } from "@/hooks/use-organization"
|
|
20
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
19
21
|
|
|
20
22
|
interface PackageFile {
|
|
21
23
|
package_file_id: string
|
|
@@ -29,13 +31,12 @@ interface ImportantFilesViewProps {
|
|
|
29
31
|
importantFiles?: PackageFile[]
|
|
30
32
|
isFetched?: boolean
|
|
31
33
|
onEditClicked?: (file_path?: string | null) => void
|
|
32
|
-
packageAuthorOwner?: string | null
|
|
33
|
-
aiDescription?: string
|
|
34
|
-
aiUsageInstructions?: string
|
|
35
34
|
aiReviewText?: string | null
|
|
36
35
|
aiReviewRequested?: boolean
|
|
37
36
|
onRequestAiReview?: () => void
|
|
37
|
+
onRequestAiDescriptionUpdate?: () => void
|
|
38
38
|
onLicenseFileRequested?: boolean
|
|
39
|
+
pkg?: Package
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
type TabType = "ai" | "ai-review" | "file"
|
|
@@ -49,30 +50,44 @@ interface TabInfo {
|
|
|
49
50
|
|
|
50
51
|
export default function ImportantFilesView({
|
|
51
52
|
importantFiles = [],
|
|
52
|
-
aiDescription,
|
|
53
|
-
aiUsageInstructions,
|
|
54
53
|
aiReviewText,
|
|
55
54
|
aiReviewRequested,
|
|
56
55
|
onRequestAiReview,
|
|
56
|
+
onRequestAiDescriptionUpdate,
|
|
57
57
|
isFetched = false,
|
|
58
58
|
onEditClicked,
|
|
59
|
-
packageAuthorOwner,
|
|
60
59
|
onLicenseFileRequested,
|
|
60
|
+
pkg,
|
|
61
61
|
}: ImportantFilesViewProps) {
|
|
62
62
|
const [activeTab, setActiveTab] = useState<TabInfo | null>(null)
|
|
63
63
|
const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
|
|
64
64
|
const { session: user } = useGlobalStore()
|
|
65
65
|
|
|
66
|
+
const { organization } = useOrganization(
|
|
67
|
+
pkg?.owner_org_id
|
|
68
|
+
? { orgId: String(pkg.owner_org_id) }
|
|
69
|
+
: pkg?.owner_github_username
|
|
70
|
+
? { github_handle: pkg.owner_github_username }
|
|
71
|
+
: {},
|
|
72
|
+
)
|
|
73
|
+
|
|
66
74
|
// Memoized computed values
|
|
67
75
|
const hasAiContent = useMemo(
|
|
68
|
-
() => Boolean(
|
|
69
|
-
[
|
|
76
|
+
() => Boolean(pkg?.ai_description || pkg?.ai_usage_instructions),
|
|
77
|
+
[pkg?.ai_description, pkg?.ai_usage_instructions],
|
|
70
78
|
)
|
|
71
79
|
const hasAiReview = useMemo(() => Boolean(aiReviewText), [aiReviewText])
|
|
72
80
|
const isOwner = useMemo(
|
|
73
|
-
() => user?.github_username ===
|
|
74
|
-
[user?.github_username,
|
|
81
|
+
() => user?.github_username === pkg?.owner_github_username,
|
|
82
|
+
[user?.github_username, pkg?.owner_github_username],
|
|
75
83
|
)
|
|
84
|
+
const canManagePackage = useMemo(() => {
|
|
85
|
+
if (isOwner) return isOwner
|
|
86
|
+
if (organization) {
|
|
87
|
+
return organization.user_permissions?.can_manage_package
|
|
88
|
+
}
|
|
89
|
+
return false
|
|
90
|
+
}, [isOwner, organization])
|
|
76
91
|
|
|
77
92
|
// File type utilities
|
|
78
93
|
const isLicenseFile = useCallback((filePath: string) => {
|
|
@@ -139,7 +154,7 @@ export default function ImportantFilesView({
|
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
// Only show AI review tab if there's actual AI review content
|
|
142
|
-
if (hasAiReview ||
|
|
157
|
+
if (hasAiReview || canManagePackage) {
|
|
143
158
|
tabs.push({
|
|
144
159
|
type: "ai-review",
|
|
145
160
|
filePath: null,
|
|
@@ -286,6 +301,16 @@ export default function ImportantFilesView({
|
|
|
286
301
|
|
|
287
302
|
if (activeTab?.type === "ai-review" && aiReviewText) {
|
|
288
303
|
textToCopy = aiReviewText
|
|
304
|
+
} else if (
|
|
305
|
+
activeTab?.type === "ai" &&
|
|
306
|
+
(pkg?.ai_description || pkg?.ai_usage_instructions)
|
|
307
|
+
) {
|
|
308
|
+
const parts = []
|
|
309
|
+
if (pkg?.ai_description)
|
|
310
|
+
parts.push(`# Description\n\n${pkg?.ai_description}`)
|
|
311
|
+
if (pkg?.ai_usage_instructions)
|
|
312
|
+
parts.push(`# Instructions\n\n${pkg?.ai_usage_instructions}`)
|
|
313
|
+
textToCopy = parts.join("\n\n")
|
|
289
314
|
} else if (activeTab?.type === "file" && activeFileContent) {
|
|
290
315
|
textToCopy = activeFileContent
|
|
291
316
|
}
|
|
@@ -298,25 +323,49 @@ export default function ImportantFilesView({
|
|
|
298
323
|
}
|
|
299
324
|
|
|
300
325
|
// Render content based on active tab
|
|
301
|
-
const renderAiContent = useCallback(
|
|
302
|
-
()
|
|
326
|
+
const renderAiContent = useCallback(() => {
|
|
327
|
+
if (!pkg?.ai_description && !pkg?.ai_usage_instructions) {
|
|
328
|
+
return (
|
|
329
|
+
<div className="flex flex-col items-center justify-center py-8 px-4">
|
|
330
|
+
<div className="text-center space-y-4 max-w-md">
|
|
331
|
+
<div className="flex justify-center">
|
|
332
|
+
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center">
|
|
333
|
+
<Loader2 className="h-6 w-6 text-gray-600 animate-spin" />
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
<div className="space-y-2">
|
|
337
|
+
<p className="text-sm text-gray-600">
|
|
338
|
+
Our AI is generating a description for your package. This
|
|
339
|
+
usually takes a few minutes. Please check back shortly.
|
|
340
|
+
</p>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return (
|
|
303
348
|
<div className="markdown-content">
|
|
304
|
-
{
|
|
349
|
+
{pkg?.ai_description && (
|
|
305
350
|
<div className="mb-6">
|
|
306
351
|
<h3 className="font-semibold text-lg mb-2">Description</h3>
|
|
307
|
-
<MarkdownViewer markdownContent={
|
|
352
|
+
<MarkdownViewer markdownContent={pkg?.ai_description} />
|
|
308
353
|
</div>
|
|
309
354
|
)}
|
|
310
|
-
{
|
|
355
|
+
{pkg?.ai_usage_instructions && (
|
|
311
356
|
<div>
|
|
312
357
|
<h3 className="font-semibold text-lg mb-2">Instructions</h3>
|
|
313
|
-
<MarkdownViewer markdownContent={
|
|
358
|
+
<MarkdownViewer markdownContent={pkg?.ai_usage_instructions} />
|
|
314
359
|
</div>
|
|
315
360
|
)}
|
|
316
361
|
</div>
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
|
|
362
|
+
)
|
|
363
|
+
}, [
|
|
364
|
+
pkg?.ai_description,
|
|
365
|
+
pkg?.ai_usage_instructions,
|
|
366
|
+
canManagePackage,
|
|
367
|
+
onRequestAiDescriptionUpdate,
|
|
368
|
+
])
|
|
320
369
|
|
|
321
370
|
const renderAiReviewContent = useCallback(() => {
|
|
322
371
|
if (!aiReviewText && !aiReviewRequested) {
|
|
@@ -334,7 +383,7 @@ export default function ImportantFilesView({
|
|
|
334
383
|
from our AI assistant.
|
|
335
384
|
</p>
|
|
336
385
|
</div>
|
|
337
|
-
{!
|
|
386
|
+
{!canManagePackage ? (
|
|
338
387
|
<p className="text-sm text-gray-500">
|
|
339
388
|
Only the package owner can generate an AI review
|
|
340
389
|
</p>
|
|
@@ -378,15 +427,17 @@ export default function ImportantFilesView({
|
|
|
378
427
|
}
|
|
379
428
|
|
|
380
429
|
return <MarkdownViewer markdownContent={aiReviewText || ""} />
|
|
381
|
-
}, [aiReviewText, aiReviewRequested,
|
|
430
|
+
}, [aiReviewText, aiReviewRequested, canManagePackage, onRequestAiReview])
|
|
382
431
|
|
|
383
432
|
const renderFileContent = useCallback(() => {
|
|
384
433
|
if (!isActiveFileFetched || !activeTab?.filePath || !activeFileContent) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
434
|
+
return (
|
|
435
|
+
<div className="text-sm p-4">
|
|
436
|
+
{SKELETON_WIDTHS.map((w, i) => (
|
|
437
|
+
<Skeleton key={i} className={`h-4 mb-2 ${w}`} />
|
|
438
|
+
))}
|
|
439
|
+
</div>
|
|
440
|
+
)
|
|
390
441
|
}
|
|
391
442
|
|
|
392
443
|
if (isMarkdownFile(String(activeTab?.filePath))) {
|
|
@@ -405,7 +456,13 @@ export default function ImportantFilesView({
|
|
|
405
456
|
}
|
|
406
457
|
|
|
407
458
|
return <pre className="whitespace-pre-wrap">{activeFileContent}</pre>
|
|
408
|
-
}, [
|
|
459
|
+
}, [
|
|
460
|
+
activeTab,
|
|
461
|
+
activeFileContent,
|
|
462
|
+
isActiveFileFetched,
|
|
463
|
+
isMarkdownFile,
|
|
464
|
+
isCodeFile,
|
|
465
|
+
])
|
|
409
466
|
|
|
410
467
|
const renderTabContent = useCallback(() => {
|
|
411
468
|
if (!activeTab) return null
|
|
@@ -500,7 +557,9 @@ export default function ImportantFilesView({
|
|
|
500
557
|
</div>
|
|
501
558
|
<div className="ml-auto flex items-center">
|
|
502
559
|
{((activeTab?.type === "file" && activeFileContent) ||
|
|
503
|
-
(activeTab?.type === "ai-review" && aiReviewText)
|
|
560
|
+
(activeTab?.type === "ai-review" && aiReviewText) ||
|
|
561
|
+
(activeTab?.type === "ai" &&
|
|
562
|
+
(pkg?.ai_description || pkg?.ai_usage_instructions))) && (
|
|
504
563
|
<button
|
|
505
564
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md transition-all duration-300"
|
|
506
565
|
onClick={handleCopy}
|
|
@@ -513,17 +572,29 @@ export default function ImportantFilesView({
|
|
|
513
572
|
<span className="sr-only">Copy</span>
|
|
514
573
|
</button>
|
|
515
574
|
)}
|
|
516
|
-
{activeTab?.type === "ai-review" &&
|
|
575
|
+
{activeTab?.type === "ai-review" &&
|
|
576
|
+
aiReviewText &&
|
|
577
|
+
canManagePackage && (
|
|
578
|
+
<button
|
|
579
|
+
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
|
|
580
|
+
onClick={onRequestAiReview}
|
|
581
|
+
title="Re-request AI Review"
|
|
582
|
+
>
|
|
583
|
+
<RefreshCcwIcon className="h-4 w-4" />
|
|
584
|
+
<span className="sr-only">Re-request AI Review</span>
|
|
585
|
+
</button>
|
|
586
|
+
)}
|
|
587
|
+
{activeTab?.type === "ai" && hasAiContent && canManagePackage && (
|
|
517
588
|
<button
|
|
518
589
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md ml-1"
|
|
519
|
-
onClick={
|
|
520
|
-
title="
|
|
590
|
+
onClick={onRequestAiDescriptionUpdate}
|
|
591
|
+
title="Regenerate AI Description"
|
|
521
592
|
>
|
|
522
593
|
<RefreshCcwIcon className="h-4 w-4" />
|
|
523
|
-
<span className="sr-only">
|
|
594
|
+
<span className="sr-only">Regenerate AI Description</span>
|
|
524
595
|
</button>
|
|
525
596
|
)}
|
|
526
|
-
{activeTab?.type === "file" && (
|
|
597
|
+
{activeTab?.type === "file" && canManagePackage && (
|
|
527
598
|
<button
|
|
528
599
|
className="hover:bg-gray-200 dark:hover:bg-[#30363d] p-1 rounded-md"
|
|
529
600
|
onClick={() => onEditClicked?.(activeTab.filePath)}
|
|
@@ -26,6 +26,7 @@ import { useLocation } from "wouter"
|
|
|
26
26
|
import { Package, PackageFile } from "fake-snippets-api/lib/db/schema"
|
|
27
27
|
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
28
28
|
import { useDownloadZip } from "@/hooks/use-download-zip"
|
|
29
|
+
import { useToast } from "@/hooks/use-toast"
|
|
29
30
|
interface MainContentHeaderProps {
|
|
30
31
|
packageFiles: PackageFile[]
|
|
31
32
|
activeView: string
|
|
@@ -63,16 +64,19 @@ export default function MainContentHeader({
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
const { downloadZip } = useDownloadZip()
|
|
67
|
+
const { toastLibrary } = useToast()
|
|
66
68
|
|
|
67
69
|
const handleDownloadZip = () => {
|
|
68
70
|
if (packageInfo && packageFiles) {
|
|
69
|
-
downloadZip(packageInfo, packageFiles)
|
|
71
|
+
toastLibrary.promise(downloadZip(packageInfo, packageFiles), {
|
|
72
|
+
loading: "Downloading ZIP...",
|
|
73
|
+
success: "ZIP downloaded successfully!",
|
|
74
|
+
error: "Failed to download ZIP",
|
|
75
|
+
})
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
|
|
73
|
-
const
|
|
74
|
-
(file) => file.file_path === "dist/circuit.json",
|
|
75
|
-
)
|
|
79
|
+
const { circuitJson } = useCurrentPackageCircuitJson()
|
|
76
80
|
|
|
77
81
|
return (
|
|
78
82
|
<div className="flex items-center justify-between mb-4">
|
|
@@ -86,7 +90,7 @@ export default function MainContentHeader({
|
|
|
86
90
|
unscopedName={packageInfo?.unscoped_name}
|
|
87
91
|
desiredImageType={activeView}
|
|
88
92
|
author={packageInfo?.owner_github_username ?? undefined}
|
|
89
|
-
|
|
93
|
+
circuitJson={circuitJson}
|
|
90
94
|
/>
|
|
91
95
|
|
|
92
96
|
{/* Code Dropdown */}
|
|
@@ -101,7 +105,7 @@ export default function MainContentHeader({
|
|
|
101
105
|
<ChevronDown className="h-4 w-4 ml-0.5" />
|
|
102
106
|
</Button>
|
|
103
107
|
</DropdownMenuTrigger>
|
|
104
|
-
<DropdownMenuContent align="end" className="w-72">
|
|
108
|
+
<DropdownMenuContent align="end" className="w-72 relative z-[101]">
|
|
105
109
|
<DropdownMenuItem disabled={!Boolean(packageInfo)} asChild>
|
|
106
110
|
<a
|
|
107
111
|
href={`/editor?package_id=${packageInfo?.package_id}`}
|
|
@@ -130,7 +130,7 @@ export default function MainContentViewSelector({
|
|
|
130
130
|
</svg>
|
|
131
131
|
</Button>
|
|
132
132
|
</DropdownMenuTrigger>
|
|
133
|
-
<DropdownMenuContent align="start">
|
|
133
|
+
<DropdownMenuContent align="start" className="z-[101]">
|
|
134
134
|
<TooltipProvider>
|
|
135
135
|
{views.map((view) => {
|
|
136
136
|
const isDisabled = !circuitJson && view.requiresCircuitJson
|