Flesh out payment stuff
This commit is contained in:
parent
b0dcdd6438
commit
acfed861ab
27 changed files with 938 additions and 173 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from 'react-oidc-context';
|
||||
import { useAuthenticatedFetch } from './useAuthenticatedFetch';
|
||||
import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question, GenerateQuizOptions, ProcessedDocument, GameConfig, DEFAULT_GAME_CONFIG, PointsBreakdown } from '../types';
|
||||
import { generateQuiz } from '../services/geminiService';
|
||||
import { QUESTION_TIME, QUESTION_TIME_MS, PLAYER_COLORS, calculatePointsWithBreakdown, getPlayerRank } from '../constants';
|
||||
|
|
@ -96,10 +97,11 @@ const clearDraftQuiz = () => {
|
|||
sessionStorage.removeItem(DRAFT_QUIZ_KEY);
|
||||
};
|
||||
|
||||
export const useGame = () => {
|
||||
export const useGame = (defaultGameConfig?: GameConfig) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const auth = useAuth();
|
||||
const { authFetch } = useAuthenticatedFetch();
|
||||
|
||||
const [role, setRole] = useState<GameRole>('HOST');
|
||||
const [gameState, setGameState] = useState<GameState>('LANDING');
|
||||
|
|
@ -120,7 +122,9 @@ export const useGame = () => {
|
|||
const [currentPlayerName, setCurrentPlayerName] = useState<string | null>(null);
|
||||
const [pendingQuizToSave, setPendingQuizToSave] = useState<{ quiz: Quiz; topic: string } | null>(null);
|
||||
const [sourceQuizId, setSourceQuizId] = useState<string | null>(null);
|
||||
const [gameConfig, setGameConfig] = useState<GameConfig>(DEFAULT_GAME_CONFIG);
|
||||
const [gameConfig, setGameConfig] = useState<GameConfig>(defaultGameConfig || DEFAULT_GAME_CONFIG);
|
||||
const defaultConfigRef = useRef<GameConfig>(defaultGameConfig || DEFAULT_GAME_CONFIG);
|
||||
const [subscriptionAccessType, setSubscriptionAccessType] = useState<'group' | 'subscription' | 'none'>('none');
|
||||
const [firstCorrectPlayerId, setFirstCorrectPlayerId] = useState<string | null>(null);
|
||||
const [hostSecret, setHostSecret] = useState<string | null>(null);
|
||||
const [isReconnecting, setIsReconnecting] = useState(false);
|
||||
|
|
@ -149,6 +153,10 @@ export const useGame = () => {
|
|||
useEffect(() => { currentQuestionIndexRef.current = currentQuestionIndex; }, [currentQuestionIndex]);
|
||||
useEffect(() => { quizRef.current = quiz; }, [quiz]);
|
||||
useEffect(() => { gameConfigRef.current = gameConfig; }, [gameConfig]);
|
||||
useEffect(() => {
|
||||
if (!defaultGameConfig) return;
|
||||
defaultConfigRef.current = defaultGameConfig;
|
||||
}, [defaultGameConfig]);
|
||||
useEffect(() => { gamePinRef.current = gamePin; }, [gamePin]);
|
||||
useEffect(() => { hostSecretRef.current = hostSecret; }, [hostSecret]);
|
||||
useEffect(() => { gameStateRef.current = gameState; }, [gameState]);
|
||||
|
|
@ -156,6 +164,38 @@ export const useGame = () => {
|
|||
useEffect(() => { currentCorrectShapeRef.current = currentCorrectShape; }, [currentCorrectShape]);
|
||||
useEffect(() => { presenterIdRef.current = presenterId; }, [presenterId]);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
if (auth.isLoading || !auth.isAuthenticated) {
|
||||
setSubscriptionAccessType('none');
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}
|
||||
|
||||
const fetchSubscriptionAccess = async () => {
|
||||
try {
|
||||
const response = await authFetch('/api/payments/status');
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
if (!isMounted) return;
|
||||
setSubscriptionAccessType(
|
||||
data.accessType === 'group' ? 'group' : (data.accessType === 'subscription' ? 'subscription' : 'none')
|
||||
);
|
||||
} catch {
|
||||
if (!isMounted) return;
|
||||
setSubscriptionAccessType('none');
|
||||
}
|
||||
};
|
||||
|
||||
fetchSubscriptionAccess();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [auth.isLoading, auth.isAuthenticated, authFetch]);
|
||||
|
||||
const isInitializingFromUrl = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -723,10 +763,12 @@ export const useGame = () => {
|
|||
};
|
||||
|
||||
const generatedQuiz = await generateQuiz(generateOptions);
|
||||
const withDefaultConfig = { ...generatedQuiz, config: defaultConfigRef.current };
|
||||
const saveLabel = options.topic || options.files?.map(f => f.name).join(', ') || '';
|
||||
setPendingQuizToSave({ quiz: generatedQuiz, topic: saveLabel });
|
||||
setQuiz(generatedQuiz);
|
||||
storeDraftQuiz({ quiz: generatedQuiz, topic: saveLabel });
|
||||
setPendingQuizToSave({ quiz: withDefaultConfig, topic: saveLabel });
|
||||
setQuiz(withDefaultConfig);
|
||||
setGameConfig(defaultConfigRef.current);
|
||||
storeDraftQuiz({ quiz: withDefaultConfig, topic: saveLabel });
|
||||
setGameState('EDITING');
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : "Failed to generate quiz.";
|
||||
|
|
@ -741,6 +783,7 @@ export const useGame = () => {
|
|||
|
||||
const startManualCreation = () => {
|
||||
setRole('HOST');
|
||||
setGameConfig(defaultConfigRef.current);
|
||||
setGameState('CREATING');
|
||||
};
|
||||
|
||||
|
|
@ -758,6 +801,7 @@ export const useGame = () => {
|
|||
const loadSavedQuiz = (savedQuiz: Quiz, quizId?: string) => {
|
||||
setRole('HOST');
|
||||
setQuiz(savedQuiz);
|
||||
setGameConfig(savedQuiz.config || defaultConfigRef.current);
|
||||
setSourceQuizId(quizId || null);
|
||||
storeDraftQuiz({ quiz: savedQuiz, sourceQuizId: quizId });
|
||||
setGameState('EDITING');
|
||||
|
|
@ -818,6 +862,20 @@ export const useGame = () => {
|
|||
if (!reconnectedPlayer && gameConfigRef.current.randomNamesEnabled) {
|
||||
assignedName = generateRandomName();
|
||||
}
|
||||
|
||||
if (!reconnectedPlayer) {
|
||||
const configuredMax = gameConfigRef.current.maxPlayers || 0;
|
||||
const cap = subscriptionAccessType === 'subscription' || subscriptionAccessType === 'group' ? 150 : 10;
|
||||
const effectiveMax = Math.min(configuredMax || cap, cap);
|
||||
const realPlayersCount = playersRef.current.filter(p => p.id !== 'host').length;
|
||||
|
||||
if (realPlayersCount >= effectiveMax) {
|
||||
conn.send({ type: 'JOIN_DENIED', payload: { reason: 'Lobby is full.' } });
|
||||
connectionsRef.current.delete(conn.peer);
|
||||
conn.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let updatedPlayers = playersRef.current;
|
||||
let newPlayer: Player | null = null;
|
||||
|
|
@ -1294,6 +1352,12 @@ export const useGame = () => {
|
|||
};
|
||||
|
||||
const handleClientData = (data: NetworkMessage) => {
|
||||
if (data.type === 'JOIN_DENIED') {
|
||||
setError(data.payload.reason || 'Unable to join the game.');
|
||||
setGamePin(null);
|
||||
setGameState('LANDING');
|
||||
return;
|
||||
}
|
||||
if (data.type === 'WELCOME') {
|
||||
const payload = data.payload;
|
||||
console.log('[CLIENT] Received WELCOME:', {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue