@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.
Files changed (185) 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 +32 -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 +151 -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 +361 -453
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1313 -639
  36. package/dist/index.d.ts +313 -6
  37. package/dist/index.js +328 -24
  38. package/dist/schema.d.ts +290 -1
  39. package/dist/schema.js +54 -1
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +219 -4
  42. package/fake-snippets-api/lib/db/schema.ts +63 -1
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
  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 +33 -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 +48 -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 +60 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +118 -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 +57 -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 +25 -19
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +27 -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 +17 -5
  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/SentryNotFoundReporter.tsx +44 -0
  84. package/src/components/UserCard.tsx +80 -0
  85. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  86. package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
  87. package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
  88. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
  89. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
  90. package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
  91. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
  92. package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
  93. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  94. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  95. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  96. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  97. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  98. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  99. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  100. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  101. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  102. package/src/components/organization/OrganizationCard.tsx +206 -0
  103. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  104. package/src/components/organization/OrganizationHeader.tsx +154 -0
  105. package/src/components/organization/OrganizationMembers.tsx +146 -0
  106. package/src/components/package-port/CodeAndPreview.tsx +15 -12
  107. package/src/components/package-port/CodeEditor.tsx +4 -30
  108. package/src/components/package-port/CodeEditorHeader.tsx +123 -61
  109. package/src/components/package-port/EditorNav.tsx +32 -49
  110. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  111. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  112. package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
  113. package/src/components/ui/tree-view.tsx +6 -3
  114. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  115. package/src/hooks/use-create-org-mutation.ts +38 -0
  116. package/src/hooks/use-create-package-mutation.ts +3 -0
  117. package/src/hooks/use-current-package-release.ts +4 -3
  118. package/src/hooks/use-download-zip.ts +2 -2
  119. package/src/hooks/use-global-store.ts +6 -4
  120. package/src/hooks/use-hydration.ts +30 -0
  121. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  122. package/src/hooks/use-list-org-members.ts +27 -0
  123. package/src/hooks/use-list-user-orgs.ts +25 -0
  124. package/src/hooks/use-org-by-github-handle.ts +26 -0
  125. package/src/hooks/use-org.ts +24 -0
  126. package/src/hooks/use-organization.ts +42 -0
  127. package/src/hooks/use-package-as-snippet.ts +4 -2
  128. package/src/hooks/use-package-builds.ts +6 -2
  129. package/src/hooks/use-package-files.ts +5 -3
  130. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  131. package/src/hooks/use-package-release-images.ts +105 -0
  132. package/src/hooks/use-package-release.ts +2 -2
  133. package/src/hooks/use-package-stars.ts +80 -4
  134. package/src/hooks/use-preview-images.ts +6 -3
  135. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  136. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  137. package/src/hooks/use-update-org-mutation.ts +41 -0
  138. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  139. package/src/hooks/useFileManagement.ts +51 -22
  140. package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
  141. package/src/hooks/usePackageFilesLoader.ts +2 -2
  142. package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
  143. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
  144. package/src/lib/download-fns/download-kicad-files.ts +22 -11
  145. package/src/lib/download-fns/download-step.ts +12 -0
  146. package/src/lib/normalize-svg-for-tile.ts +50 -0
  147. package/src/lib/posthog.ts +11 -9
  148. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  149. package/src/lib/sentry.ts +14 -0
  150. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  151. package/src/lib/ts-lib-cache.ts +122 -7
  152. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  153. package/src/lib/utils/findTargetFile.ts +45 -10
  154. package/src/lib/utils/isComponentExported.ts +2 -1
  155. package/src/main.tsx +2 -1
  156. package/src/pages/create-organization.tsx +169 -0
  157. package/src/pages/dashboard.tsx +38 -6
  158. package/src/pages/datasheet.tsx +1 -1
  159. package/src/pages/datasheets.tsx +3 -3
  160. package/src/pages/editor.tsx +4 -6
  161. package/src/pages/landing.tsx +6 -6
  162. package/src/pages/latest.tsx +3 -0
  163. package/src/pages/organization-profile.tsx +199 -0
  164. package/src/pages/organization-settings.tsx +569 -0
  165. package/src/pages/package-editor.tsx +21 -21
  166. package/src/pages/preview-release.tsx +75 -145
  167. package/src/pages/quickstart.tsx +159 -123
  168. package/src/pages/release-detail.tsx +119 -31
  169. package/src/pages/search.tsx +197 -57
  170. package/src/pages/settings-redirect.tsx +44 -0
  171. package/src/pages/trending.tsx +29 -20
  172. package/src/pages/user-profile.tsx +58 -7
  173. package/src/pages/user-settings.tsx +161 -0
  174. package/src/pages/view-package.tsx +30 -16
  175. package/vite.config.ts +9 -0
  176. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  177. package/src/components/JLCPCBImportDialog.tsx +0 -280
  178. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  179. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
  180. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
  181. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  182. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  183. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  184. package/src/components/PageSearchComponent.tsx +0 -148
  185. package/src/pages/package-builds.tsx +0 -33
@@ -1,5 +1,5 @@
1
1
  import { Button } from "@/components/ui/button"
2
- import { GitFork, Star } from "lucide-react"
2
+ import { GitFork, Loader2, Star } from "lucide-react"
3
3
  import {
4
4
  DropdownMenu,
5
5
  DropdownMenuContent,
@@ -42,7 +42,6 @@ import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
42
42
  import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
43
43
  import { TypeBadge } from "@/components/TypeBadge"
44
44
  import { useForkPackageMutation } from "@/hooks/useForkPackageMutation"
45
- import tscircuitCorePkg from "@tscircuit/core/package.json"
46
45
  import { useRenamePackageDialog } from "../dialogs/rename-package-dialog"
47
46
  import { useUpdatePackageDescriptionDialog } from "../dialogs/update-package-description-dialog"
48
47
  import { useCreateReleaseDialog } from "@/hooks/use-create-release-dialog"
@@ -295,52 +294,37 @@ export default function EditorNav({
295
294
  Not logged in, can't save
296
295
  </div>
297
296
  )}
298
- <Button
299
- variant="outline"
300
- size="sm"
301
- className={"ml-1 h-6 px-2 text-xs save-button"}
302
- disabled={canSavePackage && pkg ? !hasUnsavedChanges : !isLoggedIn}
303
- onClick={canSavePackage ? onSave : () => forkSnippet()}
304
- >
305
- {canSavePackage ? (
306
- <>
307
- <Save className="mr-1 h-3 w-3" />
308
- Save
309
- </>
310
- ) : (
311
- <>
312
- <GitFork className="mr-1 h-3 w-3" />
313
- Fork
314
- </>
315
- )}
316
- </Button>
297
+ {(canSavePackage || (isLoggedIn && pkg)) && (
298
+ <Button
299
+ variant="outline"
300
+ size="sm"
301
+ className={"ml-1 h-6 px-2 text-xs save-button"}
302
+ disabled={
303
+ canSavePackage && pkg ? !hasUnsavedChanges : !isLoggedIn
304
+ }
305
+ onClick={canSavePackage ? onSave : () => forkSnippet()}
306
+ >
307
+ {canSavePackage ? (
308
+ <>
309
+ <Save className="mr-1 h-3 w-3" />
310
+ Save
311
+ </>
312
+ ) : (
313
+ <>
314
+ <GitFork className="mr-1 h-3 w-3" />
315
+ Fork
316
+ </>
317
+ )}
318
+ </Button>
319
+ )}
317
320
  {isSaving && (
318
- <div className="animate-fadeIn bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
319
- <svg
320
- className="animate-spin h-3 w-3 mr-2 text-blue-600"
321
- xmlns="http://www.w3.org/2000/svg"
322
- fill="none"
323
- viewBox="0 0 24 24"
324
- >
325
- <circle
326
- className="opacity-25"
327
- cx="12"
328
- cy="12"
329
- r="10"
330
- stroke="currentColor"
331
- strokeWidth="4"
332
- ></circle>
333
- <path
334
- className="opacity-75"
335
- fill="currentColor"
336
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
337
- ></path>
338
- </svg>
321
+ <div className="animate-fadeIn bg-blue-100 select-none text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
322
+ <Loader2 className="animate-spin h-3 w-3 mr-2 text-blue-600" />
339
323
  Saving...
340
324
  </div>
341
325
  )}
342
326
  {hasUnsavedChanges && !isSaving && isLoggedIn && (
343
- <div className="animate-fadeIn bg-yellow-100 text-yellow-800 text-xs font-medium px-2.5 py-0.5 rounded">
327
+ <div className="animate-fadeIn bg-yellow-100 select-none text-yellow-800 text-xs font-medium px-2.5 py-0.5 rounded">
344
328
  {pkg ? "unsaved changes" : "unsaved"}
345
329
  </div>
346
330
  )}
@@ -359,7 +343,6 @@ export default function EditorNav({
359
343
  </div>
360
344
  <div className="flex items-center justify-end -space-x-1">
361
345
  <div className="flex mx-2 items-center space-x-1">
362
- {pkg && <TypeBadge type={`${packageType ?? pkg.snippet_type}`} />}
363
346
  {/* <Button
364
347
  variant="ghost"
365
348
  size="sm"
@@ -481,9 +464,6 @@ export default function EditorNav({
481
464
  </DropdownMenuItem>
482
465
  </>
483
466
  )}
484
- <DropdownMenuItem className="text-xs text-gray-500" disabled>
485
- @tscircuit/core@{tscircuitCorePkg.version}
486
- </DropdownMenuItem>
487
467
  </DropdownMenuContent>
488
468
  </DropdownMenu>
489
469
 
@@ -505,7 +485,7 @@ export default function EditorNav({
505
485
  )}
506
486
  </Button>
507
487
  </div>
508
- <div className="flex items-center ">
488
+ <div className="flex items-center">
509
489
  <DropdownMenu>
510
490
  <DropdownMenuTrigger asChild>
511
491
  <div className="md:hidden rounded-full p-1 hover:bg-gray-100 cursor-pointer">
@@ -514,7 +494,7 @@ export default function EditorNav({
514
494
  </Button>
515
495
  </div>
516
496
  </DropdownMenuTrigger>
517
- <DropdownMenuContent>
497
+ <DropdownMenuContent className="z-[101]">
518
498
  {hasUnsavedChanges && onDiscard && (
519
499
  <DropdownMenuItem
520
500
  className="text-xs text-red-600"
@@ -601,6 +581,9 @@ export default function EditorNav({
601
581
  <RenameDialog
602
582
  packageId={pkg?.package_id ?? ""}
603
583
  currentName={pkg?.unscoped_name ?? ""}
584
+ onRename={() => {
585
+ qc.invalidateQueries({ queryKey: ["package", pkg?.package_id] })
586
+ }}
604
587
  />
605
588
  <DeleteDialog
606
589
  packageId={pkg?.package_id ?? ""}
@@ -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,30 @@
1
+ import { useEffect, useState } from "react"
2
+ import { useGlobalStore } from "@/hooks/use-global-store"
3
+
4
+ export const useHydration = () => {
5
+ const [hasHydrated, setHasHydrated] = useState(() => {
6
+ if (typeof window === "undefined") return false
7
+ return useGlobalStore.persist?.hasHydrated?.() ?? false
8
+ })
9
+
10
+ useEffect(() => {
11
+ if (typeof window === "undefined") return
12
+
13
+ if (useGlobalStore.persist?.hasHydrated?.()) {
14
+ setHasHydrated(true)
15
+ return
16
+ }
17
+
18
+ const unsubFinishHydration = useGlobalStore.persist?.onFinishHydration?.(
19
+ () => {
20
+ setHasHydrated(true)
21
+ },
22
+ )
23
+
24
+ return () => {
25
+ unsubFinishHydration?.()
26
+ }
27
+ }, [])
28
+
29
+ return hasHydrated
30
+ }