@tscircuit/fake-snippets 0.0.104 → 0.0.105
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.
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
export default withRouteSpec({
|
|
5
|
+
methods: ["POST"],
|
|
6
|
+
auth: "session",
|
|
7
|
+
jsonResponse: z.object({
|
|
8
|
+
success: z.boolean(),
|
|
9
|
+
message: z.string(),
|
|
10
|
+
repos_refreshed: z.number().optional(),
|
|
11
|
+
}),
|
|
12
|
+
})(async (req, ctx) => {
|
|
13
|
+
const account = ctx.db.getAccount(ctx.auth.account_id)
|
|
14
|
+
|
|
15
|
+
if (!account) {
|
|
16
|
+
return ctx.error(401, {
|
|
17
|
+
error_code: "account_not_found",
|
|
18
|
+
message: "Account not found",
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check GitHub installation
|
|
23
|
+
const githubInstallation = ctx.db.githubInstallations.find(
|
|
24
|
+
(installation) =>
|
|
25
|
+
installation.account_id === ctx.auth.account_id && installation.is_active,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if (!githubInstallation) {
|
|
29
|
+
return ctx.error(400, {
|
|
30
|
+
error_code: "github_not_connected",
|
|
31
|
+
message: "GitHub account not connected",
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// In a real implementation, this would refresh repos from GitHub API
|
|
36
|
+
// For fake API, simulate refreshing by updating the installation timestamp
|
|
37
|
+
githubInstallation.updated_at = new Date().toISOString()
|
|
38
|
+
|
|
39
|
+
return ctx.json({
|
|
40
|
+
success: true,
|
|
41
|
+
message: "GitHub repositories refreshed successfully",
|
|
42
|
+
repos_refreshed: 3, // Mock number
|
|
43
|
+
})
|
|
44
|
+
})
|
package/package.json
CHANGED
|
@@ -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>
|