@tscircuit/fake-snippets 0.0.101 → 0.0.103
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 +6 -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 +13 -1
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +17 -0
- package/src/components/dialogs/GitHubRepositorySelector.tsx +183 -0
- package/src/components/dialogs/create-use-dialog.tsx +8 -2
- package/src/components/dialogs/edit-package-details-dialog.tsx +32 -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 +241 -0
- package/src/components/preview/ConnectedPackagesList.tsx +187 -0
- package/src/components/preview/ConnectedRepoDashboard.tsx +243 -0
- package/src/components/preview/ConnectedRepoOverview.tsx +454 -0
- package/src/components/preview/index.tsx +248 -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 +40 -24
- package/src/pages/view-connected-repo.tsx +18 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
2
2
|
import { useHotkeyCombo } from "@/hooks/use-hotkey"
|
|
3
3
|
import { basicSetup } from "@/lib/codemirror/basic-setup"
|
|
4
4
|
import {
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
} from "@/hooks/useFileManagement"
|
|
50
50
|
import { isHiddenFile } from "../ViewPackagePage/utils/is-hidden-file"
|
|
51
51
|
import { inlineCopilot } from "codemirror-copilot"
|
|
52
|
-
import {
|
|
52
|
+
import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
|
|
53
53
|
|
|
54
54
|
const defaultImports = `
|
|
55
55
|
import React from "@types/react/jsx-runtime"
|
|
@@ -92,7 +92,7 @@ export const CodeEditor = ({
|
|
|
92
92
|
const viewRef = useRef<EditorView | null>(null)
|
|
93
93
|
const ataRef = useRef<ReturnType<typeof setupTypeAcquisition> | null>(null)
|
|
94
94
|
const lastReceivedTsFileTimeRef = useRef<number>(0)
|
|
95
|
-
const apiUrl =
|
|
95
|
+
const apiUrl = useApiBaseUrl()
|
|
96
96
|
const [cursorPosition, setCursorPosition] = useState<number | null>(null)
|
|
97
97
|
const [code, setCode] = useState(files[0]?.content || "")
|
|
98
98
|
const [fontSize, setFontSize] = useState(14)
|
|
@@ -108,6 +108,8 @@ export const CodeEditor = ({
|
|
|
108
108
|
const filePathFromUrl = urlParams.get("file_path")
|
|
109
109
|
const lineNumberFromUrl = urlParams.get("line")
|
|
110
110
|
const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = useState(false)
|
|
111
|
+
const { Dialog: ViewTsFilesDialog, openDialog: openViewTsFilesDialog } =
|
|
112
|
+
useViewTsFilesDialog()
|
|
111
113
|
|
|
112
114
|
const entryPointFileName = useMemo(() => {
|
|
113
115
|
const entryPointFile = findTargetFile(files, null)
|
|
@@ -519,42 +521,47 @@ export const CodeEditor = ({
|
|
|
519
521
|
}
|
|
520
522
|
}
|
|
521
523
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
524
|
+
// TypeScript "Go to Definition" functionality
|
|
525
|
+
const facet = view.state.facet(tsFacet)
|
|
526
|
+
if (facet) {
|
|
527
|
+
const { env, path } = facet
|
|
528
|
+
const definitions =
|
|
529
|
+
env.languageService.getDefinitionAtPosition(path, pos)
|
|
530
|
+
if (definitions && definitions.length > 0) {
|
|
531
|
+
const definition = definitions[0]
|
|
532
|
+
const definitionFileName = definition.fileName
|
|
533
|
+
if (definitionFileName) {
|
|
534
|
+
const localFilePath = definitionFileName.startsWith("/")
|
|
535
|
+
? definitionFileName.replace("/", "")
|
|
536
|
+
: definitionFileName
|
|
537
|
+
if (fileMap[localFilePath]) {
|
|
538
|
+
const definitionContent = fileMap[localFilePath]
|
|
539
|
+
const lines = definitionContent
|
|
540
|
+
?.substring(0, definition.textSpan.start)
|
|
541
|
+
.split("\n")
|
|
542
|
+
const lineNumber = lines?.length
|
|
543
|
+
|
|
544
|
+
onFileSelect(localFilePath, lineNumber)
|
|
545
|
+
return true
|
|
546
|
+
} else {
|
|
547
|
+
const definitionContent =
|
|
548
|
+
env
|
|
549
|
+
.getSourceFile(definitionFileName)
|
|
550
|
+
?.getFullText() || ""
|
|
551
|
+
const lines = definitionContent
|
|
552
|
+
.substring(0, definition.textSpan.start)
|
|
553
|
+
.split("\n")
|
|
554
|
+
const lineNumber = lines.length
|
|
555
|
+
openViewTsFilesDialog({
|
|
556
|
+
initialFile: definitionFileName,
|
|
557
|
+
initialLine: lineNumber,
|
|
558
|
+
})
|
|
552
559
|
return true
|
|
553
560
|
}
|
|
554
|
-
return !!fileMap[targetPath]
|
|
555
561
|
}
|
|
556
562
|
}
|
|
557
563
|
}
|
|
564
|
+
|
|
558
565
|
return false
|
|
559
566
|
},
|
|
560
567
|
keydown: (event) => {
|
|
@@ -660,7 +667,6 @@ export const CodeEditor = ({
|
|
|
660
667
|
}, [
|
|
661
668
|
!isStreaming,
|
|
662
669
|
currentFile,
|
|
663
|
-
code !== "",
|
|
664
670
|
Boolean(highlighter),
|
|
665
671
|
isSaving,
|
|
666
672
|
fontSize,
|
|
@@ -867,6 +873,7 @@ export const CodeEditor = ({
|
|
|
867
873
|
onClose={() => setShowGlobalFindReplace(false)}
|
|
868
874
|
/>
|
|
869
875
|
)}
|
|
876
|
+
<ViewTsFilesDialog />
|
|
870
877
|
</div>
|
|
871
878
|
)
|
|
872
879
|
}
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "@/components/ui/tooltip"
|
|
28
28
|
import { convertRawEasyToTsx, fetchEasyEDAComponent } from "easyeda/browser"
|
|
29
29
|
import { ComponentSearchResult } from "@tscircuit/runframe/runner"
|
|
30
|
-
import {
|
|
30
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
31
31
|
import { ICreateFileProps, ICreateFileResult } from "@/hooks/useFileManagement"
|
|
32
32
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
33
33
|
|
|
@@ -60,7 +60,7 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
60
60
|
useImportComponentDialog()
|
|
61
61
|
const { toast, toastLibrary } = useToast()
|
|
62
62
|
const [sidebarOpen, setSidebarOpen] = fileSidebarState
|
|
63
|
-
const API_BASE =
|
|
63
|
+
const API_BASE = useApiBaseUrl()
|
|
64
64
|
const [aiAutocompleteEnabled, setAiAutocompleteEnabled] = aiAutocompleteState
|
|
65
65
|
const session = useGlobalStore((s) => s.session)
|
|
66
66
|
|
|
@@ -184,11 +184,13 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
|
|
|
184
184
|
)
|
|
185
185
|
const tsxComponent = await convertRawEasyToTsx(jlcpcbComponent)
|
|
186
186
|
let componentName = component.name.replace(/ /g, "-")
|
|
187
|
-
|
|
187
|
+
let componentPath = `imports/${componentName}.tsx`
|
|
188
|
+
if (files[componentPath] || files[`./${componentPath}`]) {
|
|
188
189
|
componentName = `${componentName}-1`
|
|
190
|
+
componentPath = `imports/${componentName}.tsx`
|
|
189
191
|
}
|
|
190
192
|
const createFileResult = createFile({
|
|
191
|
-
newFileName:
|
|
193
|
+
newFileName: componentPath,
|
|
192
194
|
content: tsxComponent,
|
|
193
195
|
onError: (error) => {
|
|
194
196
|
throw error
|
|
@@ -45,6 +45,9 @@ import { useForkPackageMutation } from "@/hooks/useForkPackageMutation"
|
|
|
45
45
|
import tscircuitCorePkg from "@tscircuit/core/package.json"
|
|
46
46
|
import { useRenamePackageDialog } from "../dialogs/rename-package-dialog"
|
|
47
47
|
import { useUpdatePackageDescriptionDialog } from "../dialogs/update-package-description-dialog"
|
|
48
|
+
import { useCreateReleaseDialog } from "@/hooks/use-create-release-dialog"
|
|
49
|
+
import { Tag } from "lucide-react"
|
|
50
|
+
import { CreateReleaseDialog } from "../CreateReleaseDialog"
|
|
48
51
|
|
|
49
52
|
export default function EditorNav({
|
|
50
53
|
circuitJson,
|
|
@@ -58,6 +61,8 @@ export default function EditorNav({
|
|
|
58
61
|
onDiscard,
|
|
59
62
|
packageType,
|
|
60
63
|
isSaving,
|
|
64
|
+
files,
|
|
65
|
+
packageFilesMeta,
|
|
61
66
|
}: {
|
|
62
67
|
pkg?: Package | null
|
|
63
68
|
circuitJson?: AnyCircuitElement[] | null
|
|
@@ -70,6 +75,13 @@ export default function EditorNav({
|
|
|
70
75
|
isSaving: boolean
|
|
71
76
|
onSave: () => void
|
|
72
77
|
onDiscard?: () => void
|
|
78
|
+
files?: { path: string; content: string }[]
|
|
79
|
+
packageFilesMeta?: {
|
|
80
|
+
created_at: string
|
|
81
|
+
file_path: string
|
|
82
|
+
package_file_id: string
|
|
83
|
+
package_release_id: string
|
|
84
|
+
}[]
|
|
73
85
|
}) {
|
|
74
86
|
const [, navigate] = useLocation()
|
|
75
87
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
@@ -84,6 +96,18 @@ export default function EditorNav({
|
|
|
84
96
|
useConfirmDeletePackageDialog()
|
|
85
97
|
const { Dialog: ViewTsFilesDialog, openDialog: openViewTsFilesDialog } =
|
|
86
98
|
useViewTsFilesDialog()
|
|
99
|
+
const createReleaseDialog = useCreateReleaseDialog({
|
|
100
|
+
packageId: pkg?.package_id ?? "",
|
|
101
|
+
packageName: pkg?.unscoped_name ?? "",
|
|
102
|
+
currentVersion: pkg?.latest_version || undefined,
|
|
103
|
+
onSuccess: () => {
|
|
104
|
+
qc.invalidateQueries({ queryKey: ["packages"] })
|
|
105
|
+
qc.invalidateQueries({ queryKey: ["packages", pkg?.package_id] })
|
|
106
|
+
},
|
|
107
|
+
files: files || [],
|
|
108
|
+
currentPackage: pkg || undefined,
|
|
109
|
+
packageFilesMeta: packageFilesMeta || [],
|
|
110
|
+
})
|
|
87
111
|
|
|
88
112
|
const [isChangingType, setIsChangingType] = useState(false)
|
|
89
113
|
const [currentType, setCurrentType] = useState(
|
|
@@ -229,14 +253,25 @@ export default function EditorNav({
|
|
|
229
253
|
</span>
|
|
230
254
|
)}
|
|
231
255
|
{pkg.owner_github_username === session?.github_username && (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
256
|
+
<>
|
|
257
|
+
<Button
|
|
258
|
+
variant="ghost"
|
|
259
|
+
size="icon"
|
|
260
|
+
className="h-6 w-6 ml-2"
|
|
261
|
+
onClick={() => openRenameDialog()}
|
|
262
|
+
>
|
|
263
|
+
<Pencil className="h-3 w-3 text-gray-700" />
|
|
264
|
+
</Button>
|
|
265
|
+
<Button
|
|
266
|
+
variant="ghost"
|
|
267
|
+
size="icon"
|
|
268
|
+
className="h-6 w-6 ml-2"
|
|
269
|
+
onClick={() => createReleaseDialog.openDialog()}
|
|
270
|
+
disabled={hasUnsavedChanges || isSaving}
|
|
271
|
+
>
|
|
272
|
+
<Tag className="h-3 w-3 text-gray-700" />
|
|
273
|
+
</Button>
|
|
274
|
+
</>
|
|
240
275
|
)}
|
|
241
276
|
{isPrivate && (
|
|
242
277
|
<div className="relative group">
|
|
@@ -363,22 +398,23 @@ export default function EditorNav({
|
|
|
363
398
|
<Eye className="mr-1 h-3 w-3" />
|
|
364
399
|
Public
|
|
365
400
|
</Button> */}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
401
|
+
|
|
402
|
+
<DropdownMenu>
|
|
403
|
+
<DropdownMenuTrigger asChild>
|
|
404
|
+
<Button variant="ghost" size="icon" className="hidden md:flex">
|
|
405
|
+
<MoreVertical className="h-3 w-3" />
|
|
406
|
+
</Button>
|
|
407
|
+
</DropdownMenuTrigger>
|
|
408
|
+
<DropdownMenuContent>
|
|
409
|
+
<DropdownMenuItem
|
|
410
|
+
className="text-xs"
|
|
411
|
+
onClick={() => openViewTsFilesDialog()}
|
|
412
|
+
>
|
|
413
|
+
<File className="mr-2 h-3 w-3" />
|
|
414
|
+
View Files
|
|
415
|
+
</DropdownMenuItem>
|
|
416
|
+
{pkg &&
|
|
417
|
+
session?.github_username === pkg?.owner_github_username && (
|
|
382
418
|
<>
|
|
383
419
|
<DropdownMenuItem
|
|
384
420
|
className="text-xs"
|
|
@@ -436,21 +472,21 @@ export default function EditorNav({
|
|
|
436
472
|
</DropdownMenuItem>
|
|
437
473
|
</DropdownMenuSubContent>
|
|
438
474
|
</DropdownMenuSub>
|
|
475
|
+
<DropdownMenuItem
|
|
476
|
+
className="text-xs text-red-600"
|
|
477
|
+
onClick={() => openDeleteDialog()}
|
|
478
|
+
>
|
|
479
|
+
<Trash2 className="mr-2 h-3 w-3" />
|
|
480
|
+
Delete Package
|
|
481
|
+
</DropdownMenuItem>
|
|
439
482
|
</>
|
|
440
483
|
)}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
</DropdownMenuItem>
|
|
448
|
-
<DropdownMenuItem className="text-xs text-gray-500" disabled>
|
|
449
|
-
@tscircuit/core@{tscircuitCorePkg.version}
|
|
450
|
-
</DropdownMenuItem>
|
|
451
|
-
</DropdownMenuContent>
|
|
452
|
-
</DropdownMenu>
|
|
453
|
-
)}
|
|
484
|
+
<DropdownMenuItem className="text-xs text-gray-500" disabled>
|
|
485
|
+
@tscircuit/core@{tscircuitCorePkg.version}
|
|
486
|
+
</DropdownMenuItem>
|
|
487
|
+
</DropdownMenuContent>
|
|
488
|
+
</DropdownMenu>
|
|
489
|
+
|
|
454
490
|
<Button
|
|
455
491
|
variant="ghost"
|
|
456
492
|
size="icon"
|
|
@@ -488,6 +524,17 @@ export default function EditorNav({
|
|
|
488
524
|
Discard Changes
|
|
489
525
|
</DropdownMenuItem>
|
|
490
526
|
)}
|
|
527
|
+
{pkg &&
|
|
528
|
+
session?.github_username === pkg?.owner_github_username && (
|
|
529
|
+
<DropdownMenuItem
|
|
530
|
+
className="text-xs"
|
|
531
|
+
onClick={() => createReleaseDialog.openDialog()}
|
|
532
|
+
disabled={hasUnsavedChanges || isSaving}
|
|
533
|
+
>
|
|
534
|
+
<Tag className="mr-1 h-3 w-3" />
|
|
535
|
+
Create Release
|
|
536
|
+
</DropdownMenuItem>
|
|
537
|
+
)}
|
|
491
538
|
<DropdownMenuItem
|
|
492
539
|
className="text-xs"
|
|
493
540
|
onClick={() => {
|
|
@@ -561,6 +608,16 @@ export default function EditorNav({
|
|
|
561
608
|
packageOwner={pkg?.owner_github_username ?? ""}
|
|
562
609
|
/>
|
|
563
610
|
<ViewTsFilesDialog />
|
|
611
|
+
<CreateReleaseDialog
|
|
612
|
+
isOpen={createReleaseDialog.isOpen}
|
|
613
|
+
onClose={createReleaseDialog.closeDialog}
|
|
614
|
+
currentVersion={createReleaseDialog.currentVersion}
|
|
615
|
+
version={createReleaseDialog.version}
|
|
616
|
+
setVersion={createReleaseDialog.setVersion}
|
|
617
|
+
isLoading={createReleaseDialog.isLoading}
|
|
618
|
+
error={createReleaseDialog.error}
|
|
619
|
+
onCreateRelease={createReleaseDialog.handleCreateRelease}
|
|
620
|
+
/>
|
|
564
621
|
</nav>
|
|
565
622
|
)
|
|
566
623
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
2
|
+
import { Badge } from "@/components/ui/badge"
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
import {
|
|
5
|
+
Clock,
|
|
6
|
+
GitBranch,
|
|
7
|
+
AlertCircle,
|
|
8
|
+
CheckCircle,
|
|
9
|
+
Loader2,
|
|
10
|
+
MoreHorizontal,
|
|
11
|
+
GitCommit,
|
|
12
|
+
Plus,
|
|
13
|
+
} from "lucide-react"
|
|
14
|
+
import {
|
|
15
|
+
DropdownMenu,
|
|
16
|
+
DropdownMenuContent,
|
|
17
|
+
DropdownMenuItem,
|
|
18
|
+
DropdownMenuTrigger,
|
|
19
|
+
} from "@/components/ui/dropdown-menu"
|
|
20
|
+
import {
|
|
21
|
+
Table,
|
|
22
|
+
TableBody,
|
|
23
|
+
TableCell,
|
|
24
|
+
TableHead,
|
|
25
|
+
TableHeader,
|
|
26
|
+
TableRow,
|
|
27
|
+
} from "@/components/ui/table"
|
|
28
|
+
import { getBuildStatus, MOCK_DEPLOYMENTS, PackageBuild, StatusIcon } from "."
|
|
29
|
+
import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
|
|
30
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
31
|
+
|
|
32
|
+
export const BuildsList = ({
|
|
33
|
+
pkg,
|
|
34
|
+
onSelectBuild,
|
|
35
|
+
}: {
|
|
36
|
+
pkg: Package
|
|
37
|
+
onSelectBuild?: (build: PackageBuild) => void
|
|
38
|
+
}) => {
|
|
39
|
+
const builds = MOCK_DEPLOYMENTS
|
|
40
|
+
return (
|
|
41
|
+
<div className="space-y-6">
|
|
42
|
+
<div className="flex items-center justify-between">
|
|
43
|
+
<div>
|
|
44
|
+
<h2 className="text-2xl font-bold text-gray-900">Builds</h2>
|
|
45
|
+
<p className="text-gray-600">Manage and monitor your builds</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<Card>
|
|
50
|
+
<CardHeader>
|
|
51
|
+
<CardTitle>Recent Deployments</CardTitle>
|
|
52
|
+
</CardHeader>
|
|
53
|
+
<CardContent>
|
|
54
|
+
<div className="overflow-x-auto">
|
|
55
|
+
<Table>
|
|
56
|
+
<TableHeader>
|
|
57
|
+
<TableRow>
|
|
58
|
+
<TableHead>Status</TableHead>
|
|
59
|
+
<TableHead>Build ID</TableHead>
|
|
60
|
+
<TableHead>Branch</TableHead>
|
|
61
|
+
<TableHead>Commit</TableHead>
|
|
62
|
+
<TableHead>Author</TableHead>
|
|
63
|
+
<TableHead>Created</TableHead>
|
|
64
|
+
<TableHead>Actions</TableHead>
|
|
65
|
+
</TableRow>
|
|
66
|
+
</TableHeader>
|
|
67
|
+
<TableBody>
|
|
68
|
+
{builds.map((build) => {
|
|
69
|
+
const { status, label } = getBuildStatus(build)
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<TableRow
|
|
73
|
+
key={build.package_build_id}
|
|
74
|
+
className="cursor-pointer hover:bg-gray-50"
|
|
75
|
+
onClick={() => onSelectBuild?.(build)}
|
|
76
|
+
>
|
|
77
|
+
<TableCell>
|
|
78
|
+
<div className="flex items-center gap-2">
|
|
79
|
+
<StatusIcon status={status} />
|
|
80
|
+
<Badge
|
|
81
|
+
variant={
|
|
82
|
+
status === "success"
|
|
83
|
+
? "default"
|
|
84
|
+
: status === "error"
|
|
85
|
+
? "destructive"
|
|
86
|
+
: "secondary"
|
|
87
|
+
}
|
|
88
|
+
className="text-xs"
|
|
89
|
+
>
|
|
90
|
+
{label}
|
|
91
|
+
</Badge>
|
|
92
|
+
</div>
|
|
93
|
+
</TableCell>
|
|
94
|
+
<TableCell>
|
|
95
|
+
<code className="text-sm bg-gray-100 px-2 py-1 rounded">
|
|
96
|
+
{build.package_build_id.slice(-8)}
|
|
97
|
+
</code>
|
|
98
|
+
</TableCell>
|
|
99
|
+
<TableCell>
|
|
100
|
+
<div className="flex items-center gap-2">
|
|
101
|
+
{build.branch_name?.includes("/") ? (
|
|
102
|
+
<GitBranch className="w-3 h-3 text-gray-500" />
|
|
103
|
+
) : (
|
|
104
|
+
<GitCommit className="w-3 h-3 text-gray-500" />
|
|
105
|
+
)}
|
|
106
|
+
<Badge variant="outline" className="text-xs">
|
|
107
|
+
{build.branch_name || "main"}
|
|
108
|
+
</Badge>
|
|
109
|
+
</div>
|
|
110
|
+
</TableCell>
|
|
111
|
+
<TableCell>
|
|
112
|
+
<div className="max-w-xs">
|
|
113
|
+
<p className="text-sm font-medium truncate">
|
|
114
|
+
{build.commit_message || "No commit message"}
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
</TableCell>
|
|
118
|
+
<TableCell>
|
|
119
|
+
<span className="text-sm text-gray-600">
|
|
120
|
+
{build.commit_author || "Unknown"}
|
|
121
|
+
</span>
|
|
122
|
+
</TableCell>
|
|
123
|
+
<TableCell>
|
|
124
|
+
<span className="text-sm text-gray-600">
|
|
125
|
+
{formatTimeAgo(build.created_at)}
|
|
126
|
+
</span>
|
|
127
|
+
</TableCell>
|
|
128
|
+
<TableCell>
|
|
129
|
+
<div className="flex items-center gap-2">
|
|
130
|
+
<DropdownMenu>
|
|
131
|
+
<DropdownMenuTrigger asChild>
|
|
132
|
+
<Button
|
|
133
|
+
variant="ghost"
|
|
134
|
+
size="sm"
|
|
135
|
+
onClick={(e) => e.stopPropagation()}
|
|
136
|
+
>
|
|
137
|
+
<MoreHorizontal className="w-3 h-3" />
|
|
138
|
+
</Button>
|
|
139
|
+
</DropdownMenuTrigger>
|
|
140
|
+
<DropdownMenuContent align="end">
|
|
141
|
+
<DropdownMenuItem
|
|
142
|
+
onClick={() => onSelectBuild?.(build)}
|
|
143
|
+
>
|
|
144
|
+
View Details
|
|
145
|
+
</DropdownMenuItem>
|
|
146
|
+
<DropdownMenuItem
|
|
147
|
+
onClick={() => onSelectBuild?.(build)}
|
|
148
|
+
>
|
|
149
|
+
View Logs
|
|
150
|
+
</DropdownMenuItem>
|
|
151
|
+
</DropdownMenuContent>
|
|
152
|
+
</DropdownMenu>
|
|
153
|
+
</div>
|
|
154
|
+
</TableCell>
|
|
155
|
+
</TableRow>
|
|
156
|
+
)
|
|
157
|
+
})}
|
|
158
|
+
</TableBody>
|
|
159
|
+
</Table>
|
|
160
|
+
</div>
|
|
161
|
+
</CardContent>
|
|
162
|
+
</Card>
|
|
163
|
+
|
|
164
|
+
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
165
|
+
<Card>
|
|
166
|
+
<CardContent className="p-6">
|
|
167
|
+
<div className="flex items-center gap-3">
|
|
168
|
+
<div className="p-2 bg-green-100 rounded-lg">
|
|
169
|
+
<CheckCircle className="w-5 h-5 text-green-600" />
|
|
170
|
+
</div>
|
|
171
|
+
<div>
|
|
172
|
+
<p className="text-sm text-gray-600">Successful</p>
|
|
173
|
+
<p className="text-2xl font-bold text-gray-900">
|
|
174
|
+
{
|
|
175
|
+
builds.filter((d) => getBuildStatus(d).status === "success")
|
|
176
|
+
.length
|
|
177
|
+
}
|
|
178
|
+
</p>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</CardContent>
|
|
182
|
+
</Card>
|
|
183
|
+
|
|
184
|
+
<Card>
|
|
185
|
+
<CardContent className="p-6">
|
|
186
|
+
<div className="flex items-center gap-3">
|
|
187
|
+
<div className="p-2 bg-red-100 rounded-lg">
|
|
188
|
+
<AlertCircle className="w-5 h-5 text-red-600" />
|
|
189
|
+
</div>
|
|
190
|
+
<div>
|
|
191
|
+
<p className="text-sm text-gray-600">Failed</p>
|
|
192
|
+
<p className="text-2xl font-bold text-gray-900">
|
|
193
|
+
{
|
|
194
|
+
builds.filter((d) => getBuildStatus(d).status === "error")
|
|
195
|
+
.length
|
|
196
|
+
}
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</CardContent>
|
|
201
|
+
</Card>
|
|
202
|
+
|
|
203
|
+
<Card>
|
|
204
|
+
<CardContent className="p-6">
|
|
205
|
+
<div className="flex items-center gap-3">
|
|
206
|
+
<div className="p-2 bg-blue-100 rounded-lg">
|
|
207
|
+
<Loader2 className="w-5 h-5 text-blue-600 animate-spin" />
|
|
208
|
+
</div>
|
|
209
|
+
<div>
|
|
210
|
+
<p className="text-sm text-gray-600">Building</p>
|
|
211
|
+
<p className="text-2xl font-bold text-gray-900">
|
|
212
|
+
{
|
|
213
|
+
builds.filter(
|
|
214
|
+
(d) => getBuildStatus(d).status === "building",
|
|
215
|
+
).length
|
|
216
|
+
}
|
|
217
|
+
</p>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</CardContent>
|
|
221
|
+
</Card>
|
|
222
|
+
|
|
223
|
+
<Card>
|
|
224
|
+
<CardContent className="p-6">
|
|
225
|
+
<div className="flex items-center gap-3">
|
|
226
|
+
<div className="p-2 bg-gray-100 rounded-lg">
|
|
227
|
+
<Clock className="w-5 h-5 text-gray-600" />
|
|
228
|
+
</div>
|
|
229
|
+
<div>
|
|
230
|
+
<p className="text-sm text-gray-600">Total</p>
|
|
231
|
+
<p className="text-2xl font-bold text-gray-900">
|
|
232
|
+
{builds.length}
|
|
233
|
+
</p>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</CardContent>
|
|
237
|
+
</Card>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
)
|
|
241
|
+
}
|