@tscircuit/fake-snippets 0.0.87 → 0.0.89
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 +96 -14
- package/bun-tests/fake-snippets-api/routes/proxy.test.ts +42 -0
- package/bun.lock +187 -206
- package/dist/bundle.js +207 -101
- package/fake-snippets-api/routes/api/package_releases/create.ts +1 -1
- package/fake-snippets-api/routes/api/proxy.ts +128 -0
- package/package.json +57 -50
- package/renovate.json +2 -1
- package/src/App.tsx +22 -3
- package/src/ContextProviders.tsx +2 -0
- package/src/build-watcher.ts +52 -0
- package/src/components/CmdKMenu.tsx +533 -197
- package/src/components/DownloadButtonAndMenu.tsx +104 -26
- package/src/components/FileSidebar.tsx +11 -1
- package/src/components/Header.tsx +5 -1
- package/src/components/Header2.tsx +7 -2
- package/src/components/HeaderLogin.tsx +1 -1
- package/src/components/PackageBuildsPage/LogContent.tsx +25 -22
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +6 -6
- package/src/components/PackageBuildsPage/build-preview-content.tsx +5 -5
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +15 -13
- package/src/components/PackageBuildsPage/package-build-header.tsx +17 -16
- package/src/components/PackageCard.tsx +66 -16
- package/src/components/PrefetchPageLink.tsx +66 -15
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +14 -2
- package/src/components/ViewPackagePage/components/important-files-view.tsx +97 -22
- package/src/components/ViewPackagePage/components/main-content-header.tsx +27 -3
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +2 -2
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +49 -34
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +20 -12
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +0 -7
- package/src/components/ViewPackagePage/utils/fuzz-search.ts +121 -0
- package/src/components/ViewPackagePage/utils/is-hidden-file.ts +4 -0
- package/src/components/ViewPackagePage/utils/is-package-file-important.ts +18 -5
- package/src/components/dialogs/confirm-delete-package-dialog.tsx +1 -1
- package/src/components/dialogs/confirm-discard-changes-dialog.tsx +73 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +2 -2
- package/src/components/dialogs/view-ts-files-dialog.tsx +478 -42
- package/src/components/package-port/CodeAndPreview.tsx +16 -0
- package/src/components/package-port/CodeEditor.tsx +113 -11
- package/src/components/package-port/CodeEditorHeader.tsx +39 -4
- package/src/components/package-port/EditorNav.tsx +41 -15
- package/src/components/package-port/GlobalFindReplace.tsx +681 -0
- package/src/components/package-port/QuickOpen.tsx +241 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tree-view.tsx +1 -1
- package/src/global.d.ts +3 -0
- package/src/hooks/use-ai-review.ts +31 -0
- package/src/hooks/use-current-package-release.ts +5 -1
- package/src/hooks/use-download-zip.ts +50 -0
- package/src/hooks/use-hotkey.ts +116 -0
- package/src/hooks/use-package-by-package-id.ts +1 -0
- package/src/hooks/use-package-by-package-name.ts +1 -0
- package/src/hooks/use-package-files.ts +3 -0
- package/src/hooks/use-package-release.ts +5 -1
- package/src/hooks/use-package.ts +1 -0
- package/src/hooks/use-request-ai-review-mutation.ts +14 -6
- package/src/hooks/use-snippet.ts +1 -0
- package/src/hooks/useFileManagement.ts +26 -8
- package/src/hooks/usePackageFilesLoader.ts +3 -1
- package/src/index.css +11 -0
- package/src/lib/decodeUrlHashToFsMap.ts +17 -0
- package/src/lib/download-fns/download-circuit-png.ts +88 -0
- package/src/lib/download-fns/download-png-utils.ts +31 -0
- package/src/lib/encodeFsMapToUrlHash.ts +13 -0
- package/src/lib/populate-query-cache-with-ssr-data.ts +39 -38
- package/src/lib/ts-lib-cache.ts +47 -0
- package/src/lib/types.ts +2 -0
- package/src/main.tsx +7 -0
- package/src/pages/dashboard.tsx +8 -5
- package/src/pages/user-profile.tsx +1 -1
- package/src/pages/view-package.tsx +15 -7
- package/vite.config.ts +100 -1
|
@@ -12,7 +12,10 @@ import ThreeDView from "./tab-views/3d-view"
|
|
|
12
12
|
import PCBView from "./tab-views/pcb-view"
|
|
13
13
|
import SchematicView from "./tab-views/schematic-view"
|
|
14
14
|
import BOMView from "./tab-views/bom-view"
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
isPackageFileImportant,
|
|
17
|
+
scorePackageFileImportance,
|
|
18
|
+
} from "../utils/is-package-file-important"
|
|
16
19
|
import Header from "@/components/Header"
|
|
17
20
|
import Footer from "@/components/Footer"
|
|
18
21
|
import PackageHeader from "./package-header"
|
|
@@ -21,6 +24,9 @@ import { useLocation } from "wouter"
|
|
|
21
24
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
22
25
|
import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
|
|
23
26
|
import { useRequestAiReviewMutation } from "@/hooks/use-request-ai-review-mutation"
|
|
27
|
+
import { useAiReview } from "@/hooks/use-ai-review"
|
|
28
|
+
import { useQueryClient } from "react-query"
|
|
29
|
+
import SidebarReleasesSection from "./sidebar-releases-section"
|
|
24
30
|
|
|
25
31
|
interface PackageFile {
|
|
26
32
|
package_file_id: string
|
|
@@ -48,14 +54,38 @@ export default function RepoPageContent({
|
|
|
48
54
|
onEditClicked,
|
|
49
55
|
}: RepoPageContentProps) {
|
|
50
56
|
const [activeView, setActiveView] = useState<string>("files")
|
|
57
|
+
const [pendingAiReviewId, setPendingAiReviewId] = useState<string | null>(
|
|
58
|
+
null,
|
|
59
|
+
)
|
|
60
|
+
const queryClient = useQueryClient()
|
|
61
|
+
const { data: aiReview } = useAiReview(pendingAiReviewId, {
|
|
62
|
+
refetchInterval: (data) => (data && !data.ai_review_text ? 2000 : false),
|
|
63
|
+
})
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (aiReview?.ai_review_text) {
|
|
66
|
+
queryClient.invalidateQueries(["packageRelease"])
|
|
67
|
+
setPendingAiReviewId(null)
|
|
68
|
+
}
|
|
69
|
+
}, [aiReview?.ai_review_text, queryClient])
|
|
51
70
|
const session = useGlobalStore((s) => s.session)
|
|
52
71
|
const { circuitJson, isLoading: isCircuitJsonLoading } =
|
|
53
72
|
useCurrentPackageCircuitJson()
|
|
54
|
-
const { mutate: requestAiReview } =
|
|
73
|
+
const { mutate: requestAiReview, isLoading: isRequestingAiReview } =
|
|
74
|
+
useRequestAiReviewMutation({
|
|
75
|
+
onSuccess: (_packageRelease, aiReview) => {
|
|
76
|
+
setPendingAiReviewId(aiReview.ai_review_id)
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const aiReviewRequested =
|
|
81
|
+
Boolean(packageRelease?.ai_review_requested) ||
|
|
82
|
+
Boolean(pendingAiReviewId) ||
|
|
83
|
+
isRequestingAiReview
|
|
55
84
|
|
|
56
85
|
// Handle initial view selection and hash-based view changes
|
|
57
86
|
useEffect(() => {
|
|
58
87
|
if (isCircuitJsonLoading) return
|
|
88
|
+
if (!packageInfo) return
|
|
59
89
|
const hash = window.location.hash.slice(1)
|
|
60
90
|
const validViews = ["files", "3d", "pcb", "schematic", "bom"]
|
|
61
91
|
const circuitDependentViews = ["3d", "pcb", "schematic", "bom"]
|
|
@@ -90,39 +120,18 @@ export default function RepoPageContent({
|
|
|
90
120
|
const importantFiles = useMemo(() => {
|
|
91
121
|
if (!packageFiles || !importantFilePaths) return []
|
|
92
122
|
|
|
93
|
-
return packageFiles
|
|
94
|
-
|
|
95
|
-
|
|
123
|
+
return packageFiles
|
|
124
|
+
.filter((file) =>
|
|
125
|
+
importantFilePaths.some((path) => file.file_path.endsWith(path)),
|
|
126
|
+
)
|
|
127
|
+
.sort((a, b) => {
|
|
128
|
+
const aImportance = scorePackageFileImportance(a.file_path)
|
|
129
|
+
const bImportance = scorePackageFileImportance(b.file_path)
|
|
130
|
+
return aImportance - bImportance
|
|
131
|
+
})
|
|
132
|
+
.reverse()
|
|
96
133
|
}, [packageFiles, importantFilePaths])
|
|
97
134
|
|
|
98
|
-
// Generate package name with version for file lookups
|
|
99
|
-
const packageNameWithVersion = useMemo(() => {
|
|
100
|
-
if (!packageInfo) return ""
|
|
101
|
-
|
|
102
|
-
// Format: @scope/packageName@version or packageName@version
|
|
103
|
-
const name = packageInfo.name
|
|
104
|
-
|
|
105
|
-
// Extract the latest version from the files (assuming version information is available)
|
|
106
|
-
const versionFile = packageFiles?.find(
|
|
107
|
-
(file) => file.file_path === "package.json",
|
|
108
|
-
)
|
|
109
|
-
let version = "latest"
|
|
110
|
-
|
|
111
|
-
if (versionFile) {
|
|
112
|
-
try {
|
|
113
|
-
const content =
|
|
114
|
-
versionFile.file_content || versionFile.content_text || "{}"
|
|
115
|
-
const packageJson = JSON.parse(content)
|
|
116
|
-
if (packageJson.version) {
|
|
117
|
-
version = packageJson.version
|
|
118
|
-
}
|
|
119
|
-
} catch (e) {
|
|
120
|
-
// If package.json can't be parsed, use "latest"
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return `${name}@${version}`
|
|
125
|
-
}, [packageInfo, packageFiles])
|
|
126
135
|
// Render the appropriate content based on the active view
|
|
127
136
|
const renderContent = () => {
|
|
128
137
|
switch (activeView) {
|
|
@@ -184,6 +193,7 @@ export default function RepoPageContent({
|
|
|
184
193
|
{/* Main Content Header with Tabs */}
|
|
185
194
|
<MainContentHeader
|
|
186
195
|
activeView={activeView}
|
|
196
|
+
packageFiles={packageFiles ?? []}
|
|
187
197
|
onViewChange={(view) => {
|
|
188
198
|
setActiveView(view)
|
|
189
199
|
// Update URL hash when view changes
|
|
@@ -200,10 +210,11 @@ export default function RepoPageContent({
|
|
|
200
210
|
importantFiles={importantFiles}
|
|
201
211
|
isLoading={!packageFiles}
|
|
202
212
|
onEditClicked={onEditClicked}
|
|
213
|
+
packageAuthorOwner={packageInfo?.owner_github_username}
|
|
203
214
|
aiDescription={packageInfo?.ai_description ?? ""}
|
|
204
215
|
aiUsageInstructions={packageInfo?.ai_usage_instructions ?? ""}
|
|
205
216
|
aiReviewText={packageRelease?.ai_review_text ?? null}
|
|
206
|
-
aiReviewRequested={
|
|
217
|
+
aiReviewRequested={aiReviewRequested}
|
|
207
218
|
onRequestAiReview={() => {
|
|
208
219
|
if (packageRelease) {
|
|
209
220
|
requestAiReview({
|
|
@@ -226,6 +237,10 @@ export default function RepoPageContent({
|
|
|
226
237
|
}}
|
|
227
238
|
/>
|
|
228
239
|
</div>
|
|
240
|
+
{/* Releases section - Only visible on small screens */}
|
|
241
|
+
<div className="block md:hidden w-full px-5">
|
|
242
|
+
<SidebarReleasesSection />
|
|
243
|
+
</div>
|
|
229
244
|
</div>
|
|
230
245
|
</div>
|
|
231
246
|
<Footer />
|
|
@@ -39,8 +39,8 @@ export default function SidebarAboutSection() {
|
|
|
39
39
|
const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
|
|
40
40
|
const isOwner =
|
|
41
41
|
isLoggedIn &&
|
|
42
|
-
packageInfo?.
|
|
43
|
-
useGlobalStore((s) => s.session?.
|
|
42
|
+
packageInfo?.owner_github_username ===
|
|
43
|
+
useGlobalStore((s) => s.session?.github_username)
|
|
44
44
|
|
|
45
45
|
// Local state to store updated values before the query refetches
|
|
46
46
|
const [localDescription, setLocalDescription] = useState<string>("")
|
|
@@ -9,21 +9,29 @@ import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
|
|
|
9
9
|
function getTranspilationStatus(
|
|
10
10
|
pr?: PackageRelease | null,
|
|
11
11
|
): BuildStep["status"] {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
switch (pr?.transpilation_display_status) {
|
|
13
|
+
case "complete":
|
|
14
|
+
return "success"
|
|
15
|
+
case "error":
|
|
16
|
+
return "error"
|
|
17
|
+
case "building":
|
|
18
|
+
return "running"
|
|
19
|
+
default:
|
|
20
|
+
return "pending"
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
function getCircuitJsonStatus(pr?: PackageRelease | null): BuildStep["status"] {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
switch (pr?.circuit_json_build_display_status) {
|
|
26
|
+
case "complete":
|
|
27
|
+
return "success"
|
|
28
|
+
case "error":
|
|
29
|
+
return "error"
|
|
30
|
+
case "building":
|
|
31
|
+
return "running"
|
|
32
|
+
default:
|
|
33
|
+
return "pending"
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
export default function SidebarReleasesSection() {
|
|
@@ -5,13 +5,6 @@ import { FileText, Folder } from "lucide-react"
|
|
|
5
5
|
import { useMemo, useState } from "react"
|
|
6
6
|
import { isHiddenFile } from "../../utils/is-hidden-file"
|
|
7
7
|
import { isWithinDirectory } from "../../utils/is-within-directory"
|
|
8
|
-
import {
|
|
9
|
-
DropdownMenu,
|
|
10
|
-
DropdownMenuContent,
|
|
11
|
-
DropdownMenuItem,
|
|
12
|
-
DropdownMenuTrigger,
|
|
13
|
-
} from "@/components/ui/dropdown-menu"
|
|
14
|
-
import { Settings } from "lucide-react"
|
|
15
8
|
import HiddenFilesDropdown from "@/components/HiddenFilesDropdown"
|
|
16
9
|
|
|
17
10
|
interface Directory {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const fuzzyMatch = (
|
|
2
|
+
pattern: string,
|
|
3
|
+
text: string,
|
|
4
|
+
): { score: number; matches: number[] } => {
|
|
5
|
+
if (!pattern) return { score: 0, matches: [] }
|
|
6
|
+
|
|
7
|
+
const normalizePattern = (pat: string) => {
|
|
8
|
+
return pat.toLowerCase().split(" ").join("")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const patternLower = normalizePattern(pattern)
|
|
12
|
+
const textLower = text.toLowerCase()
|
|
13
|
+
const matches: number[] = []
|
|
14
|
+
|
|
15
|
+
let patternIdx = 0
|
|
16
|
+
let score = 0
|
|
17
|
+
let consecutiveMatches = 0
|
|
18
|
+
let spaceBonus = 0
|
|
19
|
+
|
|
20
|
+
const spaceSegments = pattern.toLowerCase().trim().split(/\s+/)
|
|
21
|
+
const isSpaceSeparated = spaceSegments.length > 1
|
|
22
|
+
|
|
23
|
+
if (isSpaceSeparated) {
|
|
24
|
+
let segmentIdx = 0
|
|
25
|
+
let currentSegment = spaceSegments[0]
|
|
26
|
+
let segmentCharIdx = 0
|
|
27
|
+
|
|
28
|
+
for (
|
|
29
|
+
let i = 0;
|
|
30
|
+
i < textLower.length && segmentIdx < spaceSegments.length;
|
|
31
|
+
i++
|
|
32
|
+
) {
|
|
33
|
+
const char = textLower[i]
|
|
34
|
+
const targetChar = currentSegment[segmentCharIdx]
|
|
35
|
+
|
|
36
|
+
if (char === targetChar) {
|
|
37
|
+
matches.push(i)
|
|
38
|
+
segmentCharIdx++
|
|
39
|
+
consecutiveMatches++
|
|
40
|
+
score += 1 + consecutiveMatches * 0.5
|
|
41
|
+
|
|
42
|
+
if (i === 0 || /[/\-_.]/.test(text[i - 1])) {
|
|
43
|
+
score += 2
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (segmentCharIdx >= currentSegment.length) {
|
|
47
|
+
segmentIdx++
|
|
48
|
+
spaceBonus += 3
|
|
49
|
+
|
|
50
|
+
if (segmentIdx < spaceSegments.length) {
|
|
51
|
+
currentSegment = spaceSegments[segmentIdx]
|
|
52
|
+
segmentCharIdx = 0
|
|
53
|
+
consecutiveMatches = 0
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
if (
|
|
58
|
+
segmentCharIdx > 0 &&
|
|
59
|
+
/[/\-_.]/.test(char) &&
|
|
60
|
+
segmentIdx < spaceSegments.length - 1
|
|
61
|
+
) {
|
|
62
|
+
segmentIdx++
|
|
63
|
+
if (segmentIdx < spaceSegments.length) {
|
|
64
|
+
currentSegment = spaceSegments[segmentIdx]
|
|
65
|
+
segmentCharIdx = 0
|
|
66
|
+
consecutiveMatches = 0
|
|
67
|
+
if (char === currentSegment[0]) {
|
|
68
|
+
matches.push(i)
|
|
69
|
+
segmentCharIdx = 1
|
|
70
|
+
score += 2
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
consecutiveMatches = 0
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
segmentIdx < spaceSegments.length ||
|
|
81
|
+
(segmentIdx === spaceSegments.length - 1 &&
|
|
82
|
+
segmentCharIdx < currentSegment.length)
|
|
83
|
+
) {
|
|
84
|
+
return { score: -1, matches: [] }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
score += spaceBonus
|
|
88
|
+
} else {
|
|
89
|
+
for (
|
|
90
|
+
let i = 0;
|
|
91
|
+
i < textLower.length && patternIdx < patternLower.length;
|
|
92
|
+
i++
|
|
93
|
+
) {
|
|
94
|
+
if (textLower[i] === patternLower[patternIdx]) {
|
|
95
|
+
matches.push(i)
|
|
96
|
+
patternIdx++
|
|
97
|
+
consecutiveMatches++
|
|
98
|
+
|
|
99
|
+
score += 1 + consecutiveMatches * 0.5
|
|
100
|
+
|
|
101
|
+
if (i === 0 || /[/\-_.]/.test(text[i - 1])) {
|
|
102
|
+
score += 2
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
consecutiveMatches = 0
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (patternIdx !== patternLower.length) return { score: -1, matches: [] }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
score += Math.max(0, 100 - text.length) * 0.1
|
|
113
|
+
|
|
114
|
+
const fileName = text.split("/").pop() || text
|
|
115
|
+
const queryFileName = pattern.replace(/\s+/g, "")
|
|
116
|
+
if (fileName.toLowerCase().includes(queryFileName.toLowerCase())) {
|
|
117
|
+
score += 5
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { score, matches }
|
|
121
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const importanceMap = {
|
|
2
2
|
"readme.md": 200,
|
|
3
|
+
readme: 200,
|
|
3
4
|
license: 100,
|
|
4
5
|
"license.md": 100,
|
|
5
6
|
"index.ts": 90,
|
|
@@ -7,7 +8,23 @@ const importanceMap = {
|
|
|
7
8
|
"circuit.tsx": 90,
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Determines if a file is considered "important" for display in the
|
|
13
|
+
* `ImportantFilesView` component.
|
|
14
|
+
*
|
|
15
|
+
* A file is deemed important if it resides in the root directory of the package
|
|
16
|
+
* and has a positive importance score. Nested paths are not considered important.
|
|
17
|
+
*/
|
|
18
|
+
export const isPackageFileImportant = (filePath: string): boolean => {
|
|
19
|
+
const normalized = filePath.replace(/^\.\/?/, "").toLowerCase()
|
|
20
|
+
if (normalized.split("/").length > 1) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
return scorePackageFileImportance(filePath) > 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Kept for backward compatibility with older imports
|
|
27
|
+
export const scorePackageFileImportance = (filePath: string): number => {
|
|
11
28
|
const lowerCaseFilePath = filePath.toLowerCase()
|
|
12
29
|
for (const [key, value] of Object.entries(importanceMap)) {
|
|
13
30
|
if (lowerCaseFilePath.endsWith(key)) {
|
|
@@ -16,7 +33,3 @@ export const scorePackageFileImportance = (filePath: string) => {
|
|
|
16
33
|
}
|
|
17
34
|
return 0
|
|
18
35
|
}
|
|
19
|
-
|
|
20
|
-
export const isPackageFileImportant = (filePath: string) => {
|
|
21
|
-
return scorePackageFileImportance(filePath) > 0
|
|
22
|
-
}
|
|
@@ -33,7 +33,7 @@ export const ConfirmDeletePackageDialog = ({
|
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
35
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
36
|
-
<DialogContent>
|
|
36
|
+
<DialogContent className="w-[90vw]">
|
|
37
37
|
<DialogHeader>
|
|
38
38
|
<DialogTitle>Confirm Delete Package</DialogTitle>
|
|
39
39
|
</DialogHeader>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"
|
|
2
|
+
import { Button } from "../ui/button"
|
|
3
|
+
import { createUseDialog } from "./create-use-dialog"
|
|
4
|
+
import { useState } from "react"
|
|
5
|
+
|
|
6
|
+
export const ConfirmDiscardChangesDialog = ({
|
|
7
|
+
open,
|
|
8
|
+
onOpenChange,
|
|
9
|
+
onConfirm,
|
|
10
|
+
}: {
|
|
11
|
+
open: boolean
|
|
12
|
+
onOpenChange: (open: boolean) => void
|
|
13
|
+
onConfirm: () => void
|
|
14
|
+
}) => {
|
|
15
|
+
const [isConfirming, setIsConfirming] = useState(false)
|
|
16
|
+
|
|
17
|
+
const handleDiscard = () => {
|
|
18
|
+
onConfirm()
|
|
19
|
+
onOpenChange(false)
|
|
20
|
+
setIsConfirming(false)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const handleCancel = () => {
|
|
24
|
+
onOpenChange(false)
|
|
25
|
+
setIsConfirming(false)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Dialog
|
|
30
|
+
open={open}
|
|
31
|
+
onOpenChange={(open) => {
|
|
32
|
+
onOpenChange(open)
|
|
33
|
+
if (!open) setIsConfirming(false)
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<DialogContent className="w-[90vw]">
|
|
37
|
+
<DialogHeader>
|
|
38
|
+
<DialogTitle>Discard Changes</DialogTitle>
|
|
39
|
+
</DialogHeader>
|
|
40
|
+
<p>Are you sure you want to discard all unsaved changes?</p>
|
|
41
|
+
<p className="text-red-600 font-medium">
|
|
42
|
+
This action cannot be undone.
|
|
43
|
+
</p>
|
|
44
|
+
<div className="flex justify-end space-x-2 mt-4">
|
|
45
|
+
<Button variant="outline" onClick={handleCancel}>
|
|
46
|
+
Cancel
|
|
47
|
+
</Button>
|
|
48
|
+
{!isConfirming ? (
|
|
49
|
+
<Button
|
|
50
|
+
variant="destructive"
|
|
51
|
+
className="bg-red-600 hover:bg-red-700"
|
|
52
|
+
onClick={() => setIsConfirming(true)}
|
|
53
|
+
>
|
|
54
|
+
Discard Changes
|
|
55
|
+
</Button>
|
|
56
|
+
) : (
|
|
57
|
+
<Button
|
|
58
|
+
variant="destructive"
|
|
59
|
+
onClick={handleDiscard}
|
|
60
|
+
className="bg-red-600 hover:bg-red-700"
|
|
61
|
+
>
|
|
62
|
+
Yes, Discard All Changes
|
|
63
|
+
</Button>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
</DialogContent>
|
|
67
|
+
</Dialog>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const useConfirmDiscardChangesDialog = createUseDialog(
|
|
72
|
+
ConfirmDiscardChangesDialog,
|
|
73
|
+
)
|
|
@@ -207,7 +207,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
207
207
|
return (
|
|
208
208
|
<div>
|
|
209
209
|
<Dialog open={showConfirmDelete} onOpenChange={setShowConfirmDelete}>
|
|
210
|
-
<DialogContent className="
|
|
210
|
+
<DialogContent className="w-[90vw] p-6 rounded-2xl shadow-lg">
|
|
211
211
|
<DialogHeader>
|
|
212
212
|
<DialogTitle className="text-left">Confirm Deletion</DialogTitle>
|
|
213
213
|
<DialogDescription className="text-left">
|
|
@@ -255,7 +255,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
255
255
|
onChange={(e) =>
|
|
256
256
|
setFormData((prev) => ({
|
|
257
257
|
...prev,
|
|
258
|
-
unscopedPackageName: e.target.value,
|
|
258
|
+
unscopedPackageName: e.target.value.replace(/\s+/g, ""),
|
|
259
259
|
}))
|
|
260
260
|
}
|
|
261
261
|
placeholder="Enter package name"
|