Compare commits

...

3 Commits

Author SHA1 Message Date
c6fc2f6026 fix: file upload btn 2025-09-15 23:31:03 +00:00
8f82f8d5ad feat: tweak file table col sizes 2025-09-15 23:27:56 +00:00
9d62de2c99 feat: make file icon in table smaller 2025-09-15 23:04:38 +00:00
4 changed files with 71 additions and 58 deletions

View File

@@ -1,4 +1,6 @@
export function DirectoryIcon() { import { cn } from "@/lib/utils"
export function DirectoryIcon({ className }: { className?: string }) {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -6,7 +8,10 @@ export function DirectoryIcon() {
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
className="icon icon-tabler icons-tabler-filled icon-tabler-folder text-orange-300" className={cn(
"icon icon-tabler icons-tabler-filled icon-tabler-folder text-orange-300",
className,
)}
aria-label="Directory" aria-label="Directory"
> >
<title>Directory</title> <title>Directory</title>

View File

@@ -1,4 +1,6 @@
export function TextFileIcon() { import { cn } from "@/lib/utils"
export function TextFileIcon({ className }: { className?: string }) {
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -6,7 +8,10 @@ export function TextFileIcon() {
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="currentColor" fill="currentColor"
className="icon icon-tabler icons-tabler-filled icon-tabler-file-text text-blue-300" className={cn(
"icon icon-tabler icons-tabler-filled icon-tabler-file-text text-blue-300",
className,
)}
> >
<title>Text File</title> <title>Text File</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />

View File

@@ -72,6 +72,7 @@ const columns: ColumnDef<DirectoryItem>[] = [
), ),
enableSorting: false, enableSorting: false,
enableHiding: false, enableHiding: false,
size: 24,
}, },
{ {
header: "Name", header: "Name",
@@ -82,13 +83,14 @@ const columns: ColumnDef<DirectoryItem>[] = [
return <FileNameCell initialName={row.original.doc.name} /> return <FileNameCell initialName={row.original.doc.name} />
case "directory": case "directory":
return ( return (
<div className="flex items-center gap-2"> <div className="flex w-full items-center gap-2">
<DirectoryIcon /> <DirectoryIcon className="size-4" />
{row.original.doc.name} {row.original.doc.name}
</div> </div>
) )
} }
}, },
size: 1000,
}, },
{ {
header: "Size", header: "Size",
@@ -217,7 +219,10 @@ export function FileTableContent() {
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}> <TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => ( {headerGroup.headers.map((header) => (
<TableHead key={header.id}> <TableHead
key={header.id}
style={{ width: header.getSize() }}
>
{header.isPlaceholder {header.isPlaceholder
? null ? null
: flexRender( : flexRender(
@@ -240,7 +245,10 @@ export function FileTableContent() {
}} }}
> >
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}> <TableCell
key={cell.id}
style={{ width: cell.column.getSize() }}
>
{flexRender( {flexRender(
cell.column.columnDef.cell, cell.column.columnDef.cell,
cell.getContext(), cell.getContext(),
@@ -250,14 +258,7 @@ export function FileTableContent() {
</TableRow> </TableRow>
)) ))
) : ( ) : (
<TableRow> <NoResultsRow />
<TableCell
colSpan={columns.length}
className="text-center"
>
No results.
</TableCell>
</TableRow>
)} )}
<NewItemRow /> <NewItemRow />
</TableBody> </TableBody>
@@ -266,6 +267,20 @@ export function FileTableContent() {
) )
} }
function NoResultsRow() {
const newItemKind = useAtomValue(newItemKindAtom)
if (newItemKind) {
return null
}
return (
<TableRow>
<TableCell colSpan={columns.length} className="text-center">
No results.
</TableCell>
</TableRow>
)
}
function NewItemRow() { function NewItemRow() {
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const newItemFormId = useId() const newItemFormId = useId()
@@ -363,8 +378,8 @@ function NewItemRow() {
function FileNameCell({ initialName }: { initialName: string }) { function FileNameCell({ initialName }: { initialName: string }) {
return ( return (
<div className="flex items-center gap-2"> <div className="flex w-full items-center gap-2">
<TextFileIcon /> <TextFileIcon className="size-4" />
{initialName} {initialName}
</div> </div>
) )

View File

@@ -1,8 +1,6 @@
import { api } from "@convex/_generated/api" import { api } from "@convex/_generated/api"
import { import { useMutation } from "@tanstack/react-query"
useMutation as useConvexMutation, import { useMutation as useConvexMutation } from "convex/react"
useQuery as useConvexQuery,
} from "convex/react"
import { useSetAtom } from "jotai" import { useSetAtom } from "jotai"
import { import {
ChevronDownIcon, ChevronDownIcon,
@@ -10,7 +8,7 @@ import {
PlusIcon, PlusIcon,
UploadCloudIcon, UploadCloudIcon,
} from "lucide-react" } from "lucide-react"
import { type ChangeEvent, useRef, useState } from "react" import { type ChangeEvent, useRef } from "react"
import { toast } from "sonner" import { toast } from "sonner"
import { import {
DropdownMenu, DropdownMenu,
@@ -57,10 +55,32 @@ export function FilesPage() {
// tags: upload, uploadfile, uploadfilebutton, fileupload, fileuploadbutton // tags: upload, uploadfile, uploadfilebutton, fileupload, fileuploadbutton
function UploadFileButton() { function UploadFileButton() {
const [isUploading, setIsUploading] = useState(false)
const currentUser = useConvexQuery(api.users.getCurrentUser)
const generateUploadUrl = useConvexMutation(api.files.generateUploadUrl) const generateUploadUrl = useConvexMutation(api.files.generateUploadUrl)
const saveFile = useConvexMutation(api.files.saveFile) const saveFile = useConvexMutation(api.files.saveFile)
const { mutate: uploadFile, isPending: isUploading } = useMutation({
mutationFn: async (file: File) => {
const uploadUrl = await generateUploadUrl()
const uploadResult = await fetch(uploadUrl, {
method: "POST",
body: file,
headers: {
"Content-Type": file.type,
},
})
const { storageId } = await uploadResult.json()
await saveFile({
storageId,
name: file.name,
size: file.size,
mimeType: file.type,
})
},
onSuccess: () => {
toast.success("File uploaded successfully.")
},
})
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
const handleClick = () => { const handleClick = () => {
@@ -68,41 +88,9 @@ function UploadFileButton() {
} }
const onFileUpload = async (e: ChangeEvent<HTMLInputElement>) => { const onFileUpload = async (e: ChangeEvent<HTMLInputElement>) => {
if (!currentUser?._id) {
return
}
const file = e.target.files?.[0] const file = e.target.files?.[0]
if (file) { if (file) {
try { uploadFile(file)
setIsUploading(true)
const uploadUrl = await generateUploadUrl()
const uploadResult = await fetch(uploadUrl, {
method: "POST",
body: file,
headers: {
"Content-Type": file.type,
},
})
const { storageId } = await uploadResult.json()
await saveFile({
storageId,
name: file.name,
size: file.size,
mimeType: file.type,
userId: currentUser._id,
})
toast.success("File uploaded successfully.", {
position: "top-center",
})
} catch (error) {
console.error(error)
} finally {
setIsUploading(false)
}
} }
} }