@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.
- package/.github/workflows/formatbot.yml +63 -0
- package/bun-tests/fake-snippets-api/routes/accounts/get.test.ts +11 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +25 -0
- package/dist/bundle.js +15 -3
- package/fake-snippets-api/lib/db/autoload-packages.json +0 -28
- package/fake-snippets-api/routes/api/accounts/get.ts +2 -1
- package/fake-snippets-api/routes/api/package_releases/create.ts +14 -1
- package/package.json +1 -1
- package/src/components/PackageBuildsPage/package-build-header.tsx +1 -9
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +1 -0
- package/src/components/dialogs/edit-package-details-dialog.tsx +45 -11
- package/src/hooks/use-create-package-release-mutation.ts +63 -13
- package/src/hooks/use-package-details-form.ts +9 -1
- package/src/lib/templates/blank-package-template.ts +4 -5
- package/src/pages/quickstart.tsx +2 -2
- package/src/pages/user-profile.tsx +50 -15
|
@@ -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) =>
|
|
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,18 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
17
|
+
export const MyChip = (props: ChipProps<typeof pinLabels>) => (
|
|
18
|
+
<chip footprint="soic8" pinLabels={pinLabels} {...props} />
|
|
19
|
+
)
|
|
21
20
|
`.trim(),
|
|
22
21
|
}
|
package/src/pages/quickstart.tsx
CHANGED
|
@@ -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
|
|
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[]>(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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",
|
|
79
|
+
["starredPackages", githubUsername],
|
|
54
80
|
async () => {
|
|
55
81
|
const response = await axios.post(`/packages/list`, {
|
|
56
|
-
starred_by:
|
|
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
|
-
|
|
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/${
|
|
111
|
-
<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
|
|
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/${
|
|
159
|
+
href={`https://github.com/${githubUsername}`}
|
|
125
160
|
target="_blank"
|
|
126
161
|
rel="noopener noreferrer"
|
|
127
162
|
className="inline-flex items-center"
|