@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
@@ -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),
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
+ }
@@ -0,0 +1,42 @@
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 useOrganization = ({
6
+ orgId,
7
+ orgName,
8
+ github_handle,
9
+ }: { orgId?: string; orgName?: string; github_handle?: string }) => {
10
+ const axios = useAxios()
11
+
12
+ const orgQuery = useQuery<PublicOrgSchema, Error & { status: number }>(
13
+ ["orgs", "get", orgId || orgName || github_handle],
14
+ async () => {
15
+ if (!orgId && !orgName && !github_handle) {
16
+ throw new Error("Organization ID, name, or GitHub handle is required")
17
+ }
18
+ const params = orgId
19
+ ? { org_id: orgId }
20
+ : orgName
21
+ ? { org_name: orgName }
22
+ : { github_handle }
23
+ const { data } = await axios.get("/orgs/get", { params })
24
+ return data.org
25
+ },
26
+ {
27
+ enabled: Boolean(orgId || orgName),
28
+ retry: false,
29
+ refetchOnWindowFocus: false,
30
+ keepPreviousData: true,
31
+ },
32
+ )
33
+
34
+ return {
35
+ organization: orgQuery.data,
36
+ membersCount: orgQuery.data?.member_count || 0,
37
+ packagesCount: orgQuery.data?.package_count || 0,
38
+ isLoading: orgQuery.isLoading,
39
+ isError: orgQuery.isError,
40
+ error: orgQuery.error,
41
+ }
42
+ }
@@ -32,8 +32,10 @@ export const usePackageAsSnippet = (packageId: string | null) => {
32
32
  if (!packageQuery.data?.latest_package_release_id) {
33
33
  throw new Error("No latest release ID available")
34
34
  }
35
- const { data } = await axios.post("/package_files/list", {
36
- package_release_id: packageQuery.data.latest_package_release_id,
35
+ const { data } = await axios.get("/package_files/list", {
36
+ params: {
37
+ package_release_id: packageQuery.data.latest_package_release_id,
38
+ },
37
39
  })
38
40
  return data.package_files
39
41
  },
@@ -56,11 +56,14 @@ export const usePackageBuildsByReleaseId = (releaseId?: string | null) => {
56
56
  return usePackageBuilds(releaseId ? { package_release_id: releaseId } : null)
57
57
  }
58
58
 
59
- export const usePackageBuild = (packageBuildId: string | null) => {
59
+ export const usePackageBuild = (
60
+ packageBuildId: string | null,
61
+ options?: { include_logs?: boolean },
62
+ ) => {
60
63
  const axios = useAxios()
61
64
 
62
65
  return useQuery<PackageBuild, Error & { status: number }>(
63
- ["packageBuild", packageBuildId],
66
+ ["packageBuild", packageBuildId, options?.include_logs],
64
67
  async () => {
65
68
  if (!packageBuildId) {
66
69
  throw new Error("package_build_id is required")
@@ -69,6 +72,7 @@ export const usePackageBuild = (packageBuildId: string | null) => {
69
72
  const { data } = await axios.get("/package_builds/get", {
70
73
  params: {
71
74
  package_build_id: packageBuildId,
75
+ include_logs: options?.include_logs,
72
76
  },
73
77
  })
74
78
 
@@ -36,7 +36,9 @@ export const usePackageFile = (
36
36
  async () => {
37
37
  if (!query) return
38
38
 
39
- const { data } = await axios.post("/package_files/get", query)
39
+ const { data } = await axios.get("/package_files/get", {
40
+ params: query,
41
+ })
40
42
 
41
43
  if (!data.package_file) {
42
44
  throw new Error("Package file not found")
@@ -98,8 +100,8 @@ export const usePackageFiles = (packageReleaseId?: string | null) => {
98
100
  if (!packageReleaseId) return []
99
101
 
100
102
  try {
101
- const { data } = await axios.post("/package_files/list", {
102
- package_release_id: packageReleaseId,
103
+ const { data } = await axios.get("/package_files/list", {
104
+ params: { package_release_id: packageReleaseId },
103
105
  })
104
106
 
105
107
  if (!data.package_files) {
@@ -1,36 +1,45 @@
1
- import {
2
- usePackageReleaseById,
3
- usePackageReleaseByNameAndVersion,
4
- } from "./use-package-release"
1
+ import { usePackageRelease } from "./use-package-release"
5
2
  import { isUuid } from "@/lib/utils/isUuid"
6
3
 
7
4
  export const usePackageReleaseByIdOrVersion = (
8
5
  releaseIdOrVersion: string | null,
9
6
  packageName?: string | null,
7
+ options?: {
8
+ include_logs?: boolean
9
+ include_ai_review?: boolean
10
+ refetchInterval?:
11
+ | number
12
+ | false
13
+ | ((
14
+ data:
15
+ | import("fake-snippets-api/lib/db/schema").PackageRelease
16
+ | undefined,
17
+ ) => number | false)
18
+ },
10
19
  ) => {
11
20
  const isReleaseIdUuid = releaseIdOrVersion
12
21
  ? isUuid(releaseIdOrVersion)
13
22
  : false
14
23
 
15
- // If it's a UUID, use the ID-based hook
16
- const releaseByIdQuery = usePackageReleaseById(
17
- isReleaseIdUuid ? releaseIdOrVersion : null,
18
- )
24
+ let query: Parameters<typeof usePackageRelease>[0] = null
19
25
 
20
- // If it's not a UUID and we have a package name, construct the version-based query
21
- const packageNameWithVersion =
22
- !isReleaseIdUuid && packageName && releaseIdOrVersion
23
- ? `${packageName}@${releaseIdOrVersion}`
26
+ if (isReleaseIdUuid) {
27
+ query = releaseIdOrVersion
28
+ ? { package_release_id: releaseIdOrVersion }
24
29
  : null
30
+ } else if (packageName && releaseIdOrVersion) {
31
+ query = {
32
+ package_name_with_version: `${packageName}@${releaseIdOrVersion}`,
33
+ }
34
+ }
25
35
 
26
- const releaseByVersionQuery = usePackageReleaseByNameAndVersion(
27
- packageNameWithVersion,
28
- )
36
+ if (query && options?.include_logs !== undefined) {
37
+ query.include_logs = options.include_logs
38
+ }
29
39
 
30
- // Return the appropriate query result
31
- if (isReleaseIdUuid) {
32
- return releaseByIdQuery
33
- } else {
34
- return releaseByVersionQuery
40
+ if (query && options?.include_ai_review !== undefined) {
41
+ query.include_ai_review = options.include_ai_review
35
42
  }
43
+
44
+ return usePackageRelease(query, { refetchInterval: options?.refetchInterval })
36
45
  }
@@ -0,0 +1,105 @@
1
+ import { useMemo, useCallback } from "react"
2
+ import { useQueries } from "react-query"
3
+ import { useAxios } from "./use-axios"
4
+ import { useParams } from "wouter"
5
+ import { useApiBaseUrl } from "./use-packages-base-api-url"
6
+
7
+ interface UsePackageReleaseImagesProps {
8
+ packageReleaseId?: string | null
9
+ availableFilePaths?: string[] | undefined
10
+ }
11
+
12
+ interface ViewConfig {
13
+ id: string
14
+ label: string
15
+ backgroundClass: string
16
+ }
17
+
18
+ const VIEWS: ViewConfig[] = [
19
+ {
20
+ id: "3d",
21
+ label: "3D",
22
+ backgroundClass: "bg-gray-100",
23
+ },
24
+ {
25
+ id: "pcb",
26
+ label: "PCB",
27
+ backgroundClass: "bg-black",
28
+ },
29
+ {
30
+ id: "schematic",
31
+ label: "Schematic",
32
+ backgroundClass: "bg-[#F5F1ED]",
33
+ },
34
+ ]
35
+
36
+ export function usePackageReleaseImages({
37
+ packageReleaseId,
38
+ }: UsePackageReleaseImagesProps) {
39
+ const apiurl = useApiBaseUrl()
40
+ const axios = useAxios()
41
+ const { author, packageName } = useParams()
42
+
43
+ const createQueryFn = useCallback(
44
+ (viewId: string) => async () => {
45
+ if (!packageReleaseId || !author || !packageName) return null
46
+
47
+ const imageUrl = `${apiurl}/packages/images/${author}/${packageName}/${viewId}.png?package_release_id=${packageReleaseId}`
48
+
49
+ try {
50
+ const response = await axios.head(imageUrl)
51
+ return response.status === 200 ? imageUrl : null
52
+ } catch {
53
+ return null
54
+ }
55
+ },
56
+ [packageReleaseId, author, packageName, apiurl, axios],
57
+ )
58
+
59
+ const queryConfigs = useMemo(
60
+ () =>
61
+ VIEWS.map((view) => ({
62
+ queryKey: [
63
+ "packageReleaseImage",
64
+ packageReleaseId,
65
+ view.id,
66
+ author,
67
+ packageName,
68
+ ],
69
+ queryFn: createQueryFn(view.id),
70
+ enabled: Boolean(packageReleaseId && author && packageName),
71
+ retry: false,
72
+ refetchOnWindowFocus: false,
73
+ refetchOnMount: false,
74
+ refetchOnReconnect: false,
75
+ staleTime: Infinity,
76
+ cacheTime: Infinity,
77
+ })),
78
+ [packageReleaseId, author, packageName, createQueryFn],
79
+ )
80
+
81
+ const queries = useQueries(queryConfigs)
82
+
83
+ const availableViews = useMemo(() => {
84
+ const result = []
85
+
86
+ for (let i = 0; i < VIEWS.length; i++) {
87
+ const view = VIEWS[i]
88
+ const query = queries[i]
89
+
90
+ if (query.isLoading || query.data) {
91
+ result.push({
92
+ id: view.id,
93
+ label: view.label,
94
+ imageUrl: query.data || "",
95
+ isLoading: query.isLoading,
96
+ backgroundClass: view.backgroundClass,
97
+ })
98
+ }
99
+ }
100
+
101
+ return result
102
+ }, [queries])
103
+
104
+ return { availableViews }
105
+ }
@@ -40,8 +40,8 @@ export const usePackageRelease = (
40
40
 
41
41
  const { data } = await axios.post("/package_releases/get", query, {
42
42
  params: {
43
- include_logs: query.include_logs,
44
- include_ai_review: query.include_ai_review,
43
+ include_logs: Boolean(query.include_logs),
44
+ include_ai_review: Boolean(query.include_ai_review),
45
45
  },
46
46
  })
47
47
 
@@ -38,25 +38,77 @@ export const usePackageStarMutation = (query: PackageStarQuery) => {
38
38
  const axios = useAxios()
39
39
  const queryClient = useQueryClient()
40
40
 
41
- const addStar = useMutation(
41
+ const addStar = useMutation<
42
+ any,
43
+ Error,
44
+ void,
45
+ { previousStars?: PackageStarResponse }
46
+ >(
42
47
  async () => {
43
48
  const { data } = await axios.post("/packages/add_star", query)
44
49
  return data
45
50
  },
46
51
  {
47
- onSuccess: () => {
52
+ onMutate: async () => {
53
+ await queryClient.cancelQueries(["packageStars", query])
54
+ const previousStars = queryClient.getQueryData<PackageStarResponse>([
55
+ "packageStars",
56
+ query,
57
+ ])
58
+ const optimistic: PackageStarResponse = {
59
+ is_starred: true,
60
+ star_count: (previousStars?.star_count ?? 0) + 1,
61
+ }
62
+ queryClient.setQueryData(["packageStars", query], optimistic)
63
+ return { previousStars }
64
+ },
65
+ onError: (_error, _vars, context) => {
66
+ if (context?.previousStars) {
67
+ queryClient.setQueryData(
68
+ ["packageStars", query],
69
+ context.previousStars,
70
+ )
71
+ }
72
+ },
73
+ onSettled: () => {
48
74
  queryClient.invalidateQueries(["packageStars", query])
49
75
  },
50
76
  },
51
77
  )
52
78
 
53
- const removeStar = useMutation(
79
+ const removeStar = useMutation<
80
+ any,
81
+ Error,
82
+ void,
83
+ { previousStars?: PackageStarResponse }
84
+ >(
54
85
  async () => {
55
86
  const { data } = await axios.post("/packages/remove_star", query)
56
87
  return data
57
88
  },
58
89
  {
59
- onSuccess: () => {
90
+ onMutate: async () => {
91
+ await queryClient.cancelQueries(["packageStars", query])
92
+ const previousStars = queryClient.getQueryData<PackageStarResponse>([
93
+ "packageStars",
94
+ query,
95
+ ])
96
+ const optimistic: PackageStarResponse = {
97
+ is_starred: false,
98
+ star_count: Math.max(0, (previousStars?.star_count ?? 1) - 1),
99
+ }
100
+ queryClient.setQueryData(["packageStars", query], optimistic)
101
+ return { previousStars }
102
+ },
103
+ onError: (_error, _vars, context) => {
104
+ if (context?.previousStars) {
105
+ queryClient.setQueryData(
106
+ ["packageStars", query],
107
+ context.previousStars,
108
+ )
109
+ }
110
+ },
111
+ onSettled: () => {
60
112
  queryClient.invalidateQueries(["packageStars", query])
61
113
  },
62
114
  },
@@ -84,3 +136,27 @@ export const usePackageStarMutationById = (packageId: string) => {
84
136
  export const usePackageStarMutationByName = (packageName: string) => {
85
137
  return usePackageStarMutation({ name: packageName })
86
138
  }
139
+
140
+ // High-level hook that exposes current star state and a single toggle action
141
+ export const usePackageStarring = (query: PackageStarQuery | null) => {
142
+ const starsQuery = usePackageStars(query)
143
+ const mutations = usePackageStarMutation(query ?? { name: "" })
144
+
145
+ const toggleStar = async () => {
146
+ if (!query) return
147
+ const currentlyStarred = starsQuery.data?.is_starred ?? false
148
+ if (currentlyStarred) await mutations.removeStar.mutateAsync()
149
+ else await mutations.addStar.mutateAsync()
150
+ }
151
+
152
+ return {
153
+ isStarred: starsQuery.data?.is_starred ?? false,
154
+ starCount: starsQuery.data?.star_count ?? 0,
155
+ isPending: mutations.addStar.isLoading || mutations.removeStar.isLoading,
156
+ toggleStar,
157
+ }
158
+ }
159
+
160
+ export const usePackageStarringByName = (packageName: string | null) => {
161
+ return usePackageStarring(packageName ? { name: packageName } : null)
162
+ }