Ealanisln / ready-set

On-demand courier that specializes in delivery for all your needs. We are Food Safety, and HIPPA Certified. Our team can meet all your Bay Area delivery needs.
https://readysetllc.com
MIT License
0 stars 0 forks source link

Feature/34 upload files for orders #41

Closed Ealanisln closed 1 week ago

Ealanisln commented 1 week ago

Add File Upload Functionality using UploadThing API

Overview

This PR implements file upload functionality using UploadThing API, allowing users to attach files to catering orders and on-demand orders in the platform.

Changes Made

1. Schema Updates

Updated Prisma schema to support file attachments:

model file_upload {
    id         String   @id @default(cuid())
    fileName   String
    fileType   String
    fileSize   Int
    fileUrl    String
    entityType String
    entityId   String
    category   String?
    uploadedAt DateTime @default(now())
    updatedAt  DateTime @updatedAt

    userId String?
    user   user?   @relation(fields: [userId], references: [id], onDelete: SetNull)

    cateringRequestId BigInt?
    onDemandId        BigInt?

    cateringRequest catering_request? @relation(fields: [cateringRequestId], references: [id], onDelete: Cascade)
    onDemand        on_demand?        @relation(fields: [onDemandId], references: [id], onDelete: Cascade)

    @@index([entityType, entityId])
}

2. New API Routes

Upload Route Configuration (app/api/uploadthing/core.ts):

import { createUploadthing, type FileRouter } from "uploadthing/next";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";

const f = createUploadthing();

export const ourFileRouter = {
  orderAttachment: f({
    image: { maxFileSize: "4MB", maxFileCount: 4 },
    pdf: { maxFileSize: "8MB", maxFileCount: 2 },
  })
    .middleware(async ({ req }) => {
      const session = await getServerSession(authOptions);
      if (!session) throw new Error("Unauthorized");
      return { userId: session.user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      return { uploadedBy: metadata.userId, fileUrl: file.url };
    }),
} satisfies FileRouter;

File Association Route (app/api/files/route.ts):

import { prisma } from "@/lib/prisma";

export async function POST(req: Request) {
  const { 
    fileName, 
    fileType, 
    fileSize, 
    fileUrl, 
    entityType, 
    entityId,
    userId,
    category 
  } = await req.json();

  const file = await prisma.file_upload.create({
    data: {
      fileName,
      fileType,
      fileSize,
      fileUrl,
      entityType,
      entityId,
      userId,
      category,
      cateringRequestId: entityType === 'catering_request' ? entityId : null,
      onDemandId: entityType === 'on_demand' ? entityId : null,
    },
  });

  return Response.json(file);
}

3. Frontend Components

File Upload Component (components/FileUpload.tsx):

import { useUploadThing } from "@/lib/uploadthing";
import { useState } from "react";
import { FileIcon, X } from "lucide-react";

interface FileUploadProps {
  entityType: 'catering_request' | 'on_demand';
  entityId: string;
  onUploadComplete?: (files: any[]) => void;
}

export function FileUpload({ entityType, entityId, onUploadComplete }: FileUploadProps) {
  const [files, setFiles] = useState<File[]>([]);
  const { startUpload, isUploading } = useUploadThing("orderAttachment");

  const handleUpload = async (files: File[]) => {
    const uploadedFiles = await startUpload(files);

    if (uploadedFiles) {
      // Associate files with the entity
      const promises = uploadedFiles.map(file => 
        fetch('/api/files', {
          method: 'POST',
          body: JSON.stringify({
            fileName: file.name,
            fileType: file.type,
            fileSize: file.size,
            fileUrl: file.url,
            entityType,
            entityId,
          }),
        })
      );

      await Promise.all(promises);
      onUploadComplete?.(uploadedFiles);
    }
  };

  return (
    <div className="w-full">
      {/* Upload UI implementation */}
    </div>
  );
}

4. Integration in Order Forms

Usage in Catering Request Form:

<FileUpload 
  entityType="catering_request"
  entityId={cateringRequest.id}
  onUploadComplete={(files) => {
    // Handle upload completion
  }}
/>

Testing Steps

  1. Create a new catering or on-demand order
  2. Attach files using the upload component
  3. Verify files are stored and associated with the order
  4. Test file size limits and type restrictions
  5. Verify error handling for invalid uploads
  6. Test file deletion functionality

Dependencies Added

{
  "dependencies": {
    "uploadthing": "^5.0.0",
    "@uploadthing/react": "^5.0.0"
  }
}

Environment Variables Added

UPLOADTHING_SECRET=your_secret_here
UPLOADTHING_APP_ID=your_app_id_here

Notes

vercel[bot] commented 1 week ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
ready-set ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 29, 2024 3:23pm