📄 AuthContext.js
/home/palash/git/site/src/contexts/AuthContext.js
Language: js • Lines: 108
import React, { createContext, useContext, useEffect, useState } from "react";
import {
  onAuthStateChanged,
  signInWithPopup,
  signOut,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  sendEmailVerification,
} from "firebase/auth";
import { doc, getDoc, setDoc, updateDoc, serverTimestamp } from "firebase/firestore";
import { auth, googleProvider, db } from "../firebase";
import gravatarUrl from "../utils/gravatar";
import { col } from "../utils/firestorePath";

const AuthContext = createContext(null);

async function ensureProfile(user) {
  if (!user) return;
  try {
    const ref = doc(db, col("profiles"), user.uid);
    const snap = await getDoc(ref);
    if (!snap.exists()) {
      await setDoc(ref, {
        displayName: user.displayName || user.email?.split("@")[0] || "Anonymous",
        photoURL: user.photoURL || gravatarUrl(user.email),
        email: user.email,
        emailVerified: user.emailVerified,
        username: "",
        website: "",
        birthday: "",
        createdAt: serverTimestamp(),
      });
    } else if (snap.data().emailVerified !== user.emailVerified) {
      await updateDoc(ref, { emailVerified: user.emailVerified });
    }
  } catch (e) {
    console.warn("Profile sync skipped:", e.message);
  }
}

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [profile, setProfile] = useState(null);
  const [loading, setLoading] = useState(true);
  const [needsUsername, setNeedsUsername] = useState(false);

  useEffect(() => {
    const unsub = onAuthStateChanged(auth, async (u) => {
      setUser(u);
      if (u) {
        await ensureProfile(u);
        const snap = await getDoc(doc(db, "profiles", u.uid));
        const data = snap.data() || null;
        setProfile(data);
        setNeedsUsername(!data?.username);
      } else {
        setProfile(null);
        setNeedsUsername(false);
      }
      setLoading(false);
    });
    return unsub;
  }, []);

  const signInWithGoogle = () => signInWithPopup(auth, googleProvider);

  const signUpWithEmail = async (email, password) => {
    const cred = await createUserWithEmailAndPassword(auth, email, password);
    await sendEmailVerification(cred.user);
    return cred;
  };

  const signInWithEmail = (email, password) =>
    signInWithEmailAndPassword(auth, email, password);

  const logout = () => signOut(auth);

  const refreshProfile = async () => {
    if (!user) return;
    const snap = await getDoc(doc(db, col("profiles"), user.uid));
    setProfile(snap.data() || null);
  };

  return (
    <AuthContext.Provider
      value={{
        user,
        profile,
        loading,
        needsUsername,
        setNeedsUsername,
        refreshProfile,
        signInWithGoogle,
        signUpWithEmail,
        signInWithEmail,
        logout,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be used within AuthProvider");
  return ctx;
}