Switching to tabs over spaces!

This commit is contained in:
2025-06-09 09:55:49 -05:00
parent cc225fae80
commit c2e816591d
71 changed files with 4353 additions and 4157 deletions

View File

@ -7,149 +7,149 @@ import type { User } from '@/utils/supabase';
import type { Result } from '.';
export const signUp = async (
formData: FormData,
formData: FormData,
): Promise<Result<string | null>> => {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = await createServerClient();
const origin = (await headers()).get('origin');
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = await createServerClient();
const origin = (await headers()).get('origin');
if (!email || !password) {
return { success: false, error: 'Email and password are required' };
}
if (!email || !password) {
return { success: false, error: 'Email and password are required' };
}
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
data: {
full_name: name,
email,
provider: 'email',
},
},
});
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
data: {
full_name: name,
email,
provider: 'email',
},
},
});
if (error) {
return { success: false, error: error.message };
} else {
return {
success: true,
data: 'Thanks for signing up! Please check your email for a verification link.',
};
}
if (error) {
return { success: false, error: error.message };
} else {
return {
success: true,
data: 'Thanks for signing up! Please check your email for a verification link.',
};
}
};
export const signIn = async (formData: FormData): Promise<Result<null>> => {
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = await createServerClient();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = await createServerClient();
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return { success: false, error: error.message };
} else {
return { success: true, data: null };
}
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return { success: false, error: error.message };
} else {
return { success: true, data: null };
}
};
export const signInWithMicrosoft = async (): Promise<Result<string>> => {
const supabase = await createServerClient();
const origin = (await headers()).get('origin');
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'openid, profile email offline_access',
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
const supabase = await createServerClient();
const origin = (await headers()).get('origin');
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'openid, profile email offline_access',
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
};
export const signInWithApple = async (): Promise<Result<string>> => {
const supabase = await createServerClient();
const origin = process.env.BASE_URL!;
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'apple',
options: {
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
const supabase = await createServerClient();
const origin = process.env.BASE_URL!;
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'apple',
options: {
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
};
export const forgotPassword = async (
formData: FormData,
formData: FormData,
): Promise<Result<string | null>> => {
const email = formData.get('email') as string;
const supabase = await createServerClient();
const origin = (await headers()).get('origin');
const email = formData.get('email') as string;
const supabase = await createServerClient();
const origin = (await headers()).get('origin');
if (!email) {
return { success: false, error: 'Email is required' };
}
if (!email) {
return { success: false, error: 'Email is required' };
}
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${origin}/auth/callback?redirect_to=/reset-password`,
});
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${origin}/auth/callback?redirect_to=/reset-password`,
});
if (error) {
return { success: false, error: 'Could not reset password' };
}
return {
success: true,
data: 'Check your email for a link to reset your password.',
};
if (error) {
return { success: false, error: 'Could not reset password' };
}
return {
success: true,
data: 'Check your email for a link to reset your password.',
};
};
export const resetPassword = async (
formData: FormData,
formData: FormData,
): Promise<Result<null>> => {
const password = formData.get('password') as string;
const confirmPassword = formData.get('confirmPassword') as string;
if (!password || !confirmPassword) {
return {
success: false,
error: 'Password and confirm password are required!',
};
}
const supabase = await createServerClient();
if (password !== confirmPassword) {
return { success: false, error: 'Passwords do not match!' };
}
const { error } = await supabase.auth.updateUser({
password,
});
if (error) {
return {
success: false,
error: `Password update failed: ${error.message}`,
};
}
return { success: true, data: null };
const password = formData.get('password') as string;
const confirmPassword = formData.get('confirmPassword') as string;
if (!password || !confirmPassword) {
return {
success: false,
error: 'Password and confirm password are required!',
};
}
const supabase = await createServerClient();
if (password !== confirmPassword) {
return { success: false, error: 'Passwords do not match!' };
}
const { error } = await supabase.auth.updateUser({
password,
});
if (error) {
return {
success: false,
error: `Password update failed: ${error.message}`,
};
}
return { success: true, data: null };
};
export const signOut = async (): Promise<Result<null>> => {
const supabase = await createServerClient();
const { error } = await supabase.auth.signOut();
if (error) return { success: false, error: error.message };
return { success: true, data: null };
const supabase = await createServerClient();
const { error } = await supabase.auth.signOut();
if (error) return { success: false, error: error.message };
return { success: true, data: null };
};
export const getUser = async (): Promise<Result<User>> => {
try {
const supabase = await createServerClient();
const { data, error } = await supabase.auth.getUser();
if (error) throw error;
return { success: true, data: data.user };
} catch {
return { success: false, error: `Could not get user!` };
}
try {
const supabase = await createServerClient();
const { data, error } = await supabase.auth.getUser();
if (error) throw error;
return { success: true, data: data.user };
} catch {
return { success: false, error: `Could not get user!` };
}
};

View File

@ -3,5 +3,5 @@ export * from './storage';
export * from './public';
export type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
| { success: true; data: T }
| { success: false; error: string };

View File

@ -6,75 +6,75 @@ import { getUser } from '@/lib/actions';
import type { Result } from '.';
export const getProfile = async (): Promise<Result<Profile>> => {
try {
const user = await getUser();
if (!user.success || user.data === undefined)
throw new Error('User not found');
const supabase = await createServerClient();
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user.data.id)
.single();
if (error) throw error;
return { success: true, data: data as Profile };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting profile',
};
}
try {
const user = await getUser();
if (!user.success || user.data === undefined)
throw new Error('User not found');
const supabase = await createServerClient();
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user.data.id)
.single();
if (error) throw error;
return { success: true, data: data as Profile };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting profile',
};
}
};
type updateProfileProps = {
full_name?: string;
email?: string;
avatar_url?: string;
full_name?: string;
email?: string;
avatar_url?: string;
};
export const updateProfile = async ({
full_name,
email,
avatar_url,
full_name,
email,
avatar_url,
}: updateProfileProps): Promise<Result<Profile>> => {
try {
if (
full_name === undefined &&
email === undefined &&
avatar_url === undefined
)
throw new Error('No profile data provided');
try {
if (
full_name === undefined &&
email === undefined &&
avatar_url === undefined
)
throw new Error('No profile data provided');
const userResponse = await getUser();
if (!userResponse.success || userResponse.data === undefined)
throw new Error('User not found');
const userResponse = await getUser();
if (!userResponse.success || userResponse.data === undefined)
throw new Error('User not found');
const supabase = await createServerClient();
const { data, error } = await supabase
.from('profiles')
.update({
...(full_name !== undefined && { full_name }),
...(email !== undefined && { email }),
...(avatar_url !== undefined && { avatar_url }),
})
.eq('id', userResponse.data.id)
.select()
.single();
if (error) throw error;
return {
success: true,
data: data as Profile,
};
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error updating profile',
};
}
const supabase = await createServerClient();
const { data, error } = await supabase
.from('profiles')
.update({
...(full_name !== undefined && { full_name }),
...(email !== undefined && { email }),
...(avatar_url !== undefined && { avatar_url }),
})
.eq('id', userResponse.data.id)
.select()
.single();
if (error) throw error;
return {
success: true,
data: data as Profile,
};
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error updating profile',
};
}
};

View File

@ -4,253 +4,261 @@ import { createServerClient } from '@/utils/supabase';
import type { Result } from '.';
export type GetStorageProps = {
bucket: string;
url: string;
seconds?: number;
transform?: {
width?: number;
height?: number;
quality?: number;
format?: 'origin';
resize?: 'cover' | 'contain' | 'fill';
};
download?: boolean | string;
bucket: string;
url: string;
seconds?: number;
transform?: {
width?: number;
height?: number;
quality?: number;
format?: 'origin';
resize?: 'cover' | 'contain' | 'fill';
};
download?: boolean | string;
};
export type UploadStorageProps = {
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
};
export type ReplaceStorageProps = {
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
};
export type resizeImageProps = {
file: File;
options?: {
maxWidth?: number;
maxHeight?: number;
quality?: number;
};
file: File;
options?: {
maxWidth?: number;
maxHeight?: number;
quality?: number;
};
};
export const getSignedUrl = async ({
bucket,
url,
seconds = 3600,
transform = {},
download = false,
bucket,
url,
seconds = 3600,
transform = {},
download = false,
}: GetStorageProps): Promise<Result<string>> => {
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.createSignedUrl(url, seconds, {
download,
transform,
});
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.createSignedUrl(url, seconds, {
download,
transform,
});
if (error) throw error;
if (!data?.signedUrl) throw new Error('No signed URL returned');
if (error) throw error;
if (!data?.signedUrl) throw new Error('No signed URL returned');
return { success: true, data: data.signedUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting signed URL',
};
}
return { success: true, data: data.signedUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting signed URL',
};
}
};
export const getPublicUrl = async ({
bucket,
url,
transform = {},
download = false,
bucket,
url,
transform = {},
download = false,
}: GetStorageProps): Promise<Result<string>> => {
try {
const supabase = await createServerClient();
const { data } = supabase.storage.from(bucket).getPublicUrl(url, {
download,
transform,
});
try {
const supabase = await createServerClient();
const { data } = supabase.storage.from(bucket).getPublicUrl(url, {
download,
transform,
});
if (!data?.publicUrl) throw new Error('No public URL returned');
if (!data?.publicUrl) throw new Error('No public URL returned');
return { success: true, data: data.publicUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting public URL',
};
}
return { success: true, data: data.publicUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting public URL',
};
}
};
export const uploadFile = async ({
bucket,
path,
file,
options = {},
bucket,
path,
file,
options = {},
}: UploadStorageProps): Promise<Result<string>> => {
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, options);
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, options);
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error uploading file',
};
}
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error uploading file',
};
}
};
export const replaceFile = async ({
bucket,
path,
file,
options = {},
bucket,
path,
file,
options = {},
}: ReplaceStorageProps): Promise<Result<string>> => {
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.update(path, file, { ...options, upsert: true });
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error replacing file',
};
}
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.update(path, file, { ...options, upsert: true });
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error replacing file',
};
}
};
// Add a helper to delete files
export const deleteFile = async ({
bucket,
path,
bucket,
path,
}: {
bucket: string;
path: string[];
bucket: string;
path: string[];
}): Promise<Result<null>> => {
try {
const supabase = await createServerClient();
const { error } = await supabase.storage.from(bucket).remove(path);
try {
const supabase = await createServerClient();
const { error } = await supabase.storage.from(bucket).remove(path);
if (error) throw error;
if (error) throw error;
return { success: true, data: null };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error deleting file',
};
}
return { success: true, data: null };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error deleting file',
};
}
};
// Add a helper to list files in a bucket
export const listFiles = async ({
bucket,
path = '',
options = {},
bucket,
path = '',
options = {},
}: {
bucket: string;
path?: string;
options?: {
limit?: number;
offset?: number;
sortBy?: { column: string; order: 'asc' | 'desc' };
};
bucket: string;
path?: string;
options?: {
limit?: number;
offset?: number;
sortBy?: { column: string; order: 'asc' | 'desc' };
};
}): Promise<Result<Array<{ name: string; id: string; metadata: unknown }>>> => {
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.list(path, options);
try {
const supabase = await createServerClient();
const { data, error } = await supabase.storage
.from(bucket)
.list(path, options);
if (error) throw error;
if (!data) throw new Error('No data returned from list operation');
if (error) throw error;
if (!data) throw new Error('No data returned from list operation');
return { success: true, data };
} catch (error) {
console.error('Could not list files!', error);
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error listing files',
};
}
return { success: true, data };
} catch (error) {
console.error('Could not list files!', error);
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error listing files',
};
}
};
export const resizeImage = async ({
file,
options = {},
file,
options = {},
}: resizeImageProps): Promise<File> => {
const { maxWidth = 800, maxHeight = 800, quality = 0.8 } = options;
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
const img = new Image();
img.src = event.target?.result as string;
img.onload = () => {
let width = img.width;
let height = img.height;
if (width > height) {
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
} else if (height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
if (!blob) return;
const resizedFile = new File([blob], file.name, {
type: 'imgage/jpeg',
lastModified: Date.now(),
});
resolve(resizedFile);
},
'image/jpeg',
quality,
);
};
};
});
const { maxWidth = 800, maxHeight = 800, quality = 0.8 } = options;
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
const img = new Image();
img.src = event.target?.result as string;
img.onload = () => {
let width = img.width;
let height = img.height;
if (width > height) {
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
} else if (height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
if (!blob) return;
const resizedFile = new File([blob], file.name, {
type: 'imgage/jpeg',
lastModified: Date.now(),
});
resolve(resizedFile);
},
'image/jpeg',
quality,
);
};
};
});
};

View File

@ -4,145 +4,145 @@ import type { User } from '@/utils/supabase';
import type { Result } from '.';
export const signUp = async (
formData: FormData,
formData: FormData,
): Promise<Result<string | null>> => {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = createClient();
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = createClient();
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
if (!email || !password) {
return { success: false, error: 'Email and password are required' };
}
if (!email || !password) {
return { success: false, error: 'Email and password are required' };
}
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
data: {
full_name: name,
email,
provider: 'email',
},
},
});
if (error) {
return { success: false, error: error.message };
} else {
return {
success: true,
data: 'Thanks for signing up! Please check your email for a verification link.',
};
}
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
data: {
full_name: name,
email,
provider: 'email',
},
},
});
if (error) {
return { success: false, error: error.message };
} else {
return {
success: true,
data: 'Thanks for signing up! Please check your email for a verification link.',
};
}
};
export const signIn = async (formData: FormData): Promise<Result<null>> => {
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = createClient();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = createClient();
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return { success: false, error: error.message };
} else {
return { success: true, data: null };
}
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return { success: false, error: error.message };
} else {
return { success: true, data: null };
}
};
export const signInWithMicrosoft = async (): Promise<Result<string>> => {
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'openid, profile email offline_access',
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'openid, profile email offline_access',
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
};
export const signInWithApple = async (): Promise<Result<string>> => {
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'apple',
options: {
scopes: 'openid, profile email offline_access',
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'apple',
options: {
scopes: 'openid, profile email offline_access',
},
});
if (error) return { success: false, error: error.message };
return { success: true, data: data.url };
};
export const forgotPassword = async (
formData: FormData,
formData: FormData,
): Promise<Result<string | null>> => {
const email = formData.get('email') as string;
const supabase = createClient();
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
const email = formData.get('email') as string;
const supabase = createClient();
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
if (!email) {
return { success: false, error: 'Email is required' };
}
if (!email) {
return { success: false, error: 'Email is required' };
}
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${origin}/auth/callback?redirect_to=/reset-password`,
});
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${origin}/auth/callback?redirect_to=/reset-password`,
});
if (error) {
return { success: false, error: 'Could not reset password' };
}
return {
success: true,
data: 'Check your email for a link to reset your password.',
};
if (error) {
return { success: false, error: 'Could not reset password' };
}
return {
success: true,
data: 'Check your email for a link to reset your password.',
};
};
export const resetPassword = async (
formData: FormData,
formData: FormData,
): Promise<Result<null>> => {
const password = formData.get('password') as string;
const confirmPassword = formData.get('confirmPassword') as string;
if (!password || !confirmPassword) {
return {
success: false,
error: 'Password and confirm password are required!',
};
}
const supabase = createClient();
if (password !== confirmPassword) {
return { success: false, error: 'Passwords do not match!' };
}
const { error } = await supabase.auth.updateUser({
password,
});
if (error) {
return {
success: false,
error: `Password update failed: ${error.message}`,
};
}
return { success: true, data: null };
const password = formData.get('password') as string;
const confirmPassword = formData.get('confirmPassword') as string;
if (!password || !confirmPassword) {
return {
success: false,
error: 'Password and confirm password are required!',
};
}
const supabase = createClient();
if (password !== confirmPassword) {
return { success: false, error: 'Passwords do not match!' };
}
const { error } = await supabase.auth.updateUser({
password,
});
if (error) {
return {
success: false,
error: `Password update failed: ${error.message}`,
};
}
return { success: true, data: null };
};
export const signOut = async (): Promise<Result<null>> => {
const supabase = createClient();
const { error } = await supabase.auth.signOut();
if (error) return { success: false, error: error.message };
return { success: true, data: null };
const supabase = createClient();
const { error } = await supabase.auth.signOut();
if (error) return { success: false, error: error.message };
return { success: true, data: null };
};
export const getUser = async (): Promise<Result<User>> => {
try {
const supabase = createClient();
const { data, error } = await supabase.auth.getUser();
if (error) throw error;
return { success: true, data: data.user };
} catch (error) {
return { success: false, error: 'Could not get user!' };
}
try {
const supabase = createClient();
const { data, error } = await supabase.auth.getUser();
if (error) throw error;
return { success: true, data: data.user };
} catch (error) {
return { success: false, error: 'Could not get user!' };
}
};

View File

@ -4,5 +4,5 @@ export * from './storage';
export * from './useFileUpload';
export type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
| { success: true; data: T }
| { success: false; error: string };

View File

@ -5,75 +5,75 @@ import { getUser } from '@/lib/hooks';
import type { Result } from '.';
export const getProfile = async (): Promise<Result<Profile>> => {
try {
const user = await getUser();
if (!user.success || user.data === undefined)
throw new Error('User not found');
const supabase = createClient();
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user.data.id)
.single();
if (error) throw error;
return { success: true, data: data as Profile };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting profile',
};
}
try {
const user = await getUser();
if (!user.success || user.data === undefined)
throw new Error('User not found');
const supabase = createClient();
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user.data.id)
.single();
if (error) throw error;
return { success: true, data: data as Profile };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting profile',
};
}
};
type updateProfileProps = {
full_name?: string;
email?: string;
avatar_url?: string;
full_name?: string;
email?: string;
avatar_url?: string;
};
export const updateProfile = async ({
full_name,
email,
avatar_url,
full_name,
email,
avatar_url,
}: updateProfileProps): Promise<Result<Profile>> => {
try {
if (
full_name === undefined &&
email === undefined &&
avatar_url === undefined
)
throw new Error('No profile data provided');
try {
if (
full_name === undefined &&
email === undefined &&
avatar_url === undefined
)
throw new Error('No profile data provided');
const userResponse = await getUser();
if (!userResponse.success || userResponse.data === undefined)
throw new Error('User not found');
const userResponse = await getUser();
if (!userResponse.success || userResponse.data === undefined)
throw new Error('User not found');
const supabase = createClient();
const { data, error } = await supabase
.from('profiles')
.update({
...(full_name !== undefined && { full_name }),
...(email !== undefined && { email }),
...(avatar_url !== undefined && { avatar_url }),
})
.eq('id', userResponse.data.id)
.select()
.single();
if (error) throw error;
return {
success: true,
data: data as Profile,
};
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error updating profile',
};
}
const supabase = createClient();
const { data, error } = await supabase
.from('profiles')
.update({
...(full_name !== undefined && { full_name }),
...(email !== undefined && { email }),
...(avatar_url !== undefined && { avatar_url }),
})
.eq('id', userResponse.data.id)
.select()
.single();
if (error) throw error;
return {
success: true,
data: data as Profile,
};
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error updating profile',
};
}
};

View File

@ -4,256 +4,264 @@ import { createClient } from '@/utils/supabase';
import type { Result } from '.';
export type GetStorageProps = {
bucket: string;
url: string;
seconds?: number;
transform?: {
width?: number;
height?: number;
quality?: number;
format?: 'origin';
resize?: 'cover' | 'contain' | 'fill';
};
download?: boolean | string;
bucket: string;
url: string;
seconds?: number;
transform?: {
width?: number;
height?: number;
quality?: number;
format?: 'origin';
resize?: 'cover' | 'contain' | 'fill';
};
download?: boolean | string;
};
export type UploadStorageProps = {
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
};
export type ReplaceStorageProps = {
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
bucket: string;
path: string;
file: File;
options?: {
cacheControl?: string;
contentType?: string;
};
};
export type resizeImageProps = {
file: File;
options?: {
maxWidth?: number;
maxHeight?: number;
quality?: number;
};
file: File;
options?: {
maxWidth?: number;
maxHeight?: number;
quality?: number;
};
};
export const getSignedUrl = async ({
bucket,
url,
seconds = 3600,
transform = {},
download = false,
bucket,
url,
seconds = 3600,
transform = {},
download = false,
}: GetStorageProps): Promise<Result<string>> => {
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.createSignedUrl(url, seconds, {
download,
transform,
});
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.createSignedUrl(url, seconds, {
download,
transform,
});
if (error) throw error;
if (!data?.signedUrl) throw new Error('No signed URL returned');
if (error) throw error;
if (!data?.signedUrl) throw new Error('No signed URL returned');
return { success: true, data: data.signedUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting signed URL',
};
}
return { success: true, data: data.signedUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting signed URL',
};
}
};
export const getPublicUrl = async ({
bucket,
url,
transform = {},
download = false,
bucket,
url,
transform = {},
download = false,
}: GetStorageProps): Promise<Result<string>> => {
try {
const supabase = createClient();
const { data } = supabase.storage.from(bucket).getPublicUrl(url, {
download,
transform,
});
try {
const supabase = createClient();
const { data } = supabase.storage.from(bucket).getPublicUrl(url, {
download,
transform,
});
if (!data?.publicUrl) throw new Error('No public URL returned');
if (!data?.publicUrl) throw new Error('No public URL returned');
return { success: true, data: data.publicUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting public URL',
};
}
return { success: true, data: data.publicUrl };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error getting public URL',
};
}
};
export const uploadFile = async ({
bucket,
path,
file,
options = {},
bucket,
path,
file,
options = {},
}: UploadStorageProps): Promise<Result<string>> => {
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, options);
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, options);
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error uploading file',
};
}
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error uploading file',
};
}
};
export const replaceFile = async ({
bucket,
path,
file,
options = {},
bucket,
path,
file,
options = {},
}: ReplaceStorageProps): Promise<Result<string>> => {
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.update(path, file, {
...options,
upsert: true,
});
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error replacing file',
};
}
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.update(path, file, {
...options,
upsert: true,
});
if (error) throw error;
if (!data?.path) throw new Error('No path returned from upload');
return { success: true, data: data.path };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error replacing file',
};
}
};
// Add a helper to delete files
export const deleteFile = async ({
bucket,
path,
bucket,
path,
}: {
bucket: string;
path: string[];
bucket: string;
path: string[];
}): Promise<Result<null>> => {
try {
const supabase = createClient();
const { error } = await supabase.storage.from(bucket).remove(path);
try {
const supabase = createClient();
const { error } = await supabase.storage.from(bucket).remove(path);
if (error) throw error;
if (error) throw error;
return { success: true, data: null };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error deleting file',
};
}
return { success: true, data: null };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error deleting file',
};
}
};
// Add a helper to list files in a bucket
export const listFiles = async ({
bucket,
path = '',
options = {},
bucket,
path = '',
options = {},
}: {
bucket: string;
path?: string;
options?: {
limit?: number;
offset?: number;
sortBy?: { column: string; order: 'asc' | 'desc' };
};
bucket: string;
path?: string;
options?: {
limit?: number;
offset?: number;
sortBy?: { column: string; order: 'asc' | 'desc' };
};
}): Promise<Result<Array<{ name: string; id: string; metadata: unknown }>>> => {
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.list(path, options);
try {
const supabase = createClient();
const { data, error } = await supabase.storage
.from(bucket)
.list(path, options);
if (error) throw error;
if (!data) throw new Error('No data returned from list operation');
if (error) throw error;
if (!data) throw new Error('No data returned from list operation');
return { success: true, data };
} catch (error) {
console.error('Could not list files!', error);
return {
success: false,
error:
error instanceof Error ? error.message : 'Unknown error listing files',
};
}
return { success: true, data };
} catch (error) {
console.error('Could not list files!', error);
return {
success: false,
error:
error instanceof Error
? error.message
: 'Unknown error listing files',
};
}
};
export const resizeImage = async ({
file,
options = {},
file,
options = {},
}: resizeImageProps): Promise<File> => {
const { maxWidth = 800, maxHeight = 800, quality = 0.8 } = options;
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
const img = new Image();
img.src = event.target?.result as string;
img.onload = () => {
let width = img.width;
let height = img.height;
if (width > height) {
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
} else if (height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
if (!blob) return;
const resizedFile = new File([blob], file.name, {
type: 'imgage/jpeg',
lastModified: Date.now(),
});
resolve(resizedFile);
},
'image/jpeg',
quality,
);
};
};
});
const { maxWidth = 800, maxHeight = 800, quality = 0.8 } = options;
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
const img = new Image();
img.src = event.target?.result as string;
img.onload = () => {
let width = img.width;
let height = img.height;
if (width > height) {
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
} else if (height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
if (!blob) return;
const resizedFile = new File([blob], file.name, {
type: 'imgage/jpeg',
lastModified: Date.now(),
});
resolve(resizedFile);
},
'image/jpeg',
quality,
);
};
};
});
};

View File

@ -10,96 +10,98 @@ import type { Result } from '.';
export type Replace = { replace: true; path: string } | false;
export type uploadToStorageProps = {
file: File;
bucket: string;
resize: boolean;
options?: {
maxWidth?: number;
maxHeight?: number;
quality?: number;
};
replace?: Replace;
file: File;
bucket: string;
resize: boolean;
options?: {
maxWidth?: number;
maxHeight?: number;
quality?: number;
};
replace?: Replace;
};
export const useFileUpload = () => {
const [isUploading, setIsUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const { profile, isAuthenticated } = useAuth();
const [isUploading, setIsUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const { profile, isAuthenticated } = useAuth();
const uploadToStorage = async ({
file,
bucket,
resize = false,
options = {},
replace = false,
}: uploadToStorageProps): Promise<Result<string>> => {
try {
if (!isAuthenticated) throw new Error('User is not authenticated');
const uploadToStorage = async ({
file,
bucket,
resize = false,
options = {},
replace = false,
}: uploadToStorageProps): Promise<Result<string>> => {
try {
if (!isAuthenticated) throw new Error('User is not authenticated');
setIsUploading(true);
if (replace) {
const updateResult = await replaceFile({
bucket,
path: replace.path,
file,
options: {
contentType: file.type,
},
});
if (!updateResult.success) {
return { success: false, error: updateResult.error };
} else {
return { success: true, data: updateResult.data };
}
}
setIsUploading(true);
if (replace) {
const updateResult = await replaceFile({
bucket,
path: replace.path,
file,
options: {
contentType: file.type,
},
});
if (!updateResult.success) {
return { success: false, error: updateResult.error };
} else {
return { success: true, data: updateResult.data };
}
}
let fileToUpload = file;
if (resize && file.type.startsWith('image/'))
fileToUpload = await resizeImage({ file, options });
let fileToUpload = file;
if (resize && file.type.startsWith('image/'))
fileToUpload = await resizeImage({ file, options });
// Generate a unique filename to avoid collisions
const fileExt = file.name.split('.').pop();
const fileName = `${Date.now()}-${profile?.id}.${fileExt}`;
// Generate a unique filename to avoid collisions
const fileExt = file.name.split('.').pop();
const fileName = `${Date.now()}-${profile?.id}.${fileExt}`;
// Upload the file to Supabase storage
const uploadResult = await uploadFile({
bucket,
path: fileName,
file: fileToUpload,
options: {
contentType: file.type,
},
});
// Upload the file to Supabase storage
const uploadResult = await uploadFile({
bucket,
path: fileName,
file: fileToUpload,
options: {
contentType: file.type,
},
});
if (!uploadResult.success) {
throw new Error(uploadResult.error || `Failed to upload to ${bucket}`);
}
if (!uploadResult.success) {
throw new Error(
uploadResult.error || `Failed to upload to ${bucket}`,
);
}
return { success: true, data: uploadResult.data };
} catch (error) {
toast.error(
error instanceof Error
? error.message
: `Failed to upload to ${bucket}`,
);
return {
success: false,
error: `Error: ${
error instanceof Error
? error.message
: `Failed to upload to ${bucket}`
}`,
};
} finally {
setIsUploading(false);
// Clear the input value so the same file can be selected again
if (fileInputRef.current) fileInputRef.current.value = '';
}
};
return { success: true, data: uploadResult.data };
} catch (error) {
toast.error(
error instanceof Error
? error.message
: `Failed to upload to ${bucket}`,
);
return {
success: false,
error: `Error: ${
error instanceof Error
? error.message
: `Failed to upload to ${bucket}`
}`,
};
} finally {
setIsUploading(false);
// Clear the input value so the same file can be selected again
if (fileInputRef.current) fileInputRef.current.value = '';
}
};
return {
isUploading,
fileInputRef,
uploadToStorage,
};
return {
isUploading,
fileInputRef,
uploadToStorage,
};
};

View File

@ -2,5 +2,5 @@ import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
return twMerge(clsx(inputs));
}