@tscircuit/fake-snippets 0.0.24 → 0.0.26

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.
@@ -3,66 +3,63 @@ import { z } from "zod"
3
3
  import { orderSteps } from "fake-snippets-api/utils/order-steps"
4
4
 
5
5
  export default withRouteSpec({
6
- methods: ["POST"],
6
+ methods: ["GET"],
7
7
  auth: "session",
8
- jsonBody: z.object({
9
- order_id: z.string(),
10
- }),
11
8
  jsonResponse: z.object({
12
9
  success: z.boolean(),
13
- current_step: z.string().nullable(),
14
10
  }),
15
11
  })(async (req, ctx) => {
16
- const { order_id } = req.jsonBody
12
+ // Get all orders that aren't finished
13
+ const orders = ctx.db.orders.filter((order) => !order.is_finished)
14
+ const updatedOrders = []
17
15
 
18
- const orderState = ctx.db.getJlcpcbOrderStatesByOrderId(order_id)
19
- if (!orderState) {
20
- return ctx.error(404, {
21
- error_code: "order_state_not_found",
22
- message: "Order state not found",
23
- })
24
- }
16
+ for (const order of orders) {
17
+ const orderState = ctx.db.getJlcpcbOrderStatesByOrderId(order.order_id)
18
+ if (!orderState) continue
25
19
 
26
- // Find the next step to complete
27
- let nextStepIndex = -1
28
- for (let i = 0; i < orderSteps.length; i++) {
29
- const step = orderSteps[i]
30
- if (!orderState[step]) {
31
- nextStepIndex = i
32
- break
20
+ // Find the next step to complete
21
+ let nextStepIndex = -1
22
+ for (let i = 0; i < orderSteps.length; i++) {
23
+ const step = orderSteps[i]
24
+ if (!orderState[step]) {
25
+ nextStepIndex = i
26
+ break
27
+ }
33
28
  }
34
- }
35
29
 
36
- // If all steps are completed or no next step found
37
- if (nextStepIndex === -1) {
38
- return ctx.json({
39
- success: true,
40
- current_step: null,
41
- })
42
- }
30
+ // If all steps are completed or no next step found
31
+ if (nextStepIndex === -1) {
32
+ updatedOrders.push({
33
+ order_id: order.order_id,
34
+ current_step: null,
35
+ })
36
+ continue
37
+ }
43
38
 
44
- // Update the next step to true
45
- const nextStep = orderSteps[nextStepIndex]
46
- const updates = {
47
- [nextStep]: true,
48
- current_step: nextStep,
49
- }
39
+ // Update the next step to true
40
+ const nextStep = orderSteps[nextStepIndex]
41
+ const updates = {
42
+ [nextStep]: true,
43
+ current_step: nextStep,
44
+ }
50
45
 
51
- // If this is the last step, also update the order status
52
- if (nextStepIndex === orderSteps.length - 1) {
53
- const order = ctx.db.getOrderById(order_id)
54
- if (order) {
55
- ctx.db.updateOrder(order_id, {
46
+ // If this is the last step, also update the order status
47
+ if (nextStepIndex === orderSteps.length - 1) {
48
+ ctx.db.updateOrder(order.order_id, {
56
49
  is_finished: true,
57
50
  completed_at: new Date().toISOString(),
58
51
  })
59
52
  }
60
- }
61
53
 
62
- ctx.db.updateJlcpcbOrderState(order_id, updates)
54
+ ctx.db.updateJlcpcbOrderState(order.order_id, updates)
55
+
56
+ updatedOrders.push({
57
+ order_id: order.order_id,
58
+ current_step: nextStep,
59
+ })
60
+ }
63
61
 
64
62
  return ctx.json({
65
63
  success: true,
66
- current_step: nextStep,
67
64
  })
68
65
  })
@@ -5,27 +5,7 @@ import {
5
5
  } from "fake-snippets-api/lib/db/schema"
6
6
  import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
7
7
  import { z } from "zod"
8
-
9
- export const simulateScenarios = [
10
- "are_gerbers_generated",
11
- "are_gerbers_uploaded",
12
- "is_gerber_analyzed",
13
- "are_initial_costs_calculated",
14
- "is_pcb_added_to_cart",
15
- "is_bom_uploaded",
16
- "is_pnp_uploaded",
17
- "is_bom_pnp_analyzed",
18
- "is_bom_parsing_complete",
19
- "are_components_available",
20
- "is_patch_map_generated",
21
- "is_json_merge_file_created",
22
- "is_dfm_result_generated",
23
- "are_files_downloaded",
24
- "are_product_categories_fetched",
25
- "are_final_costs_calculated",
26
- "is_json_merge_file_updated",
27
- "is_added_to_cart",
28
- ] as const
8
+ import { orderSteps as simulateScenarios } from "fake-snippets-api/utils/order-steps"
29
9
 
30
10
  const defaultOrderState = {
31
11
  are_gerbers_uploaded: false,
@@ -76,6 +76,14 @@ export default withRouteSpec({
76
76
  commit_sha: commit_sha ?? null,
77
77
  })
78
78
 
79
+ // Update the package's latest_package_release_id if this is the latest release
80
+ if (is_latest) {
81
+ ctx.db.updatePackage(package_id, {
82
+ latest_package_release_id: newPackageRelease.package_release_id,
83
+ latest_version: version,
84
+ })
85
+ }
86
+
79
87
  return ctx.json({
80
88
  ok: true,
81
89
  package_release: publicMapPackageRelease(newPackageRelease),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
package/src/App.tsx CHANGED
@@ -70,6 +70,7 @@ const UserProfilePage = lazyImport(() => import("@/pages/user-profile"))
70
70
  const ViewOrderPage = lazyImport(() => import("@/pages/view-order"))
71
71
  const ViewSnippetPage = lazyImport(() => import("@/pages/view-snippet"))
72
72
  const DevLoginPage = lazyImport(() => import("@/pages/dev-login"))
73
+ const BetaPage = lazyImport(() => import("@/pages/beta"))
73
74
 
74
75
  class ErrorBoundary extends React.Component<
75
76
  { children: React.ReactNode },
@@ -99,6 +100,7 @@ function App() {
99
100
  <Suspense fallback={<FullPageLoader />}>
100
101
  <Switch>
101
102
  <Route path="/" component={LandingPage} />
103
+ <Route path="/beta" component={BetaPage} />
102
104
  <Route path="/editor" component={EditorPage} />
103
105
  <Route path="/quickstart" component={QuickstartPage} />
104
106
  <Route path="/dashboard" component={DashboardPage} />
@@ -28,6 +28,7 @@ interface OptimizedImageProps
28
28
  src: string
29
29
  alt: string
30
30
  priority?: boolean
31
+ fallbackSrc?: string
31
32
  }
32
33
 
33
34
  const getImageSizes = (src: string) => {
@@ -51,10 +52,12 @@ export function OptimizedImage({
51
52
  alt,
52
53
  className,
53
54
  priority = false,
55
+ fallbackSrc = "/assets/fallback-image.svg",
54
56
  ...props
55
57
  }: OptimizedImageProps) {
58
+ const [currentSrc, setCurrentSrc] = useState(src)
56
59
  const [imageLoading, setImageLoading] = useState(true)
57
- const { srcSet, sizes } = getImageSizes(src)
60
+ const { srcSet, sizes } = getImageSizes(currentSrc)
58
61
 
59
62
  useEffect(() => {
60
63
  if (priority) {
@@ -75,7 +78,7 @@ export function OptimizedImage({
75
78
 
76
79
  return (
77
80
  <img
78
- src={src}
81
+ src={currentSrc}
79
82
  srcSet={srcSet}
80
83
  sizes={sizes}
81
84
  alt={alt}
@@ -86,8 +89,9 @@ export function OptimizedImage({
86
89
  imageLoading ? "animate-pulse bg-gray-200" : ""
87
90
  } object-contain`}
88
91
  onLoad={() => setImageLoading(false)}
89
- onError={(e) => {
90
- console.error("Image failed to load:", e)
92
+ onError={() => {
93
+ console.error("Image failed to load:", currentSrc)
94
+ setCurrentSrc(fallbackSrc)
91
95
  setImageLoading(false)
92
96
  }}
93
97
  {...props}
@@ -0,0 +1,76 @@
1
+ import type { PackageFile } from "fake-snippets-api/lib/db/schema"
2
+ import { useMutation } from "react-query"
3
+ import { useAxios } from "./use-axios"
4
+
5
+ export const useCreatePackageFilesMutation = ({
6
+ onSuccess,
7
+ }: { onSuccess?: (packageFile: PackageFile) => void } = {}) => {
8
+ const axios = useAxios()
9
+
10
+ return useMutation(
11
+ ["createPackageFiles"],
12
+ async ({
13
+ file_path,
14
+ content_text,
15
+ content_base64,
16
+ content_mimetype,
17
+ package_release_id,
18
+ package_name_with_version,
19
+ is_release_tarball = false,
20
+ npm_pack_output,
21
+ }: {
22
+ file_path: string
23
+ content_text?: string
24
+ content_base64?: string
25
+ content_mimetype?: string
26
+ package_release_id?: string
27
+ package_name_with_version?: string
28
+ is_release_tarball?: boolean
29
+ npm_pack_output?: any
30
+ }) => {
31
+ // Validate that either content_text or content_base64 is provided, but not both
32
+ if (
33
+ (!content_text && !content_base64) ||
34
+ (content_text && content_base64)
35
+ ) {
36
+ throw new Error(
37
+ "Must provide either content_text or content_base64, but not both",
38
+ )
39
+ }
40
+
41
+ // Validate that either package_release_id or package_name_with_version is provided
42
+ if (!package_release_id && !package_name_with_version) {
43
+ throw new Error(
44
+ "Must provide either package_release_id or package_name_with_version",
45
+ )
46
+ }
47
+
48
+ const {
49
+ data: { package_file: newPackageFile },
50
+ } = await axios.post("/package_files/create", {
51
+ file_path,
52
+ content_text,
53
+ content_base64,
54
+ content_mimetype,
55
+ package_release_id,
56
+ package_name_with_version,
57
+ is_release_tarball,
58
+ npm_pack_output,
59
+ })
60
+
61
+ if (!newPackageFile) {
62
+ throw new Error("Failed to create package file")
63
+ }
64
+
65
+ return newPackageFile
66
+ },
67
+ {
68
+ onSuccess: (packageFile: PackageFile) => {
69
+ onSuccess?.(packageFile)
70
+ },
71
+ onError: (error: any) => {
72
+ console.error("Error creating package file:", error)
73
+ },
74
+ },
75
+ )
76
+ }
@@ -0,0 +1,60 @@
1
+ import type { Package } from "fake-snippets-api/lib/db/schema"
2
+ import { useMutation } from "react-query"
3
+ import { useAxios } from "./use-axios"
4
+ import { useGlobalStore } from "./use-global-store"
5
+ import { useUrlParams } from "./use-url-params"
6
+
7
+ export const useCreatePackageMutation = ({
8
+ onSuccess,
9
+ }: { onSuccess?: (pkg: Package) => void } = {}) => {
10
+ const urlParams = useUrlParams()
11
+ const templateName = urlParams.template
12
+ const axios = useAxios()
13
+ const session = useGlobalStore((s) => s.session)
14
+
15
+ return useMutation(
16
+ ["createPackage"],
17
+ async ({
18
+ name,
19
+ description,
20
+ is_private,
21
+ is_unlisted,
22
+ }: {
23
+ name: string
24
+ description?: string
25
+ is_private?: boolean
26
+ is_unlisted?: boolean
27
+ }) => {
28
+ if (!session) throw new Error("No session")
29
+
30
+ const {
31
+ data: { package: newPackage },
32
+ } = await axios.post("/packages/create", {
33
+ name,
34
+ description,
35
+ is_private,
36
+ is_unlisted,
37
+ })
38
+
39
+ if (!newPackage) {
40
+ throw new Error("Failed to create package")
41
+ }
42
+
43
+ return newPackage
44
+ },
45
+ {
46
+ onSuccess: (pkg: Package) => {
47
+ const url = new URL(window.location.href)
48
+ url.searchParams.set("package_id", pkg.package_id)
49
+ url.searchParams.delete("template")
50
+ url.searchParams.delete("should_create_package")
51
+ window.history.pushState({}, "", url.toString())
52
+ onSuccess?.(pkg)
53
+ window.dispatchEvent(new Event("popstate"))
54
+ },
55
+ onError: (error: any) => {
56
+ console.error("Error creating package:", error)
57
+ },
58
+ },
59
+ )
60
+ }
@@ -0,0 +1,57 @@
1
+ import type { PackageRelease } from "fake-snippets-api/lib/db/schema"
2
+ import { useMutation } from "react-query"
3
+ import { useAxios } from "./use-axios"
4
+
5
+ export const useCreatePackageReleaseMutation = ({
6
+ onSuccess,
7
+ }: { onSuccess?: (packageRelease: PackageRelease) => void } = {}) => {
8
+ const axios = useAxios()
9
+
10
+ return useMutation(
11
+ ["createPackageRelease"],
12
+ async ({
13
+ package_id,
14
+ version,
15
+ is_latest = true,
16
+ commit_sha,
17
+ package_name_with_version,
18
+ }: {
19
+ package_id?: string
20
+ version?: string
21
+ is_latest?: boolean
22
+ commit_sha?: string
23
+ package_name_with_version?: string
24
+ }) => {
25
+ // Validate that either package_id + version or package_name_with_version is provided
26
+ if (!package_name_with_version && (!package_id || !version)) {
27
+ throw new Error(
28
+ "Must provide either package_id + version or package_name_with_version",
29
+ )
30
+ }
31
+
32
+ const {
33
+ data: { package_release: newPackageRelease },
34
+ } = await axios.post("/package_releases/create", {
35
+ package_id,
36
+ version,
37
+ is_latest,
38
+ commit_sha,
39
+ package_name_with_version,
40
+ })
41
+
42
+ if (!newPackageRelease) {
43
+ throw new Error("Failed to create package release")
44
+ }
45
+
46
+ return newPackageRelease
47
+ },
48
+ {
49
+ onSuccess: (packageRelease: PackageRelease) => {
50
+ onSuccess?.(packageRelease)
51
+ },
52
+ onError: (error: any) => {
53
+ console.error("Error creating package release:", error)
54
+ },
55
+ },
56
+ )
57
+ }
@@ -0,0 +1,103 @@
1
+ import {
2
+ Package,
3
+ PackageFile,
4
+ PackageRelease,
5
+ } from "fake-snippets-api/lib/db/schema"
6
+ import { useMutation } from "react-query"
7
+ import { useAxios } from "./use-axios"
8
+ import { useGlobalStore } from "./use-global-store"
9
+ import { useToast } from "./use-toast"
10
+ import { useCreatePackageMutation } from "./use-create-package-mutation"
11
+ import { useCreatePackageReleaseMutation } from "./use-create-package-release-mutation"
12
+ import { useCreatePackageFilesMutation } from "./use-create-package-files-mutation"
13
+
14
+ export const useForkPackageMutation = ({
15
+ onSuccess,
16
+ }: {
17
+ onSuccess?: (forkedPackage: Package) => void
18
+ } = {}) => {
19
+ const axios = useAxios()
20
+ const session = useGlobalStore((s) => s.session)
21
+ const { toast } = useToast()
22
+
23
+ const { mutateAsync: createPackage } = useCreatePackageMutation()
24
+ const { mutateAsync: createRelease } = useCreatePackageReleaseMutation()
25
+ const { mutateAsync: createFile } = useCreatePackageFilesMutation()
26
+
27
+ return useMutation(
28
+ ["forkPackage"],
29
+ async (packageId: string) => {
30
+ if (!session) throw new Error("No session")
31
+
32
+ // Step 1: Fetch source package data
33
+ const { data: packageData } = await axios.get("/packages/get", {
34
+ params: { package_id: packageId },
35
+ })
36
+ const sourcePackage: Package = packageData.package
37
+ if (!sourcePackage) throw new Error("Source package not found")
38
+
39
+ console.log("sourcePackage", sourcePackage)
40
+ // Step 2: Fetch latest release
41
+ const { data: releaseData } = await axios.post("/package_releases/get", {
42
+ package_release_id: sourcePackage.latest_package_release_id,
43
+ })
44
+ const sourceRelease: PackageRelease = releaseData.package_release
45
+ if (!sourceRelease) throw new Error("Source release not found")
46
+
47
+ // Step 3: Fetch all files for the release
48
+ const { data: filesData } = await axios.post("/package_files/list", {
49
+ package_release_id: sourceRelease.package_release_id,
50
+ })
51
+ const sourceFiles: PackageFile[] = filesData.package_files
52
+ if (!sourceFiles?.length) throw new Error("No source files found")
53
+
54
+ // Step 4: Create new package
55
+ const newPackage = await createPackage({
56
+ name: `@${session.github_username}/${sourcePackage.unscoped_name}`,
57
+ description: `Fork of ${sourcePackage.name}`,
58
+ is_private: false,
59
+ is_unlisted: false,
60
+ })
61
+
62
+ // Step 5: Create new release
63
+ const newRelease = await createRelease({
64
+ package_id: newPackage.package_id,
65
+ version: sourceRelease.version ?? undefined,
66
+ is_latest: true,
67
+ })
68
+
69
+ // Step 6: Create all files
70
+ const newFiles = await Promise.all(
71
+ sourceFiles.map((file: PackageFile) =>
72
+ createFile({
73
+ package_release_id: newRelease.package_release_id,
74
+ file_path: file.file_path,
75
+ content_text: file.content_text ?? undefined,
76
+ }),
77
+ ),
78
+ )
79
+
80
+ return {
81
+ package: newPackage,
82
+ release: newRelease,
83
+ files: newFiles,
84
+ }
85
+ },
86
+ {
87
+ onSuccess: (result) => {
88
+ toast({
89
+ title: "Package Forked",
90
+ description: `Successfully forked package to @${session?.github_username}/${result.package.unscoped_name}`,
91
+ })
92
+ onSuccess?.(result.package)
93
+ },
94
+ onError: (error: any) => {
95
+ toast({
96
+ title: "Error",
97
+ description: "Failed to fork package. Please try again.",
98
+ variant: "destructive",
99
+ })
100
+ },
101
+ },
102
+ )
103
+ }
@@ -0,0 +1,23 @@
1
+ import { useQuery } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import type { Package } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export const usePackageById = (packageId: string | null) => {
6
+ const axios = useAxios()
7
+ return useQuery<Package, Error & { status: number }>(
8
+ ["package", packageId],
9
+ async () => {
10
+ if (!packageId) return
11
+ const { data } = await axios.get("/packages/get", {
12
+ params: {
13
+ package_id: packageId,
14
+ },
15
+ })
16
+ return data.package
17
+ },
18
+ {
19
+ retry: false,
20
+ enabled: Boolean(packageId),
21
+ },
22
+ )
23
+ }
@@ -0,0 +1,23 @@
1
+ import { useQuery } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import type { Package } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export const usePackageByName = (packageName: string | null) => {
6
+ const axios = useAxios()
7
+ return useQuery<Package, Error & { status: number }>(
8
+ ["package", packageName],
9
+ async () => {
10
+ if (!packageName) return
11
+ const { data } = await axios.get("/packages/get", {
12
+ params: {
13
+ name: packageName,
14
+ },
15
+ })
16
+ return data.package
17
+ },
18
+ {
19
+ retry: false,
20
+ enabled: Boolean(packageName),
21
+ },
22
+ )
23
+ }
@@ -0,0 +1,84 @@
1
+ import { PackageFile } from "fake-snippets-api/lib/db/schema"
2
+ import { useQuery } from "react-query"
3
+ import { useAxios } from "./use-axios"
4
+
5
+ type PackageFileQuery =
6
+ | {
7
+ package_file_id: string
8
+ }
9
+ | {
10
+ package_release_id: string
11
+ file_path: string
12
+ }
13
+ | {
14
+ package_id: string
15
+ version?: string
16
+ file_path: string
17
+ }
18
+ | {
19
+ package_name: string
20
+ version?: string
21
+ file_path: string
22
+ }
23
+ | {
24
+ package_name_with_version: string
25
+ file_path: string
26
+ }
27
+
28
+ export const usePackageFile = (query: PackageFileQuery | null) => {
29
+ const axios = useAxios()
30
+
31
+ return useQuery<PackageFile, Error & { status: number }>(
32
+ ["packageFile", query],
33
+ async () => {
34
+ if (!query) return
35
+
36
+ const { data } = await axios.post("/package_files/get", query)
37
+
38
+ if (!data.package_file) {
39
+ throw new Error("Package file not found")
40
+ }
41
+
42
+ return data.package_file
43
+ },
44
+ {
45
+ retry: false,
46
+ enabled: Boolean(query),
47
+ },
48
+ )
49
+ }
50
+
51
+ // Convenience hooks for common use cases
52
+ export const usePackageFileById = (packageFileId: string | null) => {
53
+ return usePackageFile(
54
+ packageFileId ? { package_file_id: packageFileId } : null,
55
+ )
56
+ }
57
+
58
+ export const usePackageFileByPath = (
59
+ packageNameWithVersion: string | null,
60
+ filePath: string | null,
61
+ ) => {
62
+ return usePackageFile(
63
+ packageNameWithVersion && filePath
64
+ ? {
65
+ package_name_with_version: packageNameWithVersion,
66
+ file_path: filePath,
67
+ }
68
+ : null,
69
+ )
70
+ }
71
+
72
+ export const usePackageFileByRelease = (
73
+ packageReleaseId: string | null,
74
+ filePath: string | null,
75
+ ) => {
76
+ return usePackageFile(
77
+ packageReleaseId && filePath
78
+ ? {
79
+ package_release_id: packageReleaseId,
80
+ file_path: filePath,
81
+ }
82
+ : null,
83
+ )
84
+ }