"use client" import { useState, useEffect } from "react" import Link from "next/link" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import { Input } from "@/components/ui/input" import { Alert, AlertDescription } from "@/components/ui/alert" import { ProtectedRoute } from "@/components/protected-route" import { useAuth } from "@/contexts/supabase-auth-context" import { ArrowLeft, Bell, Shield, User, Settings, Eye, EyeOff, CheckCircle, Cpu } from "lucide-react" import { supabase } from "@/lib/supabase" export default function SettingsPage() { const { user, updateUser, sendEmailChangeCode, verifyEmailChangeCode, sendPasswordChangeCode, verifyPasswordChangeCode } = useAuth() const [notifications, setNotifications] = useState<{ buildLikes: boolean comments: boolean followers: boolean newsletter: boolean emailUpdates: boolean } | null>(null); const [privacy, setPrivacy] = useState<{ profilePublic: boolean; showBuilds: boolean } | null>(null) const [newEmail, setNewEmail] = useState("") const [newPassword, setNewPassword] = useState("") const [confirmNewPassword, setConfirmNewPassword] = useState("") const [showNewPassword, setShowNewPassword] = useState(false) const [showConfirmNewPassword, setShowConfirmNewPassword] = useState(false) const [accountError, setAccountError] = useState("") const [accountSuccess, setAccountSuccess] = useState("") const [isUpdatingAccount, setIsUpdatingAccount] = useState(false) const [isCodeSent, setIsCodeSent] = useState(false); const [emailVerificationCode, setEmailVerificationCode] = useState(""); const [isPasswordCodeSent, setIsPasswordCodeSent] = useState(false); const [passwordVerificationCode, setPasswordVerificationCode] = useState(""); const [pushSupported, setPushSupported] = useState(false); const [isPushSubscribed, setIsPushSubscribed] = useState(false); const [isSubscribingToPush, setIsSubscribingToPush] = useState(false); // Initialize preferences and email when user loads useEffect(() => { if (!user) return setNewEmail(user.email || "") // Fetch preferences from notification_preferences table const fetchPreferences = async () => { try { const { data, error } = await supabase .from("notification_preferences") .select("*") .eq("user_id", user.user_id) .maybeSingle() // returns null if no row if (error) throw error if (!data) { // Insert default preferences if none exist const defaultPrefs = { build_likes: true, comments: true, followers: true, newsletter: false, email_updates: false } await supabase.from("notification_preferences").insert({ user_id: user.user_id, ...defaultPrefs, }) setNotifications({ buildLikes: defaultPrefs.build_likes, comments: defaultPrefs.comments, followers: defaultPrefs.followers, newsletter: defaultPrefs.newsletter, emailUpdates: defaultPrefs.email_updates, }) } else { setNotifications({ buildLikes: data.build_likes, comments: data.comments, followers: data.followers, newsletter: data.newsletter, emailUpdates: data.email_updates, }) } } catch (err) { console.error("Error fetching notification preferences:", err) } } fetchPreferences() // fetch from dedicated privacy_settings table const fetchPrivacy = async () => { try { const { data, error } = await supabase .from('privacy_settings') .select('*') .eq('user_id', user.user_id) .maybeSingle(); if (error) throw error; if (data) { setPrivacy({ profilePublic: data.profile_public, showBuilds: data.show_builds }); } else { // fall back to old column if present if (user.privacy_settings) { try { const settings = typeof user.privacy_settings === "string" ? JSON.parse(user.privacy_settings) : user.privacy_settings; setPrivacy(settings); } catch (e) { console.error("Error parsing privacy settings:", e); } } } } catch (e) { console.error("Error fetching privacy settings:", e); } } fetchPrivacy(); }, [user]) // Initialize push notifications useEffect(() => { if (!user) return const initPushNotifications = async () => { // Check if push notifications are supported if (!("serviceWorker" in navigator) || !("PushManager" in window)) { setPushSupported(false) return } setPushSupported(true) try { // Register service worker const registration = await navigator.serviceWorker.register("/sw.js", { scope: "/", }) // Ask for permission upfront if not already decided if (Notification.permission === "default") { const perm = await Notification.requestPermission() console.log("Notification permission requested during init:", perm) if (perm !== "granted") { // user denied or dismissed, we can't subscribe later setPushSupported(false) return } } // Check if user is already subscribed const subscription = await registration.pushManager.getSubscription() setIsPushSubscribed(!!subscription) } catch (error) { console.error("Failed to register service worker:", error) setPushSupported(false) } } initPushNotifications() }, [user]) const handlePushSubscription = async (notificationType: "buildLikes" | "comments" | "followers") => { if (!user || !pushSupported) { console.error("Push not supported or user not loaded"); setAccountError("Push notifications are not supported in your browser"); return; } setIsSubscribingToPush(true) setAccountError(""); setAccountSuccess(""); try { console.log("Starting push subscription process..."); // prompt for permission if necessary if (Notification.permission === "default") { const perm = await Notification.requestPermission(); console.log("Permission dialog result:", perm); if (perm !== "granted") { throw new Error("Notification permission denied. Please enable notifications in your browser settings."); } } else if (Notification.permission === "denied") { throw new Error("Notification permission has been denied. Please enable it in browser settings."); } // Get VAPID public key const vapidPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY; console.log("VAPID public key present:", !!vapidPublicKey); if (!vapidPublicKey) { throw new Error("VAPID public key not configured in environment variables"); } // Register/get service worker registration console.log("Getting service worker registration..."); const registration = await navigator.serviceWorker.ready; console.log("Service worker registered:", !!registration); // Subscribe to push notifications console.log("Subscribing to push manager..."); const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey), }); console.log("Push subscription obtained:", subscription); // Convert to JSON-safe object const subJson = subscription.toJSON(); console.log("Subscription JSON:", subJson); // Send subscription to backend console.log("Sending subscription to backend..."); const response = await fetch("/api/notifications/subscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: user.user_id, subscription: subJson, }), }); const responseData = await response.json(); console.log("Backend response:", responseData); if (!response.ok) { throw new Error(responseData.error || "Failed to save subscription to database"); } console.log("Backend saved subscription successfully, proceeding to update client state..."); setIsPushSubscribed(true); // Update notification preferences const newPrefs = { ...notifications }; newPrefs[notificationType] = true; setNotifications(newPrefs); try { console.log("Saving notification preferences to Supabase...", newPrefs); await saveNotificationPreferences(newPrefs); console.log("Notification preferences saved"); } catch (saveErr) { console.error("Failed to save notification preferences:", saveErr); } setAccountSuccess("Push notifications enabled! You will receive notifications for this event."); } catch (error: any) { console.error("Push subscription error:", error); setAccountError(error.message || "Failed to enable push notifications"); } finally { setIsSubscribingToPush(false); } }; const handlePushUnsubscription = async () => { if (!user || !pushSupported) return setIsSubscribingToPush(true) try { const registration = await navigator.serviceWorker.ready const subscription = await registration.pushManager.getSubscription() if (!subscription) { throw new Error("No active subscription") } // Unsubscribe from push notifications const response = await fetch("/api/notifications/unsubscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: user.user_id, endpoint: subscription.endpoint, }), }) if (!response.ok) throw new Error("Failed to unsubscribe") await subscription.unsubscribe() setIsPushSubscribed(false) // Update notification preferences to disable push notifications const newPrefs = { ...notifications, buildLikes: false, comments: false, followers: false, } setNotifications(newPrefs) await saveNotificationPreferences(newPrefs) setAccountSuccess("Disabled push notifications") } catch (error: any) { console.error("Push unsubscription error:", error) setAccountError(error.message || "Failed to disable push notifications") } finally { setIsSubscribingToPush(false) } } const urlBase64ToUint8Array = (base64String: string) => { const padding = "=".repeat((4 - (base64String.length % 4)) % 4) const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/") const rawData = window.atob(base64) const outputArray = new Uint8Array(rawData.length) for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i) } return outputArray } const saveNotificationPreferences = async (newPreferences: typeof notifications) => { if (!user) return if (!newPreferences) return try { console.log("saveNotificationPreferences: upserting", newPreferences) const { data, error } = await supabase .from("notification_preferences") .upsert({ user_id: user.user_id, build_likes: newPreferences.buildLikes, comments: newPreferences.comments, followers: newPreferences.followers, newsletter: newPreferences.newsletter, email_updates: newPreferences.emailUpdates, }) .select() if (error) { console.error("saveNotificationPreferences: supabase error", error) throw error } console.log("saveNotificationPreferences: success", data) return data } catch (err) { console.error("Error saving notification preferences:", err) throw err } } const savePrivacySettings = async (newSettings: typeof privacy) => { if (!user || !newSettings) return try { const { data, error } = await supabase .from("privacy_settings") .upsert({ user_id: user.user_id, profile_public: newSettings.profilePublic, show_builds: newSettings.showBuilds, }) if (error) { console.error("Error saving privacy settings:", error) return } updateUser({ privacy_settings: newSettings }) } catch (err) { console.error("Error saving privacy settings:", err) } } const handleSendEmailCode = async () => { if (!user) return; setAccountError(""); setAccountSuccess(""); try { const res = await sendEmailChangeCode(user.user_id, newEmail); if (!res.success) throw new Error(res.error || "Failed to send code"); setIsCodeSent(true); setAccountSuccess(res.message || "Verification code sent to your email."); } catch (err: any) { setAccountError(err.message); } }; const handleVerifyEmailCode = async () => { if (!user) return; setAccountError(""); setAccountSuccess(""); try { const res = await verifyEmailChangeCode(user.user_id, newEmail, emailVerificationCode); if (!res.success) throw new Error(res.error || "Failed to verify code"); setAccountSuccess(res.message || "Email updated successfully!"); setIsCodeSent(false); // Update local state updateUser({ email: newEmail }); setEmailVerificationCode(""); } catch (err: any) { setAccountError(err.message); } }; const handleSendPasswordCode = async () => { if (!user) return; setAccountError(""); setAccountSuccess(""); try { if (!newPassword) throw new Error("Please enter a new password."); if (newPassword !== confirmNewPassword) throw new Error("New passwords do not match."); const res = await sendPasswordChangeCode(user.user_id); if (!res.success) throw new Error(res.error || "Failed to send code"); setIsPasswordCodeSent(true); setAccountSuccess(res.message || "Verification code sent to your email."); } catch (err: any) { setAccountError(err.message); } }; const handleVerifyPasswordCode = async () => { if (!user) return; setIsUpdatingAccount(true); setAccountError(""); setAccountSuccess(""); try { if (!passwordVerificationCode) throw new Error("Please enter the verification code."); const res = await verifyPasswordChangeCode(user.user_id, passwordVerificationCode, newPassword, confirmNewPassword); if (!res.success) throw new Error(res.error || "Failed to verify code"); setAccountSuccess(res.message || "Password updated successfully!"); setIsPasswordCodeSent(false); // Reset password fields setNewPassword(""); setConfirmNewPassword(""); setPasswordVerificationCode(""); } catch (err: any) { setAccountError(err.message); } finally { setIsUpdatingAccount(false); } }; if (!user) { return (
Loading...
Manage your account and preferences
Receive emails about new features, improvements, and important announcements
{pushSupported ? "Push notification when someone likes your builds" : "Email notification when someone likes your builds"}
{pushSupported ? "Push notification when someone comments on your builds" : "Email notification when someone comments on your builds"}
{pushSupported ? "Push notification when someone follows you" : "Email notification when someone follows you"}
Get curated PC builds, component tips, and market insights delivered monthly
Make your profile visible to everyone
Display your builds on your profile