Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e1e16a2
feat: 팀 페이지 구조 작성
Jieunsse Feb 19, 2026
bcd95fc
feat: 칸반 기능 구현
Jieunsse Feb 19, 2026
c1aa5fd
feat: 멤버 초대 카드 구현
Jieunsse Feb 19, 2026
21dd60a
feat: 팀 컴포넌트 구현
Jieunsse Feb 19, 2026
9177b5b
feat: 진행상황 요약 구현
Jieunsse Feb 19, 2026
6f31e3f
chore: 누락된 인터페이스 추가
Jieunsse Feb 19, 2026
9b77543
feat: 팀 페이지 도메인 컴포넌트 추가
Jieunsse Feb 21, 2026
3c62642
feat: addteam 이미지 업로드 API 및 쿼리 추가
Jieunsse Feb 21, 2026
0579f25
feat: 팀 그룹/할일목록 API 및 쿼리 추가
Jieunsse Feb 21, 2026
9c4bbf6
feat: 칸반 아이템 인라인 수정 및 접기/펼치기 기능 구현
Jieunsse Feb 21, 2026
8149b4a
feat: 멤버 케밥 메뉴 컴포넌트 추가 및 멤버 카드에 적용
Jieunsse Feb 21, 2026
499830e
feat: 팀 생성 모달 컴포넌트 추가
Jieunsse Feb 21, 2026
18f016f
refactor: 팀 사이드바의 팀 추가를 모달에서 링크로 변경
Jieunsse Feb 21, 2026
6809394
chore: git ignore 수정
Jieunsse Feb 21, 2026
8cd3a92
chore: 팀 생성 모달 관련 불필요한 파일 삭제
Jieunsse Feb 21, 2026
6f11c8e
style: 버튼 텍스트 밑줄 삭제
Jieunsse Feb 21, 2026
6b11e3d
refactor: 코드 리팩토링
Jieunsse Feb 22, 2026
09fd056
refactor: 대시보드 리팩토링
Jieunsse Feb 22, 2026
121e39d
refactor: kanbanBoard 비즈니스 로직을 훅으로 분리
Jieunsse Feb 22, 2026
0bd5e01
refactor: 반응형 대응 및 목업 제거
Jieunsse Feb 22, 2026
6da5cb8
refactor: 커스텀훅 분리, 실제 api 연결
Jieunsse Feb 22, 2026
7e235ad
refactor: 사이드바 로그인 관련 작업 및 레이아웃 수정
Jieunsse Feb 22, 2026
f01cdde
Merge branch 'main' into fix/kanban-api
Jieunsse Feb 22, 2026
9db3e7c
chore: 환경변수 예시 파일 추가
Jieunsse Feb 22, 2026
287413b
chore: .dev-session.json을 gitignore에 추가
Jieunsse Feb 22, 2026
20383b1
refactor: api 호출을 프록시 라우트 방식으로 변경
Jieunsse Feb 22, 2026
a10b8ec
fix: 팀 삭제 후 리다이렉트 및 유저 쿼리 무효화 수정
Jieunsse Feb 22, 2026
54bb53f
fix: 사이드바 로그인 상태 전달 및 spacer 너비 스타일 수정
Jieunsse Feb 22, 2026
381500d
fix: 사이드바 접힘 시 팀 목록 숨김 처리
Jieunsse Feb 22, 2026
457dc7d
feat: 초대 링크 복사 시 토스트 메시지 표시
Jieunsse Feb 22, 2026
da9573d
style: [teamid] 레이아웃 크기 확대 및 사이드바 여백 추가
Jieunsse Feb 22, 2026
7e9076a
feat: [teamid] 모바일/태블릿 햄버거 버튼으로 사이드바 드로어 열기 기능 추가
Jieunsse Feb 22, 2026
bbdcd7e
fix: 팀 생성 후 사이드바에 새 팀이 즉시 표시되지 않는 문제 수정
Jieunsse Feb 23, 2026
95dabac
refactor: navigation 플래그 추가
Jieunsse Feb 23, 2026
7fc7522
feat: useParam 통해서 리다이렉팅 대응
Jieunsse Feb 23, 2026
753c194
refactor: route.ts delete 추가
Jieunsse Feb 23, 2026
89f55d6
refactor: 팀 참가하기 토큰값 반영되도록 수정
Jieunsse Feb 23, 2026
2d7510f
fix: 임포트 경로 에러 수정
Jieunsse Feb 23, 2026
94f5b79
fix: useEffect 수정
Jieunsse Feb 23, 2026
9ce359b
fix: useSearchParams Suspense 경계 추가로 빌드 에러 수정
Jieunsse Feb 23, 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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
API_BASE_URL=https://fe-project-cowokers.vercel.app
API_TEAM_ID=20-1
NEXT_PUBLIC_APP_URL=http://localhost:3000
80 changes: 77 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# MCP 설정 (API 토큰 포함)
.mcp.json

# dependencies
/node_modules
/.pnp
Expand Down Expand Up @@ -41,7 +44,78 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts


# personal doc
.documents/*
# dev scripts
scripts/refresh-dev-token.mjs
.dev-session.json
.documents/AI_RULES.md
.documents/clean.md
.documents/daily-report.md
.documents/demands.md
.documents/rules/_sections.md
.documents/rules/_template.md
.documents/rules/advanced-event-handler-refs.md
.documents/rules/advanced-init-once.md
.documents/rules/advanced-use-latest.md
.documents/rules/async-api-routes.md
.documents/rules/async-defer-await.md
.documents/rules/async-dependencies.md
.documents/rules/async-parallel.md
.documents/rules/async-suspense-boundaries.md
.documents/rules/bundle-barrel-imports.md
.documents/rules/bundle-conditional.md
.documents/rules/bundle-defer-third-party.md
.documents/rules/bundle-dynamic-imports.md
.documents/rules/bundle-preload.md
.documents/rules/client-event-listeners.md
.documents/rules/client-localstorage-schema.md
.documents/rules/client-passive-event-listeners.md
.documents/rules/client-swr-dedup.md
.documents/rules/js-batch-dom-css.md
.documents/rules/js-cache-function-results.md
.documents/rules/js-cache-property-access.md
.documents/rules/js-cache-storage.md
.documents/rules/js-combine-iterations.md
.documents/rules/js-early-exit.md
.documents/rules/js-hoist-regexp.md
.documents/rules/js-index-maps.md
.documents/rules/js-length-check-first.md
.documents/rules/js-min-max-loop.md
.documents/rules/js-set-map-lookups.md
.documents/rules/js-tosorted-immutable.md
.documents/rules/rendering-activity.md
.documents/rules/rendering-animate-svg-wrapper.md
.documents/rules/rendering-conditional-render.md
.documents/rules/rendering-content-visibility.md
.documents/rules/rendering-hoist-jsx.md
.documents/rules/rendering-hydration-no-flicker.md
.documents/rules/rendering-hydration-suppress-warning.md
.documents/rules/rendering-svg-precision.md
.documents/rules/rendering-usetransition-loading.md
.documents/rules/rerender-defer-reads.md
.documents/rules/rerender-dependencies.md
.documents/rules/rerender-derived-state-no-effect.md
.documents/rules/rerender-derived-state.md
.documents/rules/rerender-functional-setstate.md
.documents/rules/rerender-lazy-state-init.md
.documents/rules/rerender-memo-with-default-value.md
.documents/rules/rerender-memo.md
.documents/rules/rerender-move-effect-to-event.md
.documents/rules/rerender-simple-expression-in-memo.md
.documents/rules/rerender-transitions.md
.documents/rules/rerender-use-ref-transient-values.md
.documents/rules/server-after-nonblocking.md
.documents/rules/server-auth-actions.md
.documents/rules/server-cache-lru.md
.documents/rules/server-cache-react.md
.documents/rules/server-dedup-props.md
.documents/rules/server-parallel-fetching.md
.documents/rules/server-serialization.md
.gitignore
.claude/CLAUDE.md
.claude/config.json
.husky/pre-push
.husky/pre-push
.husky/pre-push
coworkers-swagger.json
.gitignore
GEMINI.md
10 changes: 8 additions & 2 deletions src/app/(root)/mypage/hooks/useUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
import { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation';

import { getUser, updateUser, deleteUser, changePassword, uploadImage } from '@/shared/apis/user';
import type { UserResponse } from '@/shared/apis/user';
import {
getUser,
updateUser,
deleteUser,
changePassword,
uploadImage,
} from '@/shared/apis/user/index';
import type { UserResponse } from '@/shared/apis/user/index';

export function useUser() {
const router = useRouter();
Expand Down
37 changes: 37 additions & 0 deletions src/app/[teamid]/_domain/apis/group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { requestJson, requestVoid } from '@/shared/apis/groups/http';
import type { Group, Task, UpdateGroupBody } from './types';

const GROUP_ERROR_MESSAGE = {
fetch: '그룹 정보 조회 실패',
update: '그룹 정보 수정 실패',
delete: '그룹 삭제 실패',
invitation: '초대 링크 조회 실패',
tasks: '그룹 작업 조회 실패',
} as const;

export function getGroup(groupId: number): Promise<Group> {
return requestJson<Group>(`/groups/${groupId}`, GROUP_ERROR_MESSAGE.fetch);
}

export function updateGroup(groupId: number, body: UpdateGroupBody): Promise<Group> {
return requestJson<Group>(`/groups/${groupId}`, GROUP_ERROR_MESSAGE.update, {
method: 'PATCH',
body: JSON.stringify(body),
});
}

export function deleteGroup(groupId: number): Promise<void> {
return requestVoid(`/groups/${groupId}`, GROUP_ERROR_MESSAGE.delete, {
method: 'DELETE',
});
}

// API 응답: 초대 토큰 문자열 (JWT) 그대로 반환
export function getGroupInvitation(groupId: number): Promise<string> {
return requestJson<string>(`/groups/${groupId}/invitation`, GROUP_ERROR_MESSAGE.invitation);
}
Comment on lines +30 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The requestJson helper calls response.json() internally. If the API returns a raw JWT string (as the comment on line 29 suggests), this will result in a syntax error during JSON parsing because a raw JWT is not a valid JSON value. If the response is indeed a raw string, you should use a helper that utilizes response.text() instead.


export function getGroupTasks(groupId: number, date?: string): Promise<Task[]> {
const query = date ? `?date=${encodeURIComponent(date)}` : '';
return requestJson<Task[]>(`/groups/${groupId}/tasks${query}`, GROUP_ERROR_MESSAGE.tasks);
}
31 changes: 31 additions & 0 deletions src/app/[teamid]/_domain/apis/taskList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { requestJson, requestVoid } from '@/shared/apis/groups/http';
import type { TaskList } from './types';

const TASK_LIST_ERROR_MESSAGE = {
create: '작업 목록 생성 실패',
fetch: '작업 목록 조회 실패',
delete: '작업 목록 삭제 실패',
} as const;

export function createTaskList(groupId: number, name: string): Promise<TaskList> {
return requestJson<TaskList>(`/groups/${groupId}/task-lists`, TASK_LIST_ERROR_MESSAGE.create, {
method: 'POST',
body: JSON.stringify({ name }),
});
}

export function getTaskList(groupId: number, taskListId: number, date?: string): Promise<TaskList> {
const query = date ? `?date=${encodeURIComponent(date)}` : '';
return requestJson<TaskList>(
`/groups/${groupId}/task-lists/${taskListId}${query}`,
TASK_LIST_ERROR_MESSAGE.fetch,
);
}

export function deleteTaskList(groupId: number, taskListId: number): Promise<void> {
return requestVoid(
`/groups/${groupId}/task-lists/${taskListId}`,
TASK_LIST_ERROR_MESSAGE.delete,
{ method: 'DELETE' },
);
}
60 changes: 60 additions & 0 deletions src/app/[teamid]/_domain/apis/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export type MemberRole = 'ADMIN' | 'MEMBER';

export type FrequencyType = 'ONCE' | 'DAILY' | 'WEEKLY' | 'MONTHLY';

export interface GroupMember {
userId: number;
groupId: number;
userName: string;
userEmail: string;
userImage: string;
role: MemberRole;
}

export interface TaskUser {
id: number;
nickname: string;
image: string | null;
}

export interface Task {
id: number;
name: string;
description: string | null;
date: string;
doneAt: string | null;
updatedAt: string;
frequency: FrequencyType;
recurringId: number;
deletedAt: string | null;
commentCount: number;
displayIndex: number;
writer: TaskUser | null;
doneBy: { user: TaskUser | null } | null;
}

export interface TaskList {
id: number;
name: string;
groupId: number;
displayIndex: number;
createdAt: string;
updatedAt: string;
tasks: Task[];
}

export interface Group {
id: number;
teamId: string;
name: string;
image: string;
createdAt: string;
updatedAt: string;
members: GroupMember[];
taskLists: Omit<TaskList, 'tasks'>[];
}

export interface UpdateGroupBody {
name?: string;
image?: string | null;
}
85 changes: 85 additions & 0 deletions src/app/[teamid]/_domain/components/Kanban/KanbanBoard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
.wrapper {
display: flex;
flex-direction: column;
gap: 20px;
}

.boardHeader {
display: flex;
align-items: center;
gap: 8px;
}

.boardTitle {
font-size: 16px;
font-weight: 600;
color: var(--color-text-primary);
margin: 0;
}

.boardCount {
color: var(--color-text-default);
font-weight: 400;
}

.board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
align-items: start;
}

/* 태블릿: 컬럼을 한 줄씩 세로로 쌓기 */
@media (min-width: 768px) and (max-width: 1279px) {
.board {
display: flex;
flex-direction: column;
gap: 16px;
}
}

/* 모바일 */
@media (max-width: 767px) {
.board {
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
max-width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
}

.addListButton {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 1140px;
padding: 14px 24px;
background: var(--color-background-primary);
border: 1px dashed var(--color-background-tertiary);
border-radius: 12px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: var(--color-text-default);
font-family: inherit;
transition:
background 0.15s,
color 0.15s;
}

.addListButton:hover {
background: var(--color-brand-secondary);
color: var(--color-brand-primary);
border-color: var(--color-brand-primary);
}

.dragOverlay {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
border-radius: 12px;
opacity: 0.92;
cursor: grabbing;
}
Loading
Loading