@tscircuit/fake-snippets 0.0.107 → 0.0.109

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.
Files changed (80) hide show
  1. package/api/generated-index.js +82 -22
  2. package/biome.json +7 -1
  3. package/bun-tests/fake-snippets-api/routes/package_builds/get.test.ts +0 -15
  4. package/bun-tests/fake-snippets-api/routes/package_builds/list.test.ts +0 -12
  5. package/bun.lock +62 -19
  6. package/dist/bundle.js +25 -24
  7. package/dist/index.d.ts +26 -15
  8. package/dist/index.js +19 -18
  9. package/dist/schema.d.ts +32 -24
  10. package/dist/schema.js +7 -6
  11. package/fake-snippets-api/lib/db/db-client.ts +10 -1
  12. package/fake-snippets-api/lib/db/schema.ts +4 -3
  13. package/fake-snippets-api/lib/db/seed.ts +6 -9
  14. package/fake-snippets-api/lib/public-mapping/public-map-package-build.ts +0 -3
  15. package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +3 -0
  16. package/package.json +7 -8
  17. package/src/App.tsx +12 -11
  18. package/src/components/DownloadButtonAndMenu.tsx +133 -35
  19. package/src/components/FileSidebar.tsx +45 -193
  20. package/src/components/Footer.tsx +0 -1
  21. package/src/components/HeaderLogin.tsx +1 -1
  22. package/src/components/HiddenFilesDropdown.tsx +0 -2
  23. package/src/components/PackageBreadcrumb.tsx +1 -1
  24. package/src/components/PackageBuildsPage/PackageBuildDetailsPage.tsx +0 -2
  25. package/src/components/PackageBuildsPage/build-preview-content.tsx +34 -5
  26. package/src/components/PackageCard.tsx +0 -1
  27. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +20 -11
  28. package/src/components/ViewPackagePage/components/important-files-view.tsx +75 -59
  29. package/src/components/ViewPackagePage/components/main-content-header.tsx +4 -4
  30. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +0 -1
  31. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -2
  32. package/src/components/ViewPackagePage/components/package-header.tsx +14 -17
  33. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +0 -1
  34. package/src/components/ViewPackagePage/components/repo-page-content.tsx +21 -20
  35. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +18 -2
  36. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +1 -1
  37. package/src/components/ViewPackagePage/components/sidebar.tsx +0 -2
  38. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +18 -17
  39. package/src/components/ViewPackagePage/components/theme-toggle.tsx +0 -2
  40. package/src/components/ViewPackagePage/hooks/use-toast.tsx +0 -1
  41. package/src/components/package-port/CodeAndPreview.tsx +23 -40
  42. package/src/components/package-port/CodeEditor.tsx +24 -1
  43. package/src/components/package-port/CodeEditorHeader.tsx +5 -2
  44. package/src/components/preview/BuildsList.tsx +20 -9
  45. package/src/components/preview/ConnectedPackagesList.tsx +73 -60
  46. package/src/components/preview/ConnectedRepoOverview.tsx +160 -154
  47. package/src/components/preview/PackageReleasesDashboard.tsx +41 -30
  48. package/src/components/preview/index.tsx +16 -153
  49. package/src/hooks/use-current-package-id.ts +5 -30
  50. package/src/hooks/use-current-package-info.ts +29 -5
  51. package/src/hooks/use-global-store.ts +1 -1
  52. package/src/hooks/useFileManagement.ts +153 -34
  53. package/src/hooks/useOptimizedPackageFilesLoader.ts +149 -0
  54. package/src/hooks/useUpdatePackageFilesMutation.ts +2 -0
  55. package/src/index.css +24 -0
  56. package/src/lib/download-fns/download-circuit-png.ts +11 -3
  57. package/src/lib/download-fns/download-gltf-from-circuit-json.ts +44 -0
  58. package/src/lib/utils/isComponentExported.ts +9 -0
  59. package/src/lib/utils/transformFilesToTreeData.tsx +195 -0
  60. package/src/pages/404.tsx +3 -5
  61. package/src/pages/authorize.tsx +0 -2
  62. package/src/pages/landing.tsx +0 -1
  63. package/src/pages/preview-release.tsx +279 -0
  64. package/src/pages/release-builds.tsx +0 -8
  65. package/src/pages/release-detail.tsx +17 -15
  66. package/src/pages/releases.tsx +5 -1
  67. package/src/pages/view-package.tsx +14 -13
  68. package/src/components/Footer2.tsx +0 -100
  69. package/src/components/ShippingInformationForm.tsx +0 -423
  70. package/src/components/StaticViewSnippetHeader.tsx +0 -70
  71. package/src/components/ViewPackagePage/components/file-explorer.tsx +0 -67
  72. package/src/components/ViewPackagePage/components/readme-view.tsx +0 -58
  73. package/src/components/ViewPackagePage/components/repo-header-button.tsx +0 -36
  74. package/src/components/ViewPackagePage/components/repo-header.tsx +0 -4
  75. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +0 -31
  76. package/src/components/ViewSnippetHeader.tsx +0 -181
  77. package/src/components/ui/input-otp.tsx +0 -69
  78. package/src/hooks/use-snippets-base-api-url.ts +0 -3
  79. package/src/pages/preview-build.tsx +0 -380
  80. package/src/pages/settings.tsx +0 -25
@@ -0,0 +1,149 @@
1
+ import { useQuery, useQueries } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import { usePackageFiles } from "@/hooks/use-package-files"
4
+ import { usePackageFileById } from "@/hooks/use-package-files"
5
+ import type { Package } from "fake-snippets-api/lib/db/schema"
6
+ import { useState, useMemo } from "react"
7
+ import { findTargetFile } from "@/lib/utils/findTargetFile"
8
+
9
+ export interface PackageFile {
10
+ path: string
11
+ content: string
12
+ }
13
+
14
+ export interface OptimizedLoadingState {
15
+ priorityFile: PackageFile | null
16
+ allFiles: PackageFile[]
17
+ isPriorityLoading: boolean
18
+ areAllFilesLoading: boolean
19
+ error: any
20
+ }
21
+
22
+ export function useOptimizedPackageFilesLoader(
23
+ pkg?: Package,
24
+ priorityFilePath?: string | null,
25
+ packageId?: string | null,
26
+ ) {
27
+ const axios = useAxios()
28
+ const [loadedFiles, setLoadedFiles] = useState<Map<string, PackageFile>>(
29
+ new Map(),
30
+ )
31
+
32
+ const pkgFilesRaw = usePackageFiles(pkg?.latest_package_release_id)
33
+
34
+ // Filter out dist/ files to avoid downloading build artifacts
35
+ const pkgFiles = useMemo(
36
+ () => ({
37
+ ...pkgFilesRaw,
38
+ data: pkgFilesRaw.data?.filter(
39
+ (file) => !file.file_path.startsWith("dist/"),
40
+ ),
41
+ }),
42
+ [pkgFilesRaw.data, pkgFilesRaw.isLoading, pkgFilesRaw.error],
43
+ )
44
+
45
+ const targetFilePath = useMemo(() => {
46
+ if (!pkgFiles.data) return priorityFilePath
47
+
48
+ if (priorityFilePath) {
49
+ const exactMatch = pkgFiles.data.find(
50
+ (f) => f.file_path === priorityFilePath,
51
+ )
52
+ if (exactMatch) return exactMatch.file_path
53
+
54
+ const partialMatch = pkgFiles.data.find(
55
+ (f) =>
56
+ f.file_path.includes(priorityFilePath) ||
57
+ priorityFilePath.includes(f.file_path),
58
+ )
59
+ if (partialMatch) return partialMatch.file_path
60
+ }
61
+
62
+ // Check for index.tsx first
63
+ const indexFile = pkgFiles.data.find((f) => f.file_path === "index.tsx")
64
+ if (indexFile) return indexFile.file_path
65
+
66
+ // Fallback to first file
67
+ return pkgFiles.data[0]?.file_path || null
68
+ }, [pkgFiles.data, priorityFilePath])
69
+
70
+ const priorityFileData = pkgFiles.data?.find(
71
+ (file) => file.file_path === targetFilePath,
72
+ )
73
+
74
+ const priorityFileQuery = useQuery({
75
+ queryKey: ["priorityPackageFile", priorityFileData?.package_file_id],
76
+ queryFn: async () => {
77
+ if (!priorityFileData) return null
78
+
79
+ const response = await axios.post(`/package_files/get`, {
80
+ package_file_id: priorityFileData.package_file_id,
81
+ })
82
+ const content = response.data.package_file?.content_text
83
+ const file = { path: priorityFileData.file_path, content: content ?? "" }
84
+
85
+ setLoadedFiles((prev) => {
86
+ const newMap = new Map(prev)
87
+ newMap.set(file.path, file)
88
+ return newMap
89
+ })
90
+
91
+ return file
92
+ },
93
+ enabled: !!priorityFileData,
94
+ refetchOnWindowFocus: false,
95
+ refetchOnMount: false,
96
+ staleTime: Infinity,
97
+ cacheTime: Infinity,
98
+ })
99
+
100
+ const remainingFilesQueries = useQueries(
101
+ pkgFiles.data
102
+ ?.filter((file) => file.file_path !== targetFilePath)
103
+ ?.map((file) => ({
104
+ queryKey: ["packageFile", file.package_file_id],
105
+ queryFn: async () => {
106
+ const response = await axios.post(`/package_files/get`, {
107
+ package_file_id: file.package_file_id,
108
+ })
109
+ const content = response.data.package_file?.content_text
110
+ const fileData = { path: file.file_path, content: content ?? "" }
111
+
112
+ setLoadedFiles((prev) => {
113
+ const newMap = new Map(prev)
114
+ newMap.set(fileData.path, fileData)
115
+ return newMap
116
+ })
117
+
118
+ return fileData
119
+ },
120
+ refetchOnWindowFocus: false,
121
+ refetchOnMount: false,
122
+ staleTime: Infinity,
123
+ cacheTime: Infinity,
124
+ })) ?? [],
125
+ )
126
+
127
+ const allFiles = useMemo(() => {
128
+ return Array.from(loadedFiles.values())
129
+ }, [loadedFiles])
130
+
131
+ const areAllFilesLoading =
132
+ remainingFilesQueries.some((q) => q.isLoading) ||
133
+ priorityFileQuery.isLoading
134
+ const error =
135
+ priorityFileQuery.error || remainingFilesQueries.find((q) => q.error)?.error
136
+
137
+ return {
138
+ priorityFile: priorityFileQuery.data || null,
139
+ priorityFileFetched: priorityFileQuery.isFetched,
140
+ allFiles,
141
+ isPriorityLoading: priorityFileQuery.isLoading,
142
+ areAllFilesLoading,
143
+ error,
144
+ isMetaLoading: pkgFiles.isLoading,
145
+ totalFilesCount: pkgFiles.data?.length || 0,
146
+ loadedFilesCount: allFiles.length,
147
+ isPriorityFileFetched: priorityFileQuery.isFetched,
148
+ }
149
+ }
@@ -2,6 +2,7 @@ import { useMutation } from "react-query"
2
2
  import type { Package } from "fake-snippets-api/lib/db/schema"
3
3
  import { useAxios } from "./use-axios"
4
4
  import { useToast } from "@/components/ViewPackagePage/hooks/use-toast"
5
+ import { isHiddenFile } from "@/components/ViewPackagePage/utils/is-hidden-file"
5
6
 
6
7
  interface PackageFile {
7
8
  path: string
@@ -42,6 +43,7 @@ export function useUpdatePackageFilesMutation({
42
43
  let updatedFilesCount = 0
43
44
 
44
45
  for (const file of localFiles) {
46
+ if (isHiddenFile(file.path)) continue
45
47
  const initialFile = initialFiles.find((x) => x.path === file.path)
46
48
  if (file.content !== initialFile?.content) {
47
49
  const updatePkgFilePayload = {
package/src/index.css CHANGED
@@ -1,6 +1,7 @@
1
1
  @tailwind base;
2
2
  @tailwind components;
3
3
  @tailwind utilities;
4
+
4
5
  @layer base {
5
6
  :root {
6
7
  --radius: 0.5rem;
@@ -18,6 +19,10 @@
18
19
  -ms-overflow-style: none; /* IE and Edge */
19
20
  scrollbar-width: none; /* Firefox */
20
21
  }
22
+
23
+ .shiki {
24
+ @apply no-scrollbar;
25
+ }
21
26
  }
22
27
 
23
28
  .shiki {
@@ -82,3 +87,22 @@
82
87
  body {
83
88
  overflow-x: hidden;
84
89
  }
90
+
91
+ /* Subtle scrollbar styling for html and body */
92
+ html::-webkit-scrollbar,
93
+ body::-webkit-scrollbar {
94
+ width: 6px;
95
+ background-color: transparent;
96
+ }
97
+
98
+ html::-webkit-scrollbar-thumb,
99
+ body::-webkit-scrollbar-thumb {
100
+ background-color: rgba(215, 215, 215, 0.564);
101
+ border-radius: 8px;
102
+ transition: background-color 0.2s;
103
+ }
104
+
105
+ html::-webkit-scrollbar-thumb:hover,
106
+ body::-webkit-scrollbar-thumb:hover {
107
+ background-color: rgba(193, 193, 193, 0.455);
108
+ }
@@ -1,3 +1,4 @@
1
+ import { convertCircuitJsonToSimple3dSvg } from "circuit-json-to-simple-3d"
1
2
  import { AnyCircuitElement } from "circuit-json"
2
3
  import {
3
4
  convertCircuitJsonToAssemblySvg,
@@ -6,7 +7,7 @@ import {
6
7
  } from "circuit-to-svg"
7
8
  import { saveAs } from "file-saver"
8
9
 
9
- export type ImageFormat = "schematic" | "pcb" | "assembly"
10
+ export type ImageFormat = "schematic" | "pcb" | "assembly" | "3d"
10
11
 
11
12
  interface DownloadCircuitPngOptions {
12
13
  format: ImageFormat
@@ -65,7 +66,7 @@ export const downloadCircuitPng = async (
65
66
  if (options.width) svgOptions.width = options.width
66
67
  if (options.height) svgOptions.height = options.height
67
68
 
68
- switch (options.format) {
69
+ switch (options.format.toLowerCase()) {
69
70
  case "schematic":
70
71
  svg = convertCircuitJsonToSchematicSvg(circuitJson, svgOptions)
71
72
  break
@@ -76,13 +77,20 @@ export const downloadCircuitPng = async (
76
77
  svg = convertCircuitJsonToAssemblySvg(circuitJson, svgOptions)
77
78
  break
78
79
  default:
79
- throw new Error(`Unsupported format: ${options.format}`)
80
+ svg = await convertCircuitJsonToSimple3dSvg(circuitJson, {
81
+ background: {
82
+ color: "#fff",
83
+ opacity: 0.0,
84
+ },
85
+ defaultZoomMultiplier: 1.1,
86
+ })
80
87
  }
81
88
 
82
89
  blob = await convertSvgToPng(svg)
83
90
  const downloadFileName = `${fileName}_${options.format}.png`
84
91
  saveAs(blob, downloadFileName)
85
92
  } catch (error) {
93
+ console.error(error)
86
94
  throw new Error(`Failed to download ${options.format} PNG: ${error}`)
87
95
  }
88
96
  }
@@ -0,0 +1,44 @@
1
+ import { CircuitJson } from "circuit-json"
2
+ import { saveAs } from "file-saver"
3
+ import {
4
+ convertCircuitJsonToGltf,
5
+ type ConversionOptions,
6
+ } from "circuit-json-to-gltf"
7
+ export const downloadGltfFromCircuitJson = async (
8
+ circuitJson: CircuitJson,
9
+ fileName: string,
10
+ options?: ConversionOptions,
11
+ ) => {
12
+ const result = await convertCircuitJsonToGltf(circuitJson, options)
13
+
14
+ let blob: Blob
15
+ let extension = options?.format === "glb" ? ".glb" : ".gltf"
16
+
17
+ if (result instanceof ArrayBuffer) {
18
+ blob = new Blob([result], { type: "model/gltf-binary" })
19
+ extension = options?.format
20
+ ? options.format === "glb"
21
+ ? ".glb"
22
+ : ".gltf"
23
+ : ".glb"
24
+ } else if (
25
+ typeof ArrayBuffer !== "undefined" &&
26
+ result &&
27
+ typeof (result as any).buffer === "object" &&
28
+ (result as any).byteLength !== undefined
29
+ ) {
30
+ const view = result as ArrayBufferView
31
+ blob = new Blob([view], { type: "model/gltf-binary" })
32
+ extension = options?.format
33
+ ? options.format === "glb"
34
+ ? ".glb"
35
+ : ".gltf"
36
+ : ".glb"
37
+ } else if (typeof result === "string") {
38
+ blob = new Blob([result], { type: "model/gltf+json" })
39
+ } else {
40
+ blob = new Blob([JSON.stringify(result)], { type: "model/gltf+json" })
41
+ }
42
+
43
+ saveAs(blob, fileName + extension)
44
+ }
@@ -0,0 +1,9 @@
1
+ export const isComponentExported = (code: string) => {
2
+ return (
3
+ /export function\s+\w+/.test(code) ||
4
+ /export const\s+\w+\s*=/.test(code) ||
5
+ /export default\s+\w+/.test(code) ||
6
+ /export default\s+function\s*(\w*)\s*\(/.test(code) ||
7
+ /export default\s*\(\s*\)\s*=>/.test(code)
8
+ )
9
+ }
@@ -0,0 +1,195 @@
1
+ import type { TreeDataItem } from "@/components/ui/tree-view"
2
+ import type {
3
+ IRenameFileProps,
4
+ IDeleteFileProps,
5
+ } from "@/hooks/useFileManagement"
6
+ import { isHiddenFile } from "@/components/ViewPackagePage/utils/is-hidden-file"
7
+ import { File, Folder, MoreVertical, Pencil, Trash2 } from "lucide-react"
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuGroup,
12
+ DropdownMenuItem,
13
+ DropdownMenuTrigger,
14
+ } from "@/components/ui/dropdown-menu"
15
+ import { useToast } from "@/hooks/use-toast"
16
+
17
+ type FileName = string
18
+ type TreeNode = Omit<TreeDataItem, "children"> & {
19
+ children?: Record<string, TreeNode>
20
+ }
21
+ interface TransformFilesToTreeDataProps {
22
+ files: Record<FileName, string>
23
+ currentFile: FileName | null
24
+ renamingFile: string | null
25
+ handleRenameFile: (props: IRenameFileProps) => { fileRenamed: boolean }
26
+ handleDeleteFile: (props: IDeleteFileProps) => { fileDeleted: boolean }
27
+ setRenamingFile: (filename: string | null) => void
28
+ onFileSelect: (filename: FileName) => void
29
+ onFolderSelect: (folderPath: string) => void
30
+ canModifyFiles: boolean
31
+ setErrorMessage: (message: string) => void
32
+ setSelectedFolderForCreation: (folder: string | null) => void
33
+ }
34
+
35
+ export const transformFilesToTreeData = ({
36
+ files,
37
+ currentFile,
38
+ renamingFile,
39
+ handleRenameFile,
40
+ handleDeleteFile,
41
+ setRenamingFile,
42
+ onFileSelect,
43
+ onFolderSelect,
44
+ canModifyFiles,
45
+ setErrorMessage,
46
+ setSelectedFolderForCreation,
47
+ }: TransformFilesToTreeDataProps): TreeDataItem[] => {
48
+ const { toast } = useToast()
49
+ const root: Record<string, TreeNode> = {}
50
+
51
+ Object.keys(files).forEach((filePath) => {
52
+ const hasLeadingSlash = filePath.startsWith("/")
53
+ const pathSegments = (hasLeadingSlash ? filePath.slice(1) : filePath)
54
+ .trim()
55
+ .split("/")
56
+ let currentNode: Record<string, TreeNode> = root
57
+
58
+ pathSegments.forEach((segment, segmentIndex) => {
59
+ const isLeafNode = segmentIndex === pathSegments.length - 1
60
+ const ancestorPath = pathSegments.slice(0, segmentIndex).join("/")
61
+ const relativePath = ancestorPath ? `${ancestorPath}/${segment}` : segment
62
+ const absolutePath = hasLeadingSlash ? `/${relativePath}` : relativePath
63
+ const itemId = absolutePath
64
+
65
+ if (
66
+ !currentNode[segment] &&
67
+ (!isHiddenFile(relativePath) ||
68
+ isHiddenFile(
69
+ currentFile?.startsWith("/")
70
+ ? currentFile.slice(1)
71
+ : currentFile || "",
72
+ ))
73
+ ) {
74
+ currentNode[segment] = {
75
+ id: itemId,
76
+ name: segment,
77
+ isRenaming: renamingFile === itemId,
78
+ onRename: (newFilename: string) => {
79
+ const oldPath = itemId
80
+ const pathParts = oldPath.split("/").filter((part) => part !== "")
81
+ let newPath: string
82
+
83
+ if (pathParts.length > 1) {
84
+ const folderPath = pathParts.slice(0, -1).join("/")
85
+ newPath = folderPath + "/" + newFilename
86
+ } else {
87
+ newPath = newFilename
88
+ }
89
+
90
+ if (oldPath.startsWith("/") && !newPath.startsWith("/")) {
91
+ newPath = "/" + newPath
92
+ }
93
+
94
+ const { fileRenamed } = handleRenameFile({
95
+ oldFilename: itemId,
96
+ newFilename: newPath,
97
+ onError: (error) => {
98
+ toast({
99
+ title: `Error renaming file`,
100
+ description: error.message,
101
+ variant: "destructive",
102
+ })
103
+ },
104
+ })
105
+ if (fileRenamed) {
106
+ setRenamingFile(null)
107
+ }
108
+ },
109
+ onCancelRename: () => {
110
+ setRenamingFile(null)
111
+ },
112
+ icon: isLeafNode ? File : Folder,
113
+ onClick: isLeafNode
114
+ ? () => {
115
+ onFileSelect(absolutePath)
116
+ setSelectedFolderForCreation(null)
117
+ }
118
+ : () => onFolderSelect(absolutePath),
119
+ draggable: false,
120
+ droppable: !isLeafNode,
121
+ children: isLeafNode ? undefined : {},
122
+ actions: canModifyFiles ? (
123
+ <>
124
+ <DropdownMenu key={itemId}>
125
+ <DropdownMenuTrigger asChild>
126
+ <MoreVertical className="w-4 h-4 text-gray-500 hover:text-gray-700" />
127
+ </DropdownMenuTrigger>
128
+ <DropdownMenuContent
129
+ className="w-fit bg-white shadow-lg rounded-md border-4 z-[100] border-white"
130
+ style={{
131
+ position: "absolute",
132
+ top: "100%",
133
+ left: "0",
134
+ marginTop: "0.5rem",
135
+ width: "8rem",
136
+ padding: "0.01rem",
137
+ }}
138
+ >
139
+ <DropdownMenuGroup>
140
+ {isLeafNode && (
141
+ <DropdownMenuItem
142
+ onClick={() => {
143
+ setRenamingFile(itemId)
144
+ }}
145
+ className="flex items-center px-3 py-1 text-xs text-black hover:bg-gray-100 cursor-pointer"
146
+ >
147
+ <Pencil className="mr-2 h-3 w-3" />
148
+ Rename
149
+ </DropdownMenuItem>
150
+ )}
151
+ <DropdownMenuItem
152
+ onClick={() => {
153
+ const { fileDeleted } = handleDeleteFile({
154
+ filename: itemId,
155
+ onError: (error) => {
156
+ toast({
157
+ title: `Error deleting file ${itemId}`,
158
+ description: error.message,
159
+ })
160
+ },
161
+ })
162
+ if (fileDeleted) {
163
+ setErrorMessage("")
164
+ }
165
+ }}
166
+ className="flex items-center px-3 py-1 text-xs text-red-600 hover:bg-gray-100 cursor-pointer"
167
+ >
168
+ <Trash2 className="mr-2 h-3 w-3" />
169
+ Delete
170
+ </DropdownMenuItem>
171
+ </DropdownMenuGroup>
172
+ </DropdownMenuContent>
173
+ </DropdownMenu>
174
+ </>
175
+ ) : undefined,
176
+ }
177
+ }
178
+
179
+ if (!isLeafNode && currentNode[segment]?.children) {
180
+ currentNode = currentNode[segment].children
181
+ }
182
+ })
183
+ })
184
+
185
+ const convertToArray = (items: Record<string, TreeNode>): TreeDataItem[] => {
186
+ return Object.values(items).map((item) => ({
187
+ ...item,
188
+ children: item.children ? convertToArray(item.children) : undefined,
189
+ }))
190
+ }
191
+ return convertToArray(root).filter((x) => {
192
+ if (x.children?.length === 0) return false
193
+ return true
194
+ })
195
+ }
package/src/pages/404.tsx CHANGED
@@ -5,15 +5,13 @@ import { NotFound } from "@/components/NotFound"
5
5
 
6
6
  export function NotFoundPage({
7
7
  heading = "Page Not Found",
8
- }: { heading?: string }) {
8
+ subtitle = "The page you're looking for doesn't exist.",
9
+ }: { heading?: string; subtitle?: string }) {
9
10
  return (
10
11
  <div className="flex min-h-screen flex-col">
11
12
  <Helmet>
12
13
  <title>404 - {heading} | tscircuit</title>
13
- <meta
14
- name="description"
15
- content="The page you're looking for doesn't exist."
16
- />
14
+ <meta name="description" content={subtitle} />
17
15
  </Helmet>
18
16
  <Header2 />
19
17
  <NotFound heading={heading} />
@@ -1,5 +1,3 @@
1
- "use client"
2
-
3
1
  import Header from "@/components/Header"
4
2
  import { useGlobalStore } from "@/hooks/use-global-store"
5
3
  import { useEffect, useState } from "react"
@@ -232,7 +232,6 @@ export function LandingPage() {
232
232
  </section>
233
233
  </main>
234
234
  <Footer />
235
- {/* <Footer2 /> */}
236
235
  </div>
237
236
  )
238
237
  }