Skip to content
59 changes: 59 additions & 0 deletions components/EditProfilePage/FollowUserCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { usePublicProfile } from "components/db"
import { Internal } from "components/links"
import { FollowUserButton } from "components/shared/FollowButton"
import { useTranslation } from "next-i18next"
import React from "react"
import { Col, Row, Spinner } from "../bootstrap"
import { OrgIconSmall } from "./StyledEditProfileComponents"

export function FollowUserCard({
profileId,
confirmUnfollow
}: {
profileId: string
confirmUnfollow?: boolean
}) {
const { result: profile, loading } = usePublicProfile(profileId)
const { t } = useTranslation("profile")

if (loading) {
return (
<div className={`fs-3 lh-lg`}>
<Row className="align-items-center justify-content-between g-0 w-100">
<Spinner animation="border" className="mx-auto" />
</Row>
<hr className={`mt-3`} />
</div>
)
}

const { fullName, profileImage, public: isPublic } = profile || {}
const displayName = isPublic && fullName ? fullName : t("anonymousUser")

return (
<div className={`fs-3 lh-lg`}>
<Row className="align-items-center justify-content-between g-0 w-100">
<Col className="d-flex align-items-center flex-grow-1 p-0 text-start">
<OrgIconSmall
className="mr-4 mt-0 mb-0 ms-0"
profileImage={profileImage}
/>
{isPublic ? (
<Internal href={`/profile?id=${profileId}`}>{displayName}</Internal>
) : (
<span>{displayName}</span>
)}
</Col>
{isPublic ? (
<Col xs="auto" className="d-flex justify-content-end ms-auto p-0">
<FollowUserButton
profileId={profileId}
confirmUnfollow={confirmUnfollow}
/>
</Col>
) : null}
</Row>
<hr className={`mt-3`} />
</div>
)
}
142 changes: 46 additions & 96 deletions components/EditProfilePage/FollowersTab.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { functions } from "components/firebase"
import { httpsCallable } from "firebase/functions"
import { useTranslation } from "next-i18next"
import { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react"
import { Dispatch, SetStateAction, useEffect, useState } from "react"
import { useAuth } from "../auth"
import { usePublicProfile } from "components/db"
import { Internal } from "components/links"
import { FollowUserButton } from "components/shared/FollowButton"
import React from "react"
import { Col, Row, Spinner, Stack, Alert } from "../bootstrap"
import { TitledSectionCard } from "../shared"
import { OrgIconSmall } from "./StyledEditProfileComponents"
import { FollowUserCard } from "./FollowUserCard"
import {
LoadableItemsState,
PaginatedItemsCard
} from "components/shared/PaginatedItemsCard"

export const FollowersTab = ({
className,
Expand All @@ -19,99 +17,51 @@ export const FollowersTab = ({
setFollowerCount: Dispatch<SetStateAction<number | null>>
}) => {
const uid = useAuth().user?.uid
const [followerIds, setFollowerIds] = useState<string[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [state, setState] = useState<LoadableItemsState<{ profileId: string }>>(
{
items: [],
loading: true,
error: null
}
)
const { t } = useTranslation("editProfile")

const fetchFollowers = async () => {
try {
const { data: profileIds } = await httpsCallable<void, string[]>(
functions,
"getFollowers"
)()
setState({
items: profileIds.map(profileId => ({ profileId })),
loading: false,
error: null
})
setFollowerCount(profileIds.length)
} catch (err) {
console.error("Error fetching followerIds", err)
setState({
items: [],
loading: false,
error: t("content.error")
})
}
}
useEffect(() => {
const fetchFollowers = async () => {
try {
const { data: followerIds } = await httpsCallable<void, string[]>(
functions,
"getFollowers"
)()
setFollowerIds(followerIds)
setFollowerCount(followerIds.length)
setLoading(false)
} catch (err) {
console.error("Error fetching followerIds", err)
setError("Error fetching followers.")
setLoading(false)
return
}
if (uid) {
setState(prev => ({ ...prev, loading: true, error: null }))
fetchFollowers()
} else {
setState({ items: [], loading: false, error: null })
}
if (uid) fetchFollowers()
}, [uid])
return (
<TitledSectionCard className={className}>
<div className="mx-4 mt-3 d-flex flex-column gap-3">
<Stack>
<h2>{t("follow.your_followers")}</h2>
<p className="mt-0 text-muted">
{t("follow.follower_info_disclaimer")}
</p>
<div className="mt-3">
{error ? (
<Alert variant="danger">{error}</Alert>
) : loading ? (
<Spinner animation="border" className="mx-auto" />
) : (
followerIds.map((profileId, i) => (
<FollowerCard key={i} profileId={profileId} />
))
)}
</div>
</Stack>
</div>
</TitledSectionCard>
)
}

const FollowerCard = ({ profileId }: { profileId: string }) => {
const { result: profile, loading } = usePublicProfile(profileId)
const { t } = useTranslation("profile")
if (loading) {
return (
<FollowerCardWrapper>
<Spinner animation="border" className="mx-auto" />
</FollowerCardWrapper>
)
}
const { fullName, profileImage, public: isPublic } = profile || {}
const displayName = isPublic && fullName ? fullName : t("anonymousUser")
return (
<FollowerCardWrapper>
<Col className="d-flex align-items-center flex-grow-1 p-0 text-start">
<OrgIconSmall
className="mr-4 mt-0 mb-0 ms-0"
profileImage={profileImage}
/>
{isPublic ? (
<Internal href={`/profile?id=${profileId}`}>{displayName}</Internal>
) : (
<span>{displayName}</span>
)}
</Col>
{isPublic ? (
<Col
xs="auto"
className="d-flex justify-content-end ms-auto text-end p-0"
>
<FollowUserButton profileId={profileId} />
</Col>
) : (
<></>
)}
</FollowerCardWrapper>
<PaginatedItemsCard
className={className}
title={t("follow.your_followers")}
description={t("follow.follower_info_disclaimer")}
ItemCard={FollowUserCard}
{...state}
/>
)
}

const FollowerCardWrapper = ({ children }: { children: ReactNode }) => (
<div className={`fs-3 lh-lg`}>
<Row className="align-items-center justify-content-between g-0 w-100">
{children}
</Row>
<hr className={`mt-3`} />
</div>
)
Loading
Loading