@tscircuit/fake-snippets 0.0.109 → 0.0.110

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.
Files changed (181) hide show
  1. package/.github/workflows/bun-formatcheck.yml +2 -2
  2. package/.github/workflows/bun-pver-release.yml +3 -3
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +2 -2
  5. package/.github/workflows/update-snapshots.yml +1 -1
  6. package/README.md +4 -0
  7. package/api/generated-index.js +37 -3
  8. package/biome.json +2 -1
  9. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +31 -3
  10. package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
  11. package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
  12. package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
  13. package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
  14. package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
  15. package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
  16. package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
  17. package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +99 -0
  18. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
  19. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
  20. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
  21. package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
  22. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
  23. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
  24. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
  25. package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
  26. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
  27. package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
  28. package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
  29. package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
  30. package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
  31. package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
  32. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
  33. package/bun.lock +349 -453
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1253 -624
  36. package/dist/index.d.ts +291 -4
  37. package/dist/index.js +323 -23
  38. package/dist/schema.d.ts +274 -1
  39. package/dist/schema.js +52 -1
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +214 -3
  42. package/fake-snippets-api/lib/db/schema.ts +61 -0
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
  45. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
  46. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +32 -0
  47. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
  48. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
  49. package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
  50. package/fake-snippets-api/routes/api/orgs/create.ts +46 -0
  51. package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
  52. package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
  53. package/fake-snippets-api/routes/api/orgs/list_members.ts +67 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +93 -0
  56. package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
  57. package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
  58. package/fake-snippets-api/routes/api/packages/create.ts +54 -10
  59. package/fake-snippets-api/routes/api/packages/get.ts +23 -0
  60. package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
  61. package/fake-snippets-api/routes/api/packages/list.ts +29 -2
  62. package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
  63. package/package.json +24 -20
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +29 -8
  67. package/src/ContextProviders.tsx +25 -2
  68. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  69. package/src/components/CmdKMenu.tsx +281 -247
  70. package/src/components/DownloadButtonAndMenu.tsx +3 -4
  71. package/src/components/FileSidebar.tsx +11 -17
  72. package/src/components/Footer.tsx +8 -9
  73. package/src/components/Header.tsx +19 -32
  74. package/src/components/Header2.tsx +16 -32
  75. package/src/components/HeaderDropdown.tsx +13 -8
  76. package/src/components/HeaderLogin.tsx +43 -15
  77. package/src/components/NotFound.tsx +5 -5
  78. package/src/components/PackageBreadcrumb.tsx +6 -12
  79. package/src/components/PackageSearchResults.tsx +1 -1
  80. package/src/components/PrefetchPageLink.tsx +7 -1
  81. package/src/components/ProfileRouter.tsx +32 -0
  82. package/src/components/SearchComponent.tsx +12 -8
  83. package/src/components/UserCard.tsx +80 -0
  84. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  85. package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
  86. package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
  87. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
  88. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
  89. package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
  90. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
  91. package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
  92. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  93. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  94. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  95. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  96. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  97. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  98. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  99. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  100. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  101. package/src/components/organization/OrganizationCard.tsx +204 -0
  102. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  103. package/src/components/organization/OrganizationHeader.tsx +154 -0
  104. package/src/components/organization/OrganizationMembers.tsx +146 -0
  105. package/src/components/package-port/CodeAndPreview.tsx +15 -12
  106. package/src/components/package-port/CodeEditor.tsx +4 -30
  107. package/src/components/package-port/CodeEditorHeader.tsx +123 -61
  108. package/src/components/package-port/EditorNav.tsx +32 -49
  109. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  110. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  111. package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
  112. package/src/components/ui/tree-view.tsx +6 -3
  113. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  114. package/src/hooks/use-create-org-mutation.ts +38 -0
  115. package/src/hooks/use-create-package-mutation.ts +3 -0
  116. package/src/hooks/use-current-package-release.ts +4 -3
  117. package/src/hooks/use-download-zip.ts +2 -2
  118. package/src/hooks/use-global-store.ts +6 -4
  119. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  120. package/src/hooks/use-list-org-members.ts +27 -0
  121. package/src/hooks/use-list-user-orgs.ts +25 -0
  122. package/src/hooks/use-org-by-github-handle.ts +26 -0
  123. package/src/hooks/use-org.ts +24 -0
  124. package/src/hooks/use-organization.ts +42 -0
  125. package/src/hooks/use-package-as-snippet.ts +4 -2
  126. package/src/hooks/use-package-builds.ts +6 -2
  127. package/src/hooks/use-package-files.ts +5 -3
  128. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  129. package/src/hooks/use-package-release-images.ts +105 -0
  130. package/src/hooks/use-package-release.ts +2 -2
  131. package/src/hooks/use-package-stars.ts +80 -4
  132. package/src/hooks/use-preview-images.ts +6 -3
  133. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  134. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  135. package/src/hooks/use-update-org-mutation.ts +41 -0
  136. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  137. package/src/hooks/useFileManagement.ts +51 -22
  138. package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
  139. package/src/hooks/usePackageFilesLoader.ts +2 -2
  140. package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
  141. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
  142. package/src/lib/download-fns/download-kicad-files.ts +12 -11
  143. package/src/lib/normalize-svg-for-tile.ts +50 -0
  144. package/src/lib/posthog.ts +11 -9
  145. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  146. package/src/lib/sentry.ts +14 -0
  147. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  148. package/src/lib/ts-lib-cache.ts +122 -7
  149. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  150. package/src/lib/utils/findTargetFile.ts +45 -10
  151. package/src/lib/utils/isComponentExported.ts +2 -1
  152. package/src/main.tsx +2 -1
  153. package/src/pages/create-organization.tsx +168 -0
  154. package/src/pages/dashboard.tsx +38 -6
  155. package/src/pages/datasheet.tsx +1 -1
  156. package/src/pages/datasheets.tsx +3 -3
  157. package/src/pages/editor.tsx +4 -6
  158. package/src/pages/landing.tsx +6 -6
  159. package/src/pages/latest.tsx +3 -0
  160. package/src/pages/organization-profile.tsx +199 -0
  161. package/src/pages/organization-settings.tsx +566 -0
  162. package/src/pages/package-editor.tsx +21 -21
  163. package/src/pages/preview-release.tsx +75 -145
  164. package/src/pages/quickstart.tsx +159 -123
  165. package/src/pages/release-detail.tsx +119 -31
  166. package/src/pages/search.tsx +192 -57
  167. package/src/pages/settings-redirect.tsx +44 -0
  168. package/src/pages/trending.tsx +29 -20
  169. package/src/pages/user-profile.tsx +58 -7
  170. package/src/pages/view-package.tsx +7 -13
  171. package/vite.config.ts +9 -0
  172. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  173. package/src/components/JLCPCBImportDialog.tsx +0 -280
  174. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  175. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
  176. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
  177. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  178. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  179. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  180. package/src/components/PageSearchComponent.tsx +0 -148
  181. package/src/pages/package-builds.tsx +0 -33
@@ -3,7 +3,7 @@ import { Badge } from "@/components/ui/badge"
3
3
  import { Button } from "@/components/ui/button"
4
4
  import { GitBranch, Rocket, Github } from "lucide-react"
5
5
  import { cn } from "@/lib/utils"
6
- import { PrefetchPageLink } from "../PrefetchPageLink"
6
+ import { Link } from "wouter"
7
7
  import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
8
8
  import { getBuildStatus, StatusIcon } from "."
9
9
  import { Package } from "fake-snippets-api/lib/db/schema"
@@ -116,12 +116,12 @@ export const ConnectedPackageCard = ({
116
116
  >
117
117
  <div className="flex items-start justify-between mb-4">
118
118
  <div className="flex items-center gap-3">
119
- <a
120
- href="#"
119
+ <Link
120
+ href={`/${pkg.owner_github_username}/${pkg.unscoped_name}`}
121
121
  className="text-lg font-semibold text-gray-900 hover:text-blue-600 transition-colors"
122
122
  >
123
123
  {pkg.unscoped_name}
124
- </a>
124
+ </Link>
125
125
  </div>
126
126
 
127
127
  <div className="flex items-center justify-center gap-2">
@@ -174,25 +174,25 @@ export const ConnectedPackageCard = ({
174
174
  </div>
175
175
 
176
176
  <div className="flex gap-2 w-full mt-auto">
177
- <PrefetchPageLink className="w-full" href={`/${pkg.name}/releases`}>
177
+ <Link className="w-full" href={`/${pkg.name}/releases`}>
178
178
  <Button
179
179
  size="sm"
180
180
  className="bg-blue-600 w-full hover:bg-blue-700 text-white px-4 py-2"
181
181
  >
182
182
  View
183
183
  </Button>
184
- </PrefetchPageLink>
184
+ </Link>
185
185
  {latestBuildInfo?.preview_url &&
186
186
  latestBuildInfo?.package_build_id &&
187
187
  status === "success" && (
188
- <PrefetchPageLink
188
+ <Link
189
189
  className="w-full"
190
190
  href={`/${pkg.name}/releases/${latestBuildInfo.package_release_id}/preview`}
191
191
  >
192
192
  <Button size="sm" variant="outline" className="px-4 py-2 w-full">
193
193
  Preview
194
194
  </Button>
195
- </PrefetchPageLink>
195
+ </Link>
196
196
  )}
197
197
  </div>
198
198
  </Card>
@@ -41,6 +41,7 @@ export const ConnectedRepoOverview = ({
41
41
  const [openSections, setOpenSections] = useState({
42
42
  transpilation: false,
43
43
  circuitJson: false,
44
+ imageGeneration: false,
44
45
  })
45
46
 
46
47
  // Gracefully handle when there is no build yet
@@ -120,15 +121,25 @@ export const ConnectedRepoOverview = ({
120
121
  1000,
121
122
  )
122
123
  : 0
124
+ const imageGenerationDuration = packageBuild?.image_generation_started_at
125
+ ? Math.floor(
126
+ (new Date(
127
+ packageBuild.image_generation_completed_at || new Date(),
128
+ ).getTime() -
129
+ new Date(packageBuild.image_generation_started_at).getTime()) /
130
+ 1000,
131
+ )
132
+ : 0
123
133
 
124
134
  if (
125
135
  !packageBuild?.transpilation_started_at &&
126
- !packageBuild?.circuit_json_build_started_at
136
+ !packageBuild?.circuit_json_build_started_at &&
137
+ !packageBuild?.image_generation_started_at
127
138
  ) {
128
139
  return null
129
140
  }
130
141
 
131
- return transpilationDuration + circuitJsonDuration
142
+ return transpilationDuration + circuitJsonDuration + imageGenerationDuration
132
143
  })()
133
144
  const toggleSection = (section: keyof typeof openSections) => {
134
145
  setOpenSections((prev) => ({ ...prev, [section]: !prev[section] }))
@@ -482,6 +493,95 @@ export const ConnectedRepoOverview = ({
482
493
  </div>
483
494
  </CollapsibleContent>
484
495
  </Collapsible>
496
+
497
+ <Collapsible
498
+ open={openSections.imageGeneration}
499
+ onOpenChange={() => toggleSection("imageGeneration")}
500
+ >
501
+ <CollapsibleTrigger asChild>
502
+ <div className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50">
503
+ <div className="flex items-center gap-3">
504
+ <ChevronRight
505
+ className={`w-4 h-4 transition-transform ${openSections.imageGeneration ? "rotate-90" : ""}`}
506
+ />
507
+ {packageBuild.image_generation_error ? (
508
+ <AlertCircle className="w-5 h-5 text-red-500" />
509
+ ) : packageBuild.image_generation_completed_at ? (
510
+ <CheckCircle className="w-5 h-5 text-green-500" />
511
+ ) : packageBuild.image_generation_in_progress ? (
512
+ <Loader2 className="w-5 h-5 text-blue-500 animate-spin" />
513
+ ) : (
514
+ <Clock className="w-5 h-5 text-gray-400" />
515
+ )}
516
+ <span className="font-medium">Image Generation</span>
517
+ </div>
518
+ <div className="flex items-center gap-2">
519
+ {getStepDuration(
520
+ packageBuild.image_generation_started_at,
521
+ packageBuild.image_generation_completed_at,
522
+ ) && (
523
+ <span className="text-sm text-gray-600">
524
+ {getStepDuration(
525
+ packageBuild.image_generation_started_at,
526
+ packageBuild.image_generation_completed_at,
527
+ )}
528
+ </span>
529
+ )}
530
+ <Badge
531
+ variant={
532
+ getStepStatus(
533
+ packageBuild.image_generation_error,
534
+ packageBuild.image_generation_completed_at,
535
+ packageBuild.image_generation_in_progress,
536
+ ) === "success"
537
+ ? "default"
538
+ : getStepStatus(
539
+ packageBuild.image_generation_error,
540
+ packageBuild.image_generation_completed_at,
541
+ packageBuild.image_generation_in_progress,
542
+ ) === "error"
543
+ ? "destructive"
544
+ : "secondary"
545
+ }
546
+ className="text-xs"
547
+ >
548
+ {packageBuild.image_generation_error
549
+ ? "Failed"
550
+ : packageBuild.image_generation_completed_at
551
+ ? "Completed"
552
+ : packageBuild.image_generation_in_progress
553
+ ? "Running"
554
+ : "Queued"}
555
+ </Badge>
556
+ </div>
557
+ </div>
558
+ </CollapsibleTrigger>
559
+ <CollapsibleContent>
560
+ <div className="bg-white border-x border-b border-gray-200 rounded-b-lg p-4">
561
+ <div className="font-mono text-xs space-y-1">
562
+ {packageBuild.image_generation_error ? (
563
+ <div className="text-red-600 whitespace-pre-wrap">
564
+ {packageBuild.image_generation_error}
565
+ </div>
566
+ ) : packageBuild.image_generation_logs &&
567
+ packageBuild.image_generation_logs.length > 0 ? (
568
+ packageBuild.image_generation_logs.map(
569
+ (log: any, i: number) => (
570
+ <div
571
+ key={i}
572
+ className="text-gray-600 whitespace-pre-wrap"
573
+ >
574
+ {log.msg || log.message || JSON.stringify(log)}
575
+ </div>
576
+ ),
577
+ )
578
+ ) : (
579
+ <div className="text-gray-500">No logs available</div>
580
+ )}
581
+ </div>
582
+ </div>
583
+ </CollapsibleContent>
584
+ </Collapsible>
485
585
  </div>
486
586
  </div>
487
587
  )
@@ -8,18 +8,20 @@ import {
8
8
  } from "@/components/ui/dropdown-menu"
9
9
  import { MoreHorizontal, Clock, GitBranch, Eye } from "lucide-react"
10
10
  import { GitHubLogoIcon } from "@radix-ui/react-icons"
11
- import { useLocation } from "wouter"
12
11
  import { BuildsList } from "./BuildsList"
13
12
  import Header from "../Header"
14
13
  import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
15
14
  import { getBuildStatus } from "."
16
- import { PrefetchPageLink } from "../PrefetchPageLink"
15
+ import { Link } from "wouter"
17
16
  import { PackageBreadcrumb } from "../PackageBreadcrumb"
18
17
  import {
19
18
  Package,
20
19
  PackageBuild,
21
20
  PackageRelease,
22
21
  } from "fake-snippets-api/lib/db/schema"
22
+ import { useDownloadZip } from "@/hooks/use-download-zip"
23
+ import { useToast } from "@/hooks/use-toast"
24
+ import { usePackageFiles } from "@/hooks/use-package-files"
23
25
 
24
26
  export const PackageReleasesDashboard = ({
25
27
  latestRelease,
@@ -30,9 +32,21 @@ export const PackageReleasesDashboard = ({
30
32
  latestBuild: PackageBuild | null
31
33
  pkg: Package
32
34
  }) => {
33
- const [, setLocation] = useLocation()
34
35
  const { status, label } = getBuildStatus(latestBuild)
35
-
36
+ const { toastLibrary } = useToast()
37
+ const { downloadZip } = useDownloadZip()
38
+ const { data: packageFiles } = usePackageFiles(
39
+ latestRelease.package_release_id,
40
+ )
41
+ const handleDownloadZip = () => {
42
+ if (pkg && packageFiles) {
43
+ toastLibrary.promise(downloadZip(pkg, packageFiles ?? []), {
44
+ loading: "Downloading ZIP...",
45
+ success: "ZIP downloaded successfully!",
46
+ error: "Failed to download ZIP",
47
+ })
48
+ }
49
+ }
36
50
  return (
37
51
  <>
38
52
  <Header />
@@ -59,12 +73,12 @@ export const PackageReleasesDashboard = ({
59
73
  </div>
60
74
  <div className="min-w-0 flex-1">
61
75
  <div className="flex flex-col sm:flex-row sm:items-center gap-3">
62
- <PrefetchPageLink
76
+ <Link
63
77
  href={"/" + pkg.name}
64
78
  className="text-2xl font-bold text-gray-900 truncate"
65
79
  >
66
80
  {pkg.name}
67
- </PrefetchPageLink>
81
+ </Link>
68
82
  <Badge
69
83
  variant={
70
84
  status === "success"
@@ -139,7 +153,7 @@ export const PackageReleasesDashboard = ({
139
153
  <span className="sm:hidden">Repository</span>
140
154
  </Button>
141
155
  )}
142
- {latestBuild && (
156
+ {latestBuild && status !== "error" && (
143
157
  <Button
144
158
  variant="outline"
145
159
  size="sm"
@@ -178,10 +192,8 @@ export const PackageReleasesDashboard = ({
178
192
  </a>
179
193
  </DropdownMenuItem>
180
194
  )}
181
- <DropdownMenuItem asChild>
182
- <a href="#" download>
183
- Download Build
184
- </a>
195
+ <DropdownMenuItem asChild onClick={handleDownloadZip}>
196
+ <span>Download Zip</span>
185
197
  </DropdownMenuItem>
186
198
  <DropdownMenuItem
187
199
  onClick={() => {
@@ -317,9 +317,12 @@ const TreeNode = ({
317
317
  default={defaultNodeIcon}
318
318
  />
319
319
  <span className="text-sm truncate">{item.name}</span>
320
- <TreeActions isSelected={selectedItemId === item.id}>
321
- {item.actions}
322
- </TreeActions>
320
+ <div
321
+ className="flex items-center"
322
+ onClick={(e) => e.stopPropagation()}
323
+ >
324
+ <TreeActions isSelected={true}>{item.actions}</TreeActions>
325
+ </div>
323
326
  </AccordionTrigger>
324
327
  <AccordionContent className="ml-4 pl-1 border-l">
325
328
  <TreeItem
@@ -0,0 +1,51 @@
1
+ import { useMutation, useQueryClient } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import { useGlobalStore } from "@/hooks/use-global-store"
4
+
5
+ export const useAddOrgMemberMutation = ({
6
+ onSuccess,
7
+ onError,
8
+ }: { onSuccess?: () => void; onError?: (error: any) => void } = {}) => {
9
+ const axios = useAxios()
10
+ const session = useGlobalStore((s) => s.session)
11
+ const queryClient = useQueryClient()
12
+
13
+ return useMutation(
14
+ ["addOrgMember"],
15
+ async ({
16
+ orgId,
17
+ accountId,
18
+ githubUsername,
19
+ }: {
20
+ orgId: string
21
+ accountId?: string
22
+ githubUsername?: string
23
+ }) => {
24
+ if (!session) throw new Error("No session")
25
+
26
+ const payload: any = {
27
+ org_id: orgId,
28
+ }
29
+
30
+ if (accountId) {
31
+ payload.account_id = accountId
32
+ }
33
+
34
+ if (githubUsername) {
35
+ payload.github_username = githubUsername
36
+ }
37
+
38
+ await axios.post("/orgs/add_member", payload)
39
+ },
40
+ {
41
+ onSuccess: () => {
42
+ queryClient.invalidateQueries(["orgs", "members"])
43
+ onSuccess?.()
44
+ },
45
+ onError: (error: any) => {
46
+ console.error("Error adding organization member:", error)
47
+ onError?.(error)
48
+ },
49
+ },
50
+ )
51
+ }
@@ -0,0 +1,38 @@
1
+ import { useMutation } from "react-query"
2
+ import { useAxios } from "./use-axios"
3
+ import { useGlobalStore } from "./use-global-store"
4
+ import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
5
+
6
+ export const useCreateOrgMutation = ({
7
+ onSuccess,
8
+ }: { onSuccess?: (org: PublicOrgSchema) => void } = {}) => {
9
+ const axios = useAxios()
10
+ const session = useGlobalStore((s) => s.session)
11
+
12
+ return useMutation(
13
+ ["createOrg"],
14
+ async ({ name }: { name: string }) => {
15
+ if (!session) throw new Error("No session")
16
+
17
+ const {
18
+ data: { org: newOrg },
19
+ } = await axios.post("/orgs/create", {
20
+ name,
21
+ })
22
+
23
+ if (!newOrg) {
24
+ throw new Error("Failed to create organization")
25
+ }
26
+
27
+ return newOrg
28
+ },
29
+ {
30
+ onSuccess: (org: PublicOrgSchema) => {
31
+ onSuccess?.(org)
32
+ },
33
+ onError: (error: any) => {
34
+ console.error("Error creating organization:", error)
35
+ },
36
+ },
37
+ )
38
+ }
@@ -16,11 +16,13 @@ export const useCreatePackageMutation = ({
16
16
  description,
17
17
  is_private,
18
18
  is_unlisted,
19
+ org_id,
19
20
  }: {
20
21
  name?: string
21
22
  description?: string
22
23
  is_private?: boolean
23
24
  is_unlisted?: boolean
25
+ org_id?: string
24
26
  }) => {
25
27
  if (!session) throw new Error("No session")
26
28
 
@@ -31,6 +33,7 @@ export const useCreatePackageMutation = ({
31
33
  description,
32
34
  is_private,
33
35
  is_unlisted,
36
+ org_id,
34
37
  })
35
38
 
36
39
  if (!newPackage) {
@@ -21,12 +21,13 @@ export const useCurrentPackageRelease = (options?: {
21
21
 
22
22
  let query: Parameters<typeof usePackageRelease>[0] | null = null
23
23
 
24
- if (releaseId) {
24
+ // Prioritize package_name + is_latest for better caching consistency
25
+ if (author && packageName && !version && !releaseId) {
26
+ query = { package_name: `${author}/${packageName}`, is_latest: true }
27
+ } else if (releaseId) {
25
28
  query = { package_release_id: releaseId }
26
29
  } else if (version && author && packageName) {
27
30
  query = { package_name_with_version: `${author}/${packageName}@${version}` }
28
- } else if (author && packageName) {
29
- query = { package_name: `${author}/${packageName}`, is_latest: true }
30
31
  } else if (packageId) {
31
32
  query = { package_id: packageId, is_latest: true }
32
33
  }
@@ -20,8 +20,8 @@ export const useDownloadZip = () => {
20
20
 
21
21
  for (const file of visibleFiles) {
22
22
  try {
23
- const response = await axios.post("/package_files/get", {
24
- package_file_id: file.package_file_id,
23
+ const response = await axios.get("/package_files/get", {
24
+ params: { package_file_id: file.package_file_id },
25
25
  })
26
26
 
27
27
  const content = response.data.package_file?.content_text || ""
@@ -28,7 +28,9 @@ export const useGlobalStore = create<Store>()(
28
28
  ),
29
29
  )
30
30
 
31
- useGlobalStore.subscribe((state) => {
32
- ;(window as any).globalStore = state
33
- window.TSCIRCUIT_REGISTRY_TOKEN = state.session?.token ?? null
34
- })
31
+ if (typeof window !== "undefined") {
32
+ useGlobalStore.subscribe((state) => {
33
+ ;(window as any).globalStore = state
34
+ window.TSCIRCUIT_REGISTRY_TOKEN = state.session?.token ?? null
35
+ })
36
+ }
@@ -0,0 +1,164 @@
1
+ import React, { useCallback } from "react"
2
+ import { useToast } from "@/hooks/use-toast"
3
+ import { useGlobalStore } from "@/hooks/use-global-store"
4
+ import { useLocation } from "wouter"
5
+ import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
6
+ import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
7
+ import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
8
+ import { useAxios } from "@/hooks/use-axios"
9
+ import { JlcpcbComponentTsxLoadedPayload } from "@tscircuit/runframe/runner"
10
+
11
+ export const useJlcpcbComponentImport = () => {
12
+ const { toastLibrary } = useToast()
13
+ const session = useGlobalStore((s) => s.session)
14
+ const [, navigate] = useLocation()
15
+ const axios = useAxios()
16
+ const createPackageMutation = useCreatePackageMutation()
17
+ const createReleaseMutation = useCreatePackageReleaseMutation()
18
+ const createFilesMutation = useCreatePackageFilesMutation()
19
+
20
+ const runImport = useCallback(
21
+ async ({ result, tsx }: JlcpcbComponentTsxLoadedPayload) => {
22
+ if (!session) {
23
+ throw new Error("You must be logged in to import from JLCPCB")
24
+ }
25
+
26
+ const partNumber = result.component.partNumber || "component"
27
+
28
+ const normalizePartNumber = (input: string) =>
29
+ input
30
+ .replace(/^@/, "")
31
+ .trim()
32
+ .replace(/\s+/g, "-")
33
+ .replace(/[^a-zA-Z0-9-_/]/g, "-")
34
+ .replace(/--+/g, "-")
35
+ .replace(/-+$/g, "")
36
+ .replace(/^-+/g, "") || "component"
37
+
38
+ const componentSlug = normalizePartNumber(partNumber)
39
+ const packageName = `${session.github_username}/${componentSlug}`
40
+
41
+ const fetchExistingPackage = async () => {
42
+ try {
43
+ const { data } = await axios.post("/packages/get", {
44
+ name: packageName,
45
+ })
46
+ return data.package
47
+ } catch (error: any) {
48
+ const status = error?.response?.status || error?.status
49
+ if (status === 404) return null
50
+ throw error
51
+ }
52
+ }
53
+
54
+ const existingPackage = await fetchExistingPackage()
55
+ if (existingPackage) {
56
+ navigate(`/editor?package_id=${existingPackage.package_id}`)
57
+ return {
58
+ partNumber,
59
+ packageId: existingPackage.package_id,
60
+ existing: true,
61
+ }
62
+ }
63
+
64
+ const description =
65
+ result.component.description ||
66
+ `Generated from JLCPCB part number ${partNumber}`
67
+
68
+ let newPackage
69
+ try {
70
+ newPackage = await createPackageMutation.mutateAsync({
71
+ name: packageName,
72
+ description,
73
+ })
74
+ } catch (error) {
75
+ const fallbackPackage = await fetchExistingPackage()
76
+ if (fallbackPackage) {
77
+ navigate(`/editor?package_id=${fallbackPackage.package_id}`)
78
+ return {
79
+ partNumber,
80
+ packageId: fallbackPackage.package_id,
81
+ existing: true,
82
+ }
83
+ }
84
+ throw error
85
+ }
86
+
87
+ const release = await createReleaseMutation.mutateAsync({
88
+ package_id: newPackage.package_id,
89
+ version: "0.1.0",
90
+ is_latest: true,
91
+ })
92
+
93
+ await createFilesMutation.mutateAsync({
94
+ file_path: "index.tsx",
95
+ content_text: tsx,
96
+ package_release_id: release.package_release_id,
97
+ })
98
+
99
+ navigate(`/editor?package_id=${newPackage.package_id}`)
100
+ return {
101
+ partNumber,
102
+ packageId: newPackage.package_id,
103
+ existing: false,
104
+ }
105
+ },
106
+ [
107
+ createFilesMutation,
108
+ createPackageMutation,
109
+ createReleaseMutation,
110
+ axios,
111
+ navigate,
112
+ session,
113
+ ],
114
+ )
115
+
116
+ const importComponent = useCallback(
117
+ async (payload: JlcpcbComponentTsxLoadedPayload) => {
118
+ const importPromise = runImport(payload)
119
+
120
+ toastLibrary.promise(importPromise, {
121
+ loading: "Importing component...",
122
+ success: ({ partNumber, existing }) => (
123
+ <p>
124
+ {existing
125
+ ? `Component ${partNumber} already exists. Opening package in the editor.`
126
+ : `Component ${partNumber} imported successfully. Opening package in the editor.`}
127
+ </p>
128
+ ),
129
+ error: (error) => (
130
+ <p>
131
+ {error instanceof Error
132
+ ? error.message
133
+ : "Failed to import component"}
134
+ </p>
135
+ ),
136
+ })
137
+
138
+ await importPromise
139
+ },
140
+ [runImport, toastLibrary],
141
+ )
142
+
143
+ return {
144
+ importComponent,
145
+ }
146
+ }
147
+
148
+ export const openJlcpcbImportIssue = (
149
+ partNumber: string,
150
+ errorMessage: string,
151
+ ) => {
152
+ const url = getJlcpcbImportIssueUrl(partNumber, errorMessage)
153
+ window.open(url, "_blank")
154
+ }
155
+
156
+ const getJlcpcbImportIssueUrl = (partNumber: string, errorMessage: string) => {
157
+ const issueTitle = `[${partNumber}] Failed to import from JLCPCB`
158
+ const issueBody = `I tried to import the part number ${partNumber} from JLCPCB, but it failed. Here's the error I got:\n\`\`\`\n${errorMessage}\n\`\`\`\n\nCould be an issue in \`fetchEasyEDAComponent\` or \`convertRawEasyEdaToTs\``
159
+ const issueLabels = "snippets,good first issue"
160
+ const url = `https://github.com/tscircuit/easyeda-converter/issues/new?title=${encodeURIComponent(
161
+ issueTitle,
162
+ )}&body=${encodeURIComponent(issueBody)}&labels=${encodeURIComponent(issueLabels)}`
163
+ return url
164
+ }
@@ -0,0 +1,27 @@
1
+ import { useQuery } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import type { Account } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export const useListOrgMembers = ({
6
+ orgId,
7
+ orgName,
8
+ }: { orgId?: string; orgName?: string }) => {
9
+ const axios = useAxios()
10
+ return useQuery<Account[], Error & { status: number }>(
11
+ ["orgs", "members", orgId || orgName],
12
+ async () => {
13
+ if (!orgId && !orgName) {
14
+ throw new Error("Organization ID or name is required")
15
+ }
16
+ const params = orgId ? { org_id: orgId } : { name: orgName }
17
+ const { data } = await axios.get("/orgs/list_members", { params })
18
+ return data.members
19
+ },
20
+ {
21
+ enabled: Boolean(orgId || orgName),
22
+ retry: false,
23
+ refetchOnWindowFocus: false,
24
+ keepPreviousData: true,
25
+ },
26
+ )
27
+ }
@@ -0,0 +1,25 @@
1
+ import { useQuery } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
4
+ import { useGlobalStore } from "./use-global-store"
5
+
6
+ export const useListUserOrgs = (githubHandle?: string) => {
7
+ const axios = useAxios()
8
+ const session = useGlobalStore((s) => s.session)
9
+ const github_handle = githubHandle || session?.github_username
10
+
11
+ return useQuery<PublicOrgSchema[], Error & { status: number }>(
12
+ ["orgs", "list", github_handle],
13
+ async () => {
14
+ const { data } = await axios.get("/orgs/list", {
15
+ ...(github_handle && { params: { github_handle } }),
16
+ })
17
+ return data.orgs
18
+ },
19
+ {
20
+ retry: false,
21
+ refetchOnWindowFocus: false,
22
+ enabled: Boolean(github_handle),
23
+ },
24
+ )
25
+ }