Skip to content

Commit fd10ff1

Browse files
Merge pull request #68 from kc3hack/feature/issue-67-rank-integration
feat: #67 ランク判定APIとダッシュボードの統合(繋ぎこみ)
2 parents 36dcd08 + 5d375d2 commit fd10ff1

File tree

7 files changed

+147
-0
lines changed

7 files changed

+147
-0
lines changed

frontend/src/features/dashboard/api/mock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export async function fetchUserDashboard(userId: string): Promise<UserStatus> {
8484
userId,
8585
displayName: 'Sample User',
8686
avatar: '👨‍💻',
87+
githubUsername: 'sampleuser',
8788
totalExp: 12300,
8889
currentRank: mockRank,
8990
badges: mockBadges,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
2+
3+
export interface RankAnalysisRequest {
4+
github_username: string;
5+
portfolio_text?: string;
6+
qiita_id?: string;
7+
other_info?: string;
8+
}
9+
10+
export interface RankAnalysisResponse {
11+
percentile: number;
12+
rank: number;
13+
rank_name: string;
14+
reasoning: string;
15+
}
16+
17+
export async function analyzeRank(
18+
request: RankAnalysisRequest
19+
): Promise<RankAnalysisResponse> {
20+
const response = await fetch(`${API_BASE_URL}/api/v1/analyze/rank`, {
21+
method: "POST",
22+
headers: { "Content-Type": "application/json" },
23+
body: JSON.stringify(request),
24+
});
25+
26+
if (!response.ok) {
27+
throw new Error(`Rank analysis failed: ${response.statusText}`);
28+
}
29+
30+
return response.json();
31+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use client';
2+
3+
import { RANK_COLORS, RankLevel } from "../constants/rankColors";
4+
5+
interface RankBadgeProps {
6+
rank: number;
7+
label?: string;
8+
size?: "sm" | "md" | "lg";
9+
showStar?: boolean;
10+
}
11+
12+
export function RankBadge({
13+
rank,
14+
label,
15+
size = "md",
16+
showStar = true,
17+
}: RankBadgeProps) {
18+
const colors =
19+
RANK_COLORS[rank as RankLevel] || RANK_COLORS[0];
20+
21+
const sizeClasses = {
22+
sm: "px-2 py-1 text-xs",
23+
md: "px-3 py-1.5 text-sm",
24+
lg: "px-4 py-2 text-base",
25+
};
26+
27+
return (
28+
<span
29+
className={`inline-flex items-center gap-2 rounded-full font-semibold ${sizeClasses[size]}`}
30+
style={{
31+
backgroundColor: colors.bg,
32+
color: colors.text,
33+
}}
34+
>
35+
{showStar && <span className="text-xl"></span>}
36+
<span>Rank {rank}</span>
37+
{label && <span>- {label}</span>}
38+
</span>
39+
);
40+
}

frontend/src/features/dashboard/components/StatusCard.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import Image from 'next/image';
99
import type { UserStatus } from '../types';
10+
import { useRankAnalysis } from '../hooks/useRankAnalysis';
11+
import { RankBadge } from './RankBadge';
1012

1113
interface StatusCardProps {
1214
userStatus: UserStatus;
@@ -21,6 +23,7 @@ const getRankImage = (rankTitle: string) => {
2123
};
2224

2325
export function StatusCard({ userStatus }: StatusCardProps) {
26+
const { rank, loading } = useRankAnalysis(userStatus.githubUsername);
2427
const progressPercentage = userStatus.currentRank.progress;
2528

2629
return (
@@ -46,6 +49,20 @@ export function StatusCard({ userStatus }: StatusCardProps) {
4649
</div>
4750
</div>
4851

52+
{/* ランクバッジ(新規追加) */}
53+
{loading ? (
54+
<div className="mb-4 animate-pulse">
55+
<div className="h-8 w-48 rounded-full bg-gray-200"></div>
56+
</div>
57+
) : rank ? (
58+
<div className="mb-4 space-y-2">
59+
<RankBadge rank={rank.rank} label={rank.rank_name} size="lg" />
60+
<p className="text-sm text-gray-600 dark:text-gray-400">
61+
{rank.reasoning}
62+
</p>
63+
</div>
64+
) : null}
65+
4966
{/* ランク情報 */}
5067
<div className="mb-6 space-y-2">
5168
<div className="flex items-center justify-between">
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const RANK_COLORS = {
2+
0: { bg: "#e5e7eb", text: "#6b7280", label: "初心者" },
3+
1: { bg: "#dbeafe", text: "#3b82f6", label: "見習い" },
4+
2: { bg: "#d1fae5", text: "#10b981", label: "駆け出し" },
5+
3: { bg: "#fef3c7", text: "#f59e0b", label: "中級者" },
6+
4: { bg: "#fed7aa", text: "#ea580c", label: "実践者" },
7+
5: { bg: "#fecaca", text: "#dc2626", label: "熟練者" },
8+
6: { bg: "#e9d5ff", text: "#9333ea", label: "エキスパート" },
9+
7: { bg: "#fbcfe8", text: "#db2777", label: "マスター" },
10+
8: { bg: "#c7d2fe", text: "#4f46e5", label: "レジェンド" },
11+
9: { bg: "#fde68a", text: "#ca8a04", label: "グランドマスター" },
12+
} as const;
13+
14+
export type RankLevel = keyof typeof RANK_COLORS;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use client';
2+
3+
import { useState, useEffect } from "react";
4+
import { analyzeRank, RankAnalysisResponse } from "../api/rankApi";
5+
6+
export function useRankAnalysis(githubUsername?: string) {
7+
const [rank, setRank] = useState<RankAnalysisResponse | null>(null);
8+
const [loading, setLoading] = useState(false);
9+
const [error, setError] = useState<Error | null>(null);
10+
11+
useEffect(() => {
12+
if (!githubUsername) return;
13+
14+
const fetchRank = async () => {
15+
setLoading(true);
16+
try {
17+
const mockData = {
18+
github_username: githubUsername,
19+
portfolio_text: "個人サイト: https://example.com",
20+
qiita_id: "",
21+
other_info: "エンジニアコミュニティ活動",
22+
};
23+
24+
const result = await analyzeRank(mockData);
25+
setRank(result);
26+
} catch (err) {
27+
setError(err as Error);
28+
setRank({
29+
percentile: 0,
30+
rank: 0,
31+
rank_name: "種子",
32+
reasoning: "ランク判定に失敗しました",
33+
});
34+
} finally {
35+
setLoading(false);
36+
}
37+
};
38+
39+
fetchRank();
40+
}, [githubUsername]);
41+
42+
return { rank, loading, error };
43+
}

frontend/src/features/dashboard/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface UserStatus {
3232
userId: string;
3333
displayName: string;
3434
avatar?: string;
35+
githubUsername?: string;
3536
totalExp: number;
3637
currentRank: Rank;
3738
badges: Badge[];

0 commit comments

Comments
 (0)