@tscircuit/fake-snippets 0.0.6 → 0.0.7

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.
Files changed (38) hide show
  1. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +375 -0
  2. package/bun-tests/fake-snippets-api/routes/package_files/download.test.ts +248 -0
  3. package/bun-tests/fake-snippets-api/routes/package_files/get.test.ts +220 -0
  4. package/bun-tests/fake-snippets-api/routes/package_files/list.test.ts +204 -0
  5. package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +0 -1
  6. package/bun.lock +97 -73
  7. package/dist/bundle.js +584 -212
  8. package/fake-snippets-api/lib/db/db-client.ts +13 -0
  9. package/fake-snippets-api/lib/package_file/get-package-file-id-from-file-descriptor.ts +168 -0
  10. package/fake-snippets-api/lib/package_release/find-package-release-id.ts +122 -0
  11. package/fake-snippets-api/routes/api/package_files/create.ts +132 -0
  12. package/fake-snippets-api/routes/api/package_files/download.ts +70 -153
  13. package/fake-snippets-api/routes/api/package_files/get.ts +24 -5
  14. package/fake-snippets-api/routes/api/package_files/list.ts +16 -28
  15. package/index.html +12 -1
  16. package/package.json +9 -9
  17. package/playwright-tests/profile-page.spec.ts +59 -0
  18. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-before-delete.png +0 -0
  19. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-delete-dialog.png +0 -0
  20. package/playwright-tests/snapshots/profile-page.spec.ts-profile-page-dropdown-open.png +0 -0
  21. package/scripts/generate-image-sizes.ts +0 -1
  22. package/scripts/generate_bundle_stats.js +22 -6
  23. package/src/components/AiChatInterface.tsx +8 -0
  24. package/src/components/Analytics.tsx +1 -1
  25. package/src/components/CodeAndPreview.tsx +9 -3
  26. package/src/components/CreateNewSnippetWithAiHero.tsx +6 -2
  27. package/src/components/EditorNav.tsx +4 -0
  28. package/src/components/Footer.tsx +1 -1
  29. package/src/components/Header.tsx +7 -10
  30. package/src/components/HeaderLogin.tsx +1 -1
  31. package/src/components/PreviewContent.tsx +4 -1
  32. package/src/components/SnippetList.tsx +71 -0
  33. package/src/lib/templates/blinking-led-board-template.ts +2 -1
  34. package/src/pages/dashboard.tsx +19 -44
  35. package/src/pages/editor.tsx +1 -1
  36. package/src/pages/landing.tsx +8 -16
  37. package/src/pages/quickstart.tsx +9 -9
  38. package/src/pages/user-profile.tsx +50 -3
@@ -1,4 +1,4 @@
1
- import React from "react"
1
+ import React, { useState } from "react"
2
2
  import { useQuery } from "react-query"
3
3
  import { useAxios } from "@/hooks/use-axios"
4
4
  import Header from "@/components/Header"
@@ -6,15 +6,18 @@ import Footer from "@/components/Footer"
6
6
  import { Snippet } from "fake-snippets-api/lib/db/schema"
7
7
  import { Link } from "wouter"
8
8
  import { CreateNewSnippetWithAiHero } from "@/components/CreateNewSnippetWithAiHero"
9
- import { Edit2, Star } from "lucide-react"
9
+ import { Edit2, Star, ChevronDown, ChevronUp } from "lucide-react"
10
10
  import { Button } from "@/components/ui/button"
11
11
  import { useGlobalStore } from "@/hooks/use-global-store"
12
12
  import { PrefetchPageLink } from "@/components/PrefetchPageLink"
13
+ import { SnippetList } from "@/components/SnippetList"
13
14
 
14
15
  export const DashboardPage = () => {
15
16
  const axios = useAxios()
16
17
 
17
18
  const currentUser = useGlobalStore((s) => s.session?.github_username)
19
+ const [showAllTrending, setShowAllTrending] = useState(false)
20
+ const [showAllNewest, setShowAllNewest] = useState(false)
18
21
 
19
22
  const {
20
23
  data: mySnippets,
@@ -114,48 +117,20 @@ export const DashboardPage = () => {
114
117
  )}
115
118
  </div>
116
119
  <div className="md:w-1/4">
117
- <h2 className="text-sm font-bold mb-2 text-gray-700 border-b border-gray-200">
118
- Trending Snippets
119
- </h2>
120
- {trendingSnippets && (
121
- <ul className="space-y-1">
122
- {trendingSnippets.map((snippet) => (
123
- <li key={snippet.snippet_id}>
124
- <div className="flex items-center">
125
- <Link
126
- href={`/${snippet.owner_name}/${snippet.unscoped_name}`}
127
- className="text-blue-600 hover:underline text-sm"
128
- >
129
- {snippet.owner_name}/{snippet.unscoped_name}
130
- </Link>
131
- {snippet.star_count > 0 && (
132
- <span className="ml-2 text-gray-500 text-xs flex items-center">
133
- <Star className="w-3 h-3 mr-1" />
134
- {snippet.star_count}
135
- </span>
136
- )}
137
- </div>
138
- </li>
139
- ))}
140
- </ul>
141
- )}
142
- <h2 className="text-sm font-bold mt-8 mb-2 text-gray-700 border-b border-gray-200">
143
- Newest Snippets
144
- </h2>
145
- {newestSnippets && (
146
- <ul className="space-y-1">
147
- {newestSnippets.map((snippet) => (
148
- <li key={snippet.snippet_id}>
149
- <Link
150
- href={`/${snippet.name}`}
151
- className="text-blue-600 hover:underline text-sm"
152
- >
153
- {snippet.name}
154
- </Link>
155
- </li>
156
- ))}
157
- </ul>
158
- )}
120
+ <SnippetList
121
+ title="Trending Snippets"
122
+ snippets={trendingSnippets}
123
+ showAll={showAllTrending}
124
+ onToggleShowAll={() => setShowAllTrending(!showAllTrending)}
125
+ />
126
+ <div className="mt-8">
127
+ <SnippetList
128
+ title="Newest Snippets"
129
+ snippets={newestSnippets}
130
+ showAll={showAllNewest}
131
+ onToggleShowAll={() => setShowAllNewest(!showAllNewest)}
132
+ />
133
+ </div>
159
134
  </div>
160
135
  </div>
161
136
  </div>
@@ -9,7 +9,7 @@ export const EditorPage = () => {
9
9
  const { data: snippet, isLoading, error } = useSnippet(snippetId)
10
10
 
11
11
  return (
12
- <div>
12
+ <div className="overflow-x-hidden">
13
13
  <Header />
14
14
  {!error && <CodeAndPreview snippet={snippet} />}
15
15
  {error && error.status === 404 && (
@@ -50,8 +50,8 @@ export function LandingPage() {
50
50
  <Badge variant="secondary" className="w-fit">
51
51
  Open-Source, MIT Licensed
52
52
  </Badge>
53
- <h1 className="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none">
54
- The New Foundation for Electronic Design
53
+ <h1 className="text-3xl font-bold tracking-tight sm:text-5xl xl:text-6xl/none">
54
+ AI codes electronics with tscircuit
55
55
  </h1>
56
56
  <p className="max-w-[600px] text-muted-foreground md:text-xl">
57
57
  Build electronics with code, AI, and drag'n'drop tools.
@@ -60,20 +60,12 @@ export function LandingPage() {
60
60
  and more.
61
61
  </p>
62
62
  </div>
63
- <div className="flex flex-col items-center gap-2 min-[400px]:flex-row">
64
- <Button
65
- onClick={() => {
66
- if (!isLoggedIn) {
67
- signIn()
68
- } else {
69
- navigate("/dashboard")
70
- }
71
- }}
72
- size="lg"
73
- aria-label="Get started with TSCircuit"
74
- >
75
- Get Started
76
- </Button>
63
+ <div className="flex flex-col items-center gap-2 min-[500px]:flex-row">
64
+ <a href="https://docs.tscircuit.com">
65
+ <Button size="lg" aria-label="Get started with TSCircuit">
66
+ Get Started
67
+ </Button>
68
+ </a>
77
69
  <PrefetchPageLink href="/quickstart">
78
70
  <Button
79
71
  size="lg"
@@ -65,13 +65,13 @@ export const QuickstartPage = () => {
65
65
  key={snippet.snippet_id}
66
66
  href={`/editor?snippet_id=${snippet.snippet_id}`}
67
67
  >
68
- <Card className="hover:shadow-md transition-shadow rounded-md">
68
+ <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col h-full">
69
69
  <CardHeader className="pb-0 p-4">
70
70
  <CardTitle className="text-md">
71
71
  {snippet.unscoped_name}
72
72
  </CardTitle>
73
73
  </CardHeader>
74
- <CardContent className="p-4 pt-0">
74
+ <CardContent className="p-4 pt-0 mt-auto">
75
75
  <p className="text-sm text-gray-500">
76
76
  Last edited:{" "}
77
77
  {new Date(snippet.updated_at).toLocaleDateString()}
@@ -118,7 +118,7 @@ export const QuickstartPage = () => {
118
118
 
119
119
  <div className="mt-12">
120
120
  <h2 className="text-xl font-semibold mb-4">Import as Snippet</h2>
121
- <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
121
+ <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">
122
122
  {[
123
123
  { name: "KiCad Footprint", type: "footprint" },
124
124
  { name: "KiCad Project", type: "board" },
@@ -126,7 +126,7 @@ export const QuickstartPage = () => {
126
126
  ].map((template, index) => (
127
127
  <Card
128
128
  key={index}
129
- className="hover:shadow-md transition-shadow rounded-md opacity-50"
129
+ className="hover:shadow-md transition-shadow rounded-md opacity-50 flex flex-col"
130
130
  >
131
131
  <CardHeader className="p-4 pb-0">
132
132
  <CardTitle className="text-lg flex items-center justify-between">
@@ -134,7 +134,7 @@ export const QuickstartPage = () => {
134
134
  <TypeBadge type={template.type as any} className="ml-2" />
135
135
  </CardTitle>
136
136
  </CardHeader>
137
- <CardContent className="p-4">
137
+ <CardContent className="p-4 mt-auto">
138
138
  <Button
139
139
  className="w-full"
140
140
  onClick={() => {
@@ -146,14 +146,14 @@ export const QuickstartPage = () => {
146
146
  </CardContent>
147
147
  </Card>
148
148
  ))}
149
- <Card className="hover:shadow-md transition-shadow rounded-md">
149
+ <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col">
150
150
  <CardHeader className="p-4 pb-0">
151
151
  <CardTitle className="text-lg flex items-center justify-between">
152
152
  JLCPCB Component
153
153
  <TypeBadge type="package" className="ml-2" />
154
154
  </CardTitle>
155
155
  </CardHeader>
156
- <CardContent className="p-4">
156
+ <CardContent className="p-4 mt-auto">
157
157
  <Button
158
158
  className="w-full"
159
159
  onClick={() => setIsJLCPCBDialogOpen(true)}
@@ -162,14 +162,14 @@ export const QuickstartPage = () => {
162
162
  </Button>
163
163
  </CardContent>
164
164
  </Card>
165
- <Card className="hover:shadow-md transition-shadow rounded-md">
165
+ <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col">
166
166
  <CardHeader className="p-4 pb-0">
167
167
  <CardTitle className="text-lg flex items-center justify-between">
168
168
  Circuit Json
169
169
  <TypeBadge type="module" className="ml-2" />
170
170
  </CardTitle>
171
171
  </CardHeader>
172
- <CardContent className="p-4">
172
+ <CardContent className="p-4 mt-auto">
173
173
  <Button
174
174
  className="w-full"
175
175
  onClick={() => setIsCircuitJsonImportDialogOpen(true)}
@@ -10,6 +10,14 @@ import { Button } from "@/components/ui/button"
10
10
  import { GitHubLogoIcon, StarIcon } 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"
14
+ import { useConfirmDeleteSnippetDialog } from "@/components/dialogs/confirm-delete-snippet-dialog"
15
+ import {
16
+ DropdownMenu,
17
+ DropdownMenuContent,
18
+ DropdownMenuItem,
19
+ DropdownMenuTrigger,
20
+ } from "@/components/ui/dropdown-menu"
13
21
 
14
22
  export const UserProfilePage = () => {
15
23
  const { username } = useParams()
@@ -17,6 +25,9 @@ export const UserProfilePage = () => {
17
25
  const [searchQuery, setSearchQuery] = useState("")
18
26
  const session = useGlobalStore((s) => s.session)
19
27
  const isCurrentUserProfile = username === session?.github_username
28
+ const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
29
+ useConfirmDeleteSnippetDialog()
30
+ const [snippetToDelete, setSnippetToDelete] = useState<Snippet | null>(null)
20
31
 
21
32
  const { data: userSnippets, isLoading } = useQuery<Snippet[]>(
22
33
  ["userSnippets", username],
@@ -32,6 +43,12 @@ export const UserProfilePage = () => {
32
43
  snippet.unscoped_name.toLowerCase().includes(searchQuery.toLowerCase()),
33
44
  )
34
45
 
46
+ const handleDeleteClick = (e: React.MouseEvent, snippet: Snippet) => {
47
+ e.preventDefault() // Prevent navigation
48
+ setSnippetToDelete(snippet)
49
+ openDeleteDialog()
50
+ }
51
+
35
52
  return (
36
53
  <div>
37
54
  <Header />
@@ -76,9 +93,33 @@ export const UserProfilePage = () => {
76
93
  <h3 className="text-md font-semibold">
77
94
  {snippet.unscoped_name}
78
95
  </h3>
79
- <div className="flex items-center text-gray-600">
80
- <StarIcon className="w-4 h-4 mr-1" />
81
- <span>{snippet.star_count || 0}</span>
96
+ <div className="flex items-center gap-2">
97
+ <div className="flex items-center text-gray-600">
98
+ <StarIcon className="w-4 h-4 mr-1" />
99
+ <span>{snippet.star_count || 0}</span>
100
+ </div>
101
+ {isCurrentUserProfile && (
102
+ <DropdownMenu>
103
+ <DropdownMenuTrigger asChild>
104
+ <Button
105
+ variant="ghost"
106
+ size="icon"
107
+ className="h-8 w-8"
108
+ >
109
+ <MoreVertical className="h-4 w-4" />
110
+ </Button>
111
+ </DropdownMenuTrigger>
112
+ <DropdownMenuContent>
113
+ <DropdownMenuItem
114
+ className="text-xs text-red-600"
115
+ onClick={(e) => handleDeleteClick(e, snippet)}
116
+ >
117
+ <Trash2 className="mr-2 h-3 w-3" />
118
+ Delete Snippet
119
+ </DropdownMenuItem>
120
+ </DropdownMenuContent>
121
+ </DropdownMenu>
122
+ )}
82
123
  </div>
83
124
  </div>
84
125
  <p className="text-sm text-gray-500">
@@ -91,6 +132,12 @@ export const UserProfilePage = () => {
91
132
  </div>
92
133
  )}
93
134
  </div>
135
+ {snippetToDelete && (
136
+ <DeleteDialog
137
+ snippetId={snippetToDelete.snippet_id}
138
+ snippetName={snippetToDelete.unscoped_name}
139
+ />
140
+ )}
94
141
  <Footer />
95
142
  </div>
96
143
  )