@tscircuit/fake-snippets 0.0.34 → 0.0.35
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/bun-test.yml +3 -0
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +12 -1
- package/bun-tests/fake-snippets-api/routes/accounts/get_account_balance.test.ts +1 -5
- package/bun-tests/fake-snippets-api/routes/orders/create.test.ts +0 -1
- package/bun-tests/fake-snippets-api/routes/orders/list.test.ts +3 -3
- package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +28 -84
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +12 -36
- package/bun-tests/fake-snippets-api/routes/package_releases/list.test.ts +4 -12
- package/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts +16 -48
- package/bun-tests/fake-snippets-api/routes/packages/add_star.test.ts +12 -44
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +4 -12
- package/bun-tests/fake-snippets-api/routes/packages/delete.test.ts +15 -41
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +1 -5
- package/bun-tests/fake-snippets-api/routes/packages/list-2.test.ts +6 -19
- package/bun-tests/fake-snippets-api/routes/packages/remove_star.test.ts +9 -36
- package/bun-tests/fake-snippets-api/routes/packages/update.test.ts +26 -74
- package/bun-tests/fake-snippets-api/routes/snippets/add_star.test.ts +12 -47
- package/bun-tests/fake-snippets-api/routes/snippets/create.test.ts +6 -14
- package/bun-tests/fake-snippets-api/routes/snippets/delete.test.ts +9 -33
- package/bun-tests/fake-snippets-api/routes/snippets/generate_from_jlcpcb.test.ts +3 -12
- package/bun-tests/fake-snippets-api/routes/snippets/list.test.ts +9 -34
- package/bun-tests/fake-snippets-api/routes/snippets/remove_star.test.ts +12 -44
- package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +12 -36
- package/bun.lock +27 -29
- package/dist/bundle.js +45 -27
- package/fake-snippets-api/lib/middleware/with-optional-session-auth.ts +27 -8
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +17 -6
- package/fake-snippets-api/routes/api/snippets/add_star.ts +2 -4
- package/fake-snippets-api/routes/api/snippets/create.ts +8 -10
- package/package.json +5 -4
- package/src/App.tsx +3 -0
- package/src/components/CodeAndPreview.tsx +2 -2
- package/src/components/CodeEditor.tsx +1 -1
- package/src/components/DownloadButtonAndMenu.tsx +13 -11
- package/src/components/Footer.tsx +3 -0
- package/src/components/SnippetCard.tsx +159 -0
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +89 -27
- package/src/pages/404.tsx +56 -0
- package/src/pages/trending.tsx +222 -0
- package/src/pages/user-profile.tsx +11 -102
- package/src/pages/view-package.tsx +18 -2
- package/src/lib/templates/manual-edits-template.ts +0 -5
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react"
|
|
2
|
+
import { useQuery } from "react-query"
|
|
3
|
+
import { useAxios } from "@/hooks/use-axios"
|
|
4
|
+
import { Snippet } from "fake-snippets-api/lib/db/schema"
|
|
5
|
+
import Header from "@/components/Header"
|
|
6
|
+
import Footer from "@/components/Footer"
|
|
7
|
+
import { Link } from "wouter"
|
|
8
|
+
import { StarIcon, LockClosedIcon } from "@radix-ui/react-icons"
|
|
9
|
+
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
10
|
+
import { OptimizedImage } from "@/components/OptimizedImage"
|
|
11
|
+
import {
|
|
12
|
+
GlobeIcon,
|
|
13
|
+
PencilIcon,
|
|
14
|
+
Zap,
|
|
15
|
+
Tag,
|
|
16
|
+
Calendar,
|
|
17
|
+
Search,
|
|
18
|
+
Keyboard,
|
|
19
|
+
Cpu,
|
|
20
|
+
Layers,
|
|
21
|
+
LucideBellElectric,
|
|
22
|
+
} from "lucide-react"
|
|
23
|
+
import { Input } from "@/components/ui/input"
|
|
24
|
+
import { Badge } from "@/components/ui/badge"
|
|
25
|
+
import { SnippetTypeIcon } from "@/components/SnippetTypeIcon"
|
|
26
|
+
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
27
|
+
import {
|
|
28
|
+
Select,
|
|
29
|
+
SelectContent,
|
|
30
|
+
SelectItem,
|
|
31
|
+
SelectTrigger,
|
|
32
|
+
SelectValue,
|
|
33
|
+
} from "@/components/ui/select"
|
|
34
|
+
import { SnippetCard } from "@/components/SnippetCard"
|
|
35
|
+
|
|
36
|
+
const TrendingPage: React.FC = () => {
|
|
37
|
+
const axios = useAxios()
|
|
38
|
+
const apiBaseUrl = useSnippetsBaseApiUrl()
|
|
39
|
+
const [searchQuery, setSearchQuery] = useState("")
|
|
40
|
+
const [category, setCategory] = useState("all")
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
data: snippets,
|
|
44
|
+
isLoading,
|
|
45
|
+
error,
|
|
46
|
+
refetch,
|
|
47
|
+
} = useQuery<Snippet[]>(
|
|
48
|
+
["trendingSnippets", category],
|
|
49
|
+
async () => {
|
|
50
|
+
const params = category !== "all" ? { tag: category } : {}
|
|
51
|
+
const response = await axios.get("/snippets/list_trending", { params })
|
|
52
|
+
return response.data.snippets
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
keepPreviousData: true,
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const filteredSnippets = snippets?.filter((snippet) => {
|
|
60
|
+
if (!searchQuery) return true
|
|
61
|
+
|
|
62
|
+
const query = searchQuery.toLowerCase().trim()
|
|
63
|
+
|
|
64
|
+
const searchableFields = [
|
|
65
|
+
snippet.unscoped_name.toLowerCase(),
|
|
66
|
+
snippet.owner_name.toLowerCase(),
|
|
67
|
+
(snippet.description || "").toLowerCase(),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
return searchableFields.some((field) => {
|
|
71
|
+
const queryWords = query.split(/\s+/).filter((word) => word.length > 0)
|
|
72
|
+
return queryWords.every((word) => field.includes(word))
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className="min-h-screen flex flex-col bg-gray-50">
|
|
78
|
+
<Header />
|
|
79
|
+
<main className="flex-grow container mx-auto px-4 py-8">
|
|
80
|
+
<div className="mb-8 max-w-3xl">
|
|
81
|
+
<div className="flex items-center gap-2 mb-3">
|
|
82
|
+
<Zap className="w-6 h-6 text-amber-500" />
|
|
83
|
+
<h1 className="text-4xl font-bold text-gray-900">
|
|
84
|
+
Trending Snippets
|
|
85
|
+
</h1>
|
|
86
|
+
</div>
|
|
87
|
+
<p className="text-lg text-gray-600 mb-4">
|
|
88
|
+
Discover the most popular and innovative snippets from the community
|
|
89
|
+
over the last 7 days. These trending designs showcase the best in
|
|
90
|
+
circuit creativity and technical excellence.
|
|
91
|
+
</p>
|
|
92
|
+
<div className="flex flex-wrap gap-3">
|
|
93
|
+
<Badge variant="secondary" className="px-3 py-1">
|
|
94
|
+
<Tag className="w-3.5 h-3.5 mr-1" />
|
|
95
|
+
<span>Most Starred</span>
|
|
96
|
+
</Badge>
|
|
97
|
+
<Badge variant="secondary" className="px-3 py-1">
|
|
98
|
+
<Calendar className="w-3.5 h-3.5 mr-1" />
|
|
99
|
+
<span>Last 7 Days</span>
|
|
100
|
+
</Badge>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="mb-6">
|
|
105
|
+
<div className="flex flex-col sm:flex-row gap-4 mb-4">
|
|
106
|
+
<div className="relative flex-grow">
|
|
107
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
108
|
+
<Input
|
|
109
|
+
type="text"
|
|
110
|
+
placeholder="Search trending snippets..."
|
|
111
|
+
value={searchQuery}
|
|
112
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
113
|
+
className="pl-10"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
<Select value={category} onValueChange={setCategory}>
|
|
117
|
+
<SelectTrigger className="w-full sm:w-[180px]">
|
|
118
|
+
<SelectValue placeholder="Category" />
|
|
119
|
+
</SelectTrigger>
|
|
120
|
+
<SelectContent>
|
|
121
|
+
<SelectItem value="all">All Categories</SelectItem>
|
|
122
|
+
<SelectItem value="keyboard">
|
|
123
|
+
<div className="flex items-center">
|
|
124
|
+
<Keyboard className="mr-2 h-4 w-4" />
|
|
125
|
+
<span>Keyboards</span>
|
|
126
|
+
</div>
|
|
127
|
+
</SelectItem>
|
|
128
|
+
<SelectItem value="microcontroller">
|
|
129
|
+
<div className="flex items-center">
|
|
130
|
+
<Cpu className="mr-2 h-4 w-4" />
|
|
131
|
+
<span>Microcontrollers</span>
|
|
132
|
+
</div>
|
|
133
|
+
</SelectItem>
|
|
134
|
+
<SelectItem value="connector">
|
|
135
|
+
<div className="flex items-center">
|
|
136
|
+
<Layers className="mr-2 h-4 w-4" />
|
|
137
|
+
<span>Connectors</span>
|
|
138
|
+
</div>
|
|
139
|
+
</SelectItem>
|
|
140
|
+
<SelectItem value="sensor">
|
|
141
|
+
<div className="flex items-center">
|
|
142
|
+
<LucideBellElectric className="mr-2 h-4 w-4" />
|
|
143
|
+
<span>Sensors</span>
|
|
144
|
+
</div>
|
|
145
|
+
</SelectItem>
|
|
146
|
+
</SelectContent>
|
|
147
|
+
</Select>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{isLoading ? (
|
|
152
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
153
|
+
{[...Array(6)].map((_, i) => (
|
|
154
|
+
<div key={i} className="border p-4 rounded-md animate-pulse">
|
|
155
|
+
<div className="flex items-start gap-4">
|
|
156
|
+
<div className="h-16 w-16 flex-shrink-0 rounded-md bg-slate-200"></div>
|
|
157
|
+
<div className="flex-1">
|
|
158
|
+
<div className="h-5 bg-slate-200 rounded w-3/4 mb-2"></div>
|
|
159
|
+
<div className="h-4 bg-slate-200 rounded w-1/2 mb-2"></div>
|
|
160
|
+
<div className="flex gap-2">
|
|
161
|
+
<div className="h-3 bg-slate-200 rounded w-16"></div>
|
|
162
|
+
<div className="h-3 bg-slate-200 rounded w-16"></div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
) : error ? (
|
|
170
|
+
<div className="bg-red-50 border border-red-200 text-red-700 p-6 rounded-xl shadow-sm max-w-2xl mx-auto">
|
|
171
|
+
<div className="flex items-start">
|
|
172
|
+
<div className="mr-4 bg-red-100 p-2 rounded-full">
|
|
173
|
+
<Search className="w-6 h-6 text-red-600" />
|
|
174
|
+
</div>
|
|
175
|
+
<div>
|
|
176
|
+
<h3 className="text-lg font-semibold mb-2">
|
|
177
|
+
Error Loading Snippets
|
|
178
|
+
</h3>
|
|
179
|
+
<p className="text-red-600">
|
|
180
|
+
We couldn't load the trending snippets. Please try again
|
|
181
|
+
later.
|
|
182
|
+
</p>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
) : filteredSnippets?.length === 0 ? (
|
|
187
|
+
<div className="text-center py-12 px-4">
|
|
188
|
+
<div className="bg-slate-50 inline-flex rounded-full p-4 mb-4">
|
|
189
|
+
<Search className="w-8 h-8 text-slate-400" />
|
|
190
|
+
</div>
|
|
191
|
+
<h3 className="text-xl font-medium text-slate-900 mb-2">
|
|
192
|
+
No Matching Snippets
|
|
193
|
+
</h3>
|
|
194
|
+
<p className="text-slate-500 max-w-md mx-auto mb-6">
|
|
195
|
+
{searchQuery
|
|
196
|
+
? `No snippets match your search for "${searchQuery}".`
|
|
197
|
+
: category !== "all"
|
|
198
|
+
? `No ${category} snippets found in the trending list.`
|
|
199
|
+
: "There are no trending snippets at the moment."}
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
) : (
|
|
203
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
204
|
+
{filteredSnippets
|
|
205
|
+
?.sort((a, b) => b.updated_at.localeCompare(a.updated_at))
|
|
206
|
+
?.map((snippet) => (
|
|
207
|
+
<SnippetCard
|
|
208
|
+
key={snippet.snippet_id}
|
|
209
|
+
snippet={snippet}
|
|
210
|
+
baseUrl={apiBaseUrl}
|
|
211
|
+
showOwner={true}
|
|
212
|
+
/>
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
</main>
|
|
217
|
+
<Footer />
|
|
218
|
+
</div>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export default TrendingPage
|
|
@@ -5,25 +5,15 @@ import { useAxios } from "@/hooks/use-axios"
|
|
|
5
5
|
import Header from "@/components/Header"
|
|
6
6
|
import Footer from "@/components/Footer"
|
|
7
7
|
import { Snippet } from "fake-snippets-api/lib/db/schema"
|
|
8
|
-
import { Link } from "wouter"
|
|
9
8
|
import { Button } from "@/components/ui/button"
|
|
10
|
-
import { GitHubLogoIcon
|
|
9
|
+
import { GitHubLogoIcon } from "@radix-ui/react-icons"
|
|
11
10
|
import { Input } from "@/components/ui/input"
|
|
12
11
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
13
|
-
import { GlobeIcon, MoreVertical, PencilIcon, Trash2 } from "lucide-react"
|
|
14
12
|
import { useConfirmDeleteSnippetDialog } from "@/components/dialogs/confirm-delete-snippet-dialog"
|
|
15
13
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
|
|
16
|
-
import {
|
|
17
|
-
DropdownMenu,
|
|
18
|
-
DropdownMenuContent,
|
|
19
|
-
DropdownMenuItem,
|
|
20
|
-
DropdownMenuTrigger,
|
|
21
|
-
} from "@/components/ui/dropdown-menu"
|
|
22
14
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
23
|
-
import { OptimizedImage } from "@/components/OptimizedImage"
|
|
24
15
|
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
|
|
25
|
-
import {
|
|
26
|
-
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
16
|
+
import { SnippetCard } from "@/components/SnippetCard"
|
|
27
17
|
|
|
28
18
|
export const UserProfilePage = () => {
|
|
29
19
|
const { username } = useParams()
|
|
@@ -132,97 +122,16 @@ export const UserProfilePage = () => {
|
|
|
132
122
|
{filteredSnippets
|
|
133
123
|
?.sort((a, b) => b.updated_at.localeCompare(a.updated_at))
|
|
134
124
|
?.map((snippet) => (
|
|
135
|
-
<
|
|
125
|
+
<SnippetCard
|
|
136
126
|
key={snippet.snippet_id}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
className="object-cover h-full w-full transition-transform duration-300 -rotate-45 hover:rotate-0 hover:scale-110 scale-150"
|
|
146
|
-
/>
|
|
147
|
-
</div>
|
|
148
|
-
<div className="flex-1 min-w-0">
|
|
149
|
-
<div className="flex justify-between items-start mb-[2px] -mt-[3px]">
|
|
150
|
-
<h2 className="text-md font-semibold truncate pr-[30px]">
|
|
151
|
-
{activeTab === "starred" && (
|
|
152
|
-
<>
|
|
153
|
-
<span className="text-gray-700 text-md">
|
|
154
|
-
{snippet.owner_name}
|
|
155
|
-
</span>
|
|
156
|
-
<span className="mx-1">/</span>
|
|
157
|
-
</>
|
|
158
|
-
)}
|
|
159
|
-
<span className="text-gray-900">
|
|
160
|
-
{snippet.unscoped_name}
|
|
161
|
-
</span>
|
|
162
|
-
</h2>
|
|
163
|
-
<div className="flex items-center gap-2">
|
|
164
|
-
<SnippetTypeIcon
|
|
165
|
-
type={snippet.snippet_type}
|
|
166
|
-
className="pt-[2.5px]"
|
|
167
|
-
/>
|
|
168
|
-
<div className="flex items-center gap-1 text-gray-600">
|
|
169
|
-
<StarIcon className="w-4 h-4 pt-[2.5px]" />
|
|
170
|
-
<span className="text-[16px]">
|
|
171
|
-
{snippet.star_count || 0}
|
|
172
|
-
</span>
|
|
173
|
-
</div>
|
|
174
|
-
{isCurrentUserProfile && activeTab === "all" && (
|
|
175
|
-
<DropdownMenu>
|
|
176
|
-
<DropdownMenuTrigger asChild>
|
|
177
|
-
<Button
|
|
178
|
-
variant="ghost"
|
|
179
|
-
size="icon"
|
|
180
|
-
className="h-[1.5rem] w-[1.5rem]"
|
|
181
|
-
>
|
|
182
|
-
<MoreVertical className="h-4 w-4" />
|
|
183
|
-
</Button>
|
|
184
|
-
</DropdownMenuTrigger>
|
|
185
|
-
<DropdownMenuContent>
|
|
186
|
-
<DropdownMenuItem
|
|
187
|
-
className="text-xs text-red-600"
|
|
188
|
-
onClick={(e) =>
|
|
189
|
-
handleDeleteClick(e, snippet)
|
|
190
|
-
}
|
|
191
|
-
>
|
|
192
|
-
<Trash2 className="mr-2 h-3 w-3" />
|
|
193
|
-
Delete Snippet
|
|
194
|
-
</DropdownMenuItem>
|
|
195
|
-
</DropdownMenuContent>
|
|
196
|
-
</DropdownMenu>
|
|
197
|
-
)}
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
<p
|
|
201
|
-
className={`${!snippet.description && "h-[1.25rem]"} text-sm text-gray-500 mb-1 truncate max-w-xs`}
|
|
202
|
-
>
|
|
203
|
-
{snippet.description ? snippet.description : " "}
|
|
204
|
-
</p>
|
|
205
|
-
<div className={`flex items-center gap-4`}>
|
|
206
|
-
{snippet.is_private ? (
|
|
207
|
-
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
208
|
-
<LockClosedIcon height={12} width={12} />
|
|
209
|
-
<span>Private</span>
|
|
210
|
-
</div>
|
|
211
|
-
) : (
|
|
212
|
-
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
213
|
-
<GlobeIcon height={12} width={12} />
|
|
214
|
-
<span>Public</span>
|
|
215
|
-
</div>
|
|
216
|
-
)}
|
|
217
|
-
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
218
|
-
<PencilIcon height={12} width={12} />
|
|
219
|
-
<span>{timeAgo(new Date(snippet.updated_at))}</span>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
</Link>
|
|
127
|
+
snippet={snippet}
|
|
128
|
+
baseUrl={baseUrl}
|
|
129
|
+
showOwner={activeTab === "starred"}
|
|
130
|
+
isCurrentUserSnippet={
|
|
131
|
+
isCurrentUserProfile && activeTab === "all"
|
|
132
|
+
}
|
|
133
|
+
onDeleteClick={handleDeleteClick}
|
|
134
|
+
/>
|
|
226
135
|
))}
|
|
227
136
|
</div>
|
|
228
137
|
)}
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import RepoPageContent from "@/components/ViewPackagePage/components/repo-page-content"
|
|
2
2
|
import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
|
|
3
|
-
import { usePackageByName } from "@/hooks/use-package-by-package-name"
|
|
4
3
|
import { usePackageFiles } from "@/hooks/use-package-files"
|
|
5
4
|
import { usePackageRelease } from "@/hooks/use-package-release"
|
|
6
5
|
import { useLocation, useParams } from "wouter"
|
|
7
6
|
import { Helmet } from "react-helmet-async"
|
|
7
|
+
import { useEffect, useState } from "react"
|
|
8
|
+
import NotFoundPage from "./404"
|
|
8
9
|
|
|
9
10
|
export const ViewPackagePage = () => {
|
|
10
11
|
const { packageInfo } = useCurrentPackageInfo()
|
|
11
12
|
const { author, packageName } = useParams()
|
|
12
13
|
const [, setLocation] = useLocation()
|
|
14
|
+
const [isNotFound, setIsNotFound] = useState(false)
|
|
13
15
|
|
|
14
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
data: packageRelease,
|
|
18
|
+
error: packageReleaseError,
|
|
19
|
+
isLoading: isLoadingPackageRelease,
|
|
20
|
+
} = usePackageRelease({
|
|
15
21
|
is_latest: true,
|
|
16
22
|
package_name: `${author}/${packageName}`,
|
|
17
23
|
})
|
|
@@ -19,6 +25,16 @@ export const ViewPackagePage = () => {
|
|
|
19
25
|
const { data: packageFiles } = usePackageFiles(
|
|
20
26
|
packageRelease?.package_release_id,
|
|
21
27
|
)
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (isLoadingPackageRelease) return
|
|
30
|
+
if (packageReleaseError?.status == 404) {
|
|
31
|
+
setIsNotFound(true)
|
|
32
|
+
}
|
|
33
|
+
}, [isLoadingPackageRelease, packageReleaseError])
|
|
34
|
+
|
|
35
|
+
if (isNotFound) {
|
|
36
|
+
return <NotFoundPage heading="Package Not Found" />
|
|
37
|
+
}
|
|
22
38
|
|
|
23
39
|
return (
|
|
24
40
|
<>
|