Release v0.1.1 2026-07-10

This commit is contained in:
Warren H
2026-07-10 23:47:37 -04:00
parent 537a311f52
commit 6726c600e4
40 changed files with 2987 additions and 192 deletions
+84 -5
View File
@@ -1,10 +1,12 @@
import { create } from "zustand";
import { DEFAULT_SETTINGS, normalizeHunterName } from "./data";
import { SaveRepository } from "./saveRepository";
import { AccountRepository, type AccountResult } from "./accountRepository";
import type { AppScreen, GameModeId, GameSettings, HunterSave, SaveSlotId, SaveSlotState } from "./types";
import type { BossId, HealerClassId, InventoryItem } from "../game/types";
const repository = new SaveRepository();
const accounts = new AccountRepository();
const SETTINGS_KEY = "i-want-to-heal:settings:v1";
function loadSettings(): GameSettings {
@@ -24,7 +26,19 @@ function persistSettings(settings: GameSettings) {
}
}
interface FrontendState {
function accountNotice(result: Extract<AccountResult, { ok: false }>, action: "sign-in" | "create") {
switch (result.reason) {
case "missing-credentials": return "Enter both username and password.";
case "account-exists": return "Account already exists. Sign in with its password.";
case "account-not-found": return "Account not found. Create an account before enabling online sync.";
case "invalid-password": return "Username or password is incorrect.";
case "storage-unavailable": return action === "create"
? "Account could not be saved on this device. Continue offline or try again."
: "Account could not be verified on this device. Continue offline or try again.";
}
}
export interface FrontendState {
screen: AppScreen;
accountId: string | null;
slots: SaveSlotState[];
@@ -34,7 +48,8 @@ interface FrontendState {
selectedBossId: BossId;
settings: GameSettings;
notice: string;
signIn: (accountId: string) => void;
signIn: (username: string, password: string) => Promise<boolean>;
createAccount: (username: string, password: string) => Promise<boolean>;
continueOffline: () => void;
signOut: () => void;
navigate: (screen: AppScreen) => void;
@@ -70,9 +85,23 @@ export const useFrontendStore = create<FrontendState>((set, get) => ({
settings: loadSettings(),
notice: "",
signIn: (rawAccountId) => {
const accountId = rawAccountId.trim() || "wayfinder";
set({ accountId, slots: repository.list(accountId), screen: "saves", notice: `Online sync connected as ${accountId}.` });
signIn: async (username, password) => {
const result = await accounts.authenticate(username, password);
if (!result.ok) {
set({ notice: accountNotice(result, "sign-in") });
return false;
}
set({ accountId: result.username, slots: repository.list(result.username), screen: "saves", notice: `Online sync connected as ${result.username}.` });
return true;
},
createAccount: async (username, password) => {
const result = await accounts.create(username, password);
if (!result.ok) {
set({ notice: accountNotice(result, "create") });
return false;
}
set({ accountId: result.username, slots: repository.list(result.username), screen: "saves", notice: `Account created. Online sync connected as ${result.username}.` });
return true;
},
continueOffline: () => set({ accountId: null, slots: repository.list(null), screen: "saves", notice: "Offline saves ready." }),
signOut: () => set({ accountId: null, slots: repository.list(null), activeSlotId: null, screen: "login", notice: "Signed out. Offline saves remain on this device." }),
@@ -168,6 +197,56 @@ export const useFrontendStore = create<FrontendState>((set, get) => ({
clearNotice: () => set({ notice: "" }),
}));
export type FrontendSnapshot = Omit<FrontendState,
| "signIn"
| "createAccount"
| "continueOffline"
| "signOut"
| "navigate"
| "selectSlot"
| "createSlot"
| "playSlot"
| "deleteSlot"
| "copySlot"
| "uploadSlot"
| "downloadSlot"
| "selectMode"
| "selectBoss"
| "selectHealerClass"
| "updateActiveHealerInventory"
| "updateSetting"
| "touchActiveSave"
| "recordBossVictory"
| "clearNotice"
>;
export function getFrontendSnapshot(): FrontendSnapshot {
const {
signIn: _signIn,
createAccount: _createAccount,
continueOffline: _continueOffline,
signOut: _signOut,
navigate: _navigate,
selectSlot: _selectSlot,
createSlot: _createSlot,
playSlot: _playSlot,
deleteSlot: _deleteSlot,
copySlot: _copySlot,
uploadSlot: _uploadSlot,
downloadSlot: _downloadSlot,
selectMode: _selectMode,
selectBoss: _selectBoss,
selectHealerClass: _selectHealerClass,
updateActiveHealerInventory: _updateActiveHealerInventory,
updateSetting: _updateSetting,
touchActiveSave: _touchActiveSave,
recordBossVictory: _recordBossVictory,
clearNotice: _clearNotice,
...snapshot
} = useFrontendStore.getState();
return snapshot;
}
export function useActiveHunter(): HunterSave | null {
return useFrontendStore((state) => activeSave(state.slots, state.activeSlotId));
}