@tscircuit/fake-snippets 0.0.109 → 0.0.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/.github/workflows/bun-formatcheck.yml +2 -2
  2. package/.github/workflows/bun-pver-release.yml +3 -3
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +2 -2
  5. package/.github/workflows/update-snapshots.yml +1 -1
  6. package/README.md +4 -0
  7. package/api/generated-index.js +37 -3
  8. package/biome.json +2 -1
  9. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +31 -3
  10. package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
  11. package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
  12. package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
  13. package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
  14. package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
  15. package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
  16. package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
  17. package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +99 -0
  18. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
  19. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
  20. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
  21. package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
  22. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
  23. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
  24. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
  25. package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
  26. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
  27. package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
  28. package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
  29. package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
  30. package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
  31. package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
  32. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
  33. package/bun.lock +349 -453
  34. package/bunfig.toml +2 -1
  35. package/dist/bundle.js +1253 -624
  36. package/dist/index.d.ts +291 -4
  37. package/dist/index.js +323 -23
  38. package/dist/schema.d.ts +274 -1
  39. package/dist/schema.js +52 -1
  40. package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
  41. package/fake-snippets-api/lib/db/db-client.ts +214 -3
  42. package/fake-snippets-api/lib/db/schema.ts +61 -0
  43. package/fake-snippets-api/lib/db/seed.ts +100 -0
  44. package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
  45. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
  46. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +32 -0
  47. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
  48. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
  49. package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
  50. package/fake-snippets-api/routes/api/orgs/create.ts +46 -0
  51. package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
  52. package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
  53. package/fake-snippets-api/routes/api/orgs/list_members.ts +67 -0
  54. package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
  55. package/fake-snippets-api/routes/api/orgs/update.ts +93 -0
  56. package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
  57. package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
  58. package/fake-snippets-api/routes/api/packages/create.ts +54 -10
  59. package/fake-snippets-api/routes/api/packages/get.ts +23 -0
  60. package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
  61. package/fake-snippets-api/routes/api/packages/list.ts +29 -2
  62. package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
  63. package/package.json +24 -20
  64. package/renovate.json +1 -1
  65. package/scripts/generate-sitemap.ts +1 -1
  66. package/src/App.tsx +29 -8
  67. package/src/ContextProviders.tsx +25 -2
  68. package/src/components/CircuitJsonImportDialog.tsx +1 -1
  69. package/src/components/CmdKMenu.tsx +281 -247
  70. package/src/components/DownloadButtonAndMenu.tsx +3 -4
  71. package/src/components/FileSidebar.tsx +11 -17
  72. package/src/components/Footer.tsx +8 -9
  73. package/src/components/Header.tsx +19 -32
  74. package/src/components/Header2.tsx +16 -32
  75. package/src/components/HeaderDropdown.tsx +13 -8
  76. package/src/components/HeaderLogin.tsx +43 -15
  77. package/src/components/NotFound.tsx +5 -5
  78. package/src/components/PackageBreadcrumb.tsx +6 -12
  79. package/src/components/PackageSearchResults.tsx +1 -1
  80. package/src/components/PrefetchPageLink.tsx +7 -1
  81. package/src/components/ProfileRouter.tsx +32 -0
  82. package/src/components/SearchComponent.tsx +12 -8
  83. package/src/components/UserCard.tsx +80 -0
  84. package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
  85. package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
  86. package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
  87. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
  88. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
  89. package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
  90. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
  91. package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
  92. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
  93. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
  94. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
  95. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
  96. package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
  97. package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
  98. package/src/components/dialogs/import-component-dialog.tsx +16 -9
  99. package/src/components/dialogs/import-package-dialog.tsx +3 -2
  100. package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
  101. package/src/components/organization/OrganizationCard.tsx +204 -0
  102. package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
  103. package/src/components/organization/OrganizationHeader.tsx +154 -0
  104. package/src/components/organization/OrganizationMembers.tsx +146 -0
  105. package/src/components/package-port/CodeAndPreview.tsx +15 -12
  106. package/src/components/package-port/CodeEditor.tsx +4 -30
  107. package/src/components/package-port/CodeEditorHeader.tsx +123 -61
  108. package/src/components/package-port/EditorNav.tsx +32 -49
  109. package/src/components/preview/ConnectedPackagesList.tsx +8 -8
  110. package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
  111. package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
  112. package/src/components/ui/tree-view.tsx +6 -3
  113. package/src/hooks/use-add-org-member-mutation.ts +51 -0
  114. package/src/hooks/use-create-org-mutation.ts +38 -0
  115. package/src/hooks/use-create-package-mutation.ts +3 -0
  116. package/src/hooks/use-current-package-release.ts +4 -3
  117. package/src/hooks/use-download-zip.ts +2 -2
  118. package/src/hooks/use-global-store.ts +6 -4
  119. package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
  120. package/src/hooks/use-list-org-members.ts +27 -0
  121. package/src/hooks/use-list-user-orgs.ts +25 -0
  122. package/src/hooks/use-org-by-github-handle.ts +26 -0
  123. package/src/hooks/use-org.ts +24 -0
  124. package/src/hooks/use-organization.ts +42 -0
  125. package/src/hooks/use-package-as-snippet.ts +4 -2
  126. package/src/hooks/use-package-builds.ts +6 -2
  127. package/src/hooks/use-package-files.ts +5 -3
  128. package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
  129. package/src/hooks/use-package-release-images.ts +105 -0
  130. package/src/hooks/use-package-release.ts +2 -2
  131. package/src/hooks/use-package-stars.ts +80 -4
  132. package/src/hooks/use-preview-images.ts +6 -3
  133. package/src/hooks/use-remove-org-member-mutation.ts +32 -0
  134. package/src/hooks/use-update-ai-description-mutation.ts +42 -0
  135. package/src/hooks/use-update-org-mutation.ts +41 -0
  136. package/src/hooks/use-warn-user-on-page-change.ts +71 -4
  137. package/src/hooks/useFileManagement.ts +51 -22
  138. package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
  139. package/src/hooks/usePackageFilesLoader.ts +2 -2
  140. package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
  141. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
  142. package/src/lib/download-fns/download-kicad-files.ts +12 -11
  143. package/src/lib/normalize-svg-for-tile.ts +50 -0
  144. package/src/lib/posthog.ts +11 -9
  145. package/src/lib/react-query-api-failure-tracking.ts +148 -0
  146. package/src/lib/sentry.ts +14 -0
  147. package/src/lib/templates/blank-circuit-board-template.ts +0 -4
  148. package/src/lib/ts-lib-cache.ts +122 -7
  149. package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
  150. package/src/lib/utils/findTargetFile.ts +45 -10
  151. package/src/lib/utils/isComponentExported.ts +2 -1
  152. package/src/main.tsx +2 -1
  153. package/src/pages/create-organization.tsx +168 -0
  154. package/src/pages/dashboard.tsx +38 -6
  155. package/src/pages/datasheet.tsx +1 -1
  156. package/src/pages/datasheets.tsx +3 -3
  157. package/src/pages/editor.tsx +4 -6
  158. package/src/pages/landing.tsx +6 -6
  159. package/src/pages/latest.tsx +3 -0
  160. package/src/pages/organization-profile.tsx +199 -0
  161. package/src/pages/organization-settings.tsx +566 -0
  162. package/src/pages/package-editor.tsx +21 -21
  163. package/src/pages/preview-release.tsx +75 -145
  164. package/src/pages/quickstart.tsx +159 -123
  165. package/src/pages/release-detail.tsx +119 -31
  166. package/src/pages/search.tsx +192 -57
  167. package/src/pages/settings-redirect.tsx +44 -0
  168. package/src/pages/trending.tsx +29 -20
  169. package/src/pages/user-profile.tsx +58 -7
  170. package/src/pages/view-package.tsx +7 -13
  171. package/vite.config.ts +9 -0
  172. package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
  173. package/src/components/JLCPCBImportDialog.tsx +0 -280
  174. package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
  175. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
  176. package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
  177. package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
  178. package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
  179. package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
  180. package/src/components/PageSearchComponent.tsx +0 -148
  181. package/src/pages/package-builds.tsx +0 -33
@@ -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
+ }
@@ -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
+ }
@@ -21,22 +21,25 @@ export function usePreviewImages({
21
21
  {
22
22
  id: "3d",
23
23
  label: "3D View",
24
+ backgroundClass: "bg-gray-100",
24
25
  imageUrl: packageName
25
- ? `https://registry-api.tscircuit.com/packages/images/${packageName}/3d.png?fs_sha=${fsMapHash}`
26
+ ? `https://api.tscircuit.com/packages/images/${packageName}/3d.png?fs_sha=${fsMapHash}`
26
27
  : undefined,
27
28
  },
28
29
  {
29
30
  id: "pcb",
30
31
  label: "PCB View",
32
+ backgroundClass: "bg-black",
31
33
  imageUrl: packageName
32
- ? `https://registry-api.tscircuit.com/packages/images/${packageName}/pcb.png?fs_sha=${fsMapHash}`
34
+ ? `https://api.tscircuit.com/packages/images/${packageName}/pcb.png?fs_sha=${fsMapHash}`
33
35
  : undefined,
34
36
  },
35
37
  {
36
38
  id: "schematic",
37
39
  label: "Schematic View",
40
+ backgroundClass: "bg-[#F5F1ED]",
38
41
  imageUrl: packageName
39
- ? `https://registry-api.tscircuit.com/packages/images/${packageName}/schematic.png?fs_sha=${fsMapHash}`
42
+ ? `https://api.tscircuit.com/packages/images/${packageName}/schematic.png?fs_sha=${fsMapHash}`
40
43
  : undefined,
41
44
  },
42
45
  ]
@@ -0,0 +1,32 @@
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 useRemoveOrgMemberMutation = ({
6
+ onSuccess,
7
+ }: { onSuccess?: () => void } = {}) => {
8
+ const axios = useAxios()
9
+ const session = useGlobalStore((s) => s.session)
10
+ const queryClient = useQueryClient()
11
+
12
+ return useMutation(
13
+ ["removeOrgMember"],
14
+ async ({ orgId, accountId }: { orgId: string; accountId: string }) => {
15
+ if (!session) throw new Error("No session")
16
+
17
+ await axios.post("/orgs/remove_member", {
18
+ org_id: orgId,
19
+ account_id: accountId,
20
+ })
21
+ },
22
+ {
23
+ onSuccess: () => {
24
+ queryClient.invalidateQueries(["orgs", "members"])
25
+ onSuccess?.()
26
+ },
27
+ onError: (error: any) => {
28
+ console.error("Error removing organization member:", error)
29
+ },
30
+ },
31
+ )
32
+ }
@@ -0,0 +1,42 @@
1
+ import { useMutation, useQueryClient } from "react-query"
2
+ import { useAxios } from "./use-axios"
3
+ import { useToast } from "./use-toast"
4
+
5
+ export const useUpdateAiDescriptionMutation = ({
6
+ onSuccess,
7
+ }: {
8
+ onSuccess?: () => void
9
+ } = {}) => {
10
+ const axios = useAxios()
11
+ const { toast } = useToast()
12
+ const queryClient = useQueryClient()
13
+
14
+ return useMutation(
15
+ async ({ package_id }: { package_id: string }) => {
16
+ const { data } = await axios.post("/packages/update_ai_description", {
17
+ package_id,
18
+ })
19
+ return data
20
+ },
21
+ {
22
+ onSuccess: () => {
23
+ toast({
24
+ title: "AI description update requested",
25
+ description: "The AI description will be regenerated shortly.",
26
+ })
27
+ queryClient.invalidateQueries(["package"])
28
+ onSuccess?.()
29
+ },
30
+ onError: (error: any) => {
31
+ toast({
32
+ title: "Error",
33
+ description:
34
+ error?.response?.data?.message ||
35
+ error?.data?.message ||
36
+ "Failed to request AI description update.",
37
+ variant: "destructive",
38
+ })
39
+ },
40
+ },
41
+ )
42
+ }
@@ -0,0 +1,41 @@
1
+ import { useMutation, useQueryClient } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import { useGlobalStore } from "@/hooks/use-global-store"
4
+ import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
5
+
6
+ export const useUpdateOrgMutation = ({
7
+ onSuccess,
8
+ }: { onSuccess?: (org: PublicOrgSchema) => void } = {}) => {
9
+ const axios = useAxios()
10
+ const session = useGlobalStore((s) => s.session)
11
+ const queryClient = useQueryClient()
12
+
13
+ return useMutation(
14
+ ["updateOrg"],
15
+ async ({ orgId, name }: { orgId: string; name?: string }) => {
16
+ if (!session) throw new Error("No session")
17
+
18
+ const {
19
+ data: { org: updatedOrg },
20
+ } = await axios.post("/orgs/update", {
21
+ org_id: orgId,
22
+ name,
23
+ })
24
+
25
+ if (!updatedOrg) {
26
+ throw new Error("Failed to update organization")
27
+ }
28
+
29
+ return updatedOrg
30
+ },
31
+ {
32
+ onSuccess: (org: PublicOrgSchema) => {
33
+ queryClient.invalidateQueries(["orgs"])
34
+ onSuccess?.(org)
35
+ },
36
+ onError: (error: any) => {
37
+ console.error("Error updating organization:", error)
38
+ },
39
+ },
40
+ )
41
+ }