Skip to content

Commit 6c8b1df

Browse files
committed
refactor(app): follower list
1 parent 3f79ac6 commit 6c8b1df

File tree

5 files changed

+141
-146
lines changed

5 files changed

+141
-146
lines changed

src/app/u/[handle]/[follow]/page.js

Lines changed: 2 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -16,142 +16,10 @@
1616

1717
'use client';
1818

19-
import { useSession } from 'next-auth/react';
20-
import { usePathname, useRouter } from 'next/navigation';
21-
import { useState, useCallback, useEffect } from 'react';
22-
23-
import Avatar from '@/components/Avatar';
24-
import { Button } from '@/components/Button';
25-
import { SvgIcon } from '@/components/Image';
26-
import Loader from '@/components/Loader';
27-
import { NoData } from '@/components/NoData';
28-
29-
import { fetchFollowerList, fetchFollowedList, followUser, unfollowUser } from '#/domain/profile/repository';
30-
import TabBarWidget from '#/domain/profile/widgets/tab-bar';
31-
import { useUser } from '#/state/application/hooks';
32-
33-
const followFetchMap = {
34-
followers: fetchFollowerList,
35-
following: fetchFollowedList,
36-
};
19+
import FollowerListViewWidget from '#/domain/profile/views/follower-list';
3720

3821
export default function Follow({ params }) {
39-
const { status } = useSession();
40-
const router = useRouter();
41-
const pathname = usePathname();
42-
const user = useUser();
43-
const [loading, setLoading] = useState(false);
44-
45-
const [followLoading, setFollowLoading] = useState(false);
46-
47-
const [page, setPage] = useState(1);
48-
const [list, setList] = useState();
49-
const [count, setCount] = useState();
50-
51-
const fetchList = useCallback(async () => {
52-
// handle
53-
setLoading(true);
54-
const data = await followFetchMap[params.follow]({ handle: params.handle, pageNum: page });
55-
setLoading(false);
56-
if (page === 1) {
57-
setList(data.data.list);
58-
} else {
59-
setList(list => list.concat(data.data.list));
60-
}
61-
setCount(data.data.count);
62-
}, [params, page]);
63-
64-
useEffect(() => {
65-
fetchList();
66-
}, [fetchList]);
67-
68-
const follow = async (item, index, type) => {
69-
if (status !== 'authenticated') {
70-
router.push(`/signin?from=${pathname}`);
71-
} else {
72-
setFollowLoading(index);
73-
const res = await followUser(item.user.user_id);
74-
setFollowLoading(null);
75-
if (res.success) {
76-
const _prevList = [...list];
77-
_prevList[index].mutual = type;
78-
setList(_prevList);
79-
}
80-
}
81-
};
82-
83-
const unfollow = async (item, index, type) => {
84-
if (status !== 'authenticated') {
85-
router.push(`/signin?from=${pathname}`);
86-
} else {
87-
setFollowLoading(index);
88-
const res = await unfollowUser(item.user.user_id);
89-
setFollowLoading(null);
90-
if (res.success) {
91-
const _prevList = [...list];
92-
_prevList[index].mutual = type;
93-
setList(_prevList);
94-
}
95-
}
96-
};
97-
98-
const tabs = ['Followers', 'Following'];
99-
10022
return (
101-
<div className="md:pl-[410px] md:pb-14 md:pr-14">
102-
<TabBarWidget
103-
tabs={tabs}
104-
tabClassName="h-14 md:h-9 md:px-6"
105-
current={tabs.findIndex(tab => tab.toLowerCase() === params.follow)}
106-
onChange={tabIndex => router.push(`/u/${params.handle}/${tabs[tabIndex].toLowerCase()}`)}
107-
/>
108-
<div>
109-
<ul>
110-
{list?.map((i, k) => <li key={`user-follow-${params.follow}-${i.user_id}`} className="flex items-center mt-6">
111-
<Avatar size={60} user={i.user} />
112-
<div className="flex-1 h-12 ml-4 mr-8">
113-
<h6>
114-
<a href={`/u/${i.user.user_handle}`}>{i.user.user_nick_name}</a>
115-
</h6>
116-
<p>{i.user.user_handle}</p>
117-
</div>
118-
{user?.base.user_id === i.user.user_id ? <></> : i.mutual ? <Button
119-
className="!h-10 group"
120-
variant="outlined"
121-
loading={followLoading === k}
122-
onClick={() => unfollow(i, k, false)}
123-
>
124-
<span className="!font-bold block group-hover:hidden">Following</span>
125-
<span className="!font-bold hidden group-hover:block">Unfollow</span>
126-
</Button> : <Button
127-
loading={followLoading === k}
128-
variant="contained"
129-
className="!h-10"
130-
onClick={() => follow(i, k, true)}
131-
>
132-
<SvgIcon name="plus" size={16} />
133-
<span className="!font-bold">Follow</span>
134-
</Button>}
135-
</li>)}
136-
</ul>
137-
</div>
138-
{list?.length !== 0 && list?.length < count && !loading && (
139-
<div onClick={() => setPage(page + 1)} className="flex bg-gray-400 justify-center gap-2 h-9 items-center cursor-pointer rounded group">
140-
<svg className="opacity-40 transition-all group-hover:opacity-80" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
141-
<g>
142-
<path d="M14.25 4.75L9.5 9.5L4.75 4.75" stroke="#1A1A1A" strokeLinecap="round" strokeLinejoin="round"/>
143-
<path d="M14.25 9.5L9.5 14.25L4.75 9.5" stroke="#1A1A1A" strokeLinecap="round" strokeLinejoin="round"/>
144-
</g>
145-
</svg>
146-
<span className="opacity-40 transition-all group-hover:opacity-80">More</span>
147-
</div>
148-
)}
149-
{(list?.length === 0 || list === null) && !loading && <NoData />}
150-
{loading && (
151-
<div className="flex justify-center pt-10">
152-
<Loader color="#1a1a1a" />
153-
</div>
154-
)}
155-
</div>
23+
<FollowerListViewWidget userHandle={params.handle} followType={params.follow} />
15624
);
15725
}

src/domain/profile/repository.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,20 @@ async function fetchUserActivityList(uid) {
4848
return httpClient.get(`/user/info/${uid}/creator/activity`);
4949
}
5050

51-
function resolvePaginationParams(pageNum) {
52-
const pageSize = 20;
53-
54-
return {
55-
skip: (pageNum - 1) * pageSize,
56-
take: pageSize,
57-
};
51+
function resolvePaginationParams(params) {
52+
return { ...params, take: 20 };
5853
}
5954

6055
async function fetchFollowerList(params = {}) {
61-
const { handle, pageNum } = params;
56+
const { handle, ...others } = params;
6257

63-
return httpClient.get(`/user/${handle}/followers`, { params: resolvePaginationParams(pageNum) });
58+
return httpClient.get(`/user/${handle}/followers`, { params: resolvePaginationParams(others) });
6459
}
6560

6661
async function fetchFollowedList(params = {}) {
67-
const { handle, pageNum } = params;
62+
const { handle, ...others } = params;
6863

69-
return httpClient.get(`/user/${handle}/following`, { params: resolvePaginationParams(pageNum) });
64+
return httpClient.get(`/user/${handle}/following`, { params: resolvePaginationParams(others) });
7065
}
7166

7267
async function followUser(uid) {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Copyright 2024 OpenBuild
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { useRouter } from 'next/navigation';
18+
import { useState } from 'react';
19+
20+
import Avatar from '@/components/Avatar';
21+
import { Button } from '@/components/Button';
22+
import { SvgIcon } from '@/components/Image';
23+
import LoadableList from '@/components/loadable-list';
24+
25+
import { useUser } from '#/state/application/hooks';
26+
27+
import useAuthGuard from '../../../auth/hooks/useAuthGuard';
28+
import { fetchFollowerList, fetchFollowedList, followUser, unfollowUser } from '../../repository';
29+
import TabBarWidget from '../../widgets/tab-bar';
30+
31+
const followFetchMap = {
32+
followers: fetchFollowerList,
33+
following: fetchFollowedList,
34+
};
35+
36+
function FollowerList({ list, followType, update }) {
37+
const user = useUser();
38+
const { withAuth } = useAuthGuard();
39+
const [followLoading, setFollowLoading] = useState(false);
40+
41+
const wrapHandler = updateStatus => (item, index, type) => withAuth(async () => {
42+
setFollowLoading(index);
43+
const res = await updateStatus(item.user.user_id);
44+
setFollowLoading(null);
45+
if (res.success) {
46+
const _prevList = [...list];
47+
_prevList[index].mutual = type;
48+
update(_prevList);
49+
}
50+
});
51+
52+
const follow = wrapHandler(followUser);
53+
const unfollow = wrapHandler(unfollowUser);
54+
55+
return (
56+
<div>
57+
<ul>
58+
{list?.map((i, k) => <li key={`user-follow-${followType}-${i.user_id}`} className="flex items-center mt-6">
59+
<Avatar size={60} user={i.user} />
60+
<div className="flex-1 h-12 ml-4 mr-8">
61+
<h6>
62+
<a href={`/u/${i.user.user_handle}`}>{i.user.user_nick_name}</a>
63+
</h6>
64+
<p>{i.user.user_handle}</p>
65+
</div>
66+
{user?.base.user_id === i.user.user_id ? <></> : i.mutual ? (
67+
<Button
68+
className="!h-10 group"
69+
variant="outlined"
70+
loading={followLoading === k}
71+
onClick={() => unfollow(i, k, false)}
72+
>
73+
<span className="!font-bold block group-hover:hidden">Following</span>
74+
<span className="!font-bold hidden group-hover:block">Unfollow</span>
75+
</Button>
76+
) : (
77+
<Button
78+
loading={followLoading === k}
79+
variant="contained"
80+
className="!h-10"
81+
onClick={() => follow(i, k, true)}
82+
>
83+
<SvgIcon name="plus" size={16} />
84+
<span className="!font-bold">Follow</span>
85+
</Button>
86+
)}
87+
</li>)}
88+
</ul>
89+
</div>
90+
);
91+
}
92+
93+
function FollowerListView({ userHandle, followType }) {
94+
const router = useRouter();
95+
const tabs = ['Followers', 'Following'];
96+
97+
return (
98+
<div className="md:pl-[410px] md:pb-14 md:pr-14">
99+
<TabBarWidget
100+
tabs={tabs}
101+
tabClassName="h-14 md:h-9 md:px-6"
102+
current={tabs.findIndex(tab => tab.toLowerCase() === followType)}
103+
onChange={tabIndex => router.push(`/u/${userHandle}/${tabs[tabIndex].toLowerCase()}`)}
104+
/>
105+
<LoadableList
106+
params={{ handle: userHandle }}
107+
fetch={followFetchMap[followType]}
108+
resolveResponse={res => ({ list: res.data.list, total: res.data.count })}
109+
renderList={(list, update) => <FollowerList followType={followType} list={list} update={update} />}
110+
/>
111+
</div>
112+
);
113+
}
114+
115+
export default FollowerListView;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright 2024 OpenBuild
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export { default } from './FollowerList';

src/shared/components/loadable-list/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function LoadableList({ params, fetch, resolveResponse, renderList }) {
4949

5050
return (
5151
<>
52-
{list.length > 0 && renderList(list)}
52+
{list.length > 0 && renderList(list, setList)}
5353
{loading ? (
5454
<div className="flex justify-center">
5555
<Loader color="#1a1a1a" />

0 commit comments

Comments
 (0)