mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 15:11:13 -05:00
Fix: Implement Phase 1 and 2 for Account & Profile tab
This commit is contained in:
@@ -23,16 +23,18 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { toast as sonnerToast } from 'sonner';
|
||||
import { AccountDeletionDialog } from './AccountDeletionDialog';
|
||||
import { DeletionStatusBanner } from './DeletionStatusBanner';
|
||||
import { usernameSchema, displayNameSchema, bioSchema } from '@/lib/validation';
|
||||
import { usernameSchema, displayNameSchema, bioSchema, personalLocationSchema, preferredPronounsSchema } from '@/lib/validation';
|
||||
import { z } from 'zod';
|
||||
import { AccountDeletionRequest } from '@/types/database';
|
||||
|
||||
const profileSchema = z.object({
|
||||
username: usernameSchema,
|
||||
display_name: displayNameSchema,
|
||||
bio: bioSchema,
|
||||
preferred_pronouns: z.string().max(20).optional(),
|
||||
preferred_pronouns: preferredPronounsSchema,
|
||||
show_pronouns: z.boolean(),
|
||||
preferred_language: z.string()
|
||||
preferred_language: z.string(),
|
||||
personal_location: personalLocationSchema
|
||||
});
|
||||
|
||||
type ProfileFormData = z.infer<typeof profileSchema>;
|
||||
@@ -50,7 +52,7 @@ export function AccountProfileTab() {
|
||||
const [avatarUrl, setAvatarUrl] = useState<string>(profile?.avatar_url || '');
|
||||
const [avatarImageId, setAvatarImageId] = useState<string>(profile?.avatar_image_id || '');
|
||||
const [showDeletionDialog, setShowDeletionDialog] = useState(false);
|
||||
const [deletionRequest, setDeletionRequest] = useState<any>(null);
|
||||
const [deletionRequest, setDeletionRequest] = useState<AccountDeletionRequest | null>(null);
|
||||
|
||||
const form = useForm<ProfileFormData>({
|
||||
resolver: zodResolver(profileSchema),
|
||||
@@ -60,7 +62,8 @@ export function AccountProfileTab() {
|
||||
bio: profile?.bio || '',
|
||||
preferred_pronouns: profile?.preferred_pronouns || '',
|
||||
show_pronouns: profile?.show_pronouns || false,
|
||||
preferred_language: profile?.preferred_language || 'en'
|
||||
preferred_language: profile?.preferred_language || 'en',
|
||||
personal_location: profile?.personal_location || ''
|
||||
}
|
||||
});
|
||||
|
||||
@@ -89,29 +92,28 @@ export function AccountProfileTab() {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const usernameChanged = profile?.username !== data.username;
|
||||
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
username: data.username,
|
||||
display_name: data.display_name || null,
|
||||
bio: data.bio || null,
|
||||
preferred_pronouns: data.preferred_pronouns || null,
|
||||
show_pronouns: data.show_pronouns,
|
||||
preferred_language: data.preferred_language,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq('user_id', user.id);
|
||||
// Use the update_profile RPC function with server-side validation
|
||||
const { data: result, error } = await supabase.rpc('update_profile', {
|
||||
p_username: data.username,
|
||||
p_display_name: data.display_name || null,
|
||||
p_bio: data.bio || null,
|
||||
p_preferred_pronouns: data.preferred_pronouns || null,
|
||||
p_show_pronouns: data.show_pronouns,
|
||||
p_preferred_language: data.preferred_language,
|
||||
p_personal_location: data.personal_location || null
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Type the RPC result
|
||||
const rpcResult = result as unknown as { success: boolean; username_changed: boolean; changes_count: number };
|
||||
|
||||
// Update Novu subscriber if username changed
|
||||
if (usernameChanged && notificationService.isEnabled()) {
|
||||
if (rpcResult?.username_changed && notificationService.isEnabled()) {
|
||||
await notificationService.updateSubscriber({
|
||||
subscriberId: user.id,
|
||||
email: user.email,
|
||||
firstName: data.username, // Send username as firstName to Novu
|
||||
firstName: data.username,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,19 +139,17 @@ export function AccountProfileTab() {
|
||||
const newAvatarUrl = urls[0];
|
||||
const newImageId = imageId || '';
|
||||
|
||||
// Update local state immediately
|
||||
// Update local state immediately for optimistic UI
|
||||
setAvatarUrl(newAvatarUrl);
|
||||
setAvatarImageId(newImageId);
|
||||
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
avatar_url: newAvatarUrl,
|
||||
avatar_image_id: newImageId,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq('user_id', user.id);
|
||||
// Use update_profile RPC for avatar updates
|
||||
const { error } = await supabase.rpc('update_profile', {
|
||||
p_username: profile?.username || '',
|
||||
p_avatar_url: newAvatarUrl,
|
||||
p_avatar_image_id: newImageId
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
@@ -245,7 +245,7 @@ export function AccountProfileTab() {
|
||||
setDeletionRequest(null);
|
||||
};
|
||||
|
||||
const isDeactivated = (profile as any)?.deactivated || false;
|
||||
const isDeactivated = profile?.deactivated || false;
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@@ -337,6 +337,11 @@ export function AccountProfileTab() {
|
||||
{...form.register('preferred_pronouns')}
|
||||
placeholder="e.g., they/them, she/her, he/him"
|
||||
/>
|
||||
{form.formState.errors.preferred_pronouns && (
|
||||
<p className="text-sm text-destructive">
|
||||
{form.formState.errors.preferred_pronouns.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -359,6 +364,20 @@ export function AccountProfileTab() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="personal_location">Personal Location</Label>
|
||||
<Input
|
||||
id="personal_location"
|
||||
{...form.register('personal_location')}
|
||||
placeholder="e.g., Los Angeles, CA"
|
||||
/>
|
||||
{form.formState.errors.personal_location && (
|
||||
<p className="text-sm text-destructive">
|
||||
{form.formState.errors.personal_location.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={loading || isDeactivated}>
|
||||
{loading ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user