@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
export default withRouteSpec({
|
|
5
|
+
methods: ["GET"],
|
|
6
|
+
auth: "session",
|
|
7
|
+
jsonResponse: z.object({
|
|
8
|
+
repos: z.array(
|
|
9
|
+
z.object({
|
|
10
|
+
unscoped_name: z.string(),
|
|
11
|
+
full_name: z.string(),
|
|
12
|
+
private: z.boolean(),
|
|
13
|
+
owner: z.object({
|
|
14
|
+
login: z.string(),
|
|
15
|
+
}),
|
|
16
|
+
description: z.string().nullable(),
|
|
17
|
+
default_branch: z.string(),
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
}),
|
|
21
|
+
})(async (_req, ctx) => {
|
|
22
|
+
const account = ctx.db.getAccount(ctx.auth.account_id)
|
|
23
|
+
|
|
24
|
+
if (!account) {
|
|
25
|
+
return ctx.error(401, {
|
|
26
|
+
error_code: "account_not_found",
|
|
27
|
+
message: "Account not found",
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check if user has GitHub account connected
|
|
32
|
+
if (!account.github_username) {
|
|
33
|
+
return ctx.error(400, {
|
|
34
|
+
error_code: "github_not_connected",
|
|
35
|
+
message:
|
|
36
|
+
"GitHub account not connected. Please connect your GitHub account first.",
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if user has a GitHub installation
|
|
41
|
+
const githubInstallation = ctx.db.githubInstallations.find(
|
|
42
|
+
(installation) =>
|
|
43
|
+
installation.account_id === ctx.auth.account_id && installation.is_active,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if (!githubInstallation) {
|
|
47
|
+
// Return empty array if no GitHub installation found
|
|
48
|
+
return ctx.json({
|
|
49
|
+
repos: [],
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Mock repositories for demonstration
|
|
54
|
+
// In a real implementation, this would fetch from GitHub API using the installation access token
|
|
55
|
+
const mockRepos = [
|
|
56
|
+
{
|
|
57
|
+
unscoped_name: "my-electronics-project",
|
|
58
|
+
full_name: `${account.github_username}/my-electronics-project`,
|
|
59
|
+
owner: {
|
|
60
|
+
login: account.github_username,
|
|
61
|
+
},
|
|
62
|
+
description: "Arduino-based sensor monitoring system",
|
|
63
|
+
private: false,
|
|
64
|
+
default_branch: "main",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
unscoped_name: "pcb-designs",
|
|
68
|
+
full_name: `${account.github_username}/pcb-designs`,
|
|
69
|
+
owner: {
|
|
70
|
+
login: account.github_username,
|
|
71
|
+
},
|
|
72
|
+
description: "Collection of PCB designs for various projects",
|
|
73
|
+
private: true,
|
|
74
|
+
default_branch: "main",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
unscoped_name: "tscircuit-examples",
|
|
78
|
+
full_name: `${account.github_username}/tscircuit-examples`,
|
|
79
|
+
owner: {
|
|
80
|
+
login: account.github_username,
|
|
81
|
+
},
|
|
82
|
+
description: "Examples and tutorials for tscircuit",
|
|
83
|
+
private: false,
|
|
84
|
+
default_branch: "main",
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
return ctx.json({
|
|
89
|
+
repos: mockRepos,
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -18,6 +18,7 @@ export default withRouteSpec({
|
|
|
18
18
|
.optional(),
|
|
19
19
|
description: z.string().optional(),
|
|
20
20
|
website: z.string().optional(),
|
|
21
|
+
github_repo_full_name: z.string().optional(),
|
|
21
22
|
is_private: z.boolean().optional(),
|
|
22
23
|
is_unlisted: z.boolean().optional(),
|
|
23
24
|
default_view: z.enum(["files", "3d", "pcb", "schematic"]).optional(),
|
|
@@ -38,6 +39,7 @@ export default withRouteSpec({
|
|
|
38
39
|
website,
|
|
39
40
|
is_private,
|
|
40
41
|
is_unlisted,
|
|
42
|
+
github_repo_full_name,
|
|
41
43
|
default_view,
|
|
42
44
|
} = req.jsonBody
|
|
43
45
|
|
|
@@ -81,6 +83,8 @@ export default withRouteSpec({
|
|
|
81
83
|
description: description ?? existingPackage.description,
|
|
82
84
|
unscoped_name: name ?? existingPackage.unscoped_name,
|
|
83
85
|
website: website ?? existingPackage.website,
|
|
86
|
+
github_repo_full_name:
|
|
87
|
+
github_repo_full_name ?? existingPackage.github_repo_full_name,
|
|
84
88
|
is_private: is_private ?? existingPackage.is_private,
|
|
85
89
|
is_public:
|
|
86
90
|
is_private !== undefined ? !is_private : existingPackage.is_public,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.102",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@tscircuit/mm": "^0.0.8",
|
|
84
84
|
"@tscircuit/pcb-viewer": "^1.11.194",
|
|
85
85
|
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
86
|
-
"@tscircuit/runframe": "0.0.
|
|
86
|
+
"@tscircuit/runframe": "0.0.764",
|
|
87
87
|
"@tscircuit/schematic-viewer": "^2.0.21",
|
|
88
88
|
"@types/babel__standalone": "^7.1.7",
|
|
89
89
|
"@types/bun": "^1.1.10",
|
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"
|
|
@@ -5,7 +5,7 @@ import { useHotkeyCombo } from "@/hooks/use-hotkey"
|
|
|
5
5
|
import { useNotImplementedToast } from "@/hooks/use-toast"
|
|
6
6
|
import { fuzzyMatch } from "@/components/ViewPackagePage/utils/fuzz-search"
|
|
7
7
|
import { Command } from "cmdk"
|
|
8
|
-
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
8
|
+
import { Package, Account } from "fake-snippets-api/lib/db/schema"
|
|
9
9
|
import React, { useCallback, useEffect, useMemo, useRef } from "react"
|
|
10
10
|
import { useQuery } from "react-query"
|
|
11
11
|
import {
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
Sparkles,
|
|
17
17
|
Clock,
|
|
18
18
|
ArrowRight,
|
|
19
|
+
Star,
|
|
20
|
+
User,
|
|
19
21
|
} from "lucide-react"
|
|
20
22
|
import { DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
|
21
23
|
|
|
@@ -40,6 +42,11 @@ interface ScoredPackage extends Package {
|
|
|
40
42
|
matches: number[]
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
interface ScoredAccount extends Account {
|
|
46
|
+
score: number
|
|
47
|
+
matches: number[]
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
const CmdKMenu = () => {
|
|
44
51
|
const [open, setOpen] = React.useState(false)
|
|
45
52
|
const [searchQuery, setSearchQuery] = React.useState("")
|
|
@@ -113,13 +120,42 @@ const CmdKMenu = () => {
|
|
|
113
120
|
["packageSearch", searchQuery],
|
|
114
121
|
async () => {
|
|
115
122
|
if (!searchQuery) return []
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
try {
|
|
124
|
+
const { data } = await axios.post("/packages/search", {
|
|
125
|
+
query: searchQuery,
|
|
126
|
+
})
|
|
127
|
+
return data.packages || []
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.warn("Failed to fetch packages:", error)
|
|
130
|
+
return []
|
|
131
|
+
}
|
|
120
132
|
},
|
|
121
133
|
{
|
|
122
134
|
enabled: Boolean(searchQuery),
|
|
135
|
+
retry: false,
|
|
136
|
+
refetchOnWindowFocus: false,
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const { data: allAccounts = [], isLoading: isSearchingAccounts } = useQuery(
|
|
141
|
+
["accountSearch", searchQuery],
|
|
142
|
+
async () => {
|
|
143
|
+
if (!searchQuery) return []
|
|
144
|
+
try {
|
|
145
|
+
const { data } = await axios.post("/accounts/search", {
|
|
146
|
+
query: searchQuery,
|
|
147
|
+
limit: 5,
|
|
148
|
+
})
|
|
149
|
+
return data.accounts || []
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.warn("Failed to fetch accounts:", error)
|
|
152
|
+
return []
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
enabled: Boolean(searchQuery) && Boolean(currentUser),
|
|
157
|
+
retry: false,
|
|
158
|
+
refetchOnWindowFocus: false,
|
|
123
159
|
},
|
|
124
160
|
)
|
|
125
161
|
|
|
@@ -133,20 +169,43 @@ const CmdKMenu = () => {
|
|
|
133
169
|
})
|
|
134
170
|
.filter((pkg: ScoredPackage) => pkg.score >= 0)
|
|
135
171
|
.sort((a: ScoredPackage, b: ScoredPackage) => b.score - a.score)
|
|
136
|
-
.slice(0,
|
|
172
|
+
.slice(0, 6)
|
|
137
173
|
}, [allPackages, searchQuery])
|
|
138
174
|
|
|
175
|
+
const accountSearchResults = useMemo((): ScoredAccount[] => {
|
|
176
|
+
if (!searchQuery || !allAccounts.length) return []
|
|
177
|
+
|
|
178
|
+
return allAccounts
|
|
179
|
+
.map((account: Account) => {
|
|
180
|
+
const { score, matches } = fuzzyMatch(
|
|
181
|
+
searchQuery,
|
|
182
|
+
account.github_username,
|
|
183
|
+
)
|
|
184
|
+
return { ...account, score, matches }
|
|
185
|
+
})
|
|
186
|
+
.filter((account: ScoredAccount) => account.score >= 0)
|
|
187
|
+
.sort((a: ScoredAccount, b: ScoredAccount) => b.score - a.score)
|
|
188
|
+
.slice(0, 5)
|
|
189
|
+
}, [allAccounts, searchQuery])
|
|
190
|
+
|
|
139
191
|
const { data: recentPackages = [] } = useQuery<Package[]>(
|
|
140
192
|
["userPackages", currentUser],
|
|
141
193
|
async () => {
|
|
142
194
|
if (!currentUser) return []
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
195
|
+
try {
|
|
196
|
+
const response = await axios.post(`/packages/list`, {
|
|
197
|
+
owner_github_username: currentUser,
|
|
198
|
+
})
|
|
199
|
+
return response.data.packages || []
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.warn("Failed to fetch recent packages:", error)
|
|
202
|
+
return []
|
|
203
|
+
}
|
|
147
204
|
},
|
|
148
205
|
{
|
|
149
206
|
enabled: !!currentUser && !searchQuery,
|
|
207
|
+
retry: false,
|
|
208
|
+
refetchOnWindowFocus: false,
|
|
150
209
|
},
|
|
151
210
|
)
|
|
152
211
|
|
|
@@ -195,7 +254,7 @@ const CmdKMenu = () => {
|
|
|
195
254
|
|
|
196
255
|
const allItems = useMemo(() => {
|
|
197
256
|
const items: Array<{
|
|
198
|
-
type: "package" | "recent" | "template" | "blank" | "import"
|
|
257
|
+
type: "package" | "account" | "recent" | "template" | "blank" | "import"
|
|
199
258
|
item: any
|
|
200
259
|
disabled?: boolean
|
|
201
260
|
}> = []
|
|
@@ -206,6 +265,12 @@ const CmdKMenu = () => {
|
|
|
206
265
|
})
|
|
207
266
|
}
|
|
208
267
|
|
|
268
|
+
if (searchQuery && accountSearchResults.length > 0) {
|
|
269
|
+
accountSearchResults.forEach((account) => {
|
|
270
|
+
items.push({ type: "account", item: account })
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
209
274
|
if (!searchQuery && recentPackages.length > 0) {
|
|
210
275
|
recentPackages.slice(0, 6).forEach((pkg) => {
|
|
211
276
|
items.push({ type: "recent", item: pkg })
|
|
@@ -225,7 +290,13 @@ const CmdKMenu = () => {
|
|
|
225
290
|
})
|
|
226
291
|
|
|
227
292
|
return items
|
|
228
|
-
}, [
|
|
293
|
+
}, [
|
|
294
|
+
searchQuery,
|
|
295
|
+
searchResults,
|
|
296
|
+
accountSearchResults,
|
|
297
|
+
recentPackages,
|
|
298
|
+
filteredStaticOptions,
|
|
299
|
+
])
|
|
229
300
|
|
|
230
301
|
useHotkeyCombo("cmd+k", () => {
|
|
231
302
|
setOpen((prev) => !prev)
|
|
@@ -285,6 +356,10 @@ const CmdKMenu = () => {
|
|
|
285
356
|
window.location.href = `/${item.owner_github_username}/${item.unscoped_name}`
|
|
286
357
|
setOpen(false)
|
|
287
358
|
break
|
|
359
|
+
case "account":
|
|
360
|
+
window.location.href = `/${item.github_username}`
|
|
361
|
+
setOpen(false)
|
|
362
|
+
break
|
|
288
363
|
case "blank":
|
|
289
364
|
case "template":
|
|
290
365
|
if (!item.disabled) {
|
|
@@ -362,7 +437,11 @@ const CmdKMenu = () => {
|
|
|
362
437
|
)}
|
|
363
438
|
</div>
|
|
364
439
|
</div>
|
|
365
|
-
<div className="flex items-center gap-
|
|
440
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
441
|
+
<div className="flex items-center gap-1 text-gray-500">
|
|
442
|
+
<Star className="w-3 h-3 fill-yellow-400 text-yellow-400" />
|
|
443
|
+
<span className="text-xs">{data.star_count ?? 0}</span>
|
|
444
|
+
</div>
|
|
366
445
|
<span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
|
|
367
446
|
package
|
|
368
447
|
</span>
|
|
@@ -371,6 +450,41 @@ const CmdKMenu = () => {
|
|
|
371
450
|
</div>
|
|
372
451
|
)
|
|
373
452
|
|
|
453
|
+
case "account":
|
|
454
|
+
return (
|
|
455
|
+
<div
|
|
456
|
+
key={`account-${data.account_id}`}
|
|
457
|
+
ref={isSelected ? selectedItemRef : null}
|
|
458
|
+
className={baseClasses}
|
|
459
|
+
onClick={() => !disabled && handleItemSelect(item)}
|
|
460
|
+
>
|
|
461
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
462
|
+
<img
|
|
463
|
+
src={`https://github.com/${data.github_username}.png`}
|
|
464
|
+
alt={`${data.github_username} avatar`}
|
|
465
|
+
className="w-6 h-6 rounded-full flex-shrink-0"
|
|
466
|
+
onError={(e) => {
|
|
467
|
+
const target = e.target as HTMLImageElement
|
|
468
|
+
target.style.display = "none"
|
|
469
|
+
target.nextElementSibling?.classList.remove("hidden")
|
|
470
|
+
}}
|
|
471
|
+
/>
|
|
472
|
+
<User className="w-6 h-6 text-gray-400 flex-shrink-0 hidden" />
|
|
473
|
+
<div className="flex flex-col min-w-0">
|
|
474
|
+
<span className="font-medium text-gray-900 truncate">
|
|
475
|
+
{renderHighlighted(data, data.github_username)}
|
|
476
|
+
</span>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
<div className="flex items-center gap-1 flex-shrink-0">
|
|
480
|
+
<span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
|
|
481
|
+
user
|
|
482
|
+
</span>
|
|
483
|
+
{isSelected && <ArrowRight className="w-3 h-3 text-gray-400" />}
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
)
|
|
487
|
+
|
|
374
488
|
case "blank":
|
|
375
489
|
case "template":
|
|
376
490
|
return (
|
|
@@ -464,7 +578,7 @@ const CmdKMenu = () => {
|
|
|
464
578
|
</div>
|
|
465
579
|
|
|
466
580
|
<Command.List className="max-h-80 overflow-y-auto p-2 space-y-4">
|
|
467
|
-
{isSearching ? (
|
|
581
|
+
{isSearching || isSearchingAccounts ? (
|
|
468
582
|
<Command.Loading className="p-6 text-center text-gray-500">
|
|
469
583
|
<div className="flex items-center justify-center gap-2">
|
|
470
584
|
<div className="w-3 h-3 border-2 border-gray-300 border-t-gray-600 rounded-full animate-spin"></div>
|
|
@@ -476,7 +590,7 @@ const CmdKMenu = () => {
|
|
|
476
590
|
{searchQuery && searchResults.length > 0 && (
|
|
477
591
|
<div>
|
|
478
592
|
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2">
|
|
479
|
-
|
|
593
|
+
Packages
|
|
480
594
|
</h3>
|
|
481
595
|
<div className="space-y-1">
|
|
482
596
|
{searchResults.slice(0, 8).map((pkg, localIndex) => {
|
|
@@ -490,6 +604,25 @@ const CmdKMenu = () => {
|
|
|
490
604
|
</div>
|
|
491
605
|
)}
|
|
492
606
|
|
|
607
|
+
{searchQuery && accountSearchResults.length > 0 && (
|
|
608
|
+
<div>
|
|
609
|
+
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2">
|
|
610
|
+
Users
|
|
611
|
+
</h3>
|
|
612
|
+
<div className="space-y-1">
|
|
613
|
+
{accountSearchResults
|
|
614
|
+
.slice(0, 5)
|
|
615
|
+
.map((account, localIndex) => {
|
|
616
|
+
const globalIndex = searchResults.length + localIndex
|
|
617
|
+
return renderItem(
|
|
618
|
+
{ type: "account", item: account },
|
|
619
|
+
globalIndex,
|
|
620
|
+
)
|
|
621
|
+
})}
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
)}
|
|
625
|
+
|
|
493
626
|
{!searchQuery && recentPackages.length > 0 && (
|
|
494
627
|
<div>
|
|
495
628
|
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2 flex items-center gap-1">
|
|
@@ -518,7 +651,7 @@ const CmdKMenu = () => {
|
|
|
518
651
|
(template, localIndex) => {
|
|
519
652
|
const globalIndex =
|
|
520
653
|
(searchQuery
|
|
521
|
-
? searchResults.length
|
|
654
|
+
? searchResults.length + accountSearchResults.length
|
|
522
655
|
: recentPackages.length) + localIndex
|
|
523
656
|
return renderItem(
|
|
524
657
|
{
|
|
@@ -544,7 +677,7 @@ const CmdKMenu = () => {
|
|
|
544
677
|
(template, localIndex) => {
|
|
545
678
|
const globalIndex =
|
|
546
679
|
(searchQuery
|
|
547
|
-
? searchResults.length
|
|
680
|
+
? searchResults.length + accountSearchResults.length
|
|
548
681
|
: recentPackages.length) +
|
|
549
682
|
filteredStaticOptions.blankTemplates.length +
|
|
550
683
|
localIndex
|
|
@@ -568,7 +701,7 @@ const CmdKMenu = () => {
|
|
|
568
701
|
(option, localIndex) => {
|
|
569
702
|
const globalIndex =
|
|
570
703
|
(searchQuery
|
|
571
|
-
? searchResults.length
|
|
704
|
+
? searchResults.length + accountSearchResults.length
|
|
572
705
|
: recentPackages.length) +
|
|
573
706
|
filteredStaticOptions.blankTemplates.length +
|
|
574
707
|
filteredStaticOptions.templates.length +
|
|
@@ -585,10 +718,12 @@ const CmdKMenu = () => {
|
|
|
585
718
|
|
|
586
719
|
{searchQuery &&
|
|
587
720
|
!searchResults.length &&
|
|
721
|
+
!accountSearchResults.length &&
|
|
588
722
|
!filteredStaticOptions.blankTemplates.length &&
|
|
589
723
|
!filteredStaticOptions.templates.length &&
|
|
590
724
|
!filteredStaticOptions.importOptions.length &&
|
|
591
|
-
!isSearching &&
|
|
725
|
+
!isSearching &&
|
|
726
|
+
!isSearchingAccounts && (
|
|
592
727
|
<Command.Empty className="py-8 text-center">
|
|
593
728
|
<div className="text-gray-400 mb-1">No results found</div>
|
|
594
729
|
<div className="text-gray-500 text-xs">
|
|
@@ -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
|
+
}
|