feat: add profile page UI & actions
This commit is contained in:
parent
a03c89f598
commit
d2bcba7eb1
|
|
@ -1,7 +1,9 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
domains: ["lh3.googleusercontent.com"],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"framer-motion": "^12.31.0",
|
||||
"framer-motion": "^12.33.0",
|
||||
"lucide-react": "^0.563.0",
|
||||
"next": "16.1.6",
|
||||
"next-auth": "^4.24.13",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"framer-motion": "^12.31.0",
|
||||
"framer-motion": "^12.33.0",
|
||||
"lucide-react": "^0.563.0",
|
||||
"next": "16.1.6",
|
||||
"next-auth": "^4.24.13",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "../../api/auth/[...nextauth]/route";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const getAnotherUserData = async () => {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.email) return null;
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: session.user.email },
|
||||
select: {
|
||||
gender: true,
|
||||
productReference: true,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error("Error fetching user data:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { Header } from "@/src/components/dashboards/Header";
|
||||
import { getAnotherUserData } from "./lib/action";
|
||||
import ProfileClient from "@/src/components/dashboards/ProfileClient";
|
||||
import { UserGender } from "@prisma/client";
|
||||
|
||||
export default async function ProfilePage() {
|
||||
const user = await getAnotherUserData();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<ProfileClient
|
||||
gender={user?.gender as UserGender}
|
||||
productReference={user?.productReference || "None"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -79,7 +79,6 @@ export default function DashboardClient() {
|
|||
<Header />
|
||||
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
{/* Hero Section */}
|
||||
<div
|
||||
className="mb-8 rounded-2xl p-8 text-center"
|
||||
style={{ background: "hsl(var(--primary))" }}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
|
||||
export function Header() {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
|
@ -34,7 +35,7 @@ export function Header() {
|
|||
<header className="border-b bg-card/50 backdrop-blur-sm sticky top-0 z-50">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/" className="flex items-center gap-3 cursor-pointer">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary text-primary-foreground">
|
||||
<BarChart3 className="h-5 w-5" />
|
||||
</div>
|
||||
|
|
@ -46,7 +47,7 @@ export function Header() {
|
|||
Analisis Sentimen Ulasan Laptop Tokopedia
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="hidden items-center gap-6 text-sm md:flex">
|
||||
|
|
@ -79,10 +80,12 @@ export function Header() {
|
|||
align="end"
|
||||
className="w-max bg-card border-border shadow-md"
|
||||
>
|
||||
<DropdownMenuItem className="cursor-pointer gap-2 focus:bg-secondary focus:text-primary transition-colors hover:text-primary">
|
||||
<UserCircle className="h-4 w-4 text-muted-foreground" />
|
||||
<span>Menu Profil</span>
|
||||
</DropdownMenuItem>
|
||||
<Link href="/profile">
|
||||
<DropdownMenuItem className="cursor-pointer gap-2 focus:bg-secondary focus:text-primary transition-colors hover:text-primary">
|
||||
<UserCircle className="h-4 w-4 text-muted-foreground" />
|
||||
<span>Menu Profil</span>
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
<DropdownMenuSeparator className="bg-border" />
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
"use client";
|
||||
import Image from "next/image";
|
||||
import { Button } from "../ui/button";
|
||||
import { ArrowLeft, Pencil } from "lucide-react";
|
||||
import { Separator } from "../ui/separator";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { UserGender } from "@prisma/client";
|
||||
import Link from "next/link";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface ProfileClientProps {
|
||||
gender?: UserGender;
|
||||
productReference?: string;
|
||||
}
|
||||
|
||||
export default function ProfileClient({
|
||||
gender,
|
||||
productReference,
|
||||
}: ProfileClientProps) {
|
||||
const session = useSession();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
className="container mx-auto px-4 py-8"
|
||||
>
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 text-md text-primary max-w-xl mx-auto"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Dashboard</span>
|
||||
</Link>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
className="mx-auto w-full max-w-xl rounded-xl border bg-background shadow-sm mt-4"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Image
|
||||
src={session?.data?.user?.image ?? "file.svg"}
|
||||
alt="User Avatar"
|
||||
width={80}
|
||||
height={80}
|
||||
className="h-14 w-14 rounded-full border object-cover"
|
||||
/>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold leading-tight">
|
||||
{session?.data?.user?.name || "Guest"}
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{session?.data?.user?.email || "Not logged in"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 bg-primary text-card border-none"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
Edit Profile
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 p-6 sm:grid-cols-2">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">Gender</p>
|
||||
<p className="font-medium">{gender || "Not specified"}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">Product Preference</p>
|
||||
<p className="font-medium">{productReference || "None"}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue