Merge pull request #42 from Since-3/feature/storage-bucket

added supabase storage bucket
This commit is contained in:
Rajpreet0 2026-02-02 18:39:10 +01:00 • committed by GitHub
commit 7f222baf5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 188 additions and 0 deletions

106
src/hooks/useImageUpload.ts Normal file
View file

@ -0,0 +1,106 @@
"use client";
import { useState } from "react";
import {
uploadFile,
deleteFile,
generateFilePath,
type StorageBucket,
} from "@/src/lib/supabase/storage";
interface UseImageUploadOptions {
bucket: StorageBucket;
maxSizeMB?: number;
allowedTypes?: string[];
folder?: string;
}
interface UseImageUploadReturn {
upload: (file: File, userId: string) => Promise<string>;
remove: (path: string) => Promise<void>;
isUploading: boolean;
error: string | null;
progress: number;
}
const DEFAULT_MAX_SIZE_MB = 5;
const DEFAULT_ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp", "image/gif"];
export function useImageUpload({
bucket,
maxSizeMB = DEFAULT_MAX_SIZE_MB,
allowedTypes = DEFAULT_ALLOWED_TYPES,
folder,
}: UseImageUploadOptions): UseImageUploadReturn {
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [progress, setProgress] = useState(0);
const validateFile = (file: File): string | null => {
if (!allowedTypes.includes(file.type)) {
return `Ungültiger Dateityp. Erlaubt: ${allowedTypes.join(", ")}`;
}
const maxSizeBytes = maxSizeMB * 1024 * 1024;
if (file.size > maxSizeBytes) {
return `Datei zu groß. Maximum: ${maxSizeMB}MB`;
}
return null;
};
const upload = async (file: File, userId: string): Promise<string> => {
setError(null);
setProgress(0);
const validationError = validateFile(file);
if (validationError) {
setError(validationError);
throw new Error(validationError);
}
setIsUploading(true);
setProgress(10);
try {
const path = generateFilePath(userId, file.name, folder);
setProgress(30);
const result = await uploadFile({
bucket,
path,
file,
upsert: true,
});
setProgress(100);
return result.publicUrl;
} catch (err) {
const message = err instanceof Error ? err.message : "Upload fehlgeschlagen";
setError(message);
throw err;
} finally {
setIsUploading(false);
}
};
const remove = async (path: string): Promise<void> => {
setError(null);
try {
await deleteFile(bucket, path);
} catch (err) {
const message = err instanceof Error ? err.message : "Löschen fehlgeschlagen";
setError(message);
throw err;
}
};
return {
upload,
remove,
isUploading,
error,
progress,
};
}

View file

@ -0,0 +1,82 @@
import { createClient } from "./client";
export type StorageBucket = "avatars" | "course-images" | "documents";
interface UploadOptions {
bucket: StorageBucket;
path: string;
file: File;
upsert?: boolean;
}
interface UploadResult {
path: string;
publicUrl: string;
}
/**
* Uploads a file to Supabase Storage
*/
export async function uploadFile({
bucket,
path,
file,
upsert = true,
}: UploadOptions): Promise<UploadResult> {
const supabase = createClient();
const { data, error } = await supabase.storage.from(bucket).upload(path, file, {
upsert,
cacheControl: "3600",
});
if (error) {
throw new Error(`Upload failed: ${error.message}`);
}
const {
data: { publicUrl },
} = supabase.storage.from(bucket).getPublicUrl(data.path);
return {
path: data.path,
publicUrl,
};
}
/**
* Deletes a file from Supabase Storage
*/
export async function deleteFile(bucket: StorageBucket, path: string): Promise<void> {
const supabase = createClient();
const { error } = await supabase.storage.from(bucket).remove([path]);
if (error) {
throw new Error(`Delete failed: ${error.message}`);
}
}
/**
* Gets the public URL for a file
*/
export function getPublicUrl(bucket: StorageBucket, path: string): string {
const supabase = createClient();
const {
data: { publicUrl },
} = supabase.storage.from(bucket).getPublicUrl(path);
return publicUrl;
}
/**
* Generates a unique file path with timestamp
*/
export function generateFilePath(userId: string, fileName: string, folder?: string): string {
const timestamp = Date.now();
const extension = fileName.split(".").pop();
const basePath = folder ? `${folder}/${userId}` : userId;
return `${basePath}/${timestamp}.${extension}`;
}