@tscircuit/fake-snippets 0.0.101 → 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 +530 -367
- package/dist/index.d.ts +29 -2
- package/dist/index.js +18 -1
- package/dist/schema.d.ts +94 -1
- package/dist/schema.js +17 -1
- package/fake-snippets-api/lib/db/db-client.ts +6 -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/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/CreateReleaseDialog.tsx +124 -0
- package/src/components/FileSidebar.tsx +128 -23
- package/src/components/PackageBuildsPage/package-build-header.tsx +9 -1
- 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/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 +6 -4
- 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/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
package/src/App.tsx
CHANGED
|
@@ -79,6 +79,10 @@ const PackageEditorPage = lazyImport(async () => {
|
|
|
79
79
|
])
|
|
80
80
|
return editorModule
|
|
81
81
|
})
|
|
82
|
+
const ViewConnectedRepoPage = lazyImport(
|
|
83
|
+
() => import("@/pages/view-connected-repo"),
|
|
84
|
+
)
|
|
85
|
+
const PreviewBuildPage = lazyImport(() => import("@/pages/preview-build"))
|
|
82
86
|
|
|
83
87
|
class ErrorBoundary extends React.Component<
|
|
84
88
|
{ children: React.ReactNode },
|
|
@@ -139,7 +143,7 @@ class ErrorBoundary extends React.Component<
|
|
|
139
143
|
this.cleanup() // Clean up listeners before reload
|
|
140
144
|
this.setState({ reloading: true })
|
|
141
145
|
this.reloadTimeout = window.setTimeout(() => {
|
|
142
|
-
window.location.reload()
|
|
146
|
+
// window.location.reload()
|
|
143
147
|
}, 500)
|
|
144
148
|
}
|
|
145
149
|
|
|
@@ -253,6 +257,11 @@ function App() {
|
|
|
253
257
|
<Route path="/my-orders" component={MyOrdersPage} />
|
|
254
258
|
<Route path="/dev-login" component={DevLoginPage} />
|
|
255
259
|
<Route path="/:username" component={UserProfilePage} />
|
|
260
|
+
<Route path="/build/:buildId" component={ViewConnectedRepoPage} />
|
|
261
|
+
<Route
|
|
262
|
+
path="/build/:buildId/preview"
|
|
263
|
+
component={PreviewBuildPage}
|
|
264
|
+
/>
|
|
256
265
|
<Route path="/:author/:packageName" component={ViewPackagePage} />
|
|
257
266
|
<Route
|
|
258
267
|
path="/:author/:packageName/builds"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogContent,
|
|
5
|
+
DialogDescription,
|
|
6
|
+
DialogFooter,
|
|
7
|
+
DialogHeader,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
} from "@/components/ui/dialog"
|
|
10
|
+
import { Button } from "@/components/ui/button"
|
|
11
|
+
import { Input } from "@/components/ui/input"
|
|
12
|
+
import { Label } from "@/components/ui/label"
|
|
13
|
+
import { Alert, AlertDescription } from "@/components/ui/alert"
|
|
14
|
+
import { Loader2, Tag, AlertCircle } from "lucide-react"
|
|
15
|
+
|
|
16
|
+
interface CreateReleaseDialogProps {
|
|
17
|
+
isOpen: boolean
|
|
18
|
+
onClose: () => void
|
|
19
|
+
version: string
|
|
20
|
+
setVersion: (version: string) => void
|
|
21
|
+
currentVersion?: string
|
|
22
|
+
isLoading: boolean
|
|
23
|
+
error: string | null
|
|
24
|
+
onCreateRelease: () => Promise<void>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function CreateReleaseDialog({
|
|
28
|
+
isOpen,
|
|
29
|
+
onClose,
|
|
30
|
+
version,
|
|
31
|
+
setVersion,
|
|
32
|
+
currentVersion,
|
|
33
|
+
isLoading,
|
|
34
|
+
error,
|
|
35
|
+
onCreateRelease,
|
|
36
|
+
}: CreateReleaseDialogProps) {
|
|
37
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
38
|
+
e.preventDefault()
|
|
39
|
+
await onCreateRelease()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handleClose = () => {
|
|
43
|
+
onClose()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Dialog open={isOpen} onOpenChange={handleClose}>
|
|
48
|
+
<DialogContent className="sm:max-w-[425px]">
|
|
49
|
+
<DialogHeader>
|
|
50
|
+
<DialogTitle className="flex items-center gap-2">
|
|
51
|
+
<Tag className="w-5 h-5" />
|
|
52
|
+
Create New Release
|
|
53
|
+
</DialogTitle>
|
|
54
|
+
<DialogDescription>
|
|
55
|
+
Create a new release. This will make your latest changes available
|
|
56
|
+
to users.
|
|
57
|
+
</DialogDescription>
|
|
58
|
+
</DialogHeader>
|
|
59
|
+
|
|
60
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
61
|
+
<div className="space-y-3">
|
|
62
|
+
<div className="flex justify-center">
|
|
63
|
+
<p className="text-sm text-muted-foreground text-center">
|
|
64
|
+
{currentVersion ? (
|
|
65
|
+
<span className="flex items-center justify-center gap-2">
|
|
66
|
+
<span>Current version:</span>
|
|
67
|
+
<span className="font-mono font-medium text-foreground bg-muted px-2 py-1 rounded-md text-xs border">
|
|
68
|
+
{currentVersion}
|
|
69
|
+
</span>
|
|
70
|
+
</span>
|
|
71
|
+
) : (
|
|
72
|
+
"Follow semantic versioning (e.g., 1.0.0)"
|
|
73
|
+
)}
|
|
74
|
+
</p>
|
|
75
|
+
</div>{" "}
|
|
76
|
+
<Label htmlFor="version" className="text-sm font-medium">
|
|
77
|
+
Version
|
|
78
|
+
</Label>
|
|
79
|
+
<Input
|
|
80
|
+
id="version"
|
|
81
|
+
placeholder="e.g., 1.0.0"
|
|
82
|
+
value={version}
|
|
83
|
+
onChange={(e) => setVersion(e.target.value)}
|
|
84
|
+
disabled={isLoading}
|
|
85
|
+
className="h-10"
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{error && (
|
|
90
|
+
<Alert variant="destructive">
|
|
91
|
+
<AlertCircle className="h-4 w-4" />
|
|
92
|
+
<AlertDescription>{error}</AlertDescription>
|
|
93
|
+
</Alert>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
<DialogFooter>
|
|
97
|
+
<Button
|
|
98
|
+
type="button"
|
|
99
|
+
variant="outline"
|
|
100
|
+
onClick={handleClose}
|
|
101
|
+
disabled={isLoading}
|
|
102
|
+
>
|
|
103
|
+
Cancel
|
|
104
|
+
</Button>
|
|
105
|
+
<Button
|
|
106
|
+
type="submit"
|
|
107
|
+
disabled={isLoading || !version.trim()}
|
|
108
|
+
className="min-w-[100px]"
|
|
109
|
+
>
|
|
110
|
+
{isLoading ? (
|
|
111
|
+
<>
|
|
112
|
+
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
113
|
+
Creating...
|
|
114
|
+
</>
|
|
115
|
+
) : (
|
|
116
|
+
"Create Release"
|
|
117
|
+
)}
|
|
118
|
+
</Button>
|
|
119
|
+
</DialogFooter>
|
|
120
|
+
</form>
|
|
121
|
+
</DialogContent>
|
|
122
|
+
</Dialog>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -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
|
}
|
|
@@ -5,6 +5,7 @@ import { Github, RefreshCw } from "lucide-react"
|
|
|
5
5
|
import { useParams } from "wouter"
|
|
6
6
|
import { DownloadButtonAndMenu } from "../DownloadButtonAndMenu"
|
|
7
7
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
8
|
+
import { useCurrentPackageCircuitJson } from "../ViewPackagePage/hooks/use-current-package-circuit-json"
|
|
8
9
|
|
|
9
10
|
export function PackageBuildHeader() {
|
|
10
11
|
const { author, packageName } = useParams()
|
|
@@ -15,6 +16,8 @@ export function PackageBuildHeader() {
|
|
|
15
16
|
const { mutate: rebuildPackage, isLoading } =
|
|
16
17
|
useRebuildPackageReleaseMutation()
|
|
17
18
|
|
|
19
|
+
const { circuitJson } = useCurrentPackageCircuitJson()
|
|
20
|
+
|
|
18
21
|
return (
|
|
19
22
|
<div className="border-b border-gray-200 bg-white px-4 sm:px-6 py-4">
|
|
20
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">
|
|
@@ -63,7 +66,12 @@ export function PackageBuildHeader() {
|
|
|
63
66
|
{isLoading ? "Rebuilding..." : "Rebuild"}
|
|
64
67
|
</Button>
|
|
65
68
|
)}
|
|
66
|
-
<DownloadButtonAndMenu
|
|
69
|
+
<DownloadButtonAndMenu
|
|
70
|
+
offerMultipleImageFormats
|
|
71
|
+
circuitJson={circuitJson}
|
|
72
|
+
unscopedName={packageName}
|
|
73
|
+
author={author}
|
|
74
|
+
/>
|
|
67
75
|
</div>
|
|
68
76
|
</div>
|
|
69
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",
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import {
|
|
3
|
+
Select,
|
|
4
|
+
SelectContent,
|
|
5
|
+
SelectItem,
|
|
6
|
+
SelectTrigger,
|
|
7
|
+
SelectValue,
|
|
8
|
+
} from "@/components/ui/select"
|
|
9
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
10
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
11
|
+
import { useQuery } from "react-query"
|
|
12
|
+
import { Button } from "../ui/button"
|
|
13
|
+
import { Label } from "../ui/label"
|
|
14
|
+
import { Plus } from "lucide-react"
|
|
15
|
+
|
|
16
|
+
interface GitHubRepositorySelectorProps {
|
|
17
|
+
value?: string
|
|
18
|
+
onValueChange?: (value: string) => void
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
open?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const GitHubRepositorySelector = ({
|
|
24
|
+
value,
|
|
25
|
+
onValueChange,
|
|
26
|
+
disabled = false,
|
|
27
|
+
open = false,
|
|
28
|
+
}: GitHubRepositorySelectorProps) => {
|
|
29
|
+
const axios = useAxios()
|
|
30
|
+
const apiBaseUrl = useApiBaseUrl()
|
|
31
|
+
const [selectedRepository, setSelectedRepository] = useState<string>(
|
|
32
|
+
value || "",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
// Fetch available repositories
|
|
36
|
+
const { data: repositoriesData, error: repositoriesError } = useQuery(
|
|
37
|
+
["github-repositories"],
|
|
38
|
+
async () => {
|
|
39
|
+
const response = await axios.get("/github/repos/list_available")
|
|
40
|
+
return response.data
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
enabled: open, // Only fetch when needed
|
|
44
|
+
retry: false,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const handleConnectMoreRepos = async () => {
|
|
49
|
+
window.location.href = `${apiBaseUrl}/github/installations/create_new_installation_redirect?return_to_page=${window.location.pathname}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const handleValueChange = (newValue: string) => {
|
|
53
|
+
if (newValue === "connect-more") {
|
|
54
|
+
handleConnectMoreRepos()
|
|
55
|
+
} else {
|
|
56
|
+
setSelectedRepository(newValue)
|
|
57
|
+
onValueChange?.(newValue)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-1">
|
|
63
|
+
<Label htmlFor="repository">GitHub Repository</Label>
|
|
64
|
+
{(repositoriesError as any)?.response?.status === 400 &&
|
|
65
|
+
(repositoriesError as any)?.response?.data?.error_code ===
|
|
66
|
+
"github_not_connected" ? (
|
|
67
|
+
<div className="space-y-2">
|
|
68
|
+
<div className="text-sm text-muted-foreground">
|
|
69
|
+
Connect your GitHub account to link this package to a repository.
|
|
70
|
+
</div>
|
|
71
|
+
<Button
|
|
72
|
+
type="button"
|
|
73
|
+
variant="outline"
|
|
74
|
+
onClick={handleConnectMoreRepos}
|
|
75
|
+
className="w-full"
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
>
|
|
78
|
+
<svg
|
|
79
|
+
className="w-4 h-4 mr-2"
|
|
80
|
+
viewBox="0 0 24 24"
|
|
81
|
+
fill="currentColor"
|
|
82
|
+
>
|
|
83
|
+
<path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z" />
|
|
84
|
+
</svg>
|
|
85
|
+
Connect GitHub Account
|
|
86
|
+
</Button>
|
|
87
|
+
</div>
|
|
88
|
+
) : (
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
<Select
|
|
91
|
+
value={selectedRepository}
|
|
92
|
+
onValueChange={handleValueChange}
|
|
93
|
+
disabled={disabled}
|
|
94
|
+
>
|
|
95
|
+
<SelectTrigger className="w-full">
|
|
96
|
+
<SelectValue placeholder="Select a repository" />
|
|
97
|
+
</SelectTrigger>
|
|
98
|
+
<SelectContent className="!z-[999]">
|
|
99
|
+
{repositoriesData?.repos?.map((repo: any) => (
|
|
100
|
+
<SelectItem key={repo.full_name} value={repo.full_name}>
|
|
101
|
+
<div className="flex items-center space-x-2">
|
|
102
|
+
<span>{repo.unscoped_name}</span>
|
|
103
|
+
{repo.private && (
|
|
104
|
+
<span className="text-xs text-muted-foreground">
|
|
105
|
+
(private)
|
|
106
|
+
</span>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
</SelectItem>
|
|
110
|
+
))}
|
|
111
|
+
<SelectItem value="connect-more">
|
|
112
|
+
<div className="flex items-center space-x-2 text-blue-600">
|
|
113
|
+
<Plus className="w-3 h-3" />
|
|
114
|
+
<span>Connect More Repos</span>
|
|
115
|
+
</div>
|
|
116
|
+
</SelectItem>
|
|
117
|
+
</SelectContent>
|
|
118
|
+
</Select>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|