diff --git a/.env b/.env
index bc91f363..fdd477fc 100644
--- a/.env
+++ b/.env
@@ -2,21 +2,18 @@
# These values are overwritten by config.json when deployed.
# Base URL path for the enviroment
-PUBLIC_URL = '/dev/'
+VITE_BASE_URL = '/dev/'
# Name of enviroment for build
-REACT_APP_KBASE_ENV=ci-europa
+VITE_KBASE_ENV=ci-europa
# Domain of enviroment for build
-REACT_APP_KBASE_DOMAIN=ci-europa.kbase.us
-# The following must be a subdomain of REACT_APP_KBASE_DOMAIN
-REACT_APP_KBASE_LEGACY_DOMAIN=legacy.ci-europa.kbase.us
+VITE_KBASE_DOMAIN=ci-europa.kbase.us
+# The following must be a subdomain of VITE_KBASE_DOMAIN
+VITE_KBASE_LEGACY_DOMAIN=legacy.ci-europa.kbase.us
# Backup cookie name and domain, empty if unused by enviroment
-REACT_APP_KBASE_BACKUP_COOKIE_NAME = 'test_kbase_backup_session'
-REACT_APP_KBASE_BACKUP_COOKIE_DOMAIN = 'localhost'
+VITE_KBASE_BACKUP_COOKIE_NAME = 'test_kbase_backup_session'
+VITE_KBASE_BACKUP_COOKIE_DOMAIN = 'localhost'
# Comma-separated list of allowed external redirect domains (supports wildcards like *.berdl.kbase.us)
-REACT_APP_REDIRECT_WHITELIST = '*.kbase.us'
-
-EXTEND_ESLINT=true
-SKIP_PREFLIGHT_CHECK=true
+VITE_REDIRECT_WHITELIST = '*.kbase.us'
diff --git a/.eslintrc.json b/.eslintrc.json
index 7e4eba73..94c79080 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,7 +4,14 @@
"react-app",
"plugin:@typescript-eslint/recommended"
],
- "ignorePatterns": ["package-lock.json", "!.stylelintrc.json", "*.mdx"],
+ "ignorePatterns": [
+ "package-lock.json",
+ "!.stylelintrc.json",
+ "*.mdx",
+ "node_modules",
+ "build",
+ "storybook-static"
+ ],
"overrides": [
{
"files": ["package.json"],
@@ -16,6 +23,13 @@
{
"files": ["*.json"],
"rules": { "no-unused-expressions": ["off"] }
+ },
+ {
+ "files": ["**/*.stories.tsx", "**/setupTests.ts"],
+ "rules": {
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "no-console": "off"
+ }
}
],
"parser": "@typescript-eslint/parser",
diff --git a/.github/workflows/reusable_build-push.yml b/.github/workflows/reusable_build-push.yml
index d68782cd..6a5d0a01 100644
--- a/.github/workflows/reusable_build-push.yml
+++ b/.github/workflows/reusable_build-push.yml
@@ -34,9 +34,9 @@ jobs:
password: '${{ secrets.GHCR_TOKEN }}'
- name: Set up Node.js
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v4
with:
- node-version: '20'
+ node-version: '22'
- name: Install app
run: npm ci
@@ -45,7 +45,7 @@ jobs:
run: npm run lint:strict
- name: Run tests
- run: npm test . -- --coverage --verbose
+ run: npm test -- --run --coverage
- name: Run multi-enviroment build
run: ./scripts/build_deploy.sh
diff --git a/.gitignore b/.gitignore
index 18a6bbf2..77093396 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@
# production
/build
/deploy
+/storybook-static
+debug-storybook.log
# misc
.DS_Store
diff --git a/.nvmrc b/.nvmrc
index ee09fac7..42a1c98a 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v20.11.1
+v22.22.0
diff --git a/.storybook/main.js b/.storybook/main.js
deleted file mode 100644
index 1240c769..00000000
--- a/.storybook/main.js
+++ /dev/null
@@ -1,16 +0,0 @@
-module.exports = {
- "stories": [
- "../src/**/*.stories.mdx",
- "../src/**/*.stories.@(js|jsx|ts|tsx)"
- ],
- "addons": [
- "@storybook/addon-links",
- "@storybook/addon-essentials",
- "@storybook/addon-interactions",
- "@storybook/preset-create-react-app"
- ],
- "framework": "@storybook/react",
- "core": {
- "builder": "@storybook/builder-webpack5"
- }
-}
\ No newline at end of file
diff --git a/.storybook/main.ts b/.storybook/main.ts
new file mode 100644
index 00000000..ed1266fe
--- /dev/null
+++ b/.storybook/main.ts
@@ -0,0 +1,16 @@
+import type { StorybookConfig } from '@storybook/react-vite';
+
+const config: StorybookConfig = {
+ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
+ addons: [
+ '@storybook/addon-links',
+ '@storybook/addon-essentials',
+ '@storybook/addon-interactions',
+ ],
+ framework: {
+ name: '@storybook/react-vite',
+ options: {},
+ },
+};
+
+export default config;
diff --git a/.storybook/manager.js b/.storybook/manager.ts
similarity index 53%
rename from .storybook/manager.js
rename to .storybook/manager.ts
index 0bca8d80..2e250bc1 100644
--- a/.storybook/manager.js
+++ b/.storybook/manager.ts
@@ -1,4 +1,4 @@
-import { addons } from '@storybook/addons';
+import { addons } from '@storybook/manager-api';
addons.setConfig({
isFullscreen: false,
@@ -6,7 +6,7 @@ addons.setConfig({
showPanel: true,
panelPosition: 'bottom',
enableShortcuts: true,
- isToolshown: true,
+ showToolbar: true,
theme: undefined,
selectedPanel: undefined,
initialActive: 'sidebar',
@@ -14,11 +14,4 @@ addons.setConfig({
showRoots: false,
collapsedRoots: ['other'],
},
- toolbar: {
- title: { hidden: false, },
- zoom: { hidden: false, },
- eject: { hidden: false, },
- copy: { hidden: false, },
- fullscreen: { hidden: false, },
- },
});
diff --git a/.storybook/preview.js b/.storybook/preview.js
deleted file mode 100644
index 148f25c3..00000000
--- a/.storybook/preview.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import '../src/index.scss';
-import { Provider } from 'react-redux';
-import { BrowserRouter as Router } from 'react-router-dom';
-import { createTestStore } from '../src/app/store';
-
-export const parameters = {
- actions: { argTypesRegex: '^on[A-Z].*' },
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/,
- },
- },
- options: {
- storySort: (a, b) => {
- const keyA = a[1].title;
- const keyB = b[1].title;
- return keyA === keyB
- ? 0
- : keyA.localeCompare(keyB, undefined, { numeric: true });
- },
- },
-};
-
-export const decorators = [
- (Story) => (
-
-
-
-
-
- ),
-];
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
new file mode 100644
index 00000000..ec9148a7
--- /dev/null
+++ b/.storybook/preview.tsx
@@ -0,0 +1,36 @@
+import '../src/index.scss';
+import { Provider } from 'react-redux';
+import { BrowserRouter as Router } from 'react-router-dom';
+import { createTestStore } from '../src/app/store';
+import type { Preview } from '@storybook/react';
+
+const preview: Preview = {
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
+ options: {
+ storySort: (a, b) => {
+ const keyA = a.title;
+ const keyB = b.title;
+ return keyA === keyB
+ ? 0
+ : keyA.localeCompare(keyB, undefined, { numeric: true });
+ },
+ },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+
+
+ ),
+ ],
+};
+
+export default preview;
diff --git a/README.md b/README.md
index 186a87fa..8b8a8a14 100644
--- a/README.md
+++ b/README.md
@@ -5,22 +5,18 @@
This project manages the User Interface (UI) for KBase tools, not including the
narrative interface or documentation site [kbase.us](https://kbase.us).
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app)
-using the command `npx create-react-app ui-refresh-test --template typescript`.
-
-It also includes the following:
-
-- Redux (`@reduxjs/toolkit`) for state management
-- `react-router-dom` for routing
-- `prettier` for code formatting
-- extended eslint configuration and eslint/prettier integration
-- npm linting scripts (`lint`, `lint:fix`, `lint:strict`)
-- `husky` to enable a `lint:strict` precommit hook
-- `.nvmrc` specifying the node version
-- `.editorconfig` for cross-editor config defaults. See
- [editorconfig.org](https://editorconfig.org) for compatability
-- Storybook (`npm run storybook`) for dev docs, style examples, and component
- examples.
+## Tech Stack
+
+- **Build Tool**: [Vite](https://vitejs.dev/) - Fast, modern build tool with
+ native ES modules
+- **Framework**: [React 18](https://react.dev/) with TypeScript
+- **State Management**: [Redux Toolkit](https://redux-toolkit.js.org/) with
+ RTK Query
+- **Routing**: [React Router v6](https://reactrouter.com/)
+- **Styling**: [Sass](https://sass-lang.com/) modules with [Material-UI](https://mui.com/)
+- **Testing**: [Vitest](https://vitest.dev/) with React Testing Library
+- **Component Docs**: [Storybook 8](https://storybook.js.org/)
+- **Code Quality**: ESLint, Prettier, Husky pre-commit hooks
## Architectural Decision Records
@@ -53,65 +49,105 @@ Start the app:
npm start
```
-### Troubleshooting
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-- If you receive the following error message after running `npm start`:
-`Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.`
-then you may need to set the following environment variable:
-`DANGEROUSLY_DISABLE_HOST_CHECK=true`
-- If you are using a Mac with an M1 or M2 chip, you may run into an
-error caused by `canvas` and `node-gyp` after running `npm install`.
+## Available Scripts
- - First, make sure your global python version (`python --version`)
- is under 3.12.
- - If the install step still isn't working, try to install node-canvas
- from source by following the Installation: Mac OS X, HomeBrew steps below
- or on [this page](https://github.com/Automattic/node-canvas/wiki/Installation:-Mac-OS-X).
+### `npm start`
- - `brew install pkg-config cairo pango libpng jpeg giflib librsvg`
- - This python aspect of this issue may become obsolete once
- the `node-gyp` peer dependency is able to upgrade to v10+.
+Runs the app in development mode with hot module replacement (HMR).
+The page will reload instantly when you make edits.
-## Available Scripts
+### `npm test`
-In the project directory, you can run:
+Launches Vitest in watch mode. Press `a` to run all tests, `f` to run only
+failed tests, or `q` to quit.
-### `npm start`
+```sh
+npm test # Watch mode
+npm run test:ui # Vitest UI (browser-based test runner)
+npm run test:coverage # Run with coverage report
+```
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+### `npm run build`
-The page will reload if you make edits.\
-You will also see any lint errors in the console.
+Builds the app for production to the `build` folder. The build is minified
+and optimized for best performance.
-### `npm test`
+### `npm run lint`
+
+Runs ESLint, Prettier, Stylelint, and TypeScript checks.
+
+```sh
+npm run lint # Show errors/warnings
+npm run lint:fix # Auto-fix where possible
+npm run lint:strict # Fail on any errors/warnings (used in pre-commit hook)
+```
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests][running-tests] for more information.
+### `npm run storybook`
-[running-tests]: https://facebook.github.io/create-react-app/docs/running-tests
+Opens Storybook locally for component development and documentation.
+Watches `*.stories.tsx` files and launches a local server at
+[http://localhost:6006](http://localhost:6006).
-### `npm run build`
+```sh
+npm run storybook # Start dev server
+npm run build-storybook # Build static storybook
+```
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the
-best performance.
+## Development
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
+### Environment Variables
-See the section about
-[deployment](https://facebook.github.io/create-react-app/docs/deployment) for
-more information.
+Environment variables are prefixed with `VITE_` and accessed via `import.meta.env`:
-### `npm run lint`, `npm run lint:fix`, `npm run lint:strict`
+```typescript
+const domain = import.meta.env.VITE_KBASE_DOMAIN;
+const isDev = import.meta.env.MODE === 'development';
+```
-Runs eslint/prettier and shows errors/warnings. `npm run lint:fix` will fix
-files in-place where possible. `npm run lint:strict` will fail with any
-errors/warnings and is used as a pre-commit hook.
+See `.env` files for available variables.
-### `npm run storybook`
+### API Proxy
+
+In development, requests to `/services/*` are proxied to the KBase API
+(configured in `vite.config.ts`). This avoids CORS issues during local
+development.
+
+### Network Access
+
+To access the dev server from other devices on your network
+(e.g., mobile testing):
+
+```sh
+npm start -- --host
+```
+
+Then access via your machine's IP address or hostname.
+
+## Project Structure
+
+```
+src/
+├── app/ # App setup, routes, store
+├── common/ # Shared components, hooks, utilities
+│ ├── api/ # RTK Query API definitions
+│ └── components/ # Reusable UI components
+├── features/ # Feature-based modules
+│ ├── auth/ # Authentication
+│ ├── collections/ # Data collections
+│ ├── navigator/ # Narrative browser
+│ └── ...
+├── stories/ # Storybook stories
+└── test/ # Test utilities and mocks
+```
+
+## Contributing
+
+1. Create a feature branch from `main`
+2. Make your changes
+3. Ensure tests pass: `npm test`
+4. Ensure linting passes: `npm run lint:strict`
+5. Submit a pull request
-Opens storybook locally. Builds and watches `*.stories.[tsx|mdx]` files and
-launches a local storybook server. The storybook contains component examples
-and other dev documentation.
+The pre-commit hook will run `lint:strict` automatically.
diff --git a/public/index.html b/index.html
similarity index 95%
rename from public/index.html
rename to index.html
index a63cf9bd..41507de4 100644
--- a/public/index.html
+++ b/index.html
@@ -10,7 +10,7 @@
gtag('config', 'G-KXZCE6YQFZ', { send_page_view: false });
-
+
@@ -35,5 +35,6 @@