Repository: RE6CT/jeju-olleyo
Pull request #114: Feat/#110 community page
Author: login: sohxxny
Labels: name: feat
description: 기능 개발
Created: 2025-04-14T11:30:35Z | Merged: 2025-04-14T13:44:31Z

## 📝 설명

<!-- 이 PR에서 무엇을 변경했는지 간단히 설명해주세요. -->

커뮤니티 페이지의 전체 일정을 가져오는 기능을 추가했습니다.

<br>

## 🔗 관련 이슈

<!-- 이 PR이 해결하는 이슈 번호를 적어주세요. -->

- #110

<br>

## 🔍 변경 사항

<!-- 주요 변경 사항을 목록으로 작성해주세요. -->

- 전체 일정 및 좋아요 여부 가져오기
- 로그인 시 좋아요 여부를 가져오고, 미로그인 시 모든 좋아요 값은 `false`
- 모든 플랜에 기본 이미지를 추가했습니다. 나중에 랜덤으로 추가해주시면 될 것 같아요!

<br>

## ✅ 체크리스트

<!-- PR을 제출하기 전에 확인해야 할 항목들을 체크해주세요. -->

- [x] 코드가 프로젝트의 스타일 가이드를 준수합니다.
- [x] 모든 테스트가 통과했습니다.

<br>

## ℹ️ 추가 정보

아직 정렬 기능은 넣지 않았고, 디자인은 나중에 카드 컴포넌트가 완성되면 거기에 넣을 예정이라 디테일하게 하진 않았습니다.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Launched a new Community page showcasing popular travel plans with an option to create new plans.
  - Introduced a fresh travel banner highlighting Jeju experiences with an interactive action button.

- **Style**
  - Refined global and section background colors and improved layout alignment for a cleaner, more centered interface.
  - Updated navigation links to reflect the new Community route for a streamlined user experience.
  - Added a new color option for enhanced styling throughout the application.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/public/images/jeju-banner.png b/public/images/jeju-banner.png
new file mode 100644
index 00000000..011f004c
Binary files /dev/null and b/public/images/jeju-banner.png differ
diff --git a/src/app/(mypage)/layout.tsx b/src/app/(mypage)/layout.tsx
index 41b76bb6..aba51a18 100644
--- a/src/app/(mypage)/layout.tsx
+++ b/src/app/(mypage)/layout.tsx
@@ -6,7 +6,7 @@ const Layout = ({
   children: React.ReactNode;
 }>) => {
   return (
-    <div className="flex gap-10 px-9 py-12">
+    <div className="flex gap-10 bg-gray-50 px-9 py-12">
       <MypageSidebar className="sticky top-[132px] h-fit" />
       <div className="w-full">{children}</div>
     </div>
diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx
new file mode 100644
index 00000000..9ebd8cbc
--- /dev/null
+++ b/src/app/community/page.tsx
@@ -0,0 +1,58 @@
+import JejuBanner from '@/components/features/banner/jeju-banner';
+import { PATH } from '@/constants/path.constants';
+import Link from 'next/link';
+import Duration from '@/components/commons/duration';
+import { fetchGetCurrentUser } from '@/lib/apis/auth/auth-server.api';
+import { fetchAllPlans } from '@/lib/apis/plan/plan.api';
+import PlanImage from '@/components/commons/plan-image';
+
+const Community = async () => {
+  const user = await fetchGetCurrentUser();
+  const userId = user.user?.id || null;
+
+  const plans = await fetchAllPlans(userId);
+
+  return (
+    <>
+      <JejuBanner
+        imageUrl="/images/jeju-banner.png"
+        title="나만의 제주 여행 계획하기"
+        buttonText="내 일정 만들러 가기"
+        buttonUrl={PATH.PLAN_NEW}
+      />
+      <div className="flex w-full max-w-[1024px] flex-col gap-3 p-9">
+        <h2 className="semibold-22">올레 인기 일정</h2>
+        <div className="grid w-full grid-cols-2 gap-x-3 gap-y-5 sm:grid-cols-3">
+          {plans.map((plan) => (
+            <Link href={`/plan-detail/${plan.planId}`} key={plan.planId}>
+              <div className="relative aspect-[310/216]">
+                <PlanImage
+                  image={plan.planImg}
+                  title={plan.title}
+                  className="rounded-12"
+                />
+              </div>
+
+              <div className="flex flex-col gap-1 p-2">
+                <p className="medium-16 line-clamp-1">{plan.title}</p>
+                <p className="regular-14 text-description line-clamp-2">
+                  {plan.description}
+                </p>
+                <div className="medium-12 flex items-center gap-2 text-gray-500">
+                  <span>{plan.nickname}</span>
+                  <Duration
+                    start={plan.travelStartDate}
+                    end={plan.travelEndDate}
+                    separator="-"
+                  />
+                </div>
+              </div>
+            </Link>
+          ))}
+        </div>
+      </div>
+    </>
+  );
+};
+
+export default Community;
diff --git a/src/app/globals.css b/src/app/globals.css
index 718f49f8..f487e6d6 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -59,7 +59,7 @@
 
   html,
   body {
-    @apply font-sans leading-normal text-gray-900;
+    @apply bg-white font-sans leading-normal text-gray-900;
   }
 }
 
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 82cb27fd..77fd6e8d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -5,19 +5,16 @@ import Header from '@/components/layouts/header';
 import AuthProvider from '@/config/auth-provider';
 import localFont from 'next/font/local';
 import Footer from '@/components/layouts/footer';
-
 export const metadata: Metadata = {
   title: '제주 올레요',
   description: '제주 여행을 떠나기 위한 일정 만들어요!',
 };
-
 const pretendard = localFont({
   src: '../../public/fonts/PretendardVariable.woff2',
   display: 'swap',
   weight: '45 920',
   variable: '--font-pretendard',
 });
-
 export default function RootLayout({
   children,
 }: Readonly<{
@@ -26,13 +23,15 @@ export default function RootLayout({
   return (
     <html lang="en">
       <body
-        className={`${pretendard.variable} font-pretendard flex min-h-screen w-full flex-col antialiased`}
+        className={`${pretendard.variable} font-pretendard flex min-h-screen w-full flex-col items-center justify-center antialiased`}
       >
         <AuthProvider>
           <Providers>
-            <Header />
-            <main className="flex-1">{children}</main>
-            <Footer />
+            <div className="flex w-full max-w-[1024px] flex-1 flex-col">
+              <Header />
+              <main className="flex-1">{children}</main>
+              <Footer />
+            </div>
           </Providers>
         </AuthProvider>
       </body>
diff --git a/src/components/features/banner/jeju-banner.tsx b/src/components/features/banner/jeju-banner.tsx
new file mode 100644
index 00000000..9a2a300f
--- /dev/null
+++ b/src/components/features/banner/jeju-banner.tsx
@@ -0,0 +1,64 @@
+'use client';
+
+import { JejuBannerProps } from '@/types/common.type';
+import { useRouter } from 'next/navigation';
+
+/**
+ * 제주 여행 계획 배너 컴포넌트
+ * 귤 캐릭터가 있는 배경 이미지 위에 타이틀과 버튼을 오버레이하는 배너
+ * @param imageUrl - 배너 배경 이미지 URL
+ * @param title - 배너 제목 텍스트
+ * @param buttonText - 버튼에 표시할 텍스트
+ * @param buttonUrl - 버튼 클릭 시 이동할 URL
+ */
+const JejuBanner = ({
+  imageUrl = '/images/jeju-banner.png',
+  title = '나만의 제주 여행 계획하기',
+  buttonText = '내 일정 만들러 가기 >',
+  buttonUrl = '/planner',
+}: JejuBannerProps) => {
+  const router = useRouter();
+
+  /** 일정 만들기 버튼 클릭 시의 이벤트 핸들러 */
+  const handleGotoNewPlan = () => {
+    const user = 'user';
+
+    if (user) {
+      alert('로그인이 필요합니다.');
+      return;
+    }
+    router.push(buttonUrl);
+  };
+
+  return (
+    <div className="relative w-full overflow-hidden">
+      {/* 배경 이미지  */}
+      <img src={imageUrl} alt="제주 여행 배너" className="aspect-auto w-full" />
+      {/* 텍스트와 버튼 오버레이 - 왼쪽 상단에 배치 */}
+      <div className="absolute left-1/2 top-[10%] flex -translate-x-1/2 transform flex-row items-center gap-2 sm:left-[5%] sm:transform-none sm:flex-col sm:items-start">
+        <h2 className="sm:bold-28 bold-22 whitespace-nowrap">{title}</h2>
+        <button
+          onClick={handleGotoNewPlan}
+          className="semibold-16 inline-flex items-center gap-1 rounded-full bg-primary-500 px-4 py-2 text-white"
+        >
+          <span className="whitespace-nowrap">{buttonText}</span>
+          <svg
+            width="12"
+            height="12"
+            viewBox="0 0 48 48"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              fillRule="evenodd"
+              clipRule="evenodd"
+              d="M34.6191 22.3726C35.1831 22.9368 35.5 23.702 35.5 24.4998C35.5 25.2976 35.1831 26.0627 34.6191 26.6269L17.5989 43.6471C17.0314 44.1952 16.2714 44.4984 15.4826 44.4916C14.6937 44.4847 13.9391 44.1683 13.3812 43.6105C12.8234 43.0526 12.507 42.298 12.5001 41.5091C12.4933 40.7203 12.7965 39.9603 13.3446 39.3928L28.2376 24.4998L13.3446 9.60669C12.7965 9.03924 12.4933 8.27924 12.5001 7.49037C12.507 6.70149 12.8234 5.94688 13.3812 5.38904C13.9391 4.8312 14.6937 4.51478 15.4826 4.50793C16.2714 4.50107 17.0314 4.80433 17.5989 5.35239L34.6191 22.3726Z"
+              fill="white"
+            />
+          </svg>
+        </button>
+      </div>
+    </div>
+  );
+};
+export default JejuBanner;
diff --git a/src/components/layouts/nav.tsx b/src/components/layouts/nav.tsx
index b3c78401..1cf75407 100644
--- a/src/components/layouts/nav.tsx
+++ b/src/components/layouts/nav.tsx
@@ -6,22 +6,19 @@ import { PATH } from '@/constants/path.constants';
 /** 헤더의 nav 영역 컴포넌트 */
 const Nav = async () => {
   const { user } = await fetchGetCurrentUser();
-  const userId = user?.id;
-
-  if (!userId) return;
 
   return (
     <nav className="mr-4 flex items-center">
       {user ? (
         <div className="flex gap-12">
           <Link href={PATH.MYPLAN}>내 여행</Link>
-          <Link href={PATH.SHAREDPLAN}>커뮤니티</Link>
-          <MypageButton userId={userId} />
+          <Link href={PATH.COMMUNITY}>커뮤니티</Link>
+          <MypageButton userId={user.id} />
         </div>
       ) : (
         <div className="flex gap-12">
           <Link href={PATH.MYPLAN}>내 여행</Link>
-          <Link href={PATH.SHAREDPLAN}>커뮤니티</Link>
+          <Link href={PATH.COMMUNITY}>커뮤니티</Link>
           <Link href={PATH.SIGNIN}>로그인</Link>
         </div>
       )}
diff --git a/src/constants/auth.constants.ts b/src/constants/auth.constants.ts
index b3afeaf4..5a1aee35 100644
--- a/src/constants/auth.constants.ts
+++ b/src/constants/auth.constants.ts
@@ -123,7 +123,7 @@ export const AUTH_ROUTES = {
     PATH.RESET_PASSWORD,
     PATH.CALLBACK,
     PATH.SEARCH,
-    PATH.SHAREDPLAN,
+    PATH.COMMUNITY,
   ],
 };
 
diff --git a/src/constants/path.constants.ts b/src/constants/path.constants.ts
index 4bb48b4a..278de393 100644
--- a/src/constants/path.constants.ts
+++ b/src/constants/path.constants.ts
@@ -5,7 +5,7 @@ export const PATH = {
   MYPLAN: '/my-plan',
   PLAN_DETAIL: '/plan-detail',
   SEARCH: '/search',
-  SHAREDPLAN: '/shared-plan',
+  COMMUNITY: '/community',
   FORGOT_PASSWORD: '/forgot-password',
   RESET_PASSWORD: '/reset-password',
   CALLBACK: '/auth/callback',
diff --git a/src/lib/apis/plan/plan.api.ts b/src/lib/apis/plan/plan.api.ts
index ca993ffc..a7240ae2 100644
--- a/src/lib/apis/plan/plan.api.ts
+++ b/src/lib/apis/plan/plan.api.ts
@@ -1,4 +1,5 @@
 'use server';
+
 import { Plan, PlanFilterOptions } from '@/types/plan.type';
 import { getServerClient } from '@/lib/supabase/server';
 import { camelize } from '@/lib/utils/camelize';
@@ -6,6 +7,7 @@ import {
   isDateGreaterThanOrEqual,
   isDateLessThanOrEqual,
 } from '@/lib/utils/date';
+import { fetchGetCurrentUser } from '../auth/auth-server.api';
 
 /**
  * 사용자의 일정 목록을 가져오는 API
@@ -104,3 +106,20 @@ export const fetchDeletePlan = async (planId: number) => {
     throw new Error('일정 삭제에 실패했습니다.');
   }
 };
+
+/**
+ * 전체 일정을 가져오는 함수
+ * @param userId - 사용자의 userId
+ * @returns 좋아요 여부가 포함된 전체 일정 목록
+ */
+export const fetchAllPlans = async (userId: string | null) => {
+  const supabase = await getServerClient();
+
+  const { data, error } = await supabase.rpc('get_plans', {
+    user_id_param: userId,
+  });
+
+  if (error) throw new Error(error.message);
+
+  return data.map(camelize);
+};
diff --git a/src/middleware.ts b/src/middleware.ts
index c08c7bd7..68f9ff85 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -33,7 +33,7 @@ export async function middleware(request: NextRequest) {
     PATH.SIGNUP,
     PATH.MYPLAN,
     PATH.SEARCH,
-    PATH.SHAREDPLAN,
+    PATH.COMMUNITY,
     PATH.FORGOT_PASSWORD,
     PATH.RESET_PASSWORD,
     PATH.CALLBACK,
diff --git a/src/types/common.type.ts b/src/types/common.type.ts
index 17803068..d2936871 100644
--- a/src/types/common.type.ts
+++ b/src/types/common.type.ts
@@ -49,6 +49,14 @@ export type DurationProps = {
   className?: string;
 };
 
+// 제주 여행 배너 컴포넌트 props
+export type JejuBannerProps = {
+  imageUrl: string;
+  title: string;
+  buttonText: string;
+  buttonUrl: string;
+};
+
 export type List = {
   value: string;
   label: string;
diff --git a/src/types/supabase.type.ts b/src/types/supabase.type.ts
index a25c7dae..e605d3f6 100644
--- a/src/types/supabase.type.ts
+++ b/src/types/supabase.type.ts
@@ -399,6 +399,19 @@ export type Database = {
           comment_count: number;
         };
       };
+      get_plans: {
+        Args: { user_id_param: string | null };
+        Returns: {
+          plan_id: number;
+          plan_img: string;
+          title: string;
+          description: string;
+          nickname: string;
+          travel_start_date: string;
+          travel_end_date: string;
+          is_liked: boolean;
+        }[];
+      };
     };
     Enums: {
       [_ in never]: never;
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 8f6e6712..647efc8a 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -61,6 +61,7 @@ const config: Config = {
         },
         blue: '#3E9BDF',
         red: '#FF0A0A',
+        description: '#909090',
         muted: {
           DEFAULT: 'hsl(var(--muted))',
           foreground: 'hsl(var(--muted-foreground))',


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>tailwind.config.ts (1)</summary><blockquote>

`64-64`: **Consider reusing the existing color instead of adding a duplicate**

I noticed that you've added a new color `description: '#909090'` which has the same hex value as the existing `gray.102: '#909090'` on line 52. To maintain a consistent design system, it's better to reuse the existing color definition rather than creating a duplicate value with a different name.


Consider using `gray-102` in your components instead of creating a duplicate color definition:

```diff
-        description: '#909090',
```

Or if you need a specific semantic name for this color, add a comment to document the relationship:

```diff
-        description: '#909090',
+        // Same as gray.102, used specifically for description text
+        description: '#909090',
```

</blockquote></details>
<details>
<summary>src/lib/apis/plan/plan.api.ts (2)</summary><blockquote>

`10-10`: **Unused import detected.**

The `fetchGetCurrentUser` function is imported but not used within this file. Consider removing this import unless you intend to use it in future changes.

```diff
-import { fetchGetCurrentUser } from '../auth/auth-server.api';
```

---

`110-125`: **Good implementation of the new community feature.**

The new `fetchAllPlans` function is well-implemented and aligns perfectly with the PR objective of retrieving the complete schedule for the community page. The function correctly handles both logged-in users (with like status) and non-logged-in users (defaults to false), as mentioned in the PR description.

A couple of suggestions for improvement:

1. Consider adding a proper return type annotation for better type safety:
```diff
-export const fetchAllPlans = async (userId: string | null) => {
+export const fetchAllPlans = async (userId: string | null): Promise<Plan[]> => {
```

2. Consider adding more specific error handling similar to other functions in this file:
```diff
-  if (error) throw new Error(error.message);
+  if (error) {
+    throw new Error('전체 일정 목록을 불러오는데 실패했습니다.');
+  }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 87610897b10dee287ad76357c975193503b8a391 and d99988db69173aaba05bd04f37dbc995a554028e.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `public/images/jeju-banner.png` is excluded by `!**/*.png`, `!public/**` and included by none

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `src/app/(mypage)/layout.tsx` (1 hunks)
* `src/app/community/page.tsx` (1 hunks)
* `src/app/globals.css` (1 hunks)
* `src/app/layout.tsx` (1 hunks)
* `src/components/features/banner/jeju-banner.tsx` (1 hunks)
* `src/components/layouts/nav.tsx` (1 hunks)
* `src/constants/auth.constants.ts` (1 hunks)
* `src/constants/path.constants.ts` (1 hunks)
* `src/lib/apis/plan/plan.api.ts` (2 hunks)
* `src/middleware.ts` (1 hunks)
* `src/types/common.type.ts` (1 hunks)
* `src/types/supabase.type.ts` (1 hunks)
* `tailwind.config.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{ts,tsx}`: TypeScript와 React 코드가 모범 사례를 준수하는지 확인해주세요.</summary>

> `**/*.{ts,tsx}`: TypeScript와 React 코드가 모범 사례를 준수하는지 확인해주세요.

- `src/constants/path.constants.ts`
- `src/app/(mypage)/layout.tsx`
- `tailwind.config.ts`
- `src/components/layouts/nav.tsx`
- `src/constants/auth.constants.ts`
- `src/types/common.type.ts`
- `src/app/layout.tsx`
- `src/middleware.ts`
- `src/app/community/page.tsx`
- `src/components/features/banner/jeju-banner.tsx`
- `src/types/supabase.type.ts`
- `src/lib/apis/plan/plan.api.ts`

</details>

</details><details>
<summary>🧬 Code Graph Analysis (7)</summary>

<details>
<summary>src/components/layouts/nav.tsx (1)</summary><blockquote>

<details>
<summary>src/constants/path.constants.ts (1)</summary>

* `PATH` (1-19)

</details>

</blockquote></details>
<details>
<summary>src/constants/auth.constants.ts (1)</summary><blockquote>

<details>
<summary>src/constants/path.constants.ts (1)</summary>

* `PATH` (1-19)

</details>

</blockquote></details>
<details>
<summary>src/app/layout.tsx (1)</summary><blockquote>

<details>
<summary>src/config/tq-provider.tsx (1)</summary>

* `Providers` (28-34)

</details>

</blockquote></details>
<details>
<summary>src/middleware.ts (1)</summary><blockquote>

<details>
<summary>src/constants/path.constants.ts (1)</summary>

* `PATH` (1-19)

</details>

</blockquote></details>
<details>
<summary>src/app/community/page.tsx (3)</summary><blockquote>

<details>
<summary>src/lib/apis/auth/auth-server.api.ts (1)</summary>

* `fetchGetCurrentUser` (156-192)

</details>
<details>
<summary>src/lib/apis/plan/plan.api.ts (1)</summary>

* `fetchAllPlans` (115-125)

</details>
<details>
<summary>src/constants/path.constants.ts (1)</summary>

* `PATH` (1-19)

</details>

</blockquote></details>
<details>
<summary>src/components/features/banner/jeju-banner.tsx (1)</summary><blockquote>

<details>
<summary>src/types/common.type.ts (1)</summary>

* `JejuBannerProps` (53-58)

</details>

</blockquote></details>
<details>
<summary>src/lib/apis/plan/plan.api.ts (2)</summary><blockquote>

<details>
<summary>src/lib/supabase/server.ts (1)</summary>

* `getServerClient` (5-30)

</details>
<details>
<summary>src/lib/utils/camelize.ts (1)</summary>

* `camelize` (23-47)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (19)</summary><blockquote>

<details>
<summary>src/constants/path.constants.ts (1)</summary>

`8-8`: **LGTM! Route renaming aligns with community page feature**

The route path change from `SHAREDPLAN: '/shared-plan'` to `COMMUNITY: '/community'` aligns well with the PR objectives to create a community page that retrieves the complete schedule.

</details>
<details>
<summary>src/app/(mypage)/layout.tsx (1)</summary>

`9-9`: **LGTM! Background color addition enhances visual distinction**

Adding `bg-gray-50` to the mypage layout creates a nice visual distinction from the rest of the application, which is now using a white background according to global changes.

</details>
<details>
<summary>src/types/common.type.ts (1)</summary>

`52-58`: **LGTM! Well-structured type definition for the new JejuBanner component**

The `JejuBannerProps` type is well-defined with all necessary properties and follows the same naming pattern as other component prop types in this file.

</details>
<details>
<summary>src/middleware.ts (1)</summary>

`36-36`: **Routing path updated correctly.**

The modification from `PATH.SHAREDPLAN` to `PATH.COMMUNITY` in the `validPaths` array aligns with the broader routing changes across the application. This ensures that middleware properly validates the new community page routes.

</details>
<details>
<summary>src/components/layouts/nav.tsx (2)</summary>

`15-16`: **Navigation links updated appropriately.**

The link has been correctly updated to use `PATH.COMMUNITY` with the text "커뮤니티", and the `MypageButton` implementation efficiently passes the user ID directly. This change maintains consistency with the application's new routing structure.

---

`21-21`: **Non-authenticated navigation updated consistently.**

The navigation link for non-authenticated users has been consistently updated to match the authenticated user navigation, maintaining a uniform experience across user states.

</details>
<details>
<summary>src/constants/auth.constants.ts (1)</summary>

`126-126`: **Public route definition updated correctly.**

The `AUTH_ROUTES.PUBLIC_ROUTES` array has been updated to include `PATH.COMMUNITY` instead of the previous `PATH.SHAREDPLAN`. This ensures that the community page is properly accessible as a public route without authentication requirements.

</details>
<details>
<summary>src/types/supabase.type.ts (1)</summary>

`402-414`: **Well-structured database function type for community page.**

The new `get_plans` function type definition is well-designed to support the community page functionality:

1. Accepts a nullable user ID parameter (`user_id_param: string | null`), which aligns with the requirement to handle both authenticated and non-authenticated users
2. Returns all necessary fields for plan display, including `is_liked` status
3. Follows the existing pattern for defining database functions

This implementation supports the PR objective of displaying plan schedules and like status with different behavior based on user authentication state.

</details>
<details>
<summary>src/app/layout.tsx (2)</summary>

`26-26`: **Clean layout improvement with centered content.**

The addition of `items-center justify-center` to the body class helps ensure content is properly centered both vertically and horizontally within the viewport.

---

`30-34`: **Good container implementation for consistent layout width.**

Wrapping the main application components in a flex container with a maximum width constraint of 1024px creates a more consistent user experience across different screen sizes. This aligns well with modern responsive design practices.

</details>
<details>
<summary>src/app/globals.css (2)</summary>

`62-62`: **Background color update from gray to white.**

Changing the background color from gray to white creates a cleaner, more modern look for the application. This change aligns with the community page implementation where content should stand out clearly.

---

`68-68`: **Consistent ordering of typography utility classes.**

The reordering of utility classes (`font-bold` followed by `tracking-tight-2`) improves code consistency across typography styles, making the codebase more maintainable.



Also applies to: 71-71, 74-74

</details>
<details>
<summary>src/app/community/page.tsx (4)</summary>

`9-13`: **Properly implemented user authentication check for fetching plans.**

The component correctly fetches the current user and passes the user ID (or null if not logged in) to the `fetchAllPlans` function, which aligns with the PR objective of handling both logged-in and non-logged-in users.

---

`17-22`: **Good implementation of the JejuBanner component.**

The banner is well-configured with appropriate props for the community page context. The use of path constants (`PATH.PLAN_NEW`) rather than hardcoded strings is a good practice for maintainability.

---

`23-25`: **Appropriate heading and container structure for the community page.**

The page layout uses semantic HTML with proper heading levels and flex container with padding for content spacing. The gap utilities provide consistent spacing between elements.

---

`26-52`: **Well-implemented responsive grid layout for plan cards.**

The grid layout with responsive column configuration (`grid-cols-2` on mobile and `grid-cols-3` on larger screens) ensures good display across device sizes. The card components contain all necessary information (image, title, description, author, and dates) with appropriate text truncation using `line-clamp` classes.

</details>
<details>
<summary>src/components/features/banner/jeju-banner.tsx (3)</summary>

`6-13`: **Well-documented component with clear JSDoc comments.**

The component has excellent documentation clearly explaining its purpose and parameters. This improves code maintainability and makes it easier for other developers to use the component correctly.

---

`14-19`: **Good use of default prop values.**

The component properly defines default values for all props, making it flexible and easier to use with minimal configuration while still allowing customization when needed.

---

`38-39`: **Good responsive layout for banner text positioning.**

The banner uses appropriate responsive positioning with different layouts for mobile and desktop screens. On mobile, the content is centered and stacked, while on desktop it's positioned at the left side of the banner with a horizontal layout.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-14T11:32:55Z
user: login: joyounghyun550
body: ![lhtm-300x300](https://github.com/user-attachments/assets/75ad3c3b-aa53-4ff8-b98f-6711b3f597ad)

state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T11:47:31Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>src/components/features/banner/jeju-banner.tsx (2)</summary><blockquote>

`22-31`: _⚠️ Potential issue_

**Fix the login check logic in the event handler.**

The current implementation has an inverted logic problem. It shows an alert if `user` exists (is truthy) and only navigates otherwise, which is the opposite of the intended behavior.

Additionally, the `user` variable is hardcoded as a string 'user' instead of actually checking the authentication state.



Apply this fix:

```diff
-  const handleGotoNewPlan = () => {
-    const user = 'user';
-
-    if (user) {
-      alert('로그인이 필요합니다.');
-      return;
-    }
-    router.push(buttonUrl);
-  };
+  const handleGotoNewPlan = () => {
+    // Get actual authentication state from your auth context/provider
+    const isLoggedIn = false; // Replace with actual auth check
+
+    if (!isLoggedIn) {
+      alert('로그인이 필요합니다.');
+      return;
+    }
+    router.push(buttonUrl);
+  };
```

---

`53-55`: _🛠️ Refactor suggestion_

**Use JSX camelCase attributes for SVG elements.**

In React, SVG attributes should use camelCase instead of kebab-case to follow JSX conventions.



```diff
-              fillRule="evenodd"
-              clipRule="evenodd"
+              fillRule="evenodd"
+              clipRule="evenodd"
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>src/components/features/banner/jeju-banner.tsx (2)</summary><blockquote>

`36-36`: **Improve image accessibility with a more descriptive alt text.**

The current alt text "제주 여행 배너" (Jeju Travel Banner) is somewhat generic. Improve accessibility by providing a more descriptive alt text that conveys the image content, particularly the mentioned tangerine character.


```diff
- <img src={imageUrl} alt="제주 여행 배너" className="aspect-auto w-full" />
+ <img src={imageUrl} alt="귤 캐릭터가 있는 제주 여행 계획 배너" className="aspect-auto w-full" />
```

---

`40-44`: **Enhance button accessibility with aria-label.**

Add an aria-label to improve accessibility for screen reader users, especially since the button contains both text and an icon.


```diff
<button
  onClick={handleGotoNewPlan}
  className="semibold-16 inline-flex items-center gap-1 rounded-full bg-primary-500 px-4 py-2 text-white"
+  aria-label="새로운 제주 여행 일정 만들기"
>
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d99988db69173aaba05bd04f37dbc995a554028e and 548fe35bd37f8791cf69356bc2a3c8d0f7cd8a91.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `src/components/features/banner/jeju-banner.tsx` (1 hunks)
* `src/constants/path.constants.ts` (1 hunks)
* `src/middleware.ts` (1 hunks)
* `src/types/common.type.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* src/constants/path.constants.ts
* src/types/common.type.ts
* src/middleware.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{ts,tsx}`: TypeScript와 React 코드가 모범 사례를 준수하는지 확인해주세요.</summary>

> `**/*.{ts,tsx}`: TypeScript와 React 코드가 모범 사례를 준수하는지 확인해주세요.

- `src/components/features/banner/jeju-banner.tsx`

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/components/features/banner/jeju-banner.tsx (1)</summary><blockquote>

<details>
<summary>src/types/common.type.ts (1)</summary>

* `JejuBannerProps` (53-58)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>src/components/features/banner/jeju-banner.tsx (1)</summary>

`3-19`: **Component structure and props setup looks good.**

The component is well-structured with clear JSDoc documentation explaining the purpose and parameters. The default values for props provide good fallbacks, making the component flexible for various use cases.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-14T12:06:29Z
user: login: PureunKang
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T12:49:49Z

Review comments:
diff_hunk: @@ -0,0 +1,64 @@
+'use client';
+
+import { JejuBannerProps } from '@/types/common.type';
+import { useRouter } from 'next/navigation';
+
+/**
+ * 제주 여행 계획 배너 컴포넌트
+ * 귤 캐릭터가 있는 배경 이미지 위에 타이틀과 버튼을 오버레이하는 배너
+ * @param imageUrl - 배너 배경 이미지 URL
+ * @param title - 배너 제목 텍스트
+ * @param buttonText - 버튼에 표시할 텍스트
+ * @param buttonUrl - 버튼 클릭 시 이동할 URL
+ */
+const JejuBanner = ({
+  imageUrl = '/images/jeju-banner.png',
+  title = '나만의 제주 여행 계획하기',
+  buttonText = '내 일정 만들러 가기 >',
+  buttonUrl = '/planner',
+}: JejuBannerProps) => {
+  const router = useRouter();
+
+  /** 일정 만들기 버튼 클릭 시의 이벤트 핸들러 */
+  const handleGotoNewPlan = () => {
+    const user = 'user';
+
+    if (user) {
+      alert('로그인이 필요합니다.');
+      return;
+    }
+    router.push(buttonUrl);
+  };
+
+  return (
+    <div className="relative w-full overflow-hidden">
+      {/* 배경 이미지  */}
+      <img src={imageUrl} alt="제주 여행 배너" className="aspect-auto w-full" />
+      {/* 텍스트와 버튼 오버레이 - 왼쪽 상단에 배치 */}
+      <div className="absolute left-1/2 top-[10%] flex -translate-x-1/2 transform flex-row items-center gap-2 sm:left-[5%] sm:transform-none sm:flex-col sm:items-start">
+        <h2 className="sm:bold-28 bold-22 whitespace-nowrap">{title}</h2>
+        <button
+          onClick={handleGotoNewPlan}
+          className="semibold-16 inline-flex items-center gap-1 rounded-full bg-primary-500 px-4 py-2 text-white"
+        >
+          <span className="whitespace-nowrap">{buttonText}</span>
+          <svg
+            width="12"
+            height="12"
+            viewBox="0 0 48 48"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              fill-rule="evenodd"
+              clip-rule="evenodd"
+              d="M34.6191 22.3726C35.1831 22.9368 35.5 23.702 35.5 24.4998C35.5 25.2976 35.1831 26.0627 34.6191 26.6269L17.5989 43.6471C17.0314 44.1952 16.2714 44.4984 15.4826 44.4916C14.6937 44.4847 13.9391 44.1683 13.3812 43.6105C12.8234 43.0526 12.507 42.298 12.5001 41.5091C12.4933 40.7203 12.7965 39.9603 13.3446 39.3928L28.2376 24.4998L13.3446 9.60669C12.7965 9.03924 12.4933 8.27924 12.5001 7.49037C12.507 6.70149 12.8234 5.94688 13.3812 5.38904C13.9391 4.8312 14.6937 4.51478 15.4826 4.50793C16.2714 4.50107 17.0314 4.80433 17.5989 5.35239L34.6191 22.3726Z"
+              fill="white"
path: src/components/features/banner/jeju-banner.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use JSX camelCase attributes for SVG elements.**

In React, SVG attributes should use camelCase instead of kebab-case to follow JSX conventions.


```diff
-              fill-rule="evenodd"
-              clip-rule="evenodd"
+              fillRule="evenodd"
+              clipRule="evenodd"
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
              fillRule="evenodd"
              clipRule="evenodd"
              d="M34.6191 22.3726C35.1831 22.9368 35.5 23.702 35.5 24.4998C35.5 25.2976 35.1831 26.0627 34.6191 26.6269L17.5989 43.6471C17.0314 44.1952 16.2714 44.4984 15.4826 44.4916C14.6937 44.4847 13.9391 44.1683 13.3812 43.6105C12.8234 43.0526 12.507 42.298 12.5001 41.5091C12.4933 40.7203 12.7965 39.9603 13.3446 39.3928L28.2376 24.4998L13.3446 9.60669C12.7965 9.03924 12.4933 8.27924 12.5001 7.49037C12.507 6.70149 12.8234 5.94688 13.3812 5.38904C13.9391 4.8312 14.6937 4.51478 15.4826 4.50793C16.2714 4.50107 17.0314 4.80433 17.5989 5.35239L34.6191 22.3726Z"
              fill="white"
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-14T11:32:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,64 @@
+'use client';
+
+import { JejuBannerProps } from '@/types/common.type';
+import { useRouter } from 'next/navigation';
+
+/**
+ * 제주 여행 계획 배너 컴포넌트
+ * 귤 캐릭터가 있는 배경 이미지 위에 타이틀과 버튼을 오버레이하는 배너
+ * @param imageUrl - 배너 배경 이미지 URL
+ * @param title - 배너 제목 텍스트
+ * @param buttonText - 버튼에 표시할 텍스트
+ * @param buttonUrl - 버튼 클릭 시 이동할 URL
+ */
+const JejuBanner = ({
+  imageUrl = '/images/jeju-banner.png',
+  title = '나만의 제주 여행 계획하기',
+  buttonText = '내 일정 만들러 가기 >',
+  buttonUrl = '/planner',
+}: JejuBannerProps) => {
+  const router = useRouter();
+
+  /** 일정 만들기 버튼 클릭 시의 이벤트 핸들러 */
+  const handleGotoNewPlan = () => {
+    const user = 'user';
+
+    if (user) {
+      alert('로그인이 필요합니다.');
+      return;
+    }
+    router.push(buttonUrl);
+  };
path: src/components/features/banner/jeju-banner.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix the login check logic in the event handler.**

The current implementation has an inverted logic problem. It shows an alert if `user` exists (is truthy) and only navigates otherwise, which is the opposite of the intended behavior.


Additionally, the `user` variable is hardcoded as a string 'user' instead of actually checking the authentication state.

Apply this fix:

```diff
-  const handleGotoNewPlan = () => {
-    const user = 'user';
-
-    if (user) {
-      alert('로그인이 필요합니다.');
-      return;
-    }
-    router.push(buttonUrl);
-  };
+  const handleGotoNewPlan = () => {
+    // Get actual authentication state from your auth context/provider
+    const isLoggedIn = false; // Replace with actual auth check
+
+    if (!isLoggedIn) {
+      alert('로그인이 필요합니다.');
+      return;
+    }
+    router.push(buttonUrl);
+  };
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  /** 일정 만들기 버튼 클릭 시의 이벤트 핸들러 */
  const handleGotoNewPlan = () => {
    // Get actual authentication state from your auth context/provider
    const isLoggedIn = false; // Replace with actual auth check

    if (!isLoggedIn) {
      alert('로그인이 필요합니다.');
      return;
    }
    router.push(buttonUrl);
  };
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-14T11:32:54Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-04-14T11:30:39Z
body: [vc]: #GV7RiJmLZXns0d9skPMk04LAIkqqlPvCGEwCXaFsAjc=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJqZWp1LW9sbGV5byIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJqZWp1LW9sbGV5by1naXQtZmVhdC0xMTAtY29tbXVuaXR5LXBhZ2UtamVqdW9sbGV5by52ZXJjZWwuYXBwIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9qZWp1b2xsZXlvL2planUtb2xsZXlvLzIzQnhHUkxwU3RoRTZoeVlVVGFob1FSNlhQRFoiLCJwcmV2aWV3VXJsIjoiamVqdS1vbGxleW8tZ2l0LWZlYXQtMTEwLWNvbW11bml0eS1wYWdlLWplanVvbGxleW8udmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **jeju-olleyo** | ✅ Ready ([Inspect](https://vercel.com/jejuolleyo/jeju-olleyo/23BxGRLpSthE6hyYUTahoQR6XPDZ)) | [Visit Preview](https://jeju-olleyo-git-feat-110-community-page-jejuolleyo.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/jeju-olleyo-git-feat-110-community-page-jejuolleyo.vercel.app?via=pr-comment-feedback-link) | Apr 14, 2025 1:39pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-14T11:30:41Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This pull request introduces several updates that affect styling, routing, API calls, and component structure. It enhances layout alignment and global CSS by updating classes and colors, while also replacing the old "shared plan" route with the new "community" route across the codebase. New components such as the Community page and JejuBanner have been added, along with corresponding API functions and type definitions to support plan fetching. Navigation links and middleware validations have also been updated to reflect the routing changes.

## Changes

| **File(s)** | **Change Summary** |
|-------------|--------------------|
| `src/app/(mypage)/layout.tsx`, `src/app/layout.tsx`, `src/app/globals.css` | Updated styling classes and layout structure. Background color adjustments and reordering of utility classes in CSS for improved visual alignment. |
| `src/app/community/page.tsx`, `src/components/features/banner/jeju-banner.tsx`, `src/types/common.type.ts` | Introduced a new Community page and a JejuBanner component with required props. Added type definition for JejuBanner props. |
| `src/components/layouts/nav.tsx`, `src/constants/auth.constants.ts`, `src/constants/path.constants.ts`, `src/middleware.ts` | Replaced the old SHAREDPLAN route with COMMUNITY across navigation, route constants, and middleware. Removed redundant user ID handling. |
| `src/lib/apis/plan/plan.api.ts`, `src/types/supabase.type.ts` | Added a new `fetchAllPlans` function to retrieve user plans. Updated API signatures and extended the supabase Database type with the `get_plans` function. |
| `tailwind.config.ts` | Added a new color entry `description` with hex value `#909090` to the Tailwind CSS configuration. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
  participant U as User
  participant C as CommunityPage
  participant A as API (User & Plan Data)
  
  U->>C: Request Community Page
  C->>A: fetchGetCurrentUser()
  A-->>C: Return User Info
  C->>A: fetchAllPlans(userId)
  A-->>C: Return Plan List
  C->>U: Render banner and plan cards
```

```mermaid
sequenceDiagram
  participant U as User
  participant JB as JejuBanner
  participant R as Router
  
  U->>JB: Click banner button
  JB->>JB: handleGotoNewPlan()
  alt User not logged in
    JB-->>U: Show alert "login required"
  else User is logged in
    JB->>R: Navigate to buttonUrl
  end
```

## Poem

> I'm a code rabbit, hopping through the night,  
> Aligning layouts with a sprinkle of light.  
> Gray backgrounds, new routes in sight,  
> Community and banner bring the code to flight.  
> In fields of API and CSS delight,  
> I nibble on changes with a heart so bright!  
> 🐰💻🌙

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=RE6CT/jeju-olleyo&utm_content=114):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE