* 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>
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
'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>
|
||
)
|
||
}
|