PriceLogicPriceLogic

The AI Platform for Marketplace Sellers

© Copyright 2025 PriceLogic, Inc. All Rights Reserved.

About
  • Blog
  • Contact
Product
  • Pricing
Legal
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Getting started with PriceLogic
    • Quick Start
    • Project Structure
    • Configuration
  • Diana API
  • Email & Password
  • Database
    • Database Overview
    • Migrations
    • Row Level Security
    • Querying Data
    • Functions & Triggers
  • OAuth
  • Features
    • Features Overview
    • Team Collaboration
    • File Uploads
  • Magic Links
  • Billing & Payments
    • Billing Overview
    • Pricing Plans
    • Webhook Integration

File Uploads

Handle file uploads with Supabase Storage.

Note: This is mock/placeholder content for demonstration purposes.

Enable users to upload and manage files using Supabase Storage.

Setup

Create Storage Bucket

-- Create a public bucket for avatars
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);

-- Create a private bucket for documents
INSERT INTO storage.buckets (id, name, public)
VALUES ('documents', 'documents', false);

Set Storage Policies

-- Allow users to upload their own avatars
CREATE POLICY "Users can upload their own avatar"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars' AND
  auth.uid()::text = (storage.foldername(name))[1]
);

-- Allow users to view their own avatars
CREATE POLICY "Users can view their own avatar"
ON storage.objects FOR SELECT
USING (
  bucket_id = 'avatars' AND
  auth.uid()::text = (storage.foldername(name))[1]
);

-- Allow users to delete their own avatars
CREATE POLICY "Users can delete their own avatar"
ON storage.objects FOR DELETE
USING (
  bucket_id = 'avatars' AND
  auth.uid()::text = (storage.foldername(name))[1]
);

Upload Component

Basic File Upload

'use client';

import { useState } from 'react';
import { uploadFileAction } from '../_lib/actions';

export function FileUpload() {
  const [uploading, setUploading] = useState(false);
  const [file, setFile] = useState<File | null>(null);

  const handleUpload = async () => {
    if (!file) return;

    setUploading(true);

    const formData = new FormData();
    formData.append('file', file);

    const result = await uploadFileAction(formData);

    if (result.success) {
      toast.success('File uploaded successfully');
    }

    setUploading(false);
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => setFile(e.files?.[0] || null)}
        accept="image/*"
      />
      <button
        onClick={handleUpload}
        disabled={!file || uploading}
      >
        {uploading ? 'Uploading...' : 'Upload'}
      </button>
    </div>
  );
}

Server Action

'use server';

import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerClient } from '@kit/supabase/server-client';

export const uploadFileAction = enhanceAction(
  async (formData: FormData, user) => {
    const file = formData.get('file') as File;

    if (!file) {
      throw new Error('No file provided');
    }

    const client = getSupabaseServerClient();
    const fileExt = file.name.split('.').pop();
    const fileName = `${user.id}/${Date.now()}.${fileExt}`;

    const { data, error } = await client.storage
      .from('avatars')
      .upload(fileName, file, {
        cacheControl: '3600',
        upsert: false,
      });

    if (error) throw error;

    // Get public URL
    const { data: { publicUrl } } = client.storage
      .from('avatars')
      .getPublicUrl(fileName);

    return {
      success: true,
      url: publicUrl,
      path: data.path,
    };
  },
  { auth: true }
);

Drag and Drop Upload

'use client';

import { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';

export function DragDropUpload() {
  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    for (const file of acceptedFiles) {
      const formData = new FormData();
      formData.append('file', file);
      await uploadFileAction(formData);
    }
  }, []);

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop,
    accept: {
      'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
    },
    maxSize: 5 * 1024 * 1024, // 5MB
  });

  return (
    <div
      {...getRootProps()}
      className={cn(
        'border-2 border-dashed rounded-lg p-8 text-center cursor-pointer',
        isDragActive && 'border-primary bg-primary/10'
      )}
    >
      <input {...getInputProps()} />
      {isDragActive ? (
        <p>Drop files here...</p>
      ) : (
        <p>Drag and drop files here, or click to select</p>
      )}
    </div>
  );
}

File Validation

Client-Side Validation

function validateFile(file: File) {
  const maxSize = 5 * 1024 * 1024; // 5MB
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];

  if (file.size > maxSize) {
    throw new Error('File size must be less than 5MB');
  }

  if (!allowedTypes.includes(file.type)) {
    throw new Error('File type must be JPEG, PNG, or GIF');
  }

  return true;
}

Server-Side Validation

export const uploadFileAction = enhanceAction(
  async (formData: FormData, user) => {
    const file = formData.get('file') as File;

    // Validate file size
    if (file.size > 5 * 1024 * 1024) {
      throw new Error('File too large');
    }

    // Validate file type
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedTypes.includes(file.type)) {
      throw new Error('Invalid file type');
    }

    // Validate dimensions for images
    if (file.type.startsWith('image/')) {
      const dimensions = await getImageDimensions(file);
      if (dimensions.width > 4000 || dimensions.height > 4000) {
        throw new Error('Image dimensions too large');
      }
    }

    // Continue with upload...
  },
  { auth: true }
);

Image Optimization

Resize on Upload

import sharp from 'sharp';

export const uploadAvatarAction = enhanceAction(
  async (formData: FormData, user) => {
    const file = formData.get('file') as File;
    const buffer = Buffer.from(await file.arrayBuffer());

    // Resize image
    const resized = await sharp(buffer)
      .resize(200, 200, {
        fit: 'cover',
        position: 'center',
      })
      .jpeg({ quality: 90 })
      .toBuffer();

    const client = getSupabaseServerClient();
    const fileName = `${user.id}/avatar.jpg`;

    const { error } = await client.storage
      .from('avatars')
      .upload(fileName, resized, {
        contentType: 'image/jpeg',
        upsert: true,
      });

    if (error) throw error;

    return { success: true };
  },
  { auth: true }
);

Progress Tracking

'use client';

import { useState } from 'react';

export function UploadWithProgress() {
  const [progress, setProgress] = useState(0);

  const handleUpload = async (file: File) => {
    const client = getSupabaseBrowserClient();

    const { error } = await client.storage
      .from('documents')
      .upload(`uploads/${file.name}`, file, {
        onUploadProgress: (progressEvent) => {
          const percent = (progressEvent.loaded / progressEvent.total) * 100;
          setProgress(Math.round(percent));
        },
      });

    if (error) throw error;
  };

  return (
    <div>
      <input type="file" onChange={(e) => handleUpload(e.target.files![0])} />
      {progress > 0 && (
        <div className="w-full bg-gray-200 rounded-full h-2">
          <div
            className="bg-primary h-2 rounded-full transition-all"
            style={{ width: `${progress}%` }}
          />
        </div>
      )}
    </div>
  );
}

Downloading Files

Get Public URL

const { data } = client.storage
  .from('avatars')
  .getPublicUrl('user-id/avatar.jpg');

console.log(data.publicUrl);

Download Private File

const { data, error } = await client.storage
  .from('documents')
  .download('private-file.pdf');

if (data) {
  const url = URL.createObjectURL(data);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'file.pdf';
  a.click();
}

Generate Signed URL

const { data, error } = await client.storage
  .from('documents')
  .createSignedUrl('private-file.pdf', 3600); // 1 hour

console.log(data.signedUrl);

Deleting Files

export const deleteFileAction = enhanceAction(
  async (data, user) => {
    const client = getSupabaseServerClient();

    const { error } = await client.storage
      .from('avatars')
      .remove([data.path]);

    if (error) throw error;

    return { success: true };
  },
  {
    schema: z.object({
      path: z.string(),
    }),
    auth: true,
  }
);

Best Practices

  1. Validate on both sides - Client and server
  2. Limit file sizes - Prevent abuse
  3. Sanitize filenames - Remove special characters
  4. Use unique names - Prevent collisions
  5. Optimize images - Resize before upload
  6. Set storage policies - Control access
  7. Monitor usage - Track storage costs
  8. Clean up unused files - Regular maintenance
  9. Use CDN - For public files
  10. Implement virus scanning - For user uploads
  1. Setup
    1. Create Storage Bucket
    2. Set Storage Policies
    3. Upload Component
    4. Drag and Drop Upload
    5. File Validation
    6. Image Optimization
    7. Progress Tracking
    8. Downloading Files
    9. Deleting Files
    10. Best Practices