11import { functions } from "components/firebase"
22import { httpsCallable } from "firebase/functions"
33import { useTranslation } from "next-i18next"
4- import { Dispatch , ReactNode , SetStateAction , useEffect , useState } from "react"
4+ import { Dispatch , SetStateAction , useEffect , useState } from "react"
55import { useAuth } from "../auth"
6- import { usePublicProfile } from "components/db"
7- import { Internal } from "components/links"
8- import { FollowUserButton } from "components/shared/FollowButton"
9- import React from "react"
10- import { Col , Row , Spinner , Stack , Alert } from "../bootstrap"
11- import { TitledSectionCard } from "../shared"
12- import { OrgIconSmall } from "./StyledEditProfileComponents"
6+ import { FollowUserCard } from "./FollowUserCard"
7+ import {
8+ LoadableItemsState ,
9+ PaginatedItemsCard
10+ } from "components/shared/PaginatedItemsCard"
1311
1412export const FollowersTab = ( {
1513 className,
@@ -19,99 +17,51 @@ export const FollowersTab = ({
1917 setFollowerCount : Dispatch < SetStateAction < number | null > >
2018} ) => {
2119 const uid = useAuth ( ) . user ?. uid
22- const [ followerIds , setFollowerIds ] = useState < string [ ] > ( [ ] )
23- const [ loading , setLoading ] = useState ( true )
24- const [ error , setError ] = useState < string | null > ( null )
20+ const [ state , setState ] = useState < LoadableItemsState < { profileId : string } > > (
21+ {
22+ items : [ ] ,
23+ loading : true ,
24+ error : null
25+ }
26+ )
2527 const { t } = useTranslation ( "editProfile" )
2628
29+ const fetchFollowers = async ( ) => {
30+ try {
31+ const { data : profileIds } = await httpsCallable < void , string [ ] > (
32+ functions ,
33+ "getFollowers"
34+ ) ( )
35+ setState ( {
36+ items : profileIds . map ( profileId => ( { profileId } ) ) ,
37+ loading : false ,
38+ error : null
39+ } )
40+ setFollowerCount ( profileIds . length )
41+ } catch ( err ) {
42+ console . error ( "Error fetching followerIds" , err )
43+ setState ( {
44+ items : [ ] ,
45+ loading : false ,
46+ error : t ( "content.error" )
47+ } )
48+ }
49+ }
2750 useEffect ( ( ) => {
28- const fetchFollowers = async ( ) => {
29- try {
30- const { data : followerIds } = await httpsCallable < void , string [ ] > (
31- functions ,
32- "getFollowers"
33- ) ( )
34- setFollowerIds ( followerIds )
35- setFollowerCount ( followerIds . length )
36- setLoading ( false )
37- } catch ( err ) {
38- console . error ( "Error fetching followerIds" , err )
39- setError ( "Error fetching followers." )
40- setLoading ( false )
41- return
42- }
51+ if ( uid ) {
52+ setState ( prev => ( { ...prev , loading : true , error : null } ) )
53+ fetchFollowers ( )
54+ } else {
55+ setState ( { items : [ ] , loading : false , error : null } )
4356 }
44- if ( uid ) fetchFollowers ( )
4557 } , [ uid ] )
4658 return (
47- < TitledSectionCard className = { className } >
48- < div className = "mx-4 mt-3 d-flex flex-column gap-3" >
49- < Stack >
50- < h2 > { t ( "follow.your_followers" ) } </ h2 >
51- < p className = "mt-0 text-muted" >
52- { t ( "follow.follower_info_disclaimer" ) }
53- </ p >
54- < div className = "mt-3" >
55- { error ? (
56- < Alert variant = "danger" > { error } </ Alert >
57- ) : loading ? (
58- < Spinner animation = "border" className = "mx-auto" />
59- ) : (
60- followerIds . map ( ( profileId , i ) => (
61- < FollowerCard key = { i } profileId = { profileId } />
62- ) )
63- ) }
64- </ div >
65- </ Stack >
66- </ div >
67- </ TitledSectionCard >
68- )
69- }
70-
71- const FollowerCard = ( { profileId } : { profileId : string } ) => {
72- const { result : profile , loading } = usePublicProfile ( profileId )
73- const { t } = useTranslation ( "profile" )
74- if ( loading ) {
75- return (
76- < FollowerCardWrapper >
77- < Spinner animation = "border" className = "mx-auto" />
78- </ FollowerCardWrapper >
79- )
80- }
81- const { fullName, profileImage, public : isPublic } = profile || { }
82- const displayName = isPublic && fullName ? fullName : t ( "anonymousUser" )
83- return (
84- < FollowerCardWrapper >
85- < Col className = "d-flex align-items-center flex-grow-1 p-0 text-start" >
86- < OrgIconSmall
87- className = "mr-4 mt-0 mb-0 ms-0"
88- profileImage = { profileImage }
89- />
90- { isPublic ? (
91- < Internal href = { `/profile?id=${ profileId } ` } > { displayName } </ Internal >
92- ) : (
93- < span > { displayName } </ span >
94- ) }
95- </ Col >
96- { isPublic ? (
97- < Col
98- xs = "auto"
99- className = "d-flex justify-content-end ms-auto text-end p-0"
100- >
101- < FollowUserButton profileId = { profileId } />
102- </ Col >
103- ) : (
104- < > </ >
105- ) }
106- </ FollowerCardWrapper >
59+ < PaginatedItemsCard
60+ className = { className }
61+ title = { t ( "follow.your_followers" ) }
62+ description = { t ( "follow.follower_info_disclaimer" ) }
63+ ItemCard = { FollowUserCard }
64+ { ...state }
65+ />
10766 )
10867}
109-
110- const FollowerCardWrapper = ( { children } : { children : ReactNode } ) => (
111- < div className = { `fs-3 lh-lg` } >
112- < Row className = "align-items-center justify-content-between g-0 w-100" >
113- { children }
114- </ Row >
115- < hr className = { `mt-3` } />
116- </ div >
117- )
0 commit comments