@tscircuit/fake-snippets 0.0.110 → 0.0.111

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 (32) 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/update.test.ts +52 -0
  4. package/bun.lock +14 -2
  5. package/dist/bundle.js +81 -36
  6. package/dist/index.d.ts +33 -13
  7. package/dist/index.js +10 -6
  8. package/dist/schema.d.ts +24 -8
  9. package/dist/schema.js +5 -3
  10. package/fake-snippets-api/lib/db/db-client.ts +7 -3
  11. package/fake-snippets-api/lib/db/schema.ts +3 -2
  12. package/fake-snippets-api/lib/middleware/with-session-auth.ts +59 -7
  13. package/fake-snippets-api/lib/public-mapping/public-map-org.ts +3 -2
  14. package/fake-snippets-api/routes/api/orgs/create.ts +4 -2
  15. package/fake-snippets-api/routes/api/orgs/get.ts +1 -1
  16. package/fake-snippets-api/routes/api/orgs/list_members.ts +1 -8
  17. package/fake-snippets-api/routes/api/orgs/update.ts +31 -6
  18. package/fake-snippets-api/routes/api/packages/create.ts +5 -2
  19. package/package.json +6 -4
  20. package/src/App.tsx +3 -5
  21. package/src/components/DownloadButtonAndMenu.tsx +14 -1
  22. package/src/components/SentryNotFoundReporter.tsx +44 -0
  23. package/src/components/organization/OrganizationCard.tsx +5 -3
  24. package/src/hooks/use-hydration.ts +30 -0
  25. package/src/hooks/use-org-by-github-handle.ts +1 -1
  26. package/src/lib/download-fns/download-kicad-files.ts +10 -0
  27. package/src/lib/download-fns/download-step.ts +12 -0
  28. package/src/pages/create-organization.tsx +1 -0
  29. package/src/pages/organization-settings.tsx +4 -1
  30. package/src/pages/search.tsx +7 -2
  31. package/src/pages/user-settings.tsx +161 -0
  32. package/src/pages/view-package.tsx +23 -3
@@ -13,6 +13,7 @@ export default withRouteSpec({
13
13
  z.object({
14
14
  name: z.string().optional(),
15
15
  display_name: z.string().optional(),
16
+ github_handle: z.string().trim().min(1).nullable().optional(),
16
17
  }),
17
18
  ),
18
19
  auth: "session",
@@ -20,10 +21,11 @@ export default withRouteSpec({
20
21
  org: publicOrgSchema,
21
22
  }),
22
23
  })(async (req, ctx) => {
23
- const { org_id, name, display_name } = req.commonParams as {
24
+ const { org_id, name, display_name, github_handle } = req.commonParams as {
24
25
  org_id: string
25
26
  name?: string
26
27
  display_name?: string
28
+ github_handle?: string
27
29
  }
28
30
 
29
31
  const org = ctx.db.getOrg({ org_id }, ctx.auth)
@@ -43,13 +45,13 @@ export default withRouteSpec({
43
45
  }
44
46
 
45
47
  // No changes provided
46
- if (!name && display_name === undefined) {
48
+ if (!name && display_name === undefined && github_handle === null) {
47
49
  return ctx.json({ org: publicMapOrg(org) })
48
50
  }
49
51
 
50
- if (name && name !== org.github_handle) {
52
+ if (name && name !== org.org_name) {
51
53
  // Validate duplicate name
52
- const duplicate = ctx.db.getOrg({ github_handle: name })
54
+ const duplicate = ctx.db.getOrg({ org_name: name })
53
55
 
54
56
  if (duplicate && duplicate.org_id !== org_id) {
55
57
  return ctx.error(400, {
@@ -58,20 +60,43 @@ export default withRouteSpec({
58
60
  })
59
61
  }
60
62
  }
63
+ if (
64
+ github_handle !== undefined &&
65
+ github_handle !== org.github_handle &&
66
+ github_handle !== null
67
+ ) {
68
+ const duplicateHandle = ctx.db.getOrg({ github_handle })
69
+ ? ctx.db.getOrg({ github_handle })?.org_id != org_id
70
+ : false
61
71
 
72
+ if (duplicateHandle) {
73
+ return ctx.error(400, {
74
+ error_code: "org_github_handle_already_exists",
75
+ message: "An organization with this GitHub handle already exists",
76
+ })
77
+ }
78
+ }
62
79
  const updates: {
63
- github_handle?: string
80
+ org_name?: string
64
81
  org_display_name?: string
82
+ github_handle?: string
65
83
  } = {}
66
84
 
67
85
  if (name) {
68
86
  updates.github_handle = name
87
+ updates.org_name = name
88
+ }
89
+
90
+ if (github_handle !== undefined) {
91
+ updates.github_handle = github_handle
69
92
  }
70
93
 
71
94
  if (display_name !== undefined) {
72
95
  const trimmedDisplayName = display_name.trim()
96
+ const handleForFallback =
97
+ github_handle !== undefined ? github_handle : org.github_handle
73
98
  const fallbackDisplayName =
74
- name ?? org.org_display_name ?? org.github_handle ?? ""
99
+ name ?? org.org_display_name ?? org.org_name ?? handleForFallback ?? ""
75
100
  updates.org_display_name =
76
101
  trimmedDisplayName.length > 0 ? trimmedDisplayName : fallbackDisplayName
77
102
  }
@@ -66,6 +66,7 @@ export default withRouteSpec({
66
66
  .find(
67
67
  (o) =>
68
68
  o.org_display_name?.toLowerCase() === requested_owner_lower ||
69
+ o.org_name?.toLowerCase() === requested_owner_lower ||
69
70
  o.github_handle?.toLowerCase() === requested_owner_lower,
70
71
  )
71
72
 
@@ -78,10 +79,12 @@ export default withRouteSpec({
78
79
  }
79
80
 
80
81
  owner_org_id = memberOrg.org_id
81
- owner_github_username = memberOrg.github_handle
82
+ owner_github_username = memberOrg.github_handle || memberOrg.org_name
82
83
  }
83
84
 
84
- const existingPackage = ctx.db.packages.find((pkg) => pkg.name === final_name)
85
+ const existingPackage = ctx.db
86
+ .getState()
87
+ .packages.find((pkg) => pkg.name === final_name)
85
88
 
86
89
  if (existingPackage) {
87
90
  throw ctx.error(400, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.110",
3
+ "version": "0.0.111",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -110,10 +110,12 @@
110
110
  "circuit-json-to-bom-csv": "^0.0.7",
111
111
  "circuit-json-to-gerber": "^0.0.29",
112
112
  "circuit-json-to-gltf": "^0.0.14",
113
+ "circuit-json-to-kicad": "^0.0.9",
113
114
  "circuit-json-to-pnp-csv": "^0.0.7",
114
115
  "circuit-json-to-readable-netlist": "^0.0.13",
115
116
  "circuit-json-to-simple-3d": "^0.0.7",
116
117
  "circuit-json-to-spice": "^0.0.6",
118
+ "circuit-json-to-step": "^0.0.2",
117
119
  "circuit-json-to-tscircuit": "^0.0.9",
118
120
  "circuit-to-svg": "^0.0.202",
119
121
  "class-variance-authority": "^0.7.1",
@@ -139,6 +141,7 @@
139
141
  "jscad-electronics": "^0.0.25",
140
142
  "jszip": "^3.10.1",
141
143
  "kicad-converter": "^0.0.17",
144
+ "kicadts": "^0.0.9",
142
145
  "ky": "^1.7.5",
143
146
  "lucide-react": "^0.488.0",
144
147
  "lz-string": "^1.5.0",
@@ -172,6 +175,7 @@
172
175
  "sitemap": "^8.0.0",
173
176
  "sonner": "^1.5.0",
174
177
  "states-us": "^1.1.1",
178
+ "stepts": "^0.0.1",
175
179
  "svgo": "^4.0.0",
176
180
  "tailwind-merge": "^2.5.2",
177
181
  "tailwindcss": "^3.4.13",
@@ -192,8 +196,6 @@
192
196
  "wouter": "^3.3.5",
193
197
  "zod": "^3.23.8",
194
198
  "zustand": "^4.5.5",
195
- "zustand-hoist": "^2.0.1",
196
- "circuit-json-to-kicad": "^0.0.3",
197
- "kicadts": "^0.0.9"
199
+ "zustand-hoist": "^2.0.1"
198
200
  }
199
201
  }
package/src/App.tsx CHANGED
@@ -87,9 +87,7 @@ const ReleasePreviewPage = lazyImport(() => import("@/pages/preview-release"))
87
87
  const OrganizationSettingsPage = lazyImport(
88
88
  () => import("@/pages/organization-settings"),
89
89
  )
90
- const SettingsRedirectPage = lazyImport(
91
- () => import("@/pages/settings-redirect"),
92
- )
90
+ const UserSettingsPage = lazyImport(() => import("@/pages/user-settings"))
93
91
 
94
92
  class ErrorBoundary extends React.Component<
95
93
  { children: React.ReactNode },
@@ -270,8 +268,8 @@ function App() {
270
268
  {/* Organization creation route */}
271
269
  <Route path="/orgs/new" component={CreateOrganizationPage} />
272
270
 
273
- {/* User settings redirect */}
274
- <Route path="/settings" component={SettingsRedirectPage} />
271
+ {/* User settings */}
272
+ <Route path="/settings" component={UserSettingsPage} />
275
273
 
276
274
  {/* Organization settings route */}
277
275
  <Route
@@ -25,6 +25,7 @@ import { CubeIcon } from "@radix-ui/react-icons"
25
25
  import { useState } from "react"
26
26
  import { useAxios } from "@/hooks/use-axios"
27
27
  import { useCurrentPackageId } from "@/hooks/use-current-package-id"
28
+ import { downloadStepFile } from "@/lib/download-fns/download-step"
28
29
 
29
30
  interface DownloadButtonAndMenuProps {
30
31
  className?: string
@@ -225,7 +226,19 @@ export function DownloadButtonAndMenu({
225
226
  kicad_*.zip
226
227
  </span>
227
228
  </DropdownMenuItem>
228
-
229
+ <DropdownMenuItem
230
+ className="text-xs"
231
+ onSelect={async () => {
232
+ const cj = await getCircuitJson()
233
+ downloadStepFile(cj, unscopedName || "step_file")
234
+ }}
235
+ >
236
+ <Download className="mr-1 h-3 w-3" />
237
+ <span className="flex-grow mr-6">Step Format</span>
238
+ <span className="text-[0.6rem] bg-emerald-500 opacity-80 text-white font-mono rounded-md px-1 text-center py-0.5 mr-1">
239
+ STEP
240
+ </span>
241
+ </DropdownMenuItem>
229
242
  <DropdownMenuItem
230
243
  className="text-xs"
231
244
  onSelect={async () => {
@@ -0,0 +1,44 @@
1
+ import { useEffect, useRef } from "react"
2
+ import { Sentry } from "@/lib/sentry"
3
+
4
+ type NotFoundContext = "package" | "package_release"
5
+
6
+ type SentryNotFoundReporterProps = {
7
+ context: NotFoundContext
8
+ slug: string
9
+ status?: number
10
+ message?: string
11
+ }
12
+
13
+ export const SentryNotFoundReporter = ({
14
+ context,
15
+ slug,
16
+ status,
17
+ message,
18
+ }: SentryNotFoundReporterProps) => {
19
+ const hasLoggedRef = useRef(false)
20
+
21
+ useEffect(() => {
22
+ if (typeof window === "undefined") return
23
+ if (typeof slug !== "string" || slug.length === 0) return
24
+ if (typeof status === "number" && status !== 404) return
25
+ if (hasLoggedRef.current) return
26
+
27
+ hasLoggedRef.current = true
28
+
29
+ Sentry.captureMessage(`package:view:${context}-not-found`, {
30
+ level: "warning",
31
+ tags: {
32
+ slug,
33
+ },
34
+ extra: {
35
+ status,
36
+ message,
37
+ },
38
+ })
39
+ }, [context, slug, status, message])
40
+
41
+ return null
42
+ }
43
+
44
+ export default SentryNotFoundReporter
@@ -72,12 +72,14 @@ export const OrganizationCard: React.FC<OrganizationCardProps> = ({
72
72
  const handleSettingsClick = (e: React.MouseEvent) => {
73
73
  e.preventDefault()
74
74
  e.stopPropagation()
75
- window.location.href = `/${organization.name}/settings`
75
+ window.location.href = organization.is_personal_org
76
+ ? `/settings`
77
+ : `/${organization.name}/settings`
76
78
  }
77
79
 
78
80
  const cardContent = (
79
81
  <div
80
- className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 ${className}`}
82
+ className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 h-full ${className}`}
81
83
  onClick={handleCardClick}
82
84
  >
83
85
  <div className="flex items-start gap-4">
@@ -193,7 +195,7 @@ export const OrganizationCard: React.FC<OrganizationCardProps> = ({
193
195
  <Link
194
196
  key={organization.org_id}
195
197
  href={`/${organization.name}`}
196
- className="block"
198
+ className="block h-full"
197
199
  >
198
200
  {cardContent}
199
201
  </Link>
@@ -0,0 +1,30 @@
1
+ import { useEffect, useState } from "react"
2
+ import { useGlobalStore } from "@/hooks/use-global-store"
3
+
4
+ export const useHydration = () => {
5
+ const [hasHydrated, setHasHydrated] = useState(() => {
6
+ if (typeof window === "undefined") return false
7
+ return useGlobalStore.persist?.hasHydrated?.() ?? false
8
+ })
9
+
10
+ useEffect(() => {
11
+ if (typeof window === "undefined") return
12
+
13
+ if (useGlobalStore.persist?.hasHydrated?.()) {
14
+ setHasHydrated(true)
15
+ return
16
+ }
17
+
18
+ const unsubFinishHydration = useGlobalStore.persist?.onFinishHydration?.(
19
+ () => {
20
+ setHasHydrated(true)
21
+ },
22
+ )
23
+
24
+ return () => {
25
+ unsubFinishHydration?.()
26
+ }
27
+ }, [])
28
+
29
+ return hasHydrated
30
+ }
@@ -18,7 +18,7 @@ export const useOrgByGithubHandle = (githubHandle: string | null) => {
18
18
  return data.org
19
19
  },
20
20
  {
21
- enabled: Boolean(githubHandle && session),
21
+ enabled: Boolean(githubHandle),
22
22
  retry: false,
23
23
  refetchOnWindowFocus: false,
24
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)
@@ -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
  ) : (
@@ -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 (