This app provides a comprehensive system for managing user avatars within a Next.js application using Supabase as the backend. It enables users to log in, view their current avatar, upload a new image as an avatar. This system includes real-time UI updates, Supabase integration for data management, and image processing via the sharp library for resizing images before upload.
You can log in using test@owolf.com and password 123456.
AvatarUploader Component
javascript
"use client";
import { useTransition, useEffect, useState } from"react";
import {
uploadImageToServer,
logOutFromSupabase,
} from"@/actions/serverActions";
import { createClient } from"@/utils/supabase/client";
importLinkfrom"next/link";
importImagefrom"next/image";
exportdefaultfunctionAvatarUploader() {
let [isPending, startTransition] = useTransition(); // React's concurrent mode API to manage transitionsconst [loggingOut, setLoggingOut] = useState(false); // State to handle logging out UI changesconst [imageUrl, setImageUrl] = (useState < string) | (null > null); // State to store the URL of the uploaded imageconst [user, setUser] = useState < any > null; // State to store user information// useEffect to perform side effects only when isPending changesuseEffect(() => {
if (isPending) return; // Skip fetching if a transition is in progressconst supabase = createClient(); // Initialize Supabase client// Function to fetch the avatar image from SupabaseconstgetAvatarImageFromSupabase = async () => {
const {
data: { user },
} = await supabase.auth.getUser(); // Get the currently authenticated userconsole.log("user", user?.id);
setUser(user); // Set user dataconst { data: profile } = await supabase
.from("profiles")
.select()
.eq("id", user?.id)
.single(); // Query for the user profilesetImageUrl(profile?.avatar_url); // Set image URL from user profile
};
getAvatarImageFromSupabase();
}, [isPending]);
// Handler for uploading imagesconstuploadAction = async (formData: FormData) => {
startTransition(() => {
uploadImageToServer(formData);
});
};
// Handler for logging outconstlogOutAction = async () => {
awaitlogOutFromSupabase();
};
// Render the component UIreturn (
<mainclassName="flex min-h-screen flex-col items-center justify-between py-12"><divclassName="z-10 max-w-xl w-full items-center justify-between flex flex-col gap-4 font-mono"><h1className="text-4xl font-bold text-center">Image Uploader</h1><pclassName="text-center">
This is an uploader for images built with Next.js and Supabase.
</p>
{user ? (
<divclassName="flex flex-col gap-2"><pclassName="font-bold">User: {user.email}</p><formaction={logOutAction}className="flex justify-center"><buttonclassName="border border-black rounded py-1 px-2 hover:bg-gray-300"type="submit"onClick={() => setLoggingOut(true)}
>
{loggingOut ? <p>Logging Out...</p> : <p>Log Out</p>}
</button></form></div>
) : (
<div><pclassName="text-center">
You must{" "}
<LinkclassName="underline font-bold"href="/login">
log in
</Link>{" "}
to use this app.
</p></div>
)}
<formaction={uploadAction}className="flex flex-col gap-4 pt-4"><inputtype="file"name="file" /><buttontype="submit"className="border border-black rounded py-1 px-2 hover:bg-gray-300"onClick={() => {
setImageUrl(null);
}}
>
{isPending ? "uploading..." : "upload"}{" "}
</button></form><divclassName=" w-80 h-80 border border-black rounded relative">
{imageUrl ? (
<Imagesrc={imageUrl}alt="avatar"fill={true}style={{objectFit: "cover" }}
/>
) : (
<divclassName="w-full h-full bg-gray-100 flex flex-col justify-center"><pclassName="text-center p-8 text-black">
{user ? (
<span>Avatar Image</span>
) : (
<span>You need to be logged in to see your avatar</span>
)}
</p></div>
)}
</div></div></main>
);
}
Functionality
State Management:
isPending: Manages the state of asynchronous actions, particularly during the image upload process.
loggingOut: Indicates whether a logout process is underway.
imageUrl: Stores the URL of the user's avatar image.
user: Contains user data fetched from Supabase.
Effects and Lifecycle:
An effect hook triggers upon component mount and whenever isPending changes, fetching user details and their avatar URL from Supabase.
User Interaction:
Image Upload: Users can upload a new avatar image through a file input. The upload initiates in a suspended transition state to optimize UI responsiveness.
Logging Out: Users can log out, which triggers an asynchronous request to Supabase to terminate the session.
Rendering:
Displays the user's current avatar if available.
Provides a login link if the user is not authenticated.
Shows a button to start the image upload and another to log out.
Key Components
Link from next/link: Used for navigation to the login page if the user is not logged in.
Image from next/image: Optimizes image display.
Server Actions
uploadImageToServer(formData: FormData)
javascript
"use server";
import { createClient } from"@/utils/supabase/server";
import { redirect } from"next/navigation";
import sharp from"sharp";
constIMAGE_SIZE = 600; // Define the target size of the resized imageconstSUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL; // Supabase URL from environment variables// Function to upload an image to Supabase storageexportconstuploadImageToServer = async (formData: FormData) => {
const supabase = createClient(); // Initialize the Supabase clienttry {
// Get the current authenticated userconst {
data: { user },
} = await supabase.auth.getUser();
if (!user) thrownewError("Authentication failed"); // If no user, throw an errorconst file = formData.get("file") asFile; // Extract the file from the FormDataconst resizedImage = awaitresizeImage(file); // Resize the image using the sharp library// Construct the file path where the image will be storedconst fileName = file.name.split(".")[0];
const timestamp = Math.floor(Date.now() / 1000);
const filePath = `${user.id}/${timestamp}_${fileName}.jpg`;
// Upload the resized image to Supabase storageconst { data, error } = await supabase.storage
.from("uploads")
.upload(filePath, resizedImage);
if (error) throw error; // If there's an upload error, throw it// Construct the URL to access the uploaded imageconst avatarUrl = `${SUPABASE_URL}/storage/v1/object/public/uploads/${data.path}`;
awaitupdateUserProfile(user.id, avatarUrl, supabase); // Update the user profile with the new avatar URL
} catch (error) {
console.error("Failed to upload image:", error);
}
};
Description
Processes the image upload request:
Authenticates the user.
Resizes the image to a predefined size using sharp.
Uploads the resized image to Supabase Storage.
Updates the user's profile with the new avatar URL.
Error Handling
Catches and logs errors related to user authentication and file upload processes.
resizeImage(file: File): Promise<Buffer>
javascript
// Function to resize an image to a square format and convert it to JPEGconst resizeImage = async (file: File): Promise<Buffer> => {
const buffer = await file.arrayBuffer(); // Convert file to array bufferreturnsharp(buffer)
.resize(IMAGE_SIZE, IMAGE_SIZE) // Resize the image
.toFormat("jpeg") // Convert the image to JPEG format
.toBuffer(); // Return the modified image as a buffer
};
Description
Converts a file from FormData into a Buffer and resizes it to a square format (600x600 pixels), changing the format to JPEG.
// Function to update the user profile with a new avatar URLconstupdateUserProfile = async (
userId: string,
avatarUrl: string,
supabase: any
) => {
const { error } = await supabase
.from("profiles")
.update({ avatar_url: avatarUrl })
.eq("id", userId); // Update the 'profiles' table where 'id' matches userIdif (error) throw error; // If there's an update error, throw it
};
Description
Updates the user's profile in the Supabase database with the new avatar URL.
Error Handling
Checks for errors during the profile update and logs them.
logOutFromSupabase()
javascript
// Function to log out from SupabaseexportconstlogOutFromSupabase = async () => {
const supabase = createClient(); // Initialize the Supabase clienttry {
const { error } = await supabase.auth.signOut(); // Attempt to sign outif (error) {
thrownewError(`Logout failed: ${error.message}`); // If logout fails, throw an error
}
} catch (error) {
console.error("Logout error:", error);
} finally {
redirect("/login"); // Redirect to login page after logout
}
};
Description
Logs out the current user by ending the session in Supabase and redirects the user to the login page.
Error Handling
Handles errors that might occur during the logout process and logs them, ensuring the user is redirected irrespective of whether the logout was successful.
Integration with Supabase
Authentication: Uses Supabase's authentication mechanisms to manage user sessions.
Storage: Utilizes Supabase Storage for storing and retrieving user avatars.
Realtime Database: Interacts with the Supabase realtime database for fetching and updating user profiles.
Usage
This component and its server functions are designed to be used in applications requiring user profile management with image uploads. It is suitable for environments where user experience and efficient data handling are priorities.