Skip to content

Commit

Permalink
checkpoint: form, api/post/create and dropzone ready. BD and PostView…
Browse files Browse the repository at this point in the history
… have to be adapted.
  • Loading branch information
PauMatas committed Mar 24, 2024
1 parent 46947f0 commit 4746e00
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 31 deletions.
30 changes: 15 additions & 15 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ model User {
username String? @unique
generacio Int @default(2020)
image String?
isAdmin Boolean @default(false)
isAdmin Boolean @default(false)
accounts Account[]
sessions Session[]
posts Post[]
Expand Down Expand Up @@ -97,21 +97,21 @@ model Question {
}

model Post {
id String @id @default(cuid())
title String
content String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subjectId String
authorId String
tipus TipusType
year Int
id String @id @default(cuid())
title String
content Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subjectId String
authorId String
tipus TipusType
year Int
NonUploaderAuthorEmail String?
isAnonymous Boolean @default(false)
subject Subject @relation(fields: [subjectId], references: [id])
author User @relation(fields: [authorId], references: [id])
comments Comment[]
votes PostVote[]
isAnonymous Boolean @default(false)
subject Subject @relation(fields: [subjectId], references: [id])
author User @relation(fields: [authorId], references: [id])
comments Comment[]
votes PostVote[]
@@index([title])
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/subject/post/create/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export async function POST(req: Request) {
await db.post.create({
data: {
title: title,
content: pdf,
content: JSON.stringify(pdf),
subjectId: subject.id,
authorId: authorId,
tipus: tipus as TipusType,
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/uploadthing/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export const ourFileRouter = {
.onUploadComplete(async ({}) => {}),

fileUploader: f({
pdf: { maxFileCount: 1, maxFileSize: "128MB" },
text: { maxFileCount: 5 },
pdf: { maxFileCount: 10, maxFileSize: "32MB" },
text: { maxFileCount: 10, maxFileSize: "32MB" },
})
.middleware(async ({ req }) => {
const user = await getToken({ req })
Expand Down
22 changes: 11 additions & 11 deletions src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import { Checkbox } from "@/components/ui/checkbox"
import { ApuntsPostCreationRequest } from "@/lib/validators/post"
import { uploadFiles } from "@/lib/uploadthing"
import Fireworks from "react-canvas-confetti/dist/presets/fireworks"
import { MultiFileDropzone } from "@/components/MultiFileDropzone"

const formSchema = z.object({
pdf: z.any(),
pdf: z.array(z.object({})).nonempty({
message: "Selecciona un fitxer",
}),
title: z.string({
required_error: "Selecciona un títol",
}),
Expand Down Expand Up @@ -124,11 +127,11 @@ export function ProfileForm({
}
}, [form, isAdmin])
async function onSubmit(data: ApuntsPostCreationRequest) {
const [res] = await uploadFiles("fileUploader", {
files: [data.pdf],
const res = await uploadFiles("fileUploader", {
files: data.pdf,
})
const payload: ApuntsPostCreationRequest = {
pdf: res.url,
pdf: res.map((file) => file.url),
title: data.title,
year: Number(data.year),
assignatura: data.assignatura,
Expand Down Expand Up @@ -235,13 +238,10 @@ export function ProfileForm({
<FormLabel>Fitxers PDF</FormLabel>
<FormControl>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Input
id="pdf-file"
type="file"
onChange={(e) => {
if (e.target.files) {
field.onChange(e.target.files[0])
}
<MultiFileDropzone
value={field.value}
onChange={(acceptedFiles) => {
field.onChange(acceptedFiles)
}}
/>
</div>
Expand Down
173 changes: 173 additions & 0 deletions src/components/MultiFileDropzone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"use client"

import { FileIcon, UploadCloudIcon } from "lucide-react"
import * as React from "react"
import { useCallback } from "react"
import { DropzoneOptions, useDropzone } from "react-dropzone"
import { twMerge } from "tailwind-merge"

const variants = {
base: "relative rounded-md p-4 w-96 flex justify-center items-center flex-col cursor-pointer border border-dashed border-zinc-300 transition-colors duration-200 ease-in-out bg-zinc-100",
active: "border-2",
disabled:
"bg-gray-700 border-white/20 cursor-default pointer-events-none bg-opacity-30",
accept: "border border-blue-500 bg-blue-500 bg-opacity-10",
reject: "border border-red-700 bg-red-700 bg-opacity-10",
}

type InputProps = {
className?: string
value?: File[]
onChange?: (acceptedFiles: File[]) => void | Promise<void>
onFilesAdded?: (addedFiles: File[]) => void | Promise<void>
disabled?: boolean
dropzoneOptions?: Omit<DropzoneOptions, "disabled">
}

const ERROR_MESSAGES = {
fileTooLarge(maxSize: number) {
return `The file is too large. Max size is ${formatFileSize(maxSize)}.`
},
fileInvalidType() {
return "Invalid file type."
},
tooManyFiles(maxFiles: number) {
return `You can only add ${maxFiles} file(s).`
},
fileNotSupported() {
return "The file is not supported."
},
}

const MultiFileDropzone = React.forwardRef<HTMLInputElement, InputProps>(
(
{ dropzoneOptions, value, className, disabled, onFilesAdded, onChange },
ref,
) => {
const onDrop = useCallback((acceptedFiles: File[]) => {
if (acceptedFiles) {
void onFilesAdded?.(acceptedFiles)
void onChange?.(acceptedFiles)
}
}, [])
if (dropzoneOptions?.maxFiles && value?.length) {
disabled = disabled ?? value.length >= dropzoneOptions.maxFiles
}
// dropzone configuration
const {
getRootProps,
getInputProps,
fileRejections,
isFocused,
isDragAccept,
isDragReject,
} = useDropzone({
disabled,
onDrop,
...dropzoneOptions,
})

// styling
const dropZoneClassName = React.useMemo(
() =>
twMerge(
variants.base,
isFocused && variants.active,
disabled && variants.disabled,
(isDragReject ?? fileRejections[0]) && variants.reject,
isDragAccept && variants.accept,
className,
).trim(),
[
isFocused,
fileRejections,
isDragAccept,
isDragReject,
disabled,
className,
],
)

// error validation messages
const errorMessage = React.useMemo(() => {
if (fileRejections[0]) {
const { errors } = fileRejections[0]
if (errors[0]?.code === "file-too-large") {
return ERROR_MESSAGES.fileTooLarge(dropzoneOptions?.maxSize ?? 0)
} else if (errors[0]?.code === "file-invalid-type") {
return ERROR_MESSAGES.fileInvalidType()
} else if (errors[0]?.code === "too-many-files") {
return ERROR_MESSAGES.tooManyFiles(dropzoneOptions?.maxFiles ?? 0)
} else {
return ERROR_MESSAGES.fileNotSupported()
}
}
return undefined
}, [fileRejections, dropzoneOptions])

return (
<div>
<div className="flex flex-col gap-2">
<div>
{/* Main File Input */}
<div
{...getRootProps({
className: dropZoneClassName,
})}
>
<input ref={ref} {...getInputProps()} />
<div className="flex flex-col items-center justify-center text-xs text-zinc-900">
<UploadCloudIcon className="mb-1 h-7 w-7" />
<div className="text-zinc-900">
drag & drop or click to upload
</div>
</div>
</div>

{/* Error Text */}
<div className="mt-1 text-xs text-red-500">{errorMessage}</div>
</div>

{/* Selected Files */}
{value?.map((file, i) => (
<div
key={i}
className="flex h-16 w-96 flex-col justify-center rounded border border-solid border-zinc-300 px-4 py-2"
>
<div className="flex items-center gap-2 text-zinc-900">
<FileIcon size="30" className="shrink-0" />
<div className="min-w-0 text-sm">
<div className="overflow-hidden overflow-ellipsis whitespace-nowrap">
{file.name}
</div>
<div className="text-xs text-zinc-900">
{formatFileSize(file.size)}
</div>
</div>
<div className="grow" />
</div>
</div>
))}
</div>
</div>
)
},
)
MultiFileDropzone.displayName = "MultiFileDropzone"

function formatFileSize(bytes?: number) {
if (!bytes) {
return "0 Bytes"
}
bytes = Number(bytes)
if (bytes === 0) {
return "0 Bytes"
}
const k = 1024
const dm = 2
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
}

export { MultiFileDropzone }
4 changes: 2 additions & 2 deletions src/lib/validators/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const PostValidator = z.object({
.min(3, { message: "Title must be at least 3 characters long" })
.max(128, { message: "Title must be at most 128 characters long" }),
subjectId: z.string(),
content: z.any(),
content: z.array(z.string()),
tipus: z.string(),
year: z.number(),
})
Expand All @@ -17,7 +17,7 @@ export const CommentValidator = z.object({
})

export const ApuntsPostValidator = z.object({
pdf: z.any(),
pdf: z.array(z.any()),
title: z.string(),
year: z.number(),
assignatura: z.string().min(2).max(5),
Expand Down
1 change: 1 addition & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default withAuth(function middleware(req) {}, {
req.nextUrl.pathname &&
req.nextUrl.pathname.startsWith("/") &&
!req.nextUrl.pathname.startsWith("/sign-in") &&
!req.nextUrl.pathname.startsWith("/api/uploadthing") &&
token === null
) {
return false
Expand Down

0 comments on commit 4746e00

Please sign in to comment.