bigtech-sucks/packages/web/src/hooks/useGameState.ts
CLU de53ac41a5 bigtech.sucks: living database + first assessment
The database is the source of truth; the site is a view over it.

- packages/core/db/schema.sql: vendors, products, categories, the 8-dimension
  instrument, assessments, scores, evidence, findings, off-ramp, plus the
  derived v_product_score view the game reads.
- packages/core/db/build.mjs: rebuilds bigtech.db, absorbs the legacy 43-product
  roster as unassessed, applies seeds, then verifies the instrument's own rules
  (weights sum to 100, totals recompute, grades match bands, every score has a
  rationale, no tier-4 evidence on a scored dimension, reproducible-build caps
  actually enforced).
- packages/core/db/seed/0001-signal.sql: Signal Desktop 8.36.0-alpha.1 on linux,
  75.9/100 grade B, weakest dimension local_data_exposure. Evidence points at
  the re-runnable audit artifacts in the Servetus vault.
- packages/core/src/data/generated/catalog.{ts,json}: generated export.

Verified: 44 products, 1 assessed, all checks pass.
2026-10-29 13:16:29 -05:00

91 lines
No EOL
2.3 KiB
TypeScript

import { useCallback, useMemo, useState } from 'react';
import {
advanceCapture,
buildSovereigntyReport,
computeOffRamp,
createInitialState,
persistToVault,
selectOutlaws,
setPhase,
transitionAfterShowdown,
type LoginMethod,
type PlayerJourneyState,
} from '@bigtech-sucks/core';
export function useGameState() {
const [state, setState] = useState<PlayerJourneyState>(() => createInitialState());
const report = useMemo(
() => buildSovereigntyReport(state.selectedOutlaws, state.loginMethod),
[state.selectedOutlaws, state.loginMethod],
);
const offRamp = useMemo(
() => (state.selectedOutlaws.length ? computeOffRamp(state.selectedOutlaws) : null),
[state.selectedOutlaws],
);
const vaultExport = useMemo(() => persistToVault(state), [state]);
const login = useCallback((method: LoginMethod) => {
setState((s) => ({ ...s, loginMethod: method }));
}, []);
const goToPhase = useCallback((phase: PlayerJourneyState['phase']) => {
setState((s) => setPhase(s, phase));
}, []);
const drawLine = useCallback((ids: string[]) => {
setState((s) => selectOutlaws(s, ids));
}, []);
const startShowdown = useCallback((outlawId: string) => {
setState((s) => setPhase({ ...s, activeOutlawId: outlawId }, 'showdown'));
}, []);
const showdownStep = useCallback(
(step: 'round_up' | 'validate' | 'cut') => {
setState((s) => {
if (!s.activeOutlawId) return s;
const advanced = advanceCapture(s, s.activeOutlawId, step);
if (step === 'cut') {
return transitionAfterShowdown(advanced);
}
return advanced;
});
},
[],
);
const submitWaitlist = useCallback(async (email: string, channel: string) => {
const payload = {
email,
channel,
settlerSince: state.settlerSince,
};
const res = await fetch('/api/waitlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error('Waitlist submission failed');
setState((s) => ({ ...s, waitlistEmail: email, waitlistChannel: channel }));
return payload;
}, [state.settlerSince]);
return {
state,
report,
offRamp,
vaultExport,
login,
goToPhase,
drawLine,
startShowdown,
showdownStep,
submitWaitlist,
};
}