@tscircuit/fake-snippets 0.0.67 → 0.0.69

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.
Files changed (40) hide show
  1. package/bun-tests/fake-snippets-api/routes/packages/update.test.ts +104 -0
  2. package/bun.lock +26 -83
  3. package/dist/bundle.js +17 -5
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +2 -1
  6. package/dist/schema.d.ts +8 -0
  7. package/dist/schema.js +2 -1
  8. package/fake-snippets-api/lib/db/schema.ts +4 -0
  9. package/fake-snippets-api/routes/api/packages/create.ts +1 -0
  10. package/fake-snippets-api/routes/api/packages/update.ts +11 -2
  11. package/package.json +3 -4
  12. package/src/App.tsx +0 -4
  13. package/src/components/CmdKMenu.tsx +19 -19
  14. package/src/components/FAQ.tsx +3 -1
  15. package/src/components/FileSidebar.tsx +50 -1
  16. package/src/components/Header2.tsx +20 -9
  17. package/src/components/JLCPCBImportDialog.tsx +13 -16
  18. package/src/components/ViewPackagePage/components/important-files-view.tsx +1 -1
  19. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
  20. package/src/components/ViewPackagePage/components/package-header.tsx +22 -12
  21. package/src/components/ViewPackagePage/components/repo-page-content.tsx +23 -7
  22. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +1 -0
  23. package/src/components/dialogs/confirm-delete-package-dialog.tsx +8 -0
  24. package/src/components/dialogs/edit-package-details-dialog.tsx +177 -139
  25. package/src/components/package-port/CodeAndPreview.tsx +40 -19
  26. package/src/components/package-port/CodeEditor.tsx +8 -27
  27. package/src/components/package-port/EditorNav.tsx +1 -11
  28. package/src/hooks/use-package-details-form.ts +15 -1
  29. package/src/hooks/useFileManagement.ts +59 -0
  30. package/src/index.css +13 -0
  31. package/src/lib/utils/isValidFileName.ts +5 -0
  32. package/src/pages/dashboard.tsx +1 -0
  33. package/src/pages/quickstart.tsx +5 -5
  34. package/src/pages/search.tsx +1 -1
  35. package/src/pages/user-profile.tsx +1 -0
  36. package/src/components/OrderPreviewContent.tsx +0 -61
  37. package/src/components/ViewSnippetSidebar.tsx +0 -162
  38. package/src/components/dialogs/create-order-dialog.tsx +0 -146
  39. package/src/pages/preview.tsx +0 -44
  40. package/src/pages/view-order.tsx +0 -111
@@ -19,6 +19,7 @@ import PackageHeader from "./package-header"
19
19
  import { useGlobalStore } from "@/hooks/use-global-store"
20
20
  import { useLocation } from "wouter"
21
21
  import { Package } from "fake-snippets-api/lib/db/schema"
22
+ import { useCurrentPackageCircuitJson } from "../hooks/use-current-package-circuit-json"
22
23
 
23
24
  interface PackageFile {
24
25
  package_file_id: string
@@ -43,22 +44,37 @@ export default function RepoPageContent({
43
44
  onFileClicked,
44
45
  onEditClicked,
45
46
  }: RepoPageContentProps) {
46
- const [location, setLocation] = useLocation()
47
47
  const [activeView, setActiveView] = useState<string>("files")
48
48
  const session = useGlobalStore((s) => s.session)
49
+ const { circuitJson, isLoading: isCircuitJsonLoading } =
50
+ useCurrentPackageCircuitJson()
49
51
 
50
- // Handle hash-based view selection
52
+ // Handle initial view selection and hash-based view changes
51
53
  useEffect(() => {
52
- // Get the hash without the # character
54
+ if (isCircuitJsonLoading) return
53
55
  const hash = window.location.hash.slice(1)
54
- // Valid views
55
56
  const validViews = ["files", "3d", "pcb", "schematic", "bom"]
57
+ const circuitDependentViews = ["3d", "pcb", "schematic", "bom"]
56
58
 
57
- // If hash is a valid view, set it as active
58
- if (validViews.includes(hash)) {
59
+ const availableViews = circuitJson
60
+ ? validViews
61
+ : validViews.filter((view) => !circuitDependentViews.includes(view))
62
+
63
+ if (hash && availableViews.includes(hash)) {
59
64
  setActiveView(hash)
65
+ } else if (
66
+ packageInfo?.default_view &&
67
+ availableViews.includes(packageInfo.default_view)
68
+ ) {
69
+ setActiveView(packageInfo.default_view)
70
+ window.location.hash = packageInfo.default_view
71
+ } else {
72
+ setActiveView("files")
73
+ if (!hash || !availableViews.includes(hash)) {
74
+ window.location.hash = "files"
75
+ }
60
76
  }
61
- }, [])
77
+ }, [packageInfo?.default_view, circuitJson, isCircuitJsonLoading])
62
78
 
63
79
  const importantFilePaths = packageFiles
64
80
  ?.filter((pf) => isPackageFileImportant(pf.file_path))
@@ -172,6 +172,7 @@ export default function SidebarAboutSection() {
172
172
  packageAuthor={packageInfo.owner_github_username}
173
173
  onUpdate={handlePackageUpdate}
174
174
  packageName={packageInfo.name}
175
+ currentDefaultView={packageInfo.default_view}
175
176
  />
176
177
  )}
177
178
  </>
@@ -2,24 +2,32 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"
2
2
  import { Button } from "../ui/button"
3
3
  import { createUseDialog } from "./create-use-dialog"
4
4
  import { useDeletePackage } from "@/hooks/use-delete-package"
5
+ import { useQueryClient } from "react-query"
5
6
 
6
7
  export const ConfirmDeletePackageDialog = ({
7
8
  open,
8
9
  onOpenChange,
9
10
  packageId,
10
11
  packageName,
12
+ packageOwner,
11
13
  refetchUserPackages,
12
14
  }: {
13
15
  open: boolean
14
16
  onOpenChange: (open: boolean) => void
15
17
  packageId: string
16
18
  packageName: string
19
+ packageOwner: string
17
20
  refetchUserPackages?: () => void
18
21
  }) => {
22
+ const queryClient = useQueryClient()
23
+
19
24
  const { mutate: deletePackage, isLoading } = useDeletePackage({
20
25
  onSuccess: () => {
21
26
  onOpenChange(false)
22
27
  refetchUserPackages?.()
28
+ queryClient.invalidateQueries({
29
+ queryKey: ["userPackages", packageOwner],
30
+ })
23
31
  },
24
32
  })
25
33
 
@@ -16,6 +16,7 @@ import {
16
16
  Dialog,
17
17
  DialogContent,
18
18
  DialogDescription,
19
+ DialogFooter,
19
20
  DialogHeader,
20
21
  DialogTitle,
21
22
  } from "../ui/dialog"
@@ -34,6 +35,7 @@ interface EditPackageDetailsDialogProps {
34
35
  currentDescription: string
35
36
  currentWebsite: string
36
37
  currentLicense?: string | null
38
+ currentDefaultView?: string
37
39
  isPrivate?: boolean
38
40
  packageName: string
39
41
  packageReleaseId: string | null
@@ -42,6 +44,7 @@ interface EditPackageDetailsDialogProps {
42
44
  newDescription: string,
43
45
  newWebsite: string,
44
46
  newLicense: string | null,
47
+ newDefaultView: string,
45
48
  ) => void
46
49
  }
47
50
 
@@ -52,6 +55,7 @@ export const EditPackageDetailsDialog = ({
52
55
  currentDescription,
53
56
  currentWebsite,
54
57
  currentLicense,
58
+ currentDefaultView = "files",
55
59
  isPrivate = false,
56
60
  packageName,
57
61
  packageReleaseId,
@@ -67,12 +71,14 @@ export const EditPackageDetailsDialog = ({
67
71
  setFormData,
68
72
  websiteError,
69
73
  hasLicenseChanged,
74
+ hasDefaultViewChanged,
70
75
  hasChanges,
71
76
  isFormValid,
72
77
  } = usePackageDetailsForm({
73
78
  initialDescription: currentDescription,
74
79
  initialWebsite: currentWebsite,
75
80
  initialLicense: currentLicense || null,
81
+ initialDefaultView: currentDefaultView,
76
82
  isDialogOpen: open,
77
83
  initialVisibility: isPrivate ? "private" : "public",
78
84
  })
@@ -107,6 +113,7 @@ export const EditPackageDetailsDialog = ({
107
113
  description: formData.description,
108
114
  website: formData.website,
109
115
  is_private: formData.visibility == "private",
116
+ default_view: formData.defaultView,
110
117
  })
111
118
  const privacyUpdateResponse = await axios.post("/snippets/update", {
112
119
  snippet_id: packageId,
@@ -148,6 +155,7 @@ export const EditPackageDetailsDialog = ({
148
155
  website: formData.website,
149
156
  license: formData.license,
150
157
  visibility: formData.visibility,
158
+ defaultView: formData.defaultView,
151
159
  }
152
160
  },
153
161
  onMutate: async () => {
@@ -159,11 +167,12 @@ export const EditPackageDetailsDialog = ({
159
167
  website: formData.website,
160
168
  license: formData.license,
161
169
  is_private: formData.visibility == "private",
170
+ default_view: formData.defaultView,
162
171
  }))
163
172
  return { previous }
164
173
  },
165
174
  onSuccess: (data) => {
166
- onUpdate?.(data.description, data.website, data.license)
175
+ onUpdate?.(data.description, data.website, data.license, data.defaultView)
167
176
  onOpenChange(false)
168
177
  qc.invalidateQueries([
169
178
  "packageFile",
@@ -217,151 +226,180 @@ export const EditPackageDetailsDialog = ({
217
226
  </DialogContent>
218
227
  </Dialog>
219
228
  <Dialog open={open !== showConfirmDelete} onOpenChange={onOpenChange}>
220
- <DialogContent className="sm:max-w-[500px] lg:h-[70vh] sm:h-[90vh] overflow-y-auto w-[95vw] p-6 gap-6 rounded-2xl shadow-lg">
221
- <DialogHeader>
222
- <DialogTitle>Edit Package Details</DialogTitle>
223
- <DialogDescription>
224
- Update your package’s description, website, visibility, or delete
225
- it.
226
- </DialogDescription>
227
- </DialogHeader>
229
+ <DialogContent className="sm:max-w-[500px] lg:h-[85vh] sm:h-[90vh] overflow-y-auto no-scrollbar w-[95vw] h-[80vh] p-6 gap-6 rounded-2xl shadow-lg">
230
+ <div className="flex flex-col gap-10">
231
+ <DialogHeader>
232
+ <DialogTitle>Edit Package Details</DialogTitle>
233
+ <DialogDescription>
234
+ Update your package's description, website, visibility, or
235
+ delete it.
236
+ </DialogDescription>
237
+ </DialogHeader>
238
+ <div className="">
239
+ <div className="grid gap-2">
240
+ <div className="space-y-1">
241
+ <Label htmlFor="website">Website</Label>
242
+ <Input
243
+ id="website"
244
+ value={formData.website}
245
+ onChange={(e) =>
246
+ setFormData((prev) => ({
247
+ ...prev,
248
+ website: e.target.value,
249
+ }))
250
+ }
251
+ placeholder="https://example.com"
252
+ disabled={updatePackageDetailsMutation.isLoading}
253
+ className="w-full"
254
+ aria-invalid={!!websiteError}
255
+ />
256
+ {websiteError && (
257
+ <p className="text-sm text-red-500">{websiteError}</p>
258
+ )}
259
+ </div>
260
+ <div className="space-y-1">
261
+ <Label htmlFor="visibility">Visibility</Label>
262
+ <Select
263
+ value={formData.visibility}
264
+ onValueChange={(val) => {
265
+ setFormData((prev) => ({
266
+ ...prev,
267
+ visibility: val,
268
+ }))
269
+ }}
270
+ disabled={updatePackageDetailsMutation.isLoading}
271
+ >
272
+ <SelectTrigger className="w-full">
273
+ <SelectValue placeholder="Select visibility" />
274
+ </SelectTrigger>
275
+ <SelectContent className="!z-[999]">
276
+ <SelectItem value="public">public</SelectItem>
277
+ <SelectItem value="private">private</SelectItem>
278
+ </SelectContent>
279
+ </Select>
280
+ </div>
281
+ <div className="space-y-1">
282
+ <Label htmlFor="description">Description</Label>
283
+ <Textarea
284
+ id="description"
285
+ value={formData.description}
286
+ onChange={(e) =>
287
+ setFormData((prev) => ({
288
+ ...prev,
289
+ description: e.target.value,
290
+ }))
291
+ }
292
+ placeholder="Enter package description"
293
+ disabled={updatePackageDetailsMutation.isLoading}
294
+ className="w-full min-h-[80px] resize-none"
295
+ />
296
+ </div>
297
+ <div className="space-y-1">
298
+ <Label htmlFor="license">License</Label>
299
+ <Select
300
+ value={formData.license || "unset"}
301
+ onValueChange={(value) =>
302
+ setFormData((prev) => ({
303
+ ...prev,
304
+ license: value === "unset" ? null : value,
305
+ }))
306
+ }
307
+ disabled={updatePackageDetailsMutation.isLoading}
308
+ >
309
+ <SelectTrigger className="w-full">
310
+ <SelectValue placeholder="Select a license" />
311
+ </SelectTrigger>
312
+ <SelectContent className="!z-[999]">
313
+ <SelectItem value="MIT">MIT</SelectItem>
314
+ <SelectItem value="Apache-2.0">Apache-2.0</SelectItem>
315
+ <SelectItem value="BSD-3-Clause">BSD-3-Clause</SelectItem>
316
+ <SelectItem value="GPL-3.0">GPL-3.0</SelectItem>
317
+ <SelectItem value="unset">Unset</SelectItem>
318
+ </SelectContent>
319
+ </Select>
320
+ </div>
321
+ <div className="space-y-1">
322
+ <Label htmlFor="defaultView">Default View</Label>
323
+ <Select
324
+ value={formData.defaultView}
325
+ onValueChange={(value) =>
326
+ setFormData((prev) => ({
327
+ ...prev,
328
+ defaultView: value,
329
+ }))
330
+ }
331
+ disabled={updatePackageDetailsMutation.isLoading}
332
+ >
333
+ <SelectTrigger className="w-full">
334
+ <SelectValue placeholder="Select default view" />
335
+ </SelectTrigger>
336
+ <SelectContent className="!z-[999]">
337
+ <SelectItem value="files">Files</SelectItem>
338
+ <SelectItem value="3d">3D</SelectItem>
339
+ <SelectItem value="pcb">PCB</SelectItem>
340
+ <SelectItem value="schematic">Schematic</SelectItem>
341
+ </SelectContent>
342
+ </Select>
343
+ </div>
344
+ </div>
228
345
 
229
- <div className="grid gap-4">
230
- <div className="space-y-1">
231
- <Label htmlFor="website">Website</Label>
232
- <Input
233
- id="website"
234
- value={formData.website}
235
- onChange={(e) =>
236
- setFormData((prev) => ({
237
- ...prev,
238
- website: e.target.value,
239
- }))
240
- }
241
- placeholder="https://example.com"
242
- disabled={updatePackageDetailsMutation.isLoading}
243
- className="w-full"
244
- aria-invalid={!!websiteError}
245
- />
246
- {websiteError && (
247
- <p className="text-sm text-red-500">{websiteError}</p>
248
- )}
249
- </div>
250
- <div className="space-y-1">
251
- <Label htmlFor="visibility">Visibility</Label>
252
- <Select
253
- value={formData.visibility}
254
- onValueChange={(val) => {
255
- setFormData((prev) => ({
256
- ...prev,
257
- visibility: val,
258
- }))
259
- }}
260
- disabled={updatePackageDetailsMutation.isLoading}
346
+ <details
347
+ className="mt-2 rounded-md"
348
+ onToggle={(e) => setDangerOpen(e.currentTarget.open)}
261
349
  >
262
- <SelectTrigger className="w-full">
263
- <SelectValue placeholder="Select visibility" />
264
- </SelectTrigger>
265
- <SelectContent className="!z-[999]">
266
- <SelectItem value="public">public</SelectItem>
267
- <SelectItem value="private">private</SelectItem>
268
- </SelectContent>
269
- </Select>
350
+ <summary className="cursor-pointer p-2 font-medium text-sm text-black list-none flex justify-between items-center">
351
+ Danger Zone
352
+ <ChevronDown
353
+ className={`w-4 h-4 mr-1 transition-transform ${dangerOpen ? "rotate-180" : ""}`}
354
+ />
355
+ </summary>
356
+ <div className="p-2 pr-2">
357
+ <div className="flex justify-between items-center">
358
+ <div>
359
+ <p className="text-sm text-muted-foreground">
360
+ Once deleted, it cannot be recovered.
361
+ </p>
362
+ </div>
363
+ <Button
364
+ variant="destructive"
365
+ size="default"
366
+ onClick={() => setShowConfirmDelete(true)}
367
+ disabled={deleting}
368
+ className="shrink-0 lg:w-[115px] w-[70px]"
369
+ >
370
+ {deleting ? "Deleting..." : "Delete"}
371
+ </Button>
372
+ </div>
373
+ </div>
374
+ </details>
270
375
  </div>
271
- <div className="space-y-1">
272
- <Label htmlFor="description">Description</Label>
273
- <Textarea
274
- id="description"
275
- value={formData.description}
276
- onChange={(e) =>
277
- setFormData((prev) => ({
278
- ...prev,
279
- description: e.target.value,
280
- }))
281
- }
282
- placeholder="Enter package description"
376
+ </div>
377
+
378
+ <DialogFooter className="mt-auto">
379
+ <div className="lg:px-2 flex flex-col sm:flex-row justify-end gap-2">
380
+ <Button
381
+ variant="outline"
382
+ onClick={() => onOpenChange(false)}
283
383
  disabled={updatePackageDetailsMutation.isLoading}
284
- className="w-full min-h-[100px] resize-none"
285
- />
286
- </div>
287
- <div className="space-y-1">
288
- <Label htmlFor="license">License</Label>
289
- <Select
290
- value={formData.license || "unset"}
291
- onValueChange={(value) =>
292
- setFormData((prev) => ({
293
- ...prev,
294
- license: value === "unset" ? null : value,
295
- }))
384
+ className="sm:w-auto w-full"
385
+ >
386
+ Cancel
387
+ </Button>
388
+ <Button
389
+ onClick={() => updatePackageDetailsMutation.mutate()}
390
+ disabled={
391
+ updatePackageDetailsMutation.isLoading ||
392
+ !hasChanges ||
393
+ !isFormValid
296
394
  }
297
- disabled={updatePackageDetailsMutation.isLoading}
395
+ className="sm:w-auto lg:w-[115px]"
298
396
  >
299
- <SelectTrigger className="w-full">
300
- <SelectValue placeholder="Select a license" />
301
- </SelectTrigger>
302
- <SelectContent className="!z-[999]">
303
- <SelectItem value="MIT">MIT</SelectItem>
304
- <SelectItem value="Apache-2.0">Apache-2.0</SelectItem>
305
- <SelectItem value="BSD-3-Clause">BSD-3-Clause</SelectItem>
306
- <SelectItem value="GPL-3.0">GPL-3.0</SelectItem>
307
- <SelectItem value="unset">Unset</SelectItem>
308
- </SelectContent>
309
- </Select>
397
+ {updatePackageDetailsMutation.isLoading
398
+ ? "Updating..."
399
+ : "Save Changes"}
400
+ </Button>
310
401
  </div>
311
- </div>
312
- <details
313
- className="mt-2 rounded-md"
314
- onToggle={(e) => setDangerOpen(e.currentTarget.open)}
315
- >
316
- <summary className="cursor-pointer p-2 font-medium text-sm text-black list-none flex justify-between items-center">
317
- Danger Zone
318
- <ChevronDown
319
- className={`w-4 h-4 mr-1 transition-transform ${dangerOpen ? "rotate-180" : ""}`}
320
- />
321
- </summary>
322
- <div className="p-2 pr-2">
323
- <div className="flex justify-between items-center">
324
- <div>
325
- <p className="text-sm text-muted-foreground">
326
- Once deleted, it cannot be recovered.
327
- </p>
328
- </div>
329
- <Button
330
- variant="destructive"
331
- size="default"
332
- onClick={() => setShowConfirmDelete(true)}
333
- disabled={deleting}
334
- className="shrink-0 lg:w-[115px] w-[70px]"
335
- >
336
- {deleting ? "Deleting..." : "Delete"}
337
- </Button>
338
- </div>
339
- </div>
340
- </details>
341
-
342
- <div className=" lg:px-2 flex flex-col sm:flex-row justify-end gap-3">
343
- <Button
344
- variant="outline"
345
- onClick={() => onOpenChange(false)}
346
- disabled={updatePackageDetailsMutation.isLoading}
347
- className="sm:w-auto w-full"
348
- >
349
- Cancel
350
- </Button>
351
- <Button
352
- onClick={() => updatePackageDetailsMutation.mutate()}
353
- disabled={
354
- updatePackageDetailsMutation.isLoading ||
355
- !hasChanges ||
356
- !isFormValid
357
- }
358
- className="sm:w-auto lg:w-[115px]"
359
- >
360
- {updatePackageDetailsMutation.isLoading
361
- ? "Updating..."
362
- : "Save Changes"}
363
- </Button>
364
- </div>
402
+ </DialogFooter>
365
403
  </DialogContent>
366
404
  </Dialog>
367
405
  </div>
@@ -22,6 +22,8 @@ import { usePackageFilesLoader } from "@/hooks/usePackageFilesLoader"
22
22
  import { findTargetFile } from "@/lib/utils/findTargetFile"
23
23
  import { toastManualEditConflicts } from "@/lib/utils/toastManualEditConflicts"
24
24
  import { ManualEditEvent } from "@tscircuit/props"
25
+ import { isValidFileName } from "@/lib/utils/isValidFileName"
26
+ import { useFileManagement } from "@/hooks/useFileManagement"
25
27
 
26
28
  interface Props {
27
29
  pkg?: Package
@@ -32,12 +34,19 @@ export interface PackageFile {
32
34
  content: string
33
35
  }
34
36
 
35
- interface CodeAndPreviewState {
37
+ export interface CreateFileProps {
38
+ newFileName: string
39
+ setErrorMessage: (message: string) => void
40
+ onFileSelect: (fileName: string) => void
41
+ setNewFileName: (fileName: string) => void
42
+ setIsCreatingFile: (isCreatingFile: boolean) => void
43
+ }
44
+
45
+ export interface CodeAndPreviewState {
36
46
  pkgFilesWithContent: PackageFile[]
37
47
  initialFilesLoad: PackageFile[]
38
48
  showPreview: boolean
39
49
  fullScreen: boolean
40
- dts: string
41
50
  lastSavedAt: number
42
51
  circuitJson: null | any
43
52
  isPrivate: boolean
@@ -92,7 +101,6 @@ export function CodeAndPreview({ pkg }: Props) {
92
101
  initialFilesLoad: [],
93
102
  showPreview: true,
94
103
  fullScreen: false,
95
- dts: "",
96
104
  lastSavedAt: Date.now(),
97
105
  circuitJson: null,
98
106
  isPrivate: false,
@@ -148,7 +156,6 @@ export function CodeAndPreview({ pkg }: Props) {
148
156
 
149
157
  if (loadedFiles && !isLoadingFiles) {
150
158
  const processedResults = [...loadedFiles]
151
-
152
159
  setState((prev) => ({
153
160
  ...prev,
154
161
  pkgFilesWithContent: processedResults,
@@ -159,13 +166,7 @@ export function CodeAndPreview({ pkg }: Props) {
159
166
  defaultCode,
160
167
  }))
161
168
  }
162
- }, [
163
- isLoadingFiles,
164
- pkg,
165
- pkgFiles.data,
166
- state.pkgFilesWithContent.length,
167
- defaultCode,
168
- ])
169
+ }, [isLoadingFiles, pkg, pkgFiles.data, defaultCode])
169
170
 
170
171
  const createPackageMutation = useCreatePackageMutation()
171
172
  const { mutate: createRelease } = useCreatePackageReleaseMutation({
@@ -250,10 +251,21 @@ export function CodeAndPreview({ pkg }: Props) {
250
251
  setState((prev) => ({ ...prev, lastSavedAt: Date.now() }))
251
252
 
252
253
  if (pkg) {
253
- updatePackageFilesMutation.mutate({
254
- package_name_with_version: `${pkg.name}@latest`,
255
- ...pkg,
256
- })
254
+ updatePackageFilesMutation.mutate(
255
+ {
256
+ package_name_with_version: `${pkg.name}@latest`,
257
+ ...pkg,
258
+ },
259
+ {
260
+ onSuccess: () => {
261
+ setState((prev) => ({
262
+ ...prev,
263
+ initialFilesLoad: [...prev.pkgFilesWithContent],
264
+ }))
265
+ pkgFiles.refetch()
266
+ },
267
+ },
268
+ )
257
269
  }
258
270
  }
259
271
 
@@ -284,10 +296,17 @@ export function CodeAndPreview({ pkg }: Props) {
284
296
  state.pkgFilesWithContent,
285
297
  ])
286
298
  const mainComponentPath = useMemo(() => {
287
- return state.currentFile?.endsWith(".tsx") &&
299
+ const isReactComponentExported =
300
+ /export function\s+\w+/.test(currentFileCode) ||
301
+ /export const\s+\w+\s*=/.test(currentFileCode) ||
302
+ /export default\s+\w+/.test(currentFileCode) ||
303
+ /export default\s+function\s*(\w*)\s*\(/.test(currentFileCode) ||
304
+ /export default\s*\(\s*\)\s*=>/.test(currentFileCode)
305
+
306
+ return (state.currentFile?.endsWith(".tsx") ||
307
+ state.currentFile?.endsWith(".ts")) &&
288
308
  !!state.pkgFilesWithContent.some((x) => x.path == state.currentFile) &&
289
- (currentFileCode.match(/export function (\w+)/) ||
290
- currentFileCode.match(/export const (\w+) ?=/))
309
+ isReactComponentExported
291
310
  ? state.currentFile
292
311
  : state.defaultComponentFile
293
312
  }, [state.currentFile, state.pkgFilesWithContent, currentFileCode])
@@ -327,6 +346,8 @@ export function CodeAndPreview({ pkg }: Props) {
327
346
  })
328
347
  }
329
348
 
349
+ const { handleCreateFile } = useFileManagement(state, setState)
350
+
330
351
  if ((!pkg && urlParams.package_id) || pkgFiles.isLoading || isLoadingFiles) {
331
352
  return (
332
353
  <div className="flex items-center justify-center h-64">
@@ -363,6 +384,7 @@ export function CodeAndPreview({ pkg }: Props) {
363
384
  )}
364
385
  >
365
386
  <CodeEditor
387
+ handleCreateFile={handleCreateFile}
366
388
  currentFile={state.currentFile}
367
389
  setCurrentFile={(file) =>
368
390
  setState((prev) => ({ ...prev, currentFile: file }))
@@ -379,7 +401,6 @@ export function CodeAndPreview({ pkg }: Props) {
379
401
  ),
380
402
  }))
381
403
  }}
382
- onDtsChange={(dts) => setState((prev) => ({ ...prev, dts }))}
383
404
  pkgFilesLoaded={state.pkgFilesLoaded}
384
405
  />
385
406
  </div>