@tscircuit/fake-snippets 0.0.13 → 0.0.15

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,5 +1,16 @@
1
1
  import { z } from "zod"
2
2
 
3
+ export const errorSchema = z
4
+ .object({
5
+ error_code: z.string(),
6
+ message: z.string(),
7
+ })
8
+ .passthrough()
9
+
10
+ export const errorResponseSchema = z.object({
11
+ error: errorSchema,
12
+ })
13
+
3
14
  export const snippetSchema = z.object({
4
15
  snippet_id: z.string(),
5
16
  package_release_id: z.string(),
@@ -64,22 +75,16 @@ export type Account = z.infer<typeof accountSchema>
64
75
 
65
76
  export const orderSchema = z.object({
66
77
  order_id: z.string(),
67
- account_id: z.string(),
68
- is_draft: z.boolean(),
69
- is_pending_validation_by_fab: z.boolean(),
70
- is_pending_review_by_fab: z.boolean(),
71
- is_validated_by_fab: z.boolean(),
72
- is_approved_by_fab_review: z.boolean(),
73
- is_approved_by_orderer: z.boolean(),
74
- is_in_production: z.boolean(),
75
- is_shipped: z.boolean(),
76
- is_cancelled: z.boolean(),
77
- should_be_blank_pcb: z.boolean(),
78
- should_include_stencil: z.boolean(),
79
- jlcpcb_order_params: z.record(z.any()),
80
- circuit_json: z.array(z.record(z.any())),
78
+ account_id: z.string().nullable(),
79
+ is_running: z.boolean(),
80
+ is_started: z.boolean(),
81
+ is_finished: z.boolean(),
82
+ error: errorSchema.nullable(),
83
+ has_error: z.boolean(),
81
84
  created_at: z.string(),
82
- updated_at: z.string(),
85
+ started_at: z.string().nullable(),
86
+ completed_at: z.string().nullable(),
87
+ circuit_json: z.any(),
83
88
  })
84
89
  export type Order = z.infer<typeof orderSchema>
85
90
 
@@ -163,17 +168,6 @@ export const packageSchema = z.object({
163
168
  })
164
169
  export type Package = z.infer<typeof packageSchema>
165
170
 
166
- export const errorSchema = z
167
- .object({
168
- error_code: z.string(),
169
- message: z.string(),
170
- })
171
- .passthrough()
172
-
173
- export const errorResponseSchema = z.object({
174
- error: errorSchema,
175
- })
176
-
177
171
  export const databaseSchema = z.object({
178
172
  idCounter: z.number().default(0),
179
173
  snippets: z.array(snippetSchema).default([]),
@@ -1581,18 +1581,11 @@ export const SquareWaveModule = () => (
1581
1581
 
1582
1582
  db.addOrder({
1583
1583
  account_id,
1584
- is_draft: true,
1585
- is_pending_validation_by_fab: false,
1586
- is_pending_review_by_fab: false,
1587
- is_validated_by_fab: false,
1588
- is_approved_by_fab_review: false,
1589
- is_approved_by_orderer: false,
1590
- is_in_production: false,
1591
- is_shipped: false,
1592
- is_cancelled: false,
1593
- should_be_blank_pcb: false,
1594
- should_include_stencil: false,
1595
- jlcpcb_order_params: {},
1584
+ is_running: false,
1585
+ is_started: false,
1586
+ is_finished: false,
1587
+ error: null,
1588
+ has_error: false,
1596
1589
  circuit_json: [
1597
1590
  {
1598
1591
  type: "source_component",
@@ -1603,6 +1596,7 @@ export const SquareWaveModule = () => (
1603
1596
  },
1604
1597
  ],
1605
1598
  created_at: new Date().toISOString(),
1606
- updated_at: new Date().toISOString(),
1599
+ started_at: null,
1600
+ completed_at: null,
1607
1601
  })
1608
1602
  }
@@ -0,0 +1,13 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { seed } from "fake-snippets-api/lib/db/seed"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["POST"],
7
+ auth: "none",
8
+ jsonResponse: z.object({ ok: z.boolean() }),
9
+ })(async (req, ctx) => {
10
+ seed(ctx.db)
11
+
12
+ return ctx.json({ ok: true })
13
+ })
@@ -1,36 +1,70 @@
1
1
  import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
2
  import { z } from "zod"
3
- import { orderSchema } from "fake-snippets-api/lib/db/schema"
3
+ import {
4
+ orderSchema,
5
+ errorSchema,
6
+ Order,
7
+ } from "fake-snippets-api/lib/db/schema"
8
+
9
+ const simulate_scenarios = [
10
+ // TODO: Add more scenarios
11
+ "gerber_generation_failed",
12
+ "gerber_upload_failed",
13
+ "bom_generation_failed",
14
+ "bom_upload_failed",
15
+ "pnp_generation_failed",
16
+ "pnp_upload_failed",
17
+ ] as const
4
18
 
5
19
  export default withRouteSpec({
6
20
  methods: ["POST"],
7
21
  auth: "session",
8
- jsonBody: z.object({
9
- circuit_json: z.array(z.record(z.any())),
10
- }),
22
+ jsonBody: z
23
+ .object({
24
+ package_release_id: z.string().uuid().optional(),
25
+ circuit_json: z.array(z.record(z.any())),
26
+ _simulate: z
27
+ .object({
28
+ scenario: z.enum(simulate_scenarios).optional(),
29
+ })
30
+ .optional(),
31
+ })
32
+ .refine((data) => data.package_release_id || data.circuit_json, {
33
+ message: "Either package_release_id or circuit_json must be provided",
34
+ }),
11
35
  jsonResponse: z.object({
12
36
  order: orderSchema,
13
37
  }),
14
38
  })(async (req, ctx) => {
15
- const { circuit_json } = req.jsonBody
39
+ const { circuit_json, package_release_id, _simulate } = req.jsonBody
16
40
 
17
- const newOrder = {
41
+ const newOrder: Order = {
42
+ order_id: crypto.randomUUID(),
18
43
  account_id: ctx.auth.account_id,
19
- is_draft: true,
20
- is_pending_validation_by_fab: false,
21
- is_pending_review_by_fab: false,
22
- is_validated_by_fab: false,
23
- is_approved_by_fab_review: false,
24
- is_approved_by_orderer: false,
25
- is_in_production: false,
26
- is_shipped: false,
27
- is_cancelled: false,
28
- should_be_blank_pcb: false,
29
- should_include_stencil: false,
30
- jlcpcb_order_params: {},
31
- circuit_json,
44
+ is_running: false,
45
+ is_started: false,
46
+ is_finished: false,
47
+ error: null,
48
+ has_error: false,
32
49
  created_at: new Date().toISOString(),
33
- updated_at: new Date().toISOString(),
50
+ started_at: null,
51
+ completed_at: null,
52
+ circuit_json,
53
+ }
54
+
55
+ // TODO: Handle more scenarios
56
+ if (_simulate?.scenario === "gerber_generation_failed") {
57
+ newOrder.error = errorSchema.parse({
58
+ error_code: "GERBER_GENERATION_FAILED",
59
+ message: "Gerber generation failed",
60
+ })
61
+ newOrder.has_error = true
62
+ }
63
+
64
+ if (newOrder.has_error) {
65
+ return ctx.json({
66
+ order: newOrder,
67
+ })
34
68
  }
35
69
 
36
70
  const order = ctx.db.addOrder(newOrder)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -95,6 +95,7 @@
95
95
  "file-saver": "^2.0.5",
96
96
  "immer": "^10.1.1",
97
97
  "input-otp": "^1.2.4",
98
+ "javascript-time-ago": "^2.5.11",
98
99
  "jose": "^5.9.3",
99
100
  "jscad-electronics": "^0.0.25",
100
101
  "jszip": "^3.10.1",
@@ -58,7 +58,7 @@ test.skip("test delete functionality in profile", async ({ page }) => {
58
58
  expect(remainingSnippets).toBe(0)
59
59
  })
60
60
 
61
- test("test starring a snippet and verifying in Starred Snippets tab", async ({
61
+ test("test starring a snippet and verifying in Starred Packages tab", async ({
62
62
  page,
63
63
  }) => {
64
64
  // Go to profile page
@@ -93,14 +93,14 @@ test("test starring a snippet and verifying in Starred Snippets tab", async ({
93
93
  await page.waitForLoadState("networkidle")
94
94
  await expect(page).toHaveScreenshot("profile-page-snippets-tab.png")
95
95
 
96
- // Click on the "Starred Snippets" tab
97
- await page.getByText("Starred Snippets").click()
96
+ // Click on the "Starred Packages" tab
97
+ await page.getByText("Starred Packages").click()
98
98
 
99
99
  // Wait for load and take screenshot
100
100
  await page.waitForLoadState("networkidle")
101
101
  await expect(page).toHaveScreenshot("profile-page-starred-tab.png")
102
102
 
103
- // Verify the starred snippet exists in the "Starred Snippets" tab
103
+ // Verify the starred snippet exists in the "Starred Packages" tab
104
104
  const starredSnippet = page.locator(
105
105
  `.text-md.font-semibold:has-text("${snippetName}")`,
106
106
  )
@@ -0,0 +1,36 @@
1
+ import { Box, CircuitBoard, Layers, Rotate3D } from "lucide-react"
2
+ import React from "react"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ type SnippetType = "board" | "package" | "footprint" | "model"
6
+
7
+ interface SnippetTypeIconProps {
8
+ type: SnippetType
9
+ className?: string
10
+ }
11
+
12
+ const typeIcons: Record<SnippetType, React.ReactNode> = {
13
+ board: <CircuitBoard className="w-4 h-4" />,
14
+ package: <Box className="w-4 h-4" />,
15
+ footprint: <Layers className="w-4 h-4" />,
16
+ model: <Rotate3D className="w-4 h-4" />,
17
+ }
18
+
19
+ const typeColors: Record<SnippetType, string> = {
20
+ board: "text-blue-500",
21
+ package: "text-green-500",
22
+ footprint: "text-purple-500",
23
+ model: "text-indigo-500",
24
+ }
25
+
26
+ export const SnippetTypeIcon: React.FC<SnippetTypeIconProps> = ({
27
+ type,
28
+ className,
29
+ }) => {
30
+ if (!type || !(type in typeIcons)) return null
31
+ return (
32
+ <span className={cn("flex items-center", typeColors[type], className)}>
33
+ {typeIcons[type]}
34
+ </span>
35
+ )
36
+ }
@@ -4,6 +4,7 @@ import { useAxios } from "@/hooks/use-axios"
4
4
  import { useCurrentSnippet } from "@/hooks/use-current-snippet"
5
5
  import { useGlobalStore } from "@/hooks/use-global-store"
6
6
  import { toast, useToast } from "@/hooks/use-toast"
7
+ import { LockClosedIcon } from "@radix-ui/react-icons"
7
8
  import { Snippet } from "fake-snippets-api/lib/db/schema"
8
9
  import { ChevronLeft, Eye, GitFork, Star } from "lucide-react"
9
10
  import { useEffect, useState } from "react"
@@ -136,6 +137,14 @@ export default function ViewSnippetHeader() {
136
137
  </Link>
137
138
  </h1>
138
139
  {snippet?.snippet_type && <TypeBadge type={snippet.snippet_type} />}
140
+ {snippet?.is_private && (
141
+ <div className="relative group pl-2">
142
+ <LockClosedIcon className="h-4 w-4 text-gray-700" />
143
+ <span className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-1 hidden group-hover:block bg-black text-white text-xs rounded py-1 px-2">
144
+ private
145
+ </span>
146
+ </div>
147
+ )}
139
148
  </div>
140
149
  <div className="flex items-center space-x-2">
141
150
  <Button variant="outline" size="sm" onClick={handleStarClick}>
@@ -28,7 +28,6 @@ export const PackageVisibilitySettingsDialog = ({
28
28
  <RadioGroup
29
29
  value={isPrivate ? "private" : "public"}
30
30
  onValueChange={(value) => setIsPrivate(value === "private")}
31
- className="space-y-4"
32
31
  >
33
32
  <div
34
33
  className="flex items-start space-x-2 px-2 py-4 rounded-md hover:bg-slate-100 cursor-pointer"
@@ -36,7 +36,7 @@ export const MyOrdersPage = () => {
36
36
  Created: {new Date(order.created_at).toLocaleString()}
37
37
  </p>
38
38
  <p className="text-sm text-gray-500">
39
- Status: {order.is_shipped ? "Shipped" : "Processing"}
39
+ Status: {order.is_running ? "Running" : "Finished"}
40
40
  </p>
41
41
  <Link href={`/orders/${order.order_id}`}>
42
42
  <Button className="mt-2" variant="outline">
@@ -10,7 +10,7 @@ import { Button } from "@/components/ui/button"
10
10
  import { GitHubLogoIcon, StarIcon, LockClosedIcon } from "@radix-ui/react-icons"
11
11
  import { Input } from "@/components/ui/input"
12
12
  import { useGlobalStore } from "@/hooks/use-global-store"
13
- import { MoreVertical, Trash2 } from "lucide-react"
13
+ import { GlobeIcon, MoreVertical, PencilIcon, Trash2 } from "lucide-react"
14
14
  import { useConfirmDeleteSnippetDialog } from "@/components/dialogs/confirm-delete-snippet-dialog"
15
15
  import {
16
16
  DropdownMenu,
@@ -19,6 +19,13 @@ import {
19
19
  DropdownMenuTrigger,
20
20
  } from "@/components/ui/dropdown-menu"
21
21
  import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
22
+ import { OptimizedImage } from "@/components/OptimizedImage"
23
+ import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
24
+ import { SnippetTypeIcon } from "@/components/SnippetTypeIcon"
25
+ import TimeAgo from "javascript-time-ago"
26
+ import en from "javascript-time-ago/locale/en"
27
+
28
+ TimeAgo.addDefaultLocale(en)
22
29
 
23
30
  export const UserProfilePage = () => {
24
31
  const { username } = useParams()
@@ -52,6 +59,9 @@ export const UserProfilePage = () => {
52
59
  },
53
60
  )
54
61
 
62
+ const baseUrl = useSnippetsBaseApiUrl()
63
+ const timeAgo = new TimeAgo("en-US")
64
+
55
65
  const snippetsToShow =
56
66
  activeTab === "starred" ? starredSnippets : userSnippets
57
67
  const isLoading =
@@ -93,7 +103,7 @@ export const UserProfilePage = () => {
93
103
  <Tabs defaultValue="all" onValueChange={setActiveTab} className="mb-4">
94
104
  <TabsList>
95
105
  <TabsTrigger value="all">Snippets</TabsTrigger>
96
- <TabsTrigger value="starred">Starred Snippets</TabsTrigger>
106
+ <TabsTrigger value="starred">Starred Packages</TabsTrigger>
97
107
  </TabsList>
98
108
  </Tabs>
99
109
  <Input
@@ -106,7 +116,7 @@ export const UserProfilePage = () => {
106
116
  {isLoading ? (
107
117
  <div>
108
118
  {activeTab === "starred"
109
- ? "Loading starred snippets..."
119
+ ? "Loading starred Packages..."
110
120
  : "Loading user snippets..."}
111
121
  </div>
112
122
  ) : (
@@ -118,49 +128,93 @@ export const UserProfilePage = () => {
118
128
  key={snippet.snippet_id}
119
129
  href={`/${snippet.owner_name}/${snippet.unscoped_name}`}
120
130
  >
121
- <div className="border p-4 rounded-md hover:shadow-md transition-shadow">
122
- <div className="flex justify-between items-start">
123
- <h3 className="text-md font-semibold">
124
- {snippet.unscoped_name}
125
- </h3>
126
- <div className="flex items-center gap-2">
127
- <div className="flex items-center text-gray-600">
128
- <StarIcon className="w-4 h-4 mr-1" />
129
- <span>{snippet.star_count || 0}</span>
131
+ <div className="border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4">
132
+ <div className="flex items-start gap-4">
133
+ <div className="h-16 w-16 flex-shrink-0 rounded-md overflow-hidden">
134
+ <OptimizedImage
135
+ src={`${baseUrl}/snippets/images/${snippet.owner_name}/${snippet.unscoped_name}/pcb.svg`}
136
+ alt={`${snippet.owner_name}'s profile`}
137
+ className="object-cover h-full w-full transition-transform duration-300 -rotate-45 hover:rotate-0 hover:scale-110 scale-150"
138
+ />
139
+ </div>
140
+ <div className="flex-1 min-w-0">
141
+ <div className="flex justify-between items-start mb-[2px] -mt-[3px]">
142
+ <h2 className="text-md font-semibold truncate pr-[30px]">
143
+ {activeTab === "starred" && (
144
+ <>
145
+ <span className="text-gray-700 text-md">
146
+ {snippet.owner_name}
147
+ </span>
148
+ <span className="mx-1">/</span>
149
+ </>
150
+ )}
151
+ <span className="text-gray-900">
152
+ {snippet.unscoped_name}
153
+ </span>
154
+ </h2>
155
+ <div className="flex items-center gap-2">
156
+ <SnippetTypeIcon
157
+ type={snippet.snippet_type}
158
+ className="pt-[2.5px]"
159
+ />
160
+ <div className="flex items-center gap-1 text-gray-600">
161
+ <StarIcon className="w-4 h-4 pt-[2.5px]" />
162
+ <span className="text-[16px]">
163
+ {snippet.star_count || 0}
164
+ </span>
165
+ </div>
166
+ {isCurrentUserProfile && activeTab === "all" && (
167
+ <DropdownMenu>
168
+ <DropdownMenuTrigger asChild>
169
+ <Button
170
+ variant="ghost"
171
+ size="icon"
172
+ className="h-[1.5rem] w-[1.5rem]"
173
+ >
174
+ <MoreVertical className="h-4 w-4" />
175
+ </Button>
176
+ </DropdownMenuTrigger>
177
+ <DropdownMenuContent>
178
+ <DropdownMenuItem
179
+ className="text-xs text-red-600"
180
+ onClick={(e) =>
181
+ handleDeleteClick(e, snippet)
182
+ }
183
+ >
184
+ <Trash2 className="mr-2 h-3 w-3" />
185
+ Delete Snippet
186
+ </DropdownMenuItem>
187
+ </DropdownMenuContent>
188
+ </DropdownMenu>
189
+ )}
190
+ </div>
130
191
  </div>
131
- {snippet.is_private && (
132
- <div className="flex items-center text-gray-600">
133
- <LockClosedIcon className="w-4 h-4 mr-1" />
192
+ <p
193
+ className={`${!snippet.description && "h-[1.25rem]"} text-sm text-gray-500 mb-1 truncate max-w-xs`}
194
+ >
195
+ {snippet.description ? snippet.description : " "}
196
+ </p>
197
+ <div className={`flex items-center gap-4`}>
198
+ {snippet.is_private ? (
199
+ <div className="flex items-center text-xs gap-1 text-gray-500">
200
+ <LockClosedIcon height={12} width={12} />
201
+ <span>Private</span>
202
+ </div>
203
+ ) : (
204
+ <div className="flex items-center text-xs gap-1 text-gray-500">
205
+ <GlobeIcon height={12} width={12} />
206
+ <span>Public</span>
207
+ </div>
208
+ )}
209
+ <div className="flex items-center text-xs gap-1 text-gray-500">
210
+ <PencilIcon height={12} width={12} />
211
+ <span>
212
+ {timeAgo.format(new Date(snippet.updated_at))}
213
+ </span>
134
214
  </div>
135
- )}
136
- {isCurrentUserProfile && activeTab === "all" && (
137
- <DropdownMenu>
138
- <DropdownMenuTrigger asChild>
139
- <Button
140
- variant="ghost"
141
- size="icon"
142
- className="h-8 w-8"
143
- >
144
- <MoreVertical className="h-4 w-4" />
145
- </Button>
146
- </DropdownMenuTrigger>
147
- <DropdownMenuContent>
148
- <DropdownMenuItem
149
- className="text-xs text-red-600"
150
- onClick={(e) => handleDeleteClick(e, snippet)}
151
- >
152
- <Trash2 className="mr-2 h-3 w-3" />
153
- Delete Snippet
154
- </DropdownMenuItem>
155
- </DropdownMenuContent>
156
- </DropdownMenu>
157
- )}
215
+ </div>
158
216
  </div>
159
217
  </div>
160
- <p className="text-sm text-gray-500">
161
- Last Updated:{" "}
162
- {new Date(snippet.updated_at).toLocaleString()}
163
- </p>
164
218
  </div>
165
219
  </Link>
166
220
  ))}
@@ -63,15 +63,17 @@ export const ViewOrderPage = () => {
63
63
  Status
64
64
  </dt>
65
65
  <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
66
- {order.is_shipped ? "Shipped" : "Processing"}
66
+ {order.is_running ? "Running" : "Finished"}
67
67
  </dd>
68
68
  </div>
69
69
  <div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
70
70
  <dt className="text-sm font-medium text-gray-500">
71
- Is Draft
71
+ Started at
72
72
  </dt>
73
73
  <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
74
- {order.is_draft ? "Yes" : "No"}
74
+ {order.started_at
75
+ ? new Date(order.started_at).toLocaleString()
76
+ : "Not started"}
75
77
  </dd>
76
78
  </div>
77
79
  <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
@@ -79,23 +81,9 @@ export const ViewOrderPage = () => {
79
81
  Pending Validation
80
82
  </dt>
81
83
  <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
82
- {order.is_pending_validation_by_fab ? "Yes" : "No"}
83
- </dd>
84
- </div>
85
- <div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
86
- <dt className="text-sm font-medium text-gray-500">
87
- Approved by Fab
88
- </dt>
89
- <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
90
- {order.is_approved_by_fab_review ? "Yes" : "No"}
91
- </dd>
92
- </div>
93
- <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
94
- <dt className="text-sm font-medium text-gray-500">
95
- In Production
96
- </dt>
97
- <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
98
- {order.is_in_production ? "Yes" : "No"}
84
+ {order.completed_at
85
+ ? new Date(order.completed_at).toLocaleString()
86
+ : "Not finished"}
99
87
  </dd>
100
88
  </div>
101
89
  </dl>
@@ -1,46 +0,0 @@
1
- import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server"
2
- import { test, expect } from "bun:test"
3
-
4
- test("update order", async () => {
5
- const {
6
- axios,
7
- seed: {
8
- order: { order_id },
9
- },
10
- } = await getTestServer()
11
-
12
- const response = await axios.patch("/api/orders/update", {
13
- order_id,
14
- updates: {
15
- is_draft: false,
16
- is_pending_validation_by_fab: true,
17
- jlcpcb_order_params: { param1: "value1" },
18
- },
19
- })
20
-
21
- expect(response.status).toBe(200)
22
- expect(response.data.order).toBeDefined()
23
- expect(response.data.order.order_id).toBe(order_id)
24
- expect(response.data.order.is_draft).toBe(false)
25
- expect(response.data.order.is_pending_validation_by_fab).toBe(true)
26
- expect(response.data.order.jlcpcb_order_params).toEqual({ param1: "value1" })
27
- })
28
-
29
- test("update non-existent order", async () => {
30
- const { axios } = await getTestServer()
31
-
32
- try {
33
- await axios.patch("/api/orders/update", {
34
- order_id: "non-existent-id",
35
- updates: {
36
- is_draft: false,
37
- },
38
- })
39
-
40
- // If the request doesn't throw an error, fail the test
41
- expect(true).toBe(false)
42
- } catch (error: any) {
43
- expect(error.status).toBe(404)
44
- expect(error.data.error.message).toBe("Order not found")
45
- }
46
- })
@@ -1,50 +0,0 @@
1
- import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
- import { z } from "zod"
3
- import { orderSchema } from "fake-snippets-api/lib/db/schema"
4
-
5
- export default withRouteSpec({
6
- methods: ["PATCH"],
7
- auth: "session",
8
- jsonBody: z.object({
9
- order_id: z.string(),
10
- updates: z.object({
11
- is_draft: z.boolean().optional(),
12
- is_pending_validation_by_fab: z.boolean().optional(),
13
- is_pending_review_by_fab: z.boolean().optional(),
14
- is_validated_by_fab: z.boolean().optional(),
15
- is_approved_by_fab_review: z.boolean().optional(),
16
- is_approved_by_orderer: z.boolean().optional(),
17
- is_in_production: z.boolean().optional(),
18
- is_shipped: z.boolean().optional(),
19
- is_cancelled: z.boolean().optional(),
20
- should_be_blank_pcb: z.boolean().optional(),
21
- should_include_stencil: z.boolean().optional(),
22
- jlcpcb_order_params: z.record(z.any()).optional(),
23
- circuit_json: z.array(z.record(z.any())).optional(),
24
- }),
25
- }),
26
- jsonResponse: z.object({
27
- order: orderSchema,
28
- }),
29
- })(async (req, ctx) => {
30
- const { order_id, updates } = req.jsonBody
31
-
32
- const existingOrder = ctx.db.getOrderById(order_id)
33
- if (!existingOrder) {
34
- return ctx.error(404, {
35
- error_code: "order_not_found",
36
- message: "Order not found",
37
- })
38
- }
39
-
40
- ctx.db.updateOrder(order_id, {
41
- ...updates,
42
- updated_at: new Date().toISOString(),
43
- })
44
-
45
- const updatedOrder = ctx.db.getOrderById(order_id)!
46
-
47
- return ctx.json({
48
- order: updatedOrder,
49
- })
50
- })