'use client' import Image from 'next/image' import Link from 'next/link' import { ArrowLeft, Pencil } from 'lucide-react' import { useRef, useState } from 'react' import { authClient } from '../lib/auth/client' import { updateUsername, updateProfile, uploadAvatar, updateEmailNotifications } from '../lib/auth/actions' function GoogleIcon({ className }: { className?: string }) { return ( ) } interface SettingsPageProps { user: { id: string name?: string | null email?: string | null image?: string | null } currentUsername: string | null currentGithubUrl: string | null currentXUrl: string | null currentYoutubeUrl: string | null currentEmailNotifications: boolean connectedAccounts: { providerId: string; accountId: string }[] } export function SettingsPage({ user, currentUsername, currentGithubUrl, currentXUrl, currentYoutubeUrl, currentEmailNotifications, connectedAccounts, }: SettingsPageProps) { const [username, setUsername] = useState(currentUsername ?? '') const [githubUrl, setGithubUrl] = useState(currentGithubUrl ?? '') const [xUrl, setXUrl] = useState(currentXUrl ?? '') const [youtubeUrl, setYoutubeUrl] = useState(currentYoutubeUrl ?? '') const [avatarUrl, setAvatarUrl] = useState(user.image) const [isSavingUsername, setIsSavingUsername] = useState(false) const [isSavingSocial, setIsSavingSocial] = useState(false) const [isUploadingAvatar, setIsUploadingAvatar] = useState(false) const [isConnectingGoogle, setIsConnectingGoogle] = useState(false) const fileInputRef = useRef(null) const [usernameMessage, setUsernameMessage] = useState<{ type: 'success' | 'error' text: string } | null>(null) const [socialMessage, setSocialMessage] = useState<{ type: 'success' | 'error' text: string } | null>(null) const [emailNotifications, setEmailNotifications] = useState(currentEmailNotifications) const [isSavingNotifications, setIsSavingNotifications] = useState(false) const isGoogleConnected = connectedAccounts.some((a) => a.providerId === 'google') const initials = currentUsername ? currentUsername.slice(0, 2).toUpperCase() : user.name ? user.name .split(' ') .map((n) => n[0]) .join('') .toUpperCase() .slice(0, 2) : user.email?.[0]?.toUpperCase() || 'U' const handleAvatarClick = () => { fileInputRef.current?.click() } const handleAvatarUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setIsUploadingAvatar(true) const formData = new FormData() formData.append('avatar', file) const result = await uploadAvatar(formData) if (result.success && result.imageUrl) { setAvatarUrl(result.imageUrl) } setIsUploadingAvatar(false) // Reset input so the same file can be selected again if (fileInputRef.current) fileInputRef.current.value = '' } const handleConnectGoogle = async () => { setIsConnectingGoogle(true) try { await authClient.signIn.social({ provider: 'google', callbackURL: '/settings', }) } catch { setIsConnectingGoogle(false) } } const handleSaveUsername = async (e: React.FormEvent) => { e.preventDefault() setUsernameMessage(null) setIsSavingUsername(true) const result = await updateUsername(username) setUsernameMessage({ type: result.success ? 'success' : 'error', text: result.success ? 'Username updated successfully' : (result.error ?? 'Failed'), }) setIsSavingUsername(false) } const handleSaveSocial = async (e: React.FormEvent) => { e.preventDefault() setSocialMessage(null) setIsSavingSocial(true) const result = await updateProfile({ githubUrl: githubUrl.trim() || null, xUrl: xUrl.trim() || null, youtubeUrl: youtubeUrl.trim() || null, }) setSocialMessage({ type: result.success ? 'success' : 'error', text: result.success ? 'Social links updated successfully' : (result.error ?? 'Failed'), }) setIsSavingSocial(false) } const usernameChanged = username.trim() !== (currentUsername ?? '') const socialChanged = (githubUrl.trim() || '') !== (currentGithubUrl ?? '') || (xUrl.trim() || '') !== (currentXUrl ?? '') || (youtubeUrl.trim() || '') !== (currentYoutubeUrl ?? '') const handleToggleEmailNotifications = async () => { const newValue = !emailNotifications setEmailNotifications(newValue) setIsSavingNotifications(true) await updateEmailNotifications(newValue) setIsSavingNotifications(false) } return (
Back

Settings

{/* Profile Section */}

Profile

{/* Avatar with upload */}
{user.name &&
{user.name}
} {user.email && (
{user.email}
)}

Your public display name on the community hub.

{ setUsername(e.target.value) setUsernameMessage(null) }} placeholder="your-username" className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" disabled={isSavingUsername} minLength={3} maxLength={30} pattern="[a-zA-Z0-9_-]+" />

3-30 characters. Letters, numbers, hyphens, and underscores only.

{usernameMessage && (
{usernameMessage.text}
)}
{/* Connected Accounts Section */}

Connected Accounts

Google
{isGoogleConnected ? (
Connected
) : (
Not connected
)}
{isGoogleConnected ? ( Connected ) : ( )}
{/* Notifications Section */}

Notifications

Email notifications

Receive emails about new features and updates.

{/* Social Links Section */}

Social Links

{ setGithubUrl(e.target.value) setSocialMessage(null) }} placeholder="https://github.com/yourusername" className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" disabled={isSavingSocial} />
{ setXUrl(e.target.value) setSocialMessage(null) }} placeholder="https://x.com/yourusername" className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" disabled={isSavingSocial} />
{ setYoutubeUrl(e.target.value) setSocialMessage(null) }} placeholder="https://youtube.com/@yourchannel" className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" disabled={isSavingSocial} />
{socialMessage && (
{socialMessage.text}
)}
) }