diff --git a/__test__/LandingSection.test.tsx b/__test__/LandingSection.test.tsx deleted file mode 100644 index 1f90126..0000000 --- a/__test__/LandingSection.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { render, screen, act } from "@testing-library/react"; -import LandingSection from "app/components/LandingSection"; -import { mockData } from "./mock/mock_CmsData"; - -describe("", () => { - test("LandingSectionコンポーネントが正しく表示されるかのテスト", async () => { - render(); - - await act(async () => {}); - - expect(screen.getByText(mockData.title || "")).toBeInTheDocument(); - }); - - test("アプリケーション名がレンダリングされる", async () => { - render(); - - await act(async () => {}); - - expect( - screen.getByText(mockData.applicationName || "") - ).toBeInTheDocument(); - }); - - test("各サブタイトルとテキストがレンダリングされる", async () => { - render(); - - await act(async () => {}); - - expect(screen.getByText(mockData.subTitle1 || "")).toBeInTheDocument(); - expect(screen.getByText(mockData.text1)).toBeInTheDocument(); - expect(screen.getByText(mockData.subTitle2 || "")).toBeInTheDocument(); - expect(screen.getByText(mockData.text2)).toBeInTheDocument(); - expect(screen.getByText(mockData.subTitle3 || "")).toBeInTheDocument(); - expect(screen.getByText(mockData.text3)).toBeInTheDocument(); - }); - - test("最後のメッセージがレンダリングされる", async () => { - await act(async () => { - render(); - }); - expect(screen.getByText(mockData.lastMessage || "")).toBeInTheDocument(); - }); - - test("画像が正しいsrc属性でレンダリングされる", async () => { - render(); - - const imageElement = screen.getByAltText("Home"); - const imageElement_img1 = screen.getByAltText("Image 1"); - const imageElement_img2 = screen.getByAltText("Image 2"); - const imageElement_img3 = screen.getByAltText("Image 3"); - - await act(async () => {}); - - expect(imageElement).toHaveAttribute("src"); - expect(imageElement.getAttribute("src")).toContain( - encodeURIComponent(mockData.icon?.url ?? "") - ); - - expect(imageElement_img1).toHaveAttribute("src"); - expect(imageElement_img1.getAttribute("src")).toContain( - mockData.img1?.url ?? "" - ); - - expect(imageElement_img2).toHaveAttribute("src"); - expect(imageElement_img2.getAttribute("src")).toContain( - mockData.img2?.url ?? "" - ); - - expect(imageElement_img3).toHaveAttribute("src"); - expect(imageElement_img3.getAttribute("src")).toContain( - mockData.img3?.url ?? "" - ); - }); -}); diff --git a/__test__/UpdatePage.test.tsx b/__test__/UpdatePage.test.tsx deleted file mode 100644 index 3836abe..0000000 --- a/__test__/UpdatePage.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { render, waitFor, screen, act } from "@testing-library/react"; -import { config } from "lib/config"; -import UpdatePage from "app/updatePlan/[id]/page"; -import { mockData } from "./mock/mock_UpdatePage"; - -jest.mock("next/headers", () => ({ - headers: jest.fn(() => ({ - get: jest.fn(() => "test-host"), - })), -})); - -jest.mock("next/navigation", () => ({ - useRouter: jest.fn(), -})); - -global.fetch = jest.fn(); //fetch関数をモック化 - -describe("UpdatePageコンポーネントに関するテスト", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test("APIから正しくデータを取得する", async () => { - (global.fetch as jest.Mock).mockResolvedValueOnce({ - json: jest.fn().mockResolvedValueOnce(mockData), - }); - - render(await UpdatePage({ params: { id: 1 } })); - await act(async () => {}); - - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith( - `${config.apiPrefix}test-host/api/plan/update/1`, //仮としてidが1のデータを取得する - expect.objectContaining({ - cache: "no-store", - method: "GET", - headers: { - "x-api-key": expect.any(String), - }, - }) - ); - }); - }); - - test("UpdatePageCoreに正しいpropsを渡し、それが画面に表示されていることを確認する", async () => { - (global.fetch as jest.Mock).mockResolvedValueOnce({ - json: jest.fn().mockResolvedValueOnce(mockData), - }); - - render(await UpdatePage({ params: { id: 1 } })); - - await act(async () => {}); - - expect(screen.getByDisplayValue("テストタイトル")).toBeInTheDocument(); - expect(screen.getByDisplayValue("テスト内容")).toBeInTheDocument(); - expect(screen.getByDisplayValue("講座1")).toBeInTheDocument(); - expect(screen.getByDisplayValue("講座2")).toBeInTheDocument(); - }); -}); diff --git a/app/allPost/[id]/components/CourseReview.tsx b/app/allPost/[id]/components/CourseReview.tsx index 9e33a1e..3deb1af 100644 --- a/app/allPost/[id]/components/CourseReview.tsx +++ b/app/allPost/[id]/components/CourseReview.tsx @@ -1,5 +1,4 @@ "use client"; -import useUser from "app/hooks/useUser"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useRouter } from "next/navigation"; @@ -9,8 +8,9 @@ import toast from "react-hot-toast"; interface CourseReviewProps { id: number; + auth_id: string; } -const CourseReview = ({ id }: CourseReviewProps) => { +const CourseReview = ({ id, auth_id }: CourseReviewProps) => { const { register, handleSubmit, @@ -21,7 +21,6 @@ const CourseReview = ({ id }: CourseReviewProps) => { authorId: 0, }, }); - const { user } = useUser(); const router = useRouter(); const [isLoading, setIsLoading] = useState(false); @@ -36,8 +35,8 @@ const CourseReview = ({ id }: CourseReviewProps) => { method: "POST", body: JSON.stringify({ title: data.title, - id: Number(id), - authorId: user?.id, + planId: Number(id), + auth_id: auth_id, }), headers: { "Content-Type": "application/json", diff --git a/app/allPost/[id]/components/ReviewSection.tsx b/app/allPost/[id]/components/ReviewSection.tsx index 4ecbcd2..3bf9f5c 100644 --- a/app/allPost/[id]/components/ReviewSection.tsx +++ b/app/allPost/[id]/components/ReviewSection.tsx @@ -3,8 +3,9 @@ import ParticleReview from "./ParticleReview"; interface ReviewSectionProps { id: number; + auth_id: string; } -const ReviewSection = ({ id }: ReviewSectionProps) => { +const ReviewSection = ({ id, auth_id }: ReviewSectionProps) => { return ( <>

@@ -13,7 +14,7 @@ const ReviewSection = ({ id }: ReviewSectionProps) => {

- +
diff --git a/app/allPost/[id]/page.tsx b/app/allPost/[id]/page.tsx index d6cb4e6..55c6967 100644 --- a/app/allPost/[id]/page.tsx +++ b/app/allPost/[id]/page.tsx @@ -6,6 +6,7 @@ import UserInfoCard from "./components/UserInfoCard"; import PlanDetails from "./components/PlanDetails"; import CourseListSection from "./components/CourseListSection"; import ReviewSection from "./components/ReviewSection"; +import useSeverUser from "app/hooks/useSeverUser"; async function getDetailData(id: number, host: string) { const res = await fetch(`${config.apiPrefix}${host}/api/plan/${id}`, { @@ -21,6 +22,8 @@ const SpecificPage = async ({ params }: { params: { id: number } }) => { const host = headers().get("host"); const CourseData = await getDetailData(params.id, host!); const { title, content, user, courses } = CourseData; + const { session } = useSeverUser(); + const auth_id = await session(); return ( <>
@@ -58,7 +61,7 @@ const SpecificPage = async ({ params }: { params: { id: number } }) => {
{/* レビューセクション */} - + ); diff --git a/app/api/auth/[id]/route.ts b/app/api/auth/[id]/route.ts new file mode 100644 index 0000000..d0b2bb8 --- /dev/null +++ b/app/api/auth/[id]/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from "next/server"; +import prisma from "utils/prisma/prismaClient"; + +//サーバーサイドからプロフィール情報を取得するAPI(動作確認済み) +export const GET = async (req: NextRequest) => { + try { + await prisma.$connect(); + const auth_id: string = req.url.split("/auth/")[1]; + const user = await prisma.user.findUnique({ + where: { auth_id }, + select: { + id: true, + auth_id: true, + name: true, + email: true, + university: true, + faculty: true, + department: true, + grade: true, + }, + }); + return NextResponse.json({ message: "Success", user }, { status: 200 }); + } catch (error) { + return NextResponse.json({ message: "Error", error }, { status: 500 }); + } finally { + await prisma.$disconnect(); + } +}; diff --git a/app/api/auth/confirm/route.ts b/app/api/auth/confirm/route.ts new file mode 100644 index 0000000..b77494e --- /dev/null +++ b/app/api/auth/confirm/route.ts @@ -0,0 +1,28 @@ +import { type EmailOtpType } from "@supabase/supabase-js"; +import { type NextRequest } from "next/server"; + +import { redirect } from "next/navigation"; +import { createClient } from "utils/supabase/sever"; + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const token_hash = searchParams.get("token_hash"); + const type = searchParams.get("type") as EmailOtpType | null; + const next = searchParams.get("next") ?? "/"; + + if (token_hash && type) { + const supabase = createClient(); + + const { error } = await supabase.auth.verifyOtp({ + type, + token_hash, + }); + if (!error) { + // redirect user to specified redirect URL or root of app + redirect(next); + } + } + + // redirect the user to an error page with some instructions + redirect("/error"); +} diff --git a/app/api/plan/detail/[id]/route.ts b/app/api/plan/detail/[id]/route.ts index ef01bea..61b8774 100644 --- a/app/api/plan/detail/[id]/route.ts +++ b/app/api/plan/detail/[id]/route.ts @@ -5,13 +5,14 @@ export async function GET( req: Request, { params }: { params: { id: string } } ) { - const userId = params.id; + const auth_id = params.id; const CourseDetailData = await prisma.user.findUnique({ where: { - id: parseInt(userId), + auth_id: auth_id, }, - include: { + select: { plans: true, + auth_id: true, }, }); return NextResponse.json(CourseDetailData); diff --git a/app/api/plan/route.ts b/app/api/plan/route.ts index 3720bde..c1d72e0 100644 --- a/app/api/plan/route.ts +++ b/app/api/plan/route.ts @@ -4,9 +4,23 @@ import { NextResponse } from "next/server"; // Plan投稿用API export const POST = async (req: Request) => { try { - const { title, content, userId } = await req.json(); + const { title, content, auth_id } = await req.json(); await prisma.$connect(); + + const getUser = await prisma.user.findUnique({ + where: { + auth_id: auth_id, + }, + select: { + id: true, + }, + }); + if (!getUser?.id) { + return NextResponse.json({ message: "User not found" }, { status: 404 }); + } + const userId = getUser.id; + const post = await prisma.plan.create({ data: { title, diff --git a/app/api/post/[id]/route.ts b/app/api/post/[id]/route.ts new file mode 100644 index 0000000..dd0b419 --- /dev/null +++ b/app/api/post/[id]/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import prisma from "utils/prisma/prismaClient"; + +// ユーザーが投稿したレビューを取得するAPI +export async function GET( + req: Request, + { params }: { params: { id: string } } +) { + const auth_id = params.id; + const data = await prisma.user.findUnique({ + where: { + auth_id: auth_id, + }, + select: { + posts: { + select: { + id: true, + title: true, + content: true, + createdAt: true, + }, + }, + }, + }); + + return NextResponse.json(data); +} diff --git a/app/api/post/coursepost/route.ts b/app/api/post/coursepost/route.ts index f89f6a1..bd5f7e6 100644 --- a/app/api/post/coursepost/route.ts +++ b/app/api/post/coursepost/route.ts @@ -3,19 +3,32 @@ import prisma from "utils/prisma/prismaClient"; export const POST = async (req: Request) => { try { - const { title, id, authorId } = await req.json(); + const { title, planId, auth_id } = await req.json(); await prisma.$connect(); + + const author = await prisma.user.findUnique({ + where: { + auth_id: auth_id, + }, + select: { + id: true, + }, + }); + const post = await prisma.post.create({ data: { title: title, - planId: id, - authorId: authorId, + planId: planId, + authorId: author?.id, }, }); return NextResponse.json({ message: "Success", post }, { status: 201 }); } catch (error) { - return NextResponse.json({ message: "Error", error }, { status: 500 }); + return NextResponse.json( + { message: "エラーが発生しました", error }, + { status: 500 } + ); } finally { await prisma.$disconnect(); } diff --git a/app/components/Session.tsx b/app/components/Session.tsx index 87da1ff..8577bc2 100644 --- a/app/components/Session.tsx +++ b/app/components/Session.tsx @@ -1,14 +1,13 @@ -"use client"; import Link from "next/link"; import React from "react"; -import useUser from "../hooks/useUser"; - -const HomeSession = () => { - const { session } = useUser(); +import useSeverUser from "app/hooks/useSeverUser"; +const HomeSession = async () => { + const { session } = useSeverUser(); + const isSession = await session(); return (
- {session ? ( + {isSession ? (
) : (
- - + + +
)}
diff --git a/app/components/layout/Header.tsx b/app/components/layout/Header.tsx index 716f17b..6d90c8d 100644 --- a/app/components/layout/Header.tsx +++ b/app/components/layout/Header.tsx @@ -1,46 +1,46 @@ -"use client"; +import useSeverUser from "app/hooks/useSeverUser"; import Link from "next/link"; -import { Sling as Hamburger } from "hamburger-react"; -import { useState } from "react"; -import ProfileDrawer from "./ProfileDrawer"; -import useUser from "app/hooks/useUser"; +import HamburgerMenu from "./Modal/HamburgerMenu"; -const Header = () => { - const [isOpen, setOpen] = useState(false); - const { session } = useUser(); +const Header = async () => { + const { session } = useSeverUser(); + const sessionId = await session(); return ( -
+
- + ClassPlanner -
- - setOpen(false)} /> -
+
diff --git a/app/components/layout/Modal/HamburgerMenu.tsx b/app/components/layout/Modal/HamburgerMenu.tsx new file mode 100644 index 0000000..90d20ea --- /dev/null +++ b/app/components/layout/Modal/HamburgerMenu.tsx @@ -0,0 +1,21 @@ +"use client"; +import { Sling as Hamburger } from "hamburger-react"; + +import ProfileDrawer from "../ProfileDrawer"; +import { useState } from "react"; + +const HamburgerMenu = ({ sessionId }: { sessionId: string | undefined }) => { + const [isOpen, setOpen] = useState(false); + return ( +
+ + setOpen(false)} + sessionId={sessionId} + /> +
+ ); +}; + +export default HamburgerMenu; diff --git a/app/components/layout/ProfileDrawer.tsx b/app/components/layout/ProfileDrawer.tsx index f948e20..dc9d9f2 100644 --- a/app/components/layout/ProfileDrawer.tsx +++ b/app/components/layout/ProfileDrawer.tsx @@ -2,15 +2,15 @@ import { Fragment } from "react"; import { Dialog, Transition } from "@headlessui/react"; import { IoClose } from "react-icons/io5"; import Link from "next/link"; -import useUser from "app/hooks/useUser"; import Image from "next/image"; + interface ProfileDrawerProps { isOpen: boolean; onClose: () => void; + sessionId: string | undefined; } -const ProfileDrawer = ({ isOpen, onClose }: ProfileDrawerProps) => { - const { session } = useUser(); +const ProfileDrawer = ({ isOpen, onClose, sessionId }: ProfileDrawerProps) => { return (
@@ -81,7 +81,7 @@ const ProfileDrawer = ({ isOpen, onClose }: ProfileDrawerProps) => { 全ての投稿を見る - {session ? ( + {sessionId ? ( <>
  • {
  • - {session ? ( + {sessionId ? ( <>