mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 18:11:12 -05:00
Enable RLS on rate limits table
This commit is contained in:
58
src/hooks/useAutoSave.ts
Normal file
58
src/hooks/useAutoSave.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useDebounce } from './useDebounce';
|
||||
|
||||
export type AutoSaveOptions<T> = {
|
||||
data: T;
|
||||
onSave: (data: T) => Promise<void>;
|
||||
debounceMs?: number;
|
||||
enabled?: boolean;
|
||||
isValid?: boolean;
|
||||
};
|
||||
|
||||
export const useAutoSave = <T,>({
|
||||
data,
|
||||
onSave,
|
||||
debounceMs = 3000,
|
||||
enabled = true,
|
||||
isValid = true
|
||||
}: AutoSaveOptions<T>) => {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [lastSaved, setLastSaved] = useState<Date | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const debouncedData = useDebounce(data, debounceMs);
|
||||
const initialRender = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Skip initial render
|
||||
if (initialRender.current) {
|
||||
initialRender.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if disabled or invalid
|
||||
if (!enabled || !isValid) return;
|
||||
|
||||
const save = async () => {
|
||||
setIsSaving(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await onSave(debouncedData);
|
||||
setLastSaved(new Date());
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to auto-save');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
save();
|
||||
}, [debouncedData, enabled, isValid, onSave]);
|
||||
|
||||
return {
|
||||
isSaving,
|
||||
lastSaved,
|
||||
error,
|
||||
resetError: () => setError(null)
|
||||
};
|
||||
};
|
||||
84
src/hooks/useAvatarUpload.ts
Normal file
84
src/hooks/useAvatarUpload.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { handleError, handleSuccess } from '@/lib/errorHandler';
|
||||
|
||||
export type AvatarUploadState = {
|
||||
url: string;
|
||||
imageId: string;
|
||||
isUploading: boolean;
|
||||
};
|
||||
|
||||
export const useAvatarUpload = (
|
||||
initialUrl: string = '',
|
||||
initialImageId: string = '',
|
||||
username: string
|
||||
) => {
|
||||
const [state, setState] = useState<AvatarUploadState>({
|
||||
url: initialUrl,
|
||||
imageId: initialImageId,
|
||||
isUploading: false
|
||||
});
|
||||
|
||||
const uploadAvatar = useCallback(async (
|
||||
urls: string[],
|
||||
imageId?: string
|
||||
) => {
|
||||
if (!urls[0]) return { success: false };
|
||||
|
||||
const newUrl = urls[0];
|
||||
const newImageId = imageId || '';
|
||||
|
||||
// Optimistic update
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
url: newUrl,
|
||||
imageId: newImageId,
|
||||
isUploading: true
|
||||
}));
|
||||
|
||||
try {
|
||||
const { error } = await supabase.rpc('update_profile', {
|
||||
p_username: username,
|
||||
p_avatar_url: newUrl,
|
||||
p_avatar_image_id: newImageId
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
setState(prev => ({ ...prev, isUploading: false }));
|
||||
handleSuccess('Avatar updated', 'Your avatar has been successfully updated.');
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
// Rollback on error
|
||||
setState({
|
||||
url: initialUrl,
|
||||
imageId: initialImageId,
|
||||
isUploading: false
|
||||
});
|
||||
|
||||
handleError(error, {
|
||||
action: 'Avatar upload failed',
|
||||
metadata: { username }
|
||||
});
|
||||
|
||||
return { success: false, error };
|
||||
}
|
||||
}, [username, initialUrl, initialImageId]);
|
||||
|
||||
const resetAvatar = useCallback(() => {
|
||||
setState({
|
||||
url: initialUrl,
|
||||
imageId: initialImageId,
|
||||
isUploading: false
|
||||
});
|
||||
}, [initialUrl, initialImageId]);
|
||||
|
||||
return {
|
||||
avatarUrl: state.url,
|
||||
avatarImageId: state.imageId,
|
||||
isUploading: state.isUploading,
|
||||
uploadAvatar,
|
||||
resetAvatar
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user