Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bb068f7
fix: AuthCallbackPage GlobalLayout import 삭제
Monixc Aug 7, 2025
576c684
refactor: 타입 정의 파일 경로 수정 및 새로운 타입 추가
Monixc Aug 7, 2025
4deb912
style: OnboardingStep1 및 OnboardingStep2 컴포넌트의 최소 높이 수정
Monixc Aug 7, 2025
da7b3b0
refactor: api 구조 변경에 따라 라우팅 조건 변경
Monixc Aug 7, 2025
dd048c7
chore: @use-funnel 패키지 추가
Monixc Aug 7, 2025
755b316
refactor: OnboardingPage.tsx->Onboarding/index.tsx 변경경
Monixc Aug 7, 2025
7d024e9
refactor: OnboardingPage import 경로 수정
Monixc Aug 7, 2025
d52cc66
feat: @use-funnel로 온보딩 패이지 퍼널 패턴 적용
Monixc Aug 7, 2025
16fc36f
Merge branch 'feature/api-onboarding-home' of https://github.com/prgr…
Monixc Aug 7, 2025
706fc17
file: 종속 컴포넌트 폴더명 _components로 변경
Monixc Aug 7, 2025
eca0fa4
style: 헤더 고정
Monixc Aug 7, 2025
4ccde75
feat: 위치 선택기 및 위치 쿼리 훅 추가
Monixc Aug 7, 2025
27d56bb
feat: msw 비활성화화
Monixc Aug 7, 2025
9235058
fix: main, app에서 QueryClientProvider 중복 사용 수정
Monixc Aug 8, 2025
9da0c4f
type: auth, tags api 관련 타입
Monixc Aug 8, 2025
0c11df7
feat: API 모듈 추가 - 인증, 개발자, 게시물, 태그 관련 기능 분리리
Monixc Aug 8, 2025
dbc9955
refactor: API 호출 경로 변경
Monixc Aug 8, 2025
7db063b
feat: 인증 및 태그 관련 쿼리 훅 추가
Monixc Aug 8, 2025
41e0f73
feat: 포지션 및 스택 선택기에서 API 호출을 쿼리 훅으로 변경
Monixc Aug 8, 2025
681416f
refactor: StackSelector에서 사용하지 않는 Stack 타입 임포트 제거
Monixc Aug 8, 2025
cc6666f
feat: 관심사 선택 기능을 API 호출로 변경 및 로딩 상태 처리 추가
Monixc Aug 8, 2025
0a83793
feat: GitHub 콜백에서 로그인/회원가입 분기 조건 변경, 세션 이이디를 파람으로 넘김
Monixc Aug 8, 2025
bdc72f7
feat: 온보딩 4단계에서 데이터 전달 및 회원가입 처리 로직 추가
Monixc Aug 8, 2025
e07aea2
feat: /board로 시작하는 모든 경로에서 home 아이콘 active
Monixc Aug 8, 2025
d387eec
refactor: Post 타입 구조 변경 및 파일명 변경(board.types.ts->post.types.ts)
Monixc Aug 8, 2025
8390ba5
feat: 로그인 성공 시 토큰 및 유저 정보 저장
Monixc Aug 8, 2025
9a3b9d1
refactor: GitHub 콜백에서 accessToken 및 sessionId 저장 로직 제거(result로 일괄 처리)
Monixc Aug 8, 2025
7e839f8
feat: API 기본 URL 설정 및 인증 헤더 생성 함수 추가
Monixc Aug 8, 2025
9fe9646
feat: 게시판 및 게시물 관련 쿼리 훅 추가
Monixc Aug 8, 2025
c28a3a5
refactor: ListItem 컴포넌트에서 Post 타입 변경 및 날짜 형식화 함수 추가
Monixc Aug 8, 2025
e875874
feat: 게시물 추가 모달에서 게시판 선택 및 제출 로직 개선
Monixc Aug 8, 2025
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
75 changes: 73 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@tanstack/react-query": "^5.83.0",
"@use-funnel/react-router-dom": "^0.0.14",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"lucide-react": "^0.525.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hot-toast": "^2.5.2",
"react-router-dom": "^7.7.0",
"tailwind-merge": "^3.3.1"
},
Expand Down
11 changes: 3 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { Outlet } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import GlobalLayout from "./components/layout/GlobalLayout";

const queryClient = new QueryClient();

function App() {
return (
<QueryClientProvider client={queryClient}>
<GlobalLayout>
<Outlet />
</GlobalLayout>
</QueryClientProvider>
<GlobalLayout>
<Outlet />
</GlobalLayout>
);
}

Expand Down
Empty file removed src/components/common/.gitkeep
Empty file.
126 changes: 126 additions & 0 deletions src/components/common/LocationSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useEffect } from "react";
import { Button } from "@/components/ui/button";
import { MapPin, Loader2 } from "lucide-react";
import { useLocationQuery } from "@/hooks/queries/useLocationQuery";

interface LocationSelectorProps {
onLocationChange: (location: { lng: number; lat: number }) => void;
initialLocation?: { lng: number; lat: number } | null;
}

export const LocationSelector = ({
onLocationChange,
initialLocation,
}: LocationSelectorProps) => {
const { location, isLoading, isError, refetchLocation } = useLocationQuery();

useEffect(() => {
if (location && !initialLocation) {
onLocationChange(location);
}
}, [location, onLocationChange, initialLocation]);

const handleGetLocation = async () => {
try {
await refetchLocation();
} catch (error) {
console.error("위치 가져오기 실패:", error);
}
};

const getStatus = () => {
if (isLoading) return "loading";
if (isError) return "error";
if (location) return "success";
return "initial";
};

const status = getStatus();

return (
<div className="space-y-3">
{status === "initial" && (
<div className="bg-brand-surface rounded-lg p-4 border-none">
<div className="flex items-center gap-3 mb-3">
<MapPin className="text-brand-primary" />
<span className="text-sm font-medium">위치 정보 수집</span>
</div>
<p className="text-sm text-brand-text mb-4">
주변 개발자와의 연결을 위해 위치 정보가 필요합니다. 브라우저에서
위치 정보 접근을 허용해주세요.
</p>
<Button
onClick={handleGetLocation}
className="w-full bg-brand-primary hover:bg-brand-primary/90 text-black">
위치 정보 가져오기
</Button>
</div>
)}

{status === "loading" && (
<div className="bg-brand-surface rounded-lg p-4 border-none">
<div className="flex items-center gap-3 mb-3">
<Loader2 className="text-brand-primary animate-spin" />
<span className="text-sm font-medium">위치 정보 수집 중...</span>
</div>
<p className="text-sm text-brand-text">
GPS 신호를 받고 있습니다. 잠시만 기다려주세요.
</p>
</div>
)}

{status === "success" && location && (
<div className="bg-brand-surface rounded-lg p-4 border-none">
<div className="flex items-center gap-3 mb-3">
<MapPin className="text-brand-primary" />
<span className="text-sm font-medium text-brand-primary">
위치 정보 수집 완료
</span>
</div>
{location.address ? (
<div className="space-y-2">
<p className="text-sm text-white font-medium">
현재 위치: {location.address}
</p>
<p className="text-xs text-brand-text/70">
* 약간의 오차가 있을 수 있습니다.
</p>
</div>
) : (
<p className="text-sm text-brand-text">
위도: {location.lat.toFixed(6)}, 경도: {location.lng.toFixed(6)}
</p>
)}
<Button
onClick={handleGetLocation}
variant="outline"
size="sm"
className="w-full mt-2 bg-transparent border-brand-primary text-brand-primary hover:bg-brand-primary hover:text-black">
위치 다시 가져오기
</Button>
</div>
)}

{status === "error" && (
<div className="bg-brand-surface rounded-lg p-4 border border-red-500/20">
<div className="flex items-center gap-3 mb-3">
<MapPin className="text-red-500" />
<span className="text-sm font-medium text-red-500">
위치 정보 오류
</span>
</div>
<p className="text-sm text-brand-text mb-4">
위치 정보를 가져오는 중 오류가 발생했습니다.
</p>
<Button
onClick={handleGetLocation}
variant="outline"
size="sm"
className="bg-transparent border-red-500 text-red-500 hover:bg-red-500 hover:text-white">
다시 시도
</Button>
</div>
)}
</div>
);
};
59 changes: 27 additions & 32 deletions src/components/common/PositionSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import {
Accordion,
Expand All @@ -8,8 +7,8 @@ import {
} from "@/components/ui/accordion";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { fetchPositions } from "@/lib/api";
import type { PositionCategory } from "@/types/filter";
import { usePositionsInterests } from "@/hooks/queries/useTags";
import type { Position } from "@/types/tags.types";

interface PositionSelectorProps {
selectedPosition: number | null;
Expand All @@ -22,60 +21,56 @@ export const PositionSelector = ({
onPositionChange,
label = "포지션",
}: PositionSelectorProps) => {
const [positions, setPositions] = useState<PositionCategory[]>([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const loadPositions = async () => {
try {
const fetchedPositions = await fetchPositions();
setPositions(fetchedPositions);
} catch (error) {
console.error("포지션 로딩 실패:", error);
} finally {
setIsLoading(false);
}
};

loadPositions();
}, []);
const { data: positionsInterestsData, isLoading } = usePositionsInterests();

if (isLoading) {
return <div className="text-center">로딩 중...</div>;
}

if (!positionsInterestsData?.positions) {
return <div className="text-center">포지션을 불러올 수 없습니다.</div>;
}

const positions = positionsInterestsData.positions;

// positions를 field별로 그룹화
const groupedPositions = positions.reduce((acc, position) => {
if (!acc[position.field]) {
acc[position.field] = [];
}
acc[position.field].push(position);
return acc;
}, {} as Record<string, Position[]>);

return (
<div className="space-y-3">
<Label>{label}</Label>
<Accordion
type="single"
collapsible
className="w-full border rounded-md border-brand-surface">
{positions.map((cat) => (
{Object.entries(groupedPositions).map(([field, fieldPositions]) => (
<AccordionItem
key={cat.category}
value={cat.category}
key={field}
value={field}
className="px-4 border-b-brand-surface last:border-b-0">
<AccordionTrigger className="hover:no-underline">
{cat.category}
{field}
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col gap-2 pt-2">
{cat.positions.map((pos) => (
{fieldPositions.map((position) => (
<Button
key={pos.id}
key={position.id}
variant="outline"
onClick={() => onPositionChange(pos.id)}
onClick={() => onPositionChange(position.id)}
className={cn(
"h-auto justify-start text-left whitespace-normal border-transparent bg-brand-surface",
selectedPosition === pos.id &&
selectedPosition === position.id &&
"border-brand-primary text-brand-primary border-1"
)}>
<div className="flex flex-col">
<span className="font-bold">{pos.name}</span>
<span className="text-xs text-brand-text">
{pos.description}
</span>
<span className="font-bold">{position.role}</span>
</div>
</Button>
))}
Expand Down
Loading