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:
119
src/components/ReviewCard.tsx
Normal file
119
src/components/ReviewCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user