@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.
- package/bun-tests/fake-snippets-api/fixtures/get-test-server.ts +7 -13
- package/bun-tests/fake-snippets-api/routes/orders/create.test.ts +0 -1
- package/bun-tests/fake-snippets-api/routes/orders/get.test.ts +2 -3
- package/bun.lock +5 -0
- package/dist/bundle.js +124 -148
- package/fake-snippets-api/lib/db/schema.ts +20 -26
- package/fake-snippets-api/lib/db/seed.ts +7 -13
- package/fake-snippets-api/routes/api/_fake/seed.ts +13 -0
- package/fake-snippets-api/routes/api/orders/create.ts +54 -20
- package/package.json +2 -1
- package/playwright-tests/profile-page.spec.ts +4 -4
- package/src/components/SnippetTypeIcon.tsx +36 -0
- package/src/components/ViewSnippetHeader.tsx +9 -0
- package/src/components/dialogs/package-visibility-settings-dialog.tsx +0 -1
- package/src/pages/my-orders.tsx +1 -1
- package/src/pages/user-profile.tsx +96 -42
- package/src/pages/view-order.tsx +8 -20
- package/bun-tests/fake-snippets-api/routes/orders/update.test.ts +0 -46
- package/fake-snippets-api/routes/api/orders/update.ts +0 -50
|
@@ -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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
9
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
97
|
-
await page.getByText("Starred
|
|
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
|
|
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"
|
package/src/pages/my-orders.tsx
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
))}
|
package/src/pages/view-order.tsx
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
})
|