@tscircuit/fake-snippets 0.0.108 → 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 (203) 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 +389 -450
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1255 -625
  36. package/dist/index.d.ts +296 -4
  37. package/dist/index.js +325 -24
  38. package/dist/schema.d.ts +282 -1
  39. package/dist/schema.js +54 -2
  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 +62 -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 +27 -24
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +29 -10
  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 +133 -36
  71. package/src/components/FileSidebar.tsx +41 -50
  72. package/src/components/Footer.tsx +8 -10
  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 +44 -16
  77. package/src/components/HiddenFilesDropdown.tsx +0 -2
  78. package/src/components/NotFound.tsx +5 -5
  79. package/src/components/PackageBreadcrumb.tsx +6 -12
  80. package/src/components/PackageCard.tsx +0 -1
  81. package/src/components/PackageSearchResults.tsx +1 -1
  82. package/src/components/PrefetchPageLink.tsx +7 -1
  83. package/src/components/ProfileRouter.tsx +32 -0
  84. package/src/components/SearchComponent.tsx +12 -8
  85. package/src/components/UserCard.tsx +80 -0
  86. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
  87. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  88. package/src/components/ViewPackagePage/components/important-files-view.tsx +174 -87
  89. package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -4
  90. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -2
  91. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -20
  92. package/src/components/ViewPackagePage/components/package-header.tsx +26 -37
  93. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -19
  94. package/src/components/ViewPackagePage/components/repo-page-content.tsx +33 -25
  95. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  96. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  97. package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
  98. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
  99. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  100. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  101. package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
  102. package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
  103. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  104. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  105. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  106. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  107. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  108. package/src/components/organization/OrganizationCard.tsx +204 -0
  109. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  110. package/src/components/organization/OrganizationHeader.tsx +154 -0
  111. package/src/components/organization/OrganizationMembers.tsx +146 -0
  112. package/src/components/package-port/CodeAndPreview.tsx +32 -46
  113. package/src/components/package-port/CodeEditor.tsx +28 -31
  114. package/src/components/package-port/CodeEditorHeader.tsx +128 -63
  115. package/src/components/package-port/EditorNav.tsx +32 -49
  116. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  117. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  118. package/src/components/preview/PackageReleasesDashboard.tsx +53 -36
  119. package/src/components/ui/tree-view.tsx +6 -3
  120. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  121. package/src/hooks/use-create-org-mutation.ts +38 -0
  122. package/src/hooks/use-create-package-mutation.ts +3 -0
  123. package/src/hooks/use-current-package-id.ts +5 -30
  124. package/src/hooks/use-current-package-info.ts +29 -5
  125. package/src/hooks/use-current-package-release.ts +4 -3
  126. package/src/hooks/use-download-zip.ts +2 -2
  127. package/src/hooks/use-global-store.ts +6 -4
  128. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  129. package/src/hooks/use-list-org-members.ts +27 -0
  130. package/src/hooks/use-list-user-orgs.ts +25 -0
  131. package/src/hooks/use-org-by-github-handle.ts +26 -0
  132. package/src/hooks/use-org.ts +24 -0
  133. package/src/hooks/use-organization.ts +42 -0
  134. package/src/hooks/use-package-as-snippet.ts +4 -2
  135. package/src/hooks/use-package-builds.ts +6 -2
  136. package/src/hooks/use-package-files.ts +5 -3
  137. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  138. package/src/hooks/use-package-release-images.ts +105 -0
  139. package/src/hooks/use-package-release.ts +2 -2
  140. package/src/hooks/use-package-stars.ts +80 -4
  141. package/src/hooks/use-preview-images.ts +6 -3
  142. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  143. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  144. package/src/hooks/use-update-org-mutation.ts +41 -0
  145. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  146. package/src/hooks/useFileManagement.ts +183 -35
  147. package/src/hooks/useOptimizedPackageFilesLoader.ts +136 -0
  148. package/src/hooks/usePackageFilesLoader.ts +2 -2
  149. package/src/hooks/useUpdatePackageFilesMutation.ts +15 -1
  150. package/src/lib/download-fns/download-circuit-png.ts +11 -3
  151. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
  152. package/src/lib/download-fns/download-kicad-files.ts +12 -11
  153. package/src/lib/normalize-svg-for-tile.ts +50 -0
  154. package/src/lib/posthog.ts +11 -9
  155. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  156. package/src/lib/sentry.ts +14 -0
  157. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  158. package/src/lib/ts-lib-cache.ts +122 -7
  159. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  160. package/src/lib/utils/findTargetFile.ts +45 -10
  161. package/src/lib/utils/isComponentExported.ts +10 -0
  162. package/src/main.tsx +2 -1
  163. package/src/pages/authorize.tsx +0 -2
  164. package/src/pages/create-organization.tsx +168 -0
  165. package/src/pages/dashboard.tsx +38 -6
  166. package/src/pages/datasheet.tsx +1 -1
  167. package/src/pages/datasheets.tsx +3 -3
  168. package/src/pages/editor.tsx +4 -6
  169. package/src/pages/landing.tsx +6 -7
  170. package/src/pages/latest.tsx +3 -0
  171. package/src/pages/organization-profile.tsx +199 -0
  172. package/src/pages/organization-settings.tsx +566 -0
  173. package/src/pages/package-editor.tsx +21 -21
  174. package/src/pages/preview-release.tsx +76 -136
  175. package/src/pages/quickstart.tsx +159 -123
  176. package/src/pages/release-detail.tsx +119 -31
  177. package/src/pages/search.tsx +192 -57
  178. package/src/pages/settings-redirect.tsx +44 -0
  179. package/src/pages/trending.tsx +29 -20
  180. package/src/pages/user-profile.tsx +58 -7
  181. package/src/pages/view-package.tsx +21 -26
  182. package/vite.config.ts +9 -0
  183. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  184. package/src/components/Footer2.tsx +0 -100
  185. package/src/components/JLCPCBImportDialog.tsx +0 -280
  186. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  187. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -115
  188. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -27
  189. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  190. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  191. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  192. package/src/components/PageSearchComponent.tsx +0 -148
  193. package/src/components/ShippingInformationForm.tsx +0 -423
  194. package/src/components/StaticViewSnippetHeader.tsx +0 -70
  195. package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
  196. package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
  197. package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
  198. package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
  199. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
  200. package/src/components/ViewSnippetHeader.tsx +0 -181
  201. package/src/components/ui/input-otp.tsx +0 -69
  202. package/src/pages/package-builds.tsx +0 -33
  203. package/src/pages/settings.tsx +0 -25
@@ -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"
@@ -91,8 +105,9 @@ export const PackageReleasesDashboard = ({
91
105
  </div>
92
106
  <div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 mt-2 text-sm text-gray-600">
93
107
  <div
94
- className="flex cursor-pointer items-center gap-1"
108
+ className={`flex items-center gap-1 ${pkg.github_repo_full_name ? "cursor-pointer" : ""}`}
95
109
  onClick={() =>
110
+ pkg.github_repo_full_name &&
96
111
  window?.open(
97
112
  `https://github.com/${pkg.github_repo_full_name}/tree/${latestRelease?.branch_name || "main"}`,
98
113
  "_blank",
@@ -121,22 +136,24 @@ export const PackageReleasesDashboard = ({
121
136
  </div>
122
137
 
123
138
  <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
124
- <Button
125
- variant="outline"
126
- size="sm"
127
- className="flex items-center gap-2 justify-center min-w-[120px] h-9"
128
- onClick={() =>
129
- window.open(
130
- `https://github.com/${pkg.github_repo_full_name}`,
131
- "_blank",
132
- )
133
- }
134
- >
135
- <GitHubLogoIcon className="w-4 h-4" />
136
- <span className="hidden sm:inline">Repository</span>
137
- <span className="sm:hidden">Repository</span>
138
- </Button>
139
- {latestBuild && (
139
+ {pkg.github_repo_full_name && (
140
+ <Button
141
+ variant="outline"
142
+ size="sm"
143
+ className="flex items-center gap-2 justify-center min-w-[120px] h-9"
144
+ onClick={() =>
145
+ window.open(
146
+ `https://github.com/${pkg.github_repo_full_name}`,
147
+ "_blank",
148
+ )
149
+ }
150
+ >
151
+ <GitHubLogoIcon className="w-4 h-4" />
152
+ <span className="hidden sm:inline">Repository</span>
153
+ <span className="sm:hidden">Repository</span>
154
+ </Button>
155
+ )}
156
+ {latestBuild && status !== "error" && (
140
157
  <Button
141
158
  variant="outline"
142
159
  size="sm"
@@ -164,19 +181,19 @@ export const PackageReleasesDashboard = ({
164
181
  </Button>
165
182
  </DropdownMenuTrigger>
166
183
  <DropdownMenuContent align="end">
167
- <DropdownMenuItem asChild>
168
- <a
169
- href={`https://github.com/${pkg.github_repo_full_name}`}
170
- target="_blank"
171
- rel="noopener noreferrer"
172
- >
173
- View Source
174
- </a>
175
- </DropdownMenuItem>
176
- <DropdownMenuItem asChild>
177
- <a href="#" download>
178
- Download Build
179
- </a>
184
+ {pkg.github_repo_full_name && (
185
+ <DropdownMenuItem asChild>
186
+ <a
187
+ href={`https://github.com/${pkg.github_repo_full_name}`}
188
+ target="_blank"
189
+ rel="noopener noreferrer"
190
+ >
191
+ View Source
192
+ </a>
193
+ </DropdownMenuItem>
194
+ )}
195
+ <DropdownMenuItem asChild onClick={handleDownloadZip}>
196
+ <span>Download Zip</span>
180
197
  </DropdownMenuItem>
181
198
  <DropdownMenuItem
182
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) {
@@ -1,41 +1,16 @@
1
- import { useEffect, useState } from "react"
2
- import { useParams } from "wouter"
3
- import { usePackageById } from "./use-package-by-package-id"
4
- import { usePackageByName } from "./use-package-by-package-name"
5
- import { useUrlParams } from "./use-url-params"
1
+ import { useCurrentPackageInfo } from "./use-current-package-info"
6
2
 
7
3
  export const useCurrentPackageId = (): {
8
4
  packageId: string | null
9
5
  isLoading: boolean
10
6
  error: (Error & { status: number }) | null
11
7
  } => {
12
- const urlParams = useUrlParams()
13
- const urlPackageId = urlParams.package_id
14
- const wouter = useParams()
15
- const [packageIdFromUrl, setPackageId] = useState<string | null>(urlPackageId)
16
-
17
- useEffect(() => {
18
- if (urlPackageId) {
19
- setPackageId(urlPackageId)
20
- }
21
- }, [urlPackageId])
22
-
23
- const packageName =
24
- wouter.author && wouter.packageName
25
- ? `${wouter.author}/${wouter.packageName}`
26
- : null
27
-
28
- const {
29
- data: packageByName,
30
- isLoading: isLoadingPackageByName,
31
- error: errorPackageByName,
32
- } = usePackageByName(packageName)
33
-
34
- const packageId = packageIdFromUrl ?? packageByName?.package_id ?? null
8
+ const { packageInfo, isLoading, error } = useCurrentPackageInfo()
9
+ const packageId = packageInfo?.package_id ?? null
35
10
 
36
11
  return {
37
12
  packageId,
38
- isLoading: isLoadingPackageByName,
39
- error: errorPackageByName,
13
+ isLoading,
14
+ error,
40
15
  }
41
16
  }
@@ -1,8 +1,32 @@
1
- import { useCurrentPackageId } from "./use-current-package-id"
1
+ import { useParams } from "wouter"
2
2
  import { usePackageById } from "./use-package-by-package-id"
3
+ import { usePackageByName } from "./use-package-by-package-name"
4
+ import { useUrlParams } from "./use-url-params"
5
+ import type { Package } from "fake-snippets-api/lib/db/schema"
3
6
 
4
- export const useCurrentPackageInfo = () => {
5
- const { packageId } = useCurrentPackageId()
6
- const { data: packageInfo, ...rest } = usePackageById(packageId)
7
- return { packageInfo, ...rest }
7
+ export const useCurrentPackageInfo = (): {
8
+ packageInfo: Package | undefined
9
+ isLoading: boolean
10
+ error: (Error & { status: number }) | null
11
+ refetch: () => Promise<unknown>
12
+ } => {
13
+ const urlParams = useUrlParams()
14
+ const packageIdFromQuery = urlParams.package_id ?? null
15
+
16
+ const { author, packageName } = useParams()
17
+ const packageSlug = author && packageName ? `${author}/${packageName}` : null
18
+
19
+ const queryById = usePackageById(packageIdFromQuery)
20
+ const queryByName = usePackageByName(packageSlug)
21
+
22
+ const data = queryById.data ?? queryByName.data
23
+ const isLoading = queryById.isLoading || queryByName.isLoading
24
+ const error =
25
+ (queryById.error as (Error & { status: number }) | null) ??
26
+ (queryByName.error as (Error & { status: number }) | null) ??
27
+ null
28
+
29
+ const refetch = packageIdFromQuery ? queryById.refetch : queryByName.refetch
30
+
31
+ return { packageInfo: data, isLoading, error, refetch }
8
32
  }
@@ -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, prevState) => {
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
+ }
@@ -0,0 +1,26 @@
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 useOrgByGithubHandle = (githubHandle: string | null) => {
7
+ const axios = useAxios()
8
+ const session = useGlobalStore((s) => s.session)
9
+ return useQuery<PublicOrgSchema, Error & { status: number }>(
10
+ ["orgs", "by-github-handle", githubHandle],
11
+ async () => {
12
+ if (!githubHandle) {
13
+ throw new Error("GitHub handle is required")
14
+ }
15
+ const { data } = await axios.get("/orgs/get", {
16
+ params: { github_handle: githubHandle },
17
+ })
18
+ return data.org
19
+ },
20
+ {
21
+ enabled: Boolean(githubHandle && session),
22
+ retry: false,
23
+ refetchOnWindowFocus: false,
24
+ },
25
+ )
26
+ }
@@ -0,0 +1,24 @@
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
+
5
+ export const useOrg = ({ orgName }: { orgName: string | null }) => {
6
+ const axios = useAxios()
7
+ return useQuery<PublicOrgSchema, Error & { status: number }>(
8
+ ["orgs", orgName],
9
+ async () => {
10
+ if (!orgName) {
11
+ throw new Error("Organization name is required")
12
+ }
13
+ const { data } = await axios.get("/orgs/get", {
14
+ params: { org_name: orgName },
15
+ })
16
+ return data.org
17
+ },
18
+ {
19
+ enabled: Boolean(orgName),
20
+ retry: false,
21
+ refetchOnWindowFocus: false,
22
+ },
23
+ )
24
+ }