Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ export default function AuthLayout({ children }: { children: React.ReactNode })
</div>
</div>
</div>
<div className="flex w-1/2 flex-col">
<div className="flex min-h-0 w-1/2 flex-col">
<div className="pt-6 px-8">
<LogoIpxChar width={64} height={20} />
</div>
<div className="flex flex-1 items-center justify-center px-20 py-15">{children}</div>
<div className="flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-20 py-15 scrollbar-hide">
<div className="flex min-h-215 w-125">{children}</div>
Comment on lines +44 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file =="
fd -a 'layout\.tsx$' src/app 2>/dev/null | sed 's#^\./##' | sort

echo "== target file outline/size =="
wc -l 'src/app/(auth)/layout.tsx'
ast-grep outline 'src/app/(auth)/layout.tsx' --view compact 2>/dev/null || true

echo "== target lines =="
cat -n 'src/app/(auth)/layout.tsx' | sed -n '1,120p'

echo "== ProfileStep references =="
rg -n "ProfileStep|max-w-125|w-125|min-w-0" src/app/(auth) src 2>/dev/null || true

echo "== relevant Tailwind CSS files/config if present =="
fd -a 'tailwind.*\.js$|tailwind.*\.ts$|package\.json$' . | sed 's#^\./##' | sort
for f in $(fd -a 'tailwind.*\.js$|tailwind.*\.ts$|package\.json$' .); do
  echo "--- $f ---"
  case "$f" in
    *package.json*) sed -n '1,160p' "$f" ;;
    *) sed -n '1,160p' "$f" ;;
  esac
done

Repository: CEOS-IPX/IPX-FE

Length of output: 2659


🌐 Web query:

Next.js App Router routing pages layout segments docs

💡 Result:

The Next.js App Router uses a file-system based routing architecture where folders and specific file conventions define the application's structure and behavior [1][2][3]. Route Segments and Folders Each folder within the app directory represents a route segment that maps to a URL segment [2][3]. By nesting folders, you create nested routes (e.g., app/blog/[slug]/page.tsx maps to /blog/:slug) [1][4]. Special File Conventions Next.js uses reserved file names to define specific UI and functionality for a segment [2][3]: - page.tsx: The UI unique to a route; makes a segment publicly accessible [2][4][5]. - layout.tsx: Shared UI that wraps child segments [2][6]. Layouts persist state and do not re-render upon navigation [1][2][4]. - loading.tsx: Defines loading UI (Suspense boundary) [4][7]. - error.tsx: An error boundary to catch runtime errors [4][7][5]. - not-found.tsx: UI displayed when a route is not found [4][7]. - template.tsx: Similar to a layout but re-mounts (re-renders) on navigation [3][7][5]. - route.ts: Defines server-side API endpoints [2][3]. Dynamic and Advanced Routing - Dynamic Segments: Created by wrapping a folder name in square brackets (e.g., [slug]) to generate routes from data [1][2]. Catch-all segments use [...slug], and optional catch-all segments use [[...slug]] [2][4][5]. - Route Groups: Folders wrapped in parentheses (e.g., (marketing)) allow you to organize routes without affecting the URL path [2][4][5]. - Private Folders: Folders prefixed with an underscore (_) are excluded from the router, allowing for colocation of internal logic, tests, or components [7][5]. Rendering Hierarchy Special files in a segment are rendered in a fixed hierarchy: layout -> template -> error -> loading -> not-found -> page [5]. Layouts are nested by default, meaning a parent layout wraps the child layout and page via the children prop [1][6]. The Root Layout (app/layout.tsx) is required and must contain the basic html and body tags [2][6][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target app (auth) files =="
find 'src/app/(auth)' -maxdepth 3 -type f -print 2>/dev/null | sort

echo "== ProfileStep/max-w-125/w-125/min-w-0 references =="
rg -n "ProfileStep|max-w-125|w-125|min-w-0|flex-1 overflow-y-auto|overflow-y-auto px-20" src/app 'src/components' src 2>/dev/null | head -200 || true

echo "== Next docs availability =="
fs () { find node_modules/next/dist/docs -maxdepth 3 \( -iname '*layout*' -o -iname '*page*' \) 2>/dev/null | head -50; }
fs | sort

Repository: CEOS-IPX/IPX-FE

Length of output: 12872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

target = Path('src/app/(auth)/layout.tsx')
text = target.read_text()
print("target has w-125:", "className=\"flex min-h-215 w-125\"" in text)
print("target has max-w-125:", "max-w-125" in text)
print("target has min-w-0:", "min-w-0" in text)
print("target has overflow-y-auto:", "overflow-y-auto" in text)
print("target has overflow-x hidden/scroll/auto/clip:", bool(re.search(r'overflow-x-(hidden|scroll|auto|clip)\b', text)))
print("target children count:", text.count("{children}"))

# Probe Tailwind custom width values present in config/content (best-effort static shape).
config_like = []
for p in Path('.').rglob('*.ts'):
    if any(part in ('node_modules','.next','coverage') for part in p.parts):
        continue
    try:
        s = p.read_text(errors='ignore')
    except Exception:
        continue
    if re.search(r"config\.add(['\"\`]tailwind\.)?"
                   r"textVariables|plugin\(|plugins:\s*\[|theme\s*:|content\s*:|content:\s*\[", s):
        config_like.append(str(p))

print("candidate config files:", config_like[:50])
for p in config_like[:20]:
    s = Path(p).read_text(errors='ignore')
    if "125" in s or "--breakpoint" in s or "gap-" in s or "w:" in s:
        print(f"--- {p} ---")
        print("\n".join(line for line in s.splitlines() if re.search(r"125|gap-|gap\b|w:|spacing|theme", line))[:250])
PY

Repository: CEOS-IPX/IPX-FE

Length of output: 446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tailwind config files =="
fd -a 'tailwind.*\.(js|cjs|mjs|ts)$' . 2>/dev/null | sort | head -50

echo "== package configs =="
fd -a 'package\.json$' . 2>/dev/null | sort | head -50

echo "== static width utility definitions in repo config/source =="
rg -n "125|extend:|spacing|gap|screens|breakpoints|content:" $(fd -a 'tailwind.*\.(js|cjs|mjs|ts)$' . 2>/dev/null | head -50) package.json 2>/dev/null || true

Repository: CEOS-IPX/IPX-FE

Length of output: 296


인증 폼의 가로폭 제한을 화면 너비에 맞춰주세요.

Next.js App Routerlayout은 자식 라우트 전체에 적용되므로, auth 라우트 공통 w-125는 현재 화면 폭보다 좁은 작은 디스플레이에서 컨텐츠가 넘쳐도 좌우 스크롤(나침)이 나오지 않도록 overflow-x-auto로 두지 않는 한 가로로 잘릴 수 있습니다. ProfileStep의 기존 방식처럼 min-w-0 flex-1 self-stretch max-w-125로 바꿔 고정 폭은 상한만 유지하고 화면에 맞게 수렴하게 하세요.

제안
-          <div className="flex min-h-215 w-125">{children}</div>
+          <div className="flex min-h-215 min-w-0 flex-1 self-stretch max-w-125">{children}</div>
📝 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
<div className="flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-20 py-15 scrollbar-hide">
<div className="flex min-h-215 w-125">{children}</div>
<div className="flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-20 py-15 scrollbar-hide">
<div className="flex min-h-215 min-w-0 flex-1 self-stretch max-w-125">{children}</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(auth)/layout.tsx around lines 44 - 45, Update the inner auth layout
container around {children} to use min-w-0 flex-1 self-stretch max-w-125 instead
of the fixed w-125 width, matching ProfileStep’s responsive sizing pattern while
preserving the existing maximum width.

Source: Path instructions


🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'layout\.tsx$' src 2>/dev/null | sed 's#^\./##' || true

echo "== target outline/contents =="
if [ -f 'src/app/(auth)/layout.tsx' ]; then
  wc -l 'src/app/(auth)/layout.tsx'
  cat -n 'src/app/(auth)/layout.tsx'
fi

echo "== related auth components =="
rg -n "className|my-auto|overflow-y-auto|min-h-215|items-center|children" src/app/'(auth)' -g '*.tsx' -g '*.ts' 2>/dev/null || true

echo "== next docs availability =="
if [ -d node_modules/next/dist/docs ]; then
  find node_modules/next/dist/docs -maxdepth 3 -type f | sed -n '1,80p'
else
  echo "node_modules/next/dist/docs not found"
fi

Repository: CEOS-IPX/IPX-FE

Length of output: 21789


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== layout docs references =="
for f in node_modules/next/dist/docs/01-app/01-getting-started/03-layouts-and-pages.md node_modules/next/dist/docs/01-app/01-getting-started/11-css.md; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,180p' "$f"
  fi
done

echo "== signup forms relevant sections =="
sed -n '70,170p' src/app/'(auth)'/signup/components/ProfileStep.tsx

Repository: CEOS-IPX/IPX-FE

Length of output: 13366


🌐 Web query:

React flexbox overflow auto items-center scroll does not show overhanging content documentation

💡 Result:

When using Flexbox with align-items: center or justify-content: center on a scrollable container, content that overflows is often clipped and becomes inaccessible because the browser attempts to "center" the entire overflowing content block relative to the container [1][2]. If the content is larger than the container, the centering calculation pushes the start of the content outside the viewable, scrollable area [3][2]. To fix this while maintaining centered alignment when content fits, use the following strategies: 1. Use Auto Margins (Recommended): Instead of justify-content: center or align-items: center on the container, apply margin: auto to the flex items (or the inner container) [3][2]. Unlike alignment properties, margin: auto will only center content if there is extra space. If the content overflows, the margins collapse to zero, allowing the content to align to the start and remain fully accessible [3][4][2]. 2. The Safe Alignment Keyword: Modern CSS supports the safe keyword for alignment properties (e.g., justify-content: safe center or align-items: safe center) [2]. This tells the browser that if an item overflows its container, it should align as if flex-start were used, preventing the content from being pushed out of the scrollable area [2]. Note that browser support for safe should be verified for your specific target environments. 3. Nested Containers: Use an outer container for centering and an inner container for the overflow [5]. - Outer container: display: flex; justify-content: center; (or align-items: center). - Inner container: display: flex; overflow: auto; (or scroll). This separates the alignment logic from the scrolling logic, ensuring that the inner container maintains the ability to scroll its contents without being affected by the outer centering [5]. When setting up these containers, ensure the parent has defined height/width constraints (e.g., max-height or height), as overflow: auto requires a bounded container to trigger the scrollbar [6][7].

Citations:


중간 래퍼를 두어 스크롤과 중앙 정렬을 분리하세요.

현재 flex-1 items-center justify-center overflow-y-automin-h-215가 꽉 차면, React의 flexbox 스크롤-중첩 문제와 마찬가지로 콘텐츠 상단이 중앙 기준 스크롤 시작 위치 아래로 내려가서 약관 동의 같은 첫 필드가 보이지 않습니다. React App Router CSS 기반으로 작은 콘텐츠는 여전히 중앙 정렬되도록 외곽은 flex-col overflow-y-auto, 내부 컨테이너는 my-auto를 사용해 보세요.

제안
-        <div className="flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-20 py-15 scrollbar-hide">
-          <div className="flex min-h-215 w-125">{children}</div>
+        <div className="flex min-h-0 flex-1 flex-col overflow-y-auto px-20 py-15 scrollbar-hide">
+          <div className="flex min-h-full w-full flex-col items-center">
+            <div className="my-auto flex min-h-215 w-full min-w-0 max-w-125">
+              {children}
+            </div>
+          </div>
         </div>
📝 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
<div className="flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-20 py-15 scrollbar-hide">
<div className="flex min-h-215 w-125">{children}</div>
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto px-20 py-15 scrollbar-hide">
<div className="flex min-h-full w-full flex-col items-center">
<div className="my-auto flex min-h-215 w-full min-w-0 max-w-125">
{children}
</div>
</div>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(auth)/layout.tsx around lines 44 - 45, Update the layout wrapper
around the `{children}` content to separate scrolling from centering: make the
outer container a column-oriented `overflow-y-auto` flex container, and add an
intermediate inner wrapper using `my-auto` for vertical centering. Preserve the
existing padding and child sizing while ensuring oversized content starts at the
top of the scrollable area and smaller content remains centered.

Source: Path instructions

</div>
</div>
</div>
);
Expand Down
51 changes: 39 additions & 12 deletions src/app/(auth)/signup/components/ProfileStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { z } from "zod";

import { AgreementItem } from "@/components/auth/AgreementItem";
import { PasswordField } from "@/components/auth/PasswordField";
import { TermsModal } from "@/components/auth/TermsModal";
import { Button } from "@/components/ui/Button";
import { TextField } from "@/components/ui/TextField";
import { PRIVACY_POLICY_CONTENT, SERVICE_TERMS_CONTENT } from "@/constants/auth/terms";
import { signup } from "@/lib/api/auth";
import { ApiError } from "@/lib/api/error";
import { TERMS_TYPE } from "@/types/auth.type";
Expand Down Expand Up @@ -68,6 +70,7 @@ export const ProfileStep = ({ email, verificationToken, onSubmit, onBack }: Prof
});

const [submitError, setSubmitError] = useState<string | null>(null);
const [openTerms, setOpenTerms] = useState<"service" | "privacy" | null>(null);

const agreementError = errors.agreedTerms ?? errors.agreedPrivacy;

Expand Down Expand Up @@ -152,24 +155,48 @@ export const ProfileStep = ({ email, verificationToken, onSubmit, onBack }: Prof
control={control}
name="agreedTerms"
render={({ field: { value, onChange } }) => (
<AgreementItem
required
label="IPX의 이용약관에 동의합니다"
checked={value}
onToggle={() => onChange(!value)}
/>
<>
<AgreementItem
required
label="IPX의 이용약관에 동의합니다"
checked={value}
onToggle={() => onChange(!value)}
onDetail={() => setOpenTerms("service")}
/>
{openTerms === "service" && (
<TermsModal
content={SERVICE_TERMS_CONTENT}
agreementLabel="IPX의 이용약관에 동의합니다"
checked={value}
onToggle={() => onChange(!value)}
onClose={() => setOpenTerms(null)}
/>
)}
</>
)}
/>
<Controller
control={control}
name="agreedPrivacy"
render={({ field: { value, onChange } }) => (
<AgreementItem
required
label="개인정보처리 방침에 동의합니다"
checked={value}
onToggle={() => onChange(!value)}
/>
<>
<AgreementItem
required
label="개인정보처리 방침에 동의합니다"
checked={value}
onToggle={() => onChange(!value)}
onDetail={() => setOpenTerms("privacy")}
/>
{openTerms === "privacy" && (
<TermsModal
content={PRIVACY_POLICY_CONTENT}
agreementLabel="개인정보처리 방침에 동의합니다"
checked={value}
onToggle={() => onChange(!value)}
onClose={() => setOpenTerms(null)}
/>
)}
</>
)}
/>
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/app/(auth)/signup/google/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { Suspense, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import { AgreementItem } from "@/components/auth/AgreementItem";
import { TermsModal } from "@/components/auth/TermsModal";
import { Button } from "@/components/ui/Button";
import { TextField } from "@/components/ui/TextField";
import { PRIVACY_POLICY_CONTENT, SERVICE_TERMS_CONTENT } from "@/constants/auth/terms";
import { signupWithGoogle } from "@/lib/api/auth";
import { ApiError } from "@/lib/api/error";
import { useAuthStore } from "@/store/authStore";
Expand Down Expand Up @@ -33,6 +35,7 @@ function GoogleSignupForm() {
const [agreedPrivacy, setAgreedPrivacy] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState<string | null>(null);
const [openTerms, setOpenTerms] = useState<"service" | "privacy" | null>(null);

const canSubmit = agreedTerms && agreedPrivacy && !isSubmitting;

Expand Down Expand Up @@ -98,15 +101,36 @@ function GoogleSignupForm() {
label="IPX의 이용약관에 동의합니다"
checked={agreedTerms}
onToggle={() => setAgreedTerms((checked) => !checked)}
onDetail={() => setOpenTerms("service")}
/>
<AgreementItem
required
label="개인정보처리 방침에 동의합니다"
checked={agreedPrivacy}
onToggle={() => setAgreedPrivacy((checked) => !checked)}
onDetail={() => setOpenTerms("privacy")}
/>
</div>

{openTerms === "service" && (
<TermsModal
content={SERVICE_TERMS_CONTENT}
agreementLabel="IPX의 이용약관에 동의합니다"
checked={agreedTerms}
onToggle={() => setAgreedTerms((checked) => !checked)}
onClose={() => setOpenTerms(null)}
/>
)}
{openTerms === "privacy" && (
<TermsModal
content={PRIVACY_POLICY_CONTENT}
agreementLabel="개인정보처리 방침에 동의합니다"
checked={agreedPrivacy}
onToggle={() => setAgreedPrivacy((checked) => !checked)}
onClose={() => setOpenTerms(null)}
/>
)}

{submitError && (
<p className="text-body-15 text-error-default">
{submitError}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(main)/myhistory/[id]/novelty/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export default function NoveltyPage({ params }: { params: Promise<{ id: string }
<Header
title={analysis.primaryArt.title}
status={analysis.primaryArt.legalStatus}
patentNumber={analysis.primaryArt.applicationNumber}
applicationNumber={analysis.primaryArt.applicationNumber}
organization={analysis.primaryArt.applicantName}
/>

Expand Down
10 changes: 5 additions & 5 deletions src/app/(main)/search/loading/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ function LoadingContent() {

const handleStop = async () => {
if (!caseId) {
router.push("/search");
router.push("/search?resume=1");
return;
}

setCancelError(null);
setIsStopping(true);
try {
await cancelSearch(Number(caseId));
router.push("/search");
router.push("/search?resume=1");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (err) {
if (err instanceof ApiError) {
setCancelError(
Expand All @@ -132,7 +132,7 @@ function LoadingContent() {
};

useEffect(() => {
if (!caseId) return;
if (!caseId || isStopping) return;

let cancelled = false;
let timer: ReturnType<typeof setTimeout>;
Expand All @@ -141,7 +141,7 @@ function LoadingContent() {
const poll = async () => {
try {
const result = await getSearchStatus(Number(caseId));
if (cancelled) return;
if (cancelled || isStopping) return;
consecutiveErrors = 0;
setStatus(result);
setPollError(null);
Expand Down Expand Up @@ -179,7 +179,7 @@ function LoadingContent() {
cancelled = true;
clearTimeout(timer);
};
}, [caseId, router, title]);
}, [caseId, isStopping, router, title]);

useEffect(() => {
if (caseId) return;
Expand Down
6 changes: 6 additions & 0 deletions src/app/(main)/tech/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getPriorArtDetail } from "@/lib/api/search";
import { ApiError } from "@/lib/api/error";
import { useKiprisThumbnail } from "@/hooks/useKiprisThumbnail";
import { formatPeriod } from "@/lib/priorArtFormat";
import { buildOriginalDocumentUrl } from "@/lib/patentOriginalDocument";
import { RELEVANCE_LABEL, RELEVANCE_VARIANT, scoreToRelevance } from "@/lib/priorArtRelevance";
import type { PriorArtDetail } from "@/types/search.type";

Expand Down Expand Up @@ -139,6 +140,11 @@ export default function TechDetailPage() {
size="sm"
variant="secondary"
className="h-10.25 shrink-0 gap-1 rounded-md py-2.5 pr-4 pl-3"
disabled={!buildOriginalDocumentUrl(detail.applicationNumber)}
onClick={() => {
const url = buildOriginalDocumentUrl(detail.applicationNumber);
if (url) window.open(url, "_blank", "noopener,noreferrer");
}}
>
<ExternalIcon className="size-5 shrink-0 [&_path]:fill-current" aria-hidden />
원문보기
Expand Down
86 changes: 86 additions & 0 deletions src/components/auth/TermsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import CancelIcon from "@/components/icons/icon-cancel.svg";
import { Radio } from "@/components/ui/Radio";
import type { TermsContent } from "@/constants/auth/terms";

type TermsModalProps = {
content: TermsContent;
agreementLabel: string;
checked: boolean;
onToggle: () => void;
onClose: () => void;
};

export function TermsModal({
content,
agreementLabel,
checked,
onToggle,
onClose,
}: TermsModalProps) {
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-scrim-2"
onClick={onClose}
>
<div
className="flex h-112.5 w-138.5 flex-col gap-6 rounded-lg bg-bg-surface p-8 shadow-[0px_1px_6px_0px_rgba(144,155,165,0.36)]"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between">
<h2 className="text-body-emphasis-17 text-title-primary">{content.title}</h2>
<button
type="button"
onClick={onClose}
aria-label="닫기"
className="flex cursor-pointer items-center justify-center"
>
<CancelIcon className="h-6 w-6" aria-hidden />
</button>
</div>

<div className="flex flex-1 flex-col gap-6 overflow-y-auto pr-1 scrollbar-hide">
{content.sections.map((section) => (
<div key={section.heading} className="flex flex-col gap-2">
<h3 className="text-body-emphasis-17 text-title-secondary">{section.heading}</h3>
{section.paragraphs?.map((paragraph) => (
<p key={paragraph} className="text-body-15 text-body-secondary">
{paragraph}
</p>
))}
{section.bullets && (
<ul className="flex flex-col gap-1 pl-4">
{section.bullets.map((bullet) => (
<li key={bullet} className="list-disc text-body-15 text-body-secondary">
{bullet}
</li>
))}
</ul>
)}
{section.numbered && (
<ol className="flex flex-col gap-1 pl-4">
{section.numbered.map((item) => (
<li key={item} className="list-decimal text-body-15 text-body-secondary">
{item}
</li>
))}
</ol>
)}
</div>
))}
</div>

<hr className="h-px w-full border-0 bg-outline-sub" />

<label className="flex cursor-pointer items-center gap-2">
<Radio checked={checked} readOnly onClick={onToggle} />
<span className="flex items-center gap-1">
<span className="text-label-15 text-primary-default">[필수]</span>
<span className="text-label-15 text-title-secondary">{agreementLabel}</span>
</span>
</label>
</div>
</div>
);
}
Loading
Loading