@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
|
@@ -7,15 +7,16 @@ export default withRouteSpec({
|
|
|
7
7
|
methods: ["GET", "POST"],
|
|
8
8
|
commonParams: z.object({
|
|
9
9
|
name: z.string(),
|
|
10
|
+
github_handle: z.string().optional(),
|
|
10
11
|
}),
|
|
11
12
|
auth: "session",
|
|
12
13
|
jsonResponse: z.object({
|
|
13
14
|
org: publicOrgSchema,
|
|
14
15
|
}),
|
|
15
16
|
})(async (req, ctx) => {
|
|
16
|
-
const { name } = req.commonParams
|
|
17
|
+
const { name, github_handle } = req.commonParams
|
|
17
18
|
|
|
18
|
-
const existing = ctx.db.getOrg({
|
|
19
|
+
const existing = ctx.db.getOrg({ org_name: name })
|
|
19
20
|
|
|
20
21
|
if (existing) {
|
|
21
22
|
return ctx.error(400, {
|
|
@@ -28,6 +29,7 @@ export default withRouteSpec({
|
|
|
28
29
|
name: name,
|
|
29
30
|
created_at: new Date(),
|
|
30
31
|
can_manage_org: true,
|
|
32
|
+
...(github_handle ? { github_handle } : {}),
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const org = ctx.db.addOrganization(newOrg)
|
|
@@ -9,7 +9,7 @@ export default withRouteSpec({
|
|
|
9
9
|
.object({ org_id: z.string() })
|
|
10
10
|
.or(z.object({ org_name: z.string() }))
|
|
11
11
|
.or(z.object({ github_handle: z.string() })),
|
|
12
|
-
auth: "
|
|
12
|
+
auth: "optional_session",
|
|
13
13
|
jsonResponse: z.object({
|
|
14
14
|
org: publicOrgSchema,
|
|
15
15
|
}),
|
|
@@ -13,7 +13,7 @@ export default withRouteSpec({
|
|
|
13
13
|
name: z.string(),
|
|
14
14
|
}),
|
|
15
15
|
),
|
|
16
|
-
auth: "
|
|
16
|
+
auth: "optional_session",
|
|
17
17
|
jsonResponse: z.object({
|
|
18
18
|
members: z.array(accountSchema),
|
|
19
19
|
}),
|
|
@@ -35,13 +35,6 @@ export default withRouteSpec({
|
|
|
35
35
|
})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
if (!org.can_manage_org) {
|
|
39
|
-
return ctx.error(403, {
|
|
40
|
-
error_code: "not_authorized",
|
|
41
|
-
message: "You do not have permission to manage this organization",
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
38
|
const members = ctx.db.orgAccounts
|
|
46
39
|
.map((m) => {
|
|
47
40
|
if (m.org_id == org.org_id) return ctx.db.getAccount(m.account_id)
|
|
@@ -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.
|
|
52
|
+
if (name && name !== org.org_name) {
|
|
51
53
|
// Validate duplicate name
|
|
52
|
-
const duplicate = ctx.db.getOrg({
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.0.112",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
79
79
|
"@sentry/react": "^10.10.0",
|
|
80
80
|
"@tailwindcss/typography": "^0.5.16",
|
|
81
|
-
"@tscircuit/3d-viewer": "^0.0.
|
|
81
|
+
"@tscircuit/3d-viewer": "^0.0.407",
|
|
82
82
|
"@tscircuit/assembly-viewer": "^0.0.5",
|
|
83
83
|
"@tscircuit/create-snippet-url": "^0.0.8",
|
|
84
84
|
"@tscircuit/eval": "^0.0.325",
|
|
@@ -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.22",
|
|
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.10",
|
|
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
|
|
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
|
|
274
|
-
<Route path="/settings" component={
|
|
271
|
+
{/* User settings */}
|
|
272
|
+
<Route path="/settings" component={UserSettingsPage} />
|
|
275
273
|
|
|
276
274
|
{/* Organization settings route */}
|
|
277
275
|
<Route
|
|
@@ -372,7 +372,7 @@ const CmdKMenu = () => {
|
|
|
372
372
|
switch (type) {
|
|
373
373
|
case "package":
|
|
374
374
|
case "recent":
|
|
375
|
-
window.location.href = `/${item.
|
|
375
|
+
window.location.href = `/${item.name}`
|
|
376
376
|
setOpen(false)
|
|
377
377
|
break
|
|
378
378
|
case "account":
|
|
@@ -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,33 @@
|
|
|
1
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
2
|
+
|
|
3
|
+
export const GithubAvatarWithFallback = ({
|
|
4
|
+
username,
|
|
5
|
+
fallback,
|
|
6
|
+
className,
|
|
7
|
+
fallbackClassName,
|
|
8
|
+
}: {
|
|
9
|
+
username?: string | null
|
|
10
|
+
fallback?: string | null
|
|
11
|
+
className?: string
|
|
12
|
+
fallbackClassName?: string
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<Avatar className={`border-4 border-gray-100 ${className}`}>
|
|
16
|
+
<AvatarImage
|
|
17
|
+
src={`https://github.com/${username}.png`}
|
|
18
|
+
alt={`${username || fallback} avatar`}
|
|
19
|
+
className="object-cover"
|
|
20
|
+
/>
|
|
21
|
+
<AvatarFallback
|
|
22
|
+
className={`bg-blue-100 text-blue-600 ${fallbackClassName}`}
|
|
23
|
+
>
|
|
24
|
+
{(username || fallback || "")
|
|
25
|
+
.split(" ")
|
|
26
|
+
.map((word) => word[0])
|
|
27
|
+
.join("")
|
|
28
|
+
.toUpperCase()
|
|
29
|
+
.slice(0, 2)}
|
|
30
|
+
</AvatarFallback>
|
|
31
|
+
</Avatar>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -67,7 +67,7 @@ export const PackageCard: React.FC<PackageCardProps> = ({
|
|
|
67
67
|
|
|
68
68
|
const handleShareClick = (e: React.MouseEvent) => {
|
|
69
69
|
e.preventDefault()
|
|
70
|
-
const shareUrl = `${window.location.origin}/${pkg.
|
|
70
|
+
const shareUrl = `${window.location.origin}/${pkg.name}`
|
|
71
71
|
copyToClipboard(shareUrl)
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -178,10 +178,7 @@ export const PackageCard: React.FC<PackageCardProps> = ({
|
|
|
178
178
|
|
|
179
179
|
if (withLink) {
|
|
180
180
|
return (
|
|
181
|
-
<Link
|
|
182
|
-
key={pkg.package_id}
|
|
183
|
-
href={`/${pkg.owner_github_username}/${pkg.unscoped_name}`}
|
|
184
|
-
>
|
|
181
|
+
<Link key={pkg.package_id} href={`/${pkg.name}`}>
|
|
185
182
|
{cardContent}
|
|
186
183
|
</Link>
|
|
187
184
|
)
|
|
@@ -50,10 +50,10 @@ export const PackagesList = ({
|
|
|
50
50
|
<li key={pkg.package_id}>
|
|
51
51
|
<div className="flex items-center">
|
|
52
52
|
<Link
|
|
53
|
-
href={`/${pkg.
|
|
53
|
+
href={`/${pkg.name}`}
|
|
54
54
|
className="text-blue-600 hover:underline text-sm"
|
|
55
55
|
>
|
|
56
|
-
{pkg.
|
|
56
|
+
{pkg.name}
|
|
57
57
|
</Link>
|
|
58
58
|
{pkg.star_count > 0 && (
|
|
59
59
|
<span className="ml-2 text-gray-500 text-xs flex items-center">
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import { useParams } from "wouter"
|
|
3
|
-
import { useOrgByGithubHandle } from "@/hooks/use-org-by-github-handle"
|
|
4
3
|
import { OrganizationProfilePageContent } from "@/pages/organization-profile"
|
|
5
4
|
import { UserProfilePage } from "@/pages/user-profile"
|
|
6
5
|
import NotFoundPage from "@/pages/404"
|
|
7
6
|
import { FullPageLoader } from "@/App"
|
|
7
|
+
import { useOrgByName } from "@/hooks/use-org-by-org-name"
|
|
8
8
|
|
|
9
9
|
const ProfileRouter: React.FC = () => {
|
|
10
10
|
const { username } = useParams()
|
|
@@ -12,7 +12,7 @@ const ProfileRouter: React.FC = () => {
|
|
|
12
12
|
data: organization,
|
|
13
13
|
isLoading,
|
|
14
14
|
error,
|
|
15
|
-
} =
|
|
15
|
+
} = useOrgByName(username || null)
|
|
16
16
|
|
|
17
17
|
if (!username) {
|
|
18
18
|
return <NotFoundPage heading="Username Not Provided" />
|
|
@@ -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
|
|
@@ -11,10 +11,10 @@ const CarouselItem = ({
|
|
|
11
11
|
apiBaseUrl,
|
|
12
12
|
}: { pkg: Package; apiBaseUrl: string }) => {
|
|
13
13
|
return (
|
|
14
|
-
<Link href={`/${pkg.
|
|
14
|
+
<Link href={`/${pkg.name}`}>
|
|
15
15
|
<div className="flex-shrink-0 w-[200px] bg-white p-3 py-2 rounded-lg shadow-sm border border-gray-200 hover:border-gray-300 transition-colors">
|
|
16
16
|
<div className="font-medium text-blue-600 mb-1 truncate text-sm">
|
|
17
|
-
{pkg.
|
|
17
|
+
{pkg.name}
|
|
18
18
|
</div>
|
|
19
19
|
<div className="mb-2 h-24 w-full bg-black rounded overflow-hidden">
|
|
20
20
|
<img
|
|
@@ -81,7 +81,7 @@ export default function SidebarReleasesSection() {
|
|
|
81
81
|
<div className="mb-6">
|
|
82
82
|
<h2 className="text-lg font-semibold mb-2">
|
|
83
83
|
<Link
|
|
84
|
-
href={`/${packageInfo?.
|
|
84
|
+
href={`/${packageInfo?.name}/releases`}
|
|
85
85
|
className="hover:underline"
|
|
86
86
|
>
|
|
87
87
|
Releases
|
|
@@ -89,7 +89,7 @@ export default function SidebarReleasesSection() {
|
|
|
89
89
|
</h2>
|
|
90
90
|
<div className="flex flex-col space-y-2">
|
|
91
91
|
<Link
|
|
92
|
-
href={`/${packageInfo?.
|
|
92
|
+
href={`/${packageInfo?.name}/releases`}
|
|
93
93
|
className="flex items-center hover:underline"
|
|
94
94
|
>
|
|
95
95
|
<Tag className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
|
|
@@ -21,6 +21,7 @@ import { timeAgo } from "@/lib/utils/timeAgo"
|
|
|
21
21
|
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
|
22
22
|
import { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
|
|
23
23
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
24
|
+
import { GithubAvatarWithFallback } from "../GithubAvatarWithFallback"
|
|
24
25
|
|
|
25
26
|
export interface OrganizationCardProps {
|
|
26
27
|
/** The organization data to display */
|
|
@@ -72,32 +73,28 @@ export const OrganizationCard: React.FC<OrganizationCardProps> = ({
|
|
|
72
73
|
const handleSettingsClick = (e: React.MouseEvent) => {
|
|
73
74
|
e.preventDefault()
|
|
74
75
|
e.stopPropagation()
|
|
75
|
-
window.location.href =
|
|
76
|
+
window.location.href = organization.is_personal_org
|
|
77
|
+
? `/settings`
|
|
78
|
+
: `/${organization.name}/settings`
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
const cardContent = (
|
|
79
82
|
<div
|
|
80
|
-
className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 ${className}`}
|
|
83
|
+
className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 h-full ${className}`}
|
|
81
84
|
onClick={handleCardClick}
|
|
82
85
|
>
|
|
83
86
|
<div className="flex items-start gap-4">
|
|
84
87
|
{/* Organization Avatar */}
|
|
85
88
|
<div className="flex-shrink-0">
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.map((word) => word[0])
|
|
96
|
-
.join("")
|
|
97
|
-
.toUpperCase()
|
|
98
|
-
.slice(0, 2)}
|
|
99
|
-
</AvatarFallback>
|
|
100
|
-
</Avatar>
|
|
89
|
+
<GithubAvatarWithFallback
|
|
90
|
+
username={
|
|
91
|
+
organization.is_personal_org
|
|
92
|
+
? organization.github_handle || organization.name
|
|
93
|
+
: organization.github_handle
|
|
94
|
+
}
|
|
95
|
+
fallback={organization.name}
|
|
96
|
+
fallbackClassName=" font-semibold text-lg"
|
|
97
|
+
/>
|
|
101
98
|
</div>
|
|
102
99
|
|
|
103
100
|
{/* Organization Info */}
|
|
@@ -193,7 +190,7 @@ export const OrganizationCard: React.FC<OrganizationCardProps> = ({
|
|
|
193
190
|
<Link
|
|
194
191
|
key={organization.org_id}
|
|
195
192
|
href={`/${organization.name}`}
|
|
196
|
-
className="block"
|
|
193
|
+
className="block h-full"
|
|
197
194
|
>
|
|
198
195
|
{cardContent}
|
|
199
196
|
</Link>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
1
|
+
import React from "react"
|
|
3
2
|
import { Button } from "@/components/ui/button"
|
|
4
3
|
import { Building2, Users, Package, Lock, Globe2, Settings } from "lucide-react"
|
|
5
4
|
import { cn } from "@/lib/utils"
|
|
@@ -7,6 +6,7 @@ import { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
|
|
|
7
6
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
8
7
|
import { useLocation } from "wouter"
|
|
9
8
|
import { useOrganization } from "@/hooks/use-organization"
|
|
9
|
+
import { GithubAvatarWithFallback } from "../GithubAvatarWithFallback"
|
|
10
10
|
|
|
11
11
|
interface OrganizationHeaderProps {
|
|
12
12
|
organization: PublicOrgSchema
|
|
@@ -40,21 +40,12 @@ export const OrganizationHeader: React.FC<OrganizationHeaderProps> = ({
|
|
|
40
40
|
{/* Mobile Layout */}
|
|
41
41
|
<div className="block sm:hidden">
|
|
42
42
|
<div className="flex flex-col items-center text-center space-y-4">
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<AvatarFallback className="bg-blue-100 text-blue-600 font-bold text-xl md:text-2xl lg:text-3xl">
|
|
50
|
-
{(organization.name || "")
|
|
51
|
-
.split(" ")
|
|
52
|
-
.map((word) => word[0])
|
|
53
|
-
.join("")
|
|
54
|
-
.toUpperCase()
|
|
55
|
-
.slice(0, 2)}
|
|
56
|
-
</AvatarFallback>
|
|
57
|
-
</Avatar>
|
|
43
|
+
<GithubAvatarWithFallback
|
|
44
|
+
username={organization.github_handle}
|
|
45
|
+
fallback={organization.name}
|
|
46
|
+
className="shadow-sm size-16 md:size-20 lg:size-24"
|
|
47
|
+
fallbackClassName="font-bold text-xl md:text-2xl lg:text-3xl"
|
|
48
|
+
/>
|
|
58
49
|
|
|
59
50
|
<div>
|
|
60
51
|
<div className="flex flex-col items-center gap-3 mb-3">
|
|
@@ -96,21 +87,12 @@ export const OrganizationHeader: React.FC<OrganizationHeaderProps> = ({
|
|
|
96
87
|
{/* Desktop Layout */}
|
|
97
88
|
<div className="hidden sm:block">
|
|
98
89
|
<div className="flex items-center gap-6">
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<AvatarFallback className="bg-blue-100 text-blue-600 font-bold text-xl md:text-2xl lg:text-3xl">
|
|
106
|
-
{(organization.name || "")
|
|
107
|
-
.split(" ")
|
|
108
|
-
.map((word) => word[0])
|
|
109
|
-
.join("")
|
|
110
|
-
.toUpperCase()
|
|
111
|
-
.slice(0, 2)}
|
|
112
|
-
</AvatarFallback>
|
|
113
|
-
</Avatar>
|
|
90
|
+
<GithubAvatarWithFallback
|
|
91
|
+
username={organization.github_handle}
|
|
92
|
+
fallback={organization.name}
|
|
93
|
+
className="flex-shrink-0 shadow-sm size-16 md:size-20 lg:size-24"
|
|
94
|
+
fallbackClassName="font-bold text-xl md:text-2xl lg:text-3xl"
|
|
95
|
+
/>
|
|
114
96
|
|
|
115
97
|
<div className="flex-1 min-w-0">
|
|
116
98
|
<div className="flex items-center justify-between mb-3">
|
|
@@ -2,11 +2,9 @@ import React from "react"
|
|
|
2
2
|
import { Link } from "wouter"
|
|
3
3
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
4
4
|
import { Badge } from "@/components/ui/badge"
|
|
5
|
-
import { Button } from "@/components/ui/button"
|
|
6
5
|
import { Users, Crown, Shield, User, Loader2 } from "lucide-react"
|
|
7
6
|
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
8
7
|
import { cn } from "@/lib/utils"
|
|
9
|
-
import { Account } from "fake-snippets-api/lib/db/schema"
|
|
10
8
|
import { useListOrgMembers } from "@/hooks/use-list-org-members"
|
|
11
9
|
|
|
12
10
|
interface OrganizationMembersProps {
|
|
@@ -69,7 +67,7 @@ export const OrganizationMembers: React.FC<OrganizationMembersProps> = ({
|
|
|
69
67
|
return (
|
|
70
68
|
<div
|
|
71
69
|
className={cn(
|
|
72
|
-
"bg-white rounded-lg border border-gray-200 p-4
|
|
70
|
+
"bg-white rounded-lg border border-gray-200 p-4 sm:p-6",
|
|
73
71
|
className,
|
|
74
72
|
)}
|
|
75
73
|
>
|
|
@@ -117,7 +117,7 @@ export const ConnectedPackageCard = ({
|
|
|
117
117
|
<div className="flex items-start justify-between mb-4">
|
|
118
118
|
<div className="flex items-center gap-3">
|
|
119
119
|
<Link
|
|
120
|
-
href={`/${pkg.
|
|
120
|
+
href={`/${pkg.name}`}
|
|
121
121
|
className="text-lg font-semibold text-gray-900 hover:text-blue-600 transition-colors"
|
|
122
122
|
>
|
|
123
123
|
{pkg.unscoped_name}
|
|
@@ -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
|
+
}
|