@tscircuit/fake-snippets 0.0.109 → 0.0.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +31 -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 +99 -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 +349 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1253 -624
- package/dist/index.d.ts +291 -4
- package/dist/index.js +323 -23
- package/dist/schema.d.ts +274 -1
- package/dist/schema.js +52 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +214 -3
- package/fake-snippets-api/lib/db/schema.ts +61 -0
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
- 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 +32 -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 +46 -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 +67 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +93 -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 +54 -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 +24 -20
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +29 -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 +3 -4
- 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/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 +204 -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-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 +12 -11
- 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 +168 -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 +566 -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 +192 -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/view-package.tsx +7 -13
- 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
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,168 @@
|
|
|
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
|
+
"Failed to create organization. Please try again.",
|
|
67
|
+
)
|
|
68
|
+
setIsLoading(false)
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
75
|
+
const name = e.target.value
|
|
76
|
+
setFormData((prev) => ({ ...prev, name }))
|
|
77
|
+
if (errors.name) {
|
|
78
|
+
setErrors({})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const handleCancel = () => {
|
|
83
|
+
setLocation("/dashboard")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="min-h-screen bg-white">
|
|
88
|
+
<Helmet>
|
|
89
|
+
<title>Create Organization - tscircuit</title>
|
|
90
|
+
<meta
|
|
91
|
+
name="description"
|
|
92
|
+
content="Create a new organization to collaborate with others and manage shared projects."
|
|
93
|
+
/>
|
|
94
|
+
</Helmet>
|
|
95
|
+
|
|
96
|
+
<Header />
|
|
97
|
+
|
|
98
|
+
<div className="flex items-center justify-center min-h-[calc(100vh-80px)] px-4 sm:px-6 lg:px-8">
|
|
99
|
+
<div className="w-full max-w-lg mx-auto">
|
|
100
|
+
<div className="text-center mb-8">
|
|
101
|
+
<h1 className="text-xl sm:text-2xl font-semibold text-gray-900 mb-2">
|
|
102
|
+
Set up your organization
|
|
103
|
+
</h1>
|
|
104
|
+
<p className="text-gray-600 text-sm">
|
|
105
|
+
Tell us about your organization
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
110
|
+
<div className="space-y-2">
|
|
111
|
+
<Label
|
|
112
|
+
htmlFor="org-name"
|
|
113
|
+
className="text-sm font-semibold text-gray-900"
|
|
114
|
+
>
|
|
115
|
+
Organization Name
|
|
116
|
+
<span className="text-red-500">*</span>
|
|
117
|
+
</Label>
|
|
118
|
+
<Input
|
|
119
|
+
spellCheck={false}
|
|
120
|
+
id="org-handle"
|
|
121
|
+
type="text"
|
|
122
|
+
placeholder="tscircuit"
|
|
123
|
+
value={formData.name}
|
|
124
|
+
onChange={handleNameChange}
|
|
125
|
+
className={`h-10 sm:h-11 ${
|
|
126
|
+
errors.name
|
|
127
|
+
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
|
128
|
+
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
|
129
|
+
}`}
|
|
130
|
+
disabled={isLoading || isMutating}
|
|
131
|
+
/>
|
|
132
|
+
{errors.name && (
|
|
133
|
+
<p className="text-sm text-red-600">{errors.name}</p>
|
|
134
|
+
)}
|
|
135
|
+
<p className="text-xs text-gray-500">
|
|
136
|
+
This will be the name of your organization on tscircuit.
|
|
137
|
+
<br />
|
|
138
|
+
Your URL will be:{" "}
|
|
139
|
+
<span className="font-mono text-gray-700">
|
|
140
|
+
tscircuit.com/{formData.name || "orgname"}
|
|
141
|
+
</span>
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div>
|
|
146
|
+
<Button
|
|
147
|
+
type="submit"
|
|
148
|
+
disabled={isLoading || isMutating || !formData.name}
|
|
149
|
+
className="w-full h-10 sm:h-11 bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm sm:text-base"
|
|
150
|
+
>
|
|
151
|
+
{isLoading || isMutating ? (
|
|
152
|
+
<>
|
|
153
|
+
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
154
|
+
Creating organization...
|
|
155
|
+
</>
|
|
156
|
+
) : (
|
|
157
|
+
"Create organization"
|
|
158
|
+
)}
|
|
159
|
+
</Button>
|
|
160
|
+
</div>
|
|
161
|
+
</form>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default CreateOrganizationPage
|
package/src/pages/dashboard.tsx
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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>
|
package/src/pages/datasheet.tsx
CHANGED
|
@@ -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-
|
|
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">
|
package/src/pages/datasheets.tsx
CHANGED
|
@@ -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-
|
|
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
|
-
<
|
|
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
|
package/src/pages/editor.tsx
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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://
|
|
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://
|
|
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
|
)}
|
package/src/pages/landing.tsx
CHANGED
|
@@ -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 {
|
|
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://
|
|
39
|
-
<link rel="dns-prefetch" href="https://
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
87
|
+
</Link>
|
|
88
88
|
<a
|
|
89
89
|
href="https://github.com/tscircuit/tscircuit"
|
|
90
90
|
target="_blank"
|