@tscircuit/fake-snippets 0.0.66 → 0.0.67
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-circuit-json.ts +5 -143
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +1 -4
- package/bun-tests/fake-snippets-api/fixtures/start-server.ts +7 -3
- package/bun-tests/fake-snippets-api/routes/order_quotes/create.test.ts +20 -56
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +2 -2
- package/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +0 -11
- package/bun.lock +15 -17
- package/dist/bundle.js +32 -39
- package/fake-snippets-api/routes/api/order_quotes/create.ts +30 -37
- package/fake-snippets-api/routes/api/order_quotes/get.ts +5 -8
- package/package.json +4 -3
- package/src/App.tsx +0 -7
- package/src/ContextProviders.tsx +2 -0
- package/src/components/DownloadButtonAndMenu.tsx +1 -4
- package/src/components/Footer.tsx +5 -2
- package/src/components/HeaderLogin.tsx +37 -54
- package/src/components/ImageWithFallback.tsx +37 -0
- package/src/components/JLCPCBImportDialog.tsx +43 -24
- package/src/components/PackageCard.tsx +2 -2
- package/src/components/{SnippetLink.tsx → PackageLink.tsx} +8 -16
- package/src/components/PackageSearchResults.tsx +87 -0
- package/src/components/PackagesList.tsx +3 -3
- package/src/components/PageSearchComponent.tsx +9 -9
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +5 -28
- package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -8
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +24 -14
- package/src/components/ViewPackagePage/components/package-header.tsx +6 -1
- package/src/components/package-port/CodeEditor.tsx +13 -10
- package/src/components/package-port/CodeEditorHeader.tsx +1 -1
- package/src/components/package-port/EditorNav.tsx +2 -2
- package/src/hooks/use-global-store.ts +1 -0
- package/src/hooks/use-shiki-highlighter.ts +13 -6
- package/src/lib/download-fns/download-gltf.ts +3 -10
- package/src/lib/handleManualEditsImport.tsx +1 -1
- package/src/lib/types.ts +4 -2
- package/src/pages/dashboard.tsx +3 -4
- package/src/pages/editor.tsx +20 -14
- package/src/pages/latest.tsx +25 -26
- package/src/pages/search.tsx +120 -19
- package/src/pages/trending.tsx +14 -58
- package/src/pages/user-profile.tsx +13 -8
- package/bun-tests/fake-snippets-api/routes/snippets/add_star.test.ts +0 -84
- package/bun-tests/fake-snippets-api/routes/snippets/create.test.ts +0 -53
- package/bun-tests/fake-snippets-api/routes/snippets/delete.test.ts +0 -82
- package/bun-tests/fake-snippets-api/routes/snippets/download.test.ts +0 -90
- package/bun-tests/fake-snippets-api/routes/snippets/generate_from_jlcpcb.test.ts +0 -16
- package/bun-tests/fake-snippets-api/routes/snippets/get.test.ts +0 -163
- package/bun-tests/fake-snippets-api/routes/snippets/get_image.test.ts +0 -117
- package/bun-tests/fake-snippets-api/routes/snippets/images.test.ts +0 -114
- package/bun-tests/fake-snippets-api/routes/snippets/list.test.ts +0 -169
- package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +0 -50
- package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +0 -72
- package/bun-tests/fake-snippets-api/routes/snippets/remove_star.test.ts +0 -80
- package/bun-tests/fake-snippets-api/routes/snippets/search.test.ts +0 -75
- package/bun-tests/fake-snippets-api/routes/snippets/star-count.test.ts +0 -51
- package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +0 -175
- package/src/components/AiChatInterface.tsx +0 -229
- package/src/components/CodeAndPreview.tsx +0 -289
- package/src/components/CodeEditor.tsx +0 -539
- package/src/components/CodeEditorHeader.tsx +0 -135
- package/src/components/EditorNav.tsx +0 -502
- package/src/components/PreviewContent.tsx +0 -372
- package/src/components/SnippetCard.tsx +0 -159
- package/src/components/SnippetList.tsx +0 -71
- package/src/hooks/use-compiled-tsx.ts +0 -37
- package/src/hooks/use-run-tsx/construct-circuit.tsx +0 -62
- package/src/hooks/use-run-tsx/index.tsx +0 -256
- package/src/hooks/use-save-snippet.ts +0 -66
- package/src/hooks/use-typecheck.ts +0 -54
- package/src/lib/utils/getSyntaxError.ts +0 -13
- package/src/pages/ai.tsx +0 -92
- package/src/pages/view-snippet.tsx +0 -166
package/src/pages/latest.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
2
|
import { useQuery } from "react-query"
|
|
3
3
|
import { useAxios } from "@/hooks/use-axios"
|
|
4
|
-
import {
|
|
4
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
5
5
|
import Header from "@/components/Header"
|
|
6
6
|
import Footer from "@/components/Footer"
|
|
7
7
|
import {
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
SelectTrigger,
|
|
24
24
|
SelectValue,
|
|
25
25
|
} from "@/components/ui/select"
|
|
26
|
-
import { SnippetCard } from "@/components/SnippetCard"
|
|
27
26
|
import { PackageCardSkeleton } from "@/components/PackageCardSkeleton"
|
|
27
|
+
import { PackageCard } from "@/components/PackageCard"
|
|
28
28
|
|
|
29
29
|
const LatestPage: React.FC = () => {
|
|
30
30
|
const axios = useAxios()
|
|
@@ -33,30 +33,30 @@ const LatestPage: React.FC = () => {
|
|
|
33
33
|
const [category, setCategory] = useState("all")
|
|
34
34
|
|
|
35
35
|
const {
|
|
36
|
-
data:
|
|
36
|
+
data: packages,
|
|
37
37
|
isLoading,
|
|
38
38
|
error,
|
|
39
|
-
} = useQuery<
|
|
40
|
-
["
|
|
39
|
+
} = useQuery<Package[]>(
|
|
40
|
+
["latestPackages", category],
|
|
41
41
|
async () => {
|
|
42
42
|
const params = category !== "all" ? { tag: category } : {}
|
|
43
|
-
const response = await axios.get("/
|
|
44
|
-
return response.data.
|
|
43
|
+
const response = await axios.get("/packages/list_latest", { params })
|
|
44
|
+
return response.data.packages
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
keepPreviousData: true,
|
|
48
48
|
},
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const filteredPackages = packages?.filter((pkg) => {
|
|
52
52
|
if (!searchQuery) return true
|
|
53
53
|
|
|
54
54
|
const query = searchQuery.toLowerCase().trim()
|
|
55
55
|
|
|
56
56
|
const searchableFields = [
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
(
|
|
57
|
+
pkg.unscoped_name.toLowerCase(),
|
|
58
|
+
pkg.owner_github_username?.toLowerCase() ?? "",
|
|
59
|
+
(pkg.description || "").toLowerCase(),
|
|
60
60
|
]
|
|
61
61
|
|
|
62
62
|
return searchableFields.some((field) => {
|
|
@@ -73,7 +73,7 @@ const LatestPage: React.FC = () => {
|
|
|
73
73
|
<div className="flex items-center gap-2 mb-3">
|
|
74
74
|
<Calendar className="w-6 h-6 text-blue-500" />
|
|
75
75
|
<h1 className="text-4xl font-bold text-gray-900">
|
|
76
|
-
Latest
|
|
76
|
+
Latest Packages
|
|
77
77
|
</h1>
|
|
78
78
|
</div>
|
|
79
79
|
<p className="text-lg text-gray-600 mb-4">
|
|
@@ -99,7 +99,7 @@ const LatestPage: React.FC = () => {
|
|
|
99
99
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
100
100
|
<Input
|
|
101
101
|
type="text"
|
|
102
|
-
placeholder="Search latest
|
|
102
|
+
placeholder="Search latest packages..."
|
|
103
103
|
value={searchQuery}
|
|
104
104
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
105
105
|
className="pl-10"
|
|
@@ -154,40 +154,39 @@ const LatestPage: React.FC = () => {
|
|
|
154
154
|
</div>
|
|
155
155
|
<div>
|
|
156
156
|
<h3 className="text-lg font-semibold mb-2">
|
|
157
|
-
Error Loading
|
|
157
|
+
Error Loading Packages
|
|
158
158
|
</h3>
|
|
159
159
|
<p className="text-red-600">
|
|
160
|
-
We couldn't load the latest
|
|
160
|
+
We couldn't load the latest packages. Please try again later.
|
|
161
161
|
</p>
|
|
162
162
|
</div>
|
|
163
163
|
</div>
|
|
164
164
|
</div>
|
|
165
|
-
) :
|
|
165
|
+
) : filteredPackages?.length === 0 ? (
|
|
166
166
|
<div className="text-center py-12 px-4">
|
|
167
167
|
<div className="bg-slate-50 inline-flex rounded-full p-4 mb-4">
|
|
168
168
|
<Search className="w-8 h-8 text-slate-400" />
|
|
169
169
|
</div>
|
|
170
170
|
<h3 className="text-xl font-medium text-slate-900 mb-2">
|
|
171
|
-
No Matching
|
|
171
|
+
No Matching Packages
|
|
172
172
|
</h3>
|
|
173
173
|
<p className="text-slate-500 max-w-md mx-auto mb-6">
|
|
174
174
|
{searchQuery
|
|
175
|
-
? `No
|
|
175
|
+
? `No packages match your search for "${searchQuery}".`
|
|
176
176
|
: category !== "all"
|
|
177
|
-
? `No ${category}
|
|
178
|
-
: "There are no new
|
|
177
|
+
? `No ${category} packages found in the latest list.`
|
|
178
|
+
: "There are no new packages at the moment."}
|
|
179
179
|
</p>
|
|
180
180
|
</div>
|
|
181
181
|
) : (
|
|
182
182
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
183
|
-
{
|
|
183
|
+
{filteredPackages
|
|
184
184
|
?.sort((a, b) => b.created_at.localeCompare(a.created_at))
|
|
185
|
-
?.map((
|
|
186
|
-
<
|
|
187
|
-
key={
|
|
188
|
-
|
|
185
|
+
?.map((pkg) => (
|
|
186
|
+
<PackageCard
|
|
187
|
+
key={pkg.package_id}
|
|
188
|
+
pkg={pkg}
|
|
189
189
|
baseUrl={apiBaseUrl}
|
|
190
|
-
showOwner={true}
|
|
191
190
|
/>
|
|
192
191
|
))}
|
|
193
192
|
</div>
|
package/src/pages/search.tsx
CHANGED
|
@@ -1,13 +1,92 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react"
|
|
2
|
+
import { useQuery } from "react-query"
|
|
3
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
4
|
+
import { useSearchParams } from "wouter"
|
|
1
5
|
import Header from "@/components/Header"
|
|
2
6
|
import Footer from "@/components/Footer"
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { Input } from "@/components/ui/input"
|
|
8
|
+
import { Search } from "lucide-react"
|
|
9
|
+
import PackageSearchResults from "@/components/PackageSearchResults"
|
|
10
|
+
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
} from "@/components/ui/select"
|
|
18
|
+
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
8
19
|
|
|
9
20
|
export const SearchPage = () => {
|
|
10
|
-
const
|
|
21
|
+
const axios = useAxios()
|
|
22
|
+
const apiBaseUrl = useSnippetsBaseApiUrl()
|
|
23
|
+
const [searchParams, setSearchParams] = useSearchParams()
|
|
24
|
+
|
|
25
|
+
const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "")
|
|
26
|
+
const [category, setCategory] = useState(
|
|
27
|
+
searchParams.get("category") || "all",
|
|
28
|
+
)
|
|
29
|
+
const [sortBy, setSortBy] = useState(searchParams.get("sort") || "stars")
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const params = new URLSearchParams()
|
|
33
|
+
if (searchQuery) params.set("q", searchQuery)
|
|
34
|
+
if (category !== "all") params.set("category", category)
|
|
35
|
+
if (sortBy !== "stars") params.set("sort", sortBy)
|
|
36
|
+
setSearchParams(params)
|
|
37
|
+
}, [searchQuery, category, sortBy, setSearchParams])
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
data: packages,
|
|
41
|
+
isLoading,
|
|
42
|
+
error,
|
|
43
|
+
} = useQuery(
|
|
44
|
+
["packageSearch", searchQuery, category],
|
|
45
|
+
async () => {
|
|
46
|
+
const params = new URLSearchParams()
|
|
47
|
+
if (searchQuery) params.append("q", searchQuery)
|
|
48
|
+
if (category !== "all") params.append("category", category)
|
|
49
|
+
|
|
50
|
+
const response = await axios.post(`/packages/search`, {
|
|
51
|
+
query: searchQuery,
|
|
52
|
+
})
|
|
53
|
+
return response.data.packages
|
|
54
|
+
},
|
|
55
|
+
{ enabled: Boolean(searchQuery), keepPreviousData: true },
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const filteredPackages = packages
|
|
59
|
+
?.filter((pkg: Package) => {
|
|
60
|
+
if (!searchQuery) return true
|
|
61
|
+
|
|
62
|
+
const query = searchQuery.toLowerCase().trim()
|
|
63
|
+
const searchableFields = [
|
|
64
|
+
pkg.unscoped_name.toLowerCase(),
|
|
65
|
+
(pkg.owner_github_username || "").toLowerCase(),
|
|
66
|
+
(pkg.description || "").toLowerCase(),
|
|
67
|
+
pkg.description?.toLowerCase(),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
return searchableFields.some((field) => {
|
|
71
|
+
const queryWords = query.split(/\s+/).filter((word) => word.length > 0)
|
|
72
|
+
if (!field) return false
|
|
73
|
+
return queryWords.every((word) => field.includes(word))
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
?.sort((a: Package, b: Package) => {
|
|
77
|
+
if (sortBy === "stars") {
|
|
78
|
+
return (b.star_count || 0) - (a.star_count || 0)
|
|
79
|
+
} else if (sortBy === "newest") {
|
|
80
|
+
return (
|
|
81
|
+
new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
82
|
+
)
|
|
83
|
+
} else if (sortBy === "oldest") {
|
|
84
|
+
return (
|
|
85
|
+
new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime()
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return 0
|
|
89
|
+
})
|
|
11
90
|
|
|
12
91
|
return (
|
|
13
92
|
<div className="min-h-screen flex flex-col">
|
|
@@ -17,26 +96,46 @@ export const SearchPage = () => {
|
|
|
17
96
|
<div className="max-w-8xl mx-auto">
|
|
18
97
|
<div className="mb-6">
|
|
19
98
|
<div className="flex items-center gap-2 mb-3">
|
|
20
|
-
<Search className="w-6 h-6 text-blue-500" />
|
|
21
99
|
<h1 className="text-3xl font-bold text-gray-900">
|
|
22
100
|
Search tscircuit Packages
|
|
23
101
|
</h1>
|
|
24
102
|
</div>
|
|
25
|
-
<div className="flex flex-
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
103
|
+
<div className="flex flex-col sm:flex-row gap-4 mb-4">
|
|
104
|
+
<div className="relative flex-grow">
|
|
105
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
106
|
+
<Input
|
|
107
|
+
type="search"
|
|
108
|
+
placeholder="Search packages..."
|
|
109
|
+
className="pl-10"
|
|
110
|
+
value={searchQuery}
|
|
111
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
112
|
+
aria-label="Search packages"
|
|
113
|
+
role="searchbox"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
<Select value={sortBy} onValueChange={setSortBy}>
|
|
117
|
+
<SelectTrigger className="w-[140px]">
|
|
118
|
+
<SelectValue placeholder="Sort By" />
|
|
119
|
+
</SelectTrigger>
|
|
120
|
+
<SelectContent>
|
|
121
|
+
<SelectItem value="stars">Most Starred</SelectItem>
|
|
122
|
+
<SelectItem value="newest">Newest</SelectItem>
|
|
123
|
+
<SelectItem value="oldest">Oldest</SelectItem>
|
|
124
|
+
</SelectContent>
|
|
125
|
+
</Select>
|
|
35
126
|
</div>
|
|
36
127
|
</div>
|
|
37
128
|
|
|
38
|
-
<
|
|
39
|
-
|
|
129
|
+
<PackageSearchResults
|
|
130
|
+
isLoading={isLoading}
|
|
131
|
+
error={error}
|
|
132
|
+
filteredPackages={filteredPackages}
|
|
133
|
+
apiBaseUrl={apiBaseUrl}
|
|
134
|
+
emptyStateMessage={
|
|
135
|
+
searchQuery
|
|
136
|
+
? `No packages match your search for "${searchQuery}".`
|
|
137
|
+
: "Please enter a search query to find packages."
|
|
138
|
+
}
|
|
40
139
|
/>
|
|
41
140
|
</div>
|
|
42
141
|
</div>
|
|
@@ -45,3 +144,5 @@ export const SearchPage = () => {
|
|
|
45
144
|
</div>
|
|
46
145
|
)
|
|
47
146
|
}
|
|
147
|
+
|
|
148
|
+
export default SearchPage
|
package/src/pages/trending.tsx
CHANGED
|
@@ -14,16 +14,14 @@ import {
|
|
|
14
14
|
SelectTrigger,
|
|
15
15
|
SelectValue,
|
|
16
16
|
} from "@/components/ui/select"
|
|
17
|
-
import { PackageCardSkeleton } from "@/components/PackageCardSkeleton"
|
|
18
17
|
import { Package } from "fake-snippets-api/lib/db/schema"
|
|
19
|
-
import
|
|
18
|
+
import PackageSearchResults from "@/components/PackageSearchResults"
|
|
20
19
|
|
|
21
20
|
const TrendingPage: React.FC = () => {
|
|
22
21
|
const axios = useAxios()
|
|
23
22
|
const apiBaseUrl = useSnippetsBaseApiUrl()
|
|
24
23
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
25
24
|
|
|
26
|
-
// Initialize state from URL params or defaults
|
|
27
25
|
const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "")
|
|
28
26
|
const [category, setCategory] = useState(
|
|
29
27
|
searchParams.get("category") || "all",
|
|
@@ -33,7 +31,6 @@ const TrendingPage: React.FC = () => {
|
|
|
33
31
|
)
|
|
34
32
|
const [sortBy, setSortBy] = useState(searchParams.get("sort") || "stars")
|
|
35
33
|
|
|
36
|
-
// Update URL params when filters change
|
|
37
34
|
useEffect(() => {
|
|
38
35
|
const params = new URLSearchParams()
|
|
39
36
|
if (searchQuery) params.set("q", searchQuery)
|
|
@@ -47,7 +44,6 @@ const TrendingPage: React.FC = () => {
|
|
|
47
44
|
data: packages,
|
|
48
45
|
isLoading,
|
|
49
46
|
error,
|
|
50
|
-
refetch,
|
|
51
47
|
} = useQuery<Package[]>(
|
|
52
48
|
["trendingPackages", category, time_period],
|
|
53
49
|
async () => {
|
|
@@ -70,7 +66,6 @@ const TrendingPage: React.FC = () => {
|
|
|
70
66
|
if (!searchQuery) return true
|
|
71
67
|
|
|
72
68
|
const query = searchQuery.toLowerCase().trim()
|
|
73
|
-
|
|
74
69
|
const searchableFields = [
|
|
75
70
|
pkg.unscoped_name.toLowerCase(),
|
|
76
71
|
(pkg.owner_github_username || "").toLowerCase(),
|
|
@@ -175,58 +170,19 @@ const TrendingPage: React.FC = () => {
|
|
|
175
170
|
</Select>
|
|
176
171
|
</div>
|
|
177
172
|
</div>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<div>
|
|
192
|
-
<h3 className="text-lg font-semibold mb-2">
|
|
193
|
-
Error Loading packages
|
|
194
|
-
</h3>
|
|
195
|
-
<p className="text-red-600">
|
|
196
|
-
We couldn't load the trending packages. Please try again
|
|
197
|
-
later.
|
|
198
|
-
</p>
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
) : filteredPackages?.length === 0 ? (
|
|
203
|
-
<div className="text-center py-12 px-4">
|
|
204
|
-
<div className="bg-slate-50 inline-flex rounded-full p-4 mb-4">
|
|
205
|
-
<Search className="w-8 h-8 text-slate-400" />
|
|
206
|
-
</div>
|
|
207
|
-
<h3 className="text-xl font-medium text-slate-900 mb-2">
|
|
208
|
-
No Matching Packages
|
|
209
|
-
</h3>
|
|
210
|
-
<p className="text-slate-500 max-w-md mx-auto mb-6">
|
|
211
|
-
{searchQuery
|
|
212
|
-
? `No packages match your search for "${searchQuery}".`
|
|
213
|
-
: category !== "all"
|
|
214
|
-
? `No ${category} packages found in the trending list.`
|
|
215
|
-
: "There are no trending packages at the moment."}
|
|
216
|
-
</p>
|
|
217
|
-
</div>
|
|
218
|
-
) : (
|
|
219
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
220
|
-
{filteredPackages?.map((pkg) => (
|
|
221
|
-
<PackageCard
|
|
222
|
-
key={pkg.package_id}
|
|
223
|
-
pkg={pkg}
|
|
224
|
-
baseUrl={apiBaseUrl}
|
|
225
|
-
showOwner={true}
|
|
226
|
-
/>
|
|
227
|
-
))}
|
|
228
|
-
</div>
|
|
229
|
-
)}
|
|
173
|
+
<PackageSearchResults
|
|
174
|
+
isLoading={isLoading}
|
|
175
|
+
error={error}
|
|
176
|
+
filteredPackages={filteredPackages}
|
|
177
|
+
apiBaseUrl={apiBaseUrl}
|
|
178
|
+
emptyStateMessage={
|
|
179
|
+
searchQuery
|
|
180
|
+
? `No packages match your search for "${searchQuery}".`
|
|
181
|
+
: category !== "all"
|
|
182
|
+
? `No ${category} packages found in the trending list.`
|
|
183
|
+
: "There are no trending packages at the moment."
|
|
184
|
+
}
|
|
185
|
+
/>
|
|
230
186
|
</main>
|
|
231
187
|
<Footer />
|
|
232
188
|
</div>
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
SelectValue,
|
|
24
24
|
} from "@/components/ui/select"
|
|
25
25
|
import { Box, Star } from "lucide-react"
|
|
26
|
+
import { PackageCardSkeleton } from "@/components/PackageCardSkeleton"
|
|
26
27
|
|
|
27
28
|
export const UserProfilePage = () => {
|
|
28
29
|
const { username } = useParams()
|
|
@@ -159,10 +160,10 @@ export const UserProfilePage = () => {
|
|
|
159
160
|
</Select>
|
|
160
161
|
</div>
|
|
161
162
|
{isLoading ? (
|
|
162
|
-
<div>
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
164
|
+
{[...Array(6)].map((_, i) => (
|
|
165
|
+
<PackageCardSkeleton key={i} />
|
|
166
|
+
))}
|
|
166
167
|
</div>
|
|
167
168
|
) : (
|
|
168
169
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
@@ -184,16 +185,20 @@ export const UserProfilePage = () => {
|
|
|
184
185
|
<div className="flex flex-col items-center py-12 text-gray-500">
|
|
185
186
|
{activeTab === "starred" ? (
|
|
186
187
|
<>
|
|
187
|
-
<Star />
|
|
188
|
+
<Star className="mb-2" size={24} />
|
|
188
189
|
<span className="text-lg font-medium">
|
|
189
|
-
|
|
190
|
+
{searchQuery.trim()
|
|
191
|
+
? `No starred packages matching '${searchQuery.trim()}'`
|
|
192
|
+
: "No starred packages"}
|
|
190
193
|
</span>
|
|
191
194
|
</>
|
|
192
195
|
) : (
|
|
193
196
|
<>
|
|
194
|
-
<Box />
|
|
197
|
+
<Box className="mb-2" size={24} />
|
|
195
198
|
<span className="text-lg font-medium">
|
|
196
|
-
|
|
199
|
+
{searchQuery.trim()
|
|
200
|
+
? `No packages matching '${searchQuery.trim()}'`
|
|
201
|
+
: "No packages available"}
|
|
197
202
|
</span>
|
|
198
203
|
</>
|
|
199
204
|
)}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server"
|
|
2
|
-
import { expect, test } from "bun:test"
|
|
3
|
-
|
|
4
|
-
test("add star to snippet", async () => {
|
|
5
|
-
const { axios } = await getTestServer()
|
|
6
|
-
|
|
7
|
-
// Create a test snippet using the create endpoint
|
|
8
|
-
const newSnippetData = {
|
|
9
|
-
code: "Test Content",
|
|
10
|
-
snippet_type: "package",
|
|
11
|
-
description: "Test Description",
|
|
12
|
-
}
|
|
13
|
-
const createResponse = await axios.post(
|
|
14
|
-
"/api/snippets/create",
|
|
15
|
-
newSnippetData,
|
|
16
|
-
)
|
|
17
|
-
expect(createResponse.status).toBe(200)
|
|
18
|
-
const createdSnippet = createResponse.data.snippet
|
|
19
|
-
|
|
20
|
-
// Star the snippet
|
|
21
|
-
const response = await axios.post("/api/snippets/add_star", {
|
|
22
|
-
snippet_id: createdSnippet.snippet_id,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
expect(response.status).toBe(200)
|
|
26
|
-
expect(response.data.ok).toBe(true)
|
|
27
|
-
|
|
28
|
-
// Verify star was added by checking the snippet again
|
|
29
|
-
const getResponse = await axios.get("/api/snippets/get", {
|
|
30
|
-
params: { snippet_id: createdSnippet.snippet_id },
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
expect(getResponse.status).toBe(200)
|
|
34
|
-
expect(getResponse.data.snippet.is_starred).toBe(true)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test("add star to non-existent snippet", async () => {
|
|
38
|
-
const { axios } = await getTestServer()
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
await axios.post("/api/snippets/add_star", {
|
|
42
|
-
snippet_id: "non-existent-id",
|
|
43
|
-
})
|
|
44
|
-
expect(true).toBe(false) // Should not reach here
|
|
45
|
-
} catch (error: any) {
|
|
46
|
-
expect(error.status).toBe(404)
|
|
47
|
-
expect(error.data.error.message).toBe("Snippet not found")
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
test("add star to already starred snippet", async () => {
|
|
52
|
-
const { axios } = await getTestServer()
|
|
53
|
-
|
|
54
|
-
// Create a test snippet using the create endpoint
|
|
55
|
-
const newSnippetData = {
|
|
56
|
-
code: "Test Content",
|
|
57
|
-
snippet_type: "package",
|
|
58
|
-
description: "Test Description",
|
|
59
|
-
}
|
|
60
|
-
const createResponse = await axios.post(
|
|
61
|
-
"/api/snippets/create",
|
|
62
|
-
newSnippetData,
|
|
63
|
-
)
|
|
64
|
-
expect(createResponse.status).toBe(200)
|
|
65
|
-
const createdSnippet = createResponse.data.snippet
|
|
66
|
-
|
|
67
|
-
// Star the snippet first time
|
|
68
|
-
await axios.post("/api/snippets/add_star", {
|
|
69
|
-
snippet_id: createdSnippet.snippet_id,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
// Try to star again
|
|
73
|
-
try {
|
|
74
|
-
await axios.post("/api/snippets/add_star", {
|
|
75
|
-
snippet_id: createdSnippet.snippet_id,
|
|
76
|
-
})
|
|
77
|
-
expect(true).toBe(false) // Should not reach here
|
|
78
|
-
} catch (error: any) {
|
|
79
|
-
expect(error.status).toBe(400)
|
|
80
|
-
expect(error.data.error.message).toBe(
|
|
81
|
-
"You have already starred this snippet",
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
})
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server"
|
|
2
|
-
import { expect, test } from "bun:test"
|
|
3
|
-
|
|
4
|
-
test("create snippet", async () => {
|
|
5
|
-
const { axios } = await getTestServer()
|
|
6
|
-
|
|
7
|
-
const response = await axios.post("/api/snippets/create", {
|
|
8
|
-
unscoped_name: "TestSnippet",
|
|
9
|
-
code: "Test Content",
|
|
10
|
-
snippet_type: "package",
|
|
11
|
-
description: "Test Description",
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
expect(response.status).toBe(200)
|
|
15
|
-
expect(response.data.snippet.unscoped_name).toBe("TestSnippet")
|
|
16
|
-
expect(response.data.snippet.owner_name).toBe("testuser")
|
|
17
|
-
expect(response.data.snippet.code).toBe("Test Content")
|
|
18
|
-
expect(response.data.snippet.snippet_type).toBe("package")
|
|
19
|
-
expect(response.data.snippet.description).toBe("Test Description")
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test("create snippet and call package get to verify it exists", async () => {
|
|
23
|
-
const { axios, db } = await getTestServer()
|
|
24
|
-
|
|
25
|
-
const response = await axios.post("/api/snippets/create", {
|
|
26
|
-
unscoped_name: "example-package",
|
|
27
|
-
code: "console.log('Hello, world!');",
|
|
28
|
-
snippet_type: "package",
|
|
29
|
-
description: "Test Description",
|
|
30
|
-
})
|
|
31
|
-
const packageResponse = await axios.get(`/api/packages/get`, {
|
|
32
|
-
params: {
|
|
33
|
-
package_id: response.data.snippet.snippet_id,
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
expect(packageResponse.status).toBe(200)
|
|
37
|
-
expect(packageResponse.data.package.unscoped_name).toBe("example-package")
|
|
38
|
-
expect(packageResponse.data.package.owner_github_username).toBe("testuser")
|
|
39
|
-
expect(packageResponse.data.package.description).toBe("Test Description")
|
|
40
|
-
|
|
41
|
-
// Add package file verification
|
|
42
|
-
const packageFileResponse = await axios.post(`/api/package_files/get`, {
|
|
43
|
-
package_release_id: packageResponse.data.package.latest_package_release_id,
|
|
44
|
-
file_path: "index.tsx",
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
expect(packageFileResponse.status).toBe(200)
|
|
48
|
-
expect(packageFileResponse.data.ok).toBe(true)
|
|
49
|
-
expect(packageFileResponse.data.package_file).toBeDefined()
|
|
50
|
-
expect(packageFileResponse.data.package_file.content_text).toBe(
|
|
51
|
-
"console.log('Hello, world!');",
|
|
52
|
-
)
|
|
53
|
-
})
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server"
|
|
2
|
-
import { test, expect } from "bun:test"
|
|
3
|
-
|
|
4
|
-
test("delete snippet", async () => {
|
|
5
|
-
const { axios, db } = await getTestServer()
|
|
6
|
-
|
|
7
|
-
// Add a test snippet
|
|
8
|
-
const snippet = {
|
|
9
|
-
unscoped_name: "TestSnippet",
|
|
10
|
-
owner_name: "testuser",
|
|
11
|
-
code: "Test Content",
|
|
12
|
-
created_at: "2023-01-01T00:00:00Z",
|
|
13
|
-
updated_at: "2023-01-01T00:00:00Z",
|
|
14
|
-
name: "testuser/TestSnippet",
|
|
15
|
-
snippet_type: "package",
|
|
16
|
-
description: "Test Description",
|
|
17
|
-
}
|
|
18
|
-
const addedSnippet: any = db.addSnippet(snippet as any)
|
|
19
|
-
|
|
20
|
-
// Delete the snippet
|
|
21
|
-
const response = await axios.post("/api/snippets/delete", {
|
|
22
|
-
snippet_id: addedSnippet.snippet_id,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
expect(response.status).toBe(200)
|
|
26
|
-
expect(response.data.ok).toBe(true)
|
|
27
|
-
|
|
28
|
-
// Verify the snippet was deleted from the database
|
|
29
|
-
const deletedSnippet = db.getSnippetById(addedSnippet.snippet_id)
|
|
30
|
-
expect(deletedSnippet).toBeUndefined()
|
|
31
|
-
|
|
32
|
-
// List all the snippets and verify the deleted snippet is not in the list
|
|
33
|
-
const listResponse = await axios.get("/api/snippets/list")
|
|
34
|
-
|
|
35
|
-
expect(listResponse.status).toBe(200)
|
|
36
|
-
expect(listResponse.data.snippets).toHaveLength(0)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test("delete non-existent snippet", async () => {
|
|
40
|
-
const { axios } = await getTestServer()
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
await axios.post("/api/snippets/delete", {
|
|
44
|
-
snippet_id: "non-existent-id",
|
|
45
|
-
})
|
|
46
|
-
// If the request doesn't throw an error, fail the test
|
|
47
|
-
expect(true).toBe(false)
|
|
48
|
-
} catch (error: any) {
|
|
49
|
-
expect(error.status).toBe(404)
|
|
50
|
-
expect(error.data.error.message).toBe("Snippet not found")
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test("delete snippet without permission", async () => {
|
|
55
|
-
const { axios, db } = await getTestServer()
|
|
56
|
-
|
|
57
|
-
// Add a test snippet with a different owner
|
|
58
|
-
const snippet = {
|
|
59
|
-
unscoped_name: "TestSnippet",
|
|
60
|
-
owner_name: "otheruser",
|
|
61
|
-
code: "Test Content",
|
|
62
|
-
created_at: "2023-01-01T00:00:00Z",
|
|
63
|
-
updated_at: "2023-01-01T00:00:00Z",
|
|
64
|
-
name: "otheruser/TestSnippet",
|
|
65
|
-
snippet_type: "package",
|
|
66
|
-
description: "Test Description",
|
|
67
|
-
}
|
|
68
|
-
const addedSnippet: any = db.addSnippet(snippet as any)
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
await axios.post("/api/snippets/delete", {
|
|
72
|
-
snippet_id: addedSnippet.snippet_id,
|
|
73
|
-
})
|
|
74
|
-
// If the request doesn't throw an error, fail the test
|
|
75
|
-
expect(true).toBe(false)
|
|
76
|
-
} catch (error: any) {
|
|
77
|
-
expect(error.status).toBe(403)
|
|
78
|
-
expect(error.data.error.message).toBe(
|
|
79
|
-
"You don't have permission to delete this snippet",
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
})
|