@tscircuit/fake-snippets 0.0.36 → 0.0.38

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 (31) hide show
  1. package/bun-tests/fake-snippets-api/routes/package_files/create_or_update.test.ts +575 -0
  2. package/bun-tests/fake-snippets-api/routes/package_files/delete.test.ts +233 -0
  3. package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +2 -2
  4. package/dist/bundle.js +499 -249
  5. package/dist/index.d.ts +23 -4
  6. package/dist/index.js +30 -2
  7. package/fake-snippets-api/lib/db/db-client.ts +29 -1
  8. package/fake-snippets-api/lib/db/schema.ts +3 -0
  9. package/fake-snippets-api/routes/api/package_files/create_or_update.ts +179 -0
  10. package/fake-snippets-api/routes/api/package_files/delete.ts +106 -0
  11. package/fake-snippets-api/routes/api/snippets/{list_newest.ts → list_latest.ts} +2 -2
  12. package/package.json +1 -1
  13. package/scripts/generate-sitemap.ts +1 -1
  14. package/src/App.tsx +2 -2
  15. package/src/components/EditorNav.tsx +4 -4
  16. package/src/components/Footer.tsx +9 -3
  17. package/src/components/HiddenFilesDropdown.tsx +44 -0
  18. package/src/components/LatestSnippets.tsx +1 -1
  19. package/src/components/ViewPackagePage/components/package-header.tsx +0 -4
  20. package/src/components/ViewPackagePage/components/tab-views/3d-view.tsx +1 -1
  21. package/src/components/ViewPackagePage/components/tab-views/bom-view.tsx +1 -1
  22. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +23 -2
  23. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +1 -0
  24. package/src/components/dialogs/confirm-delete-package-dialog.tsx +48 -0
  25. package/src/hooks/use-delete-package.ts +40 -0
  26. package/src/hooks/use-fork-package-mutation.ts +14 -61
  27. package/src/pages/dashboard.tsx +8 -8
  28. package/src/pages/latest.tsx +212 -0
  29. package/src/pages/user-profile.tsx +15 -14
  30. package/src/components/dialogs/confirm-delete-snippet-dialog.tsx +0 -80
  31. package/src/pages/newest.tsx +0 -16
package/dist/index.d.ts CHANGED
@@ -316,18 +316,27 @@ declare const packageFileSchema: z.ZodObject<{
316
316
  file_path: z.ZodString;
317
317
  content_text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
318
318
  created_at: z.ZodString;
319
+ content_mimetype: z.ZodOptional<z.ZodNullable<z.ZodString>>;
320
+ is_release_tarball: z.ZodOptional<z.ZodBoolean>;
321
+ npm_pack_output: z.ZodOptional<z.ZodNullable<z.ZodAny>>;
319
322
  }, "strip", z.ZodTypeAny, {
320
323
  package_release_id: string;
321
324
  created_at: string;
322
325
  package_file_id: string;
323
326
  file_path: string;
324
327
  content_text?: string | null | undefined;
328
+ content_mimetype?: string | null | undefined;
329
+ is_release_tarball?: boolean | undefined;
330
+ npm_pack_output?: any;
325
331
  }, {
326
332
  package_release_id: string;
327
333
  created_at: string;
328
334
  package_file_id: string;
329
335
  file_path: string;
330
336
  content_text?: string | null | undefined;
337
+ content_mimetype?: string | null | undefined;
338
+ is_release_tarball?: boolean | undefined;
339
+ npm_pack_output?: any;
331
340
  }>;
332
341
  type PackageFile = z.infer<typeof packageFileSchema>;
333
342
  declare const packageSchema: z.ZodObject<{
@@ -556,6 +565,9 @@ declare const createDatabase: ({ seed }?: {
556
565
  package_file_id: string;
557
566
  file_path: string;
558
567
  content_text?: string | null | undefined;
568
+ content_mimetype?: string | null | undefined;
569
+ is_release_tarball?: boolean | undefined;
570
+ npm_pack_output?: any;
559
571
  }[];
560
572
  sessions: {
561
573
  session_id: string;
@@ -690,7 +702,7 @@ declare const createDatabase: ({ seed }?: {
690
702
  step_function_name: string | null;
691
703
  error_message: string | null;
692
704
  }[];
693
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getNewestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "addPackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
705
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
694
706
  addOrder: (order: Omit<Order, "order_id">) => Order;
695
707
  getOrderById: (orderId: string) => Order | undefined;
696
708
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -722,7 +734,7 @@ declare const createDatabase: ({ seed }?: {
722
734
  updateAccountPackage: (accountPackageId: string, updates: Partial<AccountPackage>) => void;
723
735
  deleteAccountPackage: (accountPackageId: string) => boolean;
724
736
  addSnippet: (snippet: Omit<z.input<typeof snippetSchema>, "snippet_id" | "package_release_id">) => Snippet;
725
- getNewestSnippets: (limit: number) => Snippet[];
737
+ getLatestSnippets: (limit: number) => Snippet[];
726
738
  getTrendingSnippets: (limit: number, since: string) => Snippet[];
727
739
  getPackagesByAuthor: (authorName?: string) => Package[];
728
740
  getSnippetByAuthorAndName: (authorName: string, snippetName: string) => Snippet | undefined;
@@ -752,7 +764,9 @@ declare const createDatabase: ({ seed }?: {
752
764
  getPackageReleaseById: (packageReleaseId: string) => PackageRelease | undefined;
753
765
  addPackageRelease: (packageRelease: Omit<PackageRelease, "package_release_id">) => PackageRelease;
754
766
  updatePackageRelease: (packageRelease: PackageRelease) => void;
767
+ deletePackageFile: (packageFileId: string) => boolean;
755
768
  addPackageFile: (packageFile: Omit<PackageFile, "package_file_id">) => PackageFile;
769
+ updatePackageFile: (packageFileId: string, updates: Partial<Omit<PackageFile, "package_file_id">>) => PackageFile;
756
770
  getStarCount: (packageId: string) => number;
757
771
  getPackageFilesByReleaseId: (packageReleaseId: string) => PackageFile[];
758
772
  }> & Omit<{
@@ -795,6 +809,9 @@ declare const createDatabase: ({ seed }?: {
795
809
  package_file_id: string;
796
810
  file_path: string;
797
811
  content_text?: string | null | undefined;
812
+ content_mimetype?: string | null | undefined;
813
+ is_release_tarball?: boolean | undefined;
814
+ npm_pack_output?: any;
798
815
  }[];
799
816
  sessions: {
800
817
  session_id: string;
@@ -929,7 +946,7 @@ declare const createDatabase: ({ seed }?: {
929
946
  step_function_name: string | null;
930
947
  error_message: string | null;
931
948
  }[];
932
- }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getNewestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "addPackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
949
+ }, "addOrder" | "getOrderById" | "getOrderFilesByOrderId" | "getJlcpcbOrderStatesByOrderId" | "getJlcpcbOrderStepRunsByJlcpcbOrderStateId" | "updateOrder" | "addJlcpcbOrderState" | "updateJlcpcbOrderState" | "addOrderFile" | "getOrderFileById" | "addAccount" | "addAccountPackage" | "getAccountPackageById" | "updateAccountPackage" | "deleteAccountPackage" | "addSnippet" | "getLatestSnippets" | "getTrendingSnippets" | "getPackagesByAuthor" | "getSnippetByAuthorAndName" | "updateSnippet" | "getSnippetById" | "searchSnippets" | "deleteSnippet" | "addSession" | "getSessions" | "createLoginPage" | "getLoginPage" | "updateLoginPage" | "getAccount" | "updateAccount" | "createSession" | "addStar" | "removeStar" | "hasStarred" | "addPackage" | "updatePackage" | "getPackageById" | "getPackageReleaseById" | "addPackageRelease" | "updatePackageRelease" | "deletePackageFile" | "addPackageFile" | "updatePackageFile" | "getStarCount" | "getPackageFilesByReleaseId"> & {
933
950
  addOrder: (order: Omit<Order, "order_id">) => Order;
934
951
  getOrderById: (orderId: string) => Order | undefined;
935
952
  getOrderFilesByOrderId: (orderId: string) => OrderFile[];
@@ -961,7 +978,7 @@ declare const createDatabase: ({ seed }?: {
961
978
  updateAccountPackage: (accountPackageId: string, updates: Partial<AccountPackage>) => void;
962
979
  deleteAccountPackage: (accountPackageId: string) => boolean;
963
980
  addSnippet: (snippet: Omit<z.input<typeof snippetSchema>, "snippet_id" | "package_release_id">) => Snippet;
964
- getNewestSnippets: (limit: number) => Snippet[];
981
+ getLatestSnippets: (limit: number) => Snippet[];
965
982
  getTrendingSnippets: (limit: number, since: string) => Snippet[];
966
983
  getPackagesByAuthor: (authorName?: string) => Package[];
967
984
  getSnippetByAuthorAndName: (authorName: string, snippetName: string) => Snippet | undefined;
@@ -991,7 +1008,9 @@ declare const createDatabase: ({ seed }?: {
991
1008
  getPackageReleaseById: (packageReleaseId: string) => PackageRelease | undefined;
992
1009
  addPackageRelease: (packageRelease: Omit<PackageRelease, "package_release_id">) => PackageRelease;
993
1010
  updatePackageRelease: (packageRelease: PackageRelease) => void;
1011
+ deletePackageFile: (packageFileId: string) => boolean;
994
1012
  addPackageFile: (packageFile: Omit<PackageFile, "package_file_id">) => PackageFile;
1013
+ updatePackageFile: (packageFileId: string, updates: Partial<Omit<PackageFile, "package_file_id">>) => PackageFile;
995
1014
  getStarCount: (packageId: string) => number;
996
1015
  getPackageFilesByReleaseId: (packageReleaseId: string) => PackageFile[];
997
1016
  };
package/dist/index.js CHANGED
@@ -118,7 +118,10 @@ var packageFileSchema = z.object({
118
118
  package_release_id: z.string(),
119
119
  file_path: z.string(),
120
120
  content_text: z.string().nullable().optional(),
121
- created_at: z.string().datetime()
121
+ created_at: z.string().datetime(),
122
+ content_mimetype: z.string().nullable().optional(),
123
+ is_release_tarball: z.boolean().optional(),
124
+ npm_pack_output: z.any().nullable().optional()
122
125
  });
123
126
  var packageSchema = z.object({
124
127
  package_id: z.string(),
@@ -2206,7 +2209,7 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2206
2209
  is_unlisted: false
2207
2210
  };
2208
2211
  },
2209
- getNewestSnippets: (limit) => {
2212
+ getLatestSnippets: (limit) => {
2210
2213
  const state = get();
2211
2214
  const snippetPackages = state.packages.filter((pkg) => pkg.is_snippet === true).map((pkg) => {
2212
2215
  const packageRelease = state.packageReleases.find(
@@ -2825,6 +2828,20 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2825
2828
  )
2826
2829
  }));
2827
2830
  },
2831
+ deletePackageFile: (packageFileId) => {
2832
+ let deleted = false;
2833
+ set((state) => {
2834
+ const index = state.packageFiles.findIndex(
2835
+ (file) => file.package_file_id === packageFileId
2836
+ );
2837
+ if (index !== -1) {
2838
+ state.packageFiles.splice(index, 1);
2839
+ deleted = true;
2840
+ }
2841
+ return state;
2842
+ });
2843
+ return deleted;
2844
+ },
2828
2845
  addPackageFile: (packageFile) => {
2829
2846
  const newPackageFile = {
2830
2847
  package_file_id: `package_file_${Date.now()}`,
@@ -2835,6 +2852,17 @@ var initializer = combine(databaseSchema.parse({}), (set, get) => ({
2835
2852
  }));
2836
2853
  return newPackageFile;
2837
2854
  },
2855
+ updatePackageFile: (packageFileId, updates) => {
2856
+ set((state2) => ({
2857
+ packageFiles: state2.packageFiles.map(
2858
+ (file) => file.package_file_id === packageFileId ? { ...file, ...updates } : file
2859
+ )
2860
+ }));
2861
+ const state = get();
2862
+ return state.packageFiles.find(
2863
+ (file) => file.package_file_id === packageFileId
2864
+ );
2865
+ },
2838
2866
  getStarCount: (packageId) => {
2839
2867
  const state = get();
2840
2868
  return state.accountPackages.filter(
@@ -320,7 +320,7 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
320
320
  is_unlisted: false,
321
321
  }
322
322
  },
323
- getNewestSnippets: (limit: number): Snippet[] => {
323
+ getLatestSnippets: (limit: number): Snippet[] => {
324
324
  const state = get()
325
325
 
326
326
  // Get all packages that are snippets
@@ -1147,6 +1147,20 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
1147
1147
  ),
1148
1148
  }))
1149
1149
  },
1150
+ deletePackageFile: (packageFileId: string): boolean => {
1151
+ let deleted = false
1152
+ set((state) => {
1153
+ const index = state.packageFiles.findIndex(
1154
+ (file) => file.package_file_id === packageFileId,
1155
+ )
1156
+ if (index !== -1) {
1157
+ state.packageFiles.splice(index, 1)
1158
+ deleted = true
1159
+ }
1160
+ return state
1161
+ })
1162
+ return deleted
1163
+ },
1150
1164
  addPackageFile: (
1151
1165
  packageFile: Omit<PackageFile, "package_file_id">,
1152
1166
  ): PackageFile => {
@@ -1159,6 +1173,20 @@ const initializer = combine(databaseSchema.parse({}), (set, get) => ({
1159
1173
  }))
1160
1174
  return newPackageFile
1161
1175
  },
1176
+ updatePackageFile: (
1177
+ packageFileId: string,
1178
+ updates: Partial<Omit<PackageFile, "package_file_id">>,
1179
+ ): PackageFile => {
1180
+ set((state) => ({
1181
+ packageFiles: state.packageFiles.map((file) =>
1182
+ file.package_file_id === packageFileId ? { ...file, ...updates } : file,
1183
+ ),
1184
+ }))
1185
+ const state = get()
1186
+ return state.packageFiles.find(
1187
+ (file) => file.package_file_id === packageFileId,
1188
+ )!
1189
+ },
1162
1190
  getStarCount: (packageId: string): number => {
1163
1191
  const state = get()
1164
1192
  return state.accountPackages.filter(
@@ -138,6 +138,9 @@ export const packageFileSchema = z.object({
138
138
  file_path: z.string(),
139
139
  content_text: z.string().nullable().optional(),
140
140
  created_at: z.string().datetime(),
141
+ content_mimetype: z.string().nullable().optional(),
142
+ is_release_tarball: z.boolean().optional(),
143
+ npm_pack_output: z.any().nullable().optional(),
141
144
  })
142
145
  export type PackageFile = z.infer<typeof packageFileSchema>
143
146
 
@@ -0,0 +1,179 @@
1
+ import * as zt from "fake-snippets-api/lib/db/schema"
2
+ import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
3
+ import { withRouteSpec } from "fake-snippets-api/lib/with-winter-spec"
4
+ import { z } from "zod"
5
+
6
+ const routeSpec = {
7
+ methods: ["POST"],
8
+ auth: "session",
9
+ jsonBody: z
10
+ .object({
11
+ file_path: z.string(),
12
+ is_release_tarball: z.boolean().optional().default(false),
13
+ content_mimetype: z.string().optional(),
14
+ content_text: z.string().optional(),
15
+ content_base64: z.string().optional(),
16
+ package_release_id: z.string().optional(),
17
+ package_name_with_version: z.string().optional(),
18
+ npm_pack_output: z.any().optional(),
19
+ })
20
+ .refine((v) => {
21
+ if (v.package_release_id) return true
22
+ if (v.package_name_with_version) return true
23
+ return false
24
+ }, "Must specify either package_release_id or package_name_with_version")
25
+ .refine((v) => {
26
+ if (v.package_release_id && v.package_name_with_version) return false
27
+ return true
28
+ }, "Cannot specify both package_release_id and package_name_with_version")
29
+ .refine((v) => {
30
+ if (v.content_base64 && v.content_text) return false
31
+ if (!v.content_base64 && !v.content_text) return false
32
+ return true
33
+ }, "Either content_base64 or content_text is required"),
34
+ jsonResponse: z.object({
35
+ ok: z.boolean(),
36
+ package_file: zt.packageFileSchema,
37
+ }),
38
+ } as const
39
+
40
+ export default withRouteSpec(routeSpec)(async (req, ctx) => {
41
+ const {
42
+ file_path,
43
+ content_mimetype: providedContentMimetype,
44
+ content_base64,
45
+ content_text,
46
+ is_release_tarball,
47
+ npm_pack_output,
48
+ } = req.jsonBody
49
+
50
+ if (is_release_tarball && !npm_pack_output) {
51
+ return ctx.error(400, {
52
+ error_code: "missing_options",
53
+ message: "npm_pack_output is required for release tarballs",
54
+ })
55
+ }
56
+
57
+ if (!is_release_tarball && npm_pack_output) {
58
+ return ctx.error(404, {
59
+ error_code: "invalid_options",
60
+ message: "npm_pack_output is only valid for release tarballs",
61
+ })
62
+ }
63
+
64
+ let packageReleaseId = req.jsonBody.package_release_id
65
+
66
+ if (!packageReleaseId && req.jsonBody.package_name_with_version) {
67
+ const foundPackageReleaseId = await findPackageReleaseId(
68
+ { package_name_with_version: req.jsonBody.package_name_with_version },
69
+ ctx,
70
+ )
71
+ if (foundPackageReleaseId) {
72
+ packageReleaseId = foundPackageReleaseId
73
+ }
74
+ }
75
+
76
+ if (!packageReleaseId) {
77
+ return ctx.error(404, {
78
+ error_code: "package_release_not_found",
79
+ message: "Package release not found",
80
+ })
81
+ }
82
+
83
+ // Verify the package release exists
84
+ const packageRelease = ctx.db.packageReleases.find(
85
+ (pr) => pr.package_release_id === packageReleaseId,
86
+ )
87
+
88
+ if (!packageRelease) {
89
+ return ctx.error(404, {
90
+ error_code: "package_release_not_found",
91
+ message: "Package release not found",
92
+ })
93
+ }
94
+
95
+ // Get the package to check permissions
96
+ const existingPackage = ctx.db.packages.find(
97
+ (p) => p.package_id === packageRelease.package_id,
98
+ )
99
+
100
+ if (!existingPackage) {
101
+ return ctx.error(404, {
102
+ error_code: "package_not_found",
103
+ message: "Package not found",
104
+ })
105
+ }
106
+
107
+ // Check if user has permission to create/update the file
108
+ if (existingPackage.creator_account_id !== ctx.auth.account_id) {
109
+ return ctx.error(403, {
110
+ error_code: "forbidden",
111
+ message: "You don't have permission to modify files in this package",
112
+ })
113
+ }
114
+
115
+ // Check if file exists
116
+ const exisitingFile = ctx.db.packageFiles.find(
117
+ (pf) =>
118
+ pf.package_release_id === packageReleaseId && pf.file_path === file_path,
119
+ )
120
+
121
+ // Determine content mimetype
122
+ const contentMimetype =
123
+ providedContentMimetype ||
124
+ (file_path.endsWith(".ts") || file_path.endsWith(".tsx")
125
+ ? "text/typescript"
126
+ : null) ||
127
+ (file_path.endsWith(".js") ? "application/javascript" : null) ||
128
+ (file_path.endsWith(".json") ? "application/json" : null) ||
129
+ (file_path.endsWith(".md") ? "text/markdown" : null) ||
130
+ (file_path.endsWith(".html") ? "text/html" : null) ||
131
+ (file_path.endsWith(".css") ? "text/css" : null) ||
132
+ "application/octet-stream"
133
+
134
+ if (exisitingFile) {
135
+ const package_file = ctx.db.updatePackageFile(
136
+ exisitingFile.package_file_id,
137
+ {
138
+ content_text:
139
+ content_text ||
140
+ (content_base64
141
+ ? Buffer.from(content_base64, "base64").toString()
142
+ : null),
143
+ content_mimetype: contentMimetype,
144
+ is_release_tarball: is_release_tarball || false,
145
+ npm_pack_output: npm_pack_output || null,
146
+ },
147
+ )
148
+
149
+ return ctx.json({
150
+ ok: true,
151
+ package_file,
152
+ })
153
+ }
154
+
155
+ // Create the package file
156
+ const newPackageFile = {
157
+ package_file_id: crypto.randomUUID(),
158
+ package_release_id: packageReleaseId,
159
+ file_path,
160
+ content_text:
161
+ content_text ||
162
+ (content_base64
163
+ ? Buffer.from(content_base64, "base64").toString()
164
+ : null),
165
+ content_mimetype: contentMimetype,
166
+ is_directory: false,
167
+ is_release_tarball: is_release_tarball || false,
168
+ npm_pack_output: npm_pack_output || null,
169
+ created_at: new Date().toISOString(),
170
+ }
171
+
172
+ // Add to the test database
173
+ ctx.db.addPackageFile(newPackageFile)
174
+
175
+ return ctx.json({
176
+ ok: true,
177
+ package_file: newPackageFile,
178
+ })
179
+ })
@@ -0,0 +1,106 @@
1
+ import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2
+ import { z } from "zod"
3
+ import { findPackageReleaseId } from "fake-snippets-api/lib/package_release/find-package-release-id"
4
+
5
+ export default withRouteSpec({
6
+ methods: ["POST"],
7
+ auth: "session",
8
+ jsonBody: z
9
+ .object({
10
+ package_release_id: z.string().optional(),
11
+ package_name_with_version: z.string().optional(),
12
+ file_path: z.string(),
13
+ })
14
+ .refine((v) => {
15
+ if (v.package_release_id) return true
16
+ if (v.package_name_with_version) return true
17
+ return false
18
+ }, "Must specify either package_release_id or package_name_with_version")
19
+ .refine((v) => {
20
+ if (v.package_release_id && v.package_name_with_version) return false
21
+ return true
22
+ }, "Cannot specify both package_release_id and package_name_with_version"),
23
+ jsonResponse: z.object({
24
+ ok: z.boolean(),
25
+ }),
26
+ })(async (req, ctx) => {
27
+ const { file_path } = req.jsonBody
28
+
29
+ let packageReleaseId = req.jsonBody.package_release_id
30
+
31
+ if (!packageReleaseId && req.jsonBody.package_name_with_version) {
32
+ const foundPackageReleaseId = await findPackageReleaseId(
33
+ req.jsonBody.package_name_with_version,
34
+ ctx,
35
+ )
36
+ if (foundPackageReleaseId) {
37
+ packageReleaseId = foundPackageReleaseId
38
+ }
39
+ }
40
+
41
+ if (!packageReleaseId) {
42
+ return ctx.error(404, {
43
+ error_code: "package_release_not_found",
44
+ message: "Package release not found",
45
+ })
46
+ }
47
+
48
+ // Get the package release to check permissions
49
+ const packageRelease = ctx.db.packageReleases.find(
50
+ (pr) => pr.package_release_id === packageReleaseId,
51
+ )
52
+
53
+ if (!packageRelease) {
54
+ return ctx.error(404, {
55
+ error_code: "package_release_not_found",
56
+ message: "Package release not found",
57
+ })
58
+ }
59
+
60
+ // Get the package to check permissions
61
+ const existingpackage = ctx.db.packages.find(
62
+ (p) => p.package_id === packageRelease.package_id,
63
+ )
64
+
65
+ if (!existingpackage) {
66
+ return ctx.error(404, {
67
+ error_code: "package_not_found",
68
+ message: "Package not found",
69
+ })
70
+ }
71
+
72
+ // Check if user has permission to delete the file
73
+ if (existingpackage.creator_account_id !== ctx.auth.account_id) {
74
+ return ctx.error(403, {
75
+ error_code: "forbidden",
76
+ message: "You don't have permission to delete files in this package",
77
+ })
78
+ }
79
+
80
+ // Find the file
81
+ const packageFile = ctx.db.packageFiles.find(
82
+ (f) =>
83
+ f.file_path === file_path && f.package_release_id === packageReleaseId,
84
+ )
85
+
86
+ if (!packageFile) {
87
+ return ctx.error(404, {
88
+ error_code: "file_not_found",
89
+ message: "Package file not found",
90
+ })
91
+ }
92
+
93
+ // Delete the file using deletePackageFile method
94
+ const deleted = ctx.db.deletePackageFile(packageFile.package_file_id)
95
+
96
+ if (!deleted) {
97
+ return ctx.error(500, {
98
+ error_code: "file_deletion_failed",
99
+ message: "Failed to delete package file",
100
+ })
101
+ }
102
+
103
+ return ctx.json({
104
+ ok: true,
105
+ })
106
+ })
@@ -8,6 +8,6 @@ export default withRouteSpec({
8
8
  snippets: z.array(snippetSchema),
9
9
  }),
10
10
  })(async (req, ctx) => {
11
- const newestSnippets = ctx.db.getNewestSnippets(20)
12
- return ctx.json({ snippets: newestSnippets })
11
+ const latestSnippets = ctx.db.getLatestSnippets(20)
12
+ return ctx.json({ snippets: latestSnippets })
13
13
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fake-snippets",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,7 @@ const staticRoutes = [
9
9
  { url: "/playground", changefreq: "weekly", priority: 0.9 },
10
10
  { url: "/quickstart", changefreq: "monthly", priority: 0.8 },
11
11
  { url: "/dashboard", changefreq: "weekly", priority: 0.7 },
12
- { url: "/newest", changefreq: "daily", priority: 0.8 },
12
+ { url: "/latest", changefreq: "daily", priority: 0.8 },
13
13
  { url: "/search", changefreq: "weekly", priority: 0.7 },
14
14
  { url: "/settings", changefreq: "monthly", priority: 0.6 },
15
15
  { url: "/community/join-redirect", changefreq: "monthly", priority: 0.6 },
package/src/App.tsx CHANGED
@@ -61,7 +61,7 @@ const EditorPage = lazyImport(async () => {
61
61
  })
62
62
  const LandingPage = lazyImport(() => import("@/pages/landing"))
63
63
  const MyOrdersPage = lazyImport(() => import("@/pages/my-orders"))
64
- const NewestPage = lazyImport(() => import("@/pages/newest"))
64
+ const LatestPage = lazyImport(() => import("@/pages/latest"))
65
65
  const PreviewPage = lazyImport(() => import("@/pages/preview"))
66
66
  const QuickstartPage = lazyImport(() => import("@/pages/quickstart"))
67
67
  const SearchPage = lazyImport(() => import("@/pages/search"))
@@ -112,7 +112,7 @@ function App() {
112
112
  <Route path="/quickstart" component={QuickstartPage} />
113
113
  <Route path="/dashboard" component={DashboardPage} />
114
114
  <Route path="/ai" component={AiPage} />
115
- <Route path="/newest" component={NewestPage} />
115
+ <Route path="/latest" component={LatestPage} />
116
116
  <Route path="/settings" component={SettingsPage} />
117
117
  <Route path="/search" component={SearchPage} />
118
118
  <Route path="/trending" component={TrendingPage} />
@@ -38,7 +38,7 @@ import { useMutation, useQueryClient } from "react-query"
38
38
  import { Link, useLocation } from "wouter"
39
39
  import { useAxios } from "../hooks/use-axios"
40
40
  import { useToast } from "../hooks/use-toast"
41
- import { useConfirmDeleteSnippetDialog } from "./dialogs/confirm-delete-snippet-dialog"
41
+ import { useConfirmDeletePackageDialog } from "./dialogs/confirm-delete-package-dialog"
42
42
  import { useCreateOrderDialog } from "./dialogs/create-order-dialog"
43
43
  import { useFilesDialog } from "./dialogs/files-dialog"
44
44
  import { useViewTsFilesDialog } from "./dialogs/view-ts-files-dialog"
@@ -83,7 +83,7 @@ export default function EditorNav({
83
83
  openDialog: openupdateDescriptionDialog,
84
84
  } = useUpdateDescriptionDialog()
85
85
  const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
86
- useConfirmDeleteSnippetDialog()
86
+ useConfirmDeletePackageDialog()
87
87
  const { Dialog: CreateOrderDialog, openDialog: openCreateOrderDialog } =
88
88
  useCreateOrderDialog()
89
89
  const { Dialog: FilesDialog, openDialog: openFilesDialog } = useFilesDialog()
@@ -479,8 +479,8 @@ export default function EditorNav({
479
479
  currentName={snippet?.unscoped_name ?? ""}
480
480
  />
481
481
  <DeleteDialog
482
- snippetId={snippet?.snippet_id ?? ""}
483
- snippetName={snippet?.unscoped_name ?? ""}
482
+ packageId={snippet?.snippet_id ?? ""}
483
+ packageName={snippet?.unscoped_name ?? ""}
484
484
  />
485
485
  <CreateOrderDialog />
486
486
  <FilesDialog snippetId={snippet?.snippet_id ?? ""} />
@@ -26,7 +26,6 @@ export default function Footer() {
26
26
  { name: "Home", href: "/" },
27
27
  { name: "Dashboard", href: "/dashboard" },
28
28
  { name: "Editor", href: "/editor" },
29
- { name: "Create with AI", href: "https://chat.tscircuit.com" },
30
29
  {
31
30
  name: "My Profile",
32
31
  href: `/${session?.github_username}`,
@@ -44,14 +43,21 @@ export default function Footer() {
44
43
  {item.name}
45
44
  </PrefetchPageLink>
46
45
  ))}
46
+ <a
47
+ href="https://chat.tscircuit.com"
48
+ className="hover:underline"
49
+ target="_blank"
50
+ >
51
+ Create with AI
52
+ </a>
47
53
  </footer>
48
54
  </div>
49
55
 
50
56
  <div className="space-y-4">
51
57
  <h3 className="font-semibold uppercase">Explore</h3>
52
58
  <footer className="flex flex-col space-y-2">
53
- <PrefetchPageLink href="/newest" className="hover:underline">
54
- Newest Snippets
59
+ <PrefetchPageLink href="/latest" className="hover:underline">
60
+ Latest Snippets
55
61
  </PrefetchPageLink>
56
62
  <PrefetchPageLink href="/trending" className="hover:underline">
57
63
  Trending Snippets
@@ -0,0 +1,44 @@
1
+ "use client"
2
+
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu"
9
+ import { Settings, Check } from "lucide-react"
10
+
11
+ interface HiddenFilesDropdownProps {
12
+ showHiddenFiles: boolean
13
+ onToggleHiddenFiles: () => void
14
+ }
15
+
16
+ export default function HiddenFilesDropdown({
17
+ showHiddenFiles,
18
+ onToggleHiddenFiles,
19
+ }: HiddenFilesDropdownProps) {
20
+ return (
21
+ <DropdownMenu>
22
+ <DropdownMenuTrigger asChild>
23
+ <button className="ml-2 text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 focus:outline-none">
24
+ <Settings className="h-4 w-4" />
25
+ </button>
26
+ </DropdownMenuTrigger>
27
+ <DropdownMenuContent
28
+ align="end"
29
+ className="bg-white dark:bg-[#161b22] border border-gray-200 dark:border-[#30363d] rounded-md shadow-lg"
30
+ >
31
+ <DropdownMenuItem
32
+ onSelect={(e) => e.preventDefault()}
33
+ onClick={onToggleHiddenFiles}
34
+ className="text-xs text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-[#21262d] flex items-center gap-2"
35
+ >
36
+ <Check
37
+ className={`h-4 w-4 transition-opacity ${showHiddenFiles ? "opacity-100" : "opacity-0"}`}
38
+ />
39
+ Show Hidden Files
40
+ </DropdownMenuItem>
41
+ </DropdownMenuContent>
42
+ </DropdownMenu>
43
+ )
44
+ }