@tscircuit/fake-snippets 0.0.100 → 0.0.101

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
@@ -1073,7 +1073,7 @@ declare const createDatabase: ({ seed }?: {
1073
1073
  datasheet_pdf_urls: string[] | null;
1074
1074
  ai_description: string | null;
1075
1075
  }[];
1076
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1076
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "searchAccounts" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1077
1077
  addOrder: (order: Omit<Order, "order_id">) => Order;
1078
1078
  getOrderById: (orderId: string) => Order | undefined;
1079
1079
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -1121,6 +1121,7 @@ declare const createDatabase: ({ seed }?: {
1121
1121
  }) => Snippet | undefined;
1122
1122
  searchSnippets: (query: string) => Snippet[];
1123
1123
  searchPackages: (query: string) => Package[];
1124
+ searchAccounts: (query: string, limit?: number) => Account[];
1124
1125
  deleteSnippet: (snippetId: string) => boolean;
1125
1126
  addSession: (session: Omit<Session, "session_id">) => Session;
1126
1127
  getSessions: ({ account_id, is_cli_session, }: {
@@ -1426,7 +1427,7 @@ declare const createDatabase: ({ seed }?: {
1426
1427
  datasheet_pdf_urls: string[] | null;
1427
1428
  ai_description: string | null;
1428
1429
  }[];
1429
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1430
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "addOrderQuote" | "getOrderQuoteById" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "searchPackages" | "searchAccounts" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId" | "updatePackageReleaseFsSha" | "addAiReview" | "updateAiReview" | "getAiReviewById" | "listAiReviews" | "addDatasheet" | "getDatasheetById" | "getDatasheetByChipName" | "listDatasheets" | "updateDatasheet"> & {
1430
1431
  addOrder: (order: Omit<Order, "order_id">) => Order;
1431
1432
  getOrderById: (orderId: string) => Order | undefined;
1432
1433
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -1474,6 +1475,7 @@ declare const createDatabase: ({ seed }?: {
1474
1475
  }) => Snippet | undefined;
1475
1476
  searchSnippets: (query: string) => Snippet[];
1476
1477
  searchPackages: (query: string) => Package[];
1478
+ searchAccounts: (query: string, limit?: number) => Account[];
1477
1479
  deleteSnippet: (snippetId: string) => boolean;
1478
1480
  addSession: (session: Omit<Session, "session_id">) => Session;
1479
1481
  getSessions: ({ account_id, is_cli_session, }: {
package/dist/index.js CHANGED
@@ -2798,6 +2798,31 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2798
2798
  ];
2799
2799
  return matchingPackages;
2800
2800
  },
2801
+ searchAccounts: (query, limit) => {
2802
+ const state = get();
2803
+ const lowercaseQuery = query.toLowerCase();
2804
+ const accountsWithPublicPackages = /* @__PURE__ */ new Set();
2805
+ state.packages.filter((pkg) => pkg.is_public === true).forEach((pkg) => {
2806
+ if (pkg.creator_account_id) {
2807
+ accountsWithPublicPackages.add(pkg.creator_account_id);
2808
+ }
2809
+ if (pkg.owner_github_username) {
2810
+ const account = state.accounts.find(
2811
+ (acc) => acc.github_username === pkg.owner_github_username
2812
+ );
2813
+ if (account) {
2814
+ accountsWithPublicPackages.add(account.account_id);
2815
+ }
2816
+ }
2817
+ });
2818
+ const matchingAccounts = state.accounts.filter((account) => {
2819
+ if (!accountsWithPublicPackages.has(account.account_id)) {
2820
+ return false;
2821
+ }
2822
+ return account.github_username.toLowerCase().includes(lowercaseQuery);
2823
+ }).slice(0, limit || 50);
2824
+ return matchingAccounts;
2825
+ },
2801
2826
  deleteSnippet: (snippetId) => {
2802
2827
  let deleted = false;
2803
2828
  set((state) => {
@@ -1019,6 +1019,38 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
1019
1019
 
1020
1020
  return matchingPackages
1021
1021
  },
1022
+ searchAccounts: (query: string, limit?: number): Account[] => {
1023
+ const state = get()
1024
+ const lowercaseQuery = query.toLowerCase()
1025
+
1026
+ const accountsWithPublicPackages = new Set<string>()
1027
+ state.packages
1028
+ .filter((pkg) => pkg.is_public === true)
1029
+ .forEach((pkg) => {
1030
+ if (pkg.creator_account_id) {
1031
+ accountsWithPublicPackages.add(pkg.creator_account_id)
1032
+ }
1033
+ if (pkg.owner_github_username) {
1034
+ const account = state.accounts.find(
1035
+ (acc) => acc.github_username === pkg.owner_github_username,
1036
+ )
1037
+ if (account) {
1038
+ accountsWithPublicPackages.add(account.account_id)
1039
+ }
1040
+ }
1041
+ })
1042
+
1043
+ const matchingAccounts = state.accounts
1044
+ .filter((account) => {
1045
+ if (!accountsWithPublicPackages.has(account.account_id)) {
1046
+ return false
1047
+ }
1048
+ return account.github_username.toLowerCase().includes(lowercaseQuery)
1049
+ })
1050
+ .slice(0, limit || 50)
1051
+
1052
+ return matchingAccounts
1053
+ },
1022
1054
  deleteSnippet: (snippetId: string): boolean => {
1023
1055
  let deleted = false
1024
1056
  set((state) => {
@@ -0,0 +1,20 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { accountSchema } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["POST"],
7
+ auth: "session",
8
+ jsonBody: z.object({
9
+ query: z.string(),
10
+ limit: z.number().optional().default(50),
11
+ }),
12
+ jsonResponse: z.object({
13
+ ok: z.boolean(),
14
+ accounts: z.array(accountSchema),
15
+ }),
16
+ })(async (req, ctx) => {
17
+ const { query, limit } = req.jsonBody
18
+ const accounts = ctx.db.searchAccounts(query, limit)
19
+ return ctx.json({ accounts, ok: true })
20
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.100",
3
+ "version": "0.0.101",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -83,7 +83,7 @@
83
83
  "@tscircuit/mm": "^0.0.8",
84
84
  "@tscircuit/pcb-viewer": "^1.11.194",
85
85
  "@tscircuit/prompt-benchmarks": "^0.0.28",
86
- "@tscircuit/runframe": "0.0.725",
86
+ "@tscircuit/runframe": "0.0.736",
87
87
  "@tscircuit/schematic-viewer": "^2.0.21",
88
88
  "@types/babel__standalone": "^7.1.7",
89
89
  "@types/bun": "^1.1.10",
@@ -5,7 +5,7 @@ import { useHotkeyCombo } from "@/hooks/use-hotkey"
5
5
  import { useNotImplementedToast } from "@/hooks/use-toast"
6
6
  import { fuzzyMatch } from "@/components/ViewPackagePage/utils/fuzz-search"
7
7
  import { Command } from "cmdk"
8
- import { Package } from "fake-snippets-api/lib/db/schema"
8
+ import { Package, Account } from "fake-snippets-api/lib/db/schema"
9
9
  import React, { useCallback, useEffect, useMemo, useRef } from "react"
10
10
  import { useQuery } from "react-query"
11
11
  import {
@@ -16,6 +16,8 @@ import {
16
16
  Sparkles,
17
17
  Clock,
18
18
  ArrowRight,
19
+ Star,
20
+ User,
19
21
  } from "lucide-react"
20
22
  import { DialogTitle, DialogDescription } from "@/components/ui/dialog"
21
23
 
@@ -40,6 +42,11 @@ interface ScoredPackage extends Package {
40
42
  matches: number[]
41
43
  }
42
44
 
45
+ interface ScoredAccount extends Account {
46
+ score: number
47
+ matches: number[]
48
+ }
49
+
43
50
  const CmdKMenu = () => {
44
51
  const [open, setOpen] = React.useState(false)
45
52
  const [searchQuery, setSearchQuery] = React.useState("")
@@ -113,13 +120,42 @@ const CmdKMenu = () => {
113
120
  ["packageSearch", searchQuery],
114
121
  async () => {
115
122
  if (!searchQuery) return []
116
- const { data } = await axios.post("/packages/search", {
117
- query: searchQuery,
118
- })
119
- return data.packages || []
123
+ try {
124
+ const { data } = await axios.post("/packages/search", {
125
+ query: searchQuery,
126
+ })
127
+ return data.packages || []
128
+ } catch (error) {
129
+ console.warn("Failed to fetch packages:", error)
130
+ return []
131
+ }
120
132
  },
121
133
  {
122
134
  enabled: Boolean(searchQuery),
135
+ retry: false,
136
+ refetchOnWindowFocus: false,
137
+ },
138
+ )
139
+
140
+ const { data: allAccounts = [], isLoading: isSearchingAccounts } = useQuery(
141
+ ["accountSearch", searchQuery],
142
+ async () => {
143
+ if (!searchQuery) return []
144
+ try {
145
+ const { data } = await axios.post("/accounts/search", {
146
+ query: searchQuery,
147
+ limit: 5,
148
+ })
149
+ return data.accounts || []
150
+ } catch (error) {
151
+ console.warn("Failed to fetch accounts:", error)
152
+ return []
153
+ }
154
+ },
155
+ {
156
+ enabled: Boolean(searchQuery) && Boolean(currentUser),
157
+ retry: false,
158
+ refetchOnWindowFocus: false,
123
159
  },
124
160
  )
125
161
 
@@ -133,20 +169,43 @@ const CmdKMenu = () => {
133
169
  })
134
170
  .filter((pkg: ScoredPackage) => pkg.score >= 0)
135
171
  .sort((a: ScoredPackage, b: ScoredPackage) => b.score - a.score)
136
- .slice(0, 8)
172
+ .slice(0, 6)
137
173
  }, [allPackages, searchQuery])
138
174
 
175
+ const accountSearchResults = useMemo((): ScoredAccount[] => {
176
+ if (!searchQuery || !allAccounts.length) return []
177
+
178
+ return allAccounts
179
+ .map((account: Account) => {
180
+ const { score, matches } = fuzzyMatch(
181
+ searchQuery,
182
+ account.github_username,
183
+ )
184
+ return { ...account, score, matches }
185
+ })
186
+ .filter((account: ScoredAccount) => account.score >= 0)
187
+ .sort((a: ScoredAccount, b: ScoredAccount) => b.score - a.score)
188
+ .slice(0, 5)
189
+ }, [allAccounts, searchQuery])
190
+
139
191
  const { data: recentPackages = [] } = useQuery<Package[]>(
140
192
  ["userPackages", currentUser],
141
193
  async () => {
142
194
  if (!currentUser) return []
143
- const response = await axios.post(`/packages/list`, {
144
- owner_github_username: currentUser,
145
- })
146
- return response.data.packages || []
195
+ try {
196
+ const response = await axios.post(`/packages/list`, {
197
+ owner_github_username: currentUser,
198
+ })
199
+ return response.data.packages || []
200
+ } catch (error) {
201
+ console.warn("Failed to fetch recent packages:", error)
202
+ return []
203
+ }
147
204
  },
148
205
  {
149
206
  enabled: !!currentUser && !searchQuery,
207
+ retry: false,
208
+ refetchOnWindowFocus: false,
150
209
  },
151
210
  )
152
211
 
@@ -195,7 +254,7 @@ const CmdKMenu = () => {
195
254
 
196
255
  const allItems = useMemo(() => {
197
256
  const items: Array<{
198
- type: "package" | "recent" | "template" | "blank" | "import"
257
+ type: "package" | "account" | "recent" | "template" | "blank" | "import"
199
258
  item: any
200
259
  disabled?: boolean
201
260
  }> = []
@@ -206,6 +265,12 @@ const CmdKMenu = () => {
206
265
  })
207
266
  }
208
267
 
268
+ if (searchQuery && accountSearchResults.length > 0) {
269
+ accountSearchResults.forEach((account) => {
270
+ items.push({ type: "account", item: account })
271
+ })
272
+ }
273
+
209
274
  if (!searchQuery && recentPackages.length > 0) {
210
275
  recentPackages.slice(0, 6).forEach((pkg) => {
211
276
  items.push({ type: "recent", item: pkg })
@@ -225,7 +290,13 @@ const CmdKMenu = () => {
225
290
  })
226
291
 
227
292
  return items
228
- }, [searchQuery, searchResults, recentPackages, filteredStaticOptions])
293
+ }, [
294
+ searchQuery,
295
+ searchResults,
296
+ accountSearchResults,
297
+ recentPackages,
298
+ filteredStaticOptions,
299
+ ])
229
300
 
230
301
  useHotkeyCombo("cmd+k", () => {
231
302
  setOpen((prev) => !prev)
@@ -285,6 +356,10 @@ const CmdKMenu = () => {
285
356
  window.location.href = `/${item.owner_github_username}/${item.unscoped_name}`
286
357
  setOpen(false)
287
358
  break
359
+ case "account":
360
+ window.location.href = `/${item.github_username}`
361
+ setOpen(false)
362
+ break
288
363
  case "blank":
289
364
  case "template":
290
365
  if (!item.disabled) {
@@ -362,7 +437,11 @@ const CmdKMenu = () => {
362
437
  )}
363
438
  </div>
364
439
  </div>
365
- <div className="flex items-center gap-1 flex-shrink-0">
440
+ <div className="flex items-center gap-2 flex-shrink-0">
441
+ <div className="flex items-center gap-1 text-gray-500">
442
+ <Star className="w-3 h-3 fill-yellow-400 text-yellow-400" />
443
+ <span className="text-xs">{data.star_count ?? 0}</span>
444
+ </div>
366
445
  <span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
367
446
  package
368
447
  </span>
@@ -371,6 +450,41 @@ const CmdKMenu = () => {
371
450
  </div>
372
451
  )
373
452
 
453
+ case "account":
454
+ return (
455
+ <div
456
+ key={`account-${data.account_id}`}
457
+ ref={isSelected ? selectedItemRef : null}
458
+ className={baseClasses}
459
+ onClick={() => !disabled && handleItemSelect(item)}
460
+ >
461
+ <div className="flex items-center gap-2 min-w-0">
462
+ <img
463
+ src={`https://github.com/${data.github_username}.png`}
464
+ alt={`${data.github_username} avatar`}
465
+ className="w-6 h-6 rounded-full flex-shrink-0"
466
+ onError={(e) => {
467
+ const target = e.target as HTMLImageElement
468
+ target.style.display = "none"
469
+ target.nextElementSibling?.classList.remove("hidden")
470
+ }}
471
+ />
472
+ <User className="w-6 h-6 text-gray-400 flex-shrink-0 hidden" />
473
+ <div className="flex flex-col min-w-0">
474
+ <span className="font-medium text-gray-900 truncate">
475
+ {renderHighlighted(data, data.github_username)}
476
+ </span>
477
+ </div>
478
+ </div>
479
+ <div className="flex items-center gap-1 flex-shrink-0">
480
+ <span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
481
+ user
482
+ </span>
483
+ {isSelected && <ArrowRight className="w-3 h-3 text-gray-400" />}
484
+ </div>
485
+ </div>
486
+ )
487
+
374
488
  case "blank":
375
489
  case "template":
376
490
  return (
@@ -464,7 +578,7 @@ const CmdKMenu = () => {
464
578
  </div>
465
579
 
466
580
  <Command.List className="max-h-80 overflow-y-auto p-2 space-y-4">
467
- {isSearching ? (
581
+ {isSearching || isSearchingAccounts ? (
468
582
  <Command.Loading className="p-6 text-center text-gray-500">
469
583
  <div className="flex items-center justify-center gap-2">
470
584
  <div className="w-3 h-3 border-2 border-gray-300 border-t-gray-600 rounded-full animate-spin"></div>
@@ -476,7 +590,7 @@ const CmdKMenu = () => {
476
590
  {searchQuery && searchResults.length > 0 && (
477
591
  <div>
478
592
  <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2">
479
- Search Results
593
+ Packages
480
594
  </h3>
481
595
  <div className="space-y-1">
482
596
  {searchResults.slice(0, 8).map((pkg, localIndex) => {
@@ -490,6 +604,25 @@ const CmdKMenu = () => {
490
604
  </div>
491
605
  )}
492
606
 
607
+ {searchQuery && accountSearchResults.length > 0 && (
608
+ <div>
609
+ <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2">
610
+ Users
611
+ </h3>
612
+ <div className="space-y-1">
613
+ {accountSearchResults
614
+ .slice(0, 5)
615
+ .map((account, localIndex) => {
616
+ const globalIndex = searchResults.length + localIndex
617
+ return renderItem(
618
+ { type: "account", item: account },
619
+ globalIndex,
620
+ )
621
+ })}
622
+ </div>
623
+ </div>
624
+ )}
625
+
493
626
  {!searchQuery && recentPackages.length > 0 && (
494
627
  <div>
495
628
  <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2 px-2 flex items-center gap-1">
@@ -518,7 +651,7 @@ const CmdKMenu = () => {
518
651
  (template, localIndex) => {
519
652
  const globalIndex =
520
653
  (searchQuery
521
- ? searchResults.length
654
+ ? searchResults.length + accountSearchResults.length
522
655
  : recentPackages.length) + localIndex
523
656
  return renderItem(
524
657
  {
@@ -544,7 +677,7 @@ const CmdKMenu = () => {
544
677
  (template, localIndex) => {
545
678
  const globalIndex =
546
679
  (searchQuery
547
- ? searchResults.length
680
+ ? searchResults.length + accountSearchResults.length
548
681
  : recentPackages.length) +
549
682
  filteredStaticOptions.blankTemplates.length +
550
683
  localIndex
@@ -568,7 +701,7 @@ const CmdKMenu = () => {
568
701
  (option, localIndex) => {
569
702
  const globalIndex =
570
703
  (searchQuery
571
- ? searchResults.length
704
+ ? searchResults.length + accountSearchResults.length
572
705
  : recentPackages.length) +
573
706
  filteredStaticOptions.blankTemplates.length +
574
707
  filteredStaticOptions.templates.length +
@@ -585,10 +718,12 @@ const CmdKMenu = () => {
585
718
 
586
719
  {searchQuery &&
587
720
  !searchResults.length &&
721
+ !accountSearchResults.length &&
588
722
  !filteredStaticOptions.blankTemplates.length &&
589
723
  !filteredStaticOptions.templates.length &&
590
724
  !filteredStaticOptions.importOptions.length &&
591
- !isSearching && (
725
+ !isSearching &&
726
+ !isSearchingAccounts && (
592
727
  <Command.Empty className="py-8 text-center">
593
728
  <div className="text-gray-400 mb-1">No results found</div>
594
729
  <div className="text-gray-500 text-xs">
@@ -1,5 +1,5 @@
1
1
  import { Button } from "@/components/ui/button"
2
- import { CircuitBoard, Search } from "lucide-react"
2
+ import { CircuitBoard, Search, Menu, X } from "lucide-react"
3
3
  import { Link } from "wouter"
4
4
  import { PrefetchPageLink } from "./PrefetchPageLink"
5
5
  import { HeaderLogin } from "./HeaderLogin"
@@ -53,10 +53,12 @@ const SearchButtonComponent = () => {
53
53
  }
54
54
 
55
55
  export const Header2 = () => {
56
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
56
57
  const isLoggedIn = useGlobalStore((state) => Boolean(state.session))
58
+
57
59
  return (
58
60
  <>
59
- <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
61
+ <header className="sticky top-0 z-50 w-full border-b bg-white md:bg-white/80 md:backdrop-blur supports-[backdrop-filter]:md:bg-white/60">
60
62
  <div className="container mx-auto flex h-16 items-center justify-between px-2 md:px-6">
61
63
  <PrefetchPageLink
62
64
  href="/"
@@ -65,23 +67,17 @@ export const Header2 = () => {
65
67
  <CircuitBoard className="h-6 w-6" />
66
68
  <span className="text-lg font-bold">tscircuit</span>
67
69
  </PrefetchPageLink>
68
- <nav className="flex md:hidden">
70
+
71
+ {/* Desktop Navigation */}
72
+ <nav className="hidden md:flex gap-6">
69
73
  {isLoggedIn && (
70
- <Link
74
+ <PrefetchPageLink
71
75
  className="text-sm font-medium hover:underline underline-offset-4"
72
76
  href="/dashboard"
73
77
  >
74
78
  Dashboard
75
- </Link>
79
+ </PrefetchPageLink>
76
80
  )}
77
- </nav>
78
- <nav className="hidden md:flex gap-6">
79
- <PrefetchPageLink
80
- className="text-sm font-medium hover:underline underline-offset-4"
81
- href="/dashboard"
82
- >
83
- Dashboard
84
- </PrefetchPageLink>
85
81
  <PrefetchPageLink
86
82
  className="text-sm font-medium hover:underline underline-offset-4"
87
83
  href="/quickstart"
@@ -94,12 +90,6 @@ export const Header2 = () => {
94
90
  >
95
91
  Datasheets
96
92
  </PrefetchPageLink>
97
- {/* <a
98
- className="text-sm font-medium hover:underline underline-offset-4"
99
- href="https://github.com/tscircuit/tscircuit"
100
- >
101
- Github
102
- </a> */}
103
93
  <a
104
94
  className="text-sm font-medium hover:underline underline-offset-4"
105
95
  href="https://docs.tscircuit.com"
@@ -119,16 +109,107 @@ export const Header2 = () => {
119
109
  Contact
120
110
  </a>
121
111
  </nav>
122
- <div className="flex items-center gap-4">
112
+
113
+ {/* Desktop Right Side */}
114
+ <div className="hidden md:flex items-center gap-4">
123
115
  <SearchButtonComponent />
124
- {isLoggedIn && (
125
- <div className="hidden sm:block">
126
- <HeaderDropdown />
127
- </div>
128
- )}
116
+ {isLoggedIn && <HeaderDropdown />}
129
117
  <HeaderLogin />
130
118
  </div>
119
+
120
+ {/* Mobile Right Side */}
121
+ <div className="flex md:hidden items-center gap-2">
122
+ <Button
123
+ variant="ghost"
124
+ size="icon"
125
+ onClick={() => (window.location.href = "/search")}
126
+ className="h-8 w-8"
127
+ aria-label="Go to search"
128
+ >
129
+ <Search className="size-4" />
130
+ </Button>
131
+ {isLoggedIn ? (
132
+ <HeaderLogin />
133
+ ) : (
134
+ <PrefetchPageLink href="/quickstart">
135
+ <Button size="sm" className="text-xs px-3 py-1">
136
+ Get Started
137
+ </Button>
138
+ </PrefetchPageLink>
139
+ )}
140
+ <button
141
+ className="p-2"
142
+ onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
143
+ aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
144
+ >
145
+ {mobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
146
+ </button>
147
+ </div>
131
148
  </div>
149
+
150
+ {/* Mobile Menu */}
151
+ {mobileMenuOpen && (
152
+ <div className="md:hidden border-t bg-white ">
153
+ <div className="container mx-auto px-4 py-3">
154
+ <nav className="mb-4">
155
+ <div className="flex flex-col items-center gap-1">
156
+ {isLoggedIn && (
157
+ <PrefetchPageLink
158
+ className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
159
+ href="/dashboard"
160
+ onClick={() => setMobileMenuOpen(false)}
161
+ >
162
+ Dashboard
163
+ </PrefetchPageLink>
164
+ )}
165
+ <PrefetchPageLink
166
+ className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
167
+ href="/quickstart"
168
+ onClick={() => setMobileMenuOpen(false)}
169
+ >
170
+ Editor
171
+ </PrefetchPageLink>
172
+ <PrefetchPageLink
173
+ className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
174
+ href="/datasheets"
175
+ onClick={() => setMobileMenuOpen(false)}
176
+ >
177
+ Datasheets
178
+ </PrefetchPageLink>
179
+ <a
180
+ className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
181
+ href="https://docs.tscircuit.com"
182
+ >
183
+ Docs
184
+ </a>
185
+ <a
186
+ className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
187
+ href="https://tscircuit.com/join"
188
+ >
189
+ Discord
190
+ </a>
191
+ <a
192
+ className="text-sm font-medium hover:underline underline-offset-4 py-2 w-full text-center"
193
+ href="mailto:hello@tscircuit.com"
194
+ >
195
+ Contact
196
+ </a>
197
+ </div>
198
+ </nav>
199
+ <div className="flex flex-col items-center gap-4 pt-4 border-t border-gray-200">
200
+ {isLoggedIn ? (
201
+ <div className="w-full flex justify-center">
202
+ <HeaderDropdown />
203
+ </div>
204
+ ) : (
205
+ <div className="w-full flex justify-center">
206
+ <HeaderLogin />
207
+ </div>
208
+ )}
209
+ </div>
210
+ </div>
211
+ </div>
212
+ )}
132
213
  </header>
133
214
  <CmdKMenu />
134
215
  <Analytics />