Skip to content
Merged
14 changes: 11 additions & 3 deletions apps/client/src/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { Outlet, useLocation } from 'react-router-dom';
import { Sidebar } from '@shared/components/sidebar/Sidebar';
import { ROUTES_CONFIG } from '@routes/routesConfig';

const Layout = () => {
const location = useLocation();
const isOnboarding = location.pathname.startsWith('/onboarding');
const isLogin = location.pathname.startsWith('/login');

const isPolicyPage =
location.pathname === ROUTES_CONFIG.privacyPolicy.path ||
location.pathname === ROUTES_CONFIG.termsOfService.path;

const isSidebarHidden =
location.pathname.startsWith(ROUTES_CONFIG.onBoarding.path) ||
location.pathname.startsWith(ROUTES_CONFIG.login.path) ||
isPolicyPage;
Comment on lines 12 to 15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: 경로 대소문자 불일치로 인한 사이드바 숨김 로직 오류

Line 13에서 startsWith('/onboarding')로 체크하고 있지만, routesConfig.tsonBoarding.path/onBoarding(대문자 B)으로 정의되어 있습니다. 이로 인해 온보딩 페이지에서 사이드바가 의도대로 숨겨지지 않습니다.

참고로 onBoardingCallback.path/onboarding/callback(소문자)로 정의되어 있어 라우트 정의 자체에도 일관성이 없습니다.

🔎 제안하는 수정 방안

옵션 1: Layout.tsx 수정 (권장)

   const isSidebarHidden =
-    location.pathname.startsWith('/onboarding') ||
+    location.pathname.startsWith(ROUTES_CONFIG.onBoarding.path) ||
     location.pathname.startsWith('/login') ||
     isPolicyPage;

옵션 2: routesConfig.ts의 경로 통일
onBoarding.path를 소문자 /onboarding으로 변경하여 onBoardingCallback과 일관성을 맞춥니다.

📝 Committable suggestion

‼️ 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.

Suggested change
const isSidebarHidden =
location.pathname.startsWith('/onboarding') ||
location.pathname.startsWith('/login') ||
isPolicyPage;
const isSidebarHidden =
location.pathname.startsWith('/onBoarding') ||
location.pathname.startsWith('/login') ||
isPolicyPage;
🤖 Prompt for AI Agents
In @apps/client/src/layout/Layout.tsx around lines 12 - 15, The sidebar-hide
check in Layout.tsx is failing due to inconsistent casing between the hardcoded
startsWith('/onboarding') and routesConfig's onBoarding.path ('/onBoarding')
while onBoardingCallback.path uses '/onboarding'; fix by referencing the
canonical route value (e.g., use routesConfig.onBoarding.path and
routesConfig.onBoardingCallback.path) or normalize casing before comparing
(e.g., compare
location.pathname.toLowerCase().startsWith(routesConfig.onBoarding.path.toLowerCase())),
ensuring isSidebarHidden uses the route constants rather than hardcoded strings
so the onboarding path is detected correctly.


return (
<>
<div className="flex h-screen">
{!isOnboarding && !isLogin && <Sidebar />}
{!isSidebarHidden && <Sidebar />}
<main className="bg-gray-bg flex-1 overflow-y-auto">
<Outlet />
</main>
Expand Down
5 changes: 3 additions & 2 deletions apps/client/src/pages/login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { handleGoogleLogin } from '@shared/utils/handleGoogleLogin';
import { Link } from 'react-router-dom';
import Chippi from '@assets/chippi_extension_popup.svg';
import GoogleLogo from '/assets/onBoarding/icons/googleLogo.svg';
import { ROUTES_CONFIG } from '@routes/routesConfig';

const Login = () => {
return (
Expand Down Expand Up @@ -48,14 +49,14 @@ const Login = () => {
<p className="text-font-gray-3 caption2-m mt-[2.4rem] text-center">
가입 시 pinback의{' '}
<Link
to="/terms"
to={ROUTES_CONFIG.termsOfService.path}
className="underline underline-offset-2 hover:opacity-70"
>
이용 약관
</Link>{' '}
및{' '}
<Link
to="/privacy"
to={ROUTES_CONFIG.privacyPolicy.path}
className="underline underline-offset-2 hover:opacity-70"
>
개인정보처리방침
Expand Down
4 changes: 3 additions & 1 deletion apps/client/src/pages/myBookmark/MyBookmark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ArticlesLoadingBoundary from '@shared/components/articlesLoadingBoundary/
import ArticlesErrorBoundary from '@shared/components/articlesErrorBoundary/ArticlesErrorBoundary';
import { ErrorBoundary } from 'react-error-boundary';
import MyBookmarkContent from '@pages/myBookmark/components/myBookmarkContent/MyBookmarkContent';
import Footer from './components/footer/Footer';

const MyBookmark = () => {
const [activeBadge, setActiveBadge] = useState<'all' | 'notRead'>('all');
Expand Down Expand Up @@ -69,7 +70,7 @@ const MyBookmark = () => {
id == null ? '' : (REMIND_MOCK_DATA.find((d) => d.id === id)?.title ?? '');

return (
<div className="flex h-screen flex-col py-[5.2rem] pl-[8rem] pr-[5rem]">
<div className="flex h-screen flex-col pl-[8rem] pr-[5rem] pt-[5.2rem]">
<div className="flex items-center gap-[0.4rem]">
<div className="flex items-center gap-[0.4rem]">
<p className="head3">나의 북마크</p>
Expand Down Expand Up @@ -102,6 +103,7 @@ const MyBookmark = () => {
/>
</ErrorBoundary>
</Suspense>
<Footer />

<OptionsMenuPortal
open={menu.open}
Expand Down
63 changes: 63 additions & 0 deletions apps/client/src/pages/myBookmark/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Icon } from '@pinback/design-system/icons';
import { ROUTES_CONFIG } from '@routes/routesConfig';
import { Link } from 'react-router-dom';

const Footer = () => {
const currentYear = new Date().getFullYear();

return (
<footer className="mt-auto w-full pb-[1.6rem] pr-[3rem]">
<div className="mt-[2.4rem] flex gap-[0.8rem]">
<Link
to={ROUTES_CONFIG.termsOfService.path}
className="caption1-sb text-font-gray-2 cursor-pointer hover:underline"
>
이용약관
</Link>
<p className="caption1-sb text-font-gray-2">|</p>
<Link
to={ROUTES_CONFIG.privacyPolicy.path}
className="caption1-sb text-font-gray-2 cursor-pointer hover:underline"
>
개인정보처리방침
</Link>
</div>

<div className="flex items-center justify-between">
<address className="flex gap-[0.8rem] not-italic">
<p className="caption2-sb text-font-ltgray-4">운영팀 문의</p>
<p className="caption2-m text-font-ltgray-4">
이한비 · pinback0615@gmail.com
</p>
</address>
Comment on lines +27 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

이메일 주소 노출로 인한 스팸 위험

운영팀 이메일 주소가 프론트엔드 코드에 평문으로 노출되어 있어 스팸봇의 타겟이 될 수 있습니다. 다음 방법을 고려해보세요:

  • 전용 문의 폼 구현
  • 이메일 주소 난독화
  • 별도의 고객지원 이메일 서비스 활용
🔎 이메일 난독화 예시
-          <p className="caption2-m text-font-ltgray-4">
-            이한비 · pinback0615@gmail.com
-          </p>
+          <p className="caption2-m text-font-ltgray-4">
+            이한비 · pinback0615{'@'}gmail{'.'}com
+          </p>

또는 문의 페이지 링크로 대체:

-          <p className="caption2-m text-font-ltgray-4">
-            이한비 · pinback0615@gmail.com
-          </p>
+          <Link to="/contact" className="caption2-m text-font-ltgray-4 hover:underline">
+            운영팀에 문의하기
+          </Link>
📝 Committable suggestion

‼️ 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.

Suggested change
<address className="flex gap-[0.8rem] not-italic">
<p className="caption2-sb text-font-ltgray-4">운영팀 문의</p>
<p className="caption2-m text-font-ltgray-4">
이한비 · pinback0615@gmail.com
</p>
</address>
<address className="flex gap-[0.8rem] not-italic">
<p className="caption2-sb text-font-ltgray-4">운영팀 문의</p>
<p className="caption2-m text-font-ltgray-4">
이한비 · pinback0615{'@'}gmail{'.'}com
</p>
</address>
🤖 Prompt for AI Agents
In @apps/client/src/pages/myBookmark/components/footer/Footer.tsx around lines
26 - 31, The Footer component currently hardcodes the support email in the JSX
(see address block in Footer.tsx), which exposes it to crawlers; remove the
plain email string and instead render a link to a dedicated contact page or a
server-backed contact endpoint (e.g., replace the paragraph that contains "이한비 ·
pinback0615@gmail.com" with an anchor to a Contact/Support route or API), or if
you must display an email obfuscate it at runtime (construct it via JS parts or
fetch from a backend endpoint) so the raw address is not present in the source;
update the Footer rendering logic and any imports/props accordingly to use the
contact route or runtime-obfuscated value.

<p className="caption2-m text-font-ltgray-4">
©{currentYear} pinback All rights reserved.
</p>

<div className="flex items-end gap-[1.2rem]">
<p className="caption2-m text-gray400">TEAM. 도묵이</p>
<a
href="https://instagram.com/pinback.today/"
target="_blank"
rel="noopener noreferrer"
aria-label="Pinback 인스타그램"
className="transition-opacity hover:opacity-80"
>
<Icon name="instagram" width={28} height={28} />
</a>
<a
href="https://pinback.palms.blog/"
target="_blank"
rel="noopener noreferrer"
aria-label="Pinback 블로그"
className="transition-opacity hover:opacity-80"
>
<Icon name="palms" width={28} height={28} />
</a>
</div>
</div>
Comment on lines +26 to +58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

레이아웃 구조 검토 필요

justify-between을 사용하여 3개의 요소(연락처, 저작권, 소셜 아이콘)를 배치하고 있습니다. 이 경우 가운데 요소(저작권 텍스트)가 시각적으로 중앙에 위치하지 않을 수 있습니다.

의도한 레이아웃이 맞는지 확인하고, 필요시 다음과 같은 대안을 고려해보세요:

  • justify-centergap 사용
  • Grid 레이아웃 사용하여 명시적인 열 배치
  • 각 섹션에 flex-1 적용
🔎 Grid 레이아웃 대안
-      <div className="flex items-center justify-between">
+      <div className="grid grid-cols-3 items-center">
-        <address className="flex gap-[0.8rem] not-italic">
+        <address className="flex gap-[0.8rem] not-italic justify-self-start">
           <p className="caption2-sb text-font-ltgray-4">운영팀 문의</p>
           <p className="caption2-m text-font-ltgray-4">
             이한비 · pinback0615@gmail.com
           </p>
         </address>
-        <p className="caption2-m text-font-ltgray-4">
+        <p className="caption2-m text-font-ltgray-4 justify-self-center">
           ©{currentYear} pinback All rights reserved.
         </p>
 
-        <div className="flex items-end gap-[1.2rem]">
+        <div className="flex items-end gap-[1.2rem] justify-self-end">
           <p className="caption2-m text-gray400">TEAM. 도묵이</p>
🤖 Prompt for AI Agents
In @apps/client/src/pages/myBookmark/components/footer/Footer.tsx around lines
25 - 57, The row uses a single flex container with class "flex items-center
justify-between" wrapping three children (the address block, the copyright p
referencing currentYear, and the social icons div), which can leave the middle
item visually off-center; pick one fix: either change the container to a
three-column grid (e.g., replace the "flex ... justify-between" container with a
grid and use "grid-cols-3 items-center" and add "text-center" to the middle
copyright p), or keep flex and give the left and right children (the address
element and the social icons div) "flex-1" so the middle copyright p stays
centered, or use "justify-center" with a gap and ensure left/right blocks are
absolutely or relatively positioned; update Footer.tsx accordingly by modifying
the container class and/or adding flex-1/text-center to the specific children
(address, the copyright <p> using currentYear, and the social icons <div>).

</footer>
);
};

export default Footer;
18 changes: 8 additions & 10 deletions apps/client/src/pages/onBoarding/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import { Icon } from '@pinback/design-system/icons';
import { ROUTES_CONFIG } from '@routes/routesConfig';
import { Link } from 'react-router-dom';

const Footer = () => {
const currentYear = new Date().getFullYear();

return (
<footer className="absolute bottom-0 mt-auto w-full px-[8rem] py-[3.2rem]">
<div className="mt-[2.4rem] flex gap-[0.8rem]">
<a
href="https://right-turquoise-268.notion.site/pinback-2c527450eb1c80eca220c1de3293e43a?source=copy_link"
target="_blank"
rel="noopener noreferrer"
<Link
to={ROUTES_CONFIG.termsOfService.path}
className="caption1-sb text-font-gray-2 cursor-pointer hover:underline"
>
이용약관
</a>
</Link>
<p className="caption1-sb text-font-gray-2">|</p>
<a
href=" https://right-turquoise-268.notion.site/pinback-2c527450eb1c804390effb2ee32072b2?source=copy_link"
target="_blank"
rel="noopener noreferrer"
<Link
to={ROUTES_CONFIG.privacyPolicy.path}
className="caption1-sb text-font-gray-2 cursor-pointer hover:underline"
>
개인정보처리방침
</a>
</Link>
</div>

<div className="flex items-center justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Chippi from '@assets/chippi_extension_popup.svg';
import GoogleLogo from '/assets/onBoarding/icons/googleLogo.svg';
import { Link } from 'react-router-dom';
import { handleGoogleLogin } from '@shared/utils/handleGoogleLogin';
import { ROUTES_CONFIG } from '@routes/routesConfig';

const SocialLoginStep = () => {
return (
Expand Down Expand Up @@ -31,18 +32,17 @@ const SocialLoginStep = () => {
/>
구글 계정으로 로그인
</button>
{/*TODO: 개인정보처리방침 추가되면 링크 수정*/}
<p className="text-font-gray-3 caption2-m mt-[2.4rem] text-center">
가입 시 pinback의{' '}
<Link
to="/privacy"
to={ROUTES_CONFIG.termsOfService.path}
className="underline underline-offset-2 hover:opacity-70"
>
이용 약관
</Link>{' '}
및{' '}
<Link
to="/privacy"
to={ROUTES_CONFIG.privacyPolicy.path}
className="underline underline-offset-2 hover:opacity-70"
>
개인정보처리방침
Expand Down
137 changes: 137 additions & 0 deletions apps/client/src/pages/policy/PrivacyPolicy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import HeaderLogo from '/assets/onBoarding/icons/header_logo.svg';

const PrivacyPolicy = () => {
return (
<>
<header className="z-5 bg-white-bg absolute top-0 flex w-full justify-items-start px-[8.2rem] py-[2.5rem]">
<img
src={HeaderLogo}
alt="header logo"
onClick={() => window.location.reload()}
/>
</header>
<div className="scroll-smooth px-[8.2rem] py-[9.6rem]">
<section className="">
<h1 className="head6">Pinback 개인정보처리방침</h1>
<p className="text-font-gray-2 caption1-m mt-[1.2rem] whitespace-pre-line">
Pinback은 이용자의 정보를 안전하게 보호하는 것을 가장 중요한 책임 중
하나로 생각합니다.{'\n'}본 방침은 Pinback이 어떤 정보를 수집하고,
어떻게 사용하며, 어떻게 보호하는지 쉽게 이해할 수 있도록 안내하기
위해 마련되었습니다.
</p>
</section>

<section className="mt-[3.6rem]">
<h2 className="sub2-sb">① 수집하는 개인정보 종류</h2>
<p className="text-font-gray-2 sub4-sb mt-[1.2rem]">
Pinback은 서비스 제공을 위해 다음 정보를 수집합니다.
</p>
<ol className="mt-[1.2rem] list-decimal pl-[2.1rem]">
<li className="caption1-m text-font-gray-2">
계정 생성을 위한 정보: 이메일 주소, 프로필 이름, Google 계정
식별자 (UID)
</li>
<li className="caption1-m text-font-gray-2">
북마크 저장 및 관리 기능을 위한 정보: 저장한 웹페이지 URL 및 제목,
사용자가 작성한 메모, 리마인드 설정 정보, 도토리 적립 내역 및 숲
성장 단계 등 서비스 활동 데이터
</li>
<li className="caption1-m text-font-gray-2">
서비스 이용 기록: 기능 사용 내역, 접속 일시, 브라우저 정보 등 기본
로그
</li>
</ol>
</section>

<section className="mt-[3rem]">
<h2 className="sub2-sb">② 개인정보의 이용 목적</h2>
<p className="text-font-gray-2 sub4-sb mt-[1.2rem]">
Pinback은 수집된 개인정보를 서비스의 핵심 기능을 제공하기 위한 최소
범위에서만 사용합니다.
</p>
<ul className="caption1-m text-font-gray-2 mt-[1.2rem] list-disc pl-[2.1rem]">
<li>
사용자가 저장한 북마크와 메모를 안전하게 보관하고 다시 찾아볼 수
있도록 하기 위함
</li>
<li>
사용자 설정에 따라 리마인드 알림을 제공하여 저장한 콘텐츠를 다시
활용할 수 있도록 돕기 위함
</li>
<li>
도토리 보상 시스템을 통해 서비스 이용 경험을 향상하고, 지속적인
지식 관리 습관 형성을 지원하기 위함
</li>
</ul>
<p className="sub4-sb text-font-gray-2 mt-[1.2rem]">
Pinback은 수집된 모든 정보를 위 목적 이외에 다른 용도로 사용하지
않으며, 개인정보를 제 3자에게 판매하거나 공유하지 않습니다.
</p>
</section>

<section className="mt-[3rem]">
<h2 className="sub2-sb">③ 개인정보의 보유 및 보안</h2>
<div className="sub4-sb text-font-gray-2 mt-[1.2rem]">
<p>
Pinback은 사용자의 개인정보를 안전하게 보호하기 위해 적절한 보안
조치를 취하고 있습니다.
</p>
<p>
개인정보는 무단 접근, 유출, 변경, 파괴를 방지하기 위해 보호되며,
필요한 경우 암호화되어 저장됩니다.
</p>
</div>
</section>

<section className="mt-[3rem]">
<h2 className="sub2-sb">④ 개인정보 관리 및 열람 관리</h2>
<p className="text-font-gray-2 sub4-sb mt-[1.2rem]">
이용자는 언제든지 다음을 요청할 수 있습니다.
</p>
<ul className="caption1-m text-font-gray-2 mt-[1.2rem] list-disc pl-[2.1rem]">
<li>개인정보 조회</li>
<li>개인정보 수정</li>
<li>개인정보 삭제</li>
<li>개인정보 처리 정지</li>
</ul>
<div className="sub4-sb text-font-gray-2 mt-[1.2rem]">
<p>
요청은 서비스에서 지정한 이메일 또는 문의 채널을 통해 접수됩니다.
</p>
<p>
다만, 법적 의무나 요청에 의해 개인정보를 제공해야 할 경우, 해당
법령에 따라 제공할 수 있습니다.
</p>
</div>
</section>

<section className="mt-[3rem]">
<h2 className="sub2-sb">⑤ 개인정보 처리방침 변경</h2>
<div className="sub4-sb text-font-gray-2 mt-[1.2rem]">
<p>
본 방침은 서비스 운영 또는 관련 법령에 따라 변경될 수 있습니다.
</p>
<p>
변경 사항은 최소 7일 전에 공지하며, 이용자에게 불리한 변경의 경우
30일 전에 안내합니다.
</p>
</div>
</section>

<section className="mt-[3rem]">
<h3 className="sub2-sb">부칙</h3>
<div className="sub4-sb text-font-gray-2 mt-[1.2rem]">
<p>본 개인정보 처리방침은 2025년 10월 27일부터 적용됩니다.</p>
<p>
본 개인정보 처리방침은 최근 업데이트된 내용을 반영하고 있으며,
사용자는 pinback 서비스를 이용함으로써 이 개인정보 처리방침에
동의하는 것으로 간주됩니다.
</p>
</div>
</section>
</div>
</>
);
};

export default PrivacyPolicy;
Loading
Loading