Release v0.1.1 2026-07-10
This commit is contained in:
+84
-5
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user