Implement profile management (task 14)
- Add updateProfile and updatePassword server actions - Add getUserProfile function with sensitive data protection - Create ProfileForm component for name and email updates - Create PasswordChangeForm component with current password verification - Create profile page at /profile with all profile management features - Add profile and invitations navigation links to main page - Implement email uniqueness validation - Implement password hashing on password change Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
This commit is contained in:
23
app/page.tsx
23
app/page.tsx
@@ -3,17 +3,38 @@ import ListCard from '@/components/ListCard'
|
||||
import NewListButton from '@/components/NewListButton'
|
||||
import GlobalSearch from '@/components/GlobalSearch'
|
||||
import EmptyState from '@/components/EmptyState'
|
||||
import Link from 'next/link'
|
||||
import { auth } from '@/auth'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export default async function Home() {
|
||||
const lists = await getLists()
|
||||
const session = await auth()
|
||||
|
||||
return (
|
||||
<main className="container mx-auto px-4 sm:px-6 py-6 sm:py-8 max-w-7xl">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-6 sm:mb-8">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-900">Ultimate Lists</h1>
|
||||
<NewListButton />
|
||||
<div className="flex items-center gap-3">
|
||||
{session?.user && (
|
||||
<>
|
||||
<Link
|
||||
href="/invitations"
|
||||
className="px-3 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
Invitations
|
||||
</Link>
|
||||
<Link
|
||||
href="/profile"
|
||||
className="px-3 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
Profile
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<NewListButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Global Search */}
|
||||
|
||||
119
app/profile/page.tsx
Normal file
119
app/profile/page.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { redirect } from 'next/navigation'
|
||||
import { auth } from '@/auth'
|
||||
import { getUserProfile } from '@/lib/auth-actions'
|
||||
import ProfileForm from '@/components/ProfileForm'
|
||||
import PasswordChangeForm from '@/components/PasswordChangeForm'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Profile - Ultimate Lists',
|
||||
description: 'Manage your profile and account settings',
|
||||
}
|
||||
|
||||
export default async function ProfilePage() {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id) {
|
||||
redirect('/auth/login')
|
||||
}
|
||||
|
||||
const userProfile = await getUserProfile(session.user.id)
|
||||
|
||||
if (!userProfile) {
|
||||
redirect('/auth/login')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 px-4 py-8">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center text-sm text-gray-600 hover:text-gray-900 mb-4"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-1"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
Back to Lists
|
||||
</Link>
|
||||
<h1 className="text-3xl sm:text-4xl font-bold text-gray-900">
|
||||
Profile Settings
|
||||
</h1>
|
||||
<p className="text-sm sm:text-base text-gray-600 mt-2">
|
||||
Manage your account information and security settings
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Profile Information Section */}
|
||||
<div className="bg-white rounded-xl shadow-md p-6 sm:p-8 mb-6">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-gray-900 mb-4">
|
||||
Profile Information
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mb-6">
|
||||
Update your name and email address
|
||||
</p>
|
||||
<ProfileForm
|
||||
initialName={userProfile.name}
|
||||
initialEmail={userProfile.email}
|
||||
userId={userProfile.id}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password Section */}
|
||||
<div className="bg-white rounded-xl shadow-md p-6 sm:p-8 mb-6">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-gray-900 mb-4">
|
||||
Change Password
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mb-6">
|
||||
Update your password to keep your account secure
|
||||
</p>
|
||||
<PasswordChangeForm userId={userProfile.id} />
|
||||
</div>
|
||||
|
||||
{/* Account Information */}
|
||||
<div className="bg-white rounded-xl shadow-md p-6 sm:p-8">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-gray-900 mb-4">
|
||||
Account Information
|
||||
</h2>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex justify-between py-2 border-b border-gray-100">
|
||||
<span className="text-gray-600">Account ID</span>
|
||||
<span className="text-gray-900 font-mono text-xs">{userProfile.id}</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2 border-b border-gray-100">
|
||||
<span className="text-gray-600">Member Since</span>
|
||||
<span className="text-gray-900">
|
||||
{new Date(userProfile.createdAt).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2">
|
||||
<span className="text-gray-600">Last Updated</span>
|
||||
<span className="text-gray-900">
|
||||
{new Date(userProfile.updatedAt).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user