📄 Profile.js
/home/palash/git/site/src/components/Profile.js
Language: js • Lines: 117
import { useState, useEffect, useCallback } from "react";
import { useParams, Link } from "react-router-dom";
import { doc, getDoc, collection, query, where, getDocs, orderBy } from "firebase/firestore";
import { db } from "../firebase";
import { useAuth } from "../contexts/AuthContext";
import { Container, Spinner, Row, Col } from "react-bootstrap";
import { FaGlobe, FaBirthdayCake, FaEdit, FaUserCircle } from "react-icons/fa";
import slugify from "../utils/slugify";
import { col } from "../utils/firestorePath";

export default function Profile() {
  const { identifier } = useParams();
  const { user } = useAuth();
  const [profile, setProfile] = useState(null);
  const [profileUid, setProfileUid] = useState(null);
  const [scores, setScores] = useState([]);
  const [loading, setLoading] = useState(true);

  const loadData = useCallback(async () => {
    setLoading(true);
    const key = identifier.startsWith("@") ? identifier.slice(1) : identifier;

    // Try as profile UID first
    let pSnap = await getDoc(doc(db, col("profiles"), key));
    let uid = key;

    if (!pSnap.exists()) {
      // Not a UID — try as username
      const uSnap = await getDoc(doc(db, col("usernames"), key.toLowerCase()));
      if (!uSnap.exists()) { setLoading(false); return; }
      uid = uSnap.data().uid;
      pSnap = await getDoc(doc(db, col("profiles"), uid));
      if (!pSnap.exists()) { setLoading(false); return; }
    }

    setProfile({ id: uid, ...pSnap.data() });
    setProfileUid(uid);

    const q = query(
      collection(db, col("scores")),
      where("userId", "==", uid),
      where("published", "==", true),
      orderBy("updatedAt", "desc")
    );
    const sSnap = await getDocs(q);
    const mapped = sSnap.docs.map(d => {
      const data = d.data();
      const hasComposite = data.username && data.slug && data.instrument;
      return {
        id: hasComposite
          ? `${data.username}/${slugify(data.instrument)}/${data.slug}`
          : "u-" + d.id,
        ...data
      };
    });
    setScores(mapped);
    setLoading(false);
  }, [identifier]);

  useEffect(() => { loadData(); }, [loadData]);

  if (loading) return <div className="text-center py-5"><Spinner animation="border" variant="light" /></div>;
  if (!profile) return <Container className="py-5"><p className="text-secondary text-center">Profile not found.</p></Container>;

  return (
    <Container className="py-4" style={{ maxWidth: 600 }}>
      <div className="d-flex align-items-center gap-4 mb-4">
        {profile.photoURL ? (
          <img src={profile.photoURL} alt="" width={72} height={72} className="rounded-circle" style={{ objectFit: "cover" }} />
        ) : (
          <FaUserCircle size={72} className="text-secondary" />
        )}
        <div>
          <h4 className="mb-0">{profile.displayName}</h4>
          {profile.username && <span className="text-secondary small">@{profile.username}</span>}
          {profile.website && (
            <a href={profile.website} target="_blank" rel="noopener noreferrer" className="text-info small d-flex align-items-center gap-1">
              <FaGlobe size={12} /> {profile.website.replace(/^https?:\/\//, "")}
            </a>
          )}
          {profile.birthday && (
            <span className="text-secondary small d-flex align-items-center gap-1 mt-1">
              <FaBirthdayCake size={12} /> {new Date(profile.birthday + "T00:00:00").toLocaleDateString("en-US", { month: "long", day: "numeric" })}
            </span>
          )}
        </div>
        {user && user.uid === profileUid && (
          <div className="d-flex gap-2 ms-auto">
            <Link to="/tab-parser" className="btn btn-outline-info btn-sm">
              + New
            </Link>
            <Link to="/profile/edit" className="btn btn-outline-light btn-sm">
              <FaEdit className="me-1" /> Edit
            </Link>
          </div>
        )}
      </div>
      <h5 className="mb-3">Published Scores</h5>
      {scores.length === 0 ? (
        <p className="text-secondary small">No published scores yet.</p>
      ) : (
        <Row>
          {scores.map(s => (
            <Col xs={12} key={s.id} className="mb-2">
              <Link to={`/content/${s.id}`} className="text-decoration-none d-flex justify-content-between align-items-center p-3 rounded" style={{ background: "#1e1e1e" }}>
                <span className="fw-bold small">{s.name}</span>
                <span className="text-secondary" style={{ fontSize: "11px" }}>
                  {s.updatedAt?.toDate?.()?.toLocaleDateString() || ""}
                </span>
              </Link>
            </Col>
          ))}
        </Row>
      )}
    </Container>
  );
}