@tscircuit/fake-snippets 0.0.119 → 0.0.120

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.
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto"
1
2
  import type { z } from "zod"
2
3
  import { hoist } from "zustand-hoist"
3
4
  import { createStore } from "zustand/vanilla"
@@ -12,6 +13,10 @@ import {
12
13
  type Order,
13
14
  type OrderFile,
14
15
  OrderQuote,
16
+ type BugReport,
17
+ bugReportSchema,
18
+ type BugReportFile,
19
+ bugReportFileSchema,
15
20
  type Package,
16
21
  type PackageFile,
17
22
  type PackageRelease,
@@ -177,6 +182,90 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
177
182
  const state = get()
178
183
  return state.orderFiles.find((file) => file.order_file_id === orderFileId)
179
184
  },
185
+ addBugReport: ({
186
+ reporter_account_id,
187
+ text,
188
+ is_auto_deleted,
189
+ delete_at,
190
+ }: {
191
+ reporter_account_id: string
192
+ text?: string | null
193
+ is_auto_deleted?: boolean
194
+ delete_at?: string | null
195
+ }): BugReport => {
196
+ const normalizedIsAutoDeleted = Boolean(is_auto_deleted)
197
+ if (normalizedIsAutoDeleted && !delete_at) {
198
+ throw new Error("delete_at is required when is_auto_deleted is true")
199
+ }
200
+ const normalizedDeleteAt = normalizedIsAutoDeleted
201
+ ? (delete_at ?? null)
202
+ : null
203
+
204
+ const bugReport = bugReportSchema.parse({
205
+ bug_report_id: randomUUID(),
206
+ reporter_account_id,
207
+ text: text ?? null,
208
+ is_auto_deleted: normalizedIsAutoDeleted,
209
+ delete_at: normalizedDeleteAt,
210
+ created_at: new Date().toISOString(),
211
+ file_count: 0,
212
+ })
213
+
214
+ set((state) => ({
215
+ bugReports: [...state.bugReports, bugReport],
216
+ }))
217
+
218
+ return bugReport
219
+ },
220
+ getBugReportById: (bugReportId: string): BugReport | undefined => {
221
+ const state = get()
222
+ return state.bugReports.find(
223
+ (bugReport) => bugReport.bug_report_id === bugReportId,
224
+ )
225
+ },
226
+ addBugReportFile: ({
227
+ bug_report_id,
228
+ file_path,
229
+ content_mimetype,
230
+ is_text,
231
+ content_text,
232
+ content_bytes,
233
+ }: {
234
+ bug_report_id: string
235
+ file_path: string
236
+ content_mimetype: string
237
+ is_text: boolean
238
+ content_text: string | null
239
+ content_bytes: Uint8Array | null
240
+ }): BugReportFile => {
241
+ const bugReportFile = bugReportFileSchema.parse({
242
+ bug_report_file_id: randomUUID(),
243
+ bug_report_id,
244
+ file_path,
245
+ content_mimetype,
246
+ is_text,
247
+ created_at: new Date().toISOString(),
248
+ content_text,
249
+ content_bytes,
250
+ })
251
+
252
+ set((state) => ({
253
+ bugReportFiles: [...state.bugReportFiles, bugReportFile],
254
+ bugReports: state.bugReports.map((bugReport) =>
255
+ bugReport.bug_report_id === bug_report_id
256
+ ? { ...bugReport, file_count: bugReport.file_count + 1 }
257
+ : bugReport,
258
+ ),
259
+ }))
260
+
261
+ return bugReportFile
262
+ },
263
+ getBugReportFilesByBugReportId: (bugReportId: string): BugReportFile[] => {
264
+ const state = get()
265
+ return state.bugReportFiles.filter(
266
+ (file) => file.bug_report_id === bugReportId,
267
+ )
268
+ },
180
269
  addAccount: (
181
270
  account: Omit<Account, "account_id" | "is_tscircuit_staff"> &
182
271
  Partial<Pick<Account, "account_id" | "is_tscircuit_staff">>,
@@ -103,6 +103,33 @@ export const orderFileSchema = z.object({
103
103
  })
104
104
  export type OrderFile = z.infer<typeof orderFileSchema>
105
105
 
106
+ export const bugReportSchema = z.object({
107
+ bug_report_id: z.string().uuid(),
108
+ reporter_account_id: z.string(),
109
+ text: z.string().nullable(),
110
+ is_auto_deleted: z.boolean().default(false),
111
+ delete_at: z.string().datetime().nullable(),
112
+ created_at: z.string().datetime(),
113
+ file_count: z.number().int(),
114
+ })
115
+ export type BugReport = z.infer<typeof bugReportSchema>
116
+
117
+ export const bugReportFileSchema = z.object({
118
+ bug_report_file_id: z.string().uuid(),
119
+ bug_report_id: z.string().uuid(),
120
+ file_path: z.string(),
121
+ content_mimetype: z.string(),
122
+ is_text: z.boolean(),
123
+ created_at: z.string().datetime(),
124
+ content_text: z.string().nullable(),
125
+ content_bytes: z.instanceof(Uint8Array).nullable(),
126
+ })
127
+ export const bugReportFileResponseSchema = bugReportFileSchema.omit({
128
+ content_text: true,
129
+ content_bytes: true,
130
+ })
131
+ export type BugReportFile = z.infer<typeof bugReportFileSchema>
132
+
106
133
  const shippingOptionSchema = z.object({
107
134
  carrier: z.string(),
108
135
  service: z.string(),
@@ -469,5 +496,7 @@ export const databaseSchema = z.object({
469
496
  datasheets: z.array(datasheetSchema).default([]),
470
497
  githubInstallations: z.array(githubInstallationSchema).default([]),
471
498
  packageBuilds: z.array(packageBuildSchema).default([]),
499
+ bugReports: z.array(bugReportSchema).default([]),
500
+ bugReportFiles: z.array(bugReportFileSchema).default([]),
472
501
  })
473
502
  export type DatabaseSchema = z.infer<typeof databaseSchema>
@@ -0,0 +1,43 @@
1
+ import { bugReportSchema } from "fake-snippets-api/lib/db/schema"
2
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
3
+ import { z } from "zod"
4
+
5
+ const requestSchema = z
6
+ .object({
7
+ text: z.string().optional(),
8
+ is_auto_deleted: z.boolean().optional(),
9
+ delete_at: z.string().datetime().optional(),
10
+ })
11
+ .superRefine((data, ctx) => {
12
+ if (data.is_auto_deleted && !data.delete_at) {
13
+ ctx.addIssue({
14
+ code: z.ZodIssueCode.custom,
15
+ message: "delete_at is required when is_auto_deleted is true",
16
+ path: ["delete_at"],
17
+ })
18
+ }
19
+ })
20
+
21
+ export default withRouteSpec({
22
+ methods: ["POST"],
23
+ auth: "session",
24
+ jsonBody: requestSchema,
25
+ jsonResponse: z.object({
26
+ ok: z.literal(true),
27
+ bug_report: bugReportSchema,
28
+ }),
29
+ })(async (req, ctx) => {
30
+ const { text, is_auto_deleted = false, delete_at } = req.jsonBody
31
+
32
+ const bugReport = ctx.db.addBugReport({
33
+ reporter_account_id: ctx.auth.account_id,
34
+ text: text ?? null,
35
+ is_auto_deleted,
36
+ delete_at: is_auto_deleted ? delete_at : null,
37
+ })
38
+
39
+ return ctx.json({
40
+ ok: true,
41
+ bug_report: bugReport,
42
+ })
43
+ })
@@ -0,0 +1,113 @@
1
+ import { Buffer } from "node:buffer"
2
+ import { bugReportFileResponseSchema } from "fake-snippets-api/lib/db/schema"
3
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
4
+ import { normalizeProjectFilePathAndValidate } from "fake-snippets-api/utils/normalizeProjectFilePath"
5
+ import { z } from "zod"
6
+
7
+ const requestSchema = z
8
+ .object({
9
+ bug_report_id: z.string().uuid(),
10
+ file_path: z.string(),
11
+ content_mimetype: z.string().optional(),
12
+ content_text: z.string().optional(),
13
+ content_base64: z.string().optional(),
14
+ })
15
+ .superRefine((data, ctx) => {
16
+ if (data.content_text !== undefined && data.content_base64 !== undefined) {
17
+ ctx.addIssue({
18
+ code: z.ZodIssueCode.custom,
19
+ message: "Provide either content_text or content_base64, not both",
20
+ path: ["content_text"],
21
+ })
22
+ }
23
+ })
24
+
25
+ const inferMimetype = (filePath: string, provided?: string) => {
26
+ if (provided) return provided
27
+
28
+ const lowerPath = filePath.toLowerCase()
29
+ if (lowerPath.endsWith(".ts") || lowerPath.endsWith(".tsx")) {
30
+ return "text/typescript"
31
+ }
32
+ if (lowerPath.endsWith(".js")) return "application/javascript"
33
+ if (lowerPath.endsWith(".json")) return "application/json"
34
+ if (lowerPath.endsWith(".md")) return "text/markdown"
35
+ if (lowerPath.endsWith(".html")) return "text/html"
36
+ if (lowerPath.endsWith(".css")) return "text/css"
37
+ if (lowerPath.endsWith(".txt")) return "text/plain"
38
+ return "application/octet-stream"
39
+ }
40
+
41
+ export default withRouteSpec({
42
+ methods: ["POST"],
43
+ auth: "session",
44
+ jsonBody: requestSchema,
45
+ jsonResponse: z.object({
46
+ ok: z.literal(true),
47
+ bug_report_file: bugReportFileResponseSchema,
48
+ }),
49
+ })(async (req, ctx) => {
50
+ const {
51
+ bug_report_id,
52
+ file_path,
53
+ content_mimetype,
54
+ content_text,
55
+ content_base64,
56
+ } = req.jsonBody
57
+
58
+ const bugReport = ctx.db.getBugReportById(bug_report_id)
59
+ if (!bugReport) {
60
+ return ctx.error(404, {
61
+ error_code: "bug_report_not_found",
62
+ message: "Bug report not found",
63
+ })
64
+ }
65
+
66
+ if (bugReport.reporter_account_id !== ctx.auth.account_id) {
67
+ return ctx.error(403, {
68
+ error_code: "bug_report_forbidden",
69
+ message: "You do not have access to modify this bug report",
70
+ })
71
+ }
72
+
73
+ let normalizedPath: string
74
+ try {
75
+ normalizedPath = normalizeProjectFilePathAndValidate(file_path)
76
+ } catch (error) {
77
+ return ctx.error(400, {
78
+ error_code: "invalid_file_path",
79
+ message: error instanceof Error ? error.message : "Invalid file path",
80
+ })
81
+ }
82
+
83
+ const mimeType = inferMimetype(normalizedPath, content_mimetype)
84
+ const hasBase64 = content_base64 !== undefined
85
+ const hasText = content_text !== undefined
86
+ const isTextUpload = hasText || !hasBase64
87
+ const storedText = isTextUpload ? (content_text ?? "") : null
88
+ const storedBytes =
89
+ isTextUpload || !hasBase64
90
+ ? null
91
+ : Buffer.from(content_base64 ?? "", "base64")
92
+
93
+ const bugReportFile = ctx.db.addBugReportFile({
94
+ bug_report_id,
95
+ file_path: normalizedPath,
96
+ content_mimetype: mimeType,
97
+ is_text: isTextUpload,
98
+ content_text: storedText,
99
+ content_bytes: storedBytes,
100
+ })
101
+
102
+ return ctx.json({
103
+ ok: true,
104
+ bug_report_file: {
105
+ bug_report_file_id: bugReportFile.bug_report_file_id,
106
+ bug_report_id: bugReportFile.bug_report_id,
107
+ file_path: bugReportFile.file_path,
108
+ content_mimetype: bugReportFile.content_mimetype,
109
+ is_text: bugReportFile.is_text,
110
+ created_at: bugReportFile.created_at,
111
+ },
112
+ })
113
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.119",
3
+ "version": "0.0.120",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -81,7 +81,7 @@
81
81
  "@tscircuit/3d-viewer": "^0.0.407",
82
82
  "@tscircuit/assembly-viewer": "^0.0.5",
83
83
  "@tscircuit/create-snippet-url": "^0.0.8",
84
- "@tscircuit/eval": "^0.0.410",
84
+ "@tscircuit/eval": "^0.0.414",
85
85
  "@tscircuit/layout": "^0.0.29",
86
86
  "@tscircuit/mm": "^0.0.8",
87
87
  "@tscircuit/pcb-viewer": "^1.11.218",
@@ -24,7 +24,23 @@ const HeaderButton = ({
24
24
  alsoHighlightForUrl?: string
25
25
  }) => {
26
26
  const [location] = useLocation()
27
+ const isExternal = /^(https?|mailto|tel):\/?\//i.test(href)
28
+ if (isExternal) {
29
+ return (
30
+ <a
31
+ className={cn("header-button", className)}
32
+ href={href}
33
+ target="_blank"
34
+ rel="noopener noreferrer"
35
+ >
36
+ <Button className={className} variant="ghost">
37
+ {children}
38
+ </Button>
39
+ </a>
40
+ )
41
+ }
27
42
 
43
+ // For internal links, use the Link Component
28
44
  if (location === href || location === alsoHighlightForUrl) {
29
45
  return (
30
46
  <Link className={cn("header-button", className)} href={href}>
@@ -20,12 +20,11 @@ import {
20
20
  import { SnippetType, SnippetTypeIcon } from "./SnippetTypeIcon"
21
21
  import { timeAgo } from "@/lib/utils/timeAgo"
22
22
  import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
23
+ import { getPackagePreviewImageUrl } from "@/lib/utils/getPackagePreviewImageUrl"
23
24
 
24
25
  export interface PackageCardProps {
25
26
  /** The package data to display */
26
27
  pkg: Package
27
- /** Base URL for package images */
28
- baseUrl: string
29
28
  /** Whether to show the owner name (useful in starred views) */
30
29
  showOwner?: boolean
31
30
  /** Whether this is the current user's package (enables edit/delete options) */
@@ -46,7 +45,6 @@ export interface PackageCardProps {
46
45
 
47
46
  export const PackageCard: React.FC<PackageCardProps> = ({
48
47
  pkg,
49
- baseUrl,
50
48
  showOwner = false,
51
49
  isCurrentUserPackage = false,
52
50
  onDeleteClick,
@@ -73,6 +71,11 @@ export const PackageCard: React.FC<PackageCardProps> = ({
73
71
 
74
72
  const availableImages = ["pcb", "schematic", "assembly", "3d"]
75
73
 
74
+ const previewImageUrl = getPackagePreviewImageUrl(
75
+ pkg,
76
+ pkg.default_view as "pcb" | "schematic" | "3d",
77
+ )
78
+
76
79
  const cardContent = (
77
80
  <div
78
81
  className={`border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4 ${className}`}
@@ -82,7 +85,7 @@ export const PackageCard: React.FC<PackageCardProps> = ({
82
85
  className={`${imageSize} flex-shrink-0 rounded-md overflow-hidden bg-gray-50 border flex items-center justify-center`}
83
86
  >
84
87
  <img
85
- src={`${baseUrl}/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/${availableImages.includes(pkg.default_view || "") ? pkg.default_view : "3d"}.png?fs_sha=${pkg.latest_package_release_fs_sha}`}
88
+ src={String(previewImageUrl)}
86
89
  alt={`${pkg.unscoped_name} ${availableImages.includes(pkg.default_view || "") ? pkg.default_view : "3D"} view`}
87
90
  className={`object-cover h-full w-full ${imageTransform}`}
88
91
  onError={(e) => {
@@ -14,16 +14,10 @@ interface PackageSearchResultsProps {
14
14
 
15
15
  const PackageGrid = ({
16
16
  packages,
17
- baseUrl,
18
17
  }: { packages: Package[]; baseUrl: string }) => (
19
18
  <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
20
19
  {packages.map((pkg) => (
21
- <PackageCard
22
- key={pkg.package_id}
23
- pkg={pkg}
24
- baseUrl={baseUrl}
25
- showOwner={true}
26
- />
20
+ <PackageCard key={pkg.package_id} pkg={pkg} showOwner={true} />
27
21
  ))}
28
22
  </div>
29
23
  )
@@ -4,7 +4,6 @@ import { useLocation } from "wouter"
4
4
  import React, { useEffect, useRef, useState } from "react"
5
5
  import { useQuery } from "react-query"
6
6
  import { Alert } from "./ui/alert"
7
- import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
8
7
  import { Link } from "wouter"
9
8
  import { CircuitBoard } from "lucide-react"
10
9
  import { cn } from "@/lib/utils"
@@ -60,7 +59,6 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
60
59
  const resultsRef = useRef<HTMLDivElement>(null)
61
60
  const inputRef = useRef<HTMLInputElement>(null)
62
61
  const [location, setLocation] = useLocation()
63
- const apiBaseUrl = useApiBaseUrl()
64
62
 
65
63
  const { data: searchResults, isLoading } = useQuery(
66
64
  ["packageSearch", searchQuery],
@@ -202,60 +200,73 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
202
200
  >
203
201
  {searchResults.length > 0 ? (
204
202
  <ul className="divide-y divide-gray-200 no-scrollbar">
205
- {searchResults.map((pkg: any, index: number) => (
206
- <li
207
- key={pkg.package_id}
208
- className={cn(
209
- "p-2 hover:bg-gray-50",
210
- index === highlightedIndex && "bg-gray-100",
211
- )}
212
- >
213
- <LinkWithNewTabHandling
214
- href={
215
- shouldOpenInEditor
216
- ? `/editor?package_id=${pkg.package_id}`
217
- : `/${pkg.name}`
218
- }
219
- shouldOpenInNewTab={shouldOpenInNewTab}
220
- className="flex"
221
- onClick={() => {
222
- setShowResults(false)
223
- if (closeOnClick) closeOnClick()
224
- }}
203
+ {searchResults.map((pkg: any, index: number) => {
204
+ const previewImageUrl =
205
+ pkg.latest_pcb_preview_image_url ??
206
+ pkg.latest_cad_preview_image_url ??
207
+ pkg.latest_sch_preview_image_url ??
208
+ undefined
209
+ const hasPreviewImage = Boolean(previewImageUrl)
210
+
211
+ return (
212
+ <li
213
+ key={pkg.package_id}
214
+ className={cn(
215
+ "p-2 hover:bg-gray-50",
216
+ index === highlightedIndex && "bg-gray-100",
217
+ )}
225
218
  >
226
- <div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm bg-gray-50 border flex items-center justify-center">
227
- <img
228
- src={`${apiBaseUrl}/packages/images/${pkg.name}/pcb.svg`}
229
- alt={`PCB preview for ${pkg.name}`}
230
- draggable={false}
231
- className="w-12 h-12 object-contain p-1 scale-[4] rotate-45"
232
- onError={(e) => {
233
- e.currentTarget.style.display = "none"
234
- e.currentTarget.nextElementSibling?.classList.remove(
235
- "hidden",
236
- )
237
- e.currentTarget.nextElementSibling?.classList.add(
238
- "flex",
239
- )
240
- }}
241
- />
242
- <div className="w-12 h-12 hidden items-center justify-center">
243
- <CircuitBoard className="w-6 h-6 text-gray-300" />
244
- </div>
245
- </div>
246
- <div className="flex-grow">
247
- <div className="font-medium text-blue-600 break-words text-xs">
248
- {pkg.name}
219
+ <LinkWithNewTabHandling
220
+ href={
221
+ shouldOpenInEditor
222
+ ? `/editor?package_id=${pkg.package_id}`
223
+ : `/${pkg.name}`
224
+ }
225
+ shouldOpenInNewTab={shouldOpenInNewTab}
226
+ className="flex"
227
+ onClick={() => {
228
+ setShowResults(false)
229
+ if (closeOnClick) closeOnClick()
230
+ }}
231
+ >
232
+ <div className="w-12 h-12 overflow-hidden mr-2 flex-shrink-0 rounded-sm bg-gray-50 border flex items-center justify-center">
233
+ {hasPreviewImage ? (
234
+ <img
235
+ src={previewImageUrl}
236
+ alt={`PCB preview for ${pkg.name}`}
237
+ draggable={false}
238
+ className="w-12 h-12 object-contain p-1 scale-[4] rotate-45"
239
+ onError={(e) => {
240
+ e.currentTarget.style.display = "none"
241
+ e.currentTarget.nextElementSibling?.classList.remove(
242
+ "hidden",
243
+ )
244
+ e.currentTarget.nextElementSibling?.classList.add(
245
+ "flex",
246
+ )
247
+ }}
248
+ />
249
+ ) : null}
250
+ <div
251
+ className={`w-12 h-12 ${hasPreviewImage ? "hidden" : "flex"} items-center justify-center`}
252
+ >
253
+ <CircuitBoard className="w-6 h-6 text-gray-300" />
254
+ </div>
249
255
  </div>
250
- {pkg.description && (
251
- <div className="text-xs text-gray-500 break-words h-8 overflow-hidden">
252
- {pkg.description}
256
+ <div className="flex-grow">
257
+ <div className="font-medium text-blue-600 break-words text-xs">
258
+ {pkg.name}
253
259
  </div>
254
- )}
255
- </div>
256
- </LinkWithNewTabHandling>
257
- </li>
258
- ))}
260
+ {pkg.description && (
261
+ <div className="text-xs text-gray-500 break-words h-8 overflow-hidden">
262
+ {pkg.description}
263
+ </div>
264
+ )}
265
+ </div>
266
+ </LinkWithNewTabHandling>
267
+ </li>
268
+ )
269
+ })}
259
270
  </ul>
260
271
  ) : (
261
272
  <Alert variant="default" className="p-4 text-center">
@@ -3,13 +3,14 @@ import { useAxios } from "@/hooks/use-axios"
3
3
  import { StarFilledIcon } from "@radix-ui/react-icons"
4
4
  import { Link } from "wouter"
5
5
  import { Package } from "fake-snippets-api/lib/db/schema"
6
- import { useRef, useState } from "react"
7
- import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
6
+ import { useRef } from "react"
7
+ const CarouselItem = ({ pkg }: { pkg: Package }) => {
8
+ const previewImageUrl =
9
+ pkg.latest_pcb_preview_image_url ??
10
+ pkg.latest_cad_preview_image_url ??
11
+ pkg.latest_sch_preview_image_url ??
12
+ undefined
8
13
 
9
- const CarouselItem = ({
10
- pkg,
11
- apiBaseUrl,
12
- }: { pkg: Package; apiBaseUrl: string }) => {
13
14
  return (
14
15
  <Link href={`/${pkg.name}`}>
15
16
  <div className="flex-shrink-0 w-[200px] bg-white p-3 py-2 rounded-lg shadow-sm border border-gray-200 hover:border-gray-300 transition-colors">
@@ -17,11 +18,13 @@ const CarouselItem = ({
17
18
  {pkg.name}
18
19
  </div>
19
20
  <div className="mb-2 h-24 w-full bg-black rounded overflow-hidden">
20
- <img
21
- src={`${apiBaseUrl}/packages/images/${pkg.owner_github_username}/${pkg.unscoped_name}/pcb.svg?fs_map=${pkg.latest_package_release_fs_sha}`}
22
- alt="PCB preview"
23
- className="w-full h-full object-contain p-2 scale-[3] rotate-45 hover:scale-[3.5] transition-transform"
24
- />
21
+ {previewImageUrl ? (
22
+ <img
23
+ src={previewImageUrl}
24
+ alt="PCB preview"
25
+ className="w-full h-full object-contain p-2 scale-[3] rotate-45 hover:scale-[3.5] transition-transform"
26
+ />
27
+ ) : null}
25
28
  </div>
26
29
  <div className="flex items-center text-xs text-gray-500">
27
30
  <StarFilledIcon className="w-3 h-3 mr-1" />
@@ -35,8 +38,6 @@ const CarouselItem = ({
35
38
  export const TrendingPackagesCarousel = () => {
36
39
  const axios = useAxios()
37
40
  const scrollRef = useRef<HTMLDivElement>(null)
38
- const [isHovered, setIsHovered] = useState(false)
39
- const apiBaseUrl = useApiBaseUrl()
40
41
 
41
42
  const { data: trendingPackages } = useQuery<Package[]>(
42
43
  "trendingPackages",
@@ -59,22 +60,14 @@ export const TrendingPackagesCarousel = () => {
59
60
  <div className="container mx-auto px-4">
60
61
  <h2 className="text-2xl font-semibold mb-6">Trending Packages</h2>
61
62
  </div>
62
- <div
63
- className="flex gap-6 overflow-x-hidden relative"
64
- onMouseEnter={() => setIsHovered(true)}
65
- onMouseLeave={() => setIsHovered(false)}
66
- >
63
+ <div className="flex gap-6 overflow-x-hidden relative">
67
64
  <div
68
65
  ref={scrollRef}
69
66
  className="flex gap-6 transition-transform duration-1000 animate-carousel-left"
70
67
  >
71
68
  {[...(trendingPackages ?? []), ...(trendingPackages ?? [])].map(
72
69
  (pkg, i) => (
73
- <CarouselItem
74
- key={`${pkg.package_id}-${i}`}
75
- pkg={pkg}
76
- apiBaseUrl={apiBaseUrl}
77
- />
70
+ <CarouselItem key={`${pkg.package_id}-${i}`} pkg={pkg} />
78
71
  ),
79
72
  )}
80
73
  </div>
@@ -85,8 +85,9 @@ const MobileSidebar = ({
85
85
  })
86
86
 
87
87
  const { availableViews: pngViews } = usePreviewImages({
88
- packageName: packageInfo?.name,
89
- fsMapHash: packageInfo?.latest_package_release_fs_sha ?? "",
88
+ cadPreviewUrl: packageInfo?.latest_cad_preview_image_url,
89
+ pcbPreviewUrl: packageInfo?.latest_pcb_preview_image_url,
90
+ schematicPreviewUrl: packageInfo?.latest_sch_preview_image_url,
90
91
  })
91
92
 
92
93
  const viewsToRender =
@@ -4,7 +4,9 @@ import type { Package } from "fake-snippets-api/lib/db/schema"
4
4
  interface ViewPlaceholdersProps {
5
5
  packageInfo?: Pick<
6
6
  Package,
7
- "name" | "latest_package_release_fs_sha" | "latest_package_release_id"
7
+ | "latest_cad_preview_image_url"
8
+ | "latest_pcb_preview_image_url"
9
+ | "latest_sch_preview_image_url"
8
10
  >
9
11
  onViewChange?: (view: "3d" | "pcb" | "schematic") => void
10
12
  }
@@ -14,8 +16,9 @@ export default function PreviewImageSquares({
14
16
  onViewChange,
15
17
  }: ViewPlaceholdersProps) {
16
18
  const { availableViews } = usePreviewImages({
17
- packageName: packageInfo?.name,
18
- fsMapHash: packageInfo?.latest_package_release_fs_sha ?? "",
19
+ cadPreviewUrl: packageInfo?.latest_cad_preview_image_url,
20
+ pcbPreviewUrl: packageInfo?.latest_pcb_preview_image_url,
21
+ schematicPreviewUrl: packageInfo?.latest_sch_preview_image_url,
19
22
  })
20
23
  const handleViewClick = (viewId: string) => {
21
24
  onViewChange?.(viewId as "3d" | "pcb" | "schematic")