@tscircuit/fake-snippets 0.0.110 → 0.0.112

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 (48) hide show
  1. package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +1 -0
  2. package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +2 -2
  3. package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +3 -2
  4. package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +52 -0
  5. package/bun-tests/fake-snippets-api/routes/packages/list_latest.test.ts +4 -4
  6. package/bun.lock +19 -7
  7. package/dist/bundle.js +85 -36
  8. package/dist/index.d.ts +33 -13
  9. package/dist/index.js +13 -6
  10. package/dist/schema.d.ts +27 -8
  11. package/dist/schema.js +6 -3
  12. package/fake-snippets-api/lib/db/db-client.ts +7 -3
  13. package/fake-snippets-api/lib/db/schema.ts +4 -2
  14. package/fake-snippets-api/lib/db/seed.ts +2 -0
  15. package/fake-snippets-api/lib/middleware/with-session-auth.ts +59 -7
  16. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +4 -2
  17. package/fake-snippets-api/routes/api/orgs/create.ts +4 -2
  18. package/fake-snippets-api/routes/api/orgs/get.ts +1 -1
  19. package/fake-snippets-api/routes/api/orgs/list_members.ts +1 -8
  20. package/fake-snippets-api/routes/api/orgs/update.ts +31 -6
  21. package/fake-snippets-api/routes/api/packages/create.ts +5 -2
  22. package/package.json +7 -5
  23. package/src/App.tsx +3 -5
  24. package/src/components/CmdKMenu.tsx +1 -1
  25. package/src/components/DownloadButtonAndMenu.tsx +14 -1
  26. package/src/components/GithubAvatarWithFallback.tsx +33 -0
  27. package/src/components/PackageCard.tsx +2 -5
  28. package/src/components/PackagesList.tsx +2 -2
  29. package/src/components/ProfileRouter.tsx +2 -2
  30. package/src/components/SentryNotFoundReporter.tsx +44 -0
  31. package/src/components/TrendingPackagesCarousel.tsx +2 -2
  32. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +2 -2
  33. package/src/components/organization/OrganizationCard.tsx +15 -18
  34. package/src/components/organization/OrganizationHeader.tsx +14 -32
  35. package/src/components/organization/OrganizationMembers.tsx +1 -3
  36. package/src/components/preview/ConnectedPackagesList.tsx +1 -1
  37. package/src/hooks/use-hydration.ts +30 -0
  38. package/src/hooks/use-org-by-github-handle.ts +1 -3
  39. package/src/hooks/use-org-by-org-name.ts +24 -0
  40. package/src/lib/download-fns/download-kicad-files.ts +10 -0
  41. package/src/lib/download-fns/download-step.ts +12 -0
  42. package/src/pages/create-organization.tsx +1 -0
  43. package/src/pages/editor.tsx +1 -3
  44. package/src/pages/organization-settings.tsx +4 -1
  45. package/src/pages/search.tsx +7 -2
  46. package/src/pages/user-profile.tsx +1 -1
  47. package/src/pages/user-settings.tsx +161 -0
  48. package/src/pages/view-package.tsx +23 -3
@@ -1,11 +1,9 @@
1
1
  import { useQuery } from "react-query"
2
2
  import { useAxios } from "@/hooks/use-axios"
3
3
  import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
4
- import { useGlobalStore } from "./use-global-store"
5
4
 
6
5
  export const useOrgByGithubHandle = (githubHandle: string | null) => {
7
6
  const axios = useAxios()
8
- const session = useGlobalStore((s) => s.session)
9
7
  return useQuery<PublicOrgSchema, Error & { status: number }>(
10
8
  ["orgs", "by-github-handle", githubHandle],
11
9
  async () => {
@@ -18,7 +16,7 @@ export const useOrgByGithubHandle = (githubHandle: string | null) => {
18
16
  return data.org
19
17
  },
20
18
  {
21
- enabled: Boolean(githubHandle && session),
19
+ enabled: Boolean(githubHandle),
22
20
  retry: false,
23
21
  refetchOnWindowFocus: false,
24
22
  },
@@ -0,0 +1,24 @@
1
+ import { useQuery } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import type { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export const useOrgByName = (name: string | null) => {
6
+ const axios = useAxios()
7
+ return useQuery<PublicOrgSchema, Error & { status: number }>(
8
+ ["orgs", "by-org-name", name],
9
+ async () => {
10
+ if (!name) {
11
+ throw new Error("Organisation name is required")
12
+ }
13
+ const { data } = await axios.get("/orgs/get", {
14
+ params: { org_name: name },
15
+ })
16
+ return data.org
17
+ },
18
+ {
19
+ enabled: Boolean(name),
20
+ retry: false,
21
+ refetchOnWindowFocus: false,
22
+ },
23
+ )
24
+ }
@@ -2,6 +2,7 @@ import { saveAs } from "file-saver"
2
2
  import {
3
3
  CircuitJsonToKicadPcbConverter,
4
4
  CircuitJsonToKicadSchConverter,
5
+ CircuitJsonToKicadProConverter,
5
6
  } from "circuit-json-to-kicad"
6
7
  import { AnyCircuitElement } from "circuit-json"
7
8
  import JSZip from "jszip"
@@ -18,9 +19,18 @@ export const downloadKicadFiles = (
18
19
  schConverter.runUntilFinished()
19
20
  const kicadSchContent = schConverter.getOutputString()
20
21
 
22
+ const proConverter = new CircuitJsonToKicadProConverter(circuitJson, {
23
+ projectName: fileName,
24
+ schematicFilename: `${fileName}.kicad_sch`,
25
+ pcbFilename: `${fileName}.kicad_pcb`,
26
+ })
27
+ proConverter.runUntilFinished()
28
+ const kicadProContent = proConverter.getOutputString()
29
+
21
30
  const zip = new JSZip()
22
31
  zip.file(`${fileName}.kicad_pcb`, kicadPcbContent)
23
32
  zip.file(`${fileName}.kicad_sch`, kicadSchContent)
33
+ zip.file(`${fileName}.kicad_pro`, kicadProContent)
24
34
 
25
35
  zip.generateAsync({ type: "blob" }).then((content) => {
26
36
  saveAs(content, `${fileName}_kicad.zip`)
@@ -0,0 +1,12 @@
1
+ import { AnyCircuitElement } from "circuit-json"
2
+ import { saveAs } from "file-saver"
3
+ import { circuitJsonToStep } from "circuit-json-to-step"
4
+
5
+ export const downloadStepFile = async (
6
+ circuitJson: AnyCircuitElement[],
7
+ fileName: string,
8
+ ) => {
9
+ const content = await circuitJsonToStep(circuitJson)
10
+ const blob = new Blob([content], { type: "text/plain" })
11
+ saveAs(blob, fileName + ".step")
12
+ }
@@ -63,6 +63,7 @@ export const CreateOrganizationPage = () => {
63
63
  console.error("Failed to create organization:", error)
64
64
  toast.error(
65
65
  error?.response?.data?.error?.message ||
66
+ error?.data?.error?.message ||
66
67
  "Failed to create organization. Please try again.",
67
68
  )
68
69
  setIsLoading(false)
@@ -7,9 +7,7 @@ import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
7
7
  export const EditorPage = () => {
8
8
  const { packageInfo: pkg, error } = useCurrentPackageInfo()
9
9
 
10
- const projectUrl = pkg
11
- ? `https://tscircuit.com/${pkg.owner_github_username}/${pkg.unscoped_name}`
12
- : undefined
10
+ const projectUrl = pkg ? `https://tscircuit.com/${pkg.name}` : undefined
13
11
 
14
12
  return (
15
13
  <div className="overflow-x-hidden">
@@ -16,7 +16,6 @@ import {
16
16
  import { Input } from "@/components/ui/input"
17
17
  import { Button } from "@/components/ui/button"
18
18
  import { Badge } from "@/components/ui/badge"
19
- import { Separator } from "@/components/ui/separator"
20
19
  import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
21
20
  import {
22
21
  AlertDialog,
@@ -169,6 +168,10 @@ export default function OrganizationSettingsPage() {
169
168
  organization.user_permissions?.can_manage_org ||
170
169
  organization.owner_account_id === session?.account_id
171
170
 
171
+ if (organization.is_personal_org) {
172
+ return <Redirect to="/settings" />
173
+ }
174
+
172
175
  if (!canManageOrg) {
173
176
  return (
174
177
  <div className="min-h-screen bg-white">
@@ -28,7 +28,8 @@ interface ScoredPackage extends Package {
28
28
  matches: number[]
29
29
  }
30
30
 
31
- interface ScoredAccount extends Omit<Account, "account_id"> {
31
+ interface ScoredAccount
32
+ extends Omit<Account, "account_id" | "is_tscircuit_staff"> {
32
33
  score: number
33
34
  matches: number[]
34
35
  }
@@ -252,7 +253,11 @@ export const SearchPage = () => {
252
253
  ) : accountSearchResults.length > 0 ? (
253
254
  <div className="grid grid-cols-1 w-full sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-3 sm:gap-4">
254
255
  {accountSearchResults.map((account, i) => (
255
- <UserCard key={i} account={account} className="w-full" />
256
+ <UserCard
257
+ key={i}
258
+ account={account as unknown as Account}
259
+ className="w-full"
260
+ />
256
261
  ))}
257
262
  </div>
258
263
  ) : (
@@ -236,7 +236,7 @@ export const UserProfilePage = () => {
236
236
  }
237
237
  />
238
238
  ) : activeTab === "organizations" ? (
239
- <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
239
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
240
240
  {organizations && organizations.length > 0 ? (
241
241
  organizations
242
242
  ?.filter((o) => {
@@ -0,0 +1,161 @@
1
+ import { useState } from "react"
2
+ import { useLocation, Redirect } from "wouter"
3
+ import { Helmet } from "react-helmet-async"
4
+ import { Button } from "@/components/ui/button"
5
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
6
+ import {
7
+ AlertDialog,
8
+ AlertDialogAction,
9
+ AlertDialogCancel,
10
+ AlertDialogContent,
11
+ AlertDialogDescription,
12
+ AlertDialogFooter,
13
+ AlertDialogHeader,
14
+ AlertDialogTitle,
15
+ } from "@/components/ui/alert-dialog"
16
+ import { useToast } from "@/hooks/use-toast"
17
+ import { useGlobalStore } from "@/hooks/use-global-store"
18
+ import { useHydration } from "@/hooks/use-hydration"
19
+ import { AlertTriangle, Loader2, Trash2 } from "lucide-react"
20
+ import Header from "@/components/Header"
21
+ import Footer from "@/components/Footer"
22
+ import { FullPageLoader } from "@/App"
23
+
24
+ export default function UserSettingsPage() {
25
+ const [, navigate] = useLocation()
26
+ const { toast } = useToast()
27
+ const session = useGlobalStore((s) => s.session)
28
+ const hasHydrated = useHydration()
29
+
30
+ const [showDeleteAccountDialog, setShowDeleteAccountDialog] = useState(false)
31
+
32
+ if (!hasHydrated) {
33
+ return <FullPageLoader />
34
+ }
35
+
36
+ if (!session) {
37
+ return <Redirect to="/" />
38
+ }
39
+
40
+ const pageTitle = "User Settings - tscircuit"
41
+
42
+ const handleDeleteAccount = () => {
43
+ setShowDeleteAccountDialog(true)
44
+ }
45
+
46
+ const confirmDeleteAccount = () => {
47
+ // TODO: Implement delete account functionality
48
+ toast({
49
+ title: "Account deleted",
50
+ description: "Your account has been permanently deleted.",
51
+ })
52
+ navigate("/")
53
+ }
54
+
55
+ return (
56
+ <div className="min-h-screen bg-white">
57
+ <Helmet>
58
+ <title>{pageTitle}</title>
59
+ </Helmet>
60
+ <Header />
61
+
62
+ <section className="w-full px-4 sm:px-6 lg:px-8 xl:px-12 2xl:px-16">
63
+ <div className="max-w-7xl mx-auto py-8">
64
+ <div className="mb-8">
65
+ <div className="flex items-center gap-4 mb-6">
66
+ <Avatar className="h-16 w-16 border-2 border-gray-200 shadow-sm">
67
+ <AvatarImage
68
+ src={`https://github.com/${session.github_username}.png`}
69
+ alt={`${session.github_username} avatar`}
70
+ />
71
+ <AvatarFallback className="text-lg bg-gradient-to-br from-blue-100 to-indigo-100 text-blue-700 font-medium">
72
+ {(session.github_username || session.account_id || "")
73
+ .slice(0, 2)
74
+ .toUpperCase()}
75
+ </AvatarFallback>
76
+ </Avatar>
77
+ <div>
78
+ <h1 className="text-3xl font-bold text-gray-900">
79
+ Account Settings
80
+ </h1>
81
+ <p className="text-gray-600 mt-1">
82
+ Manage your account preferences and settings
83
+ </p>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <div className="space-y-8">
89
+ <div className="bg-white border border-red-200 rounded-xl shadow-sm">
90
+ <div className="px-6 py-5 border-b border-red-200 bg-red-50 rounded-t-xl">
91
+ <h2 className="text-xl font-semibold text-red-900">
92
+ Danger Zone
93
+ </h2>
94
+ <p className="text-sm text-red-600 mt-2">
95
+ Irreversible and destructive actions for your account.
96
+ </p>
97
+ </div>
98
+
99
+ <div className="p-6 lg:p-8">
100
+ <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
101
+ <div className="flex-1">
102
+ <h3 className="text-sm font-semibold text-gray-900 mb-2">
103
+ Delete Account
104
+ </h3>
105
+ <p className="text-sm text-gray-500 leading-relaxed">
106
+ Permanently delete your account and all associated data.
107
+ This action cannot be undone and will remove all your
108
+ packages, snippets, and account information.
109
+ </p>
110
+ </div>
111
+ <div className="flex-shrink-0">
112
+ <Button
113
+ variant="destructive"
114
+ onClick={handleDeleteAccount}
115
+ className="bg-red-600 hover:bg-red-700 text-white px-6 py-2.5 text-sm font-medium shadow-sm w-full lg:w-auto"
116
+ >
117
+ <Trash2 className="mr-2 h-4 w-4" />
118
+ Delete Account
119
+ </Button>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </section>
127
+
128
+ <Footer />
129
+
130
+ <AlertDialog
131
+ open={showDeleteAccountDialog}
132
+ onOpenChange={setShowDeleteAccountDialog}
133
+ >
134
+ <AlertDialogContent>
135
+ <AlertDialogHeader>
136
+ <AlertDialogTitle className="flex items-center gap-2 text-red-600">
137
+ <AlertTriangle className="h-5 w-5" />
138
+ Delete Account
139
+ </AlertDialogTitle>
140
+ <AlertDialogDescription>
141
+ Are you absolutely sure you want to delete your account? This
142
+ action is permanent and cannot be undone. All your packages,
143
+ snippets, and account data will be permanently removed.
144
+ </AlertDialogDescription>
145
+ </AlertDialogHeader>
146
+ <AlertDialogFooter>
147
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
148
+ <AlertDialogAction
149
+ onClick={confirmDeleteAccount}
150
+ disabled={true}
151
+ className="bg-red-600 hover:bg-red-700"
152
+ >
153
+ {false && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
154
+ Delete Account
155
+ </AlertDialogAction>
156
+ </AlertDialogFooter>
157
+ </AlertDialogContent>
158
+ </AlertDialog>
159
+ </div>
160
+ )
161
+ }
@@ -3,9 +3,9 @@ import { usePackageFiles } from "@/hooks/use-package-files"
3
3
  import { useCurrentPackageRelease } from "@/hooks/use-current-package-release"
4
4
  import { useLocation, useParams } from "wouter"
5
5
  import { Helmet } from "react-helmet-async"
6
- import { useEffect, useState } from "react"
7
6
  import NotFoundPage from "./404"
8
7
  import { usePackageByName } from "@/hooks/use-package-by-package-name"
8
+ import { SentryNotFoundReporter } from "@/components/SentryNotFoundReporter"
9
9
 
10
10
  export const ViewPackagePage = () => {
11
11
  const { author, packageName } = useParams()
@@ -32,11 +32,31 @@ export const ViewPackagePage = () => {
32
32
  usePackageFiles(packageRelease?.package_release_id)
33
33
 
34
34
  if (!isLoadingPackage && packageError) {
35
- return <NotFoundPage heading="Package Not Found" />
35
+ return (
36
+ <>
37
+ <SentryNotFoundReporter
38
+ context="package"
39
+ slug={packageNameFull}
40
+ status={(packageError as any)?.status}
41
+ message={packageError.message}
42
+ />
43
+ <NotFoundPage heading="Package Not Found" />
44
+ </>
45
+ )
36
46
  }
37
47
 
38
48
  if (!isLoadingPackageRelease && packageReleaseError?.status == 404) {
39
- return <NotFoundPage heading="Package Not Found" />
49
+ return (
50
+ <>
51
+ <SentryNotFoundReporter
52
+ context="package_release"
53
+ slug={packageNameFull}
54
+ status={packageReleaseError.status}
55
+ message={packageReleaseError.message}
56
+ />
57
+ <NotFoundPage heading="Package Not Found" />
58
+ </>
59
+ )
40
60
  }
41
61
 
42
62
  return (