Add modal name popup when a Pi-Rat earns a Name
When a Pi-Rat completes their second objective and ranks up, they now get a blocking modal popup (NameModal.svelte, rendered by Dashboard across all phases) to claim their Pirate Name, rather than an optional box buried in their character sheet. Removed the old inline prompt-box and its handler from CharacterSheet.svelte. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
- [x] **Clean up ruff E701 warnings**
|
- [x] **Clean up ruff E701 warnings**
|
||||||
|
|
||||||
- [ ] **Modal name popup**. When a Pi-Rat gains a name, they get a modal popup they have to fill out before continuing, rather than an optional box on their character sheet
|
- [x] **Modal name popup**. When a Pi-Rat gains a name, they get a modal popup they have to fill out before continuing, rather than an optional box on their character sheet
|
||||||
|
|
||||||
- [ ] **Cancel Revise**. In character creation, if you click "Revise" and then don't want to make any changes after all, there should be a cancel button that throws away any edits you've made since you clicked revise.
|
- [ ] **Cancel Revise**. In character creation, if you click "Revise" and then don't want to make any changes after all, there should be a cancel button that throws away any edits you've made since you clicked revise.
|
||||||
|
|
||||||
|
|||||||
88
frontend/src/components/NameModal.svelte
Normal file
88
frontend/src/components/NameModal.svelte
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<script>
|
||||||
|
import { apiRequest } from '../lib/api';
|
||||||
|
|
||||||
|
export let state;
|
||||||
|
|
||||||
|
let newNameInput = '';
|
||||||
|
let error = '';
|
||||||
|
let submitting = false;
|
||||||
|
|
||||||
|
async function submitNewName() {
|
||||||
|
if (!newNameInput.trim() || submitting) return;
|
||||||
|
error = '';
|
||||||
|
submitting = true;
|
||||||
|
try {
|
||||||
|
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/set-name`, 'POST', {
|
||||||
|
new_name: newNameInput.trim()
|
||||||
|
});
|
||||||
|
newNameInput = '';
|
||||||
|
} catch (e) {
|
||||||
|
error = e.message;
|
||||||
|
} finally {
|
||||||
|
submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeydown(e) {
|
||||||
|
if (e.key === 'Enter') submitNewName();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if state.player.needs_name}
|
||||||
|
<div class="modal-backdrop">
|
||||||
|
<div class="modal-box glass-panel">
|
||||||
|
<h3>🏴☠️ You Earned a Name!</h3>
|
||||||
|
<p class="info-text">
|
||||||
|
You completed your second objective and ranked up. No more going by your smell — what is your new Pirate Name?
|
||||||
|
</p>
|
||||||
|
{#if error}
|
||||||
|
<div class="alert alert-danger">{error}</div>
|
||||||
|
{/if}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={newNameInput}
|
||||||
|
class="input-field"
|
||||||
|
placeholder="Enter your Pirate Name..."
|
||||||
|
on:keydown={onKeydown}
|
||||||
|
autofocus
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-gold btn-full"
|
||||||
|
on:click={submitNewName}
|
||||||
|
disabled={!newNameInput.trim() || submitting}
|
||||||
|
>
|
||||||
|
Claim Name
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.modal-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
background: color-mix(in srgb, var(--bg-deep) 70%, transparent);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
.modal-box {
|
||||||
|
max-width: 440px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1.75rem;
|
||||||
|
border: 1px solid var(--edge);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-deep);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.modal-box h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.modal-box .input-field {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
export let state;
|
export let state;
|
||||||
|
|
||||||
let error = '';
|
let error = '';
|
||||||
let newNameInput = '';
|
|
||||||
let pvpOpponentId = '';
|
let pvpOpponentId = '';
|
||||||
let pvpCard = '';
|
let pvpCard = '';
|
||||||
|
|
||||||
@@ -21,19 +20,6 @@
|
|||||||
// What the chosen duel card becomes as a temporary Obstacle for the defender
|
// What the chosen duel card becomes as a temporary Obstacle for the defender
|
||||||
$: pvpCardObstacle = cardObstacleInfo(pvpCard, $obstacleTable);
|
$: pvpCardObstacle = cardObstacleInfo(pvpCard, $obstacleTable);
|
||||||
|
|
||||||
async function submitNewName() {
|
|
||||||
if (!newNameInput.trim()) return;
|
|
||||||
error = '';
|
|
||||||
try {
|
|
||||||
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/set-name`, 'POST', {
|
|
||||||
new_name: newNameInput.trim()
|
|
||||||
});
|
|
||||||
newNameInput = '';
|
|
||||||
} catch(e) {
|
|
||||||
error = e.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createPvp() {
|
async function createPvp() {
|
||||||
error = '';
|
error = '';
|
||||||
try {
|
try {
|
||||||
@@ -83,17 +69,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if state.player.needs_name}
|
|
||||||
<div class="prompt-box accent">
|
|
||||||
<h4>🏴☠️ You Earned a Name!</h4>
|
|
||||||
<p class="info-text" style="margin-bottom: 0.5rem;">You completed your second objective and ranked up! What is your new Pirate Name?</p>
|
|
||||||
<div class="input-row">
|
|
||||||
<input type="text" bind:value={newNameInput} class="input-field" placeholder="Enter new name...">
|
|
||||||
<button class="btn btn-gold" on:click={submitNewName} disabled={!newNameInput.trim()}>Claim Name</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if state.player.tax_banned}
|
{#if state.player.tax_banned}
|
||||||
<div class="prompt-box danger text-center">
|
<div class="prompt-box danger text-center">
|
||||||
<p class="info-text" style="margin: 0; font-size: 0.9rem;">🚫 You failed a refused Tax — no more Gat/Name Taxes for you this scene.</p>
|
<p class="info-text" style="margin: 0; font-size: 0.9rem;">🚫 You failed a refused Tax — no more Gat/Name Taxes for you this scene.</p>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
import RecruitPhase from '../components/RecruitPhase.svelte';
|
import RecruitPhase from '../components/RecruitPhase.svelte';
|
||||||
import GameOverPhase from '../components/GameOverPhase.svelte';
|
import GameOverPhase from '../components/GameOverPhase.svelte';
|
||||||
import EventLog from '../components/EventLog.svelte';
|
import EventLog from '../components/EventLog.svelte';
|
||||||
|
import NameModal from '../components/NameModal.svelte';
|
||||||
|
|
||||||
export let params = {};
|
export let params = {};
|
||||||
let gameId = params.id;
|
let gameId = params.id;
|
||||||
@@ -210,6 +211,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EventLog {state} />
|
<EventLog {state} />
|
||||||
|
<NameModal {state} />
|
||||||
{:else if !error}
|
{:else if !error}
|
||||||
<div class="loading-container">
|
<div class="loading-container">
|
||||||
<p>Loading game state...</p>
|
<p>Loading game state...</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user