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
4 changes: 4 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
## Frontend Structure

- Route pages stay thin under `src/pages/*` and delegate UI to feature components.
- App routes are loaded lazily from `src/App.tsx` so large team-space and presentation screens do not inflate the initial route bundle.
- Matching UI lives in `src/features/match/*`.
- Team workspace UI lives in `src/features/team/*`.
- Team workspace orchestration lives in `src/features/team/components/team-space-view.tsx`; signed-in server-backed panels are split into focused `real-team-*` components, and the signed-out mock preview lives in `src/features/team/components/mock-team-space-view.tsx`.
- Shared team workspace chrome, status helpers, member formatting, tabs, and GitHub permission notices live in focused files under `src/features/team/components/*`.
- Shared domain types live in `src/lib/types/*`.
- API request functions stay in `src/lib/api/*`.
- MSW request handlers stay in `src/lib/api/mocks/handlers.ts`, with reusable team-space fixture builders in `src/lib/api/mocks/team-space-fixtures.ts`.
- `/team` uses project group, checklist, admin permission, and GitHub App installation request functions when a user is signed in.
- In mock API mode, signed-in `/team` calls the same request functions through MSW; signed-out `/team` keeps the richer local demo workspace as a preview.

Expand Down
5 changes: 5 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@
## 2026-06-02
- UX writing approach: use concise Korean 해요체 for customer-facing guidance, lead with the user's next action, avoid internal planning terms in UI copy, and keep feature-critical product terms when they communicate real behavior.
- Repository UX writing guide: keep AI-facing workflow instructions in `.agents/skills/ux-writing/SKILL.md` and human-readable product copy standards in `docs/UX_WRITING.md`.

## 2026-06-06
- Route performance approach: lazy-load route pages from `src/App.tsx`, especially `/team` and `/deck/*`, to keep the initial app bundle smaller without changing route ownership.
- Team workspace structure: keep `/team` orchestration in `team-space-view.tsx`, split signed-in server-backed panels into focused `real-team-*` components, and keep the signed-out mock preview separate while sharing tabs, status helpers, and GitHub permission notices.
- MSW maintainability: keep route handlers in `src/lib/api/mocks/handlers.ts`, but move reusable team-space fixture builders into `src/lib/api/mocks/team-space-fixtures.ts` so handler code focuses on request behavior.
318 changes: 226 additions & 92 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { lazy, Suspense, useEffect } from "react";
import {
BrowserRouter,
Navigate,
Expand All @@ -7,106 +7,240 @@ import {
useLocation,
} from "react-router-dom";

import { EmailVerificationPage } from "@/pages/email-verification-page";
import { GithubOAuthCallbackPage } from "@/pages/github-oauth-callback-page";
import { LandingPage } from "@/pages/landing-page";
import { LoginPage } from "@/pages/login-page";
import { MatchPage } from "@/pages/match-page";
import { PasswordResetPage } from "@/pages/password-reset-page";
import { TeamPoPresentationEleventhPage } from "@/pages/team-po-presentation-eleventh-page";
import { TeamPoPresentationFourteenthPage } from "@/pages/team-po-presentation-fourteenth-page";
import { TeamPoPresentationThirteenthPage } from "@/pages/team-po-presentation-thirteenth-page";
import { TeamPoPresentationTwelfthPage } from "@/pages/team-po-presentation-twelfth-page";
import { ProfilePage } from "@/pages/profile-page";
import { TeamPoPresentationEighthPage } from "@/pages/team-po-presentation-eighth-page";
import { TeamPoPresentationFourthPage } from "@/pages/team-po-presentation-fourth-page";
import { TeamPoPresentationNinthPage } from "@/pages/team-po-presentation-ninth-page";
import { SignupPage } from "@/pages/signup-page";
import { TeamPoPresentationPage } from "@/pages/team-po-presentation-page";
import { TeamPoPresentationSecondPage } from "@/pages/team-po-presentation-second-page";
import { TeamPoPresentationThirdPage } from "@/pages/team-po-presentation-third-page";
import { TeamPoPresentationFifthPage } from "@/pages/team-po-presentation-fifth-page";
import { TeamPoPresentationSeventhPage } from "@/pages/team-po-presentation-seventh-page";
import { TeamPoPresentationSixthPage } from "@/pages/team-po-presentation-sixth-page";
import { TeamSpacePage } from "@/pages/team-space-page";
import { TeamPoPresentationTenthPage } from "@/pages/team-po-presentation-tenth-page";
const LandingPage = lazy(() =>
import("@/pages/landing-page").then(({ LandingPage }) => ({
default: LandingPage,
})),
);
const LoginPage = lazy(() =>
import("@/pages/login-page").then(({ LoginPage }) => ({
default: LoginPage,
})),
);
const SignupPage = lazy(() =>
import("@/pages/signup-page").then(({ SignupPage }) => ({
default: SignupPage,
})),
);
const PasswordResetPage = lazy(() =>
import("@/pages/password-reset-page").then(({ PasswordResetPage }) => ({
default: PasswordResetPage,
})),
);
const EmailVerificationPage = lazy(() =>
import("@/pages/email-verification-page").then(
({ EmailVerificationPage }) => ({
default: EmailVerificationPage,
}),
),
);
const GithubOAuthCallbackPage = lazy(() =>
import("@/pages/github-oauth-callback-page").then(
({ GithubOAuthCallbackPage }) => ({
default: GithubOAuthCallbackPage,
}),
),
);
const ProfilePage = lazy(() =>
import("@/pages/profile-page").then(({ ProfilePage }) => ({
default: ProfilePage,
})),
);
const MatchPage = lazy(() =>
import("@/pages/match-page").then(({ MatchPage }) => ({
default: MatchPage,
})),
);
const TeamSpacePage = lazy(() =>
import("@/pages/team-space-page").then(({ TeamSpacePage }) => ({
default: TeamSpacePage,
})),
);
const TeamPoPresentationPage = lazy(() =>
import("@/pages/team-po-presentation-page").then(
({ TeamPoPresentationPage }) => ({
default: TeamPoPresentationPage,
}),
),
);
const TeamPoPresentationSecondPage = lazy(() =>
import("@/pages/team-po-presentation-second-page").then(
({ TeamPoPresentationSecondPage }) => ({
default: TeamPoPresentationSecondPage,
}),
),
);
const TeamPoPresentationThirdPage = lazy(() =>
import("@/pages/team-po-presentation-third-page").then(
({ TeamPoPresentationThirdPage }) => ({
default: TeamPoPresentationThirdPage,
}),
),
);
const TeamPoPresentationFourthPage = lazy(() =>
import("@/pages/team-po-presentation-fourth-page").then(
({ TeamPoPresentationFourthPage }) => ({
default: TeamPoPresentationFourthPage,
}),
),
);
const TeamPoPresentationFifthPage = lazy(() =>
import("@/pages/team-po-presentation-fifth-page").then(
({ TeamPoPresentationFifthPage }) => ({
default: TeamPoPresentationFifthPage,
}),
),
);
const TeamPoPresentationSixthPage = lazy(() =>
import("@/pages/team-po-presentation-sixth-page").then(
({ TeamPoPresentationSixthPage }) => ({
default: TeamPoPresentationSixthPage,
}),
),
);
const TeamPoPresentationSeventhPage = lazy(() =>
import("@/pages/team-po-presentation-seventh-page").then(
({ TeamPoPresentationSeventhPage }) => ({
default: TeamPoPresentationSeventhPage,
}),
),
);
const TeamPoPresentationEighthPage = lazy(() =>
import("@/pages/team-po-presentation-eighth-page").then(
({ TeamPoPresentationEighthPage }) => ({
default: TeamPoPresentationEighthPage,
}),
),
);
const TeamPoPresentationNinthPage = lazy(() =>
import("@/pages/team-po-presentation-ninth-page").then(
({ TeamPoPresentationNinthPage }) => ({
default: TeamPoPresentationNinthPage,
}),
),
);
const TeamPoPresentationTenthPage = lazy(() =>
import("@/pages/team-po-presentation-tenth-page").then(
({ TeamPoPresentationTenthPage }) => ({
default: TeamPoPresentationTenthPage,
}),
),
);
const TeamPoPresentationEleventhPage = lazy(() =>
import("@/pages/team-po-presentation-eleventh-page").then(
({ TeamPoPresentationEleventhPage }) => ({
default: TeamPoPresentationEleventhPage,
}),
),
);
const TeamPoPresentationTwelfthPage = lazy(() =>
import("@/pages/team-po-presentation-twelfth-page").then(
({ TeamPoPresentationTwelfthPage }) => ({
default: TeamPoPresentationTwelfthPage,
}),
),
);
const TeamPoPresentationThirteenthPage = lazy(() =>
import("@/pages/team-po-presentation-thirteenth-page").then(
({ TeamPoPresentationThirteenthPage }) => ({
default: TeamPoPresentationThirteenthPage,
}),
),
);
const TeamPoPresentationFourteenthPage = lazy(() =>
import("@/pages/team-po-presentation-fourteenth-page").then(
({ TeamPoPresentationFourteenthPage }) => ({
default: TeamPoPresentationFourteenthPage,
}),
),
);

export function App() {
return (
<BrowserRouter>
<ScrollToTop />
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/deck/team-po" element={<TeamPoPresentationPage />} />
<Route
path="/deck/team-po-2"
element={<TeamPoPresentationSecondPage />}
/>
<Route
path="/deck/team-po-3"
element={<TeamPoPresentationThirdPage />}
/>
<Route
path="/deck/team-po-4"
element={<TeamPoPresentationFourthPage />}
/>
<Route
path="/deck/team-po-5"
element={<TeamPoPresentationFifthPage />}
/>
<Route
path="/deck/team-po-6"
element={<TeamPoPresentationSixthPage />}
/>
<Route
path="/deck/team-po-7"
element={<TeamPoPresentationSeventhPage />}
/>
<Route
path="/deck/team-po-8"
element={<TeamPoPresentationEighthPage />}
/>
<Route
path="/deck/team-po-9"
element={<TeamPoPresentationNinthPage />}
/>
<Route
path="/deck/team-po-10"
element={<TeamPoPresentationTenthPage />}
/>
<Route
path="/deck/team-po-11"
element={<TeamPoPresentationEleventhPage />}
/>
<Route
path="/deck/team-po-12"
element={<TeamPoPresentationTwelfthPage />}
/>
<Route
path="/deck/team-po-13"
element={<TeamPoPresentationThirteenthPage />}
/>
<Route
path="/deck/team-po-14"
element={<TeamPoPresentationFourteenthPage />}
/>
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/password-reset" element={<PasswordResetPage />} />
<Route path="/verify-email" element={<EmailVerificationPage />} />
<Route
path="/oauth/github/callback"
element={<GithubOAuthCallbackPage />}
/>
<Route path="/me" element={<ProfilePage />} />
<Route path="/match" element={<MatchPage />} />
<Route path="/team" element={<TeamSpacePage />} />
<Route path="*" element={<Navigate replace to="/" />} />
</Routes>
<Suspense fallback={<RouteFallback />}>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/deck/team-po" element={<TeamPoPresentationPage />} />
<Route
path="/deck/team-po-2"
element={<TeamPoPresentationSecondPage />}
/>
<Route
path="/deck/team-po-3"
element={<TeamPoPresentationThirdPage />}
/>
<Route
path="/deck/team-po-4"
element={<TeamPoPresentationFourthPage />}
/>
<Route
path="/deck/team-po-5"
element={<TeamPoPresentationFifthPage />}
/>
<Route
path="/deck/team-po-6"
element={<TeamPoPresentationSixthPage />}
/>
<Route
path="/deck/team-po-7"
element={<TeamPoPresentationSeventhPage />}
/>
<Route
path="/deck/team-po-8"
element={<TeamPoPresentationEighthPage />}
/>
<Route
path="/deck/team-po-9"
element={<TeamPoPresentationNinthPage />}
/>
<Route
path="/deck/team-po-10"
element={<TeamPoPresentationTenthPage />}
/>
<Route
path="/deck/team-po-11"
element={<TeamPoPresentationEleventhPage />}
/>
<Route
path="/deck/team-po-12"
element={<TeamPoPresentationTwelfthPage />}
/>
<Route
path="/deck/team-po-13"
element={<TeamPoPresentationThirteenthPage />}
/>
<Route
path="/deck/team-po-14"
element={<TeamPoPresentationFourteenthPage />}
/>
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/password-reset" element={<PasswordResetPage />} />
<Route path="/verify-email" element={<EmailVerificationPage />} />
<Route
path="/oauth/github/callback"
element={<GithubOAuthCallbackPage />}
/>
<Route path="/me" element={<ProfilePage />} />
<Route path="/match" element={<MatchPage />} />
<Route path="/team" element={<TeamSpacePage />} />
<Route path="*" element={<Navigate replace to="/" />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}

function RouteFallback() {
return (
<div className="grid min-h-screen place-items-center bg-background px-4 text-sm font-medium text-muted-foreground">
화면을 불러오고 있어요.
</div>
);
}

function ScrollToTop() {
const { pathname } = useLocation();

Expand Down
4 changes: 4 additions & 0 deletions src/features/auth/hooks/use-auth-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {

import { matchQueryKeys } from "@/features/match/hooks/use-match-queries";
import { projectGroupQueryKeys } from "@/features/project-groups/hooks/use-project-group-queries";
import { projectChecklistQueryKeys } from "@/features/team/hooks/use-project-checklist-queries";
import { teamSpaceQueryKeys } from "@/features/team/hooks/use-team-space-queries";
import {
exchangeGithubOAuthCode,
login,
Expand Down Expand Up @@ -67,6 +69,8 @@ export function clearAuthScopedQueryData(queryClient: QueryClient) {
clearQueryData(queryClient, authQueryKeys.currentUser);
clearQueryData(queryClient, matchQueryKeys.all);
clearQueryData(queryClient, projectGroupQueryKeys.all);
clearQueryData(queryClient, projectChecklistQueryKeys.all);
clearQueryData(queryClient, teamSpaceQueryKeys.all);
}

export function useCurrentUserQuery() {
Expand Down
Loading
Loading