@tscircuit/fake-snippets 0.0.69 → 0.0.70

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.
@@ -34,6 +34,18 @@ export default withRouteSpec({
34
34
  })
35
35
  }
36
36
 
37
+ if (
38
+ foundPackage.is_private &&
39
+ auth?.github_username !== foundPackage.owner_github_username
40
+ ) {
41
+ return ctx.error(404, {
42
+ error_code: "package_not_found",
43
+ message: `Package not found (searched using ${JSON.stringify(
44
+ req.commonParams,
45
+ )})`,
46
+ })
47
+ }
48
+
37
49
  return ctx.json({
38
50
  ok: true,
39
51
  package: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.69",
3
+ "version": "0.0.70",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -5,6 +5,7 @@ import {
5
5
  DialogHeader,
6
6
  DialogTitle,
7
7
  DialogFooter,
8
+ DialogDescription,
8
9
  } from "@/components/ui/dialog"
9
10
  import { Button } from "@/components/ui/button"
10
11
  import { Textarea } from "@/components/ui/textarea"
@@ -13,6 +14,10 @@ import { useToast } from "@/hooks/use-toast"
13
14
  import { useLocation } from "wouter"
14
15
  import { useGlobalStore } from "@/hooks/use-global-store"
15
16
  import { convertCircuitJsonToTscircuit } from "circuit-json-to-tscircuit"
17
+ import { useCreatePackageMutation } from "@/hooks/use-create-package-mutation"
18
+ import { generateRandomPackageName } from "./package-port/CodeAndPreview"
19
+ import { useCreatePackageReleaseMutation } from "@/hooks/use-create-package-release-mutation"
20
+ import { useCreatePackageFilesMutation } from "@/hooks/use-create-package-files-mutation"
16
21
 
17
22
  interface CircuitJsonImportDialogProps {
18
23
  open: boolean
@@ -36,11 +41,29 @@ export function CircuitJsonImportDialog({
36
41
  const [file, setFile] = useState<File | null>(null)
37
42
  const [isLoading, setIsLoading] = useState(false)
38
43
  const [error, setError] = useState<string | null>(null)
39
- const axios = useAxios()
44
+ const loggedInUser = useGlobalStore((s) => s.session)
40
45
  const { toast } = useToast()
46
+ const axios = useAxios()
41
47
  const [, navigate] = useLocation()
42
48
  const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
43
- const session = useGlobalStore((s) => s.session)
49
+ const createPackageMutation = useCreatePackageMutation()
50
+ const { mutate: createRelease } = useCreatePackageReleaseMutation({
51
+ onSuccess: () => {
52
+ toast({
53
+ title: "Package released",
54
+ description: "Your package has been released successfully.",
55
+ })
56
+ },
57
+ })
58
+
59
+ const createPackageFilesMutation = useCreatePackageFilesMutation({
60
+ onSuccess: () => {
61
+ toast({
62
+ title: "Package files created",
63
+ description: "Your package files have been created successfully.",
64
+ })
65
+ },
66
+ })
44
67
 
45
68
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
46
69
  const selectedFile = e.target.files?.[0]
@@ -54,80 +77,117 @@ export function CircuitJsonImportDialog({
54
77
  const handleImport = async () => {
55
78
  let importedCircuitJson
56
79
 
57
- if (file) {
80
+ const parseJson = async (jsonString: string) => {
58
81
  try {
59
- const fileText = await file.text()
60
- importedCircuitJson = JSON.parse(fileText)
61
- } catch (err) {
62
- setError("Error reading JSON file. Please ensure it is valid.")
63
- return
82
+ return JSON.parse(jsonString)
83
+ } catch {
84
+ throw new Error("Invalid JSON format.")
64
85
  }
65
- } else if (isValidJSON(circuitJson)) {
66
- setIsLoading(true)
67
- setError(null)
68
- importedCircuitJson = JSON.parse(circuitJson)
69
- } else {
70
- toast({
71
- title: "Invalid Input",
72
- description: "Please provide a valid JSON content or file.",
73
- variant: "destructive",
74
- })
75
- return
76
86
  }
77
- let tscircuit
78
- try {
79
- tscircuit = convertCircuitJsonToTscircuit(importedCircuitJson as any, {
80
- componentName: "circuit",
81
- })
82
- console.info(tscircuit)
83
- } catch {
87
+
88
+ const handleError = (message: string) => {
84
89
  toast({
85
90
  title: "Import Failed",
86
- description: "Invalid Circuit JSON was provided.",
91
+ description: message,
87
92
  variant: "destructive",
88
93
  })
89
94
  setIsLoading(false)
90
- return
95
+ }
96
+
97
+ const handleSuccess = (message: string) => {
98
+ toast({
99
+ title: "Success",
100
+ description: message,
101
+ })
91
102
  }
92
103
 
93
104
  try {
94
- const newSnippetData = {
95
- snippet_type: importedCircuitJson.type ?? "board",
96
- circuit_json: importedCircuitJson,
97
- code: tscircuit,
98
- }
99
- const response = await axios
100
- .post("/snippets/create", newSnippetData)
101
- .catch((e) => e)
102
- const { snippet, message } = response.data
103
- if (message) {
104
- setError(message)
105
- setIsLoading(false)
105
+ if (file) {
106
+ const fileText = await file.text()
107
+ importedCircuitJson = await parseJson(fileText)
108
+ } else if (isValidJSON(circuitJson)) {
109
+ setIsLoading(true)
110
+ setError(null)
111
+ importedCircuitJson = await parseJson(circuitJson)
112
+ } else {
113
+ handleError("Please provide a valid JSON content or file.")
106
114
  return
107
115
  }
108
- toast({
109
- title: "Import Successful",
110
- description: "Circuit Json has been imported successfully.",
111
- })
112
- onOpenChange(false)
113
- navigate(`/editor?snippet_id=${snippet.snippet_id}`)
116
+
117
+ const tscircuitComponentContent = convertCircuitJsonToTscircuit(
118
+ importedCircuitJson as any,
119
+ {
120
+ componentName: "circuit",
121
+ },
122
+ )
123
+ console.info(tscircuitComponentContent)
124
+
125
+ await createPackageMutation.mutateAsync(
126
+ {
127
+ name: `${loggedInUser?.github_username}/${generateRandomPackageName()}`,
128
+ description: "Imported from Circuit JSON",
129
+ },
130
+ {
131
+ onSuccess: (newPackage) => {
132
+ handleSuccess("Package has been created successfully.")
133
+ createRelease(
134
+ {
135
+ package_name_with_version: `${newPackage.name}@latest`,
136
+ },
137
+ {
138
+ onSuccess: (release) => {
139
+ createPackageFilesMutation
140
+ .mutateAsync({
141
+ file_path: "index.tsx",
142
+ content_text: tscircuitComponentContent,
143
+ package_release_id: release.package_release_id,
144
+ })
145
+ .then(() => {
146
+ navigate(`/editor?package_id=${newPackage.package_id}`)
147
+ })
148
+ },
149
+ onError: (error) => {
150
+ setError(error)
151
+ handleError("Failed to create package release.")
152
+ },
153
+ },
154
+ )
155
+ },
156
+ onError: (error) => {
157
+ setError(error)
158
+ handleError("Failed to create package.")
159
+ },
160
+ onSettled: () => {
161
+ setIsLoading(false)
162
+ onOpenChange(false)
163
+ },
164
+ },
165
+ )
114
166
  } catch (error) {
115
167
  console.error("Error importing Circuit Json:", error)
116
- toast({
117
- title: "Import Failed",
118
- description: "Failed to import the Circuit Json. Please try again.",
119
- variant: "destructive",
120
- })
168
+ handleError(
169
+ "The Circuit JSON appears to be invalid or malformed. Please check the format and try again.",
170
+ )
121
171
  } finally {
122
172
  setIsLoading(false)
123
173
  }
124
174
  }
125
175
 
176
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
177
+ if (event.ctrlKey && event.key === "Enter") {
178
+ handleImport()
179
+ }
180
+ }
181
+
126
182
  return (
127
183
  <Dialog open={open} onOpenChange={onOpenChange}>
128
184
  <DialogContent>
129
185
  <DialogHeader>
130
186
  <DialogTitle>Import Circuit JSON</DialogTitle>
187
+ <DialogDescription>
188
+ Use this dialog to import a Circuit JSON file or paste the JSON
189
+ content directly.
190
+ </DialogDescription>
131
191
  </DialogHeader>
132
192
  <div className="pb-4">
133
193
  <Textarea
@@ -135,6 +195,7 @@ export function CircuitJsonImportDialog({
135
195
  placeholder="Paste the Circuit JSON."
136
196
  value={circuitJson}
137
197
  onChange={(e) => setcircuitJson(e.target.value)}
198
+ onKeyDown={handleKeyDown}
138
199
  disabled={!!file}
139
200
  />
140
201
  <div className="mt-4 flex flex-col gap-2">
@@ -150,7 +211,7 @@ export function CircuitJsonImportDialog({
150
211
  type="file"
151
212
  accept="application/json"
152
213
  onChange={handleFileChange}
153
- className="hidden" // Hide the default file input
214
+ className="hidden"
154
215
  />
155
216
  <label
156
217
  htmlFor="file-input"
@@ -34,7 +34,7 @@ export function DownloadButtonAndMenu({
34
34
 
35
35
  if (!circuitJson) {
36
36
  return (
37
- <div>
37
+ <div className={className}>
38
38
  <Button
39
39
  disabled
40
40
  variant="ghost"
@@ -67,7 +67,7 @@ export const HeaderLogin = () => {
67
67
  </a>
68
68
  </DropdownMenuItem>
69
69
  <DropdownMenuItem asChild onClick={() => setSession(null)}>
70
- <a href="/sign-out" className="cursor-pointer">
70
+ <a href="/" className="cursor-pointer">
71
71
  Sign out
72
72
  </a>
73
73
  </DropdownMenuItem>
@@ -168,7 +168,7 @@ export default function ImportantFilesView({
168
168
  return (
169
169
  <div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
170
170
  <div className="flex items-center pl-2 pr-4 py-2 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
171
- <div className="flex items-center space-x-2">
171
+ <div className="flex items-center space-x-2 overflow-x-auto no-scrollbar">
172
172
  {/* AI Description Tab */}
173
173
  {hasAiContent && (
174
174
  <button
@@ -154,6 +154,7 @@ export default function RepoPageContent({
154
154
  <Header />
155
155
  <PackageHeader
156
156
  packageInfo={packageInfo}
157
+ isPrivate={packageInfo?.is_private ?? false}
157
158
  isCurrentUserAuthor={
158
159
  packageInfo?.creator_account_id === session?.github_username
159
160
  }
@@ -64,7 +64,7 @@ export default () => (
64
64
  )
65
65
  `.trim()
66
66
 
67
- const generateRandomPackageName = () =>
67
+ export const generateRandomPackageName = () =>
68
68
  `untitled-package-${Math.floor(Math.random() * 90) + 10}`
69
69
 
70
70
  export function CodeAndPreview({ pkg }: Props) {
@@ -136,16 +136,24 @@ export const CodeEditorHeader: React.FC<CodeEditorHeaderProps> = ({
136
136
  <>
137
137
  <div className="flex items-center gap-2 px-2 border-b border-gray-200">
138
138
  <button
139
- className={`text-gray-400 scale-90 transition-opacity duration-200 ${
140
- sidebarOpen ? "opacity-0 pointer-events-none" : "opacity-100"
139
+ className={`text-gray-400 scale-90 p-0 transition-[width,opacity] duration-300 ease-in-out overflow-hidden ${
140
+ sidebarOpen
141
+ ? "w-0 pointer-events-none opacity-0"
142
+ : "w-6 opacity-100"
141
143
  }`}
142
144
  onClick={() => setSidebarOpen(true)}
143
145
  >
144
- <PanelRightClose />
146
+ <div className="w-6 h-6 flex items-center justify-center">
147
+ <PanelRightClose />
148
+ </div>
145
149
  </button>
146
150
  <div>
147
151
  <Select value={currentFile} onValueChange={handleFileChange}>
148
- <SelectTrigger className="h-7 px-3 bg-white select-none">
152
+ <SelectTrigger
153
+ className={`h-7 px-3 bg-white select-none transition-[margin] duration-300 ease-in-out ${
154
+ sidebarOpen ? "-ml-2" : "-ml-1"
155
+ }`}
156
+ >
149
157
  <SelectValue placeholder="Select file" />
150
158
  </SelectTrigger>
151
159
  <SelectContent>
@@ -324,7 +324,7 @@ export default function EditorNav({
324
324
  <DownloadButtonAndMenu
325
325
  snippetUnscopedName={pkg?.unscoped_name}
326
326
  circuitJson={circuitJson}
327
- className="hidden md:flex"
327
+ className="flex"
328
328
  />
329
329
  <Button
330
330
  variant="ghost"
@@ -464,17 +464,42 @@ export default function EditorNav({
464
464
  </div>
465
465
  </DropdownMenuTrigger>
466
466
  <DropdownMenuContent>
467
- <DropdownMenuItem className="text-xs">
468
- <Download className="mr-1 h-3 w-3" />
469
- Download
470
- </DropdownMenuItem>
471
- <DropdownMenuItem className="text-xs">
467
+ <DropdownMenuItem
468
+ className="text-xs"
469
+ onClick={() => {
470
+ if (window) {
471
+ navigator.clipboard.writeText(
472
+ new URL(window.location.href).origin + "/" + pkg?.name,
473
+ )
474
+ toast({
475
+ title: "URL copied!",
476
+ description: "The URL has been copied to your clipboard.",
477
+ })
478
+ }
479
+ }}
480
+ >
472
481
  <Share className="mr-1 h-3 w-3" />
473
482
  Copy URL
474
483
  </DropdownMenuItem>
475
- <DropdownMenuItem className="text-xs">
484
+ <DropdownMenuItem
485
+ className="text-xs"
486
+ onClick={() => {
487
+ if (
488
+ pkg &&
489
+ session?.github_username === pkg.owner_github_username
490
+ ) {
491
+ updatePackageVisibilityToPrivate(!isPrivate)
492
+ }
493
+ }}
494
+ >
476
495
  <Eye className="mr-1 h-3 w-3" />
477
- Public
496
+ {session?.github_username === pkg?.owner_github_username
497
+ ? isPrivate
498
+ ? "Make Public"
499
+ : "Make Private"
500
+ : isPrivate
501
+ ? "Private Package"
502
+ : "Public Package"}
478
503
  </DropdownMenuItem>
479
504
  </DropdownMenuContent>
480
505
  </DropdownMenu>
@@ -7,8 +7,6 @@ import { useUrlParams } from "./use-url-params"
7
7
  export const useCreatePackageMutation = ({
8
8
  onSuccess,
9
9
  }: { onSuccess?: (pkg: Package) => void } = {}) => {
10
- const urlParams = useUrlParams()
11
- const templateName = urlParams.template
12
10
  const axios = useAxios()
13
11
  const session = useGlobalStore((s) => s.session)
14
12
 
@@ -10,13 +10,14 @@ import { useGetFsMapHashForPackage } from "@/hooks/use-get-fsmap-hash-for-packag
10
10
 
11
11
  export const EditorPage = () => {
12
12
  const { packageId } = useCurrentPackageId()
13
- const { data: pkg, isLoading, error } = usePackage(packageId)
13
+ const { data: pkg, error } = usePackage(packageId)
14
14
  const fsMapHash = useGetFsMapHashForPackage(
15
15
  pkg?.latest_package_release_id ?? "",
16
16
  )
17
17
  const uuid4RegExp = new RegExp(
18
18
  /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/,
19
19
  )
20
+
20
21
  return (
21
22
  <div className="overflow-x-hidden">
22
23
  <Helmet>
@@ -166,7 +166,7 @@ export const QuickstartPage = () => {
166
166
  <CardHeader className="p-4 pb-0">
167
167
  <CardTitle className="text-lg flex items-center justify-between">
168
168
  Circuit Json
169
- <TypeBadge type="module" className="ml-2" />
169
+ <TypeBadge type="package" className="ml-2" />
170
170
  </CardTitle>
171
171
  </CardHeader>
172
172
  <CardContent className="p-4 mt-auto">
@@ -1,18 +1,22 @@
1
1
  import RepoPageContent from "@/components/ViewPackagePage/components/repo-page-content"
2
- import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
3
2
  import { usePackageFiles } from "@/hooks/use-package-files"
4
3
  import { usePackageRelease } from "@/hooks/use-package-release"
5
4
  import { useLocation, useParams } from "wouter"
6
5
  import { Helmet } from "react-helmet-async"
7
6
  import { useEffect, useState } from "react"
8
7
  import NotFoundPage from "./404"
8
+ import { useCurrentPackageId } from "@/hooks/use-current-package-id"
9
+ import { usePackage } from "@/hooks/use-package"
9
10
 
10
11
  export const ViewPackagePage = () => {
11
- const { packageInfo } = useCurrentPackageInfo()
12
+ const {
13
+ packageId,
14
+ error: packageIdError,
15
+ isLoading: isLoadingPackageId,
16
+ } = useCurrentPackageId()
17
+ const { data: packageInfo } = usePackage(packageId)
12
18
  const { author, packageName } = useParams()
13
19
  const [, setLocation] = useLocation()
14
- const [isNotFound, setIsNotFound] = useState(false)
15
-
16
20
  const {
17
21
  data: packageRelease,
18
22
  error: packageReleaseError,
@@ -25,14 +29,12 @@ export const ViewPackagePage = () => {
25
29
  const { data: packageFiles } = usePackageFiles(
26
30
  packageRelease?.package_release_id,
27
31
  )
28
- useEffect(() => {
29
- if (isLoadingPackageRelease) return
30
- if (packageReleaseError?.status == 404) {
31
- setIsNotFound(true)
32
- }
33
- }, [isLoadingPackageRelease, packageReleaseError])
34
32
 
35
- if (isNotFound) {
33
+ if (!isLoadingPackageId && packageIdError) {
34
+ return <NotFoundPage heading="Package Not Found" />
35
+ }
36
+
37
+ if (!isLoadingPackageRelease && packageReleaseError?.status == 404) {
36
38
  return <NotFoundPage heading="Package Not Found" />
37
39
  }
38
40