This repository was archived by the owner on Jun 15, 2026. It is now read-only.
Conversation
Replace react-scripts/CRA with Vite 5 and migrate the Jest suite to Vitest. Move index.html to the project root, switch REACT_APP_* env vars to import.meta.env.VITE_*, drop the CRA service worker, and adopt an ESLint 9 flat config. Add a Playwright E2E smoke suite that drives the app against the live demo API, plus a CI quality gate (lint/typecheck/test/build/E2E) and an updated Node 20 release workflow. Switch the package manager from yarn to npm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upgrade React 16 to 19 (createRoot) and migrate react-router 5 to 7. A small compatibility shim (src/routing.tsx) reconstructs the v5-style withRouter / RouteComponentProps / useHistory API on top of v7 hooks so consumers did not need rewriting; routers themselves move to Routes/Route element/Navigate. Also upgrade react-redux 9, react-intl 6, recharts 2, and RTL 16, and fix the React 19 type changes (JSX -> React.JSX, explicit children on FC props, NavLink activeClassName -> className function). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the Redux store to Redux Toolkit configureStore and convert the client/UI-state reducers (loader, error, search) to createSlice. Introduce a TanStack Query client and migrate the server reads for the metrics resources (global and per-user) to useQuery, establishing the query pattern for server state while UI state stays in Redux. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the app-wide accessibility foundation: skip link, <main> landmark and per-route titles (in app.tsx), :focus-visible styling, prefers-reduced-motion support, and WCAG AA contrast fixes in the theme (nav links, balance text, step buttons). Make the Modal a proper dialog with a focus trap and focus restoration, label the navigation and icon-only controls (theme switcher, text scaling, add-user), and hide decorative icons from assistive tech. Wire automated axe scans + a keyboard skip-link check into the E2E suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve the remaining axe violations on the deeper screens: name the accept/cancel buttons, hide the barcode scanner's capture inputs and the user-detail focus-hack input from assistive tech, and make the global error toast a role=alert live region. Extend the axe E2E to the user detail, edit, send-money and article-form screens (all now violation-free) and document the WCAG 2.1 AA conformance status and follow-ups in specs/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-intl 7 declares React 19 support, so the workaround flags are no longer needed: remove legacy-peer-deps from .npmrc and the @types/react override from package.json. Installs now resolve cleanly under strict peer-dependency rules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the settings Redux reducer/thunk with a useSettings() query (default data as the synchronous fallback, error toast preserved via errorHandler). selector-hooks re-exports it so the 25 consumer call sites are unchanged. Move the transaction 'deletable' check to a hook (useSettings + useTransaction), drop settings from the store/action union, and update the test render helper to provide a QueryClient that seeds settings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the article Redux reducer/thunks/selectors with a queries/articles module: useArticles/useArticle/usePopularArticles read via useQuery, and the mutations (add/delete article, barcode, tag, fetch-by-barcode) run as imperative helpers that reuse errorHandler for the toast and invalidate the article caches. selector-hooks re-exports the read hooks so consumers are unchanged; drop the article reducer from the store/action union and its now-obsolete reducer tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the user and transaction Redux reducers/thunks with queries/users and queries/transactions: useUsers/useUser/useFilteredUsers/useUserTransactions read via useQuery, and createUser/updateUser/createTransaction/deleteTransaction run as imperative helpers (errorHandler for the toast, query invalidation for cache). UserList/UserCard now take user objects and TransactionListItem takes a transaction object (no per-row refetch); TransactionTable is a hook-based component. Query keys coerce ids to strings so numeric API ids and string route params hit the same cache entry. The Redux store is now only client/UI state (error, loader, search); delete the obsolete reducer tests and the dead user-multi-selection stub. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Now that the Redux store is RTK-only client state, type Dispatch as the real AppDispatch (removing the (action: any) shim), re-enable RTK's default middleware checks, and add typed useAppDispatch/useAppSelector hooks. Replace module-level mutable state with hooks: the idle timer uses a ref, and the article tag filter uses a useTags() query instead of a hand-rolled cache. Move the hardcoded accept/cancel/theme-toggle aria-labels into the i18n catalog and stop them overriding consumer-supplied intl titles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the leftover Redux machinery with a tiny subscribable global-error store (useSyncExternalStore) — the only remaining client-state piece. The error and loader slices and the dead search slice are gone; errorHandler no longer takes a dispatch and writes to the new store directly. Move the type-only modules out of src/store/reducers/ to src/types/ (with a barrel and DeepPartial), add src/queries/index.ts so consumers import server hooks from one place, and repoint every '../../store' import to '../../queries' or '../../types'. Drop the Redux Provider from app.tsx, the redux-mock-store spec helper, and the legacy filtered-users search-query coupling (the search slice was never written, so the filter was already a no-op). Uninstall react-redux, @reduxjs/toolkit, redux, redux-mock-store and their types. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite services/api.ts with a typed request() helper that throws ApiError on any non-2xx, accepts AbortSignal, and standardises mode/credentials. get/post/restDelete are now generic <T>; all queries pass their response type and forward the query's AbortSignal so navigations cancel inflight requests. Free-form path/query inputs (barcode, user ids) are now URLSearchParams/encodeURIComponent'd. errorHandler stops resetting the global error on entry (concurrent success no longer hides a pending failure), and the error.class lookup is exact against both the full FQCN and its short name (was: substring match — a namespaced superstring would have collided). Normalize User.id (number from API → string in TS) at the API boundary so the rest of the app and the cache keys match. Drop the redux-era OwnProps/StateProps/ActionProps scaffolding from ErrorMessage. Move playCashSound into the createTransaction success branch (no ka-ching on a rejected POST). Route metrics/tags through the queryKeys factory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t while pending Add useCreate/Update/Delete-* hooks alongside the imperative helpers and migrate every function-component call site (transaction-button, deposit/ dispense, split-invoice, paypal, user-article, article-form barcode/tag/ article CRUD, undo, create-user, edit-user) to consume mutateAsync + isPending. Submit buttons are now disabled while the mutation is in flight, eliminating the double-submit hole in split-invoice and rapid-rescan in article-scanner. The legacy class CreateUserTransactionForm gets an isSubmitting state guard in the same shape until the full hooks conversion in a later commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tsconfig: drop useUnknownInCatchVariables: false (all catches are now bare), flip noUnusedLocals on, remove the tsc --noEmit duplication in the build script (CI already typechecks separately). ESLint: stop hiding unused-directive warnings (reportUnusedDisableDirectives back to 'warn'), promote no-unused-vars to 'error' (was 'warn'), and surface no-explicit-any as 'warn' so new code is discouraged from using it. Remove the orphaned 'import * as React from react' lines that the automatic JSX runtime made obsolete; clean up the now-flagged stale eslint-disable directives; replace the two surviving '==' / '!=' loose-equality checks with '===' / '!==' (User.id is normalized to string at the API boundary). Drop the _initialState parameter from renderWithContext (Redux-era vestige) and the ~6 call sites that passed it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modal: drop the button-as-backdrop (now role=presentation; backdrops aren't real controls — Esc, the focus trap, and click-outside still dismiss), remove the English 'close'/'Dialog' literal defaults in favour of intl, rename the typo'd backDropTile prop out of existence, replace keyCode === 27 with e.key === 'Escape', and add a focus-restore fallback to #main-content when the previously-focused element has unmounted. user-selection now passes a real intl label. Touch targets: bump .button to 0.75rem 1rem padding + 2.75rem min-height and .fab to 2.75rem so all interactive controls meet WCAG 2.5.5 / kiosk 44x44 at every breakpoint. Dark theme contrast: brighten --textSubtile (3.1:1 → AA) so inactive nav links read correctly; rebalance --buttonGreenFont/--buttonRedFont on the darker step-button backgrounds. FormField now uses React.useId() (React 19 primitive) instead of Date.now(), and gets a real typed Props. AlertText pairs its green/red color with a screen-reader-only positive/ negative cue so colour isn't the only signal. RouteTitle renders a localized sr-only <h1> on every route so the heading outline starts at level 1 (was: every page started at h2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Toast, Scanner, CurrencyInput and CreateUserTransactionForm are now function components: - Toast: pure timer-driven visibility on useEffect. - Scanner: keystroke buffer lives in a ref so the document keydown handler always reads the current value (fixes the stale-closure bug where onChange fired with the previous barcode). - CurrencyInput: derived display value with explicit prop->state sync via useEffect; drops the @ts-expect-error ref forwarding. - CreateUserTransactionForm: uses useRouter() + useCreateTransaction() so the double-submit guard becomes a one-line isPending check instead of a hand-rolled isSubmitting state machine; ConnectedCreateCustomTransactionForm is now an identity re-export. Migrate trivial leaf consumers off the v5 routing shim to direct v7 hooks (ScrollToTop, UserDetailsSeparator). Delete confirmed dead code: BackButton, CreateUserTransactionLink, UserArticleTransactionLink, useUserArray alias, the inaccessible Tag brick, the dead common/index barrel, the unused 'first' prop on TransactionListItem and the unused 'disabled'/'getString' props on UserSelection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…wallowing Read queries no longer wrap their queryFn in errorHandler — they let ApiError/network failures bubble out to TanStack Query so isError, retry, and the data placeholder behave correctly. A QueryCache.onError handler forwards each query's meta.defaultError message id to setGlobalError, so the user-facing toast UX is unchanged. Add a TypeScript module augmentation so meta is typed. Annotate every existing query (useUsers, useArticles, useArticle, useTags, useSettings, useUserTransactions, useMetrics, useUserMetrics) with a meta.defaultError pointing at its localized message id. Mutations keep the imperative errorHandler path because they still map specific error.class values to specific messages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A11y E2E: - Replace every page.waitForTimeout with deterministic waits (nav landmark visible, network-idle, balance heading visible, first textbox visible) so the suite is reliable on slow CI. - Add a dark-theme scan (set localStorage.SELECTED_THEME='dark' via addInitScript before goto). - Add a modal-open scan + focus-trap traversal: confirms Tab cycles between the input and submit button inside the dialog and Escape dismisses it. - Add --greenText/--redText overrides for the dark palette (user-card balance was failing 4.5:1 against #2e3d4d). Release pipeline (package.yml) now runs lint + typecheck before tests/build so tag-pushes can't ship something the PR gate would reject. Tooling hygiene: - .env.example documents both demo and self-hosted VITE_API. - engines field pins Node >= 20 to match CI. - .gitignore picks up .vite/, *.log, *.tsbuildinfo, stats.html. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The lockfile was generated on macOS and only recorded @rollup/rollup-darwin-*, so 'npm ci' on the Linux CI runner failed with 'Cannot find module @rollup/rollup-linux-x64-gnu' (npm bug npm/cli#4828: optional deps for other platforms aren't recorded in package-lock.json unless they're declared explicitly). Declare the common platform natives as optionalDependencies so they land in the lockfile for every platform. npm still installs only the one matching the current OS/CPU; the rest are skipped at install time but resolved correctly under 'npm ci' on any of those platforms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…warnings
Type ButtonProps without the orphan 'ref?: any' (forwardRef provides it),
Tab as Omit<NavLinkProps,...> with explicit className/style/active(Class)Name
fields, Plus as React.SVGProps<SVGSVGElement>, SearchIcon with a typed style
prop, FlexProps.grow as number | string, and the metrics resource's local
Article precursor as Article (was any).
Recharts' Tooltip is generic-typed and trips React 19's JSX checker; assert
a narrower ComponentType<{ contentStyle?: CSSProperties }> instead of any.
Drop the now-stale eslint-disable comments in modal.tsx, nav-tab-menu.tsx
and services/sound.ts that the tighter ESLint config surfaced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- delete the now-stale docs/review.md snapshot (action items it raised are either addressed or live in docs/review-followup.md). - fix useTags meta key (ARTICLES_COULD_NOT_BE_LOADED -> TAGS_COULD_NOT_BE_LOADED) and add missing locale strings for tag-load / barcode-delete / tag-delete / article-delete failure paths so the toast doesn't lie about which entity failed. - drop Tab.active dead-on-arrival prop (no callers pass it). - inline the ConnectedCreateCustomTransactionForm identity re-export at its single consumer and remove the alias. - key RouteTitle's document-title effect on pathname only (title/match are derived, no need to retrigger on identity changes). - clarify Scanner's barcode regex limitation (-, . dropped — affects ISBN-10). - comment invalidateUsers' double prefix so the second invalidate doesn't read as a typo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two patterns were both burning the same way: Toast and Scanner ran their effect with the callback (onFadeOut / onChange) in the deps array, so every parent render created a fresh closure that tore down and re-added the setTimeout / document keydown listener. The class-component versions explicitly bound once in componentDidMount; restore that semantic by holding the callback in a ref and dropping it from the deps array. useModal: the eslint-disable directive for exhaustive-deps had been removed in the lint pass, but handleHide / handlePopState were still captured by the effect without being in the deps. Wrap handleHide/handleShow in useCallback so they're stable identities and add handleHide to the deps — now the dep array is honest and consumers can also rely on stable function references. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before, the imperative mutation helpers (createUser, createTransaction,
addArticle, …) called errorHandler() internally and swallowed every
failure into 'return undefined', so useMutation's native isError/error/
onError were permanently dead — every call site had to truthy-check the
return value and the global toast was the only surface that knew anything
went wrong.
Now:
- ApiError gains an optional errorClass field so the body-level 'error.class'
case (HTTP 200 + { error: { class } } envelope) can carry the FQCN.
- A small throwOnBodyError(data) helper turns that body shape into a thrown
ApiError; HTTP errors already throw from request<T>.
- query-client.ts registers MutationCache.onError, reads meta.errors /
meta.defaultError per mutation, and routes the right localized message
through setGlobalError — mirroring the QueryCache.onError bridge so reads
and writes use a single funnel.
- Every imperative helper now throws on failure; the useXxx hooks moved their
errors/defaultError onto useMutation's meta. mutationKey decorations that
nothing read were removed.
- Consumers that used to 'if (result)' switch to try/catch on mutateAsync
(or .catch on the promise in the paypal-transaction effect). isError and
onError now actually work for any future caller that wants per-site error
UX.
The deprecated errorHandler() function is gone; pickErrorMessage,
shortClass and ErrorClassMap remain (they're pure and shared with the
mutation cache).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ArticleDetails: re-seed form state on article.id change, not on the article reference identity — keeps unsaved edits from being blown away when the cache invalidates after another write. - CurrencyInput: explicit controlled / uncontrolled discipline (mirror the prop directly when value is given, hold internal state when not). Drops the prop-sync effect that round-tripped value→displayValue on every parent re-render; the round trip happened to be clean but the contract was vague. - SplitInvoiceForm: validation is a pure derivation of (participants, amount, recipient, settings) — replaced the useEffect+useState dance with useMemo so the validation object identity is only fresh when an input actually changes. - ArticleScanner: hold an AbortController ref and abort the in-flight fetchArticleByBarcode when a new scan arrives (or on unmount); ignore the AbortError. Prevents a slow earlier response from overwriting state for a newer scan. fetchArticleByBarcode now accepts an optional AbortSignal. - Modal: a MutationObserver watches dialog content for async-rendered controls (loading → form pattern) and moves focus to the first focusable as soon as it appears, instead of leaving focus on the dialog wrapper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four blockers from the follow-up audit: - AlertText was labeling balance=0 as 'positive balance' (SC 1.1.1/1.3.1). Three-way sign cue now — negative / zero / positive — and the prefixes are i18n keys instead of hardcoded English strings. - Transaction success played a ka-ching with no way to silence it (SC 1.4.2 Audio Control). Added a persistent localStorage-backed user preference (services/sound-preference.ts), a SoundToggle button beside ScalingButtons / ThemeSwitcher in the nav, and playCashSound now also respects prefers-reduced-motion. New SoundOnIcon / SoundOffIcon bricks. - Heading hierarchy gaps (SC 1.3.1 / 2.4.6): demoted in-view <h1>s on /split-invoice and /user/:id/metrics to <h2> (RouteTitle injects the sr-only <h1>); added sr-only <h2>s on /user/active, /user/inactive, /articles/active, /articles/inactive, /search-results, and the send-money view so screen-reader heading nav has something between the page title and the list/form. - Programmatic focus on route change (SC 2.4.3): a small FocusMainOnRouteChange component focuses #main-content on every pathname change after the initial mount, so keyboard/AT users land on the new view's landmark instead of a stale or unmounted element. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Important findings from the audit: - useModal: track open modals in a module-level stack; Esc and popstate handlers now only fire for the topmost open dialog. Previously two nested modals both closed (and history popped twice) on a single Esc. - ErrorMessage: drop redundant aria-live='assertive' next to role='alert' (the role implies it). Avoids double-announce on some AT clients. - Transaction success was silent to AT (the ka-ching sound is not an AT signal). New global-status singleton + role='status' polite live region mounted in Layout, fired from createTransaction with TRANSACTION_SUCCESS. Auto-clears after 4s. - Placeholder-as-only-label fixed on seven inputs (custom-tx comment, split-invoice comment, edit-user name/email, create-user name, search-list search, article-form item row, article-selection-bubbles): each gets an explicit aria-label matching the placeholder. CurrencyInput grows an optional aria-label prop with a sane default (the placeholder). - Programmatic focus on route change is now gated on navigationType==='PUSH' so initial loads, root→user/active redirect (REPLACE), and browser back/forward (POP) leave focus alone — the skip link stays reachable as the natural first Tab target. - Footer GitHub SVG: aria-hidden + focusable=false, matching every other decorative icon in the app. - Consolidate visually-hidden styles: drop bricks/text/text.module.css's duplicate .srOnly in favour of the global .sr-only utility in bricks/theme/theme.css. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ans, doc - Unit tests for shortClass / pickErrorMessage / throwOnBodyError covering FQCN vs short-class fallback and the body-error ApiError wrapping. - Unit tests for the queryClient bridges: QueryCache.onError routes meta.defaultError to setGlobalError on read failures; MutationCache.onError prefers meta.errors[short class] over meta.defaultError on ApiError; the bridge is a no-op when a mutation has no meta. - getGlobalError(): non-hook accessor so tests (and non-component callers) can peek the current snapshot without spinning up a renderer. - New E2E tests: dark theme axe scan extended to /articles/active and /metrics; every scanned route asserts exactly one <h1>. - specs/accessibility-conformance.md rewritten: dark-theme scan moved out of 'known follow-ups' into the verified column; new sections for route-change focus, modal stack, sound preference, three-way sign cue, status live region, placeholder-as-label fix list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two operator-facing changes: - The backend base-URL env var is now plain `API` (was `VITE_API`). Vite's envPrefix is overridden to `'API'` in vite.config.ts so Vite still exposes the value to the client bundle at `import.meta.env.API`. Updated every consumer: .env.development / .env.production / .env.example, the ImportMetaEnv ambient type, services/api.ts, playwright.config.ts (webServer.env), and the two doc references. Verified by inspecting the built bundle: '/api/' inlined for production, the demo URL for dev. - CI runners and `engines.node` move from Node 20 to Node 26 (the current release as of April 2026; becomes LTS in October). Updates ci.yml, package.yml, package.json. Local dev still works on older Node since engine-strict isn't set in .npmrc — engines is a hint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Would be really cool to modernize the frontend a bit (also to get rid of stuff like |
Contributor
Author
|
Im currently trying with Claude to donit with symfony ux without Javascript:D |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.