@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
@@ -3,12 +3,11 @@ import { useQuery } from "react-query"
3
3
  import { useAxios } from "@/hooks/use-axios"
4
4
  import Header from "@/components/Header"
5
5
  import Footer from "@/components/Footer"
6
- import { Package, Snippet } from "fake-snippets-api/lib/db/schema"
7
- import { Link } from "wouter"
6
+ import { Package } from "fake-snippets-api/lib/db/schema"
8
7
  import { Edit2, KeyRound } from "lucide-react"
9
8
  import { Button } from "@/components/ui/button"
10
9
  import { useGlobalStore } from "@/hooks/use-global-store"
11
- import { PrefetchPageLink } from "@/components/PrefetchPageLink"
10
+ import { Link } from "wouter"
12
11
  import { PackagesList } from "@/components/PackagesList"
13
12
  import { Helmet } from "react-helmet-async"
14
13
  import { useSignIn } from "@/hooks/use-sign-in"
@@ -16,9 +15,12 @@ import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
16
15
  import { useConfirmDeletePackageDialog } from "@/components/dialogs/confirm-delete-package-dialog"
17
16
  import { PackageCardSkeleton } from "@/components/PackageCardSkeleton"
18
17
  import { PackageCard } from "@/components/PackageCard"
18
+ import { useListUserOrgs } from "@/hooks/use-list-user-orgs"
19
+ import { OrganizationCard } from "@/components/organization/OrganizationCard"
19
20
 
20
21
  export const DashboardPage = () => {
21
22
  const axios = useAxios()
23
+ const { data: organizations } = useListUserOrgs()
22
24
 
23
25
  const currentUser = useGlobalStore((s) => s.session?.github_username)
24
26
  const isLoggedIn = Boolean(currentUser)
@@ -99,7 +101,9 @@ export const DashboardPage = () => {
99
101
  </Helmet>
100
102
  <Header />
101
103
  <div className="container mx-auto px-4 py-8 min-h-[80vh]">
102
- <h1 className="text-3xl font-bold mb-6">Dashboard</h1>
104
+ <div className="flex items-center justify-between mb-6">
105
+ <h1 className="text-3xl font-bold">Dashboard</h1>
106
+ </div>
103
107
  <div className="flex md:flex-row flex-col">
104
108
  <div className="md:w-3/4 p-0 md:pr-6">
105
109
  {!isLoggedIn ? (
@@ -129,7 +133,7 @@ export const DashboardPage = () => {
129
133
  {myPackages &&
130
134
  myPackages.slice(0, 3).map((pkg) => (
131
135
  <div key={pkg.package_id}>
132
- <PrefetchPageLink
136
+ <Link
133
137
  href={`/editor?package_id=${pkg.package_id}`}
134
138
  className="text-blue-600 hover:underline"
135
139
  >
@@ -141,7 +145,7 @@ export const DashboardPage = () => {
141
145
  {pkg.unscoped_name}
142
146
  <Edit2 className="w-3 h-3 ml-2" />
143
147
  </Button>
144
- </PrefetchPageLink>
148
+ </Link>
145
149
  </div>
146
150
  ))}
147
151
  </div>
@@ -188,6 +192,34 @@ export const DashboardPage = () => {
188
192
  View all packages
189
193
  </Link>
190
194
  )}
195
+
196
+ {/* Organizations Section */}
197
+ {organizations && organizations.length > 0 && (
198
+ <div className="mt-8">
199
+ <h2 className="text-sm font-bold mb-2 text-gray-700 border-b border-gray-200">
200
+ Your Organizations
201
+ </h2>
202
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
203
+ {organizations?.slice(0, 4).map((org: any, i: number) => (
204
+ <OrganizationCard
205
+ key={i}
206
+ organization={org}
207
+ withLink={true}
208
+ showStats={true}
209
+ showMembers={true}
210
+ />
211
+ ))}
212
+ </div>
213
+ {organizations && organizations.length > 4 && (
214
+ <Link
215
+ href="/organizations"
216
+ className="text-sm text-blue-600 hover:underline mt-2 inline-block"
217
+ >
218
+ View all organizations
219
+ </Link>
220
+ )}
221
+ </div>
222
+ )}
191
223
  </>
192
224
  )}
193
225
  </div>
@@ -55,7 +55,7 @@ export const DatasheetPage = () => {
55
55
  </div>
56
56
 
57
57
  {datasheetQuery.isLoading ? (
58
- <div className="flex flex-col items-center justify-center py-16">
58
+ <div className="flex flex-col items-center justify-center py-20">
59
59
  <Loader2 className="w-10 h-10 animate-spin text-blue-500 mb-4" />
60
60
  <h3 className="text-xl font-semibold mb-2">Loading Datasheet...</h3>
61
61
  <p className="text-gray-500 max-w-md text-center">
@@ -5,7 +5,7 @@ import { useCreateDatasheet } from "@/hooks/use-create-datasheet"
5
5
  import Header from "@/components/Header"
6
6
  import Footer from "@/components/Footer"
7
7
  import { Input } from "@/components/ui/input"
8
- import { Search } from "lucide-react"
8
+ import { Loader2, Search } from "lucide-react"
9
9
  import { Link, useLocation } from "wouter"
10
10
 
11
11
  interface DatasheetSummary {
@@ -72,9 +72,9 @@ export const DatasheetsPage: React.FC = () => {
72
72
  </div>
73
73
 
74
74
  {isLoading ? (
75
- <div className="text-center py-12 px-4">
75
+ <div className="text-center py-20 px-4">
76
76
  <div className="bg-slate-50 inline-flex rounded-full p-4 mb-4">
77
- <Search className="w-8 h-8 text-slate-400" />
77
+ <Loader2 className="size-8 animate-spin text-slate-400" />
78
78
  </div>
79
79
  <h3 className="text-xl font-medium text-slate-900 mb-2">
80
80
  Loading Datasheets
@@ -2,12 +2,10 @@ import { CodeAndPreview } from "@/components/package-port/CodeAndPreview"
2
2
  import Footer from "@/components/Footer"
3
3
  import Header from "@/components/Header"
4
4
  import { Helmet } from "react-helmet-async"
5
- import { useCurrentPackageId } from "@/hooks/use-current-package-id"
6
- import { usePackage } from "@/hooks/use-package"
5
+ import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
7
6
 
8
7
  export const EditorPage = () => {
9
- const { packageId } = useCurrentPackageId()
10
- const { data: pkg, isLoading, error } = usePackage(packageId)
8
+ const { packageInfo: pkg, error } = useCurrentPackageInfo()
11
9
 
12
10
  const projectUrl = pkg
13
11
  ? `https://tscircuit.com/${pkg.owner_github_username}/${pkg.unscoped_name}`
@@ -27,12 +25,12 @@ export const EditorPage = () => {
27
25
  />
28
26
  <meta
29
27
  property="og:image"
30
- content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?fs_sha=${pkg.latest_package_release_fs_sha}`}
28
+ content={`https://api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?fs_sha=${pkg.latest_package_release_fs_sha}`}
31
29
  />
32
30
  <meta name="twitter:card" content="summary_large_image" />
33
31
  <meta
34
32
  name="twitter:image"
35
- content={`https://registry-api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?fs_sha=${pkg.latest_package_release_fs_sha}`}
33
+ content={`https://api.tscircuit.com/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.png?fs_sha=${pkg.latest_package_release_fs_sha}`}
36
34
  />
37
35
  </>
38
36
  )}
@@ -18,7 +18,7 @@ import { useGlobalStore } from "@/hooks/use-global-store"
18
18
  import { navigate } from "wouter/use-browser-location"
19
19
  import { FAQ } from "@/components/FAQ"
20
20
  import { TrendingPackagesCarousel } from "@/components/TrendingPackagesCarousel"
21
- import { PrefetchPageLink } from "@/components/PrefetchPageLink"
21
+ import { Link } from "wouter"
22
22
 
23
23
  export function LandingPage() {
24
24
  const signIn = useSignIn()
@@ -35,8 +35,8 @@ export function LandingPage() {
35
35
  <link rel="preconnect" href="https://tscircuit.com" />
36
36
  <link rel="dns-prefetch" href="https://tscircuit.com" />
37
37
 
38
- <link rel="preconnect" href="https://registry-api.tscircuit.com" />
39
- <link rel="dns-prefetch" href="https://registry-api.tscircuit.com" />
38
+ <link rel="preconnect" href="https://api.tscircuit.com" />
39
+ <link rel="dns-prefetch" href="https://api.tscircuit.com" />
40
40
  </Helmet>
41
41
  <Header2 />
42
42
  <main className="flex-1">
@@ -53,7 +53,7 @@ export function LandingPage() {
53
53
  AI codes electronics with tscircuit
54
54
  </h1>
55
55
  <p className="max-w-[600px] text-muted-foreground md:text-xl">
56
- Build electronics with code, AI, and drag'n'drop tools.
56
+ Build electronics with code and AI tools.
57
57
  <br />
58
58
  Render code into schematics, PCBs, 3D, fabrication files,
59
59
  and more.
@@ -72,7 +72,7 @@ export function LandingPage() {
72
72
  Get Started
73
73
  </Button>
74
74
  </a>
75
- <PrefetchPageLink
75
+ <Link
76
76
  href="/seveibar/led-water-accelerometer#3d"
77
77
  className="w-[70vw] min-[500px]:w-auto"
78
78
  >
@@ -84,7 +84,7 @@ export function LandingPage() {
84
84
  >
85
85
  Open Online Example
86
86
  </Button>
87
- </PrefetchPageLink>
87
+ </Link>
88
88
  <a
89
89
  href="https://github.com/tscircuit/tscircuit"
90
90
  target="_blank"
@@ -35,6 +35,9 @@ const LatestPage: React.FC = () => {
35
35
  },
36
36
  {
37
37
  keepPreviousData: true,
38
+ refetchOnWindowFocus: false,
39
+ refetchOnMount: false,
40
+ refetchOnReconnect: false,
38
41
  },
39
42
  )
40
43
 
@@ -0,0 +1,199 @@
1
+ import { useEffect, useState } from "react"
2
+ import { useParams } from "wouter"
3
+ import Header from "@/components/Header"
4
+ import Footer from "@/components/Footer"
5
+ import { OrganizationHeader } from "@/components/organization/OrganizationHeader"
6
+ import { OrganizationMembers } from "@/components/organization/OrganizationMembers"
7
+ import { PackageCard } from "@/components/PackageCard"
8
+ import { Input } from "@/components/ui/input"
9
+ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "@/components/ui/select"
17
+ import { useListOrgMembers } from "@/hooks/use-list-org-members"
18
+ import { useOrgByGithubHandle } from "@/hooks/use-org-by-github-handle"
19
+ import { PackageCardSkeleton } from "@/components/PackageCardSkeleton"
20
+ import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
21
+ import { useGlobalStore } from "@/hooks/use-global-store"
22
+ import { Box } from "lucide-react"
23
+ import type { PublicOrgSchema, Package } from "fake-snippets-api/lib/db/schema"
24
+ import { NotFound } from "@/components/NotFound"
25
+ import { useQuery } from "react-query"
26
+ import { useAxios } from "@/hooks/use-axios"
27
+
28
+ export const OrganizationProfilePageContent = ({
29
+ org,
30
+ }: { org: PublicOrgSchema }) => {
31
+ const baseUrl = useApiBaseUrl()
32
+ const session = useGlobalStore((s) => s.session)
33
+ const axios = useAxios()
34
+
35
+ const [searchQuery, setSearchQuery] = useState("")
36
+ const [activeTab, setActiveTab] = useState("packages")
37
+ const [filter, setFilter] = useState("most-recent")
38
+ const [showAllMembers, setShowAllMembers] = useState(false)
39
+
40
+ const isCurrentUserOrganization = session?.account_id === org.owner_account_id
41
+
42
+ const { data: userPackages, isLoading: isLoadingUserPackages } = useQuery<
43
+ Package[]
44
+ >(
45
+ ["userPackages", org.name],
46
+ async () => {
47
+ const response = await axios.post(`/packages/list`, {
48
+ owner_github_username: org.name,
49
+ })
50
+ return response.data.packages
51
+ },
52
+ {
53
+ enabled: Boolean(org.name),
54
+ refetchOnWindowFocus: false,
55
+ },
56
+ )
57
+
58
+ const filteredPackages = userPackages
59
+ ?.filter((pkg) => {
60
+ return (
61
+ !searchQuery ||
62
+ pkg.unscoped_name
63
+ .toLowerCase()
64
+ .includes(searchQuery.toLowerCase().trim())
65
+ )
66
+ })
67
+ ?.sort((a, b) => {
68
+ switch (filter) {
69
+ case "most-recent":
70
+ return b.updated_at.localeCompare(a.updated_at)
71
+ case "least-recent":
72
+ return a.updated_at.localeCompare(b.updated_at)
73
+ case "most-starred":
74
+ return (b.star_count || 0) - (a.star_count || 0)
75
+ case "a-z":
76
+ return a.unscoped_name.localeCompare(b.unscoped_name)
77
+ case "z-a":
78
+ return b.unscoped_name.localeCompare(a.unscoped_name)
79
+ default:
80
+ return 0
81
+ }
82
+ })
83
+
84
+ return (
85
+ <div className="min-h-screen">
86
+ <Header />
87
+
88
+ <OrganizationHeader
89
+ organization={org}
90
+ isCurrentUserOrganization={isCurrentUserOrganization}
91
+ />
92
+
93
+ <div className="container mx-auto px-4 py-8">
94
+ <div className="w-full">
95
+ <div className="w-full">
96
+ <Tabs
97
+ defaultValue="packages"
98
+ onValueChange={setActiveTab}
99
+ className="mb-6"
100
+ >
101
+ <TabsList>
102
+ <TabsTrigger
103
+ value="packages"
104
+ className="flex items-center gap-2"
105
+ >
106
+ Packages
107
+ </TabsTrigger>
108
+ <TabsTrigger
109
+ value="members"
110
+ className="flex items-center gap-2"
111
+ >
112
+ Members
113
+ </TabsTrigger>
114
+ </TabsList>
115
+ </Tabs>
116
+
117
+ {activeTab === "packages" && (
118
+ <div>
119
+ <div className="flex flex-col sm:flex-row gap-4 mb-6">
120
+ <Input
121
+ type="text"
122
+ placeholder="Search packages..."
123
+ value={searchQuery}
124
+ onChange={(e) => setSearchQuery(e.target.value)}
125
+ className="flex-1"
126
+ />
127
+ <Select value={filter} onValueChange={setFilter}>
128
+ <SelectTrigger className="w-full sm:w-[180px]">
129
+ <SelectValue placeholder="Sort by" />
130
+ </SelectTrigger>
131
+ <SelectContent>
132
+ <SelectItem value="most-recent">Most Recent</SelectItem>
133
+ <SelectItem value="least-recent">Least Recent</SelectItem>
134
+ <SelectItem value="most-starred">Most Starred</SelectItem>
135
+ <SelectItem value="a-z">A-Z</SelectItem>
136
+ <SelectItem value="z-a">Z-A</SelectItem>
137
+ </SelectContent>
138
+ </Select>
139
+ </div>
140
+
141
+ {isLoadingUserPackages ? (
142
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
143
+ {[...Array(6)].map((_, i) => (
144
+ <PackageCardSkeleton key={i} />
145
+ ))}
146
+ </div>
147
+ ) : (
148
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
149
+ {filteredPackages?.length !== 0 ? (
150
+ filteredPackages?.map((pkg) => (
151
+ <PackageCard
152
+ key={pkg.package_id}
153
+ pkg={pkg}
154
+ baseUrl={baseUrl}
155
+ showOwner={false}
156
+ isCurrentUserPackage={isCurrentUserOrganization}
157
+ />
158
+ ))
159
+ ) : (
160
+ <div className="col-span-full flex justify-center">
161
+ <div className="flex flex-col items-center py-20 text-gray-500">
162
+ <Box className="mb-2" size={24} />
163
+ <span className="text-lg font-medium">
164
+ {searchQuery.trim()
165
+ ? `No packages matching '${searchQuery.trim()}'`
166
+ : "No packages available"}
167
+ </span>
168
+ </div>
169
+ </div>
170
+ )}
171
+ </div>
172
+ )}
173
+ </div>
174
+ )}
175
+
176
+ {activeTab === "members" && (
177
+ <div>
178
+ <OrganizationMembers orgId={org.org_id} />
179
+ </div>
180
+ )}
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ <Footer />
186
+ </div>
187
+ )
188
+ }
189
+
190
+ export const OrganizationProfilePage = () => {
191
+ const { username } = useParams()
192
+ const { data: organization } = useOrgByGithubHandle(username || null)
193
+
194
+ if (!organization) {
195
+ return <NotFound />
196
+ }
197
+
198
+ return <OrganizationProfilePageContent org={organization} />
199
+ }