Project: User Profile Avatar System
In this project, we will build a user profile avatar system. Users can select an image file, upload it to a public storage bucket, and save the public image link to their database profile.
1. Relational Database Tables
We store profiles in a database table containing a matching user ID reference:
-- Profiles table setup referencing auth.users UUID primary key
CREATE TABLE profiles (
id UUID REFERENCES auth.users PRIMARY KEY,
username TEXT NOT NULL,
avatar_url TEXT
);2. Implementing the Avatar Component
Here is the React user dashboard avatar component:
// src/components/AvatarUpload.tsx
import React, { useState, useEffect } from "react";
import { supabase } from "../lib/supabase";
export default function AvatarUpload() {
const [loading, setLoading] = useState(false);
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
const [username, setUsername] = useState("");
useEffect(() => {
loadProfileDetails();
}, []);
async function loadProfileDetails() {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data } = await supabase
.from("profiles")
.select("username, avatar_url")
.eq("id", user.id)
.single();
if (data) {
setUsername(data.username);
setAvatarUrl(data.avatar_url);
}
}
async function handleFileUpload(event: React.ChangeEvent<HTMLInputElement>) {
try {
setLoading(true);
const { data: { user } } = await supabase.auth.getUser();
if (!user || !event.target.files || event.target.files.length === 0) {
throw new Error("Invalid file upload target session");
}
const file = event.target.files[0];
const fileExt = file.name.split(".").pop();
const filePath = `${user.id}/avatar.${fileExt}`;
// 1. Upload the image file to the avatars storage bucket
const { error: uploadError } = await supabase.storage
.from("avatars")
.upload(filePath, file, { upsert: true });
if (uploadError) throw uploadError;
// 2. Retrieve the public URL
const { data: { publicUrl } } = supabase.storage
.from("avatars")
.getPublicUrl(filePath);
// 3. Save the public URL to the database profiles table
const { error: dbError } = await supabase
.from("profiles")
.upsert({
id: user.id,
username,
avatar_url: publicUrl,
});
if (dbError) throw dbError;
setAvatarUrl(publicUrl);
alert("Avatar updated successfully!");
} catch (err: any) {
alert(err.message || "Failed to update avatar photo");
} finally {
setLoading(false);
}
}
return (
<div className="flex flex-col items-center gap-6 p-8 border rounded-2xl max-w-sm mx-auto bg-white shadow-sm mt-10">
<div className="relative w-32 h-32 rounded-full overflow-hidden border bg-gray-50 flex items-center justify-center">
{avatarUrl ? (
<img src={avatarUrl} alt="User Profile" className="w-full h-full object-cover" />
) : (
<span className="text-gray-400 text-sm">No Photo</span>
)}
</div>
<div className="text-center">
<h4 className="font-bold text-gray-950">{username || "User Profile"}</h4>
<p className="text-gray-400 text-xs mt-1">Accepts PNG, JPG formats</p>
</div>
<label className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-5 rounded-lg text-sm cursor-pointer transition">
{loading ? "Uploading..." : "Upload Avatar"}
<input
type="file"
accept="image/*"
onChange={handleFileUpload}
disabled={loading}
className="hidden"
/>
</label>
</div>
);
}Published on Last updated: