@tscircuit/fake-snippets 0.0.101 → 0.0.103
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/api/generated-index.js +23 -1
- package/bun.lock +2 -2
- package/dist/bundle.js +530 -367
- package/dist/index.d.ts +29 -2
- package/dist/index.js +18 -1
- package/dist/schema.d.ts +94 -1
- package/dist/schema.js +17 -1
- package/fake-snippets-api/lib/db/db-client.ts +6 -1
- package/fake-snippets-api/lib/db/schema.ts +15 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package.ts +2 -0
- package/fake-snippets-api/routes/api/github/installations/create_new_installation_redirect.ts +75 -0
- package/fake-snippets-api/routes/api/github/repos/list_available.ts +91 -0
- package/fake-snippets-api/routes/api/packages/update.ts +6 -0
- package/package.json +2 -2
- package/src/App.tsx +10 -1
- package/src/components/CreateReleaseDialog.tsx +124 -0
- package/src/components/FileSidebar.tsx +128 -23
- package/src/components/PackageBuildsPage/package-build-header.tsx +9 -1
- package/src/components/PageSearchComponent.tsx +2 -2
- package/src/components/SearchComponent.tsx +2 -2
- package/src/components/SuspenseRunFrame.tsx +2 -2
- package/src/components/TrendingPackagesCarousel.tsx +2 -2
- package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
- package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +13 -1
- package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +17 -0
- package/src/components/dialogs/GitHubRepositorySelector.tsx +183 -0
- package/src/components/dialogs/create-use-dialog.tsx +8 -2
- package/src/components/dialogs/edit-package-details-dialog.tsx +32 -3
- package/src/components/dialogs/view-ts-files-dialog.tsx +178 -33
- package/src/components/package-port/CodeAndPreview.tsx +4 -1
- package/src/components/package-port/CodeEditor.tsx +42 -35
- package/src/components/package-port/CodeEditorHeader.tsx +6 -4
- package/src/components/package-port/EditorNav.tsx +94 -37
- package/src/components/preview/BuildsList.tsx +241 -0
- package/src/components/preview/ConnectedPackagesList.tsx +187 -0
- package/src/components/preview/ConnectedRepoDashboard.tsx +243 -0
- package/src/components/preview/ConnectedRepoOverview.tsx +454 -0
- package/src/components/preview/index.tsx +248 -0
- package/src/components/ui/tree-view.tsx +23 -6
- package/src/hooks/use-axios.ts +2 -2
- package/src/hooks/use-create-release-dialog.ts +160 -0
- package/src/hooks/use-package-details-form.ts +7 -0
- package/src/hooks/use-packages-base-api-url.ts +1 -1
- package/src/hooks/use-sign-in.ts +2 -2
- package/src/hooks/useFileManagement.ts +22 -2
- package/src/index.css +4 -0
- package/src/lib/utils/formatTimeAgo.ts +10 -0
- package/src/lib/utils/isValidFileName.ts +15 -3
- package/src/pages/dashboard.tsx +2 -2
- package/src/pages/dev-login.tsx +2 -2
- package/src/pages/latest.tsx +2 -2
- package/src/pages/preview-build.tsx +380 -0
- package/src/pages/search.tsx +2 -2
- package/src/pages/trending.tsx +2 -2
- package/src/pages/user-profile.tsx +40 -24
- package/src/pages/view-connected-repo.tsx +18 -0
|
@@ -40,6 +40,8 @@ type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
40
40
|
expandAll?: boolean
|
|
41
41
|
defaultNodeIcon?: any
|
|
42
42
|
defaultLeafIcon?: any
|
|
43
|
+
selectedItemId: string
|
|
44
|
+
setSelectedItemId: (id: string | undefined) => void
|
|
43
45
|
onDocumentDrag?: (sourceItem: TreeDataItem, targetItem: TreeDataItem) => void
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -54,14 +56,12 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
|
|
|
54
56
|
defaultNodeIcon,
|
|
55
57
|
className,
|
|
56
58
|
onDocumentDrag,
|
|
59
|
+
selectedItemId,
|
|
60
|
+
setSelectedItemId,
|
|
57
61
|
...props
|
|
58
62
|
},
|
|
59
63
|
ref,
|
|
60
64
|
) => {
|
|
61
|
-
const [selectedItemId, setSelectedItemId] = React.useState<
|
|
62
|
-
string | undefined
|
|
63
|
-
>(initialSelectedItemId)
|
|
64
|
-
|
|
65
65
|
React.useEffect(() => {
|
|
66
66
|
setSelectedItemId(initialSelectedItemId)
|
|
67
67
|
}, [initialSelectedItemId])
|
|
@@ -129,6 +129,7 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
|
|
|
129
129
|
<TreeItem
|
|
130
130
|
data={data}
|
|
131
131
|
ref={ref}
|
|
132
|
+
setSelectedItemId={setSelectedItemId}
|
|
132
133
|
selectedItemId={selectedItemId}
|
|
133
134
|
handleSelectChange={handleSelectChange}
|
|
134
135
|
expandedItemIds={expandedItemIds}
|
|
@@ -159,6 +160,7 @@ type TreeItemProps = TreeProps & {
|
|
|
159
160
|
defaultLeafIcon?: any
|
|
160
161
|
handleDragStart?: (item: TreeDataItem) => void
|
|
161
162
|
handleDrop?: (item: TreeDataItem) => void
|
|
163
|
+
setSelectedItemId: (id: string | undefined) => void
|
|
162
164
|
draggedItem: TreeDataItem | null
|
|
163
165
|
}
|
|
164
166
|
|
|
@@ -169,6 +171,7 @@ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
|
|
169
171
|
data,
|
|
170
172
|
selectedItemId,
|
|
171
173
|
handleSelectChange,
|
|
174
|
+
setSelectedItemId,
|
|
172
175
|
expandedItemIds,
|
|
173
176
|
defaultNodeIcon,
|
|
174
177
|
defaultLeafIcon,
|
|
@@ -182,14 +185,25 @@ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
|
|
182
185
|
if (!(data instanceof Array)) {
|
|
183
186
|
data = [data]
|
|
184
187
|
}
|
|
188
|
+
|
|
189
|
+
const sortedData = [...data].sort((a, b) => {
|
|
190
|
+
const aIsFolder = !!a.children
|
|
191
|
+
const bIsFolder = !!b.children
|
|
192
|
+
|
|
193
|
+
if (aIsFolder && !bIsFolder) return -1
|
|
194
|
+
if (!aIsFolder && bIsFolder) return 1
|
|
195
|
+
return 0
|
|
196
|
+
})
|
|
197
|
+
|
|
185
198
|
return (
|
|
186
199
|
<div ref={ref} role="tree" className={className} {...props}>
|
|
187
200
|
<ul>
|
|
188
|
-
{
|
|
201
|
+
{sortedData.map((item) => (
|
|
189
202
|
<li key={item.id}>
|
|
190
203
|
{item.children ? (
|
|
191
204
|
<TreeNode
|
|
192
205
|
item={item}
|
|
206
|
+
setSelectedItemId={setSelectedItemId}
|
|
193
207
|
selectedItemId={selectedItemId}
|
|
194
208
|
expandedItemIds={expandedItemIds}
|
|
195
209
|
handleSelectChange={handleSelectChange}
|
|
@@ -227,13 +241,15 @@ const TreeNode = ({
|
|
|
227
241
|
defaultNodeIcon,
|
|
228
242
|
defaultLeafIcon,
|
|
229
243
|
handleDragStart,
|
|
244
|
+
setSelectedItemId,
|
|
230
245
|
handleDrop,
|
|
231
246
|
draggedItem,
|
|
232
247
|
}: {
|
|
233
248
|
item: TreeDataItem
|
|
234
249
|
handleSelectChange: (item: TreeDataItem | undefined) => void
|
|
235
250
|
expandedItemIds: string[]
|
|
236
|
-
selectedItemId
|
|
251
|
+
selectedItemId: string
|
|
252
|
+
setSelectedItemId: (id: string | undefined) => void
|
|
237
253
|
defaultNodeIcon?: any
|
|
238
254
|
defaultLeafIcon?: any
|
|
239
255
|
handleDragStart?: (item: TreeDataItem) => void
|
|
@@ -309,6 +325,7 @@ const TreeNode = ({
|
|
|
309
325
|
<TreeItem
|
|
310
326
|
data={item.children ? item.children : item}
|
|
311
327
|
selectedItemId={selectedItemId}
|
|
328
|
+
setSelectedItemId={setSelectedItemId}
|
|
312
329
|
handleSelectChange={handleSelectChange}
|
|
313
330
|
expandedItemIds={expandedItemIds}
|
|
314
331
|
defaultLeafIcon={defaultLeafIcon}
|
package/src/hooks/use-axios.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import axios from "redaxios"
|
|
2
2
|
import { useMemo } from "react"
|
|
3
3
|
import { useGlobalStore } from "./use-global-store"
|
|
4
|
-
import {
|
|
4
|
+
import { useApiBaseUrl } from "./use-packages-base-api-url"
|
|
5
5
|
|
|
6
6
|
export const useAxios = () => {
|
|
7
|
-
const snippetsBaseApiUrl =
|
|
7
|
+
const snippetsBaseApiUrl = useApiBaseUrl()
|
|
8
8
|
const session = useGlobalStore((s) => s.session)
|
|
9
9
|
return useMemo(() => {
|
|
10
10
|
const instance = axios.create({
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { useCreatePackageReleaseMutation } from "./use-create-package-release-mutation"
|
|
3
|
+
import { useGlobalStore } from "./use-global-store"
|
|
4
|
+
import { useUpdatePackageFilesMutation } from "./useUpdatePackageFilesMutation"
|
|
5
|
+
import type { PackageFile } from "@/types/package"
|
|
6
|
+
import type { Package } from "fake-snippets-api/lib/db/schema"
|
|
7
|
+
import { useToast } from "./use-toast"
|
|
8
|
+
|
|
9
|
+
interface UseCreateReleaseDialogProps {
|
|
10
|
+
packageId?: string
|
|
11
|
+
packageName?: string
|
|
12
|
+
currentVersion?: string
|
|
13
|
+
onSuccess?: (release: any) => void
|
|
14
|
+
files?: PackageFile[]
|
|
15
|
+
currentPackage?: Package
|
|
16
|
+
packageFilesMeta?: {
|
|
17
|
+
created_at: string
|
|
18
|
+
file_path: string
|
|
19
|
+
package_file_id: string
|
|
20
|
+
package_release_id: string
|
|
21
|
+
}[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const useCreateReleaseDialog = ({
|
|
25
|
+
packageId,
|
|
26
|
+
packageName,
|
|
27
|
+
currentVersion,
|
|
28
|
+
onSuccess,
|
|
29
|
+
files = [],
|
|
30
|
+
currentPackage,
|
|
31
|
+
packageFilesMeta = [],
|
|
32
|
+
}: UseCreateReleaseDialogProps) => {
|
|
33
|
+
const { toast } = useToast()
|
|
34
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
35
|
+
const [version, setVersion] = useState("")
|
|
36
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
37
|
+
const [error, setError] = useState<string | null>(null)
|
|
38
|
+
|
|
39
|
+
const suggestedNextVersion = currentVersion
|
|
40
|
+
? (() => {
|
|
41
|
+
const parts = currentVersion.split(".")
|
|
42
|
+
if (parts.length === 3) {
|
|
43
|
+
const [major, minor, patch] = parts.map(Number)
|
|
44
|
+
if (!isNaN(major) && !isNaN(minor) && !isNaN(patch)) {
|
|
45
|
+
return `${major}.${minor}.${patch + 1}`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined
|
|
49
|
+
})()
|
|
50
|
+
: undefined
|
|
51
|
+
|
|
52
|
+
const { mutateAsync: createRelease } = useCreatePackageReleaseMutation({
|
|
53
|
+
onSuccess: () => {
|
|
54
|
+
toast({
|
|
55
|
+
title: "Package release created",
|
|
56
|
+
description: "Your package release has been created successfully.",
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
const session = useGlobalStore((s) => s.session)
|
|
61
|
+
|
|
62
|
+
const updatePackageFilesMutation = useUpdatePackageFilesMutation({
|
|
63
|
+
currentPackage,
|
|
64
|
+
localFiles: files,
|
|
65
|
+
initialFiles: [],
|
|
66
|
+
packageFilesMeta,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const open = () => {
|
|
70
|
+
setIsOpen(true)
|
|
71
|
+
setError(null)
|
|
72
|
+
// Auto-fill with suggested next version
|
|
73
|
+
const nextVersion = suggestedNextVersion || "0.0.1"
|
|
74
|
+
setVersion(nextVersion)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const close = () => {
|
|
78
|
+
setIsOpen(false)
|
|
79
|
+
setError(null)
|
|
80
|
+
setVersion("")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const handleCreateRelease = async () => {
|
|
84
|
+
if (!version.trim()) {
|
|
85
|
+
setError("Version is required")
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!packageId && !packageName) {
|
|
90
|
+
setError("Package information is missing")
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!session) {
|
|
95
|
+
setError("You must be logged in to create a release")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setIsLoading(true)
|
|
100
|
+
setError(null)
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const releaseData: any = {
|
|
104
|
+
version: version.trim(),
|
|
105
|
+
is_latest: true,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (packageId) {
|
|
109
|
+
releaseData.package_id = packageId
|
|
110
|
+
} else if (packageName) {
|
|
111
|
+
releaseData.package_name = packageName
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const result = await createRelease(releaseData)
|
|
115
|
+
|
|
116
|
+
if (files.length > 0 && currentPackage) {
|
|
117
|
+
try {
|
|
118
|
+
await updatePackageFilesMutation.mutateAsync({
|
|
119
|
+
package_name_with_version: `${currentPackage.name}@${version.trim()}`,
|
|
120
|
+
...currentPackage,
|
|
121
|
+
})
|
|
122
|
+
} catch (fileError: any) {
|
|
123
|
+
console.error("Error uploading files:", fileError)
|
|
124
|
+
toast({
|
|
125
|
+
title: "Error",
|
|
126
|
+
description: "Failed to upload some files to the release",
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
onSuccess?.(result)
|
|
132
|
+
close()
|
|
133
|
+
} catch (err: any) {
|
|
134
|
+
const errorMessage =
|
|
135
|
+
err?.response?.data?.error?.message ||
|
|
136
|
+
err?.message ||
|
|
137
|
+
"Failed to create release"
|
|
138
|
+
setError(errorMessage)
|
|
139
|
+
toast({
|
|
140
|
+
title: "Error",
|
|
141
|
+
description: errorMessage,
|
|
142
|
+
})
|
|
143
|
+
} finally {
|
|
144
|
+
setIsLoading(false)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
isOpen,
|
|
150
|
+
openDialog: open,
|
|
151
|
+
closeDialog: close,
|
|
152
|
+
version,
|
|
153
|
+
setVersion,
|
|
154
|
+
currentVersion,
|
|
155
|
+
suggestedNextVersion,
|
|
156
|
+
isLoading: isLoading || updatePackageFilesMutation.isLoading,
|
|
157
|
+
error,
|
|
158
|
+
handleCreateRelease,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -16,6 +16,7 @@ interface PackageDetailsForm {
|
|
|
16
16
|
license: string | null
|
|
17
17
|
visibility: string
|
|
18
18
|
defaultView: string
|
|
19
|
+
githubRepoFullName: string | null
|
|
19
20
|
unscopedPackageName: string
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -26,6 +27,7 @@ interface UsePackageDetailsFormProps {
|
|
|
26
27
|
initialVisibility: string
|
|
27
28
|
initialDefaultView: string
|
|
28
29
|
initialUnscopedPackageName: string
|
|
30
|
+
initialGithubRepoFullName: string | null
|
|
29
31
|
isDialogOpen: boolean
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -36,6 +38,7 @@ export const usePackageDetailsForm = ({
|
|
|
36
38
|
initialVisibility,
|
|
37
39
|
initialDefaultView,
|
|
38
40
|
initialUnscopedPackageName,
|
|
41
|
+
initialGithubRepoFullName,
|
|
39
42
|
isDialogOpen,
|
|
40
43
|
}: UsePackageDetailsFormProps) => {
|
|
41
44
|
const [formData, setFormData] = useState<PackageDetailsForm>({
|
|
@@ -44,6 +47,7 @@ export const usePackageDetailsForm = ({
|
|
|
44
47
|
license: initialLicense || null,
|
|
45
48
|
visibility: initialVisibility,
|
|
46
49
|
defaultView: initialDefaultView,
|
|
50
|
+
githubRepoFullName: initialGithubRepoFullName,
|
|
47
51
|
unscopedPackageName: initialUnscopedPackageName,
|
|
48
52
|
})
|
|
49
53
|
const [websiteError, setWebsiteError] = useState<string | null>(null)
|
|
@@ -55,6 +59,7 @@ export const usePackageDetailsForm = ({
|
|
|
55
59
|
website: initialWebsite,
|
|
56
60
|
license: initialLicense || null,
|
|
57
61
|
visibility: initialVisibility,
|
|
62
|
+
githubRepoFullName: initialGithubRepoFullName,
|
|
58
63
|
defaultView: initialDefaultView,
|
|
59
64
|
unscopedPackageName: initialUnscopedPackageName,
|
|
60
65
|
})
|
|
@@ -100,6 +105,7 @@ export const usePackageDetailsForm = ({
|
|
|
100
105
|
formData.license !== initialLicense ||
|
|
101
106
|
formData.visibility !== initialVisibility ||
|
|
102
107
|
formData.defaultView !== initialDefaultView ||
|
|
108
|
+
formData.githubRepoFullName !== initialGithubRepoFullName ||
|
|
103
109
|
formData.unscopedPackageName !== initialUnscopedPackageName,
|
|
104
110
|
[
|
|
105
111
|
formData,
|
|
@@ -108,6 +114,7 @@ export const usePackageDetailsForm = ({
|
|
|
108
114
|
initialLicense,
|
|
109
115
|
initialVisibility,
|
|
110
116
|
initialDefaultView,
|
|
117
|
+
initialGithubRepoFullName,
|
|
111
118
|
initialUnscopedPackageName,
|
|
112
119
|
],
|
|
113
120
|
)
|
package/src/hooks/use-sign-in.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useGlobalStore } from "./use-global-store"
|
|
2
2
|
import { useIsUsingFakeApi } from "./use-is-using-fake-api"
|
|
3
|
-
import {
|
|
3
|
+
import { useApiBaseUrl } from "./use-packages-base-api-url"
|
|
4
4
|
|
|
5
5
|
export const useSignIn = () => {
|
|
6
|
-
const snippetsBaseApiUrl =
|
|
6
|
+
const snippetsBaseApiUrl = useApiBaseUrl()
|
|
7
7
|
const isUsingFakeApi = useIsUsingFakeApi()
|
|
8
8
|
const setSession = useGlobalStore((s) => s.setSession)
|
|
9
9
|
return () => {
|
|
@@ -196,34 +196,53 @@ export function useFileManagement({
|
|
|
196
196
|
openFile = true,
|
|
197
197
|
}: ICreateFileProps): ICreateFileResult => {
|
|
198
198
|
newFileName = newFileName.trim()
|
|
199
|
+
|
|
199
200
|
if (!newFileName) {
|
|
200
201
|
onError(new Error("File name cannot be empty"))
|
|
201
202
|
return {
|
|
202
203
|
newFileCreated: false,
|
|
203
204
|
}
|
|
204
205
|
}
|
|
206
|
+
|
|
205
207
|
if (!isValidFileName(newFileName)) {
|
|
206
|
-
onError(
|
|
208
|
+
onError(
|
|
209
|
+
new Error(
|
|
210
|
+
"Invalid file name. Avoid special characters and relative paths (. or ..)",
|
|
211
|
+
),
|
|
212
|
+
)
|
|
207
213
|
return {
|
|
208
214
|
newFileCreated: false,
|
|
209
215
|
}
|
|
210
216
|
}
|
|
211
217
|
|
|
218
|
+
// Check if file already exists
|
|
212
219
|
const fileExists = localFiles?.some((file) => file.path === newFileName)
|
|
213
220
|
if (fileExists) {
|
|
214
|
-
onError(new Error(
|
|
221
|
+
onError(new Error(`File '${newFileName}' already exists`))
|
|
215
222
|
return {
|
|
216
223
|
newFileCreated: false,
|
|
217
224
|
}
|
|
218
225
|
}
|
|
226
|
+
|
|
227
|
+
// Ensure file name is not empty after path construction
|
|
228
|
+
const fileName = newFileName.split("/").pop() || ""
|
|
229
|
+
if (!fileName.trim()) {
|
|
230
|
+
onError(new Error("File name cannot be empty"))
|
|
231
|
+
return {
|
|
232
|
+
newFileCreated: false,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
219
236
|
const updatedFiles = [
|
|
220
237
|
...(localFiles || []),
|
|
221
238
|
{ path: newFileName, content: content || "" },
|
|
222
239
|
]
|
|
223
240
|
setLocalFiles(updatedFiles)
|
|
241
|
+
|
|
224
242
|
if (openFile) {
|
|
225
243
|
setCurrentFile(newFileName)
|
|
226
244
|
}
|
|
245
|
+
|
|
227
246
|
return {
|
|
228
247
|
newFileCreated: true,
|
|
229
248
|
}
|
|
@@ -428,5 +447,6 @@ export function useFileManagement({
|
|
|
428
447
|
isLoading,
|
|
429
448
|
isSaving,
|
|
430
449
|
savePackage,
|
|
450
|
+
packageFilesMeta,
|
|
431
451
|
}
|
|
432
452
|
}
|
package/src/index.css
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const formatTimeAgo = (dateString: string) => {
|
|
2
|
+
const date = new Date(dateString)
|
|
3
|
+
const now = new Date()
|
|
4
|
+
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
|
|
5
|
+
|
|
6
|
+
if (diffInSeconds < 60) return "just now"
|
|
7
|
+
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`
|
|
8
|
+
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`
|
|
9
|
+
return `${Math.floor(diffInSeconds / 86400)}d ago`
|
|
10
|
+
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export const isValidFileName = (name: string) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const invalidChars = /[<>:"\\|?*]/
|
|
3
|
+
|
|
4
|
+
if (name.length === 0) return false
|
|
5
|
+
if (invalidChars.test(name)) return false
|
|
6
|
+
|
|
7
|
+
const pathParts = name.split("/")
|
|
8
|
+
|
|
9
|
+
for (const part of pathParts) {
|
|
10
|
+
if (part === "") continue
|
|
11
|
+
if (part === "." || part === "..") return false
|
|
12
|
+
if (part.startsWith(" ") || part.endsWith(" ")) return false
|
|
13
|
+
if (part.includes("\\")) return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return true
|
|
5
17
|
}
|
package/src/pages/dashboard.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import { PrefetchPageLink } from "@/components/PrefetchPageLink"
|
|
|
12
12
|
import { PackagesList } from "@/components/PackagesList"
|
|
13
13
|
import { Helmet } from "react-helmet-async"
|
|
14
14
|
import { useSignIn } from "@/hooks/use-sign-in"
|
|
15
|
-
import {
|
|
15
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
16
16
|
import { useConfirmDeletePackageDialog } from "@/components/dialogs/confirm-delete-package-dialog"
|
|
17
17
|
import { PackageCardSkeleton } from "@/components/PackageCardSkeleton"
|
|
18
18
|
import { PackageCard } from "@/components/PackageCard"
|
|
@@ -85,7 +85,7 @@ export const DashboardPage = () => {
|
|
|
85
85
|
},
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
const baseUrl =
|
|
88
|
+
const baseUrl = useApiBaseUrl()
|
|
89
89
|
|
|
90
90
|
const handleDeleteClick = (e: React.MouseEvent, pkg: Package) => {
|
|
91
91
|
e.preventDefault() // Prevent navigation
|
package/src/pages/dev-login.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useGlobalStore } from "@/hooks/use-global-store"
|
|
2
|
-
import {
|
|
2
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
3
3
|
import { useState } from "react"
|
|
4
4
|
import { useLocation } from "wouter"
|
|
5
5
|
|
|
6
6
|
export const DevLoginPage = () => {
|
|
7
|
-
const snippetsBaseApiUrl =
|
|
7
|
+
const snippetsBaseApiUrl = useApiBaseUrl()
|
|
8
8
|
const [username, setUsername] = useState("")
|
|
9
9
|
const setSession = useGlobalStore((s) => s.setSession)
|
|
10
10
|
const [, setLocation] = useLocation()
|
package/src/pages/latest.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import Header from "@/components/Header"
|
|
|
6
6
|
import Footer from "@/components/Footer"
|
|
7
7
|
import { Search, Keyboard, Cpu, Layers, LucideBellElectric } from "lucide-react"
|
|
8
8
|
import { Input } from "@/components/ui/input"
|
|
9
|
-
import {
|
|
9
|
+
import { useApiBaseUrl } from "@/hooks/use-packages-base-api-url"
|
|
10
10
|
import {
|
|
11
11
|
Select,
|
|
12
12
|
SelectContent,
|
|
@@ -18,7 +18,7 @@ import PackageSearchResults from "@/components/PackageSearchResults"
|
|
|
18
18
|
|
|
19
19
|
const LatestPage: React.FC = () => {
|
|
20
20
|
const axios = useAxios()
|
|
21
|
-
const apiBaseUrl =
|
|
21
|
+
const apiBaseUrl = useApiBaseUrl()
|
|
22
22
|
const [searchQuery, setSearchQuery] = useState("")
|
|
23
23
|
const [category, setCategory] = useState("all")
|
|
24
24
|
|