From ab9486cb83d3f124c38b77cb7c2e4c2e9c364db8 Mon Sep 17 00:00:00 2001 From: Denis Ranneft Date: Tue, 31 Mar 2026 12:12:26 +0300 Subject: [PATCH] minor fixes --- frontend/src/App.tsx | 12 +++++++----- frontend/src/i18n/en.ts | 3 +++ frontend/src/i18n/ru.ts | 4 ++++ frontend/src/ui/DeathScreen.tsx | 29 +++++++++++++++++++---------- frontend/src/ui/HeroSheetModal.tsx | 4 ++++ frontend/src/ui/NPCDialog.tsx | 13 +++++++++++-- frontend/src/ui/QuestLog.tsx | 24 ++++++++++++++++++++---- 7 files changed, 68 insertions(+), 21 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 099707d..cc5ce58 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1173,6 +1173,9 @@ export function App() { const dismissToast = useCallback(() => setToast(null), []); + const questClaimDisabled = + gameState.phase === GamePhase.Dead || (gameState.hero?.hp ?? 0) <= 0; + return (
@@ -1205,6 +1208,7 @@ export function App() { quests={heroQuests} onQuestClaim={handleQuestClaim} onQuestAbandon={handleQuestAbandon} + questClaimDisabled={questClaimDisabled} /> )} @@ -1226,11 +1230,8 @@ export function App() { {/* Toast Notification */} @@ -1292,6 +1293,7 @@ export function App() { onQuestsChanged={refreshHeroQuests} onHeroUpdated={handleNPCHeroUpdated} onToast={(message, color) => setToast({ message, color })} + questClaimDisabled={questClaimDisabled} /> )} diff --git a/frontend/src/i18n/en.ts b/frontend/src/i18n/en.ts index 44c0576..21f8a2b 100644 --- a/frontend/src/i18n/en.ts +++ b/frontend/src/i18n/en.ts @@ -93,6 +93,7 @@ export const en = { questLog: 'Quest Log', noActiveQuests: 'No active quests. Visit an NPC to accept quests!', claimRewards: 'Claim Rewards', + claimRewardsDisabledDead: 'Revive to claim quest rewards', questDestination: 'Destination', abandon: 'Abandon', acceptQuest: 'Accept', @@ -127,6 +128,8 @@ export const en = { youDied: 'YOU DIED', reviveNow: 'REVIVE NOW', freeRevivesLeft: 'Free revives left: {count}', + revivesUnlimitedSubscription: 'Unlimited revives (subscription)', + reviveNowWithCount: 'REVIVE NOW ({count})', autoReviveIn: 'Auto-revive in {timer}s', noFreeRevives: 'No free revives left \u2014 subscription required', diff --git a/frontend/src/i18n/ru.ts b/frontend/src/i18n/ru.ts index 7ea6e2c..1b3634b 100644 --- a/frontend/src/i18n/ru.ts +++ b/frontend/src/i18n/ru.ts @@ -95,6 +95,8 @@ export const ru: Translations = { questLog: '\u0416\u0443\u0440\u043d\u0430\u043b \u0437\u0430\u0434\u0430\u043d\u0438\u0439', noActiveQuests: '\u041d\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0437\u0430\u0434\u0430\u043d\u0438\u0439. \u041f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0435 \u0441 NPC!', claimRewards: '\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u0430\u0433\u0440\u0430\u0434\u0443', + claimRewardsDisabledDead: + '\u0412\u043e\u0441\u043a\u0440\u0435\u0441\u043d\u0438\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u0430\u0433\u0440\u0430\u0434\u044b \u0437\u0430 \u0437\u0430\u0434\u0430\u043d\u0438\u044f', questDestination: '\u041f\u0443\u043d\u043a\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f', abandon: '\u041e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f', acceptQuest: '\u041f\u0440\u0438\u043d\u044f\u0442\u044c', @@ -129,6 +131,8 @@ export const ru: Translations = { youDied: '\u0412\u042b \u041f\u041e\u0413\u0418\u0411\u041b\u0418', reviveNow: '\u0412\u041e\u0421\u041a\u0420\u0415\u0421\u0418\u0422\u042c', freeRevivesLeft: '\u0411\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0445 \u0432\u043e\u0441\u043a\u0440\u0435\u0448\u0435\u043d\u0438\u0439: {count}', + revivesUnlimitedSubscription: '\u0411\u0435\u0437\u043b\u0438\u043c\u0438\u0442\u043d\u044b\u0435 \u0432\u043e\u0441\u043a\u0440\u0435\u0448\u0435\u043d\u0438\u044f (\u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430)', + reviveNowWithCount: '\u0412\u041e\u0421\u041a\u0420\u0415\u0421\u0418\u0422\u042c ({count})', autoReviveIn: '\u0410\u0432\u0442\u043e-\u0432\u043e\u0441\u043a\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 {timer}\u0441', noFreeRevives: '\u041d\u0435\u0442 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0445 \u0432\u043e\u0441\u043a\u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u2014 \u043d\u0443\u0436\u043d\u0430 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430', diff --git a/frontend/src/ui/DeathScreen.tsx b/frontend/src/ui/DeathScreen.tsx index f6b2fef..b1c4e9a 100644 --- a/frontend/src/ui/DeathScreen.tsx +++ b/frontend/src/ui/DeathScreen.tsx @@ -5,8 +5,10 @@ import { useT, t } from '../i18n'; interface DeathScreenProps { visible: boolean; onRevive: () => void; - /** Free revives left for non-subscribers; omit when subscribed / unlimited. */ - revivesRemaining?: number; + /** Active subscription: manual revives are not limited by the free quota. */ + subscriptionUnlimited?: boolean; + /** Free manual revives left (0–2) when not subscriptionUnlimited. */ + revivesRemaining: number; } /** Full-screen dimming; `pointerEvents: 'none'` so HUD controls (e.g. hero sheet) stay clickable. */ @@ -62,10 +64,15 @@ const buttonStyle: CSSProperties = { transition: 'background-color 0.2s', }; -export function DeathScreen({ visible, onRevive, revivesRemaining }: DeathScreenProps) { +export function DeathScreen({ + visible, + onRevive, + subscriptionUnlimited, + revivesRemaining, +}: DeathScreenProps) { const tr = useT(); const [timer, setTimer] = useState(REVIVE_TIMER_SECONDS); - const canRevive = revivesRemaining === undefined || revivesRemaining > 0; + const canRevive = !!subscriptionUnlimited || revivesRemaining > 0; // Countdown timer useEffect(() => { @@ -101,11 +108,11 @@ export function DeathScreen({ visible, onRevive, revivesRemaining }: DeathScreen
{tr.youDied}
{canRevive ? timer : '—'}
- {revivesRemaining !== undefined && ( -
- {t(tr.freeRevivesLeft, { count: Math.max(0, revivesRemaining) })} -
- )} +
+ {subscriptionUnlimited + ? tr.revivesUnlimitedSubscription + : t(tr.freeRevivesLeft, { count: Math.max(0, revivesRemaining) })} +
{canRevive ? t(tr.autoReviveIn, { timer }) : tr.noFreeRevives} diff --git a/frontend/src/ui/HeroSheetModal.tsx b/frontend/src/ui/HeroSheetModal.tsx index 465a9ce..bc23e23 100644 --- a/frontend/src/ui/HeroSheetModal.tsx +++ b/frontend/src/ui/HeroSheetModal.tsx @@ -124,6 +124,8 @@ interface HeroSheetModalProps { quests: HeroQuest[]; onQuestClaim: (heroQuestId: number) => void; onQuestAbandon: (heroQuestId: number) => void; + /** Disable claim while hero is dead (HP 0 / death phase). */ + questClaimDisabled?: boolean; } export function HeroSheetModal({ @@ -137,6 +139,7 @@ export function HeroSheetModal({ quests, onQuestClaim, onQuestAbandon, + questClaimDisabled = false, }: HeroSheetModalProps) { const [tab, setTab] = useState(initialTab); const tr = useT(); @@ -207,6 +210,7 @@ export function HeroSheetModal({ quests={quests} onClaim={onQuestClaim} onAbandon={onQuestAbandon} + claimDisabled={questClaimDisabled} /> )} {tab === 'settings' && ( diff --git a/frontend/src/ui/NPCDialog.tsx b/frontend/src/ui/NPCDialog.tsx index 34309b9..f660d03 100644 --- a/frontend/src/ui/NPCDialog.tsx +++ b/frontend/src/ui/NPCDialog.tsx @@ -18,6 +18,8 @@ interface NPCDialogProps { onQuestsChanged: () => void; onHeroUpdated: (hero: HeroResponse) => void; onToast: (message: string, color: string) => void; + /** Block quest reward claim while hero is dead. */ + questClaimDisabled?: boolean; } // ---- Styles ---- @@ -242,6 +244,7 @@ export function NPCDialog({ onQuestsChanged, onHeroUpdated, onToast, + questClaimDisabled = false, }: NPCDialogProps) { const tr = useT(); const [availableQuests, setAvailableQuests] = useState([]); @@ -419,8 +422,14 @@ export function NPCDialog({ )}
diff --git a/frontend/src/ui/QuestLog.tsx b/frontend/src/ui/QuestLog.tsx index 10dd712..0765e81 100644 --- a/frontend/src/ui/QuestLog.tsx +++ b/frontend/src/ui/QuestLog.tsx @@ -9,6 +9,7 @@ interface QuestLogProps { onClaim: (heroQuestId: number) => void; onAbandon: (heroQuestId: number) => void; onClose: () => void; + claimDisabled?: boolean; } // ---- Quest Type Icons ---- @@ -167,6 +168,15 @@ const claimBtnStyle: CSSProperties = { animation: 'quest-claim-glow 1.5s ease-in-out infinite', }; +const claimBtnDisabledStyle: CSSProperties = { + ...claimBtnStyle, + opacity: 0.45, + cursor: 'not-allowed', + animation: 'none', + boxShadow: 'none', + textShadow: 'none', +}; + const abandonBtnStyle: CSSProperties = { padding: '8px 14px', border: '1px solid rgba(255, 80, 80, 0.3)', @@ -189,10 +199,12 @@ interface QuestLogListProps { quests: HeroQuest[]; onClaim: (heroQuestId: number) => void; onAbandon: (heroQuestId: number) => void; + /** When true, completed-quest claim is blocked (e.g. hero dead). */ + claimDisabled?: boolean; } /** Quest list body (embedded in Hero sheet or standalone panel). */ -export function QuestLogList({ quests, onClaim, onAbandon }: QuestLogListProps) { +export function QuestLogList({ quests, onClaim, onAbandon, claimDisabled = false }: QuestLogListProps) { const tr = useT(); const [expandedId, setExpandedId] = useState(null); @@ -294,9 +306,13 @@ export function QuestLogList({ quests, onClaim, onAbandon }: QuestLogListProps)
{isCompleted ? (
- +