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
28 changes: 28 additions & 0 deletions src/api/signupPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { apiFetch } from './client'

export interface AgreementPolicy {
version: string
required: boolean
content_path: string | null
}

export interface SignupPolicy {
password_policy: {
min_length: number
max_length: number
require_letter: boolean
require_digit: boolean
}
agreements: {
service_terms: AgreementPolicy
privacy_policy: AgreementPolicy
marketing: AgreementPolicy
}
}

export function fetchSignupPolicy() {
return apiFetch<SignupPolicy>('/auth/signup-policy', {
method: 'GET',
skipAuthRetry: true,
})
}
5 changes: 4 additions & 1 deletion src/pages/IntroPage/IntroPage.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@
max-width: 640px;
background: var(--fowoco-white);
border-radius: 28px;
box-shadow: 0 24px 0 -8px rgba(7, 94, 91, 0.08), 0 32px 48px rgba(7, 94, 91, 0.16);
box-shadow:
0 24px 0 -8px rgba(7, 94, 91, 0.08),
0 32px 48px rgba(7, 94, 91, 0.16);
overflow: hidden;
}

Expand Down Expand Up @@ -984,6 +986,7 @@
background: none;
border: none;
cursor: pointer;
text-decoration: none;
}

@media (max-width: 960px) {
Expand Down
15 changes: 7 additions & 8 deletions src/pages/IntroPage/IntroPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ export function IntroPage() {
<h1 className={styles.headline}>
복잡한 HR 행정업무,
<br />
<span className={styles.headlineAccent}>Agent</span>가 준비하고 사람이
결정합니다.
<span className={styles.headlineAccent}>Agent</span>가 준비하고 사람이 결정합니다.
</h1>
<p className={styles.subtext}>
FOWOCO는 체류·계약·문서 업무를 분석해 필요한 정보와 초안을 준비합니다. 담당자는
Expand Down Expand Up @@ -260,8 +259,8 @@ export function IntroPage() {
<p className={styles.sectionKicker}>SIX WORKFLOWS</p>
<h2 className={styles.sectionTitle}>FOWOCO의 6대 Workflow</h2>
<p className={styles.sectionSubtext}>
한 사람의 한 사건을 끝까지 묶는 Master Workflow 3종과, 여러 Case에서 반복
사용하는 Reusable Workflow 3종으로 구성됩니다.
한 사람의 한 사건을 끝까지 묶는 Master Workflow 3종과, 여러 Case에서 반복 사용하는
Reusable Workflow 3종으로 구성됩니다.
</p>
<div className={styles.workflowGrid}>
{WORKFLOWS.map((workflow) => {
Expand Down Expand Up @@ -368,12 +367,12 @@ export function IntroPage() {
프로젝트 소개 · GitHub · 팀 소개 · 문의하기 · 외부 링크는 Prototype Only
</p>
<div className={styles.footerLegalActions}>
<button type="button" className={styles.footerAction}>
<Link to="/legal/privacy" className={styles.footerAction}>
개인정보처리방침
</button>
<button type="button" className={styles.footerAction}>
</Link>
<Link to="/legal/terms" className={styles.footerAction}>
서비스 이용약관
</button>
</Link>
</div>
</div>
</div>
Expand Down
128 changes: 128 additions & 0 deletions src/pages/LegalPolicyPage/LegalPolicyPage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
.page {
min-height: 100svh;
padding: 0 24px 64px;
background: var(--surface-subtle);
color: var(--text-primary);
}

.header {
width: min(880px, 100%);
min-height: 72px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
}

.brand {
color: var(--brand-primary);
font-size: 20px;
font-weight: 700;
text-decoration: none;
}

.nav {
display: flex;
gap: 20px;
}

.nav a {
color: var(--text-secondary);
font-size: 13px;
font-weight: 500;
text-decoration: none;
}

.nav a[aria-current='page'] {
color: var(--brand-primary);
}

.document {
width: min(880px, 100%);
margin: 0 auto;
padding: 56px clamp(24px, 6vw, 72px);
border: 1px solid var(--border-default);
border-radius: var(--fowoco-radius-6);
background: var(--surface-default);
}

.eyebrow {
margin: 0;
color: var(--brand-primary);
font-size: 12px;
font-weight: 700;
}

.document h1 {
margin: 10px 0 0;
font-size: clamp(30px, 5vw, 42px);
line-height: 1.2;
}

.summary {
margin: 16px 0 0;
color: var(--text-secondary);
font-size: 15px;
line-height: 1.7;
}

.meta {
margin: 12px 0 0;
color: var(--text-secondary);
font-size: 12px;
}

.notice {
margin-top: 32px;
padding: 16px 18px;
border-left: 3px solid var(--brand-primary);
background: var(--surface-subtle);
color: var(--text-secondary);
font-size: 13px;
line-height: 1.6;
}

.sections {
margin-top: 36px;
display: grid;
gap: 30px;
}

.sections section {
padding-bottom: 30px;
border-bottom: 1px solid var(--border-default);
}

.sections h2 {
margin: 0;
font-size: 17px;
}

.sections p {
margin: 10px 0 0;
color: var(--text-secondary);
font-size: 14px;
line-height: 1.8;
}

.documentFooter {
margin-top: 36px;
}

.documentFooter a {
color: var(--brand-primary);
font-size: 13px;
font-weight: 600;
text-decoration: none;
}

@media (max-width: 560px) {
.page {
padding-inline: 12px;
}

.document {
padding-block: 36px;
}
}
62 changes: 62 additions & 0 deletions src/pages/LegalPolicyPage/LegalPolicyPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { render, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { LegalPolicyPage } from './LegalPolicyPage'

beforeEach(() => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue(
new Response(
JSON.stringify({
password_policy: {
min_length: 8,
max_length: 128,
require_letter: true,
require_digit: true,
},
agreements: {
service_terms: { version: '2.0', required: true, content_path: '/legal/terms' },
privacy_policy: {
version: '3.0',
required: true,
content_path: '/legal/privacy',
},
marketing: { version: '1.0', required: false, content_path: null },
},
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } },
),
),
)
})

afterEach(() => {
vi.unstubAllGlobals()
})

describe('LegalPolicyPage', () => {
it('renders the service terms and current version', async () => {
render(
<MemoryRouter>
<LegalPolicyPage kind="terms" />
</MemoryRouter>,
)

expect(screen.getByRole('heading', { name: '서비스 이용약관' })).toBeInTheDocument()
expect(await screen.findByText(/버전 2.0/)).toBeInTheDocument()
expect(screen.getByText('3. AI 업무보조의 범위')).toBeInTheDocument()
})

it('renders the privacy processing notice and current version', async () => {
render(
<MemoryRouter>
<LegalPolicyPage kind="privacy" />
</MemoryRouter>,
)

expect(screen.getByRole('heading', { name: '개인정보 처리 안내' })).toBeInTheDocument()
expect(await screen.findByText(/버전 3.0/)).toBeInTheDocument()
expect(screen.getByText('3. 보관과 접근')).toBeInTheDocument()
})
})
Loading