Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add "accept" field to file input elements #109

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions components/EditCompany.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@

import { SLUG_START, slugComputer } from "@/app/companies/slug"
import { updateCompany } from "@/lib/crud/companies"
import { ServerSideFormHandler } from "@/lib/types"
import { FileCategory, ServerSideFormHandler } from "@/lib/types"

import FileInput from "./FileInput"
import { FormInModal } from "./forms/FormInModal"
import { GenericFormModal } from "./modals/GenericFormModal"

import { MDXEditorMethods } from "@mdxeditor/editor"
import { CompanyProfile } from "@prisma/client"
import { InfoCircledIcon } from "@radix-ui/react-icons"
import { Pencil1Icon } from "@radix-ui/react-icons"
import { Tooltip } from "@radix-ui/themes"
import { Card, Flex, IconButton, Text, TextField } from "@radix-ui/themes"
import { InfoCircledIcon, Pencil1Icon } from "@radix-ui/react-icons"
import { Card, Flex, IconButton, Text, TextField, Tooltip } from "@radix-ui/themes"
import dynamic from "next/dynamic"
import React, { useRef, useState } from "react"

Expand Down Expand Up @@ -70,9 +68,9 @@ const EditCompanyForm = ({ close, prevCompanyProfile }: { close: () => void; pre
<input type="hidden" readOnly name="summary" value={summary ?? ""} />
</label>

<FileInput name="banner" header="Banner" value={prevCompanyProfile.banner} />
<FileInput name="banner" header="Banner" value={prevCompanyProfile.banner} category={FileCategory.IMAGE} />

<FileInput name="logo" header="Logo" value={prevCompanyProfile.logo} />
<FileInput name="logo" header="Logo" value={prevCompanyProfile.logo} category={FileCategory.IMAGE} />

<label>
<Text as="div" size="2" mb="1" weight="bold">
Expand Down
11 changes: 8 additions & 3 deletions components/EditStudent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { DangerZone } from "@/components/DeleteStudent"
import DialogTOS from "@/components/DialogTOS"
import { updateStudent } from "@/lib/crud/students"
import { ServerSideFormHandler } from "@/lib/types"
import { FileCategory, ServerSideFormHandler } from "@/lib/types"

import Chip from "./Chip"
import FileInput from "./FileInput"
Expand Down Expand Up @@ -125,8 +125,13 @@ const EditStudentForm = ({
</Flex>
</label>

<FileInput name="cv" header="CV" value={prevStudentProfile.cv} />
<FileInput name="avatar" header="Profile Picture" value={prevStudentProfile.user.image} />
<FileInput name="cv" header="CV" value={prevStudentProfile.cv} category={FileCategory.DOCUMENT} />
<FileInput
name="avatar"
header="Profile Picture"
value={prevStudentProfile.user.image}
category={FileCategory.IMAGE}
/>

<label>
<Text as="div" size="2" mb="1" weight="bold">
Expand Down
23 changes: 22 additions & 1 deletion components/FileInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { FileViewer } from "@/components/FileViewer"
import { allowedDocumentTypes, allowedImageTypes } from "@/lib/constants"
import { FileCategory } from "@/lib/types"

import { Button, Flex, Text } from "@radix-ui/themes"
import { useState } from "react"
Expand All @@ -9,11 +11,29 @@ import { BsCloudUploadFill, BsSearch } from "react-icons/bs"
* @param name - The name of the file input for the form.
* @param header - The header of the file input to show on the page.
* @param value - The path to the current file, if any.
* @param category - The category of the file - image or document.
*/
const FileInput = ({ name, header, value }: { name: string; header: string; value?: string | null }) => {
const FileInput = ({
name,
header,
value,
category,
}: {
name: string
header: string
value?: string | null
category: FileCategory | "ANY"
}) => {
const [filePath, setFilePath] = useState(value ? `/api/uploads/${value}` : "")
const [file, setFile] = useState<File | null>(null)

const allowedFileTypes =
category === "ANY"
? "*"
: category === FileCategory.IMAGE
? allowedImageTypes.join(",")
: allowedDocumentTypes.join(",")

return (
<>
<Text as="div" size="2" weight="bold">
Expand All @@ -25,6 +45,7 @@ const FileInput = ({ name, header, value }: { name: string; header: string; valu
<input
type="file"
hidden
accept={allowedFileTypes}
name={name}
onChange={e => {
const file = e.target.files?.[0]
Expand Down
2 changes: 1 addition & 1 deletion components/UpsertEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const UpsertEventForm = ({
/>
</label>

<FileInput name="attachment" header="Attachment" value={prevEvent?.attachment} />
<FileInput name="attachment" header="Attachment" value={prevEvent?.attachment} category="ANY" />

<label>
<Text as="div" size="2" mb="1" weight="bold">
Expand Down
4 changes: 4 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ export const TIMEZONE = "Europe/London"

// The maximum file size in bytes
export const MAX_FILE_SIZE = 10 * 1_000_000 - 1 //10MB

// Must be the same as the allowed file types in the file viewer
export const allowedImageTypes = ["image/png", "image/jpeg", "image/gif", "image/svg+xml"]
export const allowedDocumentTypes = ["application/pdf", "text/plain"]
16 changes: 7 additions & 9 deletions lib/files/saveFile.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { MAX_FILE_SIZE } from "@/lib/constants"
"use server"

import { MAX_FILE_SIZE, allowedDocumentTypes, allowedImageTypes } from "@/lib/constants"
import { FileCategory } from "@/lib/types"

import { promises as fs } from "fs"
import { join } from "path"
import path from "path"

// Must be the same as the allowed file types in the file viewer
const allowedImageTypes = ["image/png", "image/jpeg", "image/gif", "image/svg+xml"]
const allowedDocumentTypes = ["application/pdf", "text/plain"]

/**
* Save a file to the file system
* @param filePath The path in the volume to save the file
Expand All @@ -24,7 +22,7 @@ export const saveFile = async (filePath: string, file: File, fileType: FileCateg

// Validate the file and path
validateFile(file, fileType)
if (!validateFilePath(filePath)) {
if (!(await validateFilePath(filePath))) {
throw new Error("Invalid path", {
cause: "The path provided is invalid",
})
Expand All @@ -38,7 +36,7 @@ export const saveFile = async (filePath: string, file: File, fileType: FileCateg
* @param filePath The path to validate
* @returns Whether the path is safe
*/
export const validateFilePath = (filePath: string): boolean => {
export const validateFilePath = async (filePath: string): Promise<boolean> => {
// Normalize the path to move all the ../ to the front of the path
// Apply regex to remove any ../ from the front of the path
const safePath = path.normalize(filePath).replace(/^(\.\.(\/|\\|$))+/, "")
Expand Down Expand Up @@ -84,11 +82,11 @@ const validateFile = (file: File, fileType: FileCategory) => {
* Can be used to check if a file has been provided
* @param file The file to check
*/
export const isFileNotEmpty = (file: File) => file.size > 0
export const isFileNotEmpty = async (file: File) => file.size > 0

/**
* Get the file extension of a file
* @param file The file to get the extension of
* @example getFileExtension(<some png image>) => "png"
*/
export const getFileExtension = (file: File) => file.type.split("/")[1].split("+")[0]
export const getFileExtension = async (file: File) => file.type.split("/")[1].split("+")[0]
4 changes: 2 additions & 2 deletions lib/files/updateUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export const updateUpload = async (
getOldPath: () => Promise<string | null | undefined>,
setNewPath: (path: string) => Promise<any>,
): Promise<FormPassBackState> => {
if (isFileNotEmpty(file)) {
const filePath = `${folder}/${randomBytes(16).toString("hex")}.${getFileExtension(file)}`
if (await isFileNotEmpty(file)) {
const filePath = `${folder}/${randomBytes(16).toString("hex")}.${await getFileExtension(file)}`

// Save the new file to the file system
try {
Expand Down