@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.
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +1 -0
- package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +2 -2
- package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +3 -2
- package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +52 -0
- package/bun-tests/fake-snippets-api/routes/packages/list_latest.test.ts +4 -4
- package/bun.lock +19 -7
- package/dist/bundle.js +85 -36
- package/dist/index.d.ts +33 -13
- package/dist/index.js +13 -6
- package/dist/schema.d.ts +27 -8
- package/dist/schema.js +6 -3
- package/fake-snippets-api/lib/db/db-client.ts +7 -3
- package/fake-snippets-api/lib/db/schema.ts +4 -2
- package/fake-snippets-api/lib/db/seed.ts +2 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +59 -7
- package/fake-snippets-api/lib/public-mapping/public-map-org.ts +4 -2
- package/fake-snippets-api/routes/api/orgs/create.ts +4 -2
- package/fake-snippets-api/routes/api/orgs/get.ts +1 -1
- package/fake-snippets-api/routes/api/orgs/list_members.ts +1 -8
- package/fake-snippets-api/routes/api/orgs/update.ts +31 -6
- package/fake-snippets-api/routes/api/packages/create.ts +5 -2
- package/package.json +7 -5
- package/src/App.tsx +3 -5
- package/src/components/CmdKMenu.tsx +1 -1
- package/src/components/DownloadButtonAndMenu.tsx +14 -1
- package/src/components/GithubAvatarWithFallback.tsx +33 -0
- package/src/components/PackageCard.tsx +2 -5
- package/src/components/PackagesList.tsx +2 -2
- package/src/components/ProfileRouter.tsx +2 -2
- package/src/components/SentryNotFoundReporter.tsx +44 -0
- package/src/components/TrendingPackagesCarousel.tsx +2 -2
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +2 -2
- package/src/components/organization/OrganizationCard.tsx +15 -18
- package/src/components/organization/OrganizationHeader.tsx +14 -32
- package/src/components/organization/OrganizationMembers.tsx +1 -3
- package/src/components/preview/ConnectedPackagesList.tsx +1 -1
- package/src/hooks/use-hydration.ts +30 -0
- package/src/hooks/use-org-by-github-handle.ts +1 -3
- package/src/hooks/use-org-by-org-name.ts +24 -0
- package/src/lib/download-fns/download-kicad-files.ts +10 -0
- package/src/lib/download-fns/download-step.ts +12 -0
- package/src/pages/create-organization.tsx +1 -0
- package/src/pages/editor.tsx +1 -3
- package/src/pages/organization-settings.tsx +4 -1
- package/src/pages/search.tsx +7 -2
- package/src/pages/user-profile.tsx +1 -1
- package/src/pages/user-settings.tsx +161 -0
- 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
|
|
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)
|
package/src/pages/editor.tsx
CHANGED
|
@@ -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">
|
package/src/pages/search.tsx
CHANGED
|
@@ -28,7 +28,8 @@ interface ScoredPackage extends Package {
|
|
|
28
28
|
matches: number[]
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
interface ScoredAccount
|
|
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
|
|
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
|
|
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
|
|
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 (
|