@tscircuit/fake-snippets 0.0.78 → 0.0.80

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.
@@ -1,36 +1,8 @@
1
1
  {
2
2
  "packages": [
3
- {
4
- "owner": "Abse2001",
5
- "name": "Arduino-Nano-Servo-Breakout"
6
- },
7
- {
8
- "owner": "ShiboSoftwareDev",
9
- "name": "Wifi-Camera-Module"
10
- },
11
- {
12
- "owner": "imrishabh18",
13
- "name": "Arduino-nano"
14
- },
15
3
  {
16
4
  "owner": "seveibar",
17
5
  "name": "usb-c-flashlight"
18
- },
19
- {
20
- "owner": "seveibar",
21
- "name": "nine-key-keyboard"
22
- },
23
- {
24
- "owner": "AnasSarkiz",
25
- "name": "grid-of-LEDs-with-an-ESP32"
26
- },
27
- {
28
- "owner": "seveibar",
29
- "name": "keyboard-default60"
30
- },
31
- {
32
- "owner": "imrishabh18",
33
- "name": "motor-driver-breakout"
34
6
  }
35
7
  ]
36
8
  }
@@ -17,7 +17,8 @@ export default withRouteSpec({
17
17
  if (req.method === "POST") {
18
18
  const { github_username } = req.jsonBody
19
19
  account = ctx.db.accounts.find(
20
- (acc: Account) => acc.github_username === github_username,
20
+ (acc: Account) =>
21
+ acc.github_username.toLowerCase() === github_username.toLowerCase(),
21
22
  )
22
23
  } else {
23
24
  account = ctx.db.getAccount(ctx.auth.account_id)
@@ -8,6 +8,7 @@ export default withRouteSpec({
8
8
  auth: "none",
9
9
  jsonBody: z.object({
10
10
  package_id: z.string().optional(),
11
+ package_name: z.string().optional(),
11
12
  version: z.string().optional(),
12
13
  is_latest: z.boolean().optional(),
13
14
  commit_sha: z.string().optional(),
@@ -20,6 +21,7 @@ export default withRouteSpec({
20
21
  })(async (req, ctx) => {
21
22
  let {
22
23
  package_id,
24
+ package_name,
23
25
  is_latest = true,
24
26
  version,
25
27
  commit_sha,
@@ -41,10 +43,21 @@ export default withRouteSpec({
41
43
  version = parsedVersion
42
44
  }
43
45
 
46
+ if (!package_id && package_name) {
47
+ const pkg = ctx.db.packages.find((p) => p.name === package_name)
48
+ if (!pkg) {
49
+ return ctx.error(404, {
50
+ error_code: "package_not_found",
51
+ message: `Package not found: ${package_name}`,
52
+ })
53
+ }
54
+ package_id = pkg.package_id
55
+ }
56
+
44
57
  if (!package_id || !version) {
45
58
  return ctx.error(400, {
46
59
  error_code: "missing_options",
47
- message: "package_id and version are required",
60
+ message: "package_id or package_name and version are required",
48
61
  })
49
62
  }
50
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.78",
3
+ "version": "0.0.80",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,18 +1,10 @@
1
- import { ChevronDown, Download, Github, Link, RefreshCw } from "lucide-react"
1
+ import { Github, RefreshCw } from "lucide-react"
2
2
  import { Button } from "@/components/ui/button"
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger,
8
- } from "@/components/ui/dropdown-menu"
9
3
  import { useParams } from "wouter"
10
- import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
11
4
  import { DownloadButtonAndMenu } from "../DownloadButtonAndMenu"
12
5
 
13
6
  export function PackageBuildHeader() {
14
7
  const { author, packageName } = useParams()
15
- const { packageRelease } = useCurrentPackageRelease()
16
8
 
17
9
  return (
18
10
  <div className="border-b border-gray-200 bg-white px-6 py-4">
@@ -211,6 +211,7 @@ const MobileSidebar = ({
211
211
  packageAuthor={packageInfo.owner_github_username}
212
212
  onUpdate={handlePackageUpdate}
213
213
  packageName={packageInfo.name}
214
+ unscopedPackageName={packageInfo.unscoped_name}
214
215
  currentDefaultView={packageInfo.default_view}
215
216
  />
216
217
  )}
@@ -161,6 +161,7 @@ export default function SidebarAboutSection() {
161
161
 
162
162
  {packageInfo && (
163
163
  <EditPackageDetailsDialog
164
+ unscopedPackageName={packageInfo.unscoped_name}
164
165
  packageReleaseId={packageInfo.latest_package_release_id}
165
166
  packageId={packageInfo.package_id}
166
167
  currentDescription={
@@ -38,6 +38,7 @@ interface EditPackageDetailsDialogProps {
38
38
  currentDefaultView?: string
39
39
  isPrivate?: boolean
40
40
  packageName: string
41
+ unscopedPackageName: string
41
42
  packageReleaseId: string | null
42
43
  packageAuthor?: string | null
43
44
  onUpdate?: (
@@ -57,7 +58,7 @@ export const EditPackageDetailsDialog = ({
57
58
  currentLicense,
58
59
  currentDefaultView = "files",
59
60
  isPrivate = false,
60
- packageName,
61
+ unscopedPackageName,
61
62
  packageReleaseId,
62
63
  packageAuthor,
63
64
  onUpdate,
@@ -65,13 +66,11 @@ export const EditPackageDetailsDialog = ({
65
66
  const axios = useAxios()
66
67
  const { toast } = useToast()
67
68
  const qc = useQueryClient()
68
-
69
69
  const {
70
70
  formData,
71
71
  setFormData,
72
72
  websiteError,
73
73
  hasLicenseChanged,
74
- hasDefaultViewChanged,
75
74
  hasChanges,
76
75
  isFormValid,
77
76
  } = usePackageDetailsForm({
@@ -79,6 +78,7 @@ export const EditPackageDetailsDialog = ({
79
78
  initialWebsite: currentWebsite,
80
79
  initialLicense: currentLicense || null,
81
80
  initialDefaultView: currentDefaultView,
81
+ initialUnscopedPackageName: unscopedPackageName,
82
82
  isDialogOpen: open,
83
83
  initialVisibility: isPrivate ? "private" : "public",
84
84
  })
@@ -114,16 +114,15 @@ export const EditPackageDetailsDialog = ({
114
114
  website: formData.website,
115
115
  is_private: formData.visibility == "private",
116
116
  default_view: formData.defaultView,
117
- })
118
- const privacyUpdateResponse = await axios.post("/snippets/update", {
119
- snippet_id: packageId,
120
- is_private: formData.visibility === "private",
117
+ ...(formData.unscopedPackageName !== unscopedPackageName && {
118
+ name: formData.unscopedPackageName,
119
+ }),
121
120
  })
122
121
  if (response.status !== 200)
123
122
  throw new Error("Failed to update package details")
124
123
 
125
124
  const filesRes = await axios.post("/package_files/list", {
126
- package_name_with_version: packageName,
125
+ package_name_with_version: `${packageAuthor}/${formData.unscopedPackageName}`,
127
126
  })
128
127
  const packageFiles: string[] =
129
128
  filesRes.status === 200
@@ -137,18 +136,32 @@ export const EditPackageDetailsDialog = ({
137
136
  if (hasLicenseChanged) {
138
137
  if (packageFiles.includes("LICENSE") && !licenseContent) {
139
138
  await axios.post("/package_files/delete", {
140
- package_name_with_version: packageName,
139
+ package_name_with_version: `${packageAuthor}/${formData.unscopedPackageName}`,
141
140
  file_path: "LICENSE",
142
141
  })
143
142
  }
144
143
  if (licenseContent) {
145
144
  await axios.post("/package_files/create_or_update", {
146
- package_name_with_version: packageName,
145
+ package_name_with_version: `${packageAuthor}/${formData.unscopedPackageName}`,
147
146
  file_path: "LICENSE",
148
147
  content_text: licenseContent,
149
148
  })
150
149
  }
151
150
  }
151
+ console.log(
152
+ "formData.unscopedPackageName",
153
+ formData.unscopedPackageName,
154
+ "unscopedPackageName",
155
+ unscopedPackageName,
156
+ )
157
+ if (formData.unscopedPackageName !== unscopedPackageName) {
158
+ // Use router for client-side navigation
159
+ window.history.replaceState(
160
+ {},
161
+ "",
162
+ `/${packageAuthor}/${formData.unscopedPackageName}`,
163
+ )
164
+ }
152
165
 
153
166
  return {
154
167
  description: formData.description,
@@ -187,7 +200,9 @@ export const EditPackageDetailsDialog = ({
187
200
  qc.setQueryData(["packages", packageId], context?.previous)
188
201
  toast({
189
202
  title: "Error",
190
- description: "Failed to update package details. Please try again.",
203
+ description:
204
+ (error as any)?.data?.error?.message ||
205
+ "Failed to update package details. Please try again.",
191
206
  variant: "destructive",
192
207
  })
193
208
  },
@@ -237,11 +252,29 @@ export const EditPackageDetailsDialog = ({
237
252
  </DialogHeader>
238
253
  <div className="">
239
254
  <div className="grid gap-2">
255
+ <div className="space-y-1">
256
+ <Label htmlFor="packageName">Package Name</Label>
257
+ <Input
258
+ id="packageName"
259
+ value={formData.unscopedPackageName}
260
+ onChange={(e) =>
261
+ setFormData((prev) => ({
262
+ ...prev,
263
+ unscopedPackageName: e.target.value,
264
+ }))
265
+ }
266
+ placeholder="Enter package name"
267
+ disabled={updatePackageDetailsMutation.isLoading}
268
+ className="w-full"
269
+ autoComplete="off"
270
+ />
271
+ </div>
240
272
  <div className="space-y-1">
241
273
  <Label htmlFor="website">Website</Label>
242
274
  <Input
243
275
  id="website"
244
276
  value={formData.website}
277
+ autoComplete="off"
245
278
  onChange={(e) =>
246
279
  setFormData((prev) => ({
247
280
  ...prev,
@@ -282,6 +315,7 @@ export const EditPackageDetailsDialog = ({
282
315
  <Label htmlFor="description">Description</Label>
283
316
  <Textarea
284
317
  id="description"
318
+ spellCheck={false}
285
319
  value={formData.description}
286
320
  onChange={(e) =>
287
321
  setFormData((prev) => ({
@@ -29,21 +29,71 @@ export const useCreatePackageReleaseMutation = ({
29
29
  )
30
30
  }
31
31
 
32
- const {
33
- data: { package_release: newPackageRelease },
34
- } = await axios.post("/package_releases/create", {
35
- package_id,
36
- version,
37
- is_latest,
38
- commit_sha,
39
- package_name_with_version,
40
- })
41
-
42
- if (!newPackageRelease) {
43
- throw new Error("Failed to create package release")
32
+ let resolvedVersion = version
33
+ let resolvedPkgName = package_name_with_version
34
+
35
+ // Parse version from package_name_with_version if needed
36
+ if (package_name_with_version && !version) {
37
+ const [pkgName, parsedVersion] = package_name_with_version.split("@")
38
+ resolvedVersion = parsedVersion
39
+ resolvedPkgName = pkgName
40
+ } else if (package_name_with_version) {
41
+ const [pkgName] = package_name_with_version.split("@")
42
+ resolvedPkgName = pkgName
43
+ }
44
+
45
+ // Default version to 1.0.0 when it contains no digits
46
+ if (!resolvedVersion || !/[0-9]/.test(resolvedVersion)) {
47
+ resolvedVersion = "1.0.0"
44
48
  }
45
49
 
46
- return newPackageRelease
50
+ const normalizedPackageNameWithVersion =
51
+ resolvedPkgName && `${resolvedPkgName}@${resolvedVersion}`
52
+
53
+ try {
54
+ const {
55
+ data: { package_release: newPackageRelease },
56
+ } = await axios.post("/package_releases/create", {
57
+ package_id,
58
+ version: resolvedVersion,
59
+ is_latest,
60
+ commit_sha,
61
+ package_name_with_version: normalizedPackageNameWithVersion,
62
+ })
63
+
64
+ if (!newPackageRelease) {
65
+ throw new Error("Failed to create package release")
66
+ }
67
+
68
+ return newPackageRelease
69
+ } catch (error: any) {
70
+ if (
71
+ error.status === 400 &&
72
+ error.data?.error?.error_code === "version_already_exists" &&
73
+ normalizedPackageNameWithVersion
74
+ ) {
75
+ // Update the existing release with the provided data
76
+ await axios.post("/package_releases/update", {
77
+ package_name_with_version: normalizedPackageNameWithVersion,
78
+ is_latest,
79
+ commit_sha,
80
+ })
81
+
82
+ const {
83
+ data: { package_release },
84
+ } = await axios.post("/package_releases/get", {
85
+ package_name_with_version: normalizedPackageNameWithVersion,
86
+ })
87
+
88
+ if (!package_release) {
89
+ throw new Error("Failed to update package release")
90
+ }
91
+
92
+ return package_release as PackageRelease
93
+ }
94
+
95
+ throw error
96
+ }
47
97
  },
48
98
  {
49
99
  onSuccess: (packageRelease: PackageRelease) => {
@@ -16,6 +16,7 @@ interface PackageDetailsForm {
16
16
  license: string | null
17
17
  visibility: string
18
18
  defaultView: string
19
+ unscopedPackageName: string
19
20
  }
20
21
 
21
22
  interface UsePackageDetailsFormProps {
@@ -24,6 +25,7 @@ interface UsePackageDetailsFormProps {
24
25
  initialLicense: string | null
25
26
  initialVisibility: string
26
27
  initialDefaultView: string
28
+ initialUnscopedPackageName: string
27
29
  isDialogOpen: boolean
28
30
  }
29
31
 
@@ -33,6 +35,7 @@ export const usePackageDetailsForm = ({
33
35
  initialLicense,
34
36
  initialVisibility,
35
37
  initialDefaultView,
38
+ initialUnscopedPackageName,
36
39
  isDialogOpen,
37
40
  }: UsePackageDetailsFormProps) => {
38
41
  const [formData, setFormData] = useState<PackageDetailsForm>({
@@ -41,6 +44,7 @@ export const usePackageDetailsForm = ({
41
44
  license: initialLicense || null,
42
45
  visibility: initialVisibility,
43
46
  defaultView: initialDefaultView,
47
+ unscopedPackageName: initialUnscopedPackageName,
44
48
  })
45
49
  const [websiteError, setWebsiteError] = useState<string | null>(null)
46
50
 
@@ -52,6 +56,7 @@ export const usePackageDetailsForm = ({
52
56
  license: initialLicense || null,
53
57
  visibility: initialVisibility,
54
58
  defaultView: initialDefaultView,
59
+ unscopedPackageName: initialUnscopedPackageName,
55
60
  })
56
61
  setWebsiteError(null)
57
62
  }
@@ -62,6 +67,7 @@ export const usePackageDetailsForm = ({
62
67
  initialLicense,
63
68
  initialVisibility,
64
69
  initialDefaultView,
70
+ initialUnscopedPackageName,
65
71
  ])
66
72
 
67
73
  useEffect(() => {
@@ -93,7 +99,8 @@ export const usePackageDetailsForm = ({
93
99
  formData.website !== initialWebsite ||
94
100
  formData.license !== initialLicense ||
95
101
  formData.visibility !== initialVisibility ||
96
- formData.defaultView !== initialDefaultView,
102
+ formData.defaultView !== initialDefaultView ||
103
+ formData.unscopedPackageName !== initialUnscopedPackageName,
97
104
  [
98
105
  formData,
99
106
  initialDescription,
@@ -101,6 +108,7 @@ export const usePackageDetailsForm = ({
101
108
  initialLicense,
102
109
  initialVisibility,
103
110
  initialDefaultView,
111
+ initialUnscopedPackageName,
104
112
  ],
105
113
  )
106
114
 
@@ -1,11 +1,8 @@
1
1
  export const blankPackageTemplate = {
2
2
  type: "package",
3
3
  code: `
4
- import { createUseComponent } from "@tscircuit/core"
4
+ import type { ChipProps } from "@tscircuit/props"
5
5
 
6
- export const MyChip = (props: { name: string }) => (
7
- <chip {...props} pinLabels={pinLabels} footprint="soic8" />
8
- )
9
6
  const pinLabels = {
10
7
  pin1: [],
11
8
  pin2: [],
@@ -17,6 +14,8 @@ const pinLabels = {
17
14
  pin8: [],
18
15
  } as const
19
16
 
20
- export const useMyChip = createUseComponent(MyChip, pinLabels)
17
+ export const MyChip = (props: ChipProps<typeof pinLabels>) => (
18
+ <chip footprint="soic8" pinLabels={pinLabels} {...props} />
19
+ )
21
20
  `.trim(),
22
21
  }
@@ -119,7 +119,7 @@ export const QuickstartPage = () => {
119
119
  <div className="mt-12">
120
120
  <h2 className="text-xl font-semibold mb-4">Import as Package</h2>
121
121
  <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">
122
- {[
122
+ {/* {[
123
123
  { name: "KiCad Footprint", type: "footprint" },
124
124
  { name: "KiCad Project", type: "board" },
125
125
  { name: "KiCad Module", type: "package" },
@@ -145,7 +145,7 @@ export const QuickstartPage = () => {
145
145
  </Button>
146
146
  </CardContent>
147
147
  </Card>
148
- ))}
148
+ ))} */}
149
149
  <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col">
150
150
  <CardHeader className="p-4 pb-0">
151
151
  <CardTitle className="text-lg flex items-center justify-between">
@@ -14,6 +14,7 @@ import type { Package } from "fake-snippets-api/lib/db/schema"
14
14
  import type React from "react"
15
15
  import { useState } from "react"
16
16
  import { useQuery } from "react-query"
17
+ import NotFoundPage from "./404"
17
18
  import { useParams } from "wouter"
18
19
  import {
19
20
  Select,
@@ -32,7 +33,28 @@ export const UserProfilePage = () => {
32
33
  const [activeTab, setActiveTab] = useState("all")
33
34
  const [filter, setFilter] = useState("most-recent") // Changed default from "newest" to "most-recent"
34
35
  const session = useGlobalStore((s) => s.session)
35
- const isCurrentUserProfile = username === session?.github_username
36
+ const {
37
+ data: account,
38
+ error: accountError,
39
+ isLoading: isLoadingAccount,
40
+ } = useQuery<
41
+ { account: { github_username: string } },
42
+ Error & { status: number }
43
+ >(
44
+ ["account", username],
45
+ async () => {
46
+ const response = await axios.post("/accounts/get", {
47
+ github_username: username,
48
+ })
49
+ return response.data
50
+ },
51
+ { retry: false },
52
+ )
53
+
54
+ // use the username stored in the database so the correct case is displayed
55
+ const githubUsername = account?.account.github_username || username
56
+ const isCurrentUserProfile = githubUsername === session?.github_username
57
+
36
58
  const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
37
59
  useConfirmDeletePackageDialog()
38
60
  const [packageToDelete, setPackageToDelete] = useState<Package | null>(null)
@@ -41,33 +63,42 @@ export const UserProfilePage = () => {
41
63
  data: userPackages,
42
64
  isLoading: isLoadingUserPackages,
43
65
  refetch: refetchUserPackages,
44
- } = useQuery<Package[]>(["userPackages", username], async () => {
45
- const response = await axios.post(`/packages/list`, {
46
- owner_github_username: username,
47
- })
48
- return response.data.packages
49
- })
66
+ } = useQuery<Package[]>(
67
+ ["userPackages", githubUsername],
68
+ async () => {
69
+ const response = await axios.post(`/packages/list`, {
70
+ owner_github_username: githubUsername,
71
+ })
72
+ return response.data.packages
73
+ },
74
+ { enabled: Boolean(githubUsername) },
75
+ )
50
76
 
51
77
  const { data: starredPackages, isLoading: isLoadingStarredPackages } =
52
78
  useQuery<Package[]>(
53
- ["starredPackages", username],
79
+ ["starredPackages", githubUsername],
54
80
  async () => {
55
81
  const response = await axios.post(`/packages/list`, {
56
- starred_by: username,
82
+ starred_by: githubUsername,
57
83
  })
58
84
  return response.data.packages
59
85
  },
60
86
  {
61
- enabled: activeTab === "starred",
87
+ enabled: activeTab === "starred" && Boolean(githubUsername),
62
88
  },
63
89
  )
64
90
 
65
91
  const baseUrl = useSnippetsBaseApiUrl()
66
92
 
93
+ if (accountError) {
94
+ return <NotFoundPage heading="User Not Found" />
95
+ }
96
+
67
97
  const packagesToShow =
68
98
  activeTab === "starred" ? starredPackages : userPackages
69
99
  const isLoading =
70
- activeTab === "starred" ? isLoadingStarredPackages : isLoadingUserPackages
100
+ isLoadingAccount ||
101
+ (activeTab === "starred" ? isLoadingStarredPackages : isLoadingUserPackages)
71
102
 
72
103
  const filteredPackages = packagesToShow
73
104
  ?.filter((pkg) => {
@@ -107,12 +138,16 @@ export const UserProfilePage = () => {
107
138
  <div className="container mx-auto px-4 py-8">
108
139
  <div className="flex items-center gap-4 mb-6">
109
140
  <Avatar className="h-16 w-16">
110
- <AvatarImage src={`https://github.com/${username}.png`} />
111
- <AvatarFallback>{username?.[0]?.toUpperCase()}</AvatarFallback>
141
+ <AvatarImage src={`https://github.com/${githubUsername}.png`} />
142
+ <AvatarFallback>
143
+ {githubUsername?.[0]?.toUpperCase()}
144
+ </AvatarFallback>
112
145
  </Avatar>
113
146
  <div>
114
147
  <h1 className="text-3xl font-bold">
115
- {isCurrentUserProfile ? "My Profile" : `${username}'s Profile`}
148
+ {isCurrentUserProfile
149
+ ? "My Profile"
150
+ : `${githubUsername}'s Profile`}
116
151
  </h1>
117
152
  <div className="text-gray-600 mt-1">
118
153
  {userPackages?.length || 0} packages
@@ -121,7 +156,7 @@ export const UserProfilePage = () => {
121
156
  </div>
122
157
  <div className="mb-6">
123
158
  <a
124
- href={`https://github.com/${username}`}
159
+ href={`https://github.com/${githubUsername}`}
125
160
  target="_blank"
126
161
  rel="noopener noreferrer"
127
162
  className="inline-flex items-center"