@tscircuit/fake-snippets 0.0.104 → 0.0.106
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 +1 -1
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +294 -0
- package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +304 -0
- package/dist/bundle.js +864 -390
- package/dist/index.d.ts +142 -4
- package/dist/index.js +175 -6
- package/dist/schema.d.ts +198 -1
- package/dist/schema.js +28 -1
- package/fake-snippets-api/lib/db/db-client.ts +51 -3
- package/fake-snippets-api/lib/db/schema.ts +28 -0
- package/fake-snippets-api/lib/db/seed.ts +125 -2
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +41 -0
- package/fake-snippets-api/routes/api/github/repos/refresh.ts +44 -0
- package/fake-snippets-api/routes/api/package_builds/get.ts +70 -0
- package/fake-snippets-api/routes/api/package_builds/latest.ts +109 -0
- package/fake-snippets-api/routes/api/package_builds/list.ts +98 -0
- package/package.json +1 -1
- package/src/App.tsx +2 -1
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +8 -7
- package/src/components/dialogs/GitHubRepositorySelector.tsx +185 -52
- package/src/components/preview/BuildsList.tsx +267 -188
- package/src/components/preview/ConnectedPackagesList.tsx +36 -24
- package/src/components/preview/ConnectedRepoDashboard.tsx +19 -13
- package/src/components/preview/ConnectedRepoOverview.tsx +52 -34
- package/src/components/preview/index.tsx +13 -73
- package/src/hooks/use-package-builds.ts +135 -0
- package/src/pages/view-connected-repo.tsx +38 -7
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { publicMapPackageBuild } from "fake-snippets-api/lib/public-mapping/public-map-package-build"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
queryParams: z.object({
|
|
9
|
+
package_build_id: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
jsonResponse: z.object({
|
|
12
|
+
package_build: z.any(),
|
|
13
|
+
}),
|
|
14
|
+
})(async (req, ctx) => {
|
|
15
|
+
const { package_build_id } = req.query
|
|
16
|
+
|
|
17
|
+
if (!package_build_id) {
|
|
18
|
+
return ctx.error(400, {
|
|
19
|
+
error_code: "invalid_request",
|
|
20
|
+
message: "package_build_id is required",
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const packageBuild = ctx.db.packageBuilds.find(
|
|
25
|
+
(build) => build.package_build_id === package_build_id,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if (!packageBuild) {
|
|
29
|
+
return ctx.error(404, {
|
|
30
|
+
error_code: "package_build_not_found",
|
|
31
|
+
message: "Package build not found",
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const packageRelease = ctx.db.packageReleases.find(
|
|
36
|
+
(pr) => pr.package_release_id === packageBuild.package_release_id,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if (!packageRelease) {
|
|
40
|
+
return ctx.error(404, {
|
|
41
|
+
error_code: "package_release_not_found",
|
|
42
|
+
message: "Package release not found",
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pkg = ctx.db.packages.find(
|
|
47
|
+
(p) => p.package_id === packageRelease.package_id,
|
|
48
|
+
)
|
|
49
|
+
if (!pkg) {
|
|
50
|
+
return ctx.error(404, {
|
|
51
|
+
error_code: "package_not_found",
|
|
52
|
+
message: "Package not found",
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (pkg.creator_account_id !== ctx.auth.account_id) {
|
|
57
|
+
return ctx.error(403, {
|
|
58
|
+
error_code: "unauthorized",
|
|
59
|
+
message: "You are not authorized to access this package build",
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const publicBuild = publicMapPackageBuild(packageBuild, {
|
|
64
|
+
include_logs: true,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return ctx.json({
|
|
68
|
+
package_build: publicBuild,
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { publicMapPackageBuild } from "fake-snippets-api/lib/public-mapping/public-map-package-build"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
queryParams: z.object({
|
|
9
|
+
package_id: z.string().optional(),
|
|
10
|
+
package_release_id: z.string().optional(),
|
|
11
|
+
}),
|
|
12
|
+
jsonResponse: z.object({
|
|
13
|
+
package_build: z.any().nullable(),
|
|
14
|
+
}),
|
|
15
|
+
})(async (req, ctx) => {
|
|
16
|
+
const { package_id, package_release_id } = req.query
|
|
17
|
+
|
|
18
|
+
if (!package_id && !package_release_id) {
|
|
19
|
+
return ctx.error(400, {
|
|
20
|
+
error_code: "invalid_request",
|
|
21
|
+
message: "Either package_id or package_release_id must be provided",
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let targetPackageId = package_id
|
|
26
|
+
|
|
27
|
+
if (package_release_id) {
|
|
28
|
+
const packageRelease = ctx.db.packageReleases.find(
|
|
29
|
+
(pr) => pr.package_release_id === package_release_id,
|
|
30
|
+
)
|
|
31
|
+
if (!packageRelease) {
|
|
32
|
+
return ctx.error(404, {
|
|
33
|
+
error_code: "package_release_not_found",
|
|
34
|
+
message: "Package release not found",
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
targetPackageId = packageRelease.package_id
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (targetPackageId) {
|
|
41
|
+
const pkg = ctx.db.packages.find((p) => p.package_id === targetPackageId)
|
|
42
|
+
if (!pkg) {
|
|
43
|
+
return ctx.error(404, {
|
|
44
|
+
error_code: "package_not_found",
|
|
45
|
+
message: "Package not found",
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
if (pkg.creator_account_id !== ctx.auth.account_id) {
|
|
49
|
+
console.log(
|
|
50
|
+
pkg.creator_account_id !== ctx.auth.account_id,
|
|
51
|
+
pkg.creator_account_id,
|
|
52
|
+
ctx.auth.account_id,
|
|
53
|
+
)
|
|
54
|
+
return ctx.error(403, {
|
|
55
|
+
error_code: "unauthorized",
|
|
56
|
+
message: "You are not authorized to access this package",
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let builds = ctx.db.packageBuilds
|
|
62
|
+
|
|
63
|
+
if (package_id) {
|
|
64
|
+
const packageReleases = ctx.db.packageReleases.filter(
|
|
65
|
+
(x) => x.package_id === package_id,
|
|
66
|
+
)
|
|
67
|
+
if (packageReleases.length === 0) {
|
|
68
|
+
return ctx.error(404, {
|
|
69
|
+
error_code: "package_not_found",
|
|
70
|
+
message: "Package not found",
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const packageReleaseIds = packageReleases
|
|
75
|
+
.filter((pr) => pr.package_id === package_id)
|
|
76
|
+
.map((pr) => pr.package_release_id)
|
|
77
|
+
|
|
78
|
+
builds = builds.filter((build) =>
|
|
79
|
+
packageReleaseIds.includes(build.package_release_id),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (package_release_id) {
|
|
84
|
+
builds = builds.filter(
|
|
85
|
+
(build) => build.package_release_id === package_release_id,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
builds = builds.sort(
|
|
90
|
+
(a, b) =>
|
|
91
|
+
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const latestBuild = builds[0] || null
|
|
95
|
+
|
|
96
|
+
if (!latestBuild) {
|
|
97
|
+
return ctx.json({
|
|
98
|
+
package_build: null,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const publicBuild = publicMapPackageBuild(latestBuild, {
|
|
103
|
+
include_logs: true,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return ctx.json({
|
|
107
|
+
package_build: publicBuild,
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { publicMapPackageBuild } from "fake-snippets-api/lib/public-mapping/public-map-package-build"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET"],
|
|
7
|
+
auth: "session",
|
|
8
|
+
queryParams: z.object({
|
|
9
|
+
package_id: z.string().optional(),
|
|
10
|
+
package_release_id: z.string().optional(),
|
|
11
|
+
}),
|
|
12
|
+
jsonResponse: z.object({
|
|
13
|
+
package_builds: z.array(z.any()),
|
|
14
|
+
}),
|
|
15
|
+
})(async (req, ctx) => {
|
|
16
|
+
const { package_id, package_release_id } = req.query
|
|
17
|
+
console.log(ctx.db.packageBuilds)
|
|
18
|
+
if (!package_id && !package_release_id) {
|
|
19
|
+
return ctx.error(400, {
|
|
20
|
+
error_code: "invalid_request",
|
|
21
|
+
message: "Either package_id or package_release_id must be provided",
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let targetPackageId = package_id
|
|
26
|
+
|
|
27
|
+
if (package_release_id) {
|
|
28
|
+
const packageRelease = ctx.db.packageReleases.find(
|
|
29
|
+
(pr) => pr.package_release_id === package_release_id,
|
|
30
|
+
)
|
|
31
|
+
if (!packageRelease) {
|
|
32
|
+
return ctx.error(404, {
|
|
33
|
+
error_code: "package_release_not_found",
|
|
34
|
+
message: "Package release not found",
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
targetPackageId = packageRelease.package_id
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (targetPackageId) {
|
|
41
|
+
const pkg = ctx.db.packages.find((p) => p.package_id === targetPackageId)
|
|
42
|
+
if (!pkg) {
|
|
43
|
+
return ctx.error(404, {
|
|
44
|
+
error_code: "package_not_found",
|
|
45
|
+
message: "Package not found",
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
if (pkg.creator_account_id !== ctx.auth.account_id) {
|
|
49
|
+
return ctx.error(403, {
|
|
50
|
+
error_code: "unauthorized",
|
|
51
|
+
message: "You are not authorized to access this package",
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let builds = ctx.db.packageBuilds
|
|
57
|
+
|
|
58
|
+
if (package_id) {
|
|
59
|
+
const packageReleases = ctx.db.packageReleases.filter(
|
|
60
|
+
(x) => x.package_id === package_id,
|
|
61
|
+
)
|
|
62
|
+
if (packageReleases.length === 0) {
|
|
63
|
+
return ctx.error(404, {
|
|
64
|
+
error_code: "package_not_found",
|
|
65
|
+
message: "Package not found",
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const packageReleaseIds = packageReleases
|
|
70
|
+
.filter((pr) => pr.package_id === package_id)
|
|
71
|
+
.map((pr) => pr.package_release_id)
|
|
72
|
+
|
|
73
|
+
builds = builds.filter((build) =>
|
|
74
|
+
packageReleaseIds.includes(build.package_release_id),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (package_release_id) {
|
|
79
|
+
builds = builds.filter(
|
|
80
|
+
(build) => build.package_release_id === package_release_id,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
builds = builds.sort(
|
|
85
|
+
(a, b) =>
|
|
86
|
+
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const publicBuilds = builds.map((build) =>
|
|
90
|
+
publicMapPackageBuild(build, {
|
|
91
|
+
include_logs: false,
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return ctx.json({
|
|
96
|
+
package_builds: publicBuilds,
|
|
97
|
+
})
|
|
98
|
+
})
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -143,7 +143,8 @@ class ErrorBoundary extends React.Component<
|
|
|
143
143
|
this.cleanup() // Clean up listeners before reload
|
|
144
144
|
this.setState({ reloading: true })
|
|
145
145
|
this.reloadTimeout = window.setTimeout(() => {
|
|
146
|
-
|
|
146
|
+
if (window?.location.href.includes("localhost:")) return
|
|
147
|
+
window.location.reload()
|
|
147
148
|
}, 500)
|
|
148
149
|
}
|
|
149
150
|
|
|
@@ -5,12 +5,9 @@ import { usePackageReleaseById } from "@/hooks/use-package-release"
|
|
|
5
5
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
6
6
|
import { BuildStatus, BuildStep } from "./build-status"
|
|
7
7
|
import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
8
|
-
import {
|
|
9
|
-
getBuildStatus,
|
|
10
|
-
getLatestBuildFromPackageRelease,
|
|
11
|
-
StatusIcon,
|
|
12
|
-
} from "@/components/preview"
|
|
8
|
+
import { getBuildStatus, StatusIcon } from "@/components/preview"
|
|
13
9
|
import { PrefetchPageLink } from "@/components/PrefetchPageLink"
|
|
10
|
+
import { useLatestPackageBuildByReleaseId } from "@/hooks/use-package-builds"
|
|
14
11
|
|
|
15
12
|
function getTranspilationStatus(
|
|
16
13
|
pr?: PackageRelease | null,
|
|
@@ -45,6 +42,9 @@ export default function SidebarReleasesSection() {
|
|
|
45
42
|
const { data: packageRelease } = usePackageReleaseById(
|
|
46
43
|
packageInfo?.latest_package_release_id,
|
|
47
44
|
)
|
|
45
|
+
const { data: latestBuild } = useLatestPackageBuildByReleaseId(
|
|
46
|
+
packageRelease?.package_release_id,
|
|
47
|
+
)
|
|
48
48
|
|
|
49
49
|
const buildSteps: BuildStep[] = [
|
|
50
50
|
{
|
|
@@ -74,8 +74,9 @@ export default function SidebarReleasesSection() {
|
|
|
74
74
|
)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
const
|
|
78
|
-
|
|
77
|
+
const { status, label } = latestBuild
|
|
78
|
+
? getBuildStatus(latestBuild)
|
|
79
|
+
: { status: "pending", label: "pending" }
|
|
79
80
|
return (
|
|
80
81
|
<div className="mb-6">
|
|
81
82
|
<h2 className="text-lg font-semibold mb-2">Releases</h2>
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import { useRef } from "react"
|
|
1
|
+
import { useRef, useState, useMemo } from "react"
|
|
2
|
+
import { Check, ChevronsUpDown } from "lucide-react"
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "@/components/ui/
|
|
4
|
+
Command,
|
|
5
|
+
CommandEmpty,
|
|
6
|
+
CommandGroup,
|
|
7
|
+
CommandInput,
|
|
8
|
+
CommandItem,
|
|
9
|
+
} from "@/components/ui/command"
|
|
10
|
+
import {
|
|
11
|
+
Popover,
|
|
12
|
+
PopoverContent,
|
|
13
|
+
PopoverTrigger,
|
|
14
|
+
} from "@/components/ui/popover"
|
|
15
|
+
import { cn } from "@/lib/utils"
|
|
9
16
|
import { useAxios } from "@/hooks/use-axios"
|
|
10
17
|
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
11
18
|
import { useQuery } from "react-query"
|
|
12
19
|
import { Button } from "../ui/button"
|
|
13
20
|
import { Label } from "../ui/label"
|
|
14
|
-
import { Minus, Plus } from "lucide-react"
|
|
21
|
+
import { Minus, Plus, RefreshCw } from "lucide-react"
|
|
15
22
|
import { Switch } from "../ui/switch"
|
|
16
23
|
|
|
17
24
|
interface GitHubRepositorySelectorProps {
|
|
@@ -36,8 +43,15 @@ export const GitHubRepositorySelector = ({
|
|
|
36
43
|
const axios = useAxios()
|
|
37
44
|
const apiBaseUrl = useApiBaseUrl()
|
|
38
45
|
const initialValue = useRef(selectedRepository).current
|
|
46
|
+
const [comboboxOpen, setComboboxOpen] = useState(false)
|
|
47
|
+
const [searchValue, setSearchValue] = useState("")
|
|
39
48
|
// Fetch available repositories
|
|
40
|
-
const {
|
|
49
|
+
const {
|
|
50
|
+
data: repositoriesData,
|
|
51
|
+
error: repositoriesError,
|
|
52
|
+
refetch: refetchRepositories,
|
|
53
|
+
isLoading,
|
|
54
|
+
} = useQuery(
|
|
41
55
|
["github-repositories"],
|
|
42
56
|
async () => {
|
|
43
57
|
const response = await axios.get("/github/repos/list_available")
|
|
@@ -53,20 +67,103 @@ export const GitHubRepositorySelector = ({
|
|
|
53
67
|
window.location.href = `${apiBaseUrl}/github/installations/create_new_installation_redirect?return_to_page=${window.location.pathname}`
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
const
|
|
57
|
-
|
|
70
|
+
const handleRefreshRepositories = async () => {
|
|
71
|
+
try {
|
|
72
|
+
// First call the refresh endpoint to update repositories
|
|
73
|
+
await axios.post("/github/repos/refresh")
|
|
74
|
+
// Then refetch the repositories list
|
|
75
|
+
refetchRepositories()
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error("Failed to refresh repositories:", error)
|
|
78
|
+
// Still try to refetch in case the error is not critical
|
|
79
|
+
refetchRepositories()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create searchable options for the combobox
|
|
84
|
+
const comboboxOptions = useMemo(() => {
|
|
85
|
+
const repos = repositoriesData?.repos || []
|
|
86
|
+
const repoOptions = repos.map((repo: any) => ({
|
|
87
|
+
value: repo.full_name,
|
|
88
|
+
label: repo.unscoped_name,
|
|
89
|
+
isPrivate: repo.private,
|
|
90
|
+
type: "repo" as const,
|
|
91
|
+
}))
|
|
92
|
+
|
|
93
|
+
const specialOptions = [
|
|
94
|
+
{
|
|
95
|
+
value: "connect-more",
|
|
96
|
+
label: "Connect More Repos",
|
|
97
|
+
type: "special" as const,
|
|
98
|
+
icon: "plus" as const,
|
|
99
|
+
},
|
|
100
|
+
...(initialValue
|
|
101
|
+
? [
|
|
102
|
+
{
|
|
103
|
+
value: "unlink//repo",
|
|
104
|
+
label: "Unlink Repo",
|
|
105
|
+
type: "special" as const,
|
|
106
|
+
icon: "minus" as const,
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
: []),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
return [...repoOptions, ...specialOptions]
|
|
113
|
+
}, [repositoriesData?.repos, initialValue])
|
|
114
|
+
|
|
115
|
+
// Filter options based on search
|
|
116
|
+
const filteredOptions = useMemo(() => {
|
|
117
|
+
if (!searchValue) return comboboxOptions
|
|
118
|
+
return comboboxOptions.filter(
|
|
119
|
+
(option) =>
|
|
120
|
+
option.label.toLowerCase().includes(searchValue.toLowerCase()) ||
|
|
121
|
+
option.value.toLowerCase().includes(searchValue.toLowerCase()),
|
|
122
|
+
)
|
|
123
|
+
}, [comboboxOptions, searchValue])
|
|
124
|
+
|
|
125
|
+
const handleComboboxSelect = (value: string) => {
|
|
126
|
+
if (value === "connect-more") {
|
|
58
127
|
handleConnectMoreRepos()
|
|
59
|
-
} else if (newValue === "unlink//repo") {
|
|
60
|
-
setSelectedRepository?.("unlink//repo")
|
|
61
128
|
} else {
|
|
62
|
-
setSelectedRepository?.(
|
|
129
|
+
setSelectedRepository?.(value)
|
|
63
130
|
}
|
|
131
|
+
setComboboxOpen(false)
|
|
132
|
+
setSearchValue("")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const getDisplayValue = () => {
|
|
136
|
+
if (!selectedRepository) return "Select a repository"
|
|
137
|
+
const option = comboboxOptions.find(
|
|
138
|
+
(opt) => opt.value === selectedRepository,
|
|
139
|
+
)
|
|
140
|
+
return option?.label || selectedRepository
|
|
64
141
|
}
|
|
65
142
|
|
|
66
143
|
return (
|
|
67
144
|
<>
|
|
68
145
|
<div className="space-y-1 mb-3">
|
|
69
|
-
<
|
|
146
|
+
<div className="flex items-center justify-between">
|
|
147
|
+
<Label htmlFor="repository">GitHub Repository</Label>
|
|
148
|
+
{!(
|
|
149
|
+
(repositoriesError as any)?.response?.status === 400 &&
|
|
150
|
+
(repositoriesError as any)?.response?.data?.error_code ===
|
|
151
|
+
"github_not_connected"
|
|
152
|
+
) && (
|
|
153
|
+
<Button
|
|
154
|
+
type="button"
|
|
155
|
+
variant="ghost"
|
|
156
|
+
size="sm"
|
|
157
|
+
onClick={handleRefreshRepositories}
|
|
158
|
+
disabled={disabled || isLoading}
|
|
159
|
+
className="h-auto p-1"
|
|
160
|
+
>
|
|
161
|
+
<RefreshCw
|
|
162
|
+
className={`w-3 h-3 ${isLoading ? "animate-spin" : ""}`}
|
|
163
|
+
/>
|
|
164
|
+
</Button>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
70
167
|
{(repositoriesError as any)?.response?.status === 400 &&
|
|
71
168
|
(repositoriesError as any)?.response?.data?.error_code ===
|
|
72
169
|
"github_not_connected" ? (
|
|
@@ -93,43 +190,79 @@ export const GitHubRepositorySelector = ({
|
|
|
93
190
|
</div>
|
|
94
191
|
) : (
|
|
95
192
|
<div className="space-y-2">
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
193
|
+
<Popover open={comboboxOpen} onOpenChange={setComboboxOpen}>
|
|
194
|
+
<PopoverTrigger asChild>
|
|
195
|
+
<Button
|
|
196
|
+
variant="outline"
|
|
197
|
+
role="combobox"
|
|
198
|
+
aria-expanded={comboboxOpen}
|
|
199
|
+
className="w-full justify-between"
|
|
200
|
+
disabled={disabled}
|
|
201
|
+
>
|
|
202
|
+
{getDisplayValue()}
|
|
203
|
+
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
204
|
+
</Button>
|
|
205
|
+
</PopoverTrigger>
|
|
206
|
+
<PopoverContent className="w-full p-0 z-[999]">
|
|
207
|
+
<Command shouldFilter={false}>
|
|
208
|
+
<CommandInput
|
|
209
|
+
value={searchValue}
|
|
210
|
+
onValueChange={setSearchValue}
|
|
211
|
+
placeholder="Search repositories..."
|
|
212
|
+
/>
|
|
213
|
+
<CommandEmpty className="text-sm text-slate-500 py-6">
|
|
214
|
+
No repositories found.
|
|
215
|
+
</CommandEmpty>
|
|
216
|
+
<CommandGroup className="max-h-[400px] overflow-y-auto">
|
|
217
|
+
{filteredOptions.map((option) => (
|
|
218
|
+
<CommandItem
|
|
219
|
+
key={option.value}
|
|
220
|
+
onSelect={() => handleComboboxSelect(option.value)}
|
|
221
|
+
className="cursor-pointer"
|
|
222
|
+
>
|
|
223
|
+
<div className="flex items-center space-x-2 w-full">
|
|
224
|
+
{option.type === "repo" ? (
|
|
225
|
+
<>
|
|
226
|
+
<Check
|
|
227
|
+
className={cn(
|
|
228
|
+
"mr-2 h-4 w-4",
|
|
229
|
+
selectedRepository === option.value
|
|
230
|
+
? "opacity-100"
|
|
231
|
+
: "opacity-0",
|
|
232
|
+
)}
|
|
233
|
+
/>
|
|
234
|
+
<span>{option.label}</span>
|
|
235
|
+
{option.isPrivate && (
|
|
236
|
+
<span className="text-xs text-muted-foreground">
|
|
237
|
+
(private)
|
|
238
|
+
</span>
|
|
239
|
+
)}
|
|
240
|
+
</>
|
|
241
|
+
) : (
|
|
242
|
+
<>
|
|
243
|
+
{option.icon === "plus" ? (
|
|
244
|
+
<Plus className="w-3 h-3 text-blue-600" />
|
|
245
|
+
) : (
|
|
246
|
+
<Minus className="w-3 h-3 text-red-600" />
|
|
247
|
+
)}
|
|
248
|
+
<span
|
|
249
|
+
className={
|
|
250
|
+
option.icon === "plus"
|
|
251
|
+
? "text-blue-600"
|
|
252
|
+
: "text-red-600"
|
|
253
|
+
}
|
|
254
|
+
>
|
|
255
|
+
{option.label}
|
|
256
|
+
</span>
|
|
257
|
+
</>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
</CommandItem>
|
|
261
|
+
))}
|
|
262
|
+
</CommandGroup>
|
|
263
|
+
</Command>
|
|
264
|
+
</PopoverContent>
|
|
265
|
+
</Popover>
|
|
133
266
|
</div>
|
|
134
267
|
)}
|
|
135
268
|
</div>
|