@tscircuit/fake-snippets 0.0.13 → 0.0.14
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.lock +3 -0
- package/dist/bundle.js +292 -280
- package/fake-snippets-api/routes/api/_fake/seed.ts +13 -0
- package/package.json +2 -1
- 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/user-profile.tsx +78 -40
|
@@ -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
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
"states-us": "^1.1.1",
|
|
119
119
|
"tailwind-merge": "^2.5.2",
|
|
120
120
|
"tailwindcss-animate": "^1.0.7",
|
|
121
|
+
"timeago.js": "^4.0.2",
|
|
121
122
|
"use-async-memo": "^1.2.5",
|
|
122
123
|
"use-mouse-matrix-transform": "^1.3.0",
|
|
123
124
|
"vaul": "^0.9.9",
|
|
@@ -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"
|
|
@@ -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,10 @@ 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 { format } from "timeago.js"
|
|
22
26
|
|
|
23
27
|
export const UserProfilePage = () => {
|
|
24
28
|
const { username } = useParams()
|
|
@@ -52,6 +56,8 @@ export const UserProfilePage = () => {
|
|
|
52
56
|
},
|
|
53
57
|
)
|
|
54
58
|
|
|
59
|
+
const baseUrl = useSnippetsBaseApiUrl()
|
|
60
|
+
|
|
55
61
|
const snippetsToShow =
|
|
56
62
|
activeTab === "starred" ? starredSnippets : userSnippets
|
|
57
63
|
const isLoading =
|
|
@@ -118,49 +124,81 @@ export const UserProfilePage = () => {
|
|
|
118
124
|
key={snippet.snippet_id}
|
|
119
125
|
href={`/${snippet.owner_name}/${snippet.unscoped_name}`}
|
|
120
126
|
>
|
|
121
|
-
<div className="border p-4 rounded-md hover:shadow-md transition-shadow">
|
|
122
|
-
<div className="flex
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
<div className="border p-4 rounded-md hover:shadow-md transition-shadow flex flex-col gap-4">
|
|
128
|
+
<div className="flex items-start gap-4">
|
|
129
|
+
<div className="h-16 w-16 flex-shrink-0 rounded-md overflow-hidden">
|
|
130
|
+
<OptimizedImage
|
|
131
|
+
src={`${baseUrl}/snippets/images/${snippet.owner_name}/${snippet.unscoped_name}/pcb.svg`}
|
|
132
|
+
alt={`${snippet.owner_name}'s profile`}
|
|
133
|
+
className="object-cover h-full w-full transition-transform duration-300 -rotate-45 hover:rotate-0 hover:scale-110 scale-150"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
<div className="flex-1 min-w-0">
|
|
137
|
+
<div className="flex justify-between items-start mb-[2px]">
|
|
138
|
+
<h2 className="text-md font-semibold text-gray-900 truncate">
|
|
139
|
+
{snippet.unscoped_name}
|
|
140
|
+
</h2>
|
|
141
|
+
<div className="flex items-center gap-2">
|
|
142
|
+
<SnippetTypeIcon
|
|
143
|
+
type={snippet.snippet_type}
|
|
144
|
+
className="pt-[2.5px]"
|
|
145
|
+
/>
|
|
146
|
+
<div className="flex items-center gap-1 text-gray-600">
|
|
147
|
+
<StarIcon className="w-4 h-4 pt-[2.5px]" />
|
|
148
|
+
<span className="text-[16px]">
|
|
149
|
+
{snippet.star_count || 0}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
{isCurrentUserProfile && activeTab === "all" && (
|
|
153
|
+
<DropdownMenu>
|
|
154
|
+
<DropdownMenuTrigger asChild>
|
|
155
|
+
<Button
|
|
156
|
+
variant="ghost"
|
|
157
|
+
size="icon"
|
|
158
|
+
className="h-8 w-8"
|
|
159
|
+
>
|
|
160
|
+
<MoreVertical className="h-4 w-4" />
|
|
161
|
+
</Button>
|
|
162
|
+
</DropdownMenuTrigger>
|
|
163
|
+
<DropdownMenuContent>
|
|
164
|
+
<DropdownMenuItem
|
|
165
|
+
className="text-xs text-red-600"
|
|
166
|
+
onClick={(e) =>
|
|
167
|
+
handleDeleteClick(e, snippet)
|
|
168
|
+
}
|
|
169
|
+
>
|
|
170
|
+
<Trash2 className="mr-2 h-3 w-3" />
|
|
171
|
+
Delete Snippet
|
|
172
|
+
</DropdownMenuItem>
|
|
173
|
+
</DropdownMenuContent>
|
|
174
|
+
</DropdownMenu>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
130
177
|
</div>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
<p
|
|
179
|
+
className={`${!snippet.description && "h-[1.25rem]"} text-sm text-gray-500 mb-1 truncate max-w-xs`}
|
|
180
|
+
>
|
|
181
|
+
{snippet.description ? snippet.description : " "}
|
|
182
|
+
</p>
|
|
183
|
+
<div className={`flex items-center gap-4`}>
|
|
184
|
+
{snippet.is_private ? (
|
|
185
|
+
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
186
|
+
<LockClosedIcon height={12} width={12} />
|
|
187
|
+
<span>Private</span>
|
|
188
|
+
</div>
|
|
189
|
+
) : (
|
|
190
|
+
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
191
|
+
<GlobeIcon height={12} width={12} />
|
|
192
|
+
<span>Public</span>
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
<div className="flex items-center text-xs gap-1 text-gray-500">
|
|
196
|
+
<PencilIcon height={12} width={12} />
|
|
197
|
+
<span>{format(snippet.updated_at)}</span>
|
|
134
198
|
</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
|
-
)}
|
|
199
|
+
</div>
|
|
158
200
|
</div>
|
|
159
201
|
</div>
|
|
160
|
-
<p className="text-sm text-gray-500">
|
|
161
|
-
Last Updated:{" "}
|
|
162
|
-
{new Date(snippet.updated_at).toLocaleString()}
|
|
163
|
-
</p>
|
|
164
202
|
</div>
|
|
165
203
|
</Link>
|
|
166
204
|
))}
|