-
+
+
) : (
-
)}
diff --git a/src/app/(auth-pages)/profile/page.tsx.bak b/src/app/(auth-pages)/profile/page.tsx.bak
deleted file mode 100644
index c6426af..0000000
--- a/src/app/(auth-pages)/profile/page.tsx.bak
+++ /dev/null
@@ -1,286 +0,0 @@
-'use client';
-import { z } from 'zod';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { useForm } from 'react-hook-form';
-import { getProfile, getSignedUrl, updateProfile, uploadFile } from '@/lib/actions';
-import { useState, useEffect, useRef } from 'react';
-import type { Profile } from '@/utils/supabase';
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
- Button,
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
- Input,
- Separator,
-} from '@/components/ui';
-import { toast } from 'sonner';
-import { Pencil, User } from 'lucide-react'
-
-const formSchema = z.object({
- full_name: z.string().min(5, {
- message: 'Full name is required & must be at least 5 characters.'
- }),
- email: z.string().email(),
-});
-
-const ProfilePage = () => {
- const [profile, setProfile] = useState
(undefined);
- const [avatarUrl, setAvatarUrl] = useState(undefined);
- const [isLoading, setIsLoading] = useState(true);
- const [isUploading, setIsUploading] = useState(false);
- const fileInputRef = useRef(null);
-
-
- const form = useForm>({
- resolver: zodResolver(formSchema),
- defaultValues: {
- full_name: '',
- email: '',
- },
- });
-
- useEffect(() => {
- const fetchProfile = async () => {
- try {
- setIsLoading(true);
- const profileResponse = await getProfile();
- if (!profileResponse.success)
- throw new Error('Profile response unsuccessful');
- setProfile(profileResponse.data);
- form.reset({
- full_name: profileResponse.data.full_name ?? '',
- email: profileResponse.data.email ?? '',
- });
- } catch (error) {
- setProfile(undefined);
- } finally {
- setIsLoading(false);
- }
- };
- fetchProfile().catch((error) => {
- console.error('Error getting profile:', error);
- });
- }, [form]);
-
- useEffect(() => {
- const getAvatarUrl = async () => {
- if (profile?.avatar_url) {
- try {
- const response = await getSignedUrl({
- bucket: 'avatars',
- url: profile.avatar_url,
- transform: {
- quality: 40,
- resize: 'fill',
- }
- });
-
- if (response.success) {
- setAvatarUrl(response.data);
- }
- } catch (error) {
- console.error('Error getting signed URL:', error);
- }
- }
- };
- getAvatarUrl().catch((error) => {
- toast.error(error instanceof Error ? error.message : 'Failed to get signed avatar url.');
- });
- }, [profile]);
-
- const handleAvatarClick = () => {
- fileInputRef.current?.click();
- };
-
- const handleFileChange = async (e: React.ChangeEvent) => {
- const file = e.target.files?.[0];
- if (!file)
- throw new Error('No file selected');
-
- try {
- setIsUploading(true);
-
- const fileExt = file.name.split('.').pop();
- const fileName = `${Date.now()}-${profile?.id ?? Math.random().toString(36).substring(2,15)}.${fileExt}`;
-
- const uploadResult = await uploadFile({
- bucket: 'avatars',
- path: fileName,
- file,
- options: {
- upsert: true,
- contentType: file.type,
- },
- });
- if (!uploadResult.success)
- throw new Error(uploadResult.error ?? 'Failed to upload avatar');
-
- const updateResult = await updateProfile({
- avatar_url: uploadResult.data,
- });
-
- if (!updateResult.success)
- throw new Error(updateResult.error ?? 'Failed to update profile');
-
- setProfile(updateResult.data);
- toast.success('Avatar updated successfully.')
-
- } catch (error) {
- toast.error(error instanceof Error ? error.message : 'Failed to uploaad avatar.');
- } finally {
- setIsUploading(false);
- if (fileInputRef.current) fileInputRef.current.value = '';
- }
- };
-
- const getInitials = (name: string) => {
- return name
- .split(' ')
- .map((n) => n[0])
- .join('')
- .toUpperCase();
- }
-
- const onSubmit = async (values: z.infer) => {
- try {
- setIsLoading(true);
- const result = await updateProfile({
- full_name: values.full_name,
- email: values.email,
- });
-
- if (!result.success) {
- throw new Error(result.error ?? 'Failed to update profile');
- }
- setProfile(result.data);
- toast.success('Profile updated successfully!');
- } catch (error) {
- toast.error(error instanceof Error ? error.message : 'Failed to update profile.');
- } finally {
- setIsLoading(false);
- }
- };
-
- if (profile === undefined)
- return (
-
-
Unauthorized
-
- );
-
- return (
-
-
-
- {profile?.full_name ?? 'Profile'}
-
-
- Manage your personal information & how it appears to others.
-
-
-
- {isLoading && !profile ? (
-
- ) : (
-
-
-
-
- {avatarUrl ? (
-
- ) : (
-
- {profile?.full_name
- ? profile.full_name.split(' ').map(n => n[0]).join('').toUpperCase()
- : }
-
- )}
-
-
-
-
- {isUploading && (
-
Uploading...
- )}
-
- Click on the avatar to upload a new image
-
-
-
-
-
-
- )}
-
-
- );
-};
-export default ProfilePage;
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 001fb81..b24f4c7 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils';
import { ThemeProvider } from '@/components/context/theme';
import Navigation from '@/components/default/navigation';
import Footer from '@/components/default/footer';
-import { Toaster } from '@/components/ui'
+import { Toaster } from '@/components/ui';
export const metadata: Metadata = {
title: 'T3 Template with Supabase',
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 994832a..6e32f89 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -20,17 +20,21 @@ const HomePage = async () => {
return (
-
+
- This is a protected component that you can only see as an authenticated
- user
+ This is a protected component that you can only see as an
+ authenticated user
Your user details
-
+
{JSON.stringify(user, null, 2)}
diff --git a/src/components/default/navigation/auth/AvatarDropdown.tsx b/src/components/default/navigation/auth/AvatarDropdown.tsx
index d55c3c2..4671a33 100644
--- a/src/components/default/navigation/auth/AvatarDropdown.tsx
+++ b/src/components/default/navigation/auth/AvatarDropdown.tsx
@@ -13,7 +13,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui';
-import { useProfile, useAvatar } from '@/lib/hooks'
+import { useProfile, useAvatar } from '@/lib/hooks';
import { signOut } from '@/lib/actions';
import { User } from 'lucide-react';
@@ -42,10 +42,16 @@ const AvatarDropdown = () => {
{avatarUrl ? (
) : (
-
- {profile?.full_name
- ? profile.full_name.split(' ').map(n => n[0]).join('').toUpperCase()
- : }
+
+ {profile?.full_name ? (
+ profile.full_name
+ .split(' ')
+ .map((n) => n[0])
+ .join('')
+ .toUpperCase()
+ ) : (
+
+ )}
)}
diff --git a/src/components/default/profile/AvatarUpload.tsx b/src/components/default/profile/AvatarUpload.tsx
index 60e8a5c..a1cbab5 100644
--- a/src/components/default/profile/AvatarUpload.tsx
+++ b/src/components/default/profile/AvatarUpload.tsx
@@ -9,7 +9,10 @@ type AvatarUploadProps = {
onAvatarUploaded: (path: string) => Promise;
};
-export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) => {
+export const AvatarUpload = ({
+ profile,
+ onAvatarUploaded,
+}: AvatarUploadProps) => {
const { avatarUrl, isLoading } = useAvatar(profile);
const { isUploading, fileInputRef, uploadToStorage } = useFileUpload();
@@ -20,7 +23,7 @@ export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) =
const handleFileChange = async (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (!file) return;
-
+
const result = await uploadToStorage(file, 'avatars');
if (result.success && result.path) {
await onAvatarUploaded(result.path);
@@ -29,59 +32,77 @@ export const AvatarUpload = ({ profile, onAvatarUploaded }: AvatarUploadProps) =
if (isLoading) {
return (
-
-
-
-
- {profile?.full_name
- ? profile.full_name.split(' ').map(n => n[0]).join('').toUpperCase()
- : }
-
-
+
+
+
+
+ {profile?.full_name ? (
+ profile.full_name
+ .split(' ')
+ .map((n) => n[0])
+ .join('')
+ .toUpperCase()
+ ) : (
+
+ )}
+
+
+
-
);
}
return (
-
-
-
+
+
{isUploading && (
-
-
+
+
Uploading...
)}
-
+
Click on the avatar to upload a new image
);
-}
+};
diff --git a/src/components/default/profile/ProfileForm.tsx b/src/components/default/profile/ProfileForm.tsx
index 73fb641..0541dfd 100644
--- a/src/components/default/profile/ProfileForm.tsx
+++ b/src/components/default/profile/ProfileForm.tsx
@@ -18,7 +18,7 @@ import { useEffect } from 'react';
const formSchema = z.object({
full_name: z.string().min(5, {
- message: 'Full name is required & must be at least 5 characters.'
+ message: 'Full name is required & must be at least 5 characters.',
}),
email: z.string().email(),
});
@@ -29,7 +29,11 @@ type ProfileFormProps = {
onSubmit: (values: z.infer
) => Promise;
};
-export function ProfileForm({ profile, isLoading, onSubmit }: ProfileFormProps) {
+export function ProfileForm({
+ profile,
+ isLoading,
+ onSubmit,
+}: ProfileFormProps) {
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -54,10 +58,7 @@ export function ProfileForm({ profile, isLoading, onSubmit }: ProfileFormProps)
return (