Skip to content

Commit

Permalink
フォロー機能を実装 (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
KensukeOta authored Aug 29, 2024
1 parent 7b85602 commit ea6a255
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ Next.jsのApp Routerを使用したブログアプリです。
- ページネーション機能
- いいね機能
- タグ機能
- フォロー機能
43 changes: 43 additions & 0 deletions app/(guest)/[name]/followers/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Metadata } from "next";
import type { User } from "@/app/types/User";
import { fetchFollowers } from "@/app/lib/data";
import { DefaultLayout } from "@/app/components/templates/DefaultLayout/DefaultLayout";
import { UserItem } from "@/app/components/organisms/UserItem";

export function generateMetadata(
{ params }: { params: { name: string } }
): Metadata {
const name = decodeURIComponent(params.name);

return {
title: `${name}`,
};
}

export default async function Page({
params
}: {
params: { name: string }
}) {
const users: User[] = await fetchFollowers(params.name);

const userItems = users.length > 0 ? (
users.map(user => (
<UserItem
key={user.id}
user={user}
/>
))
) : (
<p className="font-bold text-xs text-center p-4">フォロワーはいません</p>
);

return (
<DefaultLayout className="p-4">
<section className="bg-white">
<h1 className="font-bold px-4 py-4">フォロワー</h1>
{userItems}
</section>
</DefaultLayout>
);
}
43 changes: 43 additions & 0 deletions app/(guest)/[name]/following_users/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Metadata } from "next";
import type { User } from "@/app/types/User";
import { fetchFollowingUsers } from "@/app/lib/data";
import { DefaultLayout } from "@/app/components/templates/DefaultLayout/DefaultLayout";
import { UserItem } from "@/app/components/organisms/UserItem";

export function generateMetadata(
{ params }: { params: { name: string } }
): Metadata {
const name = decodeURIComponent(params.name);

return {
title: `${name}`,
};
}

export default async function Page({
params
}: {
params: { name: string }
}) {
const users: User[] = await fetchFollowingUsers(params.name);

const userItems = users.length > 0 ? (
users.map(user => (
<UserItem
key={user.id}
user={user}
/>
))
) : (
<p className="font-bold text-xs text-center p-4">フォローしているユーザーはいません</p>
);

return (
<DefaultLayout className="p-4">
<section className="bg-white">
<h1 className="font-bold px-4 py-4">フォローしているユーザー</h1>
{userItems}
</section>
</DefaultLayout>
);
}
35 changes: 35 additions & 0 deletions app/components/atoms/FollowButton/FollowButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import type { User } from "@/app/types/User";
import { useSession } from "next-auth/react";
import { createFollow, deleteFollow } from "@/app/lib/actions";
import { SubmitButton } from "@/app/components/atoms/SubmitButton";

export const FollowButton = ({ user }: { user: User }) => {
const session = useSession();

const Following = user.followers.find((follower) => follower.id === session.data?.user?.id);

const handleSubmit = () => {
if (!session.data?.user) {
alert("ログインすると「フォロー」をすることができます!");
return;
}

if (Following) {
deleteFollow(session?.data?.user?.id as string, user.id);
} else {
createFollow(session?.data?.user?.id as string, user.id)
}
};

return (
<form action={handleSubmit}>
<SubmitButton
className={`${Following ? "bg-gray-100 text-black hover:bg-gray-200" : "bg-black text-white hover:opacity-70"} border font-bold mt-6 px-4 py-1 rounded-lg w-full`}
>
{Following ? "フォロー中" : "フォロー"}
</SubmitButton>
</form>
);
};
1 change: 1 addition & 0 deletions app/components/atoms/FollowButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./FollowButton";
45 changes: 45 additions & 0 deletions app/components/organisms/UserItem/UserItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { User } from "@/app/types/User";
import Link from "next/link";
import { SessionProvider } from "next-auth/react";
import { auth } from "@/auth";
import { UserIcon } from "@/app/components/atoms/UserIcon";
import { FollowButton } from "@/app/components/atoms/FollowButton";

export const UserItem = async ({ user }: { user: User }) => {
const session = await auth();

return (
<div className="border-t p-4 bg-white">
<section className="flex gap-4">
<UserIcon
user={user}
width={48}
height={48}
/>

<div>
<Link href={`/${user.name}`} className="w-full hover:underline">
<span className="font-bold text-sm">{user.name}</span>
</Link>
<ul className="flex gap-2">
{user.tags.map(tag => (
<li key={tag.id}>
<Link
href={`/tags/${tag.name}`}
className="bg-gray-100 px-1.5 text-sm text-black/60 rounded hover:bg-gray-200"
>
{tag.name}
</Link>
</li>
))}
</ul>
</div>
</section>
{session?.user?.id !== user.id &&
<SessionProvider>
<FollowButton user={user} />
</SessionProvider>
}
</div>
);
};
1 change: 1 addition & 0 deletions app/components/organisms/UserItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./UserItem";
17 changes: 16 additions & 1 deletion app/components/organisms/UserProfile/UserProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { User } from "@/app/types/User";
import Link from "next/link";
import { SessionProvider } from "next-auth/react";
import { auth } from "@/auth";
import { UserIcon } from "@/app/components/atoms/UserIcon";
import { UserTagsForm } from "@/app/components/organisms/UserTagsForm"
import { FollowButton } from "@/app/components/atoms/FollowButton";

export const UserProfile = async ({ user }: { user: User }) => {
const session = await auth();
Expand All @@ -25,7 +27,20 @@ export const UserProfile = async ({ user }: { user: User }) => {
))}
</ul>
{session?.user?.id === user.id && <UserTagsForm user={user} />}


<div className="border-t flex items-center justify-around mt-2 pt-2">
<Link href={`/${user.name}/following_users`} className="text-xs hover:underline">
{user.followings.length}
<br />
フォロー
</Link>
<Link href={`/${user.name}/followers`} className="text-xs hover:underline">
{user.followers.length}
<br />
フォロワー
</Link>
</div>
{session?.user?.id !== user.id && <SessionProvider><FollowButton user={user} /></SessionProvider>}
</section>
);
};
46 changes: 46 additions & 0 deletions app/lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,49 @@ export async function deleteLike(likeId: string) {

revalidatePath("/")
}

export async function createFollow(followerId: string, followId: string) {
try {
const res = await fetch(`${process.env.API_URL}/v1/follows`, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ follower_id: followerId, following_id: followId }),
});
if (!res.ok) {
const errors = await res.json();
console.log(errors);
throw new Error(errors);
}
} catch (error) {
console.log(error);
return;
}

revalidatePath("/")
}

export async function deleteFollow(followerId: string, followId: string) {
try {
const res = await fetch(`${process.env.API_URL}/v1/follows`, {
method: "DELETE",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ follower_id: followerId, following_id: followId }),
});
if (!res.ok) {
const errors = await res.json();
console.log(errors);
throw new Error(errors);
}
} catch (error) {
console.log(error);
return;
}

revalidatePath("/")
}
44 changes: 44 additions & 0 deletions app/lib/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,48 @@ export async function fetchPost(id: string) {
} catch (error) {
console.log(error);
}
}

export async function fetchFollowingUsers(name: string) {
noStore();

try {
const res = await fetch(`${process.env.API_URL}/v1/users/${name}/recent_followings`, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
});
if (!res.ok) {
const errors = await res.json();
console.log(errors);
throw new Error(errors);
}
const data = await res.json();
return data;
} catch (error) {
console.log(error);
}
}

export async function fetchFollowers(name: string) {
noStore();

try {
const res = await fetch(`${process.env.API_URL}/v1/users/${name}/recent_followers`, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
});
if (!res.ok) {
const errors = await res.json();
console.log(errors);
throw new Error(errors);
}
const data = await res.json();
return data;
} catch (error) {
console.log(error);
}
}
2 changes: 2 additions & 0 deletions app/types/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ export interface User {
posts: Post[];
likes: Like[];
tags: Tag[];
followings: User[];
followers: User[];
}

0 comments on commit ea6a255

Please sign in to comment.