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
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,33 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
```
tag-support/
├── app/ # Next.js App Router
│ ├── api/ # API Routes
│ │ └── push/ # プッシュ通知API
│ ├── runner/ # 逃走者UI
│ ├── chaser/ # 鬼UI
│ └── gamemaster/ # ゲームマスターUI
├── components/ # 共通コンポーネント
│ └── Map.tsx # 地図コンポーネント
│ ├── Map.tsx # 地図コンポーネント
│ ├── GameControls.tsx # ゲーム制御UI
│ ├── MissionManager.tsx# ミッション管理UI
│ └── GameStats.tsx # 統計表示
├── hooks/ # カスタムフック
│ ├── useAuth.ts # 認証フック
│ └── useLocation.ts # 位置情報フック
├── lib/ # ライブラリ設定
│ └── supabase.ts # Supabase初期化
│ ├── useAuth.tsx # 認証フック
│ ├── useLocation.ts # 位置情報フック
│ ├── useGame.tsx # ゲーム状態管理
│ ├── useMissions.tsx # ミッション管理
│ └── useLocationHistory.tsx # 位置履歴
├── lib/ # ユーティリティライブラリ
│ ├── supabase.ts # Supabase初期化
│ ├── geometry.ts # 地理計算関数(距離・速度・方向)
│ ├── user-mapper.ts # DBユーザー型変換
│ └── web-push.ts # Web Push設定
├── constants/ # 定数定義
│ ├── ui-labels.ts # UIラベル定数
│ └── game-config.ts # ゲーム設定定数
├── types/ # TypeScript型定義
│ └── index.ts # メイン型定義
│ ├── index.ts # メイン型定義
│ └── database.ts # Supabase型定義
└── public/ # 静的ファイル
├── manifest.json # PWAマニフェスト
└── sw.js # Service Worker
Expand Down Expand Up @@ -118,6 +133,7 @@ tag-support/
✅ PWA設定・オフライン対応基礎
✅ CI/CD (GitHub Actions)
✅ テスト環境 (Vitest + Playwright)
✅ **コード品質改善(リファクタリング完了)** ⭐️ NEW

## 今後の実装予定

Expand Down
7 changes: 6 additions & 1 deletion app/api/push/send/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase';

Check failure on line 2 in app/api/push/send/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Module '"@/lib/supabase"' declares 'createClient' locally, but it is not exported.
import { sendPushNotificationBatch, type PushNotificationPayload } from '@/lib/web-push';

/**
Expand Down Expand Up @@ -80,7 +80,7 @@
// Filter by roles if specified
let filteredSubscriptions = subscriptions;
if (roles && Array.isArray(roles) && roles.length > 0) {
filteredSubscriptions = subscriptions.filter((sub) => {

Check failure on line 83 in app/api/push/send/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Parameter 'sub' implicitly has an 'any' type.
const userRole = (sub.users as { role?: string })?.role;
return userRole && roles.includes(userRole);
});
Expand All @@ -95,7 +95,7 @@
}

// Convert to push subscription format
const pushSubscriptions = filteredSubscriptions.map((sub) => ({

Check failure on line 98 in app/api/push/send/route.ts

View workflow job for this annotation

GitHub Actions / Lint & Type Check

Parameter 'sub' implicitly has an 'any' type.
endpoint: sub.endpoint,
keys: {
p256dh: sub.p256dh_key,
Expand All @@ -119,10 +119,15 @@
.map((err) => err.subscription.endpoint);

if (invalidEndpoints.length > 0) {
await supabase
const { error: deleteError } = await supabase
.from('push_subscriptions')
.delete()
.in('endpoint', invalidEndpoints);

if (deleteError) {
console.error('Failed to clean up invalid subscriptions:', deleteError);
// 削除失敗はログに記録するが、通知送信の成功には影響しない
}
}
}

Expand Down
29 changes: 3 additions & 26 deletions app/chaser/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { useGame } from '@/hooks/useGame';
import { supabase } from '@/lib/supabase';
import type { User } from '@/types';
import type { RealtimeChannel } from '@supabase/supabase-js';
import { calculateDistance } from '@/lib/geometry';
import { mapDatabaseUsersToAppUsers } from '@/lib/user-mapper';

const Map = dynamic(() => import('@/components/Map'), { ssr: false });

Expand Down Expand Up @@ -36,14 +38,7 @@ export default function ChaserPage() {
}

if (data) {
const mappedRunners: User[] = data.map((u: Record<string, unknown>) => ({
id: u.id as string,
nickname: u.nickname as string,
role: u.role as 'runner' | 'chaser' | 'gamemaster' | 'special',
team: (u.team_id as string | null) || undefined,
status: u.status === 'captured' ? 'captured' : u.status === 'offline' ? 'safe' : 'active',
lastUpdated: new Date(u.updated_at as string),
}));
const mappedRunners = mapDatabaseUsersToAppUsers(data);
setAllRunners(mappedRunners);

if (location) {
Expand Down Expand Up @@ -90,24 +85,6 @@ export default function ChaserPage() {
}
}, [isTracking, startTracking]);

const calculateDistance = (
pos1: { lat: number; lng: number },
pos2: { lat: number; lng: number }
) => {
const R = 6371e3;
const φ1 = (pos1.lat * Math.PI) / 180;
const φ2 = (pos2.lat * Math.PI) / 180;
const Δφ = ((pos2.lat - pos1.lat) * Math.PI) / 180;
const Δλ = ((pos2.lng - pos1.lng) * Math.PI) / 180;

const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

return R * c;
};

const captureRunner = async (runnerId: string) => {
if (!user) return;

Expand Down
20 changes: 5 additions & 15 deletions app/gamemaster/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useGame } from '@/hooks/useGame';
import { supabase } from '@/lib/supabase';
import type { User } from '@/types';
import type { RealtimeChannel } from '@supabase/supabase-js';
import { mapDatabaseUsersToAppUsers } from '@/lib/user-mapper';
import GameControls from '@/components/GameControls';
import MissionManager from '@/components/MissionManager';
import ZoneManager from '@/components/ZoneManager';
Expand Down Expand Up @@ -39,16 +40,7 @@ export default function GamemasterPage() {
}

if (data) {
const mappedPlayers: User[] = data.map((u: Record<string, unknown>) => ({
id: u.id as string,
nickname: u.nickname as string,
role: u.role as 'runner' | 'chaser' | 'gamemaster' | 'special',
team: (u.team_id as string | null) || undefined,
status: u.status === 'captured' ? 'captured' : u.status === 'offline' ? 'safe' : 'active',
lastUpdated: new Date(u.updated_at as string),
captureCount: 0,
}));
setAllPlayers(mappedPlayers);
setAllPlayers(mapDatabaseUsersToAppUsers(data));
}
};

Expand Down Expand Up @@ -132,11 +124,9 @@ export default function GamemasterPage() {
const activePlayers = allPlayers.filter((p) => p.status === 'active');
const capturedPlayers = allPlayers.filter((p) => p.status === 'captured');

const mapCenter: [number, number] = allPlayers.find((p) => p.location)
? [
allPlayers.find((p) => p.location)!.location!.lat,
allPlayers.find((p) => p.location)!.location!.lng,
]
const playerWithLocation = allPlayers.find((p) => p.location);
const mapCenter: [number, number] = playerWithLocation?.location
? [playerWithLocation.location.lat, playerWithLocation.location.lng]
: [35.5522, 139.7797];

return (
Expand Down
21 changes: 2 additions & 19 deletions app/runner/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useGame } from '@/hooks/useGame';
import { supabase } from '@/lib/supabase';
import type { User } from '@/types';
import MissionManager from '@/components/MissionManager';
import { mapDatabaseUsersToAppUsers } from '@/lib/user-mapper';

const Map = dynamic(() => import('@/components/Map'), { ssr: false });

Expand Down Expand Up @@ -39,25 +40,7 @@ export default function RunnerPage() {
}

if (data) {
const mappedUsers: User[] = data.map(
(u: {
id: string;
nickname: string;
role: string;
team_id: string | null;
status: string;
updated_at: string;
}) => ({
id: u.id,
nickname: u.nickname,
role: u.role as 'runner' | 'chaser' | 'gamemaster',
team: u.team_id || undefined,
status:
u.status === 'captured' ? 'captured' : u.status === 'offline' ? 'safe' : 'active',
lastUpdated: new Date(u.updated_at),
})
);
setTeammates(mappedUsers);
setTeammates(mapDatabaseUsersToAppUsers(data));
}
};

Expand Down
19 changes: 1 addition & 18 deletions components/GameStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useState, useEffect } from 'react';
import type { PlayerStats } from '@/hooks/useLocationHistory';
import { supabase } from '@/lib/supabase';
import { calculateDistance } from '@/lib/geometry';

interface PlayerStatsData {
userId: string;
Expand Down Expand Up @@ -159,24 +160,6 @@ export default function GameStats({ gameId, isGameMaster = false }: GameStatsPro
fetchPlayerStats();
}, [gameId]);

const calculateDistance = (
pos1: { lat: number; lng: number },
pos2: { lat: number; lng: number }
): number => {
const R = 6371e3;
const φ1 = (pos1.lat * Math.PI) / 180;
const φ2 = (pos2.lat * Math.PI) / 180;
const Δφ = ((pos2.lat - pos1.lat) * Math.PI) / 180;
const Δλ = ((pos2.lng - pos1.lng) * Math.PI) / 180;

const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

return R * c;
};

const formatDistance = (meters: number): string => {
if (meters < 1000) {
return `${Math.round(meters)}m`;
Expand Down
5 changes: 3 additions & 2 deletions components/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { useCallback } from 'react';
import L from 'leaflet';
import { MapContainer, TileLayer, Marker, Popup, Circle } from 'react-leaflet';
import type { User, Zone } from '@/types';
Expand Down Expand Up @@ -27,7 +28,7 @@ export default function Map({
visibleUsers = [],
zones = [],
}: MapProps) {
const getMarkerIcon = (role: string) => {
const getMarkerIcon = useCallback((role: string) => {
const color = role === 'chaser' ? 'red' : role === 'runner' ? 'blue' : 'green';
return L.divIcon({
className: 'custom-marker',
Expand All @@ -42,7 +43,7 @@ export default function Map({
iconSize: [24, 24],
iconAnchor: [12, 12],
});
};
}, []);

return (
<MapContainer
Expand Down
38 changes: 38 additions & 0 deletions constants/game-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* ゲーム設定に関する定数
*/

/**
* 位置更新の間隔(ミリ秒)
*/
export const LOCATION_UPDATE_INTERVAL = 30000; // 30秒

/**
* デフォルトのゲーム時間(秒)
*/
export const DEFAULT_GAME_DURATION = 3600; // 60分

/**
* デフォルトの鬼レーダー範囲(メートル)
*/
export const DEFAULT_CHASER_RADAR_RANGE = 200;

/**
* デフォルトの捕獲範囲(メートル)
*/
export const DEFAULT_CAPTURE_RANGE = 50;

/**
* デフォルトのミッション半径(メートル)
*/
export const DEFAULT_MISSION_RADIUS = 100;

/**
* 地図のデフォルト中心座標(東京周辺)
*/
export const DEFAULT_MAP_CENTER: [number, number] = [35.5522, 139.7797];

/**
* 地図のデフォルトズームレベル
*/
export const DEFAULT_MAP_ZOOM = 13;
Loading
Loading