@tscircuit/fake-snippets 0.0.37 → 0.0.39

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
@@ -702,7 +702,7 @@ declare const createDatabase: ({ seed }?: {
702
702
  step_function_name: string | null;
703
703
  error_message: string | null;
704
704
  }[];
705
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getNewestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
705
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
706
706
  addOrder: (order: Omit<Order, "order_id">) => Order;
707
707
  getOrderById: (orderId: string) => Order | undefined;
708
708
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -734,7 +734,7 @@ declare const createDatabase: ({ seed }?: {
734
734
  updateAccountPackage: (accountPackageId: string, updates: Partial<AccountPackage>) => void;
735
735
  deleteAccountPackage: (accountPackageId: string) => boolean;
736
736
  addSnippet: (snippet: Omit<z.input<typeof snippetSchema>, "snippet_id" | "package_release_id">) => Snippet;
737
- getNewestSnippets: (limit: number) => Snippet[];
737
+ getLatestSnippets: (limit: number) => Snippet[];
738
738
  getTrendingSnippets: (limit: number, since: string) => Snippet[];
739
739
  getPackagesByAuthor: (authorName?: string) => Package[];
740
740
  getSnippetByAuthorAndName: (authorName: string, snippetName: string) => Snippet | undefined;
@@ -946,7 +946,7 @@ declare const createDatabase: ({ seed }?: {
946
946
  step_function_name: string | null;
947
947
  error_message: string | null;
948
948
  }[];
949
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getNewestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
949
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
950
950
  addOrder: (order: Omit<Order, "order_id">) => Order;
951
951
  getOrderById: (orderId: string) => Order | undefined;
952
952
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -978,7 +978,7 @@ declare const createDatabase: ({ seed }?: {
978
978
  updateAccountPackage: (accountPackageId: string, updates: Partial<AccountPackage>) => void;
979
979
  deleteAccountPackage: (accountPackageId: string) => boolean;
980
980
  addSnippet: (snippet: Omit<z.input<typeof snippetSchema>, "snippet_id" | "package_release_id">) => Snippet;
981
- getNewestSnippets: (limit: number) => Snippet[];
981
+ getLatestSnippets: (limit: number) => Snippet[];
982
982
  getTrendingSnippets: (limit: number, since: string) => Snippet[];
983
983
  getPackagesByAuthor: (authorName?: string) => Package[];
984
984
  getSnippetByAuthorAndName: (authorName: string, snippetName: string) => Snippet | undefined;
package/dist/index.js CHANGED
@@ -2209,7 +2209,7 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2209
2209
  is_unlisted: false
2210
2210
  };
2211
2211
  },
2212
- getNewestSnippets: (limit) => {
2212
+ getLatestSnippets: (limit) => {
2213
2213
  const state = get();
2214
2214
  const snippetPackages = state.packages.filter((pkg) => pkg.is_snippet === true).map((pkg) => {
2215
2215
  const packageRelease = state.packageReleases.find(
@@ -320,7 +320,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
320
320
  is_unlisted: false,
321
321
  }
322
322
  },
323
- getNewestSnippets: (limit: number): Snippet[] => {
323
+ getLatestSnippets: (limit: number): Snippet[] => {
324
324
  const state = get()
325
325
 
326
326
  // Get all packages that are snippets
@@ -26,9 +26,7 @@ export default withRouteSpec({
26
26
 
27
27
  const pkg = ctx.db.packages[packageIndex]
28
28
 
29
- console.log("pkg", pkg.owner_org_id, ctx.auth.personal_org_id)
30
-
31
- if (pkg.owner_org_id !== ctx.auth.personal_org_id) {
29
+ if (pkg.owner_github_username !== ctx.auth.github_username) {
32
30
  return ctx.error(403, {
33
31
  error_code: "forbidden",
34
32
  message: "You don't have permission to delete this package",
@@ -8,6 +8,6 @@ export default withRouteSpec({
8
8
  snippets: z.array(snippetSchema),
9
9
  }),
10
10
  })(async (req, ctx) => {
11
- const newestSnippets = ctx.db.getNewestSnippets(20)
12
- return ctx.json({ snippets: newestSnippets })
11
+ const latestSnippets = ctx.db.getLatestSnippets(20)
12
+ return ctx.json({ snippets: latestSnippets })
13
13
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@ const staticRoutes = [
9
9
  { url: "/playground", changefreq: "weekly", priority: 0.9 },
10
10
  { url: "/quickstart", changefreq: "monthly", priority: 0.8 },
11
11
  { url: "/dashboard", changefreq: "weekly", priority: 0.7 },
12
- { url: "/newest", changefreq: "daily", priority: 0.8 },
12
+ { url: "/latest", changefreq: "daily", priority: 0.8 },
13
13
  { url: "/search", changefreq: "weekly", priority: 0.7 },
14
14
  { url: "/settings", changefreq: "monthly", priority: 0.6 },
15
15
  { url: "/community/join-redirect", changefreq: "monthly", priority: 0.6 },
package/src/App.tsx CHANGED
@@ -61,7 +61,7 @@ const EditorPage = lazyImport(async () => {
61
61
  })
62
62
  const LandingPage = lazyImport(() => import("@/pages/landing"))
63
63
  const MyOrdersPage = lazyImport(() => import("@/pages/my-orders"))
64
- const NewestPage = lazyImport(() => import("@/pages/newest"))
64
+ const LatestPage = lazyImport(() => import("@/pages/latest"))
65
65
  const PreviewPage = lazyImport(() => import("@/pages/preview"))
66
66
  const QuickstartPage = lazyImport(() => import("@/pages/quickstart"))
67
67
  const SearchPage = lazyImport(() => import("@/pages/search"))
@@ -112,7 +112,7 @@ function App() {
112
112
  <Route path="/quickstart" component={QuickstartPage} />
113
113
  <Route path="/dashboard" component={DashboardPage} />
114
114
  <Route path="/ai" component={AiPage} />
115
- <Route path="/newest" component={NewestPage} />
115
+ <Route path="/latest" component={LatestPage} />
116
116
  <Route path="/settings" component={SettingsPage} />
117
117
  <Route path="/search" component={SearchPage} />
118
118
  <Route path="/trending" component={TrendingPage} />
@@ -56,8 +56,8 @@ export default function Footer() {
56
56
  <div className="space-y-4">
57
57
  <h3 className="font-semibold uppercase">Explore</h3>
58
58
  <footer className="flex flex-col space-y-2">
59
- <PrefetchPageLink href="/newest" className="hover:underline">
60
- Newest Snippets
59
+ <PrefetchPageLink href="/latest" className="hover:underline">
60
+ Latest Snippets
61
61
  </PrefetchPageLink>
62
62
  <PrefetchPageLink href="/trending" className="hover:underline">
63
63
  Trending Snippets
@@ -0,0 +1,44 @@
1
+ "use client"
2
+
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu"
9
+ import { Settings, Check } from "lucide-react"
10
+
11
+ interface HiddenFilesDropdownProps {
12
+ showHiddenFiles: boolean
13
+ onToggleHiddenFiles: () => void
14
+ }
15
+
16
+ export default function HiddenFilesDropdown({
17
+ showHiddenFiles,
18
+ onToggleHiddenFiles,
19
+ }: HiddenFilesDropdownProps) {
20
+ return (
21
+ <DropdownMenu>
22
+ <DropdownMenuTrigger asChild>
23
+ <button className="ml-2 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 focus:outline-none">
24
+ <Settings className="h-4 w-4" />
25
+ </button>
26
+ </DropdownMenuTrigger>
27
+ <DropdownMenuContent
28
+ align="end"
29
+ className="bg-white dark:bg-[#161b22] border border-gray-200 dark:border-[#30363d] rounded-md shadow-lg"
30
+ >
31
+ <DropdownMenuItem
32
+ onSelect={(e) => e.preventDefault()}
33
+ onClick={onToggleHiddenFiles}
34
+ className="text-xs text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-[#21262d] flex items-center gap-2"
35
+ >
36
+ <Check
37
+ className={`h-4 w-4 transition-opacity ${showHiddenFiles ? "opacity-100" : "opacity-0"}`}
38
+ />
39
+ Show Hidden Files
40
+ </DropdownMenuItem>
41
+ </DropdownMenuContent>
42
+ </DropdownMenu>
43
+ )
44
+ }
@@ -11,7 +11,7 @@ export const LatestSnippets: React.FC = () => {
11
11
  isLoading,
12
12
  error,
13
13
  } = useQuery<Snippet[]>("latestSnippets", async () => {
14
- const response = await axios.get("/snippets/list_newest")
14
+ const response = await axios.get("/snippets/list_latest")
15
15
  return response.data.snippets
16
16
  })
17
17
 
@@ -24,7 +24,7 @@ export default function ThreeDView() {
24
24
 
25
25
  return (
26
26
  <div className="h-[620px]">
27
- <CadViewer circuitJson={circuitJson} />
27
+ <CadViewer clickToInteractEnabled circuitJson={circuitJson} />
28
28
  </div>
29
29
  )
30
30
  }
@@ -23,7 +23,7 @@ export default function BOMView() {
23
23
  }
24
24
 
25
25
  return (
26
- <div className="border border-gray-200 dark:border-[#30363d] rounded-md p-4 mb-4 bg-white dark:bg-[#0d1117] w-full h-[620px]">
26
+ <div className="border border-gray-200 dark:border-[#30363d] rounded-md p-4 mb-4 bg-white dark:bg-[#0d1117] w-full h-[620px] overflow-y-scroll">
27
27
  <BomTable circuitJson={circuitJson} />
28
28
  </div>
29
29
  )
@@ -5,6 +5,14 @@ import { FileText, Folder } from "lucide-react"
5
5
  import { useMemo, useState } from "react"
6
6
  import { isHiddenFile } from "../../utils/is-hidden-file"
7
7
  import { isWithinDirectory } from "../../utils/is-within-directory"
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ } from "@/components/ui/dropdown-menu"
14
+ import { Settings } from "lucide-react"
15
+ import HiddenFilesDropdown from "@/components/HiddenFilesDropdown"
8
16
 
9
17
  interface Directory {
10
18
  type: "directory"
@@ -41,6 +49,7 @@ export default function FilesView({
41
49
  onFileClicked,
42
50
  }: FilesViewProps) {
43
51
  const [activeDir, setActiveDir] = useState("")
52
+ const [showHiddenFiles, setShowHiddenFiles] = useState(false)
44
53
 
45
54
  // Parse package files to determine directories and files structure
46
55
  const { directories, files } = useMemo(() => {
@@ -52,7 +61,7 @@ export default function FilesView({
52
61
  const filesList: File[] = []
53
62
 
54
63
  packageFiles
55
- .filter((file) => !isHiddenFile(file.file_path))
64
+ .filter((file) => showHiddenFiles || !isHiddenFile(file.file_path))
56
65
  .forEach((file) => {
57
66
  // Extract directory path
58
67
  const pathParts = file.file_path.split("/")
@@ -64,6 +73,7 @@ export default function FilesView({
64
73
  currentPath += (currentPath ? "/" : "") + part
65
74
  // Only add directory if it contains visible files
66
75
  if (
76
+ showHiddenFiles ||
67
77
  packageFiles.some(
68
78
  (f) =>
69
79
  f.file_path.startsWith(currentPath + "/") &&
@@ -100,7 +110,7 @@ export default function FilesView({
100
110
  directories: dirsList,
101
111
  files: filesList,
102
112
  }
103
- }, [packageFiles])
113
+ }, [packageFiles, showHiddenFiles])
104
114
  // Format date for display
105
115
  const formatDate = (dateString: string) => {
106
116
  const date = new Date(dateString)
@@ -155,6 +165,8 @@ export default function FilesView({
155
165
  setActiveDir(parentDir)
156
166
  }
157
167
 
168
+ const toggleHiddenFiles = () => setShowHiddenFiles((prev) => !prev)
169
+
158
170
  if (isLoading) {
159
171
  return (
160
172
  <div className="mb-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
@@ -197,6 +209,11 @@ export default function FilesView({
197
209
  <span>
198
210
  {files.length} files, {directories.length} directories
199
211
  </span>
212
+
213
+ <HiddenFilesDropdown
214
+ showHiddenFiles={showHiddenFiles}
215
+ onToggleHiddenFiles={toggleHiddenFiles}
216
+ />
200
217
  </div>
201
218
 
202
219
  {/* Mobile view */}
@@ -208,6 +225,10 @@ export default function FilesView({
208
225
  </div>
209
226
  <div className="flex items-center text-xs text-gray-500 dark:text-[#8b949e]">
210
227
  <span>{files.length + directories.length} items</span>
228
+ <HiddenFilesDropdown
229
+ showHiddenFiles={showHiddenFiles}
230
+ onToggleHiddenFiles={toggleHiddenFiles}
231
+ />
211
232
  </div>
212
233
  </div>
213
234
  </div>
@@ -26,6 +26,7 @@ export default function SchematicView() {
26
26
  return (
27
27
  <div className="h-[620px]">
28
28
  <SchematicViewer
29
+ clickToInteractEnabled
29
30
  circuitJson={circuitJson}
30
31
  containerStyle={{
31
32
  height: 620,
@@ -18,7 +18,7 @@ export const DashboardPage = () => {
18
18
 
19
19
  const currentUser = useGlobalStore((s) => s.session?.github_username)
20
20
  const [showAllTrending, setShowAllTrending] = useState(false)
21
- const [showAllNewest, setShowAllNewest] = useState(false)
21
+ const [showAllLatest, setShowAllLatest] = useState(false)
22
22
 
23
23
  const {
24
24
  data: mySnippets,
@@ -41,10 +41,10 @@ export const DashboardPage = () => {
41
41
  },
42
42
  )
43
43
 
44
- const { data: newestSnippets } = useQuery<Snippet[]>(
45
- "newestSnippets",
44
+ const { data: latestSnippets } = useQuery<Snippet[]>(
45
+ "latestSnippets",
46
46
  async () => {
47
- const response = await axios.get("/snippets/list_newest")
47
+ const response = await axios.get("/snippets/list_latest")
48
48
  return response.data.snippets
49
49
  },
50
50
  )
@@ -129,10 +129,10 @@ export const DashboardPage = () => {
129
129
  />
130
130
  <div className="mt-8">
131
131
  <SnippetList
132
- title="Newest Snippets"
133
- snippets={newestSnippets}
134
- showAll={showAllNewest}
135
- onToggleShowAll={() => setShowAllNewest(!showAllNewest)}
132
+ title="Latest Snippets"
133
+ snippets={latestSnippets}
134
+ showAll={showAllLatest}
135
+ onToggleShowAll={() => setShowAllLatest(!showAllLatest)}
136
136
  />
137
137
  </div>
138
138
  </div>
@@ -0,0 +1,212 @@
1
+ import React, { useState } from "react"
2
+ import { useQuery } from "react-query"
3
+ import { useAxios } from "@/hooks/use-axios"
4
+ import { Snippet } from "fake-snippets-api/lib/db/schema"
5
+ import Header from "@/components/Header"
6
+ import Footer from "@/components/Footer"
7
+ import {
8
+ Search,
9
+ Tag,
10
+ Calendar,
11
+ Keyboard,
12
+ Cpu,
13
+ Layers,
14
+ LucideBellElectric,
15
+ } from "lucide-react"
16
+ import { Input } from "@/components/ui/input"
17
+ import { Badge } from "@/components/ui/badge"
18
+ import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
19
+ import {
20
+ Select,
21
+ SelectContent,
22
+ SelectItem,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ } from "@/components/ui/select"
26
+ import { SnippetCard } from "@/components/SnippetCard"
27
+
28
+ const LatestPage: React.FC = () => {
29
+ const axios = useAxios()
30
+ const apiBaseUrl = useSnippetsBaseApiUrl()
31
+ const [searchQuery, setSearchQuery] = useState("")
32
+ const [category, setCategory] = useState("all")
33
+
34
+ const {
35
+ data: snippets,
36
+ isLoading,
37
+ error,
38
+ } = useQuery<Snippet[]>(
39
+ ["latestSnippets", category],
40
+ async () => {
41
+ const params = category !== "all" ? { tag: category } : {}
42
+ const response = await axios.get("/snippets/list_latest", { params })
43
+ return response.data.snippets
44
+ },
45
+ {
46
+ keepPreviousData: true,
47
+ },
48
+ )
49
+
50
+ const filteredSnippets = snippets?.filter((snippet) => {
51
+ if (!searchQuery) return true
52
+
53
+ const query = searchQuery.toLowerCase().trim()
54
+
55
+ const searchableFields = [
56
+ snippet.unscoped_name.toLowerCase(),
57
+ snippet.owner_name.toLowerCase(),
58
+ (snippet.description || "").toLowerCase(),
59
+ ]
60
+
61
+ return searchableFields.some((field) => {
62
+ const queryWords = query.split(/\s+/).filter((word) => word.length > 0)
63
+ return queryWords.every((word) => field.includes(word))
64
+ })
65
+ })
66
+
67
+ return (
68
+ <div className="min-h-screen flex flex-col bg-gray-50">
69
+ <Header />
70
+ <main className="flex-grow container mx-auto px-4 py-8">
71
+ <div className="mb-8 max-w-3xl">
72
+ <div className="flex items-center gap-2 mb-3">
73
+ <Calendar className="w-6 h-6 text-blue-500" />
74
+ <h1 className="text-4xl font-bold text-gray-900">
75
+ Latest Snippets
76
+ </h1>
77
+ </div>
78
+ <p className="text-lg text-gray-600 mb-4">
79
+ Explore the latest circuit designs from our community. These fresh
80
+ additions showcase new ideas and innovative approaches to circuit
81
+ design.
82
+ </p>
83
+ <div className="flex flex-wrap gap-3">
84
+ <Badge variant="secondary" className="px-3 py-1">
85
+ <Tag className="w-3.5 h-3.5 mr-1" />
86
+ <span>Latest Uploads</span>
87
+ </Badge>
88
+ <Badge variant="secondary" className="px-3 py-1">
89
+ <Calendar className="w-3.5 h-3.5 mr-1" />
90
+ <span>Most Recent First</span>
91
+ </Badge>
92
+ </div>
93
+ </div>
94
+
95
+ <div className="mb-6">
96
+ <div className="flex flex-col sm:flex-row gap-4 mb-4">
97
+ <div className="relative flex-grow">
98
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
99
+ <Input
100
+ type="text"
101
+ placeholder="Search latest snippets..."
102
+ value={searchQuery}
103
+ onChange={(e) => setSearchQuery(e.target.value)}
104
+ className="pl-10"
105
+ />
106
+ </div>
107
+ <Select value={category} onValueChange={setCategory}>
108
+ <SelectTrigger className="w-full sm:w-[180px]">
109
+ <SelectValue placeholder="Category" />
110
+ </SelectTrigger>
111
+ <SelectContent>
112
+ <SelectItem value="all">All Categories</SelectItem>
113
+ <SelectItem value="keyboard">
114
+ <div className="flex items-center">
115
+ <Keyboard className="mr-2 h-4 w-4" />
116
+ <span>Keyboards</span>
117
+ </div>
118
+ </SelectItem>
119
+ <SelectItem value="microcontroller">
120
+ <div className="flex items-center">
121
+ <Cpu className="mr-2 h-4 w-4" />
122
+ <span>Microcontrollers</span>
123
+ </div>
124
+ </SelectItem>
125
+ <SelectItem value="connector">
126
+ <div className="flex items-center">
127
+ <Layers className="mr-2 h-4 w-4" />
128
+ <span>Connectors</span>
129
+ </div>
130
+ </SelectItem>
131
+ <SelectItem value="sensor">
132
+ <div className="flex items-center">
133
+ <LucideBellElectric className="mr-2 h-4 w-4" />
134
+ <span>Sensors</span>
135
+ </div>
136
+ </SelectItem>
137
+ </SelectContent>
138
+ </Select>
139
+ </div>
140
+ </div>
141
+
142
+ {isLoading ? (
143
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
144
+ {[...Array(6)].map((_, i) => (
145
+ <div key={i} className="border p-4 rounded-md animate-pulse">
146
+ <div className="flex items-start gap-4">
147
+ <div className="h-16 w-16 flex-shrink-0 rounded-md bg-slate-200"></div>
148
+ <div className="flex-1">
149
+ <div className="h-5 bg-slate-200 rounded w-3/4 mb-2"></div>
150
+ <div className="h-4 bg-slate-200 rounded w-1/2 mb-2"></div>
151
+ <div className="flex gap-2">
152
+ <div className="h-3 bg-slate-200 rounded w-16"></div>
153
+ <div className="h-3 bg-slate-200 rounded w-16"></div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ ))}
159
+ </div>
160
+ ) : error ? (
161
+ <div className="bg-red-50 border border-red-200 text-red-700 p-6 rounded-xl shadow-sm max-w-2xl mx-auto">
162
+ <div className="flex items-start">
163
+ <div className="mr-4 bg-red-100 p-2 rounded-full">
164
+ <Search className="w-6 h-6 text-red-600" />
165
+ </div>
166
+ <div>
167
+ <h3 className="text-lg font-semibold mb-2">
168
+ Error Loading Snippets
169
+ </h3>
170
+ <p className="text-red-600">
171
+ We couldn't load the latest snippets. Please try again later.
172
+ </p>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ ) : filteredSnippets?.length === 0 ? (
177
+ <div className="text-center py-12 px-4">
178
+ <div className="bg-slate-50 inline-flex rounded-full p-4 mb-4">
179
+ <Search className="w-8 h-8 text-slate-400" />
180
+ </div>
181
+ <h3 className="text-xl font-medium text-slate-900 mb-2">
182
+ No Matching Snippets
183
+ </h3>
184
+ <p className="text-slate-500 max-w-md mx-auto mb-6">
185
+ {searchQuery
186
+ ? `No snippets match your search for "${searchQuery}".`
187
+ : category !== "all"
188
+ ? `No ${category} snippets found in the latest list.`
189
+ : "There are no new snippets at the moment."}
190
+ </p>
191
+ </div>
192
+ ) : (
193
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
194
+ {filteredSnippets
195
+ ?.sort((a, b) => b.created_at.localeCompare(a.created_at))
196
+ ?.map((snippet) => (
197
+ <SnippetCard
198
+ key={snippet.snippet_id}
199
+ snippet={snippet}
200
+ baseUrl={apiBaseUrl}
201
+ showOwner={true}
202
+ />
203
+ ))}
204
+ </div>
205
+ )}
206
+ </main>
207
+ <Footer />
208
+ </div>
209
+ )
210
+ }
211
+
212
+ export default LatestPage
@@ -1,16 +0,0 @@
1
- import Header from "@/components/Header"
2
- import Footer from "@/components/Footer"
3
- import { LatestSnippets } from "@/components/LatestSnippets"
4
-
5
- export const NewestPage = () => {
6
- return (
7
- <div>
8
- <Header />
9
- <div className="container mx-auto px-4 py-8">
10
- <h1 className="text-3xl font-bold mb-6">Newest Snippets</h1>
11
- <LatestSnippets />
12
- </div>
13
- <Footer />
14
- </div>
15
- )
16
- }