@tscircuit/fake-snippets 0.0.100 → 0.0.102
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/api/generated-index.js +23 -1
- package/bun.lock +2 -2
- package/dist/bundle.js +620 -412
- package/dist/index.d.ts +33 -4
- package/dist/index.js +43 -1
- package/dist/schema.d.ts +94 -1
- package/dist/schema.js +17 -1
- package/fake-snippets-api/lib/db/db-client.ts +38 -1
- package/fake-snippets-api/lib/db/schema.ts +15 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package.ts +2 -0
- package/fake-snippets-api/routes/api/accounts/search.ts +20 -0
- package/fake-snippets-api/routes/api/github/installations/create_new_installation_redirect.ts +75 -0
- package/fake-snippets-api/routes/api/github/repos/list_available.ts +91 -0
- package/fake-snippets-api/routes/api/packages/update.ts +4 -0
- package/package.json +2 -2
- package/src/App.tsx +10 -1
- package/src/components/CmdKMenu.tsx +154 -19
- package/src/components/CreateReleaseDialog.tsx +124 -0
- package/src/components/FileSidebar.tsx +128 -23
- package/src/components/Header2.tsx +106 -25
- package/src/components/PackageBuildsPage/package-build-header.tsx +28 -16
- package/src/components/PageSearchComponent.tsx +2 -2
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +2 -2
- package/src/components/TrendingPackagesCarousel.tsx +2 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +18 -13
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +1 -0
- package/src/components/dialogs/GitHubRepositorySelector.tsx +123 -0
- package/src/components/dialogs/create-use-dialog.tsx +8 -2
- package/src/components/dialogs/edit-package-details-dialog.tsx +22 -3
- package/src/components/dialogs/view-ts-files-dialog.tsx +178 -33
- package/src/components/package-port/CodeAndPreview.tsx +4 -1
- package/src/components/package-port/CodeEditor.tsx +42 -35
- package/src/components/package-port/CodeEditorHeader.tsx +26 -20
- package/src/components/package-port/EditorNav.tsx +94 -37
- package/src/components/preview/BuildsList.tsx +238 -0
- package/src/components/preview/ConnectedRepoDashboard.tsx +258 -0
- package/src/components/preview/ConnectedRepoOverview.tsx +454 -0
- package/src/components/preview/ConnectedRepoSettings.tsx +343 -0
- package/src/components/preview/ConnectedReposCards.tsx +191 -0
- package/src/components/preview/index.tsx +207 -0
- package/src/components/ui/tree-view.tsx +23 -6
- package/src/hooks/use-axios.ts +2 -2
- package/src/hooks/use-create-release-dialog.ts +160 -0
- package/src/hooks/use-package-details-form.ts +7 -0
- package/src/hooks/use-packages-base-api-url.ts +1 -1
- package/src/hooks/use-sign-in.ts +2 -2
- package/src/hooks/useFileManagement.ts +22 -2
- package/src/index.css +4 -0
- package/src/lib/utils/formatTimeAgo.ts +10 -0
- package/src/lib/utils/isValidFileName.ts +15 -3
- package/src/pages/dashboard.tsx +2 -2
- package/src/pages/dev-login.tsx +2 -2
- package/src/pages/landing.tsx +1 -1
- package/src/pages/latest.tsx +2 -2
- package/src/pages/preview-build.tsx +380 -0
- package/src/pages/search.tsx +2 -2
- package/src/pages/trending.tsx +2 -2
- package/src/pages/user-profile.tsx +32 -24
- package/src/pages/view-connected-repo.tsx +24 -0
|
@@ -57,16 +57,24 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
57
57
|
handleRenameFile,
|
|
58
58
|
isCreatingFile,
|
|
59
59
|
setIsCreatingFile,
|
|
60
|
-
pkg,
|
|
61
60
|
}) => {
|
|
62
61
|
const [sidebarOpen, setSidebarOpen] = fileSidebarState
|
|
63
62
|
const [newFileName, setNewFileName] = useState("")
|
|
64
63
|
const [errorMessage, setErrorMessage] = useState("")
|
|
65
64
|
const [renamingFile, setRenamingFile] = useState<string | null>(null)
|
|
65
|
+
const [selectedFolderForCreation, setSelectedFolderForCreation] = useState<
|
|
66
|
+
string | null
|
|
67
|
+
>(null)
|
|
68
|
+
const [selectedItemId, setSelectedItemId] = React.useState<string>(
|
|
69
|
+
currentFile || "",
|
|
70
|
+
)
|
|
66
71
|
const { toast } = useToast()
|
|
67
|
-
const session = useGlobalStore((s) => s.session)
|
|
68
72
|
const canModifyFiles = true
|
|
69
73
|
|
|
74
|
+
const onFolderSelect = (folderPath: string) => {
|
|
75
|
+
setSelectedFolderForCreation(folderPath)
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
const transformFilesToTreeData = (
|
|
71
79
|
files: Record<FileName, string>,
|
|
72
80
|
): TreeDataItem[] => {
|
|
@@ -90,6 +98,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
90
98
|
: segment
|
|
91
99
|
const absolutePath = hasLeadingSlash ? `/${relativePath}` : relativePath
|
|
92
100
|
const itemId = absolutePath
|
|
101
|
+
|
|
93
102
|
if (
|
|
94
103
|
!currentNode[segment] &&
|
|
95
104
|
(!isHiddenFile(relativePath) ||
|
|
@@ -104,21 +113,17 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
104
113
|
name: segment,
|
|
105
114
|
isRenaming: renamingFile === itemId,
|
|
106
115
|
onRename: (newFilename: string) => {
|
|
107
|
-
// Preserve the folder structure when renaming
|
|
108
116
|
const oldPath = itemId
|
|
109
|
-
const pathParts = oldPath.split("/").filter((part) => part !== "")
|
|
117
|
+
const pathParts = oldPath.split("/").filter((part) => part !== "")
|
|
110
118
|
let newPath: string
|
|
111
119
|
|
|
112
120
|
if (pathParts.length > 1) {
|
|
113
|
-
// File is in a folder, preserve the folder structure
|
|
114
121
|
const folderPath = pathParts.slice(0, -1).join("/")
|
|
115
122
|
newPath = folderPath + "/" + newFilename
|
|
116
123
|
} else {
|
|
117
|
-
// File is in root, just use the new filename
|
|
118
124
|
newPath = newFilename
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
// Preserve leading slash if original path had one
|
|
122
127
|
if (oldPath.startsWith("/") && !newPath.startsWith("/")) {
|
|
123
128
|
newPath = "/" + newPath
|
|
124
129
|
}
|
|
@@ -142,7 +147,12 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
142
147
|
setRenamingFile(null)
|
|
143
148
|
},
|
|
144
149
|
icon: isLeafNode ? File : Folder,
|
|
145
|
-
onClick: isLeafNode
|
|
150
|
+
onClick: isLeafNode
|
|
151
|
+
? () => {
|
|
152
|
+
onFileSelect(absolutePath)
|
|
153
|
+
setSelectedFolderForCreation(null)
|
|
154
|
+
}
|
|
155
|
+
: () => onFolderSelect(absolutePath),
|
|
146
156
|
draggable: false,
|
|
147
157
|
droppable: !isLeafNode,
|
|
148
158
|
children: isLeafNode ? undefined : {},
|
|
@@ -209,7 +219,6 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
209
219
|
})
|
|
210
220
|
})
|
|
211
221
|
|
|
212
|
-
// Convert the nested object structure to array structure
|
|
213
222
|
const convertToArray = (
|
|
214
223
|
items: Record<string, TreeNode>,
|
|
215
224
|
): TreeDataItem[] => {
|
|
@@ -226,17 +235,70 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
226
235
|
|
|
227
236
|
const treeData = transformFilesToTreeData(files)
|
|
228
237
|
|
|
238
|
+
const getCurrentFolderPath = (): string => {
|
|
239
|
+
if (selectedFolderForCreation) {
|
|
240
|
+
return selectedFolderForCreation
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!selectedItemId || selectedItemId === "") return ""
|
|
244
|
+
|
|
245
|
+
const hasLeadingSlash = selectedItemId.startsWith("/")
|
|
246
|
+
const normalizedPath = hasLeadingSlash
|
|
247
|
+
? selectedItemId.slice(1)
|
|
248
|
+
: selectedItemId
|
|
249
|
+
const pathParts = selectedItemId.split("/")
|
|
250
|
+
|
|
251
|
+
if (pathParts.length > 1) {
|
|
252
|
+
const folderPath = pathParts.slice(0, -1).join("/")
|
|
253
|
+
return hasLeadingSlash ? `/${folderPath}` : folderPath
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return hasLeadingSlash ? "/" : ""
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const constructFilePath = (fileName: string): string => {
|
|
260
|
+
const trimmedFileName = fileName.trim()
|
|
261
|
+
|
|
262
|
+
if (!trimmedFileName) {
|
|
263
|
+
return ""
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const currentFolder = getCurrentFolderPath()
|
|
267
|
+
|
|
268
|
+
if (trimmedFileName.startsWith("/")) {
|
|
269
|
+
return trimmedFileName
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!currentFolder || currentFolder === "/") {
|
|
273
|
+
const result =
|
|
274
|
+
currentFolder === "/" ? `/${trimmedFileName}` : trimmedFileName
|
|
275
|
+
return result
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const result = `${currentFolder}/${trimmedFileName}`
|
|
279
|
+
return result
|
|
280
|
+
}
|
|
229
281
|
const handleCreateFileInline = () => {
|
|
282
|
+
const finalFileName = constructFilePath(newFileName)
|
|
283
|
+
if (!finalFileName) {
|
|
284
|
+
setErrorMessage("File name cannot be empty")
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
230
288
|
const { newFileCreated } = handleCreateFile({
|
|
231
|
-
newFileName,
|
|
289
|
+
newFileName: finalFileName,
|
|
232
290
|
onError: (error) => {
|
|
233
291
|
setErrorMessage(error.message)
|
|
234
292
|
},
|
|
235
293
|
})
|
|
294
|
+
|
|
236
295
|
if (newFileCreated) {
|
|
237
296
|
setIsCreatingFile(false)
|
|
238
297
|
setNewFileName("")
|
|
239
298
|
setErrorMessage("")
|
|
299
|
+
onFileSelect(finalFileName)
|
|
300
|
+
setSelectedItemId(finalFileName)
|
|
301
|
+
setSelectedFolderForCreation(null)
|
|
240
302
|
}
|
|
241
303
|
}
|
|
242
304
|
|
|
@@ -245,6 +307,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
245
307
|
setIsCreatingFile(false)
|
|
246
308
|
setNewFileName("")
|
|
247
309
|
setErrorMessage("")
|
|
310
|
+
setSelectedFolderForCreation(null)
|
|
248
311
|
return
|
|
249
312
|
}
|
|
250
313
|
handleCreateFileInline()
|
|
@@ -255,6 +318,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
255
318
|
setErrorMessage("")
|
|
256
319
|
setIsCreatingFile(false)
|
|
257
320
|
setNewFileName("")
|
|
321
|
+
setSelectedFolderForCreation(null)
|
|
258
322
|
}
|
|
259
323
|
|
|
260
324
|
return (
|
|
@@ -267,9 +331,7 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
267
331
|
>
|
|
268
332
|
<button
|
|
269
333
|
onClick={toggleSidebar}
|
|
270
|
-
className={`z-[99] mt-2 ml-2 text-gray-400 scale-90 transition-opacity duration-200 ${
|
|
271
|
-
!sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"
|
|
272
|
-
}`}
|
|
334
|
+
className={`z-[99] mt-2 ml-2 text-gray-400 scale-90 transition-opacity duration-200 ${!sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"}`}
|
|
273
335
|
>
|
|
274
336
|
<PanelRightOpen />
|
|
275
337
|
</button>
|
|
@@ -286,33 +348,76 @@ const FileSidebar: React.FC<FileSidebarProps> = ({
|
|
|
286
348
|
autoFocus
|
|
287
349
|
value={newFileName}
|
|
288
350
|
spellCheck={false}
|
|
289
|
-
onChange={(e) =>
|
|
351
|
+
onChange={(e) => {
|
|
352
|
+
setNewFileName(e.target.value)
|
|
353
|
+
if (errorMessage) {
|
|
354
|
+
setErrorMessage("")
|
|
355
|
+
}
|
|
356
|
+
}}
|
|
290
357
|
onBlur={handleCreateFileBlur}
|
|
291
358
|
onKeyDown={(e) => {
|
|
292
359
|
if (e.key === "Enter") {
|
|
360
|
+
e.preventDefault()
|
|
293
361
|
handleCreateFileInline()
|
|
294
362
|
} else if (e.key === "Escape") {
|
|
363
|
+
e.preventDefault()
|
|
295
364
|
setIsCreatingFile(false)
|
|
296
365
|
setNewFileName("")
|
|
297
366
|
setErrorMessage("")
|
|
367
|
+
setSelectedFolderForCreation(null)
|
|
368
|
+
} else if (e.key === "Tab") {
|
|
369
|
+
e.preventDefault()
|
|
370
|
+
const currentFolder = getCurrentFolderPath()
|
|
371
|
+
if (currentFolder && !newFileName.includes("/")) {
|
|
372
|
+
const displayPath = currentFolder.startsWith("/")
|
|
373
|
+
? currentFolder.slice(1)
|
|
374
|
+
: currentFolder
|
|
375
|
+
setNewFileName(`${displayPath}/`)
|
|
376
|
+
}
|
|
298
377
|
}
|
|
299
378
|
}}
|
|
300
|
-
placeholder=
|
|
379
|
+
placeholder={(() => {
|
|
380
|
+
const currentFolder = getCurrentFolderPath()
|
|
381
|
+
if (!currentFolder || currentFolder === "/") {
|
|
382
|
+
return "Enter file name (root folder)"
|
|
383
|
+
}
|
|
384
|
+
const displayPath = currentFolder.startsWith("/")
|
|
385
|
+
? currentFolder.slice(1)
|
|
386
|
+
: currentFolder
|
|
387
|
+
return `Enter file name (${displayPath}/)`
|
|
388
|
+
})()}
|
|
389
|
+
className={
|
|
390
|
+
errorMessage ? "border-red-500 focus:border-red-500" : ""
|
|
391
|
+
}
|
|
301
392
|
/>
|
|
302
393
|
{errorMessage && (
|
|
303
|
-
<div className="text-red-500 mt-1">{errorMessage}</div>
|
|
394
|
+
<div className="text-red-500 text-xs mt-1 px-1">{errorMessage}</div>
|
|
304
395
|
)}
|
|
396
|
+
<div className="text-gray-400 text-xs mt-1 px-1">
|
|
397
|
+
Tip: Use / for subfolders, Tab to auto-complete current folder
|
|
398
|
+
</div>
|
|
305
399
|
</div>
|
|
306
400
|
)}
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
item.onClick()
|
|
401
|
+
<div
|
|
402
|
+
onClick={(e) => {
|
|
403
|
+
if (e.target === e.currentTarget) {
|
|
404
|
+
setSelectedFolderForCreation(null)
|
|
405
|
+
setSelectedItemId("")
|
|
313
406
|
}
|
|
314
407
|
}}
|
|
315
|
-
|
|
408
|
+
className="flex-1 border-2 h-full"
|
|
409
|
+
>
|
|
410
|
+
<TreeView
|
|
411
|
+
data={treeData}
|
|
412
|
+
setSelectedItemId={(value) => setSelectedItemId(value || "")}
|
|
413
|
+
selectedItemId={selectedItemId}
|
|
414
|
+
onSelectChange={(item) => {
|
|
415
|
+
if (item?.onClick) {
|
|
416
|
+
item.onClick()
|
|
417
|
+
}
|
|
418
|
+
}}
|
|
419
|
+
/>
|
|
420
|
+
</div>
|
|
316
421
|
</div>
|
|
317
422
|
)
|
|
318
423
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Button } from "@/components/ui/button"
|
|
2
|
-
import { CircuitBoard, Search } from "lucide-react"
|
|
2
|
+
import { CircuitBoard, Search, Menu, X } from "lucide-react"
|
|
3
3
|
import { Link } from "wouter"
|
|
4
4
|
import { PrefetchPageLink } from "./PrefetchPageLink"
|
|
5
5
|
import { HeaderLogin } from "./HeaderLogin"
|
|
@@ -53,10 +53,12 @@ const SearchButtonComponent = () => {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export const Header2 = () => {
|
|
56
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
56
57
|
const isLoggedIn = useGlobalStore((state) => Boolean(state.session))
|
|
58
|
+
|
|
57
59
|
return (
|
|
58
60
|
<>
|
|
59
|
-
<header className="sticky top-0 z-50 w-full border-b bg-
|
|
61
|
+
<header className="sticky top-0 z-50 w-full border-b bg-white md:bg-white/80 md:backdrop-blur supports-[backdrop-filter]:md:bg-white/60">
|
|
60
62
|
<div className="container mx-auto flex h-16 items-center justify-between px-2 md:px-6">
|
|
61
63
|
<PrefetchPageLink
|
|
62
64
|
href="/"
|
|
@@ -65,23 +67,17 @@ export const Header2 = () => {
|
|
|
65
67
|
<CircuitBoard className="h-6 w-6" />
|
|
66
68
|
<span className="text-lg font-bold">tscircuit</span>
|
|
67
69
|
</PrefetchPageLink>
|
|
68
|
-
|
|
70
|
+
|
|
71
|
+
{/* Desktop Navigation */}
|
|
72
|
+
<nav className="hidden md:flex gap-6">
|
|
69
73
|
{isLoggedIn && (
|
|
70
|
-
<
|
|
74
|
+
<PrefetchPageLink
|
|
71
75
|
className="text-sm font-medium hover:underline underline-offset-4"
|
|
72
76
|
href="/dashboard"
|
|
73
77
|
>
|
|
74
78
|
Dashboard
|
|
75
|
-
</
|
|
79
|
+
</PrefetchPageLink>
|
|
76
80
|
)}
|
|
77
|
-
</nav>
|
|
78
|
-
<nav className="hidden md:flex gap-6">
|
|
79
|
-
<PrefetchPageLink
|
|
80
|
-
className="text-sm font-medium hover:underline underline-offset-4"
|
|
81
|
-
href="/dashboard"
|
|
82
|
-
>
|
|
83
|
-
Dashboard
|
|
84
|
-
</PrefetchPageLink>
|
|
85
81
|
<PrefetchPageLink
|
|
86
82
|
className="text-sm font-medium hover:underline underline-offset-4"
|
|
87
83
|
href="/quickstart"
|
|
@@ -94,12 +90,6 @@ export const Header2 = () => {
|
|
|
94
90
|
>
|
|
95
91
|
Datasheets
|
|
96
92
|
</PrefetchPageLink>
|
|
97
|
-
{/* <a
|
|
98
|
-
className="text-sm font-medium hover:underline underline-offset-4"
|
|
99
|
-
href="https://github.com/tscircuit/tscircuit"
|
|
100
|
-
>
|
|
101
|
-
Github
|
|
102
|
-
</a> */}
|
|
103
93
|
<a
|
|
104
94
|
className="text-sm font-medium hover:underline underline-offset-4"
|
|
105
95
|
href="https://docs.tscircuit.com"
|
|
@@ -119,16 +109,107 @@ export const Header2 = () => {
|
|
|
119
109
|
Contact
|
|
120
110
|
</a>
|
|
121
111
|
</nav>
|
|
122
|
-
|
|
112
|
+
|
|
113
|
+
{/* Desktop Right Side */}
|
|
114
|
+
<div className="hidden md:flex items-center gap-4">
|
|
123
115
|
<SearchButtonComponent />
|
|
124
|
-
{isLoggedIn &&
|
|
125
|
-
<div className="hidden sm:block">
|
|
126
|
-
<HeaderDropdown />
|
|
127
|
-
</div>
|
|
128
|
-
)}
|
|
116
|
+
{isLoggedIn && <HeaderDropdown />}
|
|
129
117
|
<HeaderLogin />
|
|
130
118
|
</div>
|
|
119
|
+
|
|
120
|
+
{/* Mobile Right Side */}
|
|
121
|
+
<div className="flex md:hidden items-center gap-2">
|
|
122
|
+
<Button
|
|
123
|
+
variant="ghost"
|
|
124
|
+
size="icon"
|
|
125
|
+
onClick={() => (window.location.href = "/search")}
|
|
126
|
+
className="h-8 w-8"
|
|
127
|
+
aria-label="Go to search"
|
|
128
|
+
>
|
|
129
|
+
<Search className="size-4" />
|
|
130
|
+
</Button>
|
|
131
|
+
{isLoggedIn ? (
|
|
132
|
+
<HeaderLogin />
|
|
133
|
+
) : (
|
|
134
|
+
<PrefetchPageLink href="/quickstart">
|
|
135
|
+
<Button size="sm" className="text-xs px-3 py-1">
|
|
136
|
+
Get Started
|
|
137
|
+
</Button>
|
|
138
|
+
</PrefetchPageLink>
|
|
139
|
+
)}
|
|
140
|
+
<button
|
|
141
|
+
className="p-2"
|
|
142
|
+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
143
|
+
aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
|
|
144
|
+
>
|
|
145
|
+
{mobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
131
148
|
</div>
|
|
149
|
+
|
|
150
|
+
{/* Mobile Menu */}
|
|
151
|
+
{mobileMenuOpen && (
|
|
152
|
+
<div className="md:hidden border-t bg-white ">
|
|
153
|
+
<div className="container mx-auto px-4 py-3">
|
|
154
|
+
<nav className="mb-4">
|
|
155
|
+
<div className="flex flex-col items-center gap-1">
|
|
156
|
+
{isLoggedIn && (
|
|
157
|
+
<PrefetchPageLink
|
|
158
|
+
className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
|
|
159
|
+
href="/dashboard"
|
|
160
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
161
|
+
>
|
|
162
|
+
Dashboard
|
|
163
|
+
</PrefetchPageLink>
|
|
164
|
+
)}
|
|
165
|
+
<PrefetchPageLink
|
|
166
|
+
className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
|
|
167
|
+
href="/quickstart"
|
|
168
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
169
|
+
>
|
|
170
|
+
Editor
|
|
171
|
+
</PrefetchPageLink>
|
|
172
|
+
<PrefetchPageLink
|
|
173
|
+
className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
|
|
174
|
+
href="/datasheets"
|
|
175
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
176
|
+
>
|
|
177
|
+
Datasheets
|
|
178
|
+
</PrefetchPageLink>
|
|
179
|
+
<a
|
|
180
|
+
className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
|
|
181
|
+
href="https://docs.tscircuit.com"
|
|
182
|
+
>
|
|
183
|
+
Docs
|
|
184
|
+
</a>
|
|
185
|
+
<a
|
|
186
|
+
className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
|
|
187
|
+
href="https://tscircuit.com/join"
|
|
188
|
+
>
|
|
189
|
+
Discord
|
|
190
|
+
</a>
|
|
191
|
+
<a
|
|
192
|
+
className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
|
|
193
|
+
href="mailto:hello@tscircuit.com"
|
|
194
|
+
>
|
|
195
|
+
Contact
|
|
196
|
+
</a>
|
|
197
|
+
</div>
|
|
198
|
+
</nav>
|
|
199
|
+
<div className="flex flex-col items-center gap-4 pt-4 border-t border-gray-200">
|
|
200
|
+
{isLoggedIn ? (
|
|
201
|
+
<div className="w-full flex justify-center">
|
|
202
|
+
<HeaderDropdown />
|
|
203
|
+
</div>
|
|
204
|
+
) : (
|
|
205
|
+
<div className="w-full flex justify-center">
|
|
206
|
+
<HeaderLogin />
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
132
213
|
</header>
|
|
133
214
|
<CmdKMenu />
|
|
134
215
|
<Analytics />
|
|
@@ -4,15 +4,20 @@ import { useRebuildPackageReleaseMutation } from "@/hooks/use-rebuild-package-re
|
|
|
4
4
|
import { Github, RefreshCw } from "lucide-react"
|
|
5
5
|
import { useParams } from "wouter"
|
|
6
6
|
import { DownloadButtonAndMenu } from "../DownloadButtonAndMenu"
|
|
7
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
8
|
+
import { useCurrentPackageCircuitJson } from "../ViewPackagePage/hooks/use-current-package-circuit-json"
|
|
7
9
|
|
|
8
10
|
export function PackageBuildHeader() {
|
|
9
11
|
const { author, packageName } = useParams()
|
|
12
|
+
const session = useGlobalStore((s) => s.session)
|
|
10
13
|
const { packageRelease } = useCurrentPackageRelease({
|
|
11
14
|
include_logs: true,
|
|
12
15
|
})
|
|
13
16
|
const { mutate: rebuildPackage, isLoading } =
|
|
14
17
|
useRebuildPackageReleaseMutation()
|
|
15
18
|
|
|
19
|
+
const { circuitJson } = useCurrentPackageCircuitJson()
|
|
20
|
+
|
|
16
21
|
return (
|
|
17
22
|
<div className="border-b border-gray-200 bg-white px-4 sm:px-6 py-4">
|
|
18
23
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4 container mx-auto max-w-7xl">
|
|
@@ -44,22 +49,29 @@ export function PackageBuildHeader() {
|
|
|
44
49
|
<span className="xs:hidden">Report</span>
|
|
45
50
|
</a>
|
|
46
51
|
</Button>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
{session?.github_username == author && (
|
|
53
|
+
<Button
|
|
54
|
+
variant="outline"
|
|
55
|
+
size="sm"
|
|
56
|
+
className="border-gray-300 bg-white hover:bg-gray-50 text-xs sm:text-sm"
|
|
57
|
+
disabled={isLoading || !packageRelease}
|
|
58
|
+
onClick={() =>
|
|
59
|
+
packageRelease &&
|
|
60
|
+
rebuildPackage({
|
|
61
|
+
package_release_id: packageRelease.package_release_id,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
<RefreshCw className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
|
|
66
|
+
{isLoading ? "Rebuilding..." : "Rebuild"}
|
|
67
|
+
</Button>
|
|
68
|
+
)}
|
|
69
|
+
<DownloadButtonAndMenu
|
|
70
|
+
offerMultipleImageFormats
|
|
71
|
+
circuitJson={circuitJson}
|
|
72
|
+
unscopedName={packageName}
|
|
73
|
+
author={author}
|
|
74
|
+
/>
|
|
63
75
|
</div>
|
|
64
76
|
</div>
|
|
65
77
|
</div>
|
|
@@ -3,7 +3,7 @@ import { useAxios } from "@/hooks/use-axios"
|
|
|
3
3
|
import { useLocation } from "wouter"
|
|
4
4
|
import React, { useState } from "react"
|
|
5
5
|
import { useQuery } from "react-query"
|
|
6
|
-
import {
|
|
6
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
7
7
|
import { Search } from "lucide-react"
|
|
8
8
|
import { Button } from "./ui/button"
|
|
9
9
|
import { PackageCardSkeleton } from "./PackageCardSkeleton"
|
|
@@ -20,7 +20,7 @@ const PageSearchComponent: React.FC<PageSearchComponentProps> = ({
|
|
|
20
20
|
}) => {
|
|
21
21
|
const [location, setLocation] = useLocation()
|
|
22
22
|
const axios = useAxios()
|
|
23
|
-
const snippetsBaseApiUrl =
|
|
23
|
+
const snippetsBaseApiUrl = useApiBaseUrl()
|
|
24
24
|
|
|
25
25
|
// Initialize search query directly from URL
|
|
26
26
|
const [searchQuery, setSearchQuery] = useState(
|
|
@@ -4,7 +4,7 @@ import { useLocation } from "wouter"
|
|
|
4
4
|
import React, { useEffect, useRef, useState } from "react"
|
|
5
5
|
import { useQuery } from "react-query"
|
|
6
6
|
import { Alert } from "./ui/alert"
|
|
7
|
-
import {
|
|
7
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
8
8
|
import { PrefetchPageLink } from "./PrefetchPageLink"
|
|
9
9
|
import { CircuitBoard } from "lucide-react"
|
|
10
10
|
import { cn } from "@/lib/utils"
|
|
@@ -60,7 +60,7 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
|
|
|
60
60
|
const resultsRef = useRef<HTMLDivElement>(null)
|
|
61
61
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
62
62
|
const [location, setLocation] = useLocation()
|
|
63
|
-
const snippetsBaseApiUrl =
|
|
63
|
+
const snippetsBaseApiUrl = useApiBaseUrl()
|
|
64
64
|
|
|
65
65
|
const { data: searchResults, isLoading } = useQuery(
|
|
66
66
|
["packageSearch", searchQuery],
|
|
@@ -6,7 +6,7 @@ const RunFrame = lazy(async () => {
|
|
|
6
6
|
})
|
|
7
7
|
|
|
8
8
|
export const SuspenseRunFrame = (
|
|
9
|
-
props: React.ComponentProps<typeof RunFrame
|
|
9
|
+
props: React.ComponentProps<typeof RunFrame> & { className?: string },
|
|
10
10
|
) => {
|
|
11
11
|
return (
|
|
12
12
|
<Suspense
|
|
@@ -20,7 +20,7 @@ export const SuspenseRunFrame = (
|
|
|
20
20
|
</div>
|
|
21
21
|
}
|
|
22
22
|
>
|
|
23
|
-
<div className=
|
|
23
|
+
<div className={`h-[98vh] ${props.className}`}>
|
|
24
24
|
<RunFrame {...props} />
|
|
25
25
|
</div>
|
|
26
26
|
</Suspense>
|
|
@@ -4,7 +4,7 @@ import { StarFilledIcon } from "@radix-ui/react-icons"
|
|
|
4
4
|
import { Link } from "wouter"
|
|
5
5
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
6
6
|
import { useRef, useState } from "react"
|
|
7
|
-
import {
|
|
7
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
8
8
|
|
|
9
9
|
const CarouselItem = ({
|
|
10
10
|
pkg,
|
|
@@ -36,7 +36,7 @@ export const TrendingPackagesCarousel = () => {
|
|
|
36
36
|
const axios = useAxios()
|
|
37
37
|
const scrollRef = useRef<HTMLDivElement>(null)
|
|
38
38
|
const [isHovered, setIsHovered] = useState(false)
|
|
39
|
-
const apiBaseUrl =
|
|
39
|
+
const apiBaseUrl = useApiBaseUrl()
|
|
40
40
|
|
|
41
41
|
const { data: trendingPackages } = useQuery<Package[]>(
|
|
42
42
|
"trendingPackages",
|
|
@@ -17,6 +17,7 @@ import { usePackageFile } from "@/hooks/use-package-files"
|
|
|
17
17
|
import { ShikiCodeViewer } from "./ShikiCodeViewer"
|
|
18
18
|
import MarkdownViewer from "./markdown-viewer"
|
|
19
19
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
20
|
+
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
20
21
|
|
|
21
22
|
interface PackageFile {
|
|
22
23
|
package_file_id: string
|
|
@@ -129,14 +130,12 @@ export default function ImportantFilesView({
|
|
|
129
130
|
const availableTabs = useMemo((): TabInfo[] => {
|
|
130
131
|
const tabs: TabInfo[] = []
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
})
|
|
139
|
-
}
|
|
133
|
+
tabs.push({
|
|
134
|
+
type: "ai",
|
|
135
|
+
filePath: null,
|
|
136
|
+
label: "Description",
|
|
137
|
+
icon: <SparklesIcon className="h-3.5 w-3.5 mr-1.5" />,
|
|
138
|
+
})
|
|
140
139
|
|
|
141
140
|
tabs.push({
|
|
142
141
|
type: "ai-review",
|
|
@@ -266,6 +265,8 @@ export default function ImportantFilesView({
|
|
|
266
265
|
}
|
|
267
266
|
}, [activeTab, importantFiles, getDefaultTab])
|
|
268
267
|
|
|
268
|
+
const { circuitJson } = useCurrentPackageCircuitJson()
|
|
269
|
+
|
|
269
270
|
// Get active file content
|
|
270
271
|
const partialActiveFile = useMemo(() => {
|
|
271
272
|
if (activeTab?.type !== "file" || !activeTab.filePath) return null
|
|
@@ -321,7 +322,15 @@ export default function ImportantFilesView({
|
|
|
321
322
|
from our AI assistant.
|
|
322
323
|
</p>
|
|
323
324
|
</div>
|
|
324
|
-
{isOwner ? (
|
|
325
|
+
{!isOwner ? (
|
|
326
|
+
<p className="text-sm text-gray-500">
|
|
327
|
+
Only the package owner can generate an AI review
|
|
328
|
+
</p>
|
|
329
|
+
) : !Boolean(circuitJson) ? (
|
|
330
|
+
<p className="text-sm text-gray-500">
|
|
331
|
+
Circuit JSON is required for AI review.
|
|
332
|
+
</p>
|
|
333
|
+
) : (
|
|
325
334
|
<Button
|
|
326
335
|
onClick={onRequestAiReview}
|
|
327
336
|
size="sm"
|
|
@@ -330,10 +339,6 @@ export default function ImportantFilesView({
|
|
|
330
339
|
<SparklesIcon className="h-4 w-4 mr-2" />
|
|
331
340
|
Request AI Review
|
|
332
341
|
</Button>
|
|
333
|
-
) : (
|
|
334
|
-
<p className="text-sm text-gray-500">
|
|
335
|
-
Only the package owner can generate an AI review
|
|
336
|
-
</p>
|
|
337
342
|
)}
|
|
338
343
|
</div>
|
|
339
344
|
</div>
|
|
@@ -205,6 +205,7 @@ const MobileSidebar = ({
|
|
|
205
205
|
currentDescription={
|
|
206
206
|
packageInfo.description || packageInfo?.ai_description || ""
|
|
207
207
|
}
|
|
208
|
+
currentGithubRepoFullName={packageInfo.github_repo_full_name}
|
|
208
209
|
currentLicense={currentLicense}
|
|
209
210
|
currentWebsite={(packageInfo as any)?.website || ""}
|
|
210
211
|
isPrivate={Boolean(packageInfo.is_private)}
|
|
@@ -171,6 +171,7 @@ export default function SidebarAboutSection({
|
|
|
171
171
|
unscopedPackageName={packageInfo.unscoped_name}
|
|
172
172
|
packageReleaseId={packageInfo.latest_package_release_id}
|
|
173
173
|
packageId={packageInfo.package_id}
|
|
174
|
+
currentGithubRepoFullName={packageInfo.github_repo_full_name}
|
|
174
175
|
currentDescription={
|
|
175
176
|
packageInfo.description || packageInfo?.ai_description || ""
|
|
176
177
|
}
|