@tscircuit/fake-snippets 0.0.40 → 0.0.41

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/dist/index.d.ts CHANGED
@@ -365,6 +365,7 @@ declare const packageSchema: z.ZodObject<{
365
365
  website: z.ZodDefault<z.ZodNullable<z.ZodString>>;
366
366
  star_count: z.ZodDefault<z.ZodNumber>;
367
367
  ai_description: z.ZodNullable<z.ZodString>;
368
+ latest_license: z.ZodOptional<z.ZodNullable<z.ZodString>>;
368
369
  ai_usage_instructions: z.ZodNullable<z.ZodString>;
369
370
  }, "strip", z.ZodTypeAny, {
370
371
  name: string;
@@ -393,6 +394,7 @@ declare const packageSchema: z.ZodObject<{
393
394
  ai_description: string | null;
394
395
  ai_usage_instructions: string | null;
395
396
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
397
+ latest_license?: string | null | undefined;
396
398
  }, {
397
399
  name: string;
398
400
  unscoped_name: string;
@@ -420,6 +422,7 @@ declare const packageSchema: z.ZodObject<{
420
422
  is_footprint?: boolean | undefined;
421
423
  is_source_from_github?: boolean | undefined;
422
424
  website?: string | null | undefined;
425
+ latest_license?: string | null | undefined;
423
426
  }>;
424
427
  type Package = z.infer<typeof packageSchema>;
425
428
  declare const jlcpcbOrderStateSchema: z.ZodObject<{
@@ -626,6 +629,7 @@ declare const createDatabase: ({ seed }?: {
626
629
  ai_description: string | null;
627
630
  ai_usage_instructions: string | null;
628
631
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
632
+ latest_license?: string | null | undefined;
629
633
  }[];
630
634
  orders: {
631
635
  error: z.objectOutputType<{
@@ -901,6 +905,7 @@ declare const createDatabase: ({ seed }?: {
901
905
  ai_description: string | null;
902
906
  ai_usage_instructions: string | null;
903
907
  snippet_type?: "board" | "package" | "model" | "footprint" | undefined;
908
+ latest_license?: string | null | undefined;
904
909
  }[];
905
910
  orders: {
906
911
  error: z.objectOutputType<{
package/dist/index.js CHANGED
@@ -179,6 +179,7 @@ var packageSchema = z.object({
179
179
  website: z.string().nullable().default(null),
180
180
  star_count: z.number().default(0),
181
181
  ai_description: z.string().nullable(),
182
+ latest_license: z.string().nullable().optional(),
182
183
  ai_usage_instructions: z.string().nullable()
183
184
  });
184
185
  var jlcpcbOrderStateSchema = z.object({
@@ -206,6 +206,7 @@ export const packageSchema = z.object({
206
206
  website: z.string().nullable().default(null),
207
207
  star_count: z.number().default(0),
208
208
  ai_description: z.string().nullable(),
209
+ latest_license: z.string().nullable().optional(),
209
210
  ai_usage_instructions: z.string().nullable(),
210
211
  })
211
212
  export type Package = z.infer<typeof packageSchema>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -6,36 +6,35 @@ import { usePreviewImages } from "@/hooks/use-preview-images"
6
6
  import { useGlobalStore } from "@/hooks/use-global-store"
7
7
  import { Button } from "@/components/ui/button"
8
8
  import { useEditPackageDetailsDialog } from "@/components/dialogs/edit-package-details-dialog"
9
- import { useState, useEffect } from "react"
9
+ import { useState, useEffect, useMemo } from "react"
10
10
  import { useCurrentPackageInfo } from "@/hooks/use-current-package-info"
11
-
12
- interface PackageInfo {
13
- name: string
14
- package_id: string
15
- unscoped_name: string
16
- owner_github_username: string
17
- star_count: string
18
- latest_version?: string
19
- description: string
20
- ai_description: string
21
- creator_account_id?: string
22
- owner_org_id?: string
23
- is_package?: boolean
24
- website?: string
25
- }
11
+ import { PackageInfo } from "./sidebar-about-section"
12
+ import { usePackageFile } from "@/hooks/use-package-files"
13
+ import { getLicenseFromLicenseContent } from "@/lib/getLicenseFromLicenseContent"
26
14
 
27
15
  interface MobileSidebarProps {
28
- packageInfo?: PackageInfo
29
16
  isLoading?: boolean
30
17
  onViewChange: (view: "schematic" | "pcb" | "3d") => void
31
18
  }
32
19
 
33
20
  export default function MobileSidebar({
34
- packageInfo,
35
21
  isLoading = false,
36
22
  onViewChange,
37
23
  }: MobileSidebarProps) {
38
- const { refetch: refetchPackageInfo } = useCurrentPackageInfo()
24
+ const { packageInfo, refetch: refetchPackageInfo } = useCurrentPackageInfo()
25
+ const { data: licenseFileMeta } = usePackageFile({
26
+ package_release_id: packageInfo?.latest_package_release_id ?? "",
27
+ file_path: "LICENSE",
28
+ })
29
+ const currentLicense = useMemo(() => {
30
+ if (packageInfo?.latest_license) {
31
+ return packageInfo?.latest_license
32
+ }
33
+ if (licenseFileMeta?.content_text) {
34
+ return getLicenseFromLicenseContent(licenseFileMeta?.content_text)
35
+ }
36
+ return undefined
37
+ }, [licenseFileMeta?.content_text])
39
38
  const topics = packageInfo?.is_package ? ["Package"] : ["Board"]
40
39
  const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
41
40
  const isOwner =
@@ -192,12 +191,16 @@ export default function MobileSidebar({
192
191
  </div>
193
192
  {packageInfo && (
194
193
  <EditPackageDetailsDialog
194
+ packageReleaseId={packageInfo.latest_package_release_id}
195
195
  packageId={packageInfo.package_id}
196
196
  currentDescription={
197
197
  packageInfo.description || packageInfo?.ai_description || ""
198
198
  }
199
+ currentLicense={currentLicense}
200
+ packageAuthor={packageInfo.owner_github_username}
199
201
  currentWebsite={(packageInfo as any)?.website || ""}
200
202
  onUpdate={handlePackageUpdate}
203
+ packageName={packageInfo.name}
201
204
  />
202
205
  )}
203
206
  </div>
@@ -117,7 +117,6 @@ export default function RepoPageContent({
117
117
 
118
118
  return `${name}@${version}`
119
119
  }, [packageInfo, packageFiles])
120
-
121
120
  // Render the appropriate content based on the active view
122
121
  const renderContent = () => {
123
122
  switch (activeView) {
@@ -166,7 +165,6 @@ export default function RepoPageContent({
166
165
  // Update URL hash when view changes
167
166
  window.location.hash = view
168
167
  }}
169
- packageInfo={packageInfo}
170
168
  isLoading={!packageInfo}
171
169
  />
172
170
  </div>
@@ -203,7 +201,6 @@ export default function RepoPageContent({
203
201
  {/* Sidebar - Hidden on mobile, shown on md and up */}
204
202
  <div className="hidden md:block md:w-[296px] flex-shrink-0">
205
203
  <Sidebar
206
- packageInfo={packageInfo}
207
204
  isLoading={!packageInfo}
208
205
  onViewChange={(view) => {
209
206
  setActiveView(view)
@@ -6,9 +6,11 @@ import { usePackageReleaseById } from "@/hooks/use-package-release"
6
6
  import { useGlobalStore } from "@/hooks/use-global-store"
7
7
  import { Button } from "@/components/ui/button"
8
8
  import { useEditPackageDetailsDialog } from "@/components/dialogs/edit-package-details-dialog"
9
- import { useState, useEffect } from "react"
9
+ import { useState, useEffect, useMemo } from "react"
10
+ import { usePackageFile } from "@/hooks/use-package-files"
11
+ import { getLicenseFromLicenseContent } from "@/lib/getLicenseFromLicenseContent"
10
12
 
11
- interface PackageInfo {
13
+ export interface PackageInfo {
12
14
  name: string
13
15
  unscoped_name: string
14
16
  owner_github_username: string
@@ -33,6 +35,20 @@ export default function SidebarAboutSection() {
33
35
  const { data: packageRelease } = usePackageReleaseById(
34
36
  packageInfo?.latest_package_release_id,
35
37
  )
38
+
39
+ const { data: licenseFileMeta, refetch: refetchLicense } = usePackageFile({
40
+ package_release_id: packageInfo?.latest_package_release_id ?? "",
41
+ file_path: "LICENSE",
42
+ })
43
+ const currentLicense = useMemo(() => {
44
+ if (packageInfo?.latest_license) {
45
+ return packageInfo?.latest_license
46
+ }
47
+ if (licenseFileMeta?.content_text) {
48
+ return getLicenseFromLicenseContent(licenseFileMeta?.content_text)
49
+ }
50
+ return null
51
+ }, [licenseFileMeta])
36
52
  const topics = packageInfo?.is_package ? ["Package"] : ["Board"]
37
53
  const isLoading = !packageInfo || !packageRelease
38
54
  const isLoggedIn = useGlobalStore((s) => Boolean(s.session))
@@ -136,21 +152,20 @@ export default function SidebarAboutSection() {
136
152
  ))}
137
153
  </div>
138
154
  <div className="space-y-2 text-sm">
139
- {packageInfo?.license && (
140
- <div className="flex items-center">
141
- <svg
142
- className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]"
143
- viewBox="0 0 16 16"
144
- fill="currentColor"
145
- >
146
- <path d="M8.75.75V2h.985c.304 0 .603.08.867.231l1.29.736c.038.022.08.033.124.033h2.234a.75.75 0 0 1 0 1.5h-.427l2.111 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.006.005-.01.01-.045.04c-.21.176-.441.327-.686.45C14.556 10.78 13.88 11 13 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L12.178 4.5h-.162c-.305 0-.604-.079-.868-.231l-1.29-.736a.245.245 0 0 0-.124-.033H8.75V13h2.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5h2.5V3.5h-.984a.245.245 0 0 0-.124.033l-1.29.736c-.264.152-.563.231-.868.231h-.162l2.112 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.016.015-.045.04c-.21.176-.441.327-.686.45C4.556 10.78 3.88 11 3 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L2.178 4.5H1.75a.75.75 0 0 1 0-1.5h2.234a.249.249 0 0 0 .125-.033l1.29-.736c.263-.15.561-.231.865-.231H7.25V.75a.75.75 0 0 1 1.5 0Zm2.945 8.477c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327Zm-10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327Z"></path>
147
- </svg>
148
- <span>{packageInfo.license} license</span>
149
- </div>
150
- )}
155
+ <div className="flex items-center">
156
+ <svg
157
+ className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]"
158
+ viewBox="0 0 16 16"
159
+ fill="currentColor"
160
+ >
161
+ <path d="M8.75.75V2h.985c.304 0 .603.08.867.231l1.29.736c.038.022.08.033.124.033h2.234a.75.75 0 0 1 0 1.5h-.427l2.111 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.006.005-.01.01-.045.04c-.21.176-.441.327-.686.45C14.556 10.78 13.88 11 13 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L12.178 4.5h-.162c-.305 0-.604-.079-.868-.231l-1.29-.736a.245.245 0 0 0-.124-.033H8.75V13h2.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5h2.5V3.5h-.984a.245.245 0 0 0-.124.033l-1.29.736c-.264.152-.563.231-.868.231h-.162l2.112 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.016.015-.045.04c-.21.176-.441.327-.686.45C4.556 10.78 3.88 11 3 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L2.178 4.5H1.75a.75.75 0 0 1 0-1.5h2.234a.249.249 0 0 0 .125-.033l1.29-.736c.263-.15.561-.231.865-.231H7.25V.75a.75.75 0 0 1 1.5 0Zm2.945 8.477c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327Zm-10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327Z"></path>
162
+ </svg>
163
+ <span>{currentLicense ?? "Unset"} license</span>
164
+ </div>
165
+
151
166
  <div className="flex items-center">
152
167
  <Star className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
153
- <span>{packageInfo?.star_count} stars</span>
168
+ <span>{packageInfo?.star_count} stars </span>
154
169
  </div>
155
170
  <div className="flex items-center">
156
171
  <GitFork className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
@@ -161,12 +176,16 @@ export default function SidebarAboutSection() {
161
176
 
162
177
  {packageInfo && (
163
178
  <EditPackageDetailsDialog
179
+ packageReleaseId={packageInfo.latest_package_release_id}
164
180
  packageId={packageInfo.package_id}
165
181
  currentDescription={
166
182
  packageInfo.description || packageInfo?.ai_description || ""
167
183
  }
184
+ currentLicense={currentLicense}
185
+ packageAuthor={packageInfo.owner_github_username}
168
186
  currentWebsite={(packageInfo as any)?.website || ""}
169
187
  onUpdate={handlePackageUpdate}
188
+ packageName={packageInfo.name}
170
189
  />
171
190
  )}
172
191
  </>
@@ -4,6 +4,7 @@ import { useState } from "react"
4
4
  import SidebarAboutSection from "./sidebar-about-section"
5
5
  import SidebarReleasesSection from "./sidebar-releases-section"
6
6
  import PreviewImageSquares from "./preview-image-squares"
7
+ import { PackageFile } from "fake-snippets-api/lib/db/schema"
7
8
 
8
9
  interface PackageInfo {
9
10
  name: string
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Generate license content based on license type
3
+ * @param licenseType The type of license (e.g., "MIT")
4
+ * @param authorName The name of the package author
5
+ * @returns The license content as a string
6
+ */
7
+ export type LicenseType =
8
+ | "MIT"
9
+ | "Apache-2.0"
10
+ | "BSD-3-Clause"
11
+ | "GPL-3.0"
12
+ | "unset"
13
+ | string
14
+
15
+ export const getLicenseContent = (
16
+ licenseType: LicenseType,
17
+ authorName?: string | null,
18
+ ): string => {
19
+ const currentYear = new Date().getFullYear()
20
+ const author = authorName ?? ""
21
+
22
+ switch (licenseType) {
23
+ case "MIT": {
24
+ return `MIT License
25
+
26
+ Copyright (c) ${currentYear} ${author}
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining a copy
29
+ of this software and associated documentation files (the "Software"), to deal
30
+ in the Software without restriction, including without limitation the rights
31
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32
+ copies of the Software, and to permit persons to whom the Software is
33
+ furnished to do so, subject to the following conditions:
34
+
35
+ The above copyright notice and this permission notice shall be included in all
36
+ copies or substantial portions of the Software.
37
+
38
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
+ SOFTWARE.`
45
+ }
46
+ case "Apache-2.0": {
47
+ return ` Apache License
48
+ Version 2.0, January 2004
49
+ http://www.apache.org/licenses/
50
+
51
+ Copyright ${currentYear} ${author}
52
+
53
+ Licensed under the Apache License, Version 2.0 (the "License");
54
+ you may not use this file except in compliance with the License.
55
+ You may obtain a copy of the License at
56
+
57
+ http://www.apache.org/licenses/LICENSE-2.0
58
+
59
+ Unless required by applicable law or agreed to in writing, software
60
+ distributed under the License is distributed on an "AS IS" BASIS,
61
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
62
+ See the License for the specific language governing permissions and
63
+ limitations under the License.`
64
+ }
65
+ case "BSD-3-Clause": {
66
+ return `BSD 3-Clause License
67
+
68
+ Copyright (c) ${currentYear}, ${author}
69
+ All rights reserved.
70
+
71
+ Redistribution and use in source and binary forms, with or without
72
+ modification, are permitted provided that the following conditions are met:
73
+
74
+ 1. Redistributions of source code must retain the above copyright notice, this
75
+ list of conditions and the following disclaimer.
76
+
77
+ 2. Redistributions in binary form must reproduce the above copyright notice,
78
+ this list of conditions and the following disclaimer in the documentation
79
+ and/or other materials provided with the distribution.
80
+
81
+ 3. Neither the name of the copyright holder nor the names of its
82
+ contributors may be used to endorse or promote products derived from
83
+ this software without specific prior written permission.
84
+
85
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
86
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
87
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
88
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
89
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
90
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
91
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
92
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
93
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
94
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.`
95
+ }
96
+ case "GPL-3.0": {
97
+ return ` GNU GENERAL PUBLIC LICENSE
98
+ Version 3, 29 June 2007
99
+
100
+ Copyright (C) ${currentYear} ${author}
101
+
102
+ This program is free software: you can redistribute it and/or modify
103
+ it under the terms of the GNU General Public License as published by
104
+ the Free Software Foundation, either version 3 of the License, or
105
+ (at your option) any later version.
106
+
107
+ This program is distributed in the hope that it will be useful,
108
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
109
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
110
+ GNU General Public License for more details.
111
+
112
+ You should have received a copy of the GNU General Public License
113
+ along with this program. If not, see <http://www.gnu.org/licenses/>.`
114
+ }
115
+ default: {
116
+ return ""
117
+ }
118
+ }
119
+ }
@@ -6,7 +6,6 @@ import {
6
6
  DialogDescription,
7
7
  } from "../ui/dialog"
8
8
  import { Button } from "../ui/button"
9
- import { useState, useEffect, useMemo } from "react"
10
9
  import { useMutation, useQueryClient } from "react-query"
11
10
  import { createUseDialog } from "./create-use-dialog"
12
11
  import { useAxios } from "@/hooks/use-axios"
@@ -14,16 +13,15 @@ import { useToast } from "@/hooks/use-toast"
14
13
  import { Textarea } from "../ui/textarea"
15
14
  import { Input } from "../ui/input"
16
15
  import { Label } from "../ui/label"
17
-
18
- const isValidUrl = (url: string): boolean => {
19
- if (!url) return true
20
- try {
21
- new URL(url)
22
- return true
23
- } catch (e) {
24
- return false
25
- }
26
- }
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectItem,
20
+ SelectTrigger,
21
+ SelectValue,
22
+ } from "@/components/ui/select"
23
+ import { getLicenseContent } from "../ViewPackagePage/utils/get-license-content"
24
+ import { usePackageDetailsForm } from "@/hooks/use-package-details-form"
27
25
 
28
26
  export const EditPackageDetailsDialog = ({
29
27
  open,
@@ -31,45 +29,44 @@ export const EditPackageDetailsDialog = ({
31
29
  packageId,
32
30
  currentDescription,
33
31
  currentWebsite,
32
+ currentLicense,
34
33
  onUpdate,
34
+ packageName,
35
+ packageReleaseId,
36
+ packageAuthor,
35
37
  }: {
36
38
  open: boolean
37
39
  onOpenChange: (open: boolean) => void
38
40
  packageId: string
39
41
  currentDescription: string
40
42
  currentWebsite: string
41
- onUpdate?: (newDescription: string, newWebsite: string) => void
43
+ currentLicense?: string | null
44
+ packageAuthor?: string | null
45
+ packageName: string
46
+ packageReleaseId: string | null
47
+ onUpdate?: (
48
+ newDescription: string,
49
+ newWebsite: string,
50
+ newLicense: string | null,
51
+ ) => void
42
52
  }) => {
43
- const [description, setDescription] = useState(currentDescription)
44
- const [website, setWebsite] = useState(currentWebsite)
45
- const [websiteError, setWebsiteError] = useState<string | null>(null)
46
53
  const axios = useAxios()
47
54
  const { toast } = useToast()
48
55
  const qc = useQueryClient()
49
56
 
50
- useEffect(() => {
51
- if (open) {
52
- setDescription(currentDescription)
53
- setWebsite(currentWebsite)
54
- setWebsiteError(null)
55
- }
56
- }, [open, currentDescription, currentWebsite])
57
-
58
- useEffect(() => {
59
- if (website && !isValidUrl(website)) {
60
- setWebsiteError("Please enter a valid URL (e.g., https://tscircuit.com)")
61
- } else {
62
- setWebsiteError(null)
63
- }
64
- }, [website])
65
-
66
- const hasChanges = useMemo(() => {
67
- return description !== currentDescription || website !== currentWebsite
68
- }, [description, website, currentDescription, currentWebsite])
69
-
70
- const isFormValid = useMemo(() => {
71
- return !websiteError
72
- }, [websiteError])
57
+ const {
58
+ formData,
59
+ setFormData,
60
+ websiteError,
61
+ hasLicenseChanged,
62
+ hasChanges,
63
+ isFormValid,
64
+ } = usePackageDetailsForm({
65
+ initialDescription: currentDescription,
66
+ initialWebsite: currentWebsite,
67
+ initialLicense: currentLicense || null,
68
+ isDialogOpen: open,
69
+ })
73
70
 
74
71
  const updatePackageDetailsMutation = useMutation({
75
72
  mutationFn: async () => {
@@ -77,29 +74,83 @@ export const EditPackageDetailsDialog = ({
77
74
  throw new Error("Please fix the form errors before submitting")
78
75
  }
79
76
 
77
+ if (hasLicenseChanged) {
78
+ await axios.post("/package_releases/update", {
79
+ package_id: packageId,
80
+ description: formData.description,
81
+ package_release_id: packageReleaseId,
82
+ website: formData.website,
83
+ license: formData.license ?? "unset",
84
+ })
85
+ }
86
+
80
87
  const response = await axios.post("/packages/update", {
81
88
  package_id: packageId,
82
- description: description,
83
- website: website,
89
+ description: formData.description,
90
+ package_release_id: packageReleaseId,
91
+ website: formData.website,
84
92
  })
93
+
85
94
  if (response.status !== 200) {
86
95
  console.error("Failed to update package details:", response.data)
87
96
  throw new Error("Failed to update package details")
88
97
  }
89
- return response.data
98
+
99
+ const packageFiles = []
100
+ const packageFilesResponse = await axios.post("/package_files/list", {
101
+ package_name_with_version: `${packageName}`,
102
+ })
103
+
104
+ if (packageFilesResponse.status === 200) {
105
+ packageFiles.push(
106
+ ...packageFilesResponse.data.package_files.map(
107
+ (x: { file_path: string }) => x.file_path,
108
+ ),
109
+ )
110
+ }
111
+
112
+ const licenseContent = getLicenseContent(
113
+ formData.license ?? "",
114
+ packageAuthor,
115
+ )
116
+ if (hasLicenseChanged) {
117
+ let concludedLicenseResult
118
+ if (packageFiles.includes("LICENSE") && !licenseContent) {
119
+ concludedLicenseResult = await axios.post("/package_files/delete", {
120
+ package_name_with_version: `${packageName}`,
121
+ file_path: "LICENSE",
122
+ })
123
+ }
124
+ if (licenseContent) {
125
+ concludedLicenseResult = await axios.post(
126
+ "/package_files/create_or_update",
127
+ {
128
+ package_name_with_version: `${packageName}`,
129
+ file_path: "LICENSE",
130
+ content_text: licenseContent,
131
+ },
132
+ )
133
+ }
134
+ try {
135
+ if (concludedLicenseResult) {
136
+ window?.location?.reload?.()
137
+ }
138
+ } catch {}
139
+ }
90
140
  },
91
141
  onMutate: async () => {
92
142
  await qc.cancelQueries({ queryKey: ["packages", packageId] })
93
143
  const previousPackage = qc.getQueryData(["packages", packageId])
94
144
  qc.setQueryData(["packages", packageId], (old: any) => ({
95
145
  ...old,
96
- description: description,
97
- website: website,
146
+ description: formData.description,
147
+ website: formData.website,
148
+ license: formData.license,
98
149
  }))
99
150
  return { previousPackage }
100
151
  },
101
152
  onSuccess: () => {
102
- onUpdate?.(description, website)
153
+ onUpdate?.(formData.description, formData.website, formData.license)
103
154
  onOpenChange(false)
104
155
  toast({
105
156
  title: "Package details updated",
@@ -135,8 +186,10 @@ export const EditPackageDetailsDialog = ({
135
186
  <Label htmlFor="website">Website</Label>
136
187
  <Input
137
188
  id="website"
138
- value={website}
139
- onChange={(e) => setWebsite(e.target.value)}
189
+ value={formData.website}
190
+ onChange={(e) =>
191
+ setFormData((prev) => ({ ...prev, website: e.target.value }))
192
+ }
140
193
  placeholder="https://example.com"
141
194
  disabled={updatePackageDetailsMutation.isLoading}
142
195
  aria-invalid={!!websiteError}
@@ -146,12 +199,41 @@ export const EditPackageDetailsDialog = ({
146
199
  <p className="text-sm text-red-500 mt-1">{websiteError}</p>
147
200
  )}
148
201
  </div>
202
+ <div className="space-y-2">
203
+ <Label htmlFor="license">License</Label>
204
+ <Select
205
+ value={formData.license || "unset"}
206
+ onValueChange={(value) =>
207
+ setFormData((prev) => ({
208
+ ...prev,
209
+ license: value === "unset" ? null : value,
210
+ }))
211
+ }
212
+ disabled={updatePackageDetailsMutation.isLoading}
213
+ >
214
+ <SelectTrigger className="w-full">
215
+ <SelectValue placeholder="Select a license" />
216
+ </SelectTrigger>
217
+ <SelectContent className="!z-[999]">
218
+ <SelectItem value="MIT">MIT</SelectItem>
219
+ <SelectItem value="Apache-2.0">Apache-2.0</SelectItem>
220
+ <SelectItem value="BSD-3-Clause">BSD-3-Clause</SelectItem>
221
+ <SelectItem value="GPL-3.0">GPL-3.0</SelectItem>
222
+ <SelectItem value="unset">Unset</SelectItem>
223
+ </SelectContent>
224
+ </Select>
225
+ </div>
149
226
  <div className="space-y-2">
150
227
  <Label htmlFor="description">Description</Label>
151
228
  <Textarea
152
229
  id="description"
153
- value={description}
154
- onChange={(e) => setDescription(e.target.value)}
230
+ value={formData.description}
231
+ onChange={(e) =>
232
+ setFormData((prev) => ({
233
+ ...prev,
234
+ description: e.target.value,
235
+ }))
236
+ }
155
237
  placeholder="Enter package description"
156
238
  disabled={updatePackageDetailsMutation.isLoading}
157
239
  className="resize-none min-h-[100px] w-full"