Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
20b595b
fix: ๋‘๊ฐ€์ง€ ๋ฒ„๊ทธ ํ”ฝ์Šค
Hyeonjun0527 Feb 1, 2026
fbfddc4
Merge pull request #359 from code-zero-to-one/fix/new-feature-bug
Hyeonjun0527 Feb 1, 2026
d943df1
fix: ๊ฐ์ข… ๋ฒ„๊ทธ ์ˆ˜์ •
Hyeonjun0527 Feb 1, 2026
87be95b
Merge pull request #361 from code-zero-to-one/release/new-feature-bugโ€ฆ
Hyeonjun0527 Feb 1, 2026
776874b
fix: ์Šคํฌ๋กค ๋ฌธ์ œ ํ•ด๊ฒฐ
Hyeonjun0527 Feb 1, 2026
4f9ef70
Merge pull request #363 from code-zero-to-one/fix/scroll
Hyeonjun0527 Feb 1, 2026
59514c0
fix: ์Šค๋ ˆ๋“œ ๋ฐ ๋Œ“๊ธ€ ์กฐํšŒ ๊ธฐ๋Šฅ ๊ฐœ์„ , ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ถ”๊ฐ€ ๋ฐ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋ฉ”์‹œ์ง€ ๊ฐœ์„ 
HA-SEUNG-JEONG Feb 2, 2026
d8344a8
fix: ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋ฉ”์‹œ์ง€์— ๋‹ค์‹œ ์‹œ๋„ ๋ฒ„ํŠผ ์ถ”๊ฐ€
HA-SEUNG-JEONG Feb 2, 2026
dd67f74
fix: ๋Œ“๊ธ€ ์ž‘์„ฑ ํ›„ ํŽ˜์ด์ง€๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ์ฝ”๋“œ ์ œ๊ฑฐ
HA-SEUNG-JEONG Feb 2, 2026
c906fc5
fix: ๋Œ“๊ธ€ ์กฐํšŒ ๊ธฐ๋Šฅ ๊ฐœ์„  ๋ฐ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ถ”๊ฐ€, ๋Œ“๊ธ€ ์‘๋‹ต ํƒ€์ž… ์ˆ˜์ •
HA-SEUNG-JEONG Feb 2, 2026
e7d7012
Merge pull request #366 from code-zero-to-one/fix-mission
HA-SEUNG-JEONG Feb 2, 2026
05e8327
chore: ์ฝ”๋“œ๋ž˜๋น—
Hyeonjun0527 Feb 3, 2026
716e8ce
chore:์ฝ”๋“œ๋ž˜๋น— 2
Hyeonjun0527 Feb 3, 2026
1f37be9
fix : ๊ทธ๋ฃน/๋ฉ˜ํ† ์Šคํ„ฐ๋”” ๊ธฐ๋ณธ๊ฐ’์„ '๋ชจ์ง‘์ค‘' ์œผ๋กœ
seong-jin-jo Feb 3, 2026
9f6cc59
chore : prettier
seong-jin-jo Feb 3, 2026
41b1313
chore : prettier ์—์„œ corderabbit ์ œ์™ธ
seong-jin-jo Feb 3, 2026
92e916a
feat : ๊ทธ๋ฃน์Šคํ„ฐ๋””/๋ฉ˜ํ† ์Šคํ„ฐ๋””์—์„œ ๋‚ด๊ฐ€ ์ฐธ์—ฌ์ค‘์ธ ์Šคํ„ฐ๋””๋ชฉ๋ก ์กฐํšŒ
seong-jin-jo Feb 3, 2026
c396df8
chore : ๋‚˜์˜ ์†Œ์ค‘ํ•œ ์Šคํ„ฐ๋””๋กœ ์ •์ •
seong-jin-jo Feb 3, 2026
9237593
chore : CI
seong-jin-jo Feb 3, 2026
31be1a8
Merge pull request #370 from code-zero-to-one/feat/SRPINT3/enhance-joโ€ฆ
seong-jin-jo Feb 3, 2026
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
89 changes: 89 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "ko-KR"
early_access: false

reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
high_level_summary_placeholder: "@coderabbitai summary"
poem: true
review_status: true
review_details: false

# ์ž๋™ ๋ฆฌ๋ทฐ ์„ค์ •
auto_review:
enabled: true
# drafts: false = ๋“œ๋ž˜ํ”„ํŠธ PR์€ ๋ฆฌ๋ทฐํ•˜์ง€ ์•Š์Œ
# drafts: true = ๋“œ๋ž˜ํ”„ํŠธ PR๋„ ๋ฆฌ๋ทฐํ•จ (๋ชจ๋“  ๋ธŒ๋žœ์น˜์— ์ ์šฉ๋˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๋“œ๋ž˜ํ”„ํŠธ PR ํฌํ•จ ์—ฌ๋ถ€)
drafts: true
auto_incremental_review: true
base_branches:
- develop
- main

# ๊ฒฝ๋กœ ํ•„ํ„ฐ (ํ•„์š”์‹œ ์กฐ์ •)
path_filters: []

# ๋„๊ตฌ ์„ค์ •
tools:
# ESLint: JavaScript/TypeScript ๋ฆฐํ„ฐ (ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ ์ค‘)
eslint:
enabled: true
# Biome: ๋น ๋ฅธ ํฌ๋งทํ„ฐ/๋ฆฐํ„ฐ (ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ ์•ˆ ํ•จ - ๋น„ํ™œ์„ฑํ™” ๊ถŒ์žฅ)
biome:
enabled: false
# Markdownlint: Markdown ํŒŒ์ผ ๋ฆฐํ„ฐ
markdownlint:
enabled: true
# GitHub Checks: GitHub์˜ ์ฒดํฌ ์ƒํƒœ ํ‘œ์‹œ
github-checks:
enabled: true
# Gitleaks: ์‹œํฌ๋ฆฟ(API ํ‚ค, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ) ์œ ์ถœ ๊ฒ€์‚ฌ
gitleaks:
enabled: true
# OSV Scanner: ์˜คํ”ˆ์†Œ์Šค ์ทจ์•ฝ์  ์Šค์บ๋„ˆ
osvScanner:
enabled: true

chat:
auto_reply: true
art: true

# ์ง€์‹ ๋ฒ ์ด์Šค ์„ค์ •
knowledge_base:
opt_out: false
web_search:
enabled: true
code_guidelines:
enabled: true
# ์ฝ”๋“œ ๊ฐ€์ด๋“œ๋ผ์ธ ํŒŒ์ผ ํŒจํ„ด (์‹ค์ œ ์กด์žฌํ•˜๋Š” ํŒŒ์ผ๋งŒ ํฌํ•จ)
filePatterns:
- "**/CLAUDE.md" # โœ… ์กด์žฌํ•จ (๋ฃจํŠธ์— ์žˆ์Œ)
# - "**/.cursorrules" # โŒ ์กด์žฌํ•˜์ง€ ์•Š์Œ
# - ".github/copilot-instructions.md" # โŒ ์กด์žฌํ•˜์ง€ ์•Š์Œ
# - ".github/instructions/*.instructions.md" # โŒ ์กด์žฌํ•˜์ง€ ์•Š์Œ
pull_requests:
scope: "auto"
issues:
scope: "auto"

# ์ฝ”๋“œ ์ƒ์„ฑ ์„ค์ •
code_generation:
docstrings:
language: "ko-KR"
path_instructions: []
unit_tests:
path_instructions: []

# ์ด์Šˆ ํ’๋ถ€ํ™” ์„ค์ •
issue_enrichment:
planning:
enabled: true
auto_planning:
enabled: true
labels: []
labeling:
auto_apply_labels: false
labeling_instructions: []

5 changes: 4 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ build/
# OpenAPI auto-generated files
src/api/openapi

**/*.json
**/*.json

# CodeRabbit config
.coderabbit.yaml
14 changes: 13 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { NextConfig } from 'next';
import type { RemotePattern } from 'next/dist/shared/lib/image-config';

const isProd = process.env.NODE_ENV === 'production';

const nextConfig: NextConfig = {
// output: 'standalone',
Expand Down Expand Up @@ -31,7 +34,16 @@ const nextConfig: NextConfig = {
hostname: 'www.zeroone.it.kr',
pathname: '/**',
},
// CMS ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ด๋ฏธ์ง€ ๋„๋ฉ”์ธ ํ—ˆ์šฉ ์„ค์ •
...(isProd
? ([] as RemotePattern[])
: ([
{
protocol: 'http',
hostname: 'localhost',
port: '8080',
pathname: '/**',
},
] as RemotePattern[])),
{
protocol: 'http',
hostname: 'localhost',
Expand Down
10 changes: 8 additions & 2 deletions src/app/(service)/home/home-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ import CommunityTab from '@/features/study/one-to-one/balance-game/ui/community-
import HallOfFameTab from '@/features/study/one-to-one/hall-of-fame/ui/hall-of-fame-tab';
import StudyHistoryTab from '@/features/study/one-to-one/history/ui/study-history-tab';
import StudyTab from '@/features/study/one-to-one/schedule/ui/home-study-tab';
import { getServerCookie } from '@/utils/server-cookie';
import { isNumeric } from '@/utils/validation';

interface HomeContentProps {
activeTab: string;
}

export default function HomeContent({ activeTab }: HomeContentProps) {
export default async function HomeContent({ activeTab }: HomeContentProps) {
const isHistoryTab = activeTab === 'history';
const memberIdStr = isHistoryTab ? await getServerCookie('memberId') : null;
const isLoggedIn = !!memberIdStr && isNumeric(memberIdStr);

const renderTabContent = () => {
switch (activeTab) {
case 'study':
return <StudyTab />;
case 'history':
return <StudyHistoryTab />;
return isLoggedIn ? <StudyHistoryTab /> : <StudyTab />;
case 'ranking':
return <HallOfFameTab />;
case 'archive':
Expand Down
6 changes: 3 additions & 3 deletions src/app/(service)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ export default function ServiceLayout({
return (
<html lang="ko">
<head>{GTM_ID && <GoogleTagManager gtmId={GTM_ID} />}</head>
<body className={clsx(pretendard.className, 'h-screen w-screen')}>
<body className={clsx(pretendard.className, 'min-h-screen w-screen')}>
<MainProvider>
<PageViewTracker />
<div className="flex h-screen w-full flex-col overflow-hidden">
<div className="flex min-h-screen w-full flex-col overflow-x-auto">
<Header />
<main className="w-full flex-1 overflow-auto">{children}</main>
<main className="w-full flex-1">{children}</main>
</div>
</MainProvider>
</body>
Expand Down
2 changes: 1 addition & 1 deletion src/components/card/voting-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default function VotingCard({ voting, onClick }: VotingCardProps) {
</p>
)}

{/* ๊ฐ„๋‹จํ•œ ํˆฌํ‘œ ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (ํˆฌํ‘œํ–ˆ์„ ๋•Œ๋งŒ) */}
{/* ๊ฐ„๋‹จํ•œ ํˆฌํ‘œ ๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */}
{hasVoted && (
<div className="rounded-100 border-border-subtle bg-background-alternative mb-300 border p-300">
<div className="font-designer-12b text-text-subtle mb-100">
Expand Down
5 changes: 3 additions & 2 deletions src/components/filtering/study-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,16 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) {
type: [],
targetRoles: [],
method: [],
recruiting: false,
recruiting: true, // ๊ธฐ๋ณธ๊ฐ’: ๋ชจ์ง‘ ์ค‘๋งŒ ๋ณด๊ธฐ
});
}, [onChange]);

// recruiting์€ ๊ธฐ๋ณธ๊ฐ’์ด๋ฏ€๋กœ ํ•„ํ„ฐ๋กœ ๊ฐ„์ฃผํ•˜์ง€ ์•Š์Œ
const hasAnyFilter =
values.type.length > 0 ||
values.targetRoles.length > 0 ||
values.method.length > 0 ||
values.recruiting;
!values.recruiting; // recruiting์ด false๋ฉด ํ•„ํ„ฐ ์ ์šฉ ์ค‘

return (
<div className="flex items-center gap-100">
Expand Down
10 changes: 9 additions & 1 deletion src/components/home/tab-navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
History,
} from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import { getCookie } from '@/api/client/cookie';
import { cn } from '@/components/ui/(shadcn)/lib/utils';
import { useAuth } from '@/hooks/common/use-auth';

interface TabNavigationProps {
activeTab: string;
Expand Down Expand Up @@ -50,6 +52,12 @@ const TABS = [
export default function TabNavigation({ activeTab }: TabNavigationProps) {
const router = useRouter();
const searchParams = useSearchParams();
const { isAuthenticated } = useAuth();
const hasMemberId = !!getCookie('memberId');
const canViewHistory = isAuthenticated && hasMemberId;
const visibleTabs = canViewHistory
? TABS
: TABS.filter((tab) => tab.id !== 'history');

const handleTabChange = (tabId: string) => {
const params = new URLSearchParams(searchParams.toString());
Expand All @@ -64,7 +72,7 @@ export default function TabNavigation({ activeTab }: TabNavigationProps) {
</div>

<nav className="border-border-subtle flex gap-100 border-b">
{TABS.map((tab) => {
{visibleTabs.map((tab) => {
const Icon = tab.icon;
const isActive = activeTab === tab.id;

Expand Down
31 changes: 26 additions & 5 deletions src/components/pages/group-study-list-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useGetStudies } from '@/hooks/queries/study-query';
import GroupStudyFormModal from '../../features/study/group/ui/group-study-form-modal';
import GroupStudyPagination from '../../features/study/group/ui/group-study-pagination';
import GroupStudyList from '../lists/group-study-list';
import MyParticipatingStudiesSection from '../section/my-participating-studies-section';

// Carousel์ด ํด๋ผ์ด์–ธํŠธ ์ „์šฉ์ด๋ฏ€๋กœ dynamic import๋กœ ๋กœ๋“œ
const Banner = dynamic(() => import('@/widgets/home/banner'), {
Expand All @@ -38,12 +39,18 @@ export default function GroupStudyListPage() {
const [searchQuery, setSearchQuery] = useState('');

// URL์—์„œ ํ•„ํ„ฐ ๊ฐ’ ์ฝ๊ธฐ
// ๊ธฐ๋ณธ๊ฐ’: recruiting = true (๋ชจ์ง‘ ์ค‘๋งŒ ๋ณด๊ธฐ)
// ์‚ฌ์šฉ์ž๊ฐ€ ํ† ๊ธ€์„ ์กฐ์ž‘ํ•˜๋ฉด URL์— ๋ช…์‹œ์ ์œผ๋กœ ์ €์žฅ๋จ
const filterValues = useMemo<StudyFilterValues>(() => {
const type = searchParams.get('type')?.split(',').filter(Boolean) ?? [];
const targetRoles =
searchParams.get('targetRoles')?.split(',').filter(Boolean) ?? [];
const method = searchParams.get('method')?.split(',').filter(Boolean) ?? [];
const recruiting = searchParams.get('recruiting') === 'true';
// URL์— recruiting ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ true (๋ชจ์ง‘ ์ค‘๋งŒ ๋ณด๊ธฐ)
// ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ๊ฐ’ ์‚ฌ์šฉ (๋ช…์‹œ์  ์‚ฌ์šฉ์ž ์„ ํƒ)
const recruitingParam = searchParams.get('recruiting');
const recruiting =
recruitingParam === null ? true : recruitingParam === 'true';

return { type, targetRoles, method, recruiting };
}, [searchParams]);
Expand All @@ -67,7 +74,8 @@ export default function GroupStudyListPage() {
filterValues.method.length > 0
? (filterValues.method as GetGroupStudiesMethodEnum[])
: undefined,
recruiting: filterValues.recruiting || undefined,
// ๊ธฐ๋ณธ๊ฐ’: true (๋ชจ์ง‘ ์ค‘๋งŒ), false๋ฉด ์ „์ฒด ์กฐํšŒ
recruiting: filterValues.recruiting ? true : undefined,
});

const allStudies = useMemo(() => data?.content ?? [], [data?.content]);
Expand All @@ -78,10 +86,16 @@ export default function GroupStudyListPage() {
const params = new URLSearchParams(searchParams.toString());

Object.entries(updates).forEach(([key, value]) => {
if (value === undefined || value === '' || value === 'false') {
if (value === undefined || value === '') {
params.delete(key);
} else {
params.set(key, value);
// recruiting์˜ ๊ฒฝ์šฐ 'false'๋„ ๋ช…์‹œ์ ์œผ๋กœ ์ €์žฅ (๊ธฐ๋ณธ๊ฐ’์ด true์ด๋ฏ€๋กœ)
// ๋‹ค๋ฅธ ํ•„ํ„ฐ๋Š” 'false'์ผ ๋•Œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ฑฐ
if (key === 'recruiting' || value !== 'false') {
params.set(key, value);
} else {
params.delete(key);
}
}
});

Expand All @@ -97,6 +111,8 @@ export default function GroupStudyListPage() {
);

// ํ•„ํ„ฐ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ
// recruiting ํ† ๊ธ€: true๋ฉด 'true' ์ €์žฅ, false๋ฉด 'false' ๋ช…์‹œ์ ์œผ๋กœ ์ €์žฅ
// (๊ธฐ๋ณธ๊ฐ’์ด true์ด๋ฏ€๋กœ false์ผ ๋•Œ๋„ ๋ช…์‹œ์ ์œผ๋กœ ์ €์žฅํ•ด์•ผ ํ† ๊ธ€์ด ์ •์ƒ ์ž‘๋™)
const handleFilterChange = useCallback(
(values: StudyFilterValues) => {
updateSearchParams({
Expand All @@ -106,7 +122,9 @@ export default function GroupStudyListPage() {
? values.targetRoles.join(',')
: undefined,
method: values.method.length > 0 ? values.method.join(',') : undefined,
recruiting: values.recruiting ? 'true' : undefined,
// recruiting: true๋ฉด 'true', false๋ฉด 'false' ๋ช…์‹œ์ ์œผ๋กœ ์ €์žฅ
// (๊ธฐ๋ณธ๊ฐ’์ด true์ด๋ฏ€๋กœ false์ผ ๋•Œ๋„ URL์— ์ €์žฅํ•ด์•ผ ํ† ๊ธ€ ํ•ด์ œ ๊ฐ€๋Šฅ)
recruiting: values.recruiting ? 'true' : 'false',
});
},
[updateSearchParams],
Expand Down Expand Up @@ -158,6 +176,9 @@ export default function GroupStudyListPage() {
<Banner />
</div>

{/* ๋‚ด๊ฐ€ ์ฐธ์—ฌ์ค‘์ธ ์Šคํ„ฐ๋”” ์„น์…˜ */}
<MyParticipatingStudiesSection classification="GROUP_STUDY" />

{/* ํ—ค๋” */}
<div className="mb-400 flex items-center justify-between">
<h1 className="font-designer-24b text-text-default">
Expand Down
Loading
Loading