Files
accounted/components/onboarding/journey/YearBand.tsx
T
Jakob WennbergandClaude Fable 5 b771c1f923 feat(onboarding): journey PR B — reducer, orb, track, question primitives (behind /sandbox demo) (#1145)
* refactor(onboarding): extract first-year defaults + shared TIC lookup client (journey PR A)

First of four PRs replacing the onboarding wizard with the journey flow
(dev_docs/onboarding_migration_plan.md, local). No UI change.

- Move deriveFirstYearDefaults + parseStartMonthDay out of
  WelcomeOnboarding into lib/company/first-year-defaults.ts and unit-test
  them (11-vs-13-months boundary, UTC month seeding, malformed input).
- Add the missing computeFiscalPeriod unit tests (calendar year, brutet
  ar, first year short/extended, EF calendar-year rule, period names,
  BFL 3 kap. 6-18 month window errors).
- New shared fetchCompanyLookup() client: the single client path to the
  Lens-backed /lookup, typed outcomes (found / not_found / disabled /
  error / aborted), never throws. Fixes the 403/404 conflation: the
  dispatcher's 404 ("Extension not found") and feature-flag 503
  (EXTENSION_DISABLED) now degrade silently instead of rendering as
  "company not found"; only the TIC handler's own 404 does.
- Step2CompanyDetails consumes the helper; identical UX otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(onboarding): journey state machine reducer with full branch coverage (journey PR B, 1/3)

Pure reducer for the journey onboarding: owns every transition and every
CompanySettings write; the component layer only renders steps, runs the
single TIC lookup, and calls the server action.

Encodes the plan's invariants: entry-snapshot history (Back rolls answers
AND stations), lookupRan gates fact-vs-question per field (BankID prefill
without lookup degrades to questions), vat_registered is never defaulted
without lookup data or an explicit answer, entity change wipes downstream,
org_number_invalid bounces to the Företaget station, station jumps rewind
to a station's first step.

34 unit tests: AB/EF found, not-found manual, ceased, BankID prefill
(found + degraded + disabled), first year, brutet år, moms nej, Back from
every step, station jumps, server-error bounces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(onboarding): journey visual primitives + sandbox gallery (journey PR B, 2/3)

Ports the founder-approved concept (artifact c82c9358) to React:

- JourneyOrb: 320-particle canvas sphere with comet travel, check morph
  and the monogram finale (glyph sampled live from --font-display). Own
  component per plan, NOT thinking-orbs. rAF pauses on document.hidden;
  reduced motion renders static frames.
- JourneyTrack: five stations with inked answers; completed stations are
  keyboard-accessible jump-back buttons; answers mirrored to an aria-live
  region.
- Question primitives: Question (ink title + "?" popover, Esc closes),
  ChipRow (fly-to-orb ghost), YearBand (springy fiscal-year preview),
  JourneyDatePicker (year -> month by name -> day), AddressFields
  (Enter-chained, skippable).
- journey.css: concept stylesheet namespaced under .jny on app tokens,
  incl. the no-scroll composition (100dvh + optical-centering balance
  spacer) and the dawn layer.
- /sandbox/journey: internal primitive gallery (auth-free sandbox path),
  demo data only: this page makes ZERO TIC calls.

i18n note: primitives are copy-agnostic (strings via props); the real
flow's sv/en keys land with their consumer in PR C.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: log journey reducer location decision

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(onboarding): annotate ENTITY_PICKED settings as Partial<CompanySettings>

The wipeDownstream return narrows against the inferred initializer type;
tsc strict rejects the reassignment without the explicit annotation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:32:45 +02:00

57 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { InkText } from './ink'
/**
* The fiscal-year band: 24 or 36 month cells with a springy highlight span,
* a serif label ("1 januari 31 december") and a small note line.
* Pure display; the parent drives span/label/note from hover or selection.
*/
const MONTH_LETTERS = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D']
interface YearBandProps {
cells?: 24 | 36
year0: number
/** Inclusive month-cell indices to highlight, or null for none. */
span?: [number, number] | null
label?: string
note?: string
}
export default function YearBand({ cells = 24, year0, span, label, note }: YearBandProps) {
const years = Array.from({ length: cells / 12 }, (_, i) => year0 + i)
return (
<div className="jny-yband">
<div className="jny-yb-grid">
<div
className={`jny-yb-span${span ? ' is-on' : ''}`}
style={
span
? {
left: `${(span[0] / cells) * 100}%`,
width: `${((span[1] - span[0] + 1) / cells) * 100}%`,
}
: undefined
}
/>
<div className="jny-yb-cells" style={{ gridTemplateColumns: `repeat(${cells}, 1fr)` }}>
{Array.from({ length: cells }, (_, i) => (
<span key={i} className={span && i >= span[0] && i <= span[1] ? 'is-in' : undefined}>
{MONTH_LETTERS[i % 12]}
</span>
))}
</div>
</div>
<div className="jny-yb-years" style={{ gridTemplateColumns: `repeat(${cells}, 1fr)` }}>
{years.map((y) => (
<span key={y} style={{ gridColumn: 'span 12' }}>
{y}
</span>
))}
</div>
<div className="jny-yb-label">{label ? <InkText text={label} step={40} /> : null}</div>
<div className="jny-yb-note">{note}</div>
</div>
)
}