@tscircuit/fake-snippets 0.0.109 → 0.0.111
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 +32 -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 +151 -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 +361 -453
- package/bunfig.toml +2 -1
- package/dist/bundle.js +1313 -639
- package/dist/index.d.ts +313 -6
- package/dist/index.js +328 -24
- package/dist/schema.d.ts +290 -1
- package/dist/schema.js +54 -1
- package/fake-snippets-api/lib/db/autoload-dev-packages.ts +31 -20
- package/fake-snippets-api/lib/db/db-client.ts +219 -4
- package/fake-snippets-api/lib/db/schema.ts +63 -1
- package/fake-snippets-api/lib/db/seed.ts +100 -0
- package/fake-snippets-api/lib/middleware/with-session-auth.ts +60 -8
- 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 +33 -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 +48 -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 +60 -0
- package/fake-snippets-api/routes/api/orgs/remove_member.ts +46 -0
- package/fake-snippets-api/routes/api/orgs/update.ts +118 -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 +57 -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 +25 -19
- package/renovate.json +1 -1
- package/scripts/generate-sitemap.ts +1 -1
- package/src/App.tsx +27 -8
- 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 +17 -5
- package/src/components/FileSidebar.tsx +11 -17
- package/src/components/Footer.tsx +8 -9
- 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 +43 -15
- package/src/components/NotFound.tsx +5 -5
- package/src/components/PackageBreadcrumb.tsx +6 -12
- 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/SentryNotFoundReporter.tsx +44 -0
- package/src/components/UserCard.tsx +80 -0
- package/src/components/ViewPackagePage/components/build-status.tsx +1 -1
- package/src/components/ViewPackagePage/components/important-files-view.tsx +105 -34
- package/src/components/ViewPackagePage/components/main-content-header.tsx +10 -6
- package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +1 -1
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +54 -19
- package/src/components/ViewPackagePage/components/package-header.tsx +25 -33
- package/src/components/ViewPackagePage/components/preview-image-squares.tsx +11 -18
- package/src/components/ViewPackagePage/components/repo-page-content.tsx +12 -5
- 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/tab-views/pcb-view.tsx +1 -2
- package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +2 -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 +206 -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 +15 -12
- package/src/components/package-port/CodeEditor.tsx +4 -30
- package/src/components/package-port/CodeEditorHeader.tsx +123 -61
- 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 +23 -11
- 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-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-hydration.ts +30 -0
- 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 +51 -22
- package/src/hooks/useOptimizedPackageFilesLoader.ts +11 -24
- package/src/hooks/usePackageFilesLoader.ts +2 -2
- package/src/hooks/useUpdatePackageFilesMutation.ts +13 -1
- package/src/lib/download-fns/download-gltf-from-circuit-json.ts +1 -1
- package/src/lib/download-fns/download-kicad-files.ts +22 -11
- package/src/lib/download-fns/download-step.ts +12 -0
- 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 +2 -1
- package/src/main.tsx +2 -1
- package/src/pages/create-organization.tsx +169 -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 -6
- package/src/pages/latest.tsx +3 -0
- package/src/pages/organization-profile.tsx +199 -0
- package/src/pages/organization-settings.tsx +569 -0
- package/src/pages/package-editor.tsx +21 -21
- package/src/pages/preview-release.tsx +75 -145
- package/src/pages/quickstart.tsx +159 -123
- package/src/pages/release-detail.tsx +119 -31
- package/src/pages/search.tsx +197 -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/user-settings.tsx +161 -0
- package/src/pages/view-package.tsx +30 -16
- package/vite.config.ts +9 -0
- package/fake-snippets-api/routes/api/autocomplete/create_autocomplete.ts +0 -133
- package/src/components/JLCPCBImportDialog.tsx +0 -280
- package/src/components/PackageBuildsPage/LogContent.tsx +0 -72
- package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -113
- package/src/components/PackageBuildsPage/build-preview-content.tsx +0 -56
- 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/pages/package-builds.tsx +0 -33
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CommandGroup,
|
|
7
7
|
CommandInput,
|
|
8
8
|
CommandItem,
|
|
9
|
+
CommandList,
|
|
9
10
|
} from "@/components/ui/command"
|
|
10
11
|
import {
|
|
11
12
|
Popover,
|
|
@@ -203,63 +204,69 @@ export const GitHubRepositorySelector = ({
|
|
|
203
204
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
204
205
|
</Button>
|
|
205
206
|
</PopoverTrigger>
|
|
206
|
-
<PopoverContent
|
|
207
|
+
<PopoverContent
|
|
208
|
+
className="w-[var(--radix-popover-trigger-width)] p-0 z-[999]"
|
|
209
|
+
align="start"
|
|
210
|
+
>
|
|
207
211
|
<Command shouldFilter={false}>
|
|
208
212
|
<CommandInput
|
|
209
213
|
value={searchValue}
|
|
210
214
|
onValueChange={setSearchValue}
|
|
211
215
|
placeholder="Search repositories..."
|
|
212
216
|
/>
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
217
|
+
<CommandList className="max-h-[400px] overflow-y-auto">
|
|
218
|
+
<CommandEmpty className="text-sm text-slate-500 py-6 pl-4">
|
|
219
|
+
No repositories found.
|
|
220
|
+
</CommandEmpty>
|
|
221
|
+
<CommandGroup>
|
|
222
|
+
{filteredOptions.map((option) => (
|
|
223
|
+
<CommandItem
|
|
224
|
+
key={option.value}
|
|
225
|
+
value={option.value}
|
|
226
|
+
onSelect={() => handleComboboxSelect(option.value)}
|
|
227
|
+
className="cursor-pointer hover:bg-slate-100"
|
|
228
|
+
>
|
|
229
|
+
<div className="flex items-center space-x-2 w-full">
|
|
230
|
+
{option.type === "repo" ? (
|
|
231
|
+
<>
|
|
232
|
+
<Check
|
|
233
|
+
className={cn(
|
|
234
|
+
"mr-2 h-4 w-4",
|
|
235
|
+
selectedRepository === option.value
|
|
236
|
+
? "opacity-100"
|
|
237
|
+
: "opacity-0",
|
|
238
|
+
)}
|
|
239
|
+
/>
|
|
240
|
+
<span>{option.label}</span>
|
|
241
|
+
{option.isPrivate && (
|
|
242
|
+
<span className="text-xs text-muted-foreground">
|
|
243
|
+
(private)
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
</>
|
|
247
|
+
) : (
|
|
248
|
+
<>
|
|
249
|
+
{option.icon === "plus" ? (
|
|
250
|
+
<Plus className="w-3 h-3 text-blue-600" />
|
|
251
|
+
) : (
|
|
252
|
+
<Minus className="w-3 h-3 text-red-600" />
|
|
232
253
|
)}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
254
|
+
<span
|
|
255
|
+
className={
|
|
256
|
+
option.icon === "plus"
|
|
257
|
+
? "text-blue-600"
|
|
258
|
+
: "text-red-600"
|
|
259
|
+
}
|
|
260
|
+
>
|
|
261
|
+
{option.label}
|
|
238
262
|
</span>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
<Minus className="w-3 h-3 text-red-600" />
|
|
247
|
-
)}
|
|
248
|
-
<span
|
|
249
|
-
className={
|
|
250
|
-
option.icon === "plus"
|
|
251
|
-
? "text-blue-600"
|
|
252
|
-
: "text-red-600"
|
|
253
|
-
}
|
|
254
|
-
>
|
|
255
|
-
{option.label}
|
|
256
|
-
</span>
|
|
257
|
-
</>
|
|
258
|
-
)}
|
|
259
|
-
</div>
|
|
260
|
-
</CommandItem>
|
|
261
|
-
))}
|
|
262
|
-
</CommandGroup>
|
|
263
|
+
</>
|
|
264
|
+
)}
|
|
265
|
+
</div>
|
|
266
|
+
</CommandItem>
|
|
267
|
+
))}
|
|
268
|
+
</CommandGroup>
|
|
269
|
+
</CommandList>
|
|
263
270
|
</Command>
|
|
264
271
|
</PopoverContent>
|
|
265
272
|
</Popover>
|
|
@@ -133,8 +133,10 @@ export const EditPackageDetailsDialog = ({
|
|
|
133
133
|
if (response.status !== 200)
|
|
134
134
|
throw new Error("Failed to update package details")
|
|
135
135
|
|
|
136
|
-
const filesRes = await axios.
|
|
137
|
-
|
|
136
|
+
const filesRes = await axios.get("/package_files/list", {
|
|
137
|
+
params: {
|
|
138
|
+
package_name_with_version: `${packageAuthor}/${formData.unscopedPackageName}`,
|
|
139
|
+
},
|
|
138
140
|
})
|
|
139
141
|
const packageFiles: string[] =
|
|
140
142
|
filesRes.status === 200
|
|
@@ -194,10 +196,7 @@ export const EditPackageDetailsDialog = ({
|
|
|
194
196
|
onSuccess: (data) => {
|
|
195
197
|
onUpdate?.(data.description, data.website, data.license, data.defaultView)
|
|
196
198
|
onOpenChange(false)
|
|
197
|
-
qc.invalidateQueries(
|
|
198
|
-
"packageFile",
|
|
199
|
-
{ package_release_id: packageReleaseId },
|
|
200
|
-
])
|
|
199
|
+
qc.invalidateQueries("packageFile")
|
|
201
200
|
qc.invalidateQueries(["packageFiles", packageReleaseId])
|
|
202
201
|
toast({
|
|
203
202
|
title: "Package details updated",
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import { createUseDialog } from "./create-use-dialog"
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
ImportComponentDialog2 as RunframeImportComponentDialog,
|
|
4
|
+
type ImportComponentDialog2Props,
|
|
5
5
|
} from "@tscircuit/runframe/runner"
|
|
6
6
|
|
|
7
|
+
export type ImportComponentDialogProps = {
|
|
8
|
+
open: boolean
|
|
9
|
+
onOpenChange: (open: boolean) => void
|
|
10
|
+
} & Pick<
|
|
11
|
+
ImportComponentDialog2Props,
|
|
12
|
+
| "onTscircuitPackageSelected"
|
|
13
|
+
| "onJlcpcbComponentTsxLoaded"
|
|
14
|
+
| "onKicadStringSelected"
|
|
15
|
+
| "jlcpcbProxyRequestHeaders"
|
|
16
|
+
>
|
|
17
|
+
|
|
7
18
|
export const ImportComponentDialog = ({
|
|
8
19
|
open,
|
|
9
20
|
onOpenChange,
|
|
10
|
-
|
|
11
|
-
}: {
|
|
12
|
-
open: boolean
|
|
13
|
-
onOpenChange: (open: boolean) => any
|
|
14
|
-
onComponentSelected: (pkg: ComponentSearchResult) => any
|
|
15
|
-
}) => {
|
|
21
|
+
...rest
|
|
22
|
+
}: ImportComponentDialogProps) => {
|
|
16
23
|
return (
|
|
17
24
|
<RunframeImportComponentDialog
|
|
18
25
|
isOpen={open}
|
|
19
26
|
onClose={() => onOpenChange(false)}
|
|
20
|
-
|
|
27
|
+
{...rest}
|
|
21
28
|
/>
|
|
22
29
|
)
|
|
23
30
|
}
|
|
@@ -7,6 +7,7 @@ import { Button } from "../ui/button"
|
|
|
7
7
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"
|
|
8
8
|
import { Input } from "../ui/input"
|
|
9
9
|
import { createUseDialog } from "./create-use-dialog"
|
|
10
|
+
import { Link } from "wouter"
|
|
10
11
|
|
|
11
12
|
export const ImportPackageDialog = ({
|
|
12
13
|
open,
|
|
@@ -55,13 +56,13 @@ export const ImportPackageDialog = ({
|
|
|
55
56
|
className="flex flex-col sm:flex-row items-start sm:items-center my-2 text-sm w-full"
|
|
56
57
|
key={pkg.package_id}
|
|
57
58
|
>
|
|
58
|
-
<
|
|
59
|
+
<Link
|
|
59
60
|
href={`/${pkg.name}`}
|
|
60
61
|
target="_blank"
|
|
61
62
|
className="text-blue-500 hover:underline cursor-pointer flex-shrink-0 mb-1 sm:mb-0 sm:mr-2"
|
|
62
63
|
>
|
|
63
64
|
{pkg.name}
|
|
64
|
-
</
|
|
65
|
+
</Link>
|
|
65
66
|
<div className="text-gray-500 flex-grow overflow-hidden text-ellipsis whitespace-nowrap mb-1 sm:mb-0">
|
|
66
67
|
{pkg.description}
|
|
67
68
|
</div>
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from "react"
|
|
2
|
+
import { Button } from "../ui/button"
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogHeader,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
} from "../ui/dialog"
|
|
10
|
+
import { Input } from "../ui/input"
|
|
11
|
+
import { Label } from "../ui/label"
|
|
12
|
+
import {
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
18
|
+
} from "../ui/select"
|
|
19
|
+
import { createUseDialog } from "./create-use-dialog"
|
|
20
|
+
import { useListUserOrgs } from "@/hooks/use-list-user-orgs"
|
|
21
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
22
|
+
|
|
23
|
+
export const NewPackageSavePromptDialog = ({
|
|
24
|
+
open,
|
|
25
|
+
onOpenChange,
|
|
26
|
+
initialIsPrivate = false,
|
|
27
|
+
initialName = "",
|
|
28
|
+
onSave,
|
|
29
|
+
}: {
|
|
30
|
+
open: boolean
|
|
31
|
+
onOpenChange: (open: boolean) => void
|
|
32
|
+
initialIsPrivate?: boolean
|
|
33
|
+
initialName?: string
|
|
34
|
+
onSave: ({
|
|
35
|
+
name,
|
|
36
|
+
isPrivate,
|
|
37
|
+
orgId,
|
|
38
|
+
}: {
|
|
39
|
+
name?: string
|
|
40
|
+
isPrivate: boolean
|
|
41
|
+
orgId: string
|
|
42
|
+
}) => void
|
|
43
|
+
}) => {
|
|
44
|
+
const [packageName, setPackageName] = useState(initialName)
|
|
45
|
+
const session = useGlobalStore((s) => s.session)
|
|
46
|
+
const [isPrivate, setIsPrivate] = useState(initialIsPrivate)
|
|
47
|
+
const [selectedOrgId, setSelectedOrgId] = useState<string>("")
|
|
48
|
+
const { data: organizations, isLoading: orgsLoading } = useListUserOrgs()
|
|
49
|
+
const fullPackageName = useMemo(() => {
|
|
50
|
+
if (selectedOrgId) {
|
|
51
|
+
return `${organizations?.find((x) => x.org_id === selectedOrgId)?.name}/${packageName}`
|
|
52
|
+
}
|
|
53
|
+
return `${session?.github_username}/${packageName}`
|
|
54
|
+
}, [selectedOrgId, packageName, organizations, session?.github_username])
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (organizations && organizations.length > 0 && !selectedOrgId) {
|
|
57
|
+
setSelectedOrgId(
|
|
58
|
+
organizations.find((x) => x.is_personal_org)?.org_id ||
|
|
59
|
+
organizations[0].org_id,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}, [organizations, selectedOrgId])
|
|
63
|
+
return (
|
|
64
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
65
|
+
<DialogContent>
|
|
66
|
+
<DialogHeader>
|
|
67
|
+
<DialogTitle>Creating new package</DialogTitle>
|
|
68
|
+
<DialogDescription>
|
|
69
|
+
Would you like to save this package?
|
|
70
|
+
</DialogDescription>
|
|
71
|
+
</DialogHeader>
|
|
72
|
+
<div className="space-y-4 py-1">
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
<Label className="text-sm font-medium">Organization</Label>
|
|
75
|
+
<Select
|
|
76
|
+
value={selectedOrgId}
|
|
77
|
+
onValueChange={setSelectedOrgId}
|
|
78
|
+
disabled={orgsLoading}
|
|
79
|
+
>
|
|
80
|
+
<SelectTrigger className="w-full">
|
|
81
|
+
<div className="flex items-center gap-2 flex-1">
|
|
82
|
+
{selectedOrgId && organizations ? (
|
|
83
|
+
<span className="truncate">
|
|
84
|
+
{organizations.find((org) => org.org_id === selectedOrgId)
|
|
85
|
+
?.display_name ||
|
|
86
|
+
organizations.find(
|
|
87
|
+
(org) => org.org_id === selectedOrgId,
|
|
88
|
+
)?.name ||
|
|
89
|
+
`Org ${selectedOrgId.slice(0, 8)}`}
|
|
90
|
+
</span>
|
|
91
|
+
) : (
|
|
92
|
+
<span className="text-slate-500">
|
|
93
|
+
{orgsLoading
|
|
94
|
+
? "Loading organizations..."
|
|
95
|
+
: "Select organization"}
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</SelectTrigger>
|
|
100
|
+
<SelectContent className="!z-[999]">
|
|
101
|
+
{organizations?.length === 0 ? (
|
|
102
|
+
<div className="px-2 py-1.5 text-sm text-slate-500">
|
|
103
|
+
No organizations found
|
|
104
|
+
</div>
|
|
105
|
+
) : (
|
|
106
|
+
organizations?.map((org) => (
|
|
107
|
+
<SelectItem
|
|
108
|
+
key={org.org_id}
|
|
109
|
+
value={org.org_id}
|
|
110
|
+
className="cursor-pointer"
|
|
111
|
+
>
|
|
112
|
+
{org.display_name ||
|
|
113
|
+
org.name ||
|
|
114
|
+
`Org ${org.org_id.slice(0, 8)}`}
|
|
115
|
+
</SelectItem>
|
|
116
|
+
))
|
|
117
|
+
)}
|
|
118
|
+
</SelectContent>
|
|
119
|
+
</Select>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div className="space-y-2">
|
|
123
|
+
<Label className="text-sm font-medium">Package Name</Label>
|
|
124
|
+
<Input
|
|
125
|
+
value={packageName}
|
|
126
|
+
onChange={(e) => setPackageName(e.target.value)}
|
|
127
|
+
placeholder="Untitled Package"
|
|
128
|
+
className="w-full"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div className="space-y-2">
|
|
133
|
+
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 sm:gap-4">
|
|
134
|
+
<div className="space-y-1 flex-1">
|
|
135
|
+
<Label className="text-sm font-medium">Visibility</Label>
|
|
136
|
+
<p className="text-xs text-slate-500">
|
|
137
|
+
{isPrivate
|
|
138
|
+
? "Only you can view and use this package"
|
|
139
|
+
: "Anyone can view and use your package"}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
<Select
|
|
143
|
+
value={isPrivate ? "private" : "public"}
|
|
144
|
+
onValueChange={(value) => setIsPrivate(value === "private")}
|
|
145
|
+
>
|
|
146
|
+
<SelectTrigger className="w-full sm:w-32 sm:mt-2">
|
|
147
|
+
<SelectValue />
|
|
148
|
+
</SelectTrigger>
|
|
149
|
+
<SelectContent className="!z-[999]">
|
|
150
|
+
<SelectItem value="public" className="cursor-pointer">
|
|
151
|
+
<div className="flex items-center gap-2">
|
|
152
|
+
<span>Public</span>
|
|
153
|
+
</div>
|
|
154
|
+
</SelectItem>
|
|
155
|
+
<SelectItem value="private" className="cursor-pointer">
|
|
156
|
+
<div className="flex items-center gap-2">
|
|
157
|
+
<span>Private</span>
|
|
158
|
+
</div>
|
|
159
|
+
</SelectItem>
|
|
160
|
+
</SelectContent>
|
|
161
|
+
</Select>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="flex justify-end gap-2">
|
|
166
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
167
|
+
Cancel
|
|
168
|
+
</Button>
|
|
169
|
+
<Button
|
|
170
|
+
onClick={() => {
|
|
171
|
+
onSave({
|
|
172
|
+
name: fullPackageName.trim(),
|
|
173
|
+
isPrivate,
|
|
174
|
+
orgId: selectedOrgId,
|
|
175
|
+
})
|
|
176
|
+
onOpenChange(false)
|
|
177
|
+
}}
|
|
178
|
+
disabled={!selectedOrgId || orgsLoading || !session}
|
|
179
|
+
>
|
|
180
|
+
Save
|
|
181
|
+
</Button>
|
|
182
|
+
</div>
|
|
183
|
+
</DialogContent>
|
|
184
|
+
</Dialog>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const useNewPackageSavePromptDialog = createUseDialog(
|
|
189
|
+
NewPackageSavePromptDialog,
|
|
190
|
+
)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Link } from "wouter"
|
|
3
|
+
import {
|
|
4
|
+
Users,
|
|
5
|
+
Package,
|
|
6
|
+
Globe,
|
|
7
|
+
Lock,
|
|
8
|
+
MoreVertical,
|
|
9
|
+
Share2,
|
|
10
|
+
Settings,
|
|
11
|
+
} from "lucide-react"
|
|
12
|
+
import { Button } from "@/components/ui/button"
|
|
13
|
+
import {
|
|
14
|
+
DropdownMenu,
|
|
15
|
+
DropdownMenuContent,
|
|
16
|
+
DropdownMenuItem,
|
|
17
|
+
DropdownMenuTrigger,
|
|
18
|
+
} from "@/components/ui/dropdown-menu"
|
|
19
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
20
|
+
import { timeAgo } from "@/lib/utils/timeAgo"
|
|
21
|
+
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
|
22
|
+
import { PublicOrgSchema } from "fake-snippets-api/lib/db/schema"
|
|
23
|
+
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
24
|
+
|
|
25
|
+
export interface OrganizationCardProps {
|
|
26
|
+
/** The organization data to display */
|
|
27
|
+
organization: PublicOrgSchema
|
|
28
|
+
/** Whether to show member count */
|
|
29
|
+
showMembers?: boolean
|
|
30
|
+
/** Whether to show statistics (packages, members) */
|
|
31
|
+
showStats?: boolean
|
|
32
|
+
/** Callback when the card is clicked */
|
|
33
|
+
onClick?: (org: PublicOrgSchema) => void
|
|
34
|
+
/** Custom class name for the card container */
|
|
35
|
+
className?: string
|
|
36
|
+
/** Whether to render the card with a link to the organization page */
|
|
37
|
+
withLink?: boolean
|
|
38
|
+
/** Custom render function for actions */
|
|
39
|
+
renderActions?: (org: PublicOrgSchema) => React.ReactNode
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const OrganizationCard: React.FC<OrganizationCardProps> = ({
|
|
43
|
+
organization,
|
|
44
|
+
showMembers = true,
|
|
45
|
+
showStats = true,
|
|
46
|
+
onClick,
|
|
47
|
+
className = "",
|
|
48
|
+
withLink = true,
|
|
49
|
+
renderActions,
|
|
50
|
+
}) => {
|
|
51
|
+
const { copyToClipboard } = useCopyToClipboard()
|
|
52
|
+
const { session } = useGlobalStore()
|
|
53
|
+
|
|
54
|
+
const canManageOrg =
|
|
55
|
+
organization.owner_account_id === session?.account_id ||
|
|
56
|
+
organization.user_permissions?.can_manage_org
|
|
57
|
+
|
|
58
|
+
const handleCardClick = (e: React.MouseEvent) => {
|
|
59
|
+
if (onClick && !withLink) {
|
|
60
|
+
e.preventDefault()
|
|
61
|
+
onClick(organization)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const handleShareClick = (e: React.MouseEvent) => {
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
e.stopPropagation()
|
|
68
|
+
const shareUrl = `${window.location.origin}/${organization.name}`
|
|
69
|
+
copyToClipboard(shareUrl)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleSettingsClick = (e: React.MouseEvent) => {
|
|
73
|
+
e.preventDefault()
|
|
74
|
+
e.stopPropagation()
|
|
75
|
+
window.location.href = organization.is_personal_org
|
|
76
|
+
? `/settings`
|
|
77
|
+
: `/${organization.name}/settings`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const cardContent = (
|
|
81
|
+
<div
|
|
82
|
+
className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 h-full ${className}`}
|
|
83
|
+
onClick={handleCardClick}
|
|
84
|
+
>
|
|
85
|
+
<div className="flex items-start gap-4">
|
|
86
|
+
{/* Organization Avatar */}
|
|
87
|
+
<div className="flex-shrink-0">
|
|
88
|
+
<Avatar className="h-16 w-16 border-2 border-gray-100">
|
|
89
|
+
<AvatarImage
|
|
90
|
+
src={`https://github.com/${organization.name}.png`}
|
|
91
|
+
alt={`${organization.name} avatar`}
|
|
92
|
+
className="object-cover"
|
|
93
|
+
/>
|
|
94
|
+
<AvatarFallback className="bg-blue-100 text-blue-600 font-semibold text-lg">
|
|
95
|
+
{organization.name
|
|
96
|
+
?.split(" ")
|
|
97
|
+
.map((word) => word[0])
|
|
98
|
+
.join("")
|
|
99
|
+
.toUpperCase()
|
|
100
|
+
.slice(0, 2)}
|
|
101
|
+
</AvatarFallback>
|
|
102
|
+
</Avatar>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{/* Organization Info */}
|
|
106
|
+
<div className="flex-1 min-w-0">
|
|
107
|
+
<div className="flex justify-between items-start mb-1">
|
|
108
|
+
<div className="min-w-0 flex-1">
|
|
109
|
+
<h2 className="text-md font-semibold text-gray-900 truncate pr-8">
|
|
110
|
+
{organization.name}
|
|
111
|
+
</h2>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
{/* Actions Dropdown */}
|
|
115
|
+
<div className="flex items-center gap-2">
|
|
116
|
+
<DropdownMenu>
|
|
117
|
+
<DropdownMenuTrigger asChild>
|
|
118
|
+
<Button
|
|
119
|
+
variant="ghost"
|
|
120
|
+
size="icon"
|
|
121
|
+
className="h-6 w-6"
|
|
122
|
+
onClick={(e) => e.stopPropagation()}
|
|
123
|
+
>
|
|
124
|
+
<MoreVertical className="h-4 w-4" />
|
|
125
|
+
</Button>
|
|
126
|
+
</DropdownMenuTrigger>
|
|
127
|
+
<DropdownMenuContent align="end">
|
|
128
|
+
<DropdownMenuItem
|
|
129
|
+
className="text-xs cursor-pointer"
|
|
130
|
+
onClick={handleShareClick}
|
|
131
|
+
>
|
|
132
|
+
<Share2 className="mr-2 h-3 w-3" />
|
|
133
|
+
Share Organization
|
|
134
|
+
</DropdownMenuItem>
|
|
135
|
+
{canManageOrg && (
|
|
136
|
+
<DropdownMenuItem
|
|
137
|
+
className="text-xs cursor-pointer"
|
|
138
|
+
onClick={handleSettingsClick}
|
|
139
|
+
>
|
|
140
|
+
<Settings className="mr-2 h-3 w-3" />
|
|
141
|
+
Organization Settings
|
|
142
|
+
</DropdownMenuItem>
|
|
143
|
+
)}
|
|
144
|
+
</DropdownMenuContent>
|
|
145
|
+
</DropdownMenu>
|
|
146
|
+
{renderActions && renderActions(organization)}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{/* Statistics and Metadata */}
|
|
151
|
+
<div className="flex items-center gap-4 text-xs text-gray-500 mb-1">
|
|
152
|
+
{/* Visibility */}
|
|
153
|
+
<div className="flex items-center gap-1">
|
|
154
|
+
{!organization.is_personal_org ? (
|
|
155
|
+
<>
|
|
156
|
+
<Globe className="h-3 w-3" />
|
|
157
|
+
<span>Public</span>
|
|
158
|
+
</>
|
|
159
|
+
) : (
|
|
160
|
+
<>
|
|
161
|
+
<Lock className="h-3 w-3" />
|
|
162
|
+
<span>Personal</span>
|
|
163
|
+
</>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{/* Statistics */}
|
|
168
|
+
{showStats && (
|
|
169
|
+
<>
|
|
170
|
+
{showMembers && !organization.is_personal_org && (
|
|
171
|
+
<div className="flex items-center gap-1">
|
|
172
|
+
<Users className="h-3 w-3" />
|
|
173
|
+
<span>{organization.member_count} members</span>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
<div className="flex items-center gap-1">
|
|
177
|
+
<Package className="h-3 w-3" />
|
|
178
|
+
<span>{organization.package_count} packages</span>
|
|
179
|
+
</div>
|
|
180
|
+
</>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
{/* Created time */}
|
|
185
|
+
<div className="text-xs text-gray-400">
|
|
186
|
+
<span>Created {timeAgo(new Date(organization.created_at))}</span>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if (withLink) {
|
|
194
|
+
return (
|
|
195
|
+
<Link
|
|
196
|
+
key={organization.org_id}
|
|
197
|
+
href={`/${organization.name}`}
|
|
198
|
+
className="block h-full"
|
|
199
|
+
>
|
|
200
|
+
{cardContent}
|
|
201
|
+
</Link>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return cardContent
|
|
206
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
export const OrganizationCardSkeleton: React.FC = () => {
|
|
4
|
+
return (
|
|
5
|
+
<div className="border p-4 rounded-md animate-pulse">
|
|
6
|
+
<div className="flex flex-col gap-4">
|
|
7
|
+
<div className="flex items-start gap-4">
|
|
8
|
+
{/* Organization Avatar skeleton */}
|
|
9
|
+
<div className="flex-shrink-0">
|
|
10
|
+
<div className="h-16 w-16 rounded-full bg-slate-200 border-2 border-gray-100"></div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
{/* Organization Info skeleton */}
|
|
14
|
+
<div className="flex-1 min-w-0">
|
|
15
|
+
<div className="flex justify-between items-start mb-1">
|
|
16
|
+
<div className="min-w-0 flex-1">
|
|
17
|
+
{/* Organization name */}
|
|
18
|
+
<div className="h-5 bg-slate-200 rounded w-3/4 sm:w-1/2 mb-1"></div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
{/* Actions dropdown skeleton */}
|
|
22
|
+
<div className="flex items-center gap-2">
|
|
23
|
+
<div className="h-6 w-6 bg-slate-200 rounded"></div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{/* Statistics and Metadata skeleton */}
|
|
28
|
+
<div className="flex flex-wrap items-center gap-2 sm:gap-4 text-xs mb-1">
|
|
29
|
+
{/* Visibility */}
|
|
30
|
+
<div className="flex items-center gap-1">
|
|
31
|
+
<div className="h-3 w-3 bg-slate-200 rounded"></div>
|
|
32
|
+
<div className="h-3 bg-slate-200 rounded w-8 sm:w-12"></div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{/* Members */}
|
|
36
|
+
<div className="flex items-center gap-1">
|
|
37
|
+
<div className="h-3 w-3 bg-slate-200 rounded"></div>
|
|
38
|
+
<div className="h-3 bg-slate-200 rounded w-12 sm:w-16"></div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{/* Packages */}
|
|
42
|
+
<div className="flex items-center gap-1">
|
|
43
|
+
<div className="h-3 w-3 bg-slate-200 rounded"></div>
|
|
44
|
+
<div className="h-3 bg-slate-200 rounded w-14 sm:w-16"></div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Created time skeleton */}
|
|
49
|
+
<div className="h-3 bg-slate-200 rounded w-20 sm:w-24"></div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|