feat: add review writing page and integrate Steam account linking

- Implemented WriteReviewPage for users to submit reviews with ratings and playtime.
- Created global CSS styles for consistent theming across the application.
- Established RootLayout for metadata and global styles.
- Developed LoginPage for user authentication with email/password and Steam login options.
- Built Home page to display game search results and recent reviews.
- Added LinkSteamPage for linking Steam accounts to user profiles.
- Created ProfilePage to manage user information and display their reviews.
- Developed GameCard and ReviewCard components for displaying games and reviews.
- Implemented Header component for navigation and user session management.
- Added Providers component to wrap the application with session context.
- Integrated NextAuth for user authentication with Steam and credentials.
- Set up Prisma client for database interactions.
- Created Steam API utility functions for fetching game and user data.
- Configured TypeScript settings for the project.
This commit is contained in:
2026-02-22 02:53:23 +05:00
parent ce018da271
commit 1a6e754e4b
41 changed files with 2093 additions and 2 deletions

View File

@@ -0,0 +1,119 @@
'use client'
import { useState } from 'react'
import { useSession } from 'next-auth/react'
interface ReviewCardProps {
id: string
username: string
avatarUrl?: string | null
content: string
rating: number
playtimeHours?: number | null
upvotes: number
downvotes: number
userVote?: number | null
createdAt: string
onVote: (reviewId: string, voteType: 1 | -1) => Promise<void>
}
export function ReviewCard({
id,
username,
avatarUrl,
content,
rating,
playtimeHours,
upvotes,
downvotes,
userVote,
createdAt,
onVote
}: ReviewCardProps) {
const { data: session } = useSession()
const [voting, setVoting] = useState(false)
const handleVote = async (voteType: 1 | -1) => {
if (!session) return
setVoting(true)
try {
await onVote(id, voteType)
} finally {
setVoting(false)
}
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
return (
<div className="bg-[#16213e] rounded-xl p-6">
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-full bg-[#1a1a2e] flex items-center justify-center overflow-hidden">
{avatarUrl ? (
<img src={avatarUrl} alt={username} className="w-full h-full object-cover" />
) : (
<span className="text-2xl">👤</span>
)}
</div>
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<span className="text-white font-medium">{username}</span>
<span className="text-[#00d4ff] font-bold">{rating}/10</span>
{playtimeHours !== null && playtimeHours !== undefined && (
<span className="text-gray-400 text-sm">{playtimeHours}h played</span>
)}
</div>
<p className="text-gray-300 mb-4">{content}</p>
<div className="flex items-center justify-between">
<span className="text-gray-500 text-sm">{formatDate(createdAt)}</span>
{session && (
<div className="flex items-center gap-2">
<button
onClick={() => handleVote(1)}
disabled={voting}
className={`flex items-center gap-1 px-3 py-1 rounded-lg transition-colors ${
userVote === 1
? 'bg-green-600 text-white'
: 'bg-[#1a1a2e] text-gray-400 hover:text-green-400'
}`}
>
<span></span>
<span>{upvotes}</span>
</button>
<button
onClick={() => handleVote(-1)}
disabled={voting}
className={`flex items-center gap-1 px-3 py-1 rounded-lg transition-colors ${
userVote === -1
? 'bg-red-600 text-white'
: 'bg-[#1a1a2e] text-gray-400 hover:text-red-400'
}`}
>
<span></span>
<span>{downvotes}</span>
</button>
</div>
)}
{!session && (
<span className="text-gray-500 text-sm">
Sign in to vote
</span>
)}
</div>
</div>
</div>
</div>
)
}