mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-18 13:34:06 +01:00
feat: add profile page
This commit is contained in:
parent
690c44bd7d
commit
f775379662
@ -1,4 +1,19 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL_SOURCE;
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: SUPABASE_URL,
|
||||
port: "",
|
||||
pathname: "/storage/v1/object/sign/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
1149
package-lock.json
generated
1149
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -30,6 +30,7 @@
|
||||
"b2d-ventures": "file:",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"embla-carousel-react": "^8.2.0",
|
||||
"lucide-react": "^0.428.0",
|
||||
@ -39,12 +40,14 @@
|
||||
"react-countup": "^6.5.3",
|
||||
"react-dom": "^18",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"recharts": "^2.12.7",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.47.2",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
|
||||
BIN
public/banner.jpg
Normal file
BIN
public/banner.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
100
src/app/(user)/profile/page.tsx
Normal file
100
src/app/(user)/profile/page.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
// components/ProfilePage.tsx
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
|
||||
import { getUserProfile } from "@/lib/data/userQuery";
|
||||
import { Tables } from "@/types/database.types";
|
||||
import { format } from "date-fns";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
interface Profile extends Tables<"Profiles"> {}
|
||||
|
||||
export default async function ProfilePage() {
|
||||
const supabase = createSupabaseClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
<p className="text-red-500">No user found!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { data: profileData, error } = await getUserProfile(supabase, user.id);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
<p className="text-red-500">Error loading profile: {error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!profileData) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
<p>Loading profile...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container max-w-screen-xl px-4 py-8">
|
||||
<div className="bg-card border-2 border-border shadow-xl rounded-lg overflow-hidden">
|
||||
<div className="bg-cover bg-center h-64 p-4" style={{ backgroundImage: "url(./banner.jpg)" }}>
|
||||
<div className="flex justify-end">
|
||||
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||
Edit Profile
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 py-2">
|
||||
{/* Upper */}
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
{/* Profile Image */}
|
||||
<div className="text-center">
|
||||
<Image
|
||||
src={profileData.avatar_url || "https://via.placeholder.com/150"}
|
||||
alt={profileData.full_name || "Profile"}
|
||||
width={150}
|
||||
height={150}
|
||||
className="rounded-full border-4 border-white -mt-16 mx-auto sm:mx-0"
|
||||
/>
|
||||
</div>
|
||||
{/* Name and Username */}
|
||||
<div className="flex-grow text-center sm:text-left mt-4 sm:mt-0">
|
||||
<h1 className="text-3xl font-bold">{profileData.full_name || "No Name"}</h1>
|
||||
<p className="text-gray-600">@{profileData.username || "username"}</p>
|
||||
{profileData.website && (
|
||||
<p className="text-blue-500 hover:text-blue-700">
|
||||
<a href={profileData.website} target="_blank" rel="noopener noreferrer">
|
||||
{profileData.website}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Lower */}
|
||||
<div>
|
||||
<div className="mt-6">
|
||||
<h2 className="text-xl font-semibold mb-2">Bio</h2>
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<ReactMarkdown>{profileData.bio || "No bio available."}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 py-4 border-t">
|
||||
<p className="text-sm text-gray-600">
|
||||
Last updated: {profileData.updated_at ? format(new Date(profileData.updated_at), "PPpp") : "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -57,7 +57,9 @@ const AuthenticatedComponents = () => {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
<Link href="/profile">Profile</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem>Support</DropdownMenuItem>
|
||||
|
||||
23
src/lib/data/userQuery.ts
Normal file
23
src/lib/data/userQuery.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
|
||||
async function getUserProfile(client: SupabaseClient, userId: string) {
|
||||
try {
|
||||
const { data, error } = await client
|
||||
.from("Profiles")
|
||||
.select("updated_at, username, full_name, avatar_url, website, bio")
|
||||
.eq("id", userId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching user profile:", error.message);
|
||||
return { data: null, error: error.message };
|
||||
}
|
||||
|
||||
return { data, error: null };
|
||||
} catch (err) {
|
||||
console.error("Unexpected error:", err);
|
||||
return { data: null, error: "An unexpected error occurred." };
|
||||
}
|
||||
}
|
||||
|
||||
export { getUserProfile };
|
||||
Binary file not shown.
@ -74,7 +74,7 @@ const config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
plugins: [require("tailwindcss-animate"), require('@tailwindcss/typography'),],
|
||||
} satisfies Config
|
||||
|
||||
export default config
|
||||
Loading…
Reference in New Issue
Block a user