@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
@@ -0,0 +1,154 @@
1
+ import React, { useState } from "react"
2
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
3
+ import { Button } from "@/components/ui/button"
4
+ import { Building2, Users, Package, Lock, Globe2, Settings } from "lucide-react"
5
+ import { cn } from "@/lib/utils"
6
+ import { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
7
+ import { useGlobalStore } from "@/hooks/use-global-store"
8
+ import { useLocation } from "wouter"
9
+ import { useOrganization } from "@/hooks/use-organization"
10
+
11
+ interface OrganizationHeaderProps {
12
+ organization: PublicOrgSchema
13
+ isCurrentUserOrganization?: boolean
14
+ className?: string
15
+ showActions?: boolean
16
+ }
17
+
18
+ export const OrganizationHeader: React.FC<OrganizationHeaderProps> = ({
19
+ organization,
20
+ className,
21
+ showActions = true,
22
+ }) => {
23
+ const session = useGlobalStore((s) => s.session)
24
+ const [, navigate] = useLocation()
25
+ const canManageOrg =
26
+ organization.user_permissions?.can_manage_org ||
27
+ organization.owner_account_id === session?.account_id
28
+
29
+ const { membersCount, packagesCount, isLoading } = useOrganization({
30
+ orgId: organization.org_id,
31
+ orgName: organization.name!,
32
+ })
33
+
34
+ const handleSettingsClick = () => {
35
+ navigate(`/${organization.name}/settings`)
36
+ }
37
+ return (
38
+ <div className={cn("bg-white border-b border-gray-200", className)}>
39
+ <div className="container mx-auto px-6 py-6">
40
+ {/* Mobile Layout */}
41
+ <div className="block sm:hidden">
42
+ <div className="flex flex-col items-center text-center space-y-4">
43
+ <Avatar className="border-4 border-gray-100 shadow-sm size-16 md:size-20 lg:size-24">
44
+ <AvatarImage
45
+ src={`https://github.com/${organization.name}.png`}
46
+ alt={`${organization.name} avatar`}
47
+ className="object-cover"
48
+ />
49
+ <AvatarFallback className="bg-blue-100 text-blue-600 font-bold text-xl md:text-2xl lg:text-3xl">
50
+ {(organization.name || "")
51
+ .split(" ")
52
+ .map((word) => word[0])
53
+ .join("")
54
+ .toUpperCase()
55
+ .slice(0, 2)}
56
+ </AvatarFallback>
57
+ </Avatar>
58
+
59
+ <div>
60
+ <div className="flex flex-col items-center gap-3 mb-3">
61
+ <h1 className="font-bold text-gray-900 text-xl">
62
+ {organization.name}
63
+ </h1>
64
+ {canManageOrg && showActions && (
65
+ <Button
66
+ variant="outline"
67
+ size="sm"
68
+ onClick={handleSettingsClick}
69
+ >
70
+ <Settings className="mr-2 h-4 w-4" />
71
+ Settings
72
+ </Button>
73
+ )}
74
+ </div>
75
+
76
+ <div className="grid grid-cols-2 md:flex flex-wrap justify-center gap-4 text-sm">
77
+ <div className="flex items-center gap-1.5 text-gray-600">
78
+ <Users className="h-3.5 w-3.5" />
79
+ <span className="font-medium">
80
+ {isLoading ? "..." : membersCount}
81
+ </span>
82
+ <span>members</span>
83
+ </div>
84
+ <div className="flex items-center gap-1.5 text-gray-600">
85
+ <Package className="h-3.5 w-3.5" />
86
+ <span className="font-medium">
87
+ {isLoading ? "..." : packagesCount}
88
+ </span>
89
+ <span>packages</span>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+
96
+ {/* Desktop Layout */}
97
+ <div className="hidden sm:block">
98
+ <div className="flex items-center gap-6">
99
+ <Avatar className="border-4 border-gray-100 shadow-sm size-16 md:size-20 lg:size-24 flex-shrink-0">
100
+ <AvatarImage
101
+ src={`https://github.com/${organization.name}.png`}
102
+ alt={`${organization.name} avatar`}
103
+ className="object-cover"
104
+ />
105
+ <AvatarFallback className="bg-blue-100 text-blue-600 font-bold text-xl md:text-2xl lg:text-3xl">
106
+ {(organization.name || "")
107
+ .split(" ")
108
+ .map((word) => word[0])
109
+ .join("")
110
+ .toUpperCase()
111
+ .slice(0, 2)}
112
+ </AvatarFallback>
113
+ </Avatar>
114
+
115
+ <div className="flex-1 min-w-0">
116
+ <div className="flex items-center justify-between mb-3">
117
+ <h1 className="font-bold text-gray-900 text-2xl md:text-3xl truncate">
118
+ {organization.name}
119
+ </h1>
120
+ {canManageOrg && showActions && (
121
+ <Button variant="outline" onClick={handleSettingsClick}>
122
+ <Settings className="mr-2 h-4 w-4" />
123
+ Settings
124
+ </Button>
125
+ )}
126
+ </div>
127
+
128
+ <div className="flex flex-wrap items-center gap-6 text-sm text-gray-600">
129
+ <div className="flex items-center gap-2">
130
+ <Users className="h-4 w-4" />
131
+ <span className="font-medium text-gray-900">
132
+ {isLoading ? "..." : membersCount}
133
+ </span>
134
+ <span>members</span>
135
+ </div>
136
+ <div className="flex items-center gap-2">
137
+ <Package className="h-4 w-4" />
138
+ <span className="font-medium text-gray-900">
139
+ {isLoading ? "..." : packagesCount}
140
+ </span>
141
+ <span>packages</span>
142
+ </div>
143
+ <div className="flex items-center gap-2">
144
+ <Building2 className="h-4 w-4" />
145
+ <span>Organization</span>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ )
154
+ }
@@ -0,0 +1,146 @@
1
+ import React from "react"
2
+ import { Link } from "wouter"
3
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
4
+ import { Badge } from "@/components/ui/badge"
5
+ import { Button } from "@/components/ui/button"
6
+ import { Users, Crown, Shield, User, Loader2 } from "lucide-react"
7
+ import { timeAgo } from "@/lib/utils/timeAgo"
8
+ import { cn } from "@/lib/utils"
9
+ import { Account } from "fake-snippets-api/lib/db/schema"
10
+ import { useListOrgMembers } from "@/hooks/use-list-org-members"
11
+
12
+ interface OrganizationMembersProps {
13
+ orgId: string
14
+ className?: string
15
+ }
16
+ type MemberRole = "owner" | "admin" | "member" //todo
17
+ const getRoleIcon = (role: MemberRole) => {
18
+ switch (role) {
19
+ case "owner":
20
+ return <Crown className="h-3 w-3" />
21
+ case "admin":
22
+ return <Shield className="h-3 w-3" />
23
+ case "member":
24
+ return <User className="h-3 w-3" />
25
+ default:
26
+ return <User className="h-3 w-3" />
27
+ }
28
+ }
29
+
30
+ const getRoleColor = (role: MemberRole) => {
31
+ switch (role) {
32
+ case "owner":
33
+ return "bg-yellow-100 text-yellow-800 border-yellow-200"
34
+ case "admin":
35
+ return "bg-purple-100 text-purple-800 border-purple-200"
36
+ case "member":
37
+ return "bg-gray-100 text-gray-800 border-gray-200"
38
+ default:
39
+ return "bg-gray-100 text-gray-800 border-gray-200"
40
+ }
41
+ }
42
+
43
+ export const OrganizationMembers: React.FC<OrganizationMembersProps> = ({
44
+ orgId,
45
+ className,
46
+ }) => {
47
+ const { data: members = [], isLoading } = useListOrgMembers({ orgId })
48
+
49
+ if (isLoading) {
50
+ return (
51
+ <div
52
+ className={cn(
53
+ "bg-white rounded-lg border border-gray-200 p-4 sm:p-6",
54
+ className,
55
+ )}
56
+ >
57
+ <div className="mb-4">
58
+ <h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
59
+ Members
60
+ </h2>
61
+ </div>
62
+ <div className="text-center py-20 text-gray-500 grid place-items-center">
63
+ <Loader2 className="w-8 h-8 text-blue-600 dark:text-blue-400 animate-spin" />
64
+ <p className="mt-2 text-sm select-none">Loading members...</p>
65
+ </div>
66
+ </div>
67
+ )
68
+ }
69
+ return (
70
+ <div
71
+ className={cn(
72
+ "bg-white rounded-lg border border-gray-200 p-4 py-20 sm:p-6",
73
+ className,
74
+ )}
75
+ >
76
+ <div className="mb-4">
77
+ <h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
78
+ Members ({members.length})
79
+ </h2>
80
+ </div>
81
+
82
+ <div className="space-y-2 sm:space-y-3">
83
+ {members.map((member) => (
84
+ <Link
85
+ key={member.account_id || member.github_username}
86
+ href={`/${member.github_username}`}
87
+ className="block"
88
+ >
89
+ <div className="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer">
90
+ <div className="flex items-center gap-3 min-w-0 flex-1">
91
+ <Avatar className="h-10 w-10 sm:h-12 sm:w-12 flex-shrink-0">
92
+ <AvatarImage
93
+ src={`https://github.com/${member.github_username}.png`}
94
+ alt={`${member.github_username} avatar`}
95
+ />
96
+ <AvatarFallback className="text-sm font-medium">
97
+ {member.github_username
98
+ .split(" ")
99
+ .map((word) => word[0])
100
+ .join("")
101
+ .toUpperCase()
102
+ .slice(0, 2)}
103
+ </AvatarFallback>
104
+ </Avatar>
105
+
106
+ <div className="min-w-0 flex-1">
107
+ <div className="flex items-center gap-2 mb-1">
108
+ <h3 className="font-medium text-gray-900 truncate">
109
+ {member.github_username}
110
+ </h3>
111
+ <Badge
112
+ variant="outline"
113
+ className={cn(
114
+ "text-xs flex items-center gap-1 flex-shrink-0",
115
+ getRoleColor("admin"),
116
+ )}
117
+ >
118
+ {getRoleIcon("admin")}
119
+ {"admin"}
120
+ </Badge>
121
+ </div>
122
+ <p className="text-sm text-gray-500 truncate">
123
+ @{member.github_username}
124
+ </p>
125
+ </div>
126
+ </div>
127
+
128
+ <div className="text-right flex-shrink-0 hidden sm:block">
129
+ <p className="text-xs text-gray-500">
130
+ Joined {timeAgo(new Date())}
131
+ </p>
132
+ </div>
133
+ </div>
134
+ </Link>
135
+ ))}
136
+ </div>
137
+
138
+ {members.length === 0 && (
139
+ <div className="text-center py-6 sm:py-8 text-gray-500">
140
+ <Users className="h-10 w-10 sm:h-12 sm:w-12 mx-auto mb-3 text-gray-300" />
141
+ <p className="text-sm sm:text-base">No members found</p>
142
+ </div>
143
+ )}
144
+ </div>
145
+ )
146
+ }
@@ -1,5 +1,4 @@
1
1
  import { CodeEditor } from "@/components/package-port/CodeEditor"
2
- import { usePackageVisibilitySettingsDialog } from "@/components/dialogs/package-visibility-settings-dialog"
3
2
  import { useConfirmDiscardChangesDialog } from "@/components/dialogs/confirm-discard-changes-dialog"
4
3
  import { useToast } from "@/hooks/use-toast"
5
4
  import { useUrlParams } from "@/hooks/use-url-params"
@@ -7,7 +6,6 @@ import useWarnUserOnPageChange from "@/hooks/use-warn-user-on-page-change"
7
6
  import { getSnippetTemplate } from "@/lib/get-snippet-template"
8
7
  import { cn } from "@/lib/utils"
9
8
  import type { Package } from "fake-snippets-api/lib/db/schema"
10
- import { Loader2 } from "lucide-react"
11
9
  import { useMemo, useState } from "react"
12
10
  import EditorNav from "@/components/package-port/EditorNav"
13
11
  import { SuspenseRunFrame } from "../SuspenseRunFrame"
@@ -15,8 +13,8 @@ import { applyEditEventsToManualEditsFile } from "@tscircuit/core"
15
13
  import { toastManualEditConflicts } from "@/lib/utils/toastManualEditConflicts"
16
14
  import { ManualEditEvent } from "@tscircuit/props"
17
15
  import { useFileManagement } from "@/hooks/useFileManagement"
18
- import { DEFAULT_CODE } from "@/lib/utils/package-utils"
19
- import { useGlobalStore } from "@/hooks/use-global-store"
16
+ import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
17
+ import { useNewPackageSavePromptDialog } from "../dialogs/new-package-save-prompt-dialog"
20
18
 
21
19
  interface Props {
22
20
  pkg?: Package
@@ -39,13 +37,13 @@ export interface CodeAndPreviewState {
39
37
 
40
38
  export function CodeAndPreview({ pkg, projectUrl }: Props) {
41
39
  const { toast } = useToast()
42
- const session = useGlobalStore((s) => s.session)
43
40
  const urlParams = useUrlParams()
44
41
 
45
42
  const templateFromUrl = useMemo(
46
43
  () => (urlParams.template ? getSnippetTemplate(urlParams.template) : null),
47
44
  [urlParams.template],
48
45
  )
46
+
49
47
  const [state, setState] = useState<CodeAndPreviewState>({
50
48
  showPreview: true,
51
49
  fullScreen: false,
@@ -59,7 +57,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
59
57
  pkg?.snippet_type ?? templateFromUrl?.type ?? urlParams.snippet_type
60
58
 
61
59
  const { Dialog: NewPackageSaveDialog, openDialog: openNewPackageSaveDialog } =
62
- usePackageVisibilitySettingsDialog()
60
+ useNewPackageSavePromptDialog()
63
61
 
64
62
  const { Dialog: DiscardChangesDialog, openDialog: openDiscardChangesDialog } =
65
63
  useConfirmDiscardChangesDialog()
@@ -69,20 +67,26 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
69
67
  isSaving,
70
68
  currentFile,
71
69
  fsMap,
70
+ priorityFileFetched,
72
71
  isLoading,
73
72
  createFile,
73
+ mainComponentPath,
74
74
  deleteFile,
75
+ isFullyLoaded,
75
76
  onFileSelect,
77
+ totalFilesCount,
76
78
  saveFiles,
77
79
  setLocalFiles,
80
+ loadedFilesCount,
78
81
  localFiles,
82
+ currentFileCode,
79
83
  initialFiles,
80
84
  renameFile,
81
85
  packageFilesMeta,
82
86
  } = useFileManagement({
83
87
  templateCode: templateFromUrl?.code,
84
88
  currentPackage: pkg,
85
- fileChoosen: urlParams.file_path ?? null,
89
+ urlParams,
86
90
  openNewPackageSaveDialog,
87
91
  updateLastUpdated: () => {
88
92
  setState((prev) => ({ ...prev, lastSavedAt: Date.now() }))
@@ -94,6 +98,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
94
98
  (!isSaving &&
95
99
  Date.now() - state.lastSavedAt > 1000 &&
96
100
  localFiles.some((file) => {
101
+ if (isHiddenFile(file.path)) return false
97
102
  const initialFile = initialFiles.find((x) => x.path === file.path)
98
103
  return initialFile?.content !== file.content
99
104
  })) ||
@@ -101,31 +106,6 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
101
106
  [localFiles, initialFiles, isSaving, state.lastSavedAt],
102
107
  )
103
108
 
104
- useWarnUserOnPageChange({ hasUnsavedChanges })
105
-
106
- const currentFileCode = useMemo(
107
- () =>
108
- localFiles.find((x) => x.path === currentFile)?.content ??
109
- state.defaultComponentFile ??
110
- DEFAULT_CODE,
111
- [localFiles, currentFile],
112
- )
113
-
114
- const mainComponentPath = useMemo(() => {
115
- const isReactComponentExported =
116
- /export function\s+\w+/.test(currentFileCode) ||
117
- /export const\s+\w+\s*=/.test(currentFileCode) ||
118
- /export default\s+\w+/.test(currentFileCode) ||
119
- /export default\s+function\s*(\w*)\s*\(/.test(currentFileCode) ||
120
- /export default\s*\(\s*\)\s*=>/.test(currentFileCode)
121
-
122
- return (currentFile?.endsWith(".tsx") || currentFile?.endsWith(".ts")) &&
123
- !!localFiles.some((x) => x.path == currentFile) &&
124
- isReactComponentExported
125
- ? currentFile
126
- : state.defaultComponentFile
127
- }, [currentFile, localFiles, currentFileCode])
128
-
129
109
  const handleEditEvent = (event: ManualEditEvent) => {
130
110
  const parsedManualEdits = JSON.parse(
131
111
  localFiles.find((x) => x.path === "manual-edits.json")?.content || "{}",
@@ -171,19 +151,13 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
171
151
  })
172
152
  }
173
153
 
174
- if (urlParams.package_id && (!pkg || isLoading)) {
175
- return (
176
- <div className="flex items-center justify-center h-[80vh]">
177
- <div className="flex flex-col items-center justify-center">
178
- <div className="text-lg text-gray-500 mb-4">Loading</div>
179
- <Loader2 className="w-16 h-16 animate-spin text-gray-400" />
180
- </div>
181
- </div>
182
- )
183
- }
154
+ useWarnUserOnPageChange({
155
+ hasUnsavedChanges: Boolean(hasUnsavedChanges),
156
+ isPackageThere: Boolean(pkg),
157
+ })
184
158
 
185
159
  return (
186
- <div className="flex flex-col min-h-[50vh]">
160
+ <div className="flex flex-col h-full">
187
161
  <EditorNav
188
162
  circuitJson={state.circuitJson}
189
163
  pkg={pkg}
@@ -202,7 +176,9 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
202
176
  packageFilesMeta={packageFilesMeta}
203
177
  />
204
178
  <div
205
- className={`flex ${state.showPreview ? "flex-col md:flex-row" : ""}`}
179
+ className={`flex flex-1 min-h-0 ${
180
+ state.showPreview ? "flex-col md:flex-row" : ""
181
+ }`}
206
182
  >
207
183
  <div
208
184
  className={cn(
@@ -213,8 +189,14 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
213
189
  <CodeEditor
214
190
  isSaving={isSaving}
215
191
  handleCreateFile={createFile}
192
+ totalFilesCount={totalFilesCount}
193
+ loadedFilesCount={loadedFilesCount}
194
+ isFullyLoaded={isFullyLoaded}
216
195
  handleDeleteFile={deleteFile}
217
196
  handleRenameFile={renameFile}
197
+ isPriorityFileFetched={
198
+ !priorityFileFetched && Boolean(urlParams.package_id)
199
+ }
218
200
  pkg={pkg}
219
201
  currentFile={currentFile}
220
202
  onFileSelect={onFileSelect}
@@ -234,7 +216,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
234
216
  </div>
235
217
  <div
236
218
  className={cn(
237
- "flex p-0 flex-col min-h-[640px] overflow-y-hidden",
219
+ "flex p-0 flex-col overflow-y-hidden",
238
220
  state.fullScreen
239
221
  ? "fixed inset-0 z-50 bg-white p-4 overflow-hidden"
240
222
  : "w-full md:w-1/2",
@@ -242,8 +224,10 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
242
224
  )}
243
225
  >
244
226
  <SuspenseRunFrame
227
+ showFileMenu={false}
245
228
  showRunButton
246
229
  forceLatestEvalVersion
230
+ isLoadingFiles={isLoading}
247
231
  onRenderStarted={() =>
248
232
  setState((prev) => ({ ...prev, lastRunCode: currentFileCode }))
249
233
  }
@@ -255,7 +239,7 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
255
239
  onEditEvent={(event) => {
256
240
  handleEditEvent(event)
257
241
  }}
258
- fsMap={fsMap ?? {}}
242
+ fsMap={fsMap}
259
243
  projectUrl={projectUrl}
260
244
  />
261
245
  </div>
@@ -265,3 +249,5 @@ export function CodeAndPreview({ pkg, projectUrl }: Props) {
265
249
  </div>
266
250
  )
267
251
  }
252
+
253
+ export default CodeAndPreview
@@ -23,7 +23,7 @@ import {
23
23
  createSystem,
24
24
  createVirtualTypeScriptEnvironment,
25
25
  } from "@typescript/vfs"
26
- import { loadDefaultLibMap } from "@/lib/ts-lib-cache"
26
+ import { loadDefaultLibMap, fetchWithPackageCaching } from "@/lib/ts-lib-cache"
27
27
  import { tsAutocomplete, tsFacet, tsSync } from "@valtown/codemirror-ts"
28
28
  import { getLints } from "@valtown/codemirror-ts"
29
29
  import { EditorView } from "codemirror"
@@ -50,6 +50,7 @@ import {
50
50
  import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
51
51
  import { inlineCopilot } from "codemirror-copilot"
52
52
  import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
53
+ import { Loader2 } from "lucide-react"
53
54
 
54
55
  const defaultImports = `
55
56
  import React from "@types/react/jsx-runtime"
@@ -59,6 +60,7 @@ import type { CommonLayoutProps } from "@tscircuit/props"
59
60
 
60
61
  export const CodeEditor = ({
61
62
  onCodeChange,
63
+ isPriorityFileFetched,
62
64
  readOnly = false,
63
65
  files = [],
64
66
  isSaving = false,
@@ -72,9 +74,13 @@ export const CodeEditor = ({
72
74
  handleCreateFile,
73
75
  handleDeleteFile,
74
76
  pkg,
77
+ isFullyLoaded = false,
78
+ totalFilesCount = 0,
79
+ loadedFilesCount = 0,
75
80
  }: {
76
81
  onCodeChange: (code: string, filename?: string) => void
77
82
  files: PackageFile[]
83
+ isPriorityFileFetched: boolean
78
84
  isSaving?: boolean
79
85
  handleCreateFile: (props: ICreateFileProps) => ICreateFileResult
80
86
  handleDeleteFile: (props: IDeleteFileProps) => IDeleteFileResult
@@ -87,6 +93,9 @@ export const CodeEditor = ({
87
93
  onFileContentChanged?: (path: string, content: string) => void
88
94
  currentFile: string | null
89
95
  onFileSelect: (path: string, lineNumber?: number) => void
96
+ isFullyLoaded?: boolean
97
+ totalFilesCount?: number
98
+ loadedFilesCount?: number
90
99
  }) => {
91
100
  const editorRef = useRef<HTMLDivElement>(null)
92
101
  const viewRef = useRef<EditorView | null>(null)
@@ -112,7 +121,7 @@ export const CodeEditor = ({
112
121
  useViewTsFilesDialog()
113
122
 
114
123
  const entryPointFileName = useMemo(() => {
115
- const entryPointFile = findTargetFile(files, null)
124
+ const entryPointFile = findTargetFile({ files, filePathFromUrl: null })
116
125
  if (entryPointFile?.path) return entryPointFile.path
117
126
  return files.find((x) => x.path === "index.tsx")?.path || "index.tsx"
118
127
  }, [files])
@@ -120,11 +129,14 @@ export const CodeEditor = ({
120
129
  const [sidebarOpen, setSidebarOpen] = useState(false)
121
130
  const [isCreatingFile, setIsCreatingFile] = useState(false)
122
131
 
123
- // Set current file on component mount
132
+ // Set current file on component mount - only when explicitly requested via URL
124
133
  useEffect(() => {
125
134
  if (files.length === 0 || !pkgFilesLoaded || currentFile) return
126
135
 
127
- const targetFile = findTargetFile(files, filePathFromUrl)
136
+ // Only run this if there's an explicit file_path in URL - don't auto-select files
137
+ if (!filePathFromUrl) return
138
+
139
+ const targetFile = findTargetFile({ files, filePathFromUrl })
128
140
  if (targetFile) {
129
141
  const lineNumber = lineNumberFromUrl
130
142
  ? parseInt(lineNumberFromUrl, 10)
@@ -207,33 +219,7 @@ export const CodeEditor = ({
207
219
  projectName: "my-project",
208
220
  typescript: tsModule,
209
221
  logger: console,
210
- fetcher: (async (input: RequestInfo | URL, init?: RequestInit) => {
211
- const registryPrefixes = [
212
- "https://data.jsdelivr.com/v1/package/resolve/npm/@tsci/",
213
- "https://data.jsdelivr.com/v1/package/npm/@tsci/",
214
- "https://cdn.jsdelivr.net/npm/@tsci/",
215
- ]
216
- if (
217
- typeof input === "string" &&
218
- registryPrefixes.some((prefix) => input.startsWith(prefix))
219
- ) {
220
- const fullPackageName = input
221
- .replace(registryPrefixes[0], "")
222
- .replace(registryPrefixes[1], "")
223
- .replace(registryPrefixes[2], "")
224
- const packageName = fullPackageName.split("/")[0].replace(/\./, "/")
225
- const pathInPackage = fullPackageName.split("/").slice(1).join("/")
226
- const jsdelivrPath = `${packageName}${
227
- pathInPackage ? `/${pathInPackage}` : ""
228
- }`
229
- return fetch(
230
- `${apiUrl}/snippets/download?jsdelivr_resolve=${input.includes(
231
- "/resolve/",
232
- )}&jsdelivr_path=${encodeURIComponent(jsdelivrPath)}`,
233
- )
234
- }
235
- return fetch(input, init)
236
- }) as typeof fetch,
222
+ fetcher: fetchWithPackageCaching as typeof fetch,
237
223
  delegate: {
238
224
  started: () => {
239
225
  const manualEditsTypeDeclaration = `
@@ -825,10 +811,15 @@ export const CodeEditor = ({
825
811
  isCreatingFile={isCreatingFile}
826
812
  setIsCreatingFile={setIsCreatingFile}
827
813
  pkg={pkg}
814
+ isLoadingFiles={!isFullyLoaded}
815
+ loadingProgress={
816
+ totalFilesCount > 0 ? `${loadedFilesCount}/${totalFilesCount}` : null
817
+ }
828
818
  />
829
819
  <div className="flex flex-col flex-1 w-full min-w-0 h-full">
830
820
  {showImportAndFormatButtons && (
831
821
  <CodeEditorHeader
822
+ isLoadingFiles={!isFullyLoaded}
832
823
  entrypointFileName={entryPointFileName}
833
824
  appendNewFile={(path: string, content: string) => {
834
825
  onFileContentChanged?.(path, content)
@@ -854,7 +845,13 @@ export const CodeEditor = ({
854
845
  className={
855
846
  "flex-1 overflow-auto [&_.cm-editor]:h-full [&_.cm-scroller]:!h-full"
856
847
  }
848
+ style={{ display: isPriorityFileFetched ? "none" : "block" }}
857
849
  />
850
+ {isPriorityFileFetched && (
851
+ <div className="grid place-items-center h-full">
852
+ <Loader2 className="w-16 h-16 animate-spin text-gray-400" />
853
+ </div>
854
+ )}
858
855
  </div>
859
856
  {showQuickOpen && (
860
857
  <QuickOpen