@tscircuit/fake-snippets 0.0.108 → 0.0.110
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-formatcheck.yml +2 -2
- package/.github/workflows/bun-pver-release.yml +3 -3
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +2 -2
- package/.github/workflows/update-snapshots.yml +1 -1
- package/README.md +4 -0
- package/api/generated-index.js +37 -3
- package/biome.json +2 -1
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +31 -3
- package/bun-tests/fake-snippets-api/fixtures/preload.ts +18 -0
- package/bun-tests/fake-snippets-api/routes/orgs/add_member.test.ts +26 -0
- package/bun-tests/fake-snippets-api/routes/orgs/create.test.ts +37 -0
- package/bun-tests/fake-snippets-api/routes/orgs/get.test.ts +52 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list.test.ts +17 -0
- package/bun-tests/fake-snippets-api/routes/orgs/list_members.test.ts +23 -0
- package/bun-tests/fake-snippets-api/routes/orgs/remove_member.test.ts +81 -0
- package/bun-tests/fake-snippets-api/routes/orgs/update.test.ts +99 -0
- package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +1 -1
- package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +15 -13
- package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +26 -24
- package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +9 -9
- package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +38 -28
- package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +23 -15
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +33 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +4 -4
- package/bun-tests/fake-snippets-api/routes/package_releases/get_image_generation_fields.test.ts +38 -0
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +19 -0
- package/bun-tests/fake-snippets-api/routes/packages/fork.test.ts +3 -4
- package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +30 -0
- package/bun-tests/fake-snippets-api/routes/packages/images.test.ts +4 -2
- package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +34 -0
- package/bun.lock +389 -450
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1255 -625
- package/dist/index.d.ts +296 -4
- package/dist/index.js +325 -24
- package/dist/schema.d.ts +282 -1
- package/dist/schema.js +54 -2
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +214 -3
- package/fake-snippets-api/lib/db/schema.ts +62 -0
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +1 -1
- package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +2 -2
- package/fake-snippets-api/lib/public-mapping/public-map-org.ts +32 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +10 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +17 -0
- package/fake-snippets-api/routes/api/orgs/add_member.ts +52 -0
- package/fake-snippets-api/routes/api/orgs/create.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/get.ts +39 -0
- package/fake-snippets-api/routes/api/orgs/list.ts +31 -0
- package/fake-snippets-api/routes/api/orgs/list_members.ts +67 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +93 -0
- package/fake-snippets-api/routes/api/package_files/get.ts +3 -6
- package/fake-snippets-api/routes/api/package_files/list.ts +7 -4
- package/fake-snippets-api/routes/api/packages/create.ts +54 -10
- package/fake-snippets-api/routes/api/packages/get.ts +23 -0
- package/fake-snippets-api/routes/api/packages/images/[owner_github_username]/[unscoped_name]/[view_format].ts +13 -11
- package/fake-snippets-api/routes/api/packages/list.ts +29 -2
- package/fake-snippets-api/routes/api/packages/update_ai_description.ts +37 -0
- package/package.json +27 -24
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +29 -10
- package/src/ContextProviders.tsx +25 -2
- package/src/components/CircuitJsonImportDialog.tsx +1 -1
- package/src/components/CmdKMenu.tsx +281 -247
- package/src/components/DownloadButtonAndMenu.tsx +133 -36
- package/src/components/FileSidebar.tsx +41 -50
- package/src/components/Footer.tsx +8 -10
- package/src/components/Header.tsx +19 -32
- package/src/components/Header2.tsx +16 -32
- package/src/components/HeaderDropdown.tsx +13 -8
- package/src/components/HeaderLogin.tsx +44 -16
- package/src/components/HiddenFilesDropdown.tsx +0 -2
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- package/src/components/PackageCard.tsx +0 -1
- package/src/components/PackageSearchResults.tsx +1 -1
- package/src/components/PrefetchPageLink.tsx +7 -1
- package/src/components/ProfileRouter.tsx +32 -0
- package/src/components/SearchComponent.tsx +12 -8
- package/src/components/UserCard.tsx +80 -0
- package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
- package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +174 -87
- package/src/components/ViewPackagePage/components/main-content-header.tsx +8 -4
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -20
- package/src/components/ViewPackagePage/components/package-header.tsx +26 -37
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -19
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +33 -25
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +16 -10
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +11 -11
- package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
- package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
- package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -1
- package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
- package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
- package/src/components/dialogs/GitHubRepositorySelector.tsx +56 -49
- package/src/components/dialogs/edit-package-details-dialog.tsx +5 -6
- package/src/components/dialogs/import-component-dialog.tsx +16 -9
- package/src/components/dialogs/import-package-dialog.tsx +3 -2
- package/src/components/dialogs/new-package-save-prompt-dialog.tsx +190 -0
- package/src/components/organization/OrganizationCard.tsx +204 -0
- package/src/components/organization/OrganizationCardSkeleton.tsx +55 -0
- package/src/components/organization/OrganizationHeader.tsx +154 -0
- package/src/components/organization/OrganizationMembers.tsx +146 -0
- package/src/components/package-port/CodeAndPreview.tsx +32 -46
- package/src/components/package-port/CodeEditor.tsx +28 -31
- package/src/components/package-port/CodeEditorHeader.tsx +128 -63
- package/src/components/package-port/EditorNav.tsx +32 -49
- package/src/components/preview/ConnectedPackagesList.tsx +8 -8
- package/src/components/preview/ConnectedRepoOverview.tsx +102 -2
- package/src/components/preview/PackageReleasesDashboard.tsx +53 -36
- package/src/components/ui/tree-view.tsx +6 -3
- package/src/hooks/use-add-org-member-mutation.ts +51 -0
- package/src/hooks/use-create-org-mutation.ts +38 -0
- package/src/hooks/use-create-package-mutation.ts +3 -0
- package/src/hooks/use-current-package-id.ts +5 -30
- package/src/hooks/use-current-package-info.ts +29 -5
- package/src/hooks/use-current-package-release.ts +4 -3
- package/src/hooks/use-download-zip.ts +2 -2
- package/src/hooks/use-global-store.ts +6 -4
- package/src/hooks/use-jlcpcb-component-import.tsx +164 -0
- package/src/hooks/use-list-org-members.ts +27 -0
- package/src/hooks/use-list-user-orgs.ts +25 -0
- package/src/hooks/use-org-by-github-handle.ts +26 -0
- package/src/hooks/use-org.ts +24 -0
- package/src/hooks/use-organization.ts +42 -0
- package/src/hooks/use-package-as-snippet.ts +4 -2
- package/src/hooks/use-package-builds.ts +6 -2
- package/src/hooks/use-package-files.ts +5 -3
- package/src/hooks/use-package-release-by-id-or-version.ts +29 -20
- package/src/hooks/use-package-release-images.ts +105 -0
- package/src/hooks/use-package-release.ts +2 -2
- package/src/hooks/use-package-stars.ts +80 -4
- package/src/hooks/use-preview-images.ts +6 -3
- package/src/hooks/use-remove-org-member-mutation.ts +32 -0
- package/src/hooks/use-update-ai-description-mutation.ts +42 -0
- package/src/hooks/use-update-org-mutation.ts +41 -0
- package/src/hooks/use-warn-user-on-page-change.ts +71 -4
- package/src/hooks/useFileManagement.ts +183 -35
- package/src/hooks/useOptimizedPackageFilesLoader.ts +136 -0
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +15 -1
- package/src/lib/download-fns/download-circuit-png.ts +11 -3
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
- package/src/lib/download-fns/download-kicad-files.ts +12 -11
- package/src/lib/normalize-svg-for-tile.ts +50 -0
- package/src/lib/posthog.ts +11 -9
- package/src/lib/react-query-api-failure-tracking.ts +148 -0
- package/src/lib/sentry.ts +14 -0
- package/src/lib/templates/blank-circuit-board-template.ts +0 -4
- package/src/lib/ts-lib-cache.ts +122 -7
- package/src/lib/utils/checkIfManualEditsImported.ts +4 -4
- package/src/lib/utils/findTargetFile.ts +45 -10
- package/src/lib/utils/isComponentExported.ts +10 -0
- package/src/main.tsx +2 -1
- package/src/pages/authorize.tsx +0 -2
- package/src/pages/create-organization.tsx +168 -0
- package/src/pages/dashboard.tsx +38 -6
- package/src/pages/datasheet.tsx +1 -1
- package/src/pages/datasheets.tsx +3 -3
- package/src/pages/editor.tsx +4 -6
- package/src/pages/landing.tsx +6 -7
- package/src/pages/latest.tsx +3 -0
- package/src/pages/organization-profile.tsx +199 -0
- package/src/pages/organization-settings.tsx +566 -0
- package/src/pages/package-editor.tsx +21 -21
- package/src/pages/preview-release.tsx +76 -136
- package/src/pages/quickstart.tsx +159 -123
- package/src/pages/release-detail.tsx +119 -31
- package/src/pages/search.tsx +192 -57
- package/src/pages/settings-redirect.tsx +44 -0
- package/src/pages/trending.tsx +29 -20
- package/src/pages/user-profile.tsx +58 -7
- package/src/pages/view-package.tsx +21 -26
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/Footer2.tsx +0 -100
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -115
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -27
- package/src/components/PackageBuildsPage/collapsible-section.tsx +0 -63
- package/src/components/PackageBuildsPage/package-build-details-panel.tsx +0 -166
- package/src/components/PackageBuildsPage/package-build-header.tsx +0 -79
- package/src/components/PageSearchComponent.tsx +0 -148
- package/src/components/ShippingInformationForm.tsx +0 -423
- package/src/components/StaticViewSnippetHeader.tsx +0 -70
- package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
- package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
- package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
- package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
- package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
- package/src/components/ViewSnippetHeader.tsx +0 -181
- package/src/components/ui/input-otp.tsx +0 -69
- package/src/pages/package-builds.tsx +0 -33
- package/src/pages/settings.tsx +0 -25
|
@@ -6,10 +6,14 @@ import { usePackageBuild } from "@/hooks/use-package-builds"
|
|
|
6
6
|
import { ConnectedRepoOverview } from "@/components/preview/ConnectedRepoOverview"
|
|
7
7
|
import Header from "@/components/Header"
|
|
8
8
|
import { Badge } from "@/components/ui/badge"
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Button } from "@/components/ui/button"
|
|
10
|
+
import { Calendar, GitBranch, RefreshCw } from "lucide-react"
|
|
11
11
|
import { formatTimeAgo } from "@/lib/utils/formatTimeAgo"
|
|
12
12
|
import { PackageBreadcrumb } from "@/components/PackageBreadcrumb"
|
|
13
|
+
import { usePackageReleaseImages } from "@/hooks/use-package-release-images"
|
|
14
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
15
|
+
import { useRebuildPackageReleaseMutation } from "@/hooks/use-rebuild-package-release-mutation"
|
|
16
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
13
17
|
|
|
14
18
|
export default function ReleaseDetailPage() {
|
|
15
19
|
const params = useParams<{
|
|
@@ -24,8 +28,6 @@ export default function ReleaseDetailPage() {
|
|
|
24
28
|
? `${params.author}/${params.packageName}`
|
|
25
29
|
: null
|
|
26
30
|
|
|
27
|
-
const [copied, setCopied] = useState(false)
|
|
28
|
-
|
|
29
31
|
const {
|
|
30
32
|
data: pkg,
|
|
31
33
|
isLoading: isLoadingPackage,
|
|
@@ -39,16 +41,58 @@ export default function ReleaseDetailPage() {
|
|
|
39
41
|
data: packageRelease,
|
|
40
42
|
isLoading: isLoadingRelease,
|
|
41
43
|
error: releaseError,
|
|
42
|
-
} = usePackageReleaseByIdOrVersion(releaseIdOrVersion, packageName
|
|
44
|
+
} = usePackageReleaseByIdOrVersion(releaseIdOrVersion, packageName, {
|
|
45
|
+
include_logs: true,
|
|
46
|
+
})
|
|
43
47
|
|
|
44
48
|
const {
|
|
45
49
|
data: latestBuild,
|
|
46
50
|
isLoading: isLoadingBuild,
|
|
47
51
|
error: buildError,
|
|
48
|
-
} = usePackageBuild(packageRelease?.latest_package_build_id ?? null
|
|
52
|
+
} = usePackageBuild(packageRelease?.latest_package_build_id ?? null, {
|
|
53
|
+
include_logs: true,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const { availableViews } = usePackageReleaseImages({
|
|
57
|
+
packageReleaseId: packageRelease?.package_release_id,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const session = useGlobalStore((s) => s.session)
|
|
61
|
+
const { mutate: rebuildPackage, isLoading: isRebuildLoading } =
|
|
62
|
+
useRebuildPackageReleaseMutation()
|
|
49
63
|
|
|
50
64
|
if (isLoadingPackage || isLoadingRelease) {
|
|
51
|
-
return
|
|
65
|
+
return (
|
|
66
|
+
<>
|
|
67
|
+
<Header />
|
|
68
|
+
<div className="min-h-screen bg-white">
|
|
69
|
+
{/* Page Header Skeleton */}
|
|
70
|
+
<div className="bg-gray-50 border-b py-6">
|
|
71
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
72
|
+
<Skeleton className="h-6 w-64 mb-4" />
|
|
73
|
+
<div className="flex items-center gap-4">
|
|
74
|
+
<Skeleton className="h-4 w-20" />
|
|
75
|
+
<Skeleton className="h-4 w-32" />
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Images Skeleton */}
|
|
81
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
|
82
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
83
|
+
{[1, 2, 3].map((i) => (
|
|
84
|
+
<Skeleton key={i} className="h-48 rounded-lg" />
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Main Content Skeleton */}
|
|
90
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
|
91
|
+
<Skeleton className="h-64 w-full" />
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</>
|
|
95
|
+
)
|
|
52
96
|
}
|
|
53
97
|
|
|
54
98
|
if (packageError?.status === 404 || !pkg) {
|
|
@@ -65,7 +109,7 @@ export default function ReleaseDetailPage() {
|
|
|
65
109
|
<div className="min-h-screen bg-white">
|
|
66
110
|
{/* Page Header */}
|
|
67
111
|
<div className="bg-gray-50 border-b py-6">
|
|
68
|
-
<div className="max-w-7xl
|
|
112
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
69
113
|
{/* Breadcrumb */}
|
|
70
114
|
<PackageBreadcrumb
|
|
71
115
|
author={pkg.owner_github_username || ""}
|
|
@@ -78,35 +122,79 @@ export default function ReleaseDetailPage() {
|
|
|
78
122
|
/>
|
|
79
123
|
|
|
80
124
|
{/* Header Content */}
|
|
81
|
-
<div className="flex flex-
|
|
82
|
-
<div className="flex-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Created {formatTimeAgo(packageRelease.created_at)}
|
|
102
|
-
</span>
|
|
103
|
-
</div>
|
|
125
|
+
<div className="flex flex-wrap items-center justify-between gap-3 mt-4">
|
|
126
|
+
<div className="flex flex-wrap items-center gap-4 text-sm text-gray-600">
|
|
127
|
+
{packageRelease.is_pr_preview && (
|
|
128
|
+
<a
|
|
129
|
+
href={`https://github.com/${pkg.github_repo_full_name}/pull/${packageRelease.github_pr_number}`}
|
|
130
|
+
target="_blank"
|
|
131
|
+
rel="noopener noreferrer"
|
|
132
|
+
className="flex items-center gap-1 hover:text-gray-800 transition-colors"
|
|
133
|
+
>
|
|
134
|
+
<GitBranch className="w-4 h-4" />
|
|
135
|
+
<Badge variant="outline" className="text-xs">
|
|
136
|
+
PR #{packageRelease.github_pr_number}
|
|
137
|
+
</Badge>
|
|
138
|
+
</a>
|
|
139
|
+
)}
|
|
140
|
+
<div className="flex items-center gap-1">
|
|
141
|
+
<Calendar className="w-4 h-4" />
|
|
142
|
+
<span>
|
|
143
|
+
Created {formatTimeAgo(packageRelease.created_at)}
|
|
144
|
+
</span>
|
|
104
145
|
</div>
|
|
105
146
|
</div>
|
|
147
|
+
|
|
148
|
+
{/* Rebuild Button */}
|
|
149
|
+
{session?.github_username === pkg.owner_github_username && (
|
|
150
|
+
<Button
|
|
151
|
+
variant="outline"
|
|
152
|
+
size="sm"
|
|
153
|
+
className="border-gray-300 bg-white hover:bg-gray-50 flex-shrink-0"
|
|
154
|
+
disabled={isRebuildLoading || !packageRelease}
|
|
155
|
+
onClick={() =>
|
|
156
|
+
packageRelease &&
|
|
157
|
+
rebuildPackage({
|
|
158
|
+
package_release_id: packageRelease.package_release_id,
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
>
|
|
162
|
+
<RefreshCw
|
|
163
|
+
className={`w-4 h-4 mr-2 ${isRebuildLoading ? "animate-spin" : ""}`}
|
|
164
|
+
/>
|
|
165
|
+
{isRebuildLoading ? "Rebuilding..." : "Rebuild"}
|
|
166
|
+
</Button>
|
|
167
|
+
)}
|
|
106
168
|
</div>
|
|
107
169
|
</div>
|
|
108
170
|
</div>
|
|
109
171
|
|
|
172
|
+
{/* Images Section - Always show with skeletons while loading */}
|
|
173
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
|
174
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
175
|
+
{availableViews.length > 0
|
|
176
|
+
? availableViews.map((view) => (
|
|
177
|
+
<div
|
|
178
|
+
key={view.id}
|
|
179
|
+
className="flex items-center justify-center border rounded-lg bg-gray-50 overflow-hidden h-48"
|
|
180
|
+
>
|
|
181
|
+
{view.isLoading ? (
|
|
182
|
+
<Skeleton className="w-full h-full" />
|
|
183
|
+
) : (
|
|
184
|
+
<img
|
|
185
|
+
src={view.imageUrl}
|
|
186
|
+
alt={`${view.label} preview`}
|
|
187
|
+
className={`w-full h-full object-contain ${view.label.toLowerCase() == "pcb" ? "bg-black" : view.label.toLowerCase() == "schematic" ? "bg-[#F5F1ED]" : "bg-gray-100"}`}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
))
|
|
192
|
+
: [1, 2, 3].map((i) => (
|
|
193
|
+
<Skeleton key={i} className="h-48 rounded-lg" />
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
110
198
|
{/* Main Content */}
|
|
111
199
|
<ConnectedRepoOverview
|
|
112
200
|
packageBuild={latestBuild ?? null}
|
package/src/pages/search.tsx
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState, useEffect, useMemo } from "react"
|
|
2
2
|
import { useQuery } from "react-query"
|
|
3
3
|
import { useAxios } from "@/hooks/use-axios"
|
|
4
4
|
import { useSearchParams } from "wouter"
|
|
5
5
|
import Header from "@/components/Header"
|
|
6
6
|
import Footer from "@/components/Footer"
|
|
7
7
|
import { Input } from "@/components/ui/input"
|
|
8
|
-
import { Search } from "lucide-react"
|
|
9
|
-
import PackageSearchResults
|
|
8
|
+
import { Search, User } from "lucide-react"
|
|
9
|
+
import PackageSearchResults, {
|
|
10
|
+
LoadingState,
|
|
11
|
+
} from "@/components/PackageSearchResults"
|
|
12
|
+
import { UserCard } from "@/components/UserCard"
|
|
10
13
|
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
14
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
15
|
+
import { fuzzyMatch } from "@/components/ViewPackagePage/utils/fuzz-search"
|
|
11
16
|
import {
|
|
12
17
|
Select,
|
|
13
18
|
SelectContent,
|
|
@@ -15,31 +20,47 @@ import {
|
|
|
15
20
|
SelectTrigger,
|
|
16
21
|
SelectValue,
|
|
17
22
|
} from "@/components/ui/select"
|
|
18
|
-
import {
|
|
23
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
24
|
+
import { Package, Account } from "fake-snippets-api/lib/db/schema"
|
|
25
|
+
|
|
26
|
+
interface ScoredPackage extends Package {
|
|
27
|
+
score: number
|
|
28
|
+
matches: number[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ScoredAccount extends Omit<Account, "account_id"> {
|
|
32
|
+
score: number
|
|
33
|
+
matches: number[]
|
|
34
|
+
}
|
|
19
35
|
|
|
20
36
|
export const SearchPage = () => {
|
|
21
37
|
const axios = useAxios()
|
|
22
38
|
const apiBaseUrl = useApiBaseUrl()
|
|
23
39
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
40
|
+
const currentUser = useGlobalStore((s) => s.session?.github_username)
|
|
24
41
|
|
|
25
42
|
const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "")
|
|
26
43
|
const [category, setCategory] = useState(
|
|
27
44
|
searchParams.get("category") || "all",
|
|
28
45
|
)
|
|
29
46
|
const [sortBy, setSortBy] = useState(searchParams.get("sort") || "stars")
|
|
47
|
+
const [activeTab, setActiveTab] = useState(
|
|
48
|
+
searchParams.get("tab") || "packages",
|
|
49
|
+
)
|
|
30
50
|
|
|
31
51
|
useEffect(() => {
|
|
32
52
|
const params = new URLSearchParams()
|
|
33
53
|
if (searchQuery) params.set("q", searchQuery)
|
|
34
54
|
if (category !== "all") params.set("category", category)
|
|
35
55
|
if (sortBy !== "stars") params.set("sort", sortBy)
|
|
56
|
+
if (activeTab !== "packages") params.set("tab", activeTab)
|
|
36
57
|
setSearchParams(params)
|
|
37
|
-
}, [searchQuery, category, sortBy, setSearchParams])
|
|
58
|
+
}, [searchQuery, category, sortBy, activeTab, setSearchParams])
|
|
38
59
|
|
|
39
60
|
const {
|
|
40
61
|
data: packages,
|
|
41
|
-
isLoading,
|
|
42
|
-
error,
|
|
62
|
+
isLoading: isLoadingPackages,
|
|
63
|
+
error: packagesError,
|
|
43
64
|
} = useQuery(
|
|
44
65
|
["packageSearch", searchQuery, category],
|
|
45
66
|
async () => {
|
|
@@ -52,69 +73,130 @@ export const SearchPage = () => {
|
|
|
52
73
|
})
|
|
53
74
|
return response.data.packages
|
|
54
75
|
},
|
|
55
|
-
{
|
|
76
|
+
{
|
|
77
|
+
enabled: Boolean(searchQuery),
|
|
78
|
+
keepPreviousData: true,
|
|
79
|
+
refetchOnWindowFocus: false,
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const { data: allAccounts = [], isLoading: isLoadingAccounts } = useQuery(
|
|
84
|
+
["accountSearch", searchQuery],
|
|
85
|
+
async () => {
|
|
86
|
+
if (!searchQuery) return []
|
|
87
|
+
try {
|
|
88
|
+
const { data } = await axios.post("/accounts/search", {
|
|
89
|
+
query: searchQuery,
|
|
90
|
+
limit: 20,
|
|
91
|
+
})
|
|
92
|
+
return data.accounts || []
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn("Failed to fetch accounts:", error)
|
|
95
|
+
return []
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
enabled: Boolean(searchQuery) && Boolean(currentUser),
|
|
100
|
+
retry: false,
|
|
101
|
+
refetchOnWindowFocus: false,
|
|
102
|
+
},
|
|
56
103
|
)
|
|
57
104
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
pkg
|
|
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))
|
|
105
|
+
const searchResults = useMemo((): ScoredPackage[] => {
|
|
106
|
+
if (!searchQuery || !packages?.length) return []
|
|
107
|
+
|
|
108
|
+
return packages
|
|
109
|
+
.map((pkg: Package) => {
|
|
110
|
+
const { score, matches } = fuzzyMatch(searchQuery, pkg.name)
|
|
111
|
+
return { ...pkg, score, matches }
|
|
74
112
|
})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
113
|
+
.filter((pkg: ScoredPackage) => pkg.score >= 0)
|
|
114
|
+
.sort((a: ScoredPackage, b: ScoredPackage) => b.score - a.score)
|
|
115
|
+
}, [packages, searchQuery])
|
|
116
|
+
|
|
117
|
+
const filteredPackages = searchResults?.sort((a: Package, b: Package) => {
|
|
118
|
+
if (sortBy === "stars") {
|
|
119
|
+
return (b.star_count || 0) - (a.star_count || 0)
|
|
120
|
+
} else if (sortBy === "newest") {
|
|
121
|
+
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
122
|
+
} else if (sortBy === "oldest") {
|
|
123
|
+
return new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime()
|
|
124
|
+
}
|
|
125
|
+
return 0
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const accountSearchResults = useMemo((): ScoredAccount[] => {
|
|
129
|
+
if (!searchQuery) return []
|
|
130
|
+
|
|
131
|
+
// First, get scored accounts from API
|
|
132
|
+
const apiAccounts = allAccounts
|
|
133
|
+
.map((account: Account) => {
|
|
134
|
+
const { score, matches } = fuzzyMatch(
|
|
135
|
+
searchQuery,
|
|
136
|
+
account.github_username,
|
|
86
137
|
)
|
|
138
|
+
return { ...account, score, matches }
|
|
139
|
+
})
|
|
140
|
+
.filter((account: ScoredAccount) => account.score >= 0)
|
|
141
|
+
|
|
142
|
+
// Then, extract unique package owners not already in API accounts
|
|
143
|
+
const packageOwners: ScoredAccount[] = []
|
|
144
|
+
const existingUsernames = new Set(
|
|
145
|
+
apiAccounts.map((acc: Account) => acc.github_username),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
filteredPackages.forEach((pkg) => {
|
|
149
|
+
if (
|
|
150
|
+
pkg.owner_github_username &&
|
|
151
|
+
!existingUsernames.has(pkg.owner_github_username)
|
|
152
|
+
) {
|
|
153
|
+
packageOwners.push({
|
|
154
|
+
github_username: pkg.owner_github_username,
|
|
155
|
+
score: 1,
|
|
156
|
+
matches: [],
|
|
157
|
+
})
|
|
158
|
+
existingUsernames.add(pkg.owner_github_username)
|
|
87
159
|
}
|
|
88
|
-
return 0
|
|
89
160
|
})
|
|
161
|
+
return [...apiAccounts, ...packageOwners].sort(
|
|
162
|
+
(a: ScoredAccount, b: ScoredAccount) => b.score - a.score,
|
|
163
|
+
)
|
|
164
|
+
}, [allAccounts, searchQuery, filteredPackages])
|
|
90
165
|
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (accountSearchResults.length == 0 && !isLoadingAccounts) {
|
|
168
|
+
setActiveTab("packages")
|
|
169
|
+
}
|
|
170
|
+
}, [accountSearchResults, isLoadingAccounts])
|
|
91
171
|
return (
|
|
92
172
|
<div className="min-h-screen flex flex-col">
|
|
93
173
|
<Header />
|
|
94
|
-
<main className="flex-grow pb-12 min-h-[80vh]">
|
|
95
|
-
<div className="container mx-auto px-4 py-8">
|
|
96
|
-
<div className="max-w-
|
|
97
|
-
<div className="mb-6">
|
|
174
|
+
<main className="flex-grow pb-12 min-h-[80vh] w-full min-w-full">
|
|
175
|
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
|
176
|
+
<div className="max-w-7xl mx-auto">
|
|
177
|
+
<div className="mb-4 sm:mb-6">
|
|
98
178
|
<div className="flex items-center gap-2 mb-3">
|
|
99
|
-
<h1 className="text-3xl font-bold text-gray-900">
|
|
100
|
-
Search
|
|
179
|
+
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900">
|
|
180
|
+
Search
|
|
101
181
|
</h1>
|
|
102
182
|
</div>
|
|
103
|
-
<div className="flex flex-col sm:flex-row gap-4 mb-4">
|
|
183
|
+
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 mb-4">
|
|
104
184
|
<div className="relative flex-grow">
|
|
105
185
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
106
186
|
<Input
|
|
107
187
|
type="search"
|
|
108
|
-
placeholder="Search packages..."
|
|
188
|
+
placeholder="Search packages and users..."
|
|
109
189
|
className="pl-10"
|
|
110
190
|
value={searchQuery}
|
|
191
|
+
spellCheck={false}
|
|
192
|
+
autoComplete="off"
|
|
111
193
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
112
|
-
aria-label="Search packages"
|
|
194
|
+
aria-label="Search packages and users"
|
|
113
195
|
role="searchbox"
|
|
114
196
|
/>
|
|
115
197
|
</div>
|
|
116
198
|
<Select value={sortBy} onValueChange={setSortBy}>
|
|
117
|
-
<SelectTrigger className="w-[140px]">
|
|
199
|
+
<SelectTrigger className="w-full sm:w-[140px]">
|
|
118
200
|
<SelectValue placeholder="Sort By" />
|
|
119
201
|
</SelectTrigger>
|
|
120
202
|
<SelectContent>
|
|
@@ -126,17 +208,70 @@ export const SearchPage = () => {
|
|
|
126
208
|
</div>
|
|
127
209
|
</div>
|
|
128
210
|
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
211
|
+
<Tabs
|
|
212
|
+
value={activeTab}
|
|
213
|
+
onValueChange={setActiveTab}
|
|
214
|
+
className="w-full"
|
|
215
|
+
>
|
|
216
|
+
{currentUser && accountSearchResults.length > 0 && (
|
|
217
|
+
<TabsList className="grid grid-cols-2 mb-6 select-none w-full max-w-md mx-auto">
|
|
218
|
+
<TabsTrigger
|
|
219
|
+
value="packages"
|
|
220
|
+
className="flex items-center gap-2"
|
|
221
|
+
>
|
|
222
|
+
Packages
|
|
223
|
+
</TabsTrigger>
|
|
224
|
+
<TabsTrigger
|
|
225
|
+
value="users"
|
|
226
|
+
className="flex items-center gap-2"
|
|
227
|
+
>
|
|
228
|
+
Users
|
|
229
|
+
</TabsTrigger>
|
|
230
|
+
</TabsList>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
<TabsContent value="packages" className="w-full">
|
|
234
|
+
<PackageSearchResults
|
|
235
|
+
isLoading={isLoadingPackages}
|
|
236
|
+
error={packagesError}
|
|
237
|
+
filteredPackages={filteredPackages}
|
|
238
|
+
apiBaseUrl={apiBaseUrl}
|
|
239
|
+
emptyStateMessage={
|
|
240
|
+
searchQuery
|
|
241
|
+
? `No packages match your search for "${searchQuery}".`
|
|
242
|
+
: "Please enter a search query to find packages."
|
|
243
|
+
}
|
|
244
|
+
/>
|
|
245
|
+
</TabsContent>
|
|
246
|
+
|
|
247
|
+
<TabsContent value="users" className="w-full">
|
|
248
|
+
{isLoadingAccounts ? (
|
|
249
|
+
<div>
|
|
250
|
+
<LoadingState />
|
|
251
|
+
</div>
|
|
252
|
+
) : accountSearchResults.length > 0 ? (
|
|
253
|
+
<div className="grid grid-cols-1 w-full sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-3 sm:gap-4">
|
|
254
|
+
{accountSearchResults.map((account, i) => (
|
|
255
|
+
<UserCard key={i} account={account} className="w-full" />
|
|
256
|
+
))}
|
|
257
|
+
</div>
|
|
258
|
+
) : (
|
|
259
|
+
<div className="text-center py-12 px-4">
|
|
260
|
+
<div className="bg-slate-50 inline-flex rounded-full p-4 mb-4">
|
|
261
|
+
<User className="w-8 h-8 text-slate-400" />
|
|
262
|
+
</div>
|
|
263
|
+
<h3 className="text-xl font-medium text-slate-900 mb-2">
|
|
264
|
+
No Matching Users
|
|
265
|
+
</h3>
|
|
266
|
+
<p className="text-slate-500 max-w-md mx-auto mb-6">
|
|
267
|
+
{searchQuery
|
|
268
|
+
? `No users match your search for "${searchQuery}".`
|
|
269
|
+
: "Please enter a search query to find users."}
|
|
270
|
+
</p>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
</TabsContent>
|
|
274
|
+
</Tabs>
|
|
140
275
|
</div>
|
|
141
276
|
</div>
|
|
142
277
|
</main>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
import { Redirect } from "wouter"
|
|
3
|
+
|
|
4
|
+
import { FullPageLoader } from "@/App"
|
|
5
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
6
|
+
|
|
7
|
+
const SettingsRedirectPage = () => {
|
|
8
|
+
const session = useGlobalStore((state) => state.session)
|
|
9
|
+
const [hasHydrated, setHasHydrated] = useState(() => {
|
|
10
|
+
if (typeof window === "undefined") return false
|
|
11
|
+
return useGlobalStore.persist?.hasHydrated?.() ?? false
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (typeof window === "undefined") return
|
|
16
|
+
|
|
17
|
+
if (useGlobalStore.persist?.hasHydrated?.()) {
|
|
18
|
+
setHasHydrated(true)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const unsubFinishHydration = useGlobalStore.persist?.onFinishHydration?.(
|
|
23
|
+
() => {
|
|
24
|
+
setHasHydrated(true)
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
unsubFinishHydration?.()
|
|
30
|
+
}
|
|
31
|
+
}, [])
|
|
32
|
+
|
|
33
|
+
if (!hasHydrated) {
|
|
34
|
+
return <FullPageLoader />
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!session?.github_username) {
|
|
38
|
+
return <Redirect to="/" />
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return <Redirect to={`/${session.github_username}/settings`} />
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default SettingsRedirectPage
|
package/src/pages/trending.tsx
CHANGED
|
@@ -49,7 +49,7 @@ const TrendingPage: React.FC = () => {
|
|
|
49
49
|
async () => {
|
|
50
50
|
const params = new URLSearchParams()
|
|
51
51
|
if (category !== "all") params.append("tag", category)
|
|
52
|
-
params.append("time_period", time_period)
|
|
52
|
+
if (time_period !== "all") params.append("time_period", time_period)
|
|
53
53
|
|
|
54
54
|
const response = await axios.get(
|
|
55
55
|
`/packages/list_trending?${params.toString()}`,
|
|
@@ -57,34 +57,47 @@ const TrendingPage: React.FC = () => {
|
|
|
57
57
|
return response.data.packages
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
keepPreviousData:
|
|
60
|
+
keepPreviousData: false,
|
|
61
|
+
refetchOnWindowFocus: false,
|
|
62
|
+
refetchOnMount: true,
|
|
63
|
+
staleTime: 5 * 60 * 1000,
|
|
61
64
|
},
|
|
62
65
|
)
|
|
63
66
|
|
|
64
|
-
const
|
|
65
|
-
|
|
67
|
+
const filteredAndSortedPackages = React.useMemo(() => {
|
|
68
|
+
if (!packages) return []
|
|
69
|
+
|
|
70
|
+
let filtered = packages.filter((pkg) => {
|
|
66
71
|
if (!searchQuery) return true
|
|
67
72
|
|
|
68
73
|
const query = searchQuery.toLowerCase().trim()
|
|
74
|
+
if (!query) return true
|
|
75
|
+
|
|
69
76
|
const searchableFields = [
|
|
70
|
-
pkg.unscoped_name
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
pkg.unscoped_name?.toLowerCase() || "",
|
|
78
|
+
pkg.owner_github_username?.toLowerCase() || "",
|
|
79
|
+
pkg.description?.toLowerCase() || "",
|
|
80
|
+
].filter(Boolean)
|
|
81
|
+
|
|
82
|
+
const queryWords = query.split(/\s+/).filter((word) => word.length > 0)
|
|
75
83
|
|
|
76
84
|
return searchableFields.some((field) => {
|
|
77
|
-
const queryWords = query.split(/\s+/).filter((word) => word.length > 0)
|
|
78
85
|
if (!field) return false
|
|
79
86
|
return queryWords.every((word) => field.includes(word))
|
|
80
87
|
})
|
|
81
88
|
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
|
|
90
|
+
return filtered.sort((a, b) => {
|
|
91
|
+
if (sortBy === "recent") {
|
|
92
|
+
const dateA = new Date(a.updated_at).getTime()
|
|
93
|
+
const dateB = new Date(b.updated_at).getTime()
|
|
94
|
+
return dateB - dateA
|
|
85
95
|
}
|
|
86
|
-
|
|
96
|
+
const starsA = a.star_count || 0
|
|
97
|
+
const starsB = b.star_count || 0
|
|
98
|
+
return starsB - starsA
|
|
87
99
|
})
|
|
100
|
+
}, [packages, searchQuery, sortBy])
|
|
88
101
|
|
|
89
102
|
return (
|
|
90
103
|
<div className="min-h-screen flex flex-col">
|
|
@@ -107,11 +120,7 @@ const TrendingPage: React.FC = () => {
|
|
|
107
120
|
<SelectItem value="recent">Most Recent</SelectItem>
|
|
108
121
|
</SelectContent>
|
|
109
122
|
</Select>
|
|
110
|
-
<Select
|
|
111
|
-
value={time_period}
|
|
112
|
-
onValueChange={setTimePeriod}
|
|
113
|
-
disabled={sortBy === "recent"}
|
|
114
|
-
>
|
|
123
|
+
<Select value={time_period} onValueChange={setTimePeriod}>
|
|
115
124
|
<SelectTrigger className="w-[140px]">
|
|
116
125
|
<SelectValue placeholder="Time Period" />
|
|
117
126
|
</SelectTrigger>
|
|
@@ -173,7 +182,7 @@ const TrendingPage: React.FC = () => {
|
|
|
173
182
|
<PackageSearchResults
|
|
174
183
|
isLoading={isLoading}
|
|
175
184
|
error={error}
|
|
176
|
-
filteredPackages={
|
|
185
|
+
filteredPackages={filteredAndSortedPackages}
|
|
177
186
|
apiBaseUrl={apiBaseUrl}
|
|
178
187
|
emptyStateMessage={
|
|
179
188
|
searchQuery
|