@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.
- package/.github/workflows/bun-formatcheck.yml +2 -2
- package/.github/workflows/bun-pver-release.yml +3 -3
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +2 -2
- package/.github/workflows/update-snapshots.yml +1 -1
- package/README.md +4 -0
- package/api/generated-index.js +37 -3
- package/biome.json +2 -1
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +32 -3
- package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
- package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
- package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
- package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
- package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
- package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +151 -0
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
- package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
- package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
- package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
- package/bun.lock +361 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1313 -639
- package/dist/index.d.ts +313 -6
- package/dist/index.js +328 -24
- package/dist/schema.d.ts +290 -1
- package/dist/schema.js +54 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +219 -4
- package/fake-snippets-api/lib/db/schema.ts +63 -1
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
- package/fake-snippets-api/lib/public-mapping/public-map-org.ts +33 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
- package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
- package/fake-snippets-api/routes/api/orgs/create.ts +48 -0
- package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
- package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
- package/fake-snippets-api/routes/api/orgs/list_members.ts +60 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +118 -0
- package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
- package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
- package/fake-snippets-api/routes/api/packages/create.ts +57 -10
- package/fake-snippets-api/routes/api/packages/get.ts +23 -0
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
- package/fake-snippets-api/routes/api/packages/list.ts +29 -2
- package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
- package/package.json +25 -19
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +27 -8
- package/src/ContextProviders.tsx +25 -2
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +281 -247
- package/src/components/DownloadButtonAndMenu.tsx +17 -5
- package/src/components/FileSidebar.tsx +11 -17
- package/src/components/Footer.tsx +8 -9
- package/src/components/Header.tsx +19 -32
- package/src/components/Header2.tsx +16 -32
- package/src/components/HeaderDropdown.tsx +13 -8
- package/src/components/HeaderLogin.tsx +43 -15
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- package/src/components/PackageSearchResults.tsx +1 -1
- package/src/components/PrefetchPageLink.tsx +7 -1
- package/src/components/ProfileRouter.tsx +32 -0
- package/src/components/SearchComponent.tsx +12 -8
- package/src/components/SentryNotFoundReporter.tsx +44 -0
- package/src/components/UserCard.tsx +80 -0
- package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
- package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
- package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
- package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
- package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
- package/src/components/dialogs/import-component-dialog.tsx +16 -9
- package/src/components/dialogs/import-package-dialog.tsx +3 -2
- package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
- package/src/components/organization/OrganizationCard.tsx +206 -0
- package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
- package/src/components/organization/OrganizationHeader.tsx +154 -0
- package/src/components/organization/OrganizationMembers.tsx +146 -0
- package/src/components/package-port/CodeAndPreview.tsx +15 -12
- package/src/components/package-port/CodeEditor.tsx +4 -30
- package/src/components/package-port/CodeEditorHeader.tsx +123 -61
- package/src/components/package-port/EditorNav.tsx +32 -49
- package/src/components/preview/ConnectedPackagesList.tsx +8 -8
- package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
- package/src/components/preview/PackageReleasesDashboard.tsx +23 -11
- package/src/components/ui/tree-view.tsx +6 -3
- package/src/hooks/use-add-org-member-mutation.ts +51 -0
- package/src/hooks/use-create-org-mutation.ts +38 -0
- package/src/hooks/use-create-package-mutation.ts +3 -0
- package/src/hooks/use-current-package-release.ts +4 -3
- package/src/hooks/use-download-zip.ts +2 -2
- package/src/hooks/use-global-store.ts +6 -4
- package/src/hooks/use-hydration.ts +30 -0
- package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
- package/src/hooks/use-list-org-members.ts +27 -0
- package/src/hooks/use-list-user-orgs.ts +25 -0
- package/src/hooks/use-org-by-github-handle.ts +26 -0
- package/src/hooks/use-org.ts +24 -0
- package/src/hooks/use-organization.ts +42 -0
- package/src/hooks/use-package-as-snippet.ts +4 -2
- package/src/hooks/use-package-builds.ts +6 -2
- package/src/hooks/use-package-files.ts +5 -3
- package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
- package/src/hooks/use-package-release-images.ts +105 -0
- package/src/hooks/use-package-release.ts +2 -2
- package/src/hooks/use-package-stars.ts +80 -4
- package/src/hooks/use-preview-images.ts +6 -3
- package/src/hooks/use-remove-org-member-mutation.ts +32 -0
- package/src/hooks/use-update-ai-description-mutation.ts +42 -0
- package/src/hooks/use-update-org-mutation.ts +41 -0
- package/src/hooks/use-warn-user-on-page-change.ts +71 -4
- package/src/hooks/useFileManagement.ts +51 -22
- package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
- package/src/lib/download-fns/download-kicad-files.ts +22 -11
- package/src/lib/download-fns/download-step.ts +12 -0
- package/src/lib/normalize-svg-for-tile.ts +50 -0
- package/src/lib/posthog.ts +11 -9
- package/src/lib/react-query-api-failure-tracking.ts +148 -0
- package/src/lib/sentry.ts +14 -0
- package/src/lib/templates/blank-circuit-board-template.ts +0 -4
- package/src/lib/ts-lib-cache.ts +122 -7
- package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
- package/src/lib/utils/findTargetFile.ts +45 -10
- package/src/lib/utils/isComponentExported.ts +2 -1
- package/src/main.tsx +2 -1
- package/src/pages/create-organization.tsx +169 -0
- package/src/pages/dashboard.tsx +38 -6
- package/src/pages/datasheet.tsx +1 -1
- package/src/pages/datasheets.tsx +3 -3
- package/src/pages/editor.tsx +4 -6
- package/src/pages/landing.tsx +6 -6
- package/src/pages/latest.tsx +3 -0
- package/src/pages/organization-profile.tsx +199 -0
- package/src/pages/organization-settings.tsx +569 -0
- package/src/pages/package-editor.tsx +21 -21
- package/src/pages/preview-release.tsx +75 -145
- package/src/pages/quickstart.tsx +159 -123
- package/src/pages/release-detail.tsx +119 -31
- package/src/pages/search.tsx +197 -57
- package/src/pages/settings-redirect.tsx +44 -0
- package/src/pages/trending.tsx +29 -20
- package/src/pages/user-profile.tsx +58 -7
- package/src/pages/user-settings.tsx +161 -0
- package/src/pages/view-package.tsx +30 -16
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
- package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
- package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
- package/src/components/PageSearchComponent.tsx +0 -148
- package/src/pages/package-builds.tsx +0 -33
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { QueryKey } from "react-query"
|
|
2
|
+
import { posthog } from "./posthog"
|
|
3
|
+
|
|
4
|
+
const TARGET_HOSTNAMES = new Set(["api.tscircuit.com"])
|
|
5
|
+
|
|
6
|
+
type FailureContext = {
|
|
7
|
+
operationType: "query" | "mutation"
|
|
8
|
+
queryKey?: QueryKey
|
|
9
|
+
mutationKey?: unknown
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type RedaxiosConfig = {
|
|
13
|
+
url?: string
|
|
14
|
+
baseURL?: string
|
|
15
|
+
method?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type ResponseLike = {
|
|
19
|
+
url?: string
|
|
20
|
+
status?: number
|
|
21
|
+
statusText?: string
|
|
22
|
+
config?: RedaxiosConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isPosthogLoaded = () => Boolean((posthog as any)?.__loaded)
|
|
26
|
+
|
|
27
|
+
const toUpperCaseMethod = (method?: string) => method?.toUpperCase() ?? "GET"
|
|
28
|
+
|
|
29
|
+
const resolveAbsoluteUrl = (url?: string, baseURL?: string) => {
|
|
30
|
+
if (!url) return undefined
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (baseURL) {
|
|
34
|
+
return new URL(url, baseURL).toString()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof window !== "undefined") {
|
|
38
|
+
return new URL(url, window.location.origin).toString()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return new URL(url).toString()
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.warn("Failed to resolve API failure URL", error)
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const shouldTrackUrl = (resolvedUrl?: string) => {
|
|
49
|
+
if (!resolvedUrl) return false
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const { hostname } = new URL(resolvedUrl)
|
|
53
|
+
return TARGET_HOSTNAMES.has(hostname)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn("Failed to parse URL for API failure tracking", error)
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const serializeKey = (key?: unknown) => {
|
|
61
|
+
if (!key) return undefined
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(key)
|
|
65
|
+
} catch {
|
|
66
|
+
return String(key)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const extractFromResponse = (error: unknown): ResponseLike | null => {
|
|
71
|
+
if (!error || typeof error !== "object") return null
|
|
72
|
+
|
|
73
|
+
const maybeResponse = error as Partial<ResponseLike>
|
|
74
|
+
if (typeof maybeResponse.status !== "number" || !("url" in maybeResponse)) {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
url: maybeResponse.url,
|
|
80
|
+
status: maybeResponse.status,
|
|
81
|
+
statusText: maybeResponse.statusText,
|
|
82
|
+
config: maybeResponse.config,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const extractFromAxiosError = (error: unknown): ResponseLike | null => {
|
|
87
|
+
if (!error || typeof error !== "object") return null
|
|
88
|
+
|
|
89
|
+
const maybeAxiosError = error as {
|
|
90
|
+
response?: ResponseLike
|
|
91
|
+
config?: RedaxiosConfig
|
|
92
|
+
message?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!maybeAxiosError.response && !maybeAxiosError.config) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
url: maybeAxiosError.response?.url,
|
|
101
|
+
status: maybeAxiosError.response?.status,
|
|
102
|
+
statusText: maybeAxiosError.response?.statusText,
|
|
103
|
+
config: maybeAxiosError.response?.config ?? maybeAxiosError.config,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const captureApiFailure = (
|
|
108
|
+
response: ResponseLike,
|
|
109
|
+
error: unknown,
|
|
110
|
+
context: FailureContext,
|
|
111
|
+
) => {
|
|
112
|
+
if (!isPosthogLoaded()) return
|
|
113
|
+
|
|
114
|
+
const resolvedUrl =
|
|
115
|
+
resolveAbsoluteUrl(response.url, response.config?.baseURL) ??
|
|
116
|
+
resolveAbsoluteUrl(response.config?.url, response.config?.baseURL)
|
|
117
|
+
if (!shouldTrackUrl(resolvedUrl)) return
|
|
118
|
+
|
|
119
|
+
const errorMessage =
|
|
120
|
+
error instanceof Error
|
|
121
|
+
? error.message
|
|
122
|
+
: typeof error === "string"
|
|
123
|
+
? error
|
|
124
|
+
: undefined
|
|
125
|
+
|
|
126
|
+
posthog.capture("api_request_failed", {
|
|
127
|
+
url: resolvedUrl,
|
|
128
|
+
method: toUpperCaseMethod(response.config?.method),
|
|
129
|
+
status: response.status,
|
|
130
|
+
statusText: response.statusText,
|
|
131
|
+
errorMessage,
|
|
132
|
+
operationType: context.operationType,
|
|
133
|
+
queryKey: serializeKey(context.queryKey),
|
|
134
|
+
mutationKey: serializeKey(context.mutationKey),
|
|
135
|
+
environment:
|
|
136
|
+
typeof window !== "undefined" ? window.location.hostname : undefined,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const trackReactQueryApiFailure = (
|
|
141
|
+
error: unknown,
|
|
142
|
+
context: FailureContext,
|
|
143
|
+
) => {
|
|
144
|
+
const response = extractFromResponse(error) ?? extractFromAxiosError(error)
|
|
145
|
+
if (!response) return
|
|
146
|
+
|
|
147
|
+
captureApiFailure(response, error, context)
|
|
148
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/react"
|
|
2
|
+
|
|
3
|
+
if (
|
|
4
|
+
typeof window !== "undefined" &&
|
|
5
|
+
import.meta.env.VITE_SENTRY_DSN &&
|
|
6
|
+
!window.location.hostname.includes("localhost") &&
|
|
7
|
+
!window.location.hostname.includes("127.0.0.1")
|
|
8
|
+
) {
|
|
9
|
+
Sentry.init({
|
|
10
|
+
dsn: import.meta.env.VITE_SENTRY_DSN,
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { Sentry }
|
|
@@ -7,15 +7,11 @@ export default () => (
|
|
|
7
7
|
resistance="1k"
|
|
8
8
|
footprint="0402"
|
|
9
9
|
name="R1"
|
|
10
|
-
schX={3}
|
|
11
|
-
pcbX={3}
|
|
12
10
|
/>
|
|
13
11
|
<capacitor
|
|
14
12
|
capacitance="1000pF"
|
|
15
13
|
footprint="0402"
|
|
16
14
|
name="C1"
|
|
17
|
-
schX={-3}
|
|
18
|
-
pcbX={-3}
|
|
19
15
|
/>
|
|
20
16
|
<trace from=".R1 > .pin1" to=".C1 > .pin1" />
|
|
21
17
|
</board>
|
package/src/lib/ts-lib-cache.ts
CHANGED
|
@@ -8,6 +8,7 @@ import ts from "typescript"
|
|
|
8
8
|
|
|
9
9
|
const TS_LIB_VERSION = "5.6.3"
|
|
10
10
|
const CACHE_PREFIX = `ts-lib-${TS_LIB_VERSION}-`
|
|
11
|
+
const CACHE_TTL = 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
11
12
|
|
|
12
13
|
export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
13
14
|
const fsMap = new Map<string, string>()
|
|
@@ -18,9 +19,23 @@ export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
|
18
19
|
const missing: string[] = []
|
|
19
20
|
|
|
20
21
|
for (const lib of libs) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const cacheKey = CACHE_PREFIX + lib
|
|
23
|
+
const cached = await get(cacheKey)
|
|
24
|
+
if (
|
|
25
|
+
cached &&
|
|
26
|
+
typeof cached === "object" &&
|
|
27
|
+
"compressedFileContent" in cached &&
|
|
28
|
+
"timestamp" in cached
|
|
29
|
+
) {
|
|
30
|
+
const { compressedFileContent, timestamp } = cached as {
|
|
31
|
+
compressedFileContent: string
|
|
32
|
+
timestamp: number
|
|
33
|
+
}
|
|
34
|
+
if (Date.now() - timestamp < CACHE_TTL) {
|
|
35
|
+
fsMap.set("/" + lib, decompressFromUTF16(compressedFileContent))
|
|
36
|
+
} else {
|
|
37
|
+
missing.push(lib)
|
|
38
|
+
}
|
|
24
39
|
} else {
|
|
25
40
|
missing.push(lib)
|
|
26
41
|
}
|
|
@@ -36,12 +51,112 @@ export async function loadDefaultLibMap(): Promise<Map<string, string>> {
|
|
|
36
51
|
)
|
|
37
52
|
for (const [filename, content] of fetched) {
|
|
38
53
|
fsMap.set(filename, content)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
const cacheKey = CACHE_PREFIX + filename.replace(/^\//, "")
|
|
55
|
+
const compressed = compressToUTF16(content)
|
|
56
|
+
await set(cacheKey, {
|
|
57
|
+
compressedFileContent: compressed,
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
}).catch(() => {})
|
|
43
60
|
}
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
return fsMap
|
|
47
64
|
}
|
|
65
|
+
|
|
66
|
+
export async function fetchWithPackageCaching(
|
|
67
|
+
input: RequestInfo | URL,
|
|
68
|
+
init?: RequestInit,
|
|
69
|
+
): Promise<Response> {
|
|
70
|
+
const url = typeof input === "string" ? input : input.toString()
|
|
71
|
+
|
|
72
|
+
// Only cache GET requests for packages
|
|
73
|
+
if (init?.method && init.method !== "GET") {
|
|
74
|
+
return fetch(input, init)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if this should be cached
|
|
78
|
+
const shouldCache =
|
|
79
|
+
url.includes("jsdelivr.net") ||
|
|
80
|
+
url.includes("unpkg.com") ||
|
|
81
|
+
url.includes("@types/") ||
|
|
82
|
+
url.includes("@tsci/")
|
|
83
|
+
|
|
84
|
+
if (!shouldCache) {
|
|
85
|
+
return fetch(input, init)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const cacheKey = `package-cache-${url}`
|
|
89
|
+
|
|
90
|
+
// Check cache
|
|
91
|
+
const cached = await get(cacheKey).catch(() => null)
|
|
92
|
+
if (
|
|
93
|
+
cached &&
|
|
94
|
+
typeof cached === "object" &&
|
|
95
|
+
"compressedFileContent" in cached &&
|
|
96
|
+
"timestamp" in cached
|
|
97
|
+
) {
|
|
98
|
+
const { compressedFileContent, timestamp } = cached as {
|
|
99
|
+
compressedFileContent: string
|
|
100
|
+
timestamp: number
|
|
101
|
+
}
|
|
102
|
+
if (Date.now() - timestamp < CACHE_TTL) {
|
|
103
|
+
return new Response(decompressFromUTF16(compressedFileContent), {
|
|
104
|
+
status: 200,
|
|
105
|
+
statusText: "OK",
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle @tsci packages
|
|
111
|
+
let fetchUrl = url
|
|
112
|
+
if (
|
|
113
|
+
url.includes("@tsci/") &&
|
|
114
|
+
(url.includes("jsdelivr.net") || url.includes("data.jsdelivr.com"))
|
|
115
|
+
) {
|
|
116
|
+
let packagePath = ""
|
|
117
|
+
if (url.includes("jsdelivr.net")) {
|
|
118
|
+
packagePath = url.replace("https://cdn.jsdelivr.net/npm/@tsci/", "")
|
|
119
|
+
} else if (url.includes("/v1/package/resolve/npm/@tsci/")) {
|
|
120
|
+
const resolveIndex = url.indexOf("/v1/package/resolve/npm/@tsci/")
|
|
121
|
+
packagePath = url.substring(
|
|
122
|
+
resolveIndex + "/v1/package/resolve/npm/@tsci/".length,
|
|
123
|
+
)
|
|
124
|
+
} else if (url.includes("/v1/package/npm/@tsci/")) {
|
|
125
|
+
const npmIndex = url.indexOf("/v1/package/npm/@tsci/")
|
|
126
|
+
packagePath = url.substring(npmIndex + "/v1/package/npm/@tsci/".length)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (packagePath) {
|
|
130
|
+
// Convert dots to slashes in the package name part (like original logic)
|
|
131
|
+
const parts = packagePath.split("/")
|
|
132
|
+
if (parts.length > 0) {
|
|
133
|
+
parts[0] = parts[0].replace(/\./, "/")
|
|
134
|
+
}
|
|
135
|
+
const transformedPackagePath = parts.join("/")
|
|
136
|
+
|
|
137
|
+
const apiUrl = import.meta.env.VITE_SNIPPETS_API_URL ?? "/api"
|
|
138
|
+
const isResolve = url.includes("/resolve/")
|
|
139
|
+
fetchUrl = `${apiUrl}/snippets/download?jsdelivr_resolve=${isResolve}&jsdelivr_path=${encodeURIComponent(
|
|
140
|
+
transformedPackagePath,
|
|
141
|
+
)}`
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fetch and cache
|
|
146
|
+
const response = await fetch(fetchUrl, init)
|
|
147
|
+
if (response.ok) {
|
|
148
|
+
const text = await response.text()
|
|
149
|
+
const compressed = compressToUTF16(text)
|
|
150
|
+
await set(cacheKey, {
|
|
151
|
+
compressedFileContent: compressed,
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
}).catch(() => {})
|
|
154
|
+
return new Response(text, {
|
|
155
|
+
status: response.status,
|
|
156
|
+
statusText: response.statusText,
|
|
157
|
+
headers: response.headers,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return response
|
|
162
|
+
}
|
|
@@ -5,10 +5,10 @@ export const checkIfManualEditsImported = (
|
|
|
5
5
|
file: string = "index.tsx",
|
|
6
6
|
) => {
|
|
7
7
|
if (!files[file]) return false
|
|
8
|
-
const targetFile = findTargetFile(
|
|
9
|
-
Object.keys(files).map((f) => ({ path: f, content: files[f] })),
|
|
10
|
-
null,
|
|
11
|
-
)
|
|
8
|
+
const targetFile = findTargetFile({
|
|
9
|
+
files: Object.keys(files).map((f) => ({ path: f, content: files[f] })),
|
|
10
|
+
filePathFromUrl: null,
|
|
11
|
+
})
|
|
12
12
|
if (targetFile && file !== targetFile.path) {
|
|
13
13
|
return false
|
|
14
14
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { isHiddenFile } from "@/components/ViewPackagePage/utils/is-hidden-file"
|
|
1
2
|
import { PackageFile } from "@/types/package"
|
|
3
|
+
import { isComponentExported } from "./isComponentExported"
|
|
2
4
|
|
|
3
5
|
export const findMainEntrypointFileFromTscircuitConfig = (
|
|
4
6
|
files: PackageFile[],
|
|
@@ -24,37 +26,70 @@ export const findMainEntrypointFileFromTscircuitConfig = (
|
|
|
24
26
|
return null
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
export const findTargetFile = (
|
|
28
|
-
files
|
|
29
|
-
filePathFromUrl
|
|
30
|
-
|
|
29
|
+
export const findTargetFile = ({
|
|
30
|
+
files,
|
|
31
|
+
filePathFromUrl,
|
|
32
|
+
fallbackToAnyFile = true,
|
|
33
|
+
}: {
|
|
34
|
+
files: PackageFile[]
|
|
35
|
+
filePathFromUrl: string | null
|
|
36
|
+
fallbackToAnyFile?: boolean
|
|
37
|
+
}): PackageFile | null => {
|
|
31
38
|
if (files.length === 0) {
|
|
32
39
|
return null
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
let targetFile: PackageFile | null = null
|
|
42
|
+
let targetFile: PackageFile | undefined | null = null
|
|
43
|
+
if (!filePathFromUrl) {
|
|
44
|
+
files = files.filter((x) => !isHiddenFile(x.path))
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
if (filePathFromUrl) {
|
|
38
|
-
|
|
48
|
+
const file = files.find((file) => file.path === filePathFromUrl)?.path
|
|
49
|
+
if (
|
|
50
|
+
file &&
|
|
51
|
+
!file.endsWith(".ts") &&
|
|
52
|
+
!file.endsWith(".tsx") &&
|
|
53
|
+
fallbackToAnyFile
|
|
54
|
+
) {
|
|
55
|
+
targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
|
|
56
|
+
} else {
|
|
57
|
+
const _isComponentExported = isComponentExported(
|
|
58
|
+
files.find((file) => file.path === filePathFromUrl)?.content || "",
|
|
59
|
+
)
|
|
60
|
+
if (_isComponentExported) {
|
|
61
|
+
targetFile = files.find((file) => file.path === filePathFromUrl) ?? null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
39
64
|
}
|
|
40
65
|
|
|
41
66
|
if (!targetFile) {
|
|
42
67
|
targetFile = findMainEntrypointFileFromTscircuitConfig(files)
|
|
43
68
|
}
|
|
69
|
+
if (!targetFile) {
|
|
70
|
+
targetFile =
|
|
71
|
+
files.find(
|
|
72
|
+
(file) => file.path === "index.tsx" || file.path === "index.ts",
|
|
73
|
+
) ?? null
|
|
74
|
+
}
|
|
44
75
|
|
|
45
76
|
if (!targetFile) {
|
|
46
|
-
targetFile =
|
|
77
|
+
targetFile =
|
|
78
|
+
files.find((file) => file.path.endsWith(".circuit.tsx")) ?? null
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
if (!targetFile) {
|
|
50
|
-
targetFile =
|
|
82
|
+
targetFile =
|
|
83
|
+
files.find(
|
|
84
|
+
(file) => file.path === "main.tsx" || file.path === "main.ts",
|
|
85
|
+
) ?? null
|
|
51
86
|
}
|
|
52
87
|
|
|
53
88
|
if (!targetFile) {
|
|
54
|
-
targetFile = files.find((file) => file.path
|
|
89
|
+
targetFile = files.find((file) => file.path.endsWith(".tsx")) ?? null
|
|
55
90
|
}
|
|
56
91
|
|
|
57
|
-
if (!targetFile && files[0]) {
|
|
92
|
+
if (!targetFile && files[0] && fallbackToAnyFile) {
|
|
58
93
|
targetFile = files[0]
|
|
59
94
|
}
|
|
60
95
|
|
|
@@ -4,6 +4,7 @@ export const isComponentExported = (code: string) => {
|
|
|
4
4
|
/export const\s+\w+\s*=/.test(code) ||
|
|
5
5
|
/export default\s+\w+/.test(code) ||
|
|
6
6
|
/export default\s+function\s*(\w*)\s*\(/.test(code) ||
|
|
7
|
-
/export default\s*\(\s*\)\s*=>/.test(code)
|
|
7
|
+
/export default\s*\(\s*\)\s*=>/.test(code) ||
|
|
8
|
+
/export default\s*\(.*?\)\s*=>/.test(code)
|
|
8
9
|
)
|
|
9
10
|
}
|
package/src/main.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { StrictMode } from "react"
|
|
2
2
|
import { createRoot } from "react-dom/client"
|
|
3
3
|
import App from "./App.tsx"
|
|
4
|
+
import "./lib/sentry"
|
|
5
|
+
import "./index.css"
|
|
4
6
|
|
|
5
7
|
if (typeof window !== "undefined" && !window.__APP_LOADED_AT) {
|
|
6
8
|
window.__APP_LOADED_AT = Date.now()
|
|
7
9
|
}
|
|
8
|
-
import "./index.css"
|
|
9
10
|
|
|
10
11
|
createRoot(document.getElementById("root")!).render(
|
|
11
12
|
<StrictMode>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React, { useState } from "react"
|
|
2
|
+
import { useLocation } from "wouter"
|
|
3
|
+
import { Helmet } from "react-helmet-async"
|
|
4
|
+
import Header from "@/components/Header"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { Input } from "@/components/ui/input"
|
|
7
|
+
import { Label } from "@/components/ui/label"
|
|
8
|
+
import toast from "react-hot-toast"
|
|
9
|
+
import { useCreateOrgMutation } from "@/hooks/use-create-org-mutation"
|
|
10
|
+
|
|
11
|
+
interface FormErrors {
|
|
12
|
+
name?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const CreateOrganizationPage = () => {
|
|
16
|
+
const [, setLocation] = useLocation()
|
|
17
|
+
|
|
18
|
+
const [formData, setFormData] = useState({
|
|
19
|
+
name: "",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const [errors, setErrors] = useState<FormErrors>({})
|
|
23
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
24
|
+
|
|
25
|
+
const { mutate: createOrganization, isLoading: isMutating } =
|
|
26
|
+
useCreateOrgMutation({
|
|
27
|
+
onSuccess: (newOrganization) => {
|
|
28
|
+
toast.success(
|
|
29
|
+
`Organization "${newOrganization.name || formData.name}" created successfully!`,
|
|
30
|
+
)
|
|
31
|
+
setLocation(`/${newOrganization.name || formData.name}`)
|
|
32
|
+
setIsLoading(false)
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const validateForm = (): boolean => {
|
|
37
|
+
const newErrors: FormErrors = {}
|
|
38
|
+
|
|
39
|
+
if (!formData.name) {
|
|
40
|
+
newErrors.name = "Organization name is required"
|
|
41
|
+
} else if (formData.name.length > 30) {
|
|
42
|
+
newErrors.name = "Organization name must be less than 30 characters"
|
|
43
|
+
} else if (!/^[a-zA-Z0-9-_]+$/.test(formData.name)) {
|
|
44
|
+
newErrors.name =
|
|
45
|
+
"Organization name can only contain letters, numbers, hyphens, and underscores"
|
|
46
|
+
}
|
|
47
|
+
setErrors(newErrors)
|
|
48
|
+
return Object.keys(newErrors).length === 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
52
|
+
e.preventDefault()
|
|
53
|
+
|
|
54
|
+
if (!validateForm()) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setIsLoading(true)
|
|
59
|
+
createOrganization(
|
|
60
|
+
{ name: formData.name },
|
|
61
|
+
{
|
|
62
|
+
onError: (error: any) => {
|
|
63
|
+
console.error("Failed to create organization:", error)
|
|
64
|
+
toast.error(
|
|
65
|
+
error?.response?.data?.error?.message ||
|
|
66
|
+
error?.data?.error?.message ||
|
|
67
|
+
"Failed to create organization. Please try again.",
|
|
68
|
+
)
|
|
69
|
+
setIsLoading(false)
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
76
|
+
const name = e.target.value
|
|
77
|
+
setFormData((prev) => ({ ...prev, name }))
|
|
78
|
+
if (errors.name) {
|
|
79
|
+
setErrors({})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const handleCancel = () => {
|
|
84
|
+
setLocation("/dashboard")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="min-h-screen bg-white">
|
|
89
|
+
<Helmet>
|
|
90
|
+
<title>Create Organization - tscircuit</title>
|
|
91
|
+
<meta
|
|
92
|
+
name="description"
|
|
93
|
+
content="Create a new organization to collaborate with others and manage shared projects."
|
|
94
|
+
/>
|
|
95
|
+
</Helmet>
|
|
96
|
+
|
|
97
|
+
<Header />
|
|
98
|
+
|
|
99
|
+
<div className="flex items-center justify-center min-h-[calc(100vh-80px)] px-4 sm:px-6 lg:px-8">
|
|
100
|
+
<div className="w-full max-w-lg mx-auto">
|
|
101
|
+
<div className="text-center mb-8">
|
|
102
|
+
<h1 className="text-xl sm:text-2xl font-semibold text-gray-900 mb-2">
|
|
103
|
+
Set up your organization
|
|
104
|
+
</h1>
|
|
105
|
+
<p className="text-gray-600 text-sm">
|
|
106
|
+
Tell us about your organization
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
111
|
+
<div className="space-y-2">
|
|
112
|
+
<Label
|
|
113
|
+
htmlFor="org-name"
|
|
114
|
+
className="text-sm font-semibold text-gray-900"
|
|
115
|
+
>
|
|
116
|
+
Organization Name
|
|
117
|
+
<span className="text-red-500">*</span>
|
|
118
|
+
</Label>
|
|
119
|
+
<Input
|
|
120
|
+
spellCheck={false}
|
|
121
|
+
id="org-handle"
|
|
122
|
+
type="text"
|
|
123
|
+
placeholder="tscircuit"
|
|
124
|
+
value={formData.name}
|
|
125
|
+
onChange={handleNameChange}
|
|
126
|
+
className={`h-10 sm:h-11 ${
|
|
127
|
+
errors.name
|
|
128
|
+
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
|
129
|
+
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
|
130
|
+
}`}
|
|
131
|
+
disabled={isLoading || isMutating}
|
|
132
|
+
/>
|
|
133
|
+
{errors.name && (
|
|
134
|
+
<p className="text-sm text-red-600">{errors.name}</p>
|
|
135
|
+
)}
|
|
136
|
+
<p className="text-xs text-gray-500">
|
|
137
|
+
This will be the name of your organization on tscircuit.
|
|
138
|
+
<br />
|
|
139
|
+
Your URL will be:{" "}
|
|
140
|
+
<span className="font-mono text-gray-700">
|
|
141
|
+
tscircuit.com/{formData.name || "orgname"}
|
|
142
|
+
</span>
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div>
|
|
147
|
+
<Button
|
|
148
|
+
type="submit"
|
|
149
|
+
disabled={isLoading || isMutating || !formData.name}
|
|
150
|
+
className="w-full h-10 sm:h-11 bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm sm:text-base"
|
|
151
|
+
>
|
|
152
|
+
{isLoading || isMutating ? (
|
|
153
|
+
<>
|
|
154
|
+
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
155
|
+
Creating organization...
|
|
156
|
+
</>
|
|
157
|
+
) : (
|
|
158
|
+
"Create organization"
|
|
159
|
+
)}
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
</form>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export default CreateOrganizationPage
|