diff --git a/src/app/(menu)/(public)/[username]/_components/Following.tsx b/src/app/(menu)/(public)/[username]/_components/Following.tsx new file mode 100644 index 00000000..c749002e --- /dev/null +++ b/src/app/(menu)/(public)/[username]/_components/Following.tsx @@ -0,0 +1,14 @@ +'use client'; + +import FollowList from '@/app/(menu)/(public)/[username]/_components/layouts/FollowList'; +import { useFollowing } from '@/swr/client/follows'; +import { UserWithFollows } from '@cuculus/cuculus-api'; + +/** + * フォロー一覧表示コンポーネント + * @param user + * @constructor + */ +export default function Following({ user }: { user: UserWithFollows }) { + return ; +} diff --git a/src/app/(menu)/(public)/[username]/_components/layouts/FFProfileCard.tsx b/src/app/(menu)/(public)/[username]/_components/layouts/FFProfileCard.tsx new file mode 100644 index 00000000..f915dbda --- /dev/null +++ b/src/app/(menu)/(public)/[username]/_components/layouts/FFProfileCard.tsx @@ -0,0 +1,112 @@ +'use client'; +import FollowButton from '@/app/(menu)/(public)/[username]/_components/elements/FollowButton'; +import UserIcon from '@/app/(menu)/(public)/[username]/_components/elements/UserIcon'; +import { Box, Typography, styled } from '@mui/material'; + +const UnselectableCard = styled('div')` + border-bottom: 1px solid ${({ theme }) => theme.palette.grey[100]}; + background-color: ${({ theme }) => theme.palette.background.paper}; + color: rgba(0, 0, 0, 0.87); +`; + +const Flex = styled(Box)` + display: flex; + flex-wrap: nowrap; +`; + +const VFlex = styled(Flex)` + flex-direction: column; + width: 100%; +`; + +const HFlex = styled(Flex)` + flex-direction: row; +`; + +const HFlexS = styled(Flex)` + flex-direction: row; + justify-content: space-between; +`; + +const FillFlex = styled(Box)` + flex-grow: 1; +`; + +const DisplayName = styled(Typography)` + word-wrap: break-word; + font-weight: bold; + font-size: 20px; +`; + +const ButtonArea = styled(Box)` + width: 100%; + text-align: right; +`; + +const UserName = styled(Typography)` + color: #8899a6; + font-size: 15px; +`; + +const Avater = styled(UserIcon)` + aspect-ratio: 1; + height: 64px; + width: 64px; + margin: 0 10px auto; + + ${({ theme }) => theme.breakpoints.down('desktop')} { + margin: 0 10px auto; + height: 64px; + width: 64px; + } +`; + +const Bio = styled(Typography)` + white-space: pre-wrap; + margin-bottom: 12px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +`; + +type Props = { + id: number; + name: string; + username: string; + profileImageUrl: string; + bio: string; +}; + +export default function FFProfileCard({ + name, + username, + profileImageUrl, + bio, + id, +}: Props) { + return ( + + + + + + + {name} + @{username} + + {/* フォローされてるか表示 */} + {/* */} + + + + + + {bio} + + + + + ); +} diff --git a/src/app/(menu)/(public)/[username]/_components/layouts/FollowList.tsx b/src/app/(menu)/(public)/[username]/_components/layouts/FollowList.tsx new file mode 100644 index 00000000..c147f126 --- /dev/null +++ b/src/app/(menu)/(public)/[username]/_components/layouts/FollowList.tsx @@ -0,0 +1,52 @@ +import { SWRInfiniteResponse } from 'swr/infinite'; +import { FollowList } from '@cuculus/cuculus-api'; +import { CircularProgress } from '@mui/material'; +import FFProfileCard from '@/app/(menu)/(public)/[username]/_components/layouts/FFProfileCard'; + +type Props = { + follows: SWRInfiniteResponse; +}; + +/** + * フォロー/フォロワーリスト + * @param follows + * @constructor + */ +export default function FollowList({ follows }: Props) { + const { data, isLoading, size, setSize } = follows; + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + <> +
現在のページ: {size}
+ + {data?.map((follows, index) => ( +
+ {follows?.users.map((User, number) => ( + + ))} +
+ ))} + + ); +} diff --git a/src/app/(menu)/(public)/[username]/followers/page.tsx b/src/app/(menu)/(public)/[username]/followers/page.tsx index acda81f8..58b923bf 100644 --- a/src/app/(menu)/(public)/[username]/followers/page.tsx +++ b/src/app/(menu)/(public)/[username]/followers/page.tsx @@ -1,10 +1,40 @@ -import ComingSoon from '@/app/(menu)/_components/main/ComingSoon'; import PrimaryColumn from '@/app/(menu)/_components/main/PrimaryColumn'; +import { cache } from 'react'; +import { usersApi } from '@/libs/cuculus-client'; +import { notFound } from 'next/navigation'; +import Following from '@/app/(menu)/(public)/[username]/_components/Following'; + +type Params = { params: { username: string } }; + +const getUser = cache(async (username: string) => { + try { + return await usersApi.getUserByUsername( + { username }, + { + next: { + revalidate: 300, + }, + }, + ); + } catch { + return undefined; + } +}); + +/** + * フォロワー一覧ページ + * @param params + */ +export default async function page({ params }: Params) { + const username = decodeURIComponent(params.username); + const user = await getUser(username); + if (!user) { + notFound(); + } -export default function page({}: { params: { userName: string } }) { return ( - + {/* */} ); } diff --git a/src/app/(menu)/(public)/[username]/following/page.tsx b/src/app/(menu)/(public)/[username]/following/page.tsx index acda81f8..08a45a79 100644 --- a/src/app/(menu)/(public)/[username]/following/page.tsx +++ b/src/app/(menu)/(public)/[username]/following/page.tsx @@ -1,10 +1,40 @@ -import ComingSoon from '@/app/(menu)/_components/main/ComingSoon'; import PrimaryColumn from '@/app/(menu)/_components/main/PrimaryColumn'; +import { cache } from 'react'; +import { usersApi } from '@/libs/cuculus-client'; +import { notFound } from 'next/navigation'; +import Following from '@/app/(menu)/(public)/[username]/_components/Following'; + +type Params = { params: { username: string } }; + +const getUser = cache(async (username: string) => { + try { + return await usersApi.getUserByUsername( + { username }, + { + next: { + revalidate: 300, + }, + }, + ); + } catch { + return undefined; + } +}); + +/** + * フォロー一覧ページ + * @param params + */ +export default async function page({ params }: Params) { + const username = decodeURIComponent(params.username); + const user = await getUser(username); + if (!user) { + notFound(); + } -export default function page({}: { params: { userName: string } }) { return ( - + ); } diff --git a/src/swr/client/follows.ts b/src/swr/client/follows.ts new file mode 100644 index 00000000..0d89ffba --- /dev/null +++ b/src/swr/client/follows.ts @@ -0,0 +1,53 @@ +import { useAuth } from '@/swr/client/auth'; +import { usersApi } from '@/libs/cuculus-client'; +import { getAuthorizationHeader } from '@/libs/auth'; +import useSWRInfinite from 'swr/infinite'; +import { FollowList } from '@cuculus/cuculus-api'; + +// FIXME 確認用に一旦10件にしている +const LIMIT = 10; + +type SWRKey = { + key: string; + authId?: number; + userId: number; + nextUntil?: Date; +}; + +/** + * フォロー一覧の取得 + * @param userId + */ +export const useFollowing = (userId: number) => { + const { data: authId } = useAuth(); + return useSWRInfinite< + FollowList | undefined, + Error, + (index: number, prev: FollowList | undefined) => SWRKey | null + >( + (index, previousPageData) => { + // 取得結果が空の場合は次のページがないと判断して終了 + if (previousPageData && !previousPageData.users.length) { + return null; + } + return { + key: 'useFollowing', + authId, + userId, + nextUntil: previousPageData?.nextUntil, + }; + }, + async (args) => { + try { + return await usersApi.getUserFollowing( + { id: args.userId, until: args.nextUntil, limit: LIMIT }, + { + headers: await getAuthorizationHeader(authId), + }, + ); + } catch (error) { + throw error; + } + }, + ); +};