diff --git a/packages/element/package.json b/packages/element/package.json index d9c2881c9..ebf18de3a 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -61,6 +61,7 @@ "@availity/mui-progress": "workspace:*", "@availity/mui-snackbar": "workspace:*", "@availity/mui-spaces": "workspace:*", + "@availity/mui-stash-receiver": "workspace:*", "@availity/mui-stepper": "workspace:*", "@availity/mui-table": "workspace:*", "@availity/mui-tabs": "workspace:*", diff --git a/packages/element/src/index.ts b/packages/element/src/index.ts index bed859150..02376033b 100644 --- a/packages/element/src/index.ts +++ b/packages/element/src/index.ts @@ -375,7 +375,7 @@ export { MoneyBillIcon, CartShoppingIcon, BuildingIcon, - HospitalIcon + HospitalIcon, } from '@availity/mui-icon'; // Layout @@ -534,3 +534,7 @@ export type { TypographyProps } from '@availity/mui-typography'; // Utils export { visuallyHidden } from '@availity/mui-utils'; + +// StashReceiver +export { StashReceiver, StashContext, useStashContext } from '@availity/mui-stash-receiver'; +export type { StashReceiverProps, StashContextType, StashData } from '@availity/mui-stash-receiver'; diff --git a/packages/mock/src/lib/handlers.ts b/packages/mock/src/lib/handlers.ts index a51808934..153c3e293 100644 --- a/packages/mock/src/lib/handlers.ts +++ b/packages/mock/src/lib/handlers.ts @@ -425,6 +425,11 @@ export const handlers = [ }); }), + // Stash + http.get(routes.STASH, ({ params }) => { + return HttpResponse.json({ memberId: 'member-123', sessionId: params.sessionId }); + }), + // Example http.post(routes.EXAMPLE, async ({ request }) => { await delayRequest(); diff --git a/packages/mock/src/lib/routes.ts b/packages/mock/src/lib/routes.ts index 1e7cadd57..7c74f699d 100644 --- a/packages/mock/src/lib/routes.ts +++ b/packages/mock/src/lib/routes.ts @@ -17,5 +17,8 @@ export const ATTACHMENTS_CLOUD_POST = '/cloud/web/appl/vault/upload/v1/resumable export const ATTACHMENTS_CLOUD_PATCH = '/cloud/web/appl/vault/upload/v1/resumable/:bucket/:location'; export const ATTACHMENTS_CLOUD_HEAD = '/cloud/web/appl/vault/upload/v1/resumable/:bucket/:location'; +// Stash +export const STASH = '/cloud/web/appl/stash/v1/session/data/:sessionId'; + // Misc export const EXAMPLE = '/api/v1/example'; diff --git a/packages/stash-receiver/CHANGELOG.md b/packages/stash-receiver/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/stash-receiver/README.md b/packages/stash-receiver/README.md new file mode 100644 index 000000000..8c2eb12ea --- /dev/null +++ b/packages/stash-receiver/README.md @@ -0,0 +1,61 @@ +# @availity/mui-stash-receiver + +> Availity MUI StashReceiver component to be used with @availity/element design system. + +[![Version](https://img.shields.io/npm/v/@availity/mui-stash-receiver.svg?style=for-the-badge)](https://www.npmjs.com/package/@availity/mui-stash-receiver) +[![NPM Downloads](https://img.shields.io/npm/dt/@availity/mui-stash-receiver.svg?style=for-the-badge)](https://www.npmjs.com/package/@availity/mui-stash-receiver) +[![Dependency Status](https://img.shields.io/librariesio/release/npm/@availity/mui-stash-receiver?style=for-the-badge)](https://github.com/Availity/element/blob/main/packages/mui-stash-receiver/package.json) + +## Documentation + +This package extends the MUI StashReceiver component: [MUI StashReceiver Docs](https://mui.com/components/stash-receiver/) + +Live demo and documentation in our [Storybook](https://availity.github.io/element/?path=/docs/components-stash-receiver-introduction--docs) + +Availity standards for design and usage can be found in the [Availity Design Guide](https://design.availity.com/2e36e50c7) + +## Installation + +### Import Through @availity/element (Recommended) + +#### NPM + +```bash +npm install @availity/element +``` + +#### Yarn + +```bash +yarn add @availity/element +``` + +### Direct Import + +#### NPM + +_This package has a few peer dependencies. Add `@mui/material` & `@emotion/react` to your project if not already installed._ + +```bash +npm install @availity/mui-stash-receiver +``` + +#### Yarn + +```bash +yarn add @availity/mui-stash-receiver +``` + +### Usage + +#### Import through @availity/element + +```tsx +import { StashReceiver } from '@availity/element'; +``` + +#### Direct import + +```tsx +import { StashReceiver } from '@availity/mui-stash-receiver'; +``` diff --git a/packages/stash-receiver/introduction.mdx b/packages/stash-receiver/introduction.mdx new file mode 100644 index 000000000..6d8996b55 --- /dev/null +++ b/packages/stash-receiver/introduction.mdx @@ -0,0 +1,9 @@ +import { Markdown, Meta } from '@storybook/addon-docs/blocks'; +import ReadMe from './README.md?raw'; +import CHANGELOG from './CHANGELOG.md?raw'; + + + +{ReadMe} + +{CHANGELOG} diff --git a/packages/stash-receiver/jest.config.js b/packages/stash-receiver/jest.config.js new file mode 100644 index 000000000..c862fb56c --- /dev/null +++ b/packages/stash-receiver/jest.config.js @@ -0,0 +1,7 @@ +const global = require('../../jest.config.global'); + +module.exports = { + ...global, + displayName: 'stash-receiver', + coverageDirectory: '../../coverage/stash-receiver', +}; diff --git a/packages/stash-receiver/package.json b/packages/stash-receiver/package.json new file mode 100644 index 000000000..aa6efa6d4 --- /dev/null +++ b/packages/stash-receiver/package.json @@ -0,0 +1,58 @@ +{ + "name": "@availity/mui-stash-receiver", + "version": "0.0.0", + "description": "Availity MUI StashReceiver Component - part of the @availity/element design system", + "keywords": [ + "react", + "typescript", + "availity", + "mui" + ], + "homepage": "https://availity.github.io/element/?path=/docs/components-stash-receiver-introduction--docs", + "bugs": { + "url": "https://github.com/Availity/element/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/Availity/element.git", + "directory": "packages/stash-receiver" + }, + "license": "MIT", + "author": "Availity Developers ", + "browser": "./dist/index.js", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "dev": "tsup src/index.ts --format esm,cjs --watch --dts", + "clean": "rm -rf dist", + "clean:nm": "rm -rf node_modules", + "publish": "yarn npm publish --tolerate-republish --access public", + "publish:canary": "yarn npm publish --access public --tag canary" + }, + "devDependencies": { + "@mui/material": "^7.3.4", + "axios": "^1.16.1", + "react": "19.2.0", + "react-dom": "19.2.0", + "tsup": "^8.4.0", + "typescript": "^5.4.5" + }, + "peerDependencies": { + "@mui/material": "^7.0.0", + "axios": ">=1.0.0", + "react": ">=17.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/stash-receiver/project.json b/packages/stash-receiver/project.json new file mode 100644 index 000000000..6ef183e72 --- /dev/null +++ b/packages/stash-receiver/project.json @@ -0,0 +1,41 @@ +{ + "name": "mui-stash-receiver", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/stash-receiver/src", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "options": { + "eslintConfig": ".eslintrc.json", + "silent": false, + "fix": false, + "cache": true, + "cacheLocation": "./node_modules/.cache/stash-receiver/.eslintcache", + "maxWarnings": -1, + "quiet": false, + "noEslintrc": false, + "hasTypeAwareRules": true, + "cacheStrategy": "metadata" + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/stash-receiver"], + "options": { + "jestConfig": "packages/stash-receiver/jest.config.js" + } + }, + "version": { + "executor": "@jscutlery/semver:version", + "options": { + "preset": "conventional", + "commitMessageFormat": "chore({projectName}): release version ${version} [skip ci]", + "tagPrefix": "@availity/{projectName}@", + "trackDeps": true, + "skipCommitTypes": ["docs"] + } + } + } +} diff --git a/packages/stash-receiver/src/index.ts b/packages/stash-receiver/src/index.ts new file mode 100644 index 000000000..04ea268d4 --- /dev/null +++ b/packages/stash-receiver/src/index.ts @@ -0,0 +1 @@ +export * from './lib/StashReceiver'; diff --git a/packages/stash-receiver/src/lib/StashReceiver.stories.tsx b/packages/stash-receiver/src/lib/StashReceiver.stories.tsx new file mode 100644 index 000000000..497f49f50 --- /dev/null +++ b/packages/stash-receiver/src/lib/StashReceiver.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { StashReceiver, useStashContext } from './StashReceiver'; + +const meta: Meta = { + title: 'Components/StashReceiver/StashReceiver', + component: StashReceiver, + tags: ['autodocs'], +}; + +export default meta; + +const StashDisplay = () => { + const { data, loading, error } = useStashContext(); + if (loading) return

Loading stash data...

; + if (error) return

Error: {error}

; + return
{JSON.stringify(data, null, 2)}
; +}; + +export const _StashReceiver: StoryObj = { + render: (args) => ( + + + + ), + args: { + sessionId: 'example-session-id', + }, +}; diff --git a/packages/stash-receiver/src/lib/StashReceiver.test.tsx b/packages/stash-receiver/src/lib/StashReceiver.test.tsx new file mode 100644 index 000000000..14733cc9b --- /dev/null +++ b/packages/stash-receiver/src/lib/StashReceiver.test.tsx @@ -0,0 +1,53 @@ +import '@testing-library/jest-dom'; +import { render, waitFor } from '@testing-library/react'; +import { http, HttpResponse } from 'msw'; +import { StashReceiver, useStashContext } from './StashReceiver'; + +// eslint-disable-next-line @nx/enforce-module-boundaries +import { server } from '../../../mock/src/lib/server'; + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + +const Consumer = () => { + const { data, loading, error } = useStashContext(); + if (loading) return loading; + if (error) return error: {error}; + return memberId: {String(data?.memberId)}; +}; + +describe('StashReceiver', () => { + it('shows loading state initially', () => { + const { getByText } = render( + + + + ); + expect(getByText('loading')).toBeInTheDocument(); + }); + + it('provides stash data to children after fetch', async () => { + const { getByText } = render( + + + + ); + await waitFor(() => expect(getByText('memberId: member-123')).toBeInTheDocument()); + }); + + it('provides error state when fetch fails', async () => { + server.use( + http.get('/cloud/web/appl/stash/v1/session/data/:sessionId', () => + HttpResponse.json({ message: 'Server Error' }, { status: 500 }) + ) + ); + + const { getByText } = render( + + + + ); + await waitFor(() => expect(getByText('error: Error fetching stash data.')).toBeInTheDocument()); + }); +}); diff --git a/packages/stash-receiver/src/lib/StashReceiver.tsx b/packages/stash-receiver/src/lib/StashReceiver.tsx new file mode 100644 index 000000000..e6ec2c7a7 --- /dev/null +++ b/packages/stash-receiver/src/lib/StashReceiver.tsx @@ -0,0 +1,56 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import axios from 'axios'; + +export type StashData = Record; + +export type StashContextType = { + /** The data returned from the Stash API. */ + data: StashData | undefined; + /** Whether the Stash API call is in progress. */ + loading: boolean; + /** Error message if the Stash API call failed. */ + error: string | undefined; +}; + +export type StashReceiverProps = { + /** The session ID used to fetch data from the Stash API. */ + sessionId: string; + /** Children rendered inside the provider. */ + children?: React.ReactNode; +}; + +export const STASH_API_URL = '/cloud/web/appl/stash/v1/session/data'; + +export const INITIAL_STATE: StashContextType = { + data: undefined, + loading: true, + error: undefined, +}; + +export const StashContext = createContext(INITIAL_STATE); + +export const useStashContext = () => useContext(StashContext); + +export const StashReceiver = ({ sessionId, children }: StashReceiverProps): React.JSX.Element => { + const [data, setData] = useState(undefined); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(undefined); + + useEffect(() => { + setLoading(true); + setError(undefined); + axios + .get(`${STASH_API_URL}/${sessionId}`) + .then((res) => { + setData(res.data); + }) + .catch(() => { + setError('Error fetching stash data.'); + }) + .finally(() => { + setLoading(false); + }); + }, [sessionId]); + + return {children}; +}; diff --git a/packages/stash-receiver/tsconfig.json b/packages/stash-receiver/tsconfig.json new file mode 100644 index 000000000..fcaa54641 --- /dev/null +++ b/packages/stash-receiver/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["."], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/stash-receiver/tsconfig.spec.json b/packages/stash-receiver/tsconfig.spec.json new file mode 100644 index 000000000..e4d456740 --- /dev/null +++ b/packages/stash-receiver/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node", "@testing-library/jest-dom"], + "allowJs": true + }, + "include": ["**/*.test.js", "**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 2316500ac..9adb05c5d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -63,6 +63,7 @@ "@availity/mui-progress": ["packages/progress/src/index.ts"], "@availity/mui-snackbar": ["packages/snackbar/src/index.ts"], "@availity/mui-spaces": ["packages/spaces/src/index.ts"], + "@availity/mui-stash-receiver": ["packages/stash-receiver/src/index.ts"], "@availity/mui-stepper": ["packages/stepper/src/index.ts"], "@availity/mui-table": ["packages/table/src/index.ts"], "@availity/mui-tabs": ["packages/tabs/src/index.ts"], diff --git a/yarn.lock b/yarn.lock index 0bae905e7..5eeff6a4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -128,6 +128,7 @@ __metadata: "@availity/mui-progress": "workspace:*" "@availity/mui-snackbar": "workspace:*" "@availity/mui-spaces": "workspace:*" + "@availity/mui-stash-receiver": "workspace:*" "@availity/mui-stepper": "workspace:*" "@availity/mui-table": "workspace:*" "@availity/mui-tabs": "workspace:*" @@ -991,6 +992,23 @@ __metadata: languageName: unknown linkType: soft +"@availity/mui-stash-receiver@workspace:*, @availity/mui-stash-receiver@workspace:packages/stash-receiver": + version: 0.0.0-use.local + resolution: "@availity/mui-stash-receiver@workspace:packages/stash-receiver" + dependencies: + "@mui/material": "npm:^7.3.4" + axios: "npm:^1.16.1" + react: "npm:19.2.0" + react-dom: "npm:19.2.0" + tsup: "npm:^8.4.0" + typescript: "npm:^5.4.5" + peerDependencies: + "@mui/material": ^7.0.0 + axios: ">=1.0.0" + react: ">=17.0.0" + languageName: unknown + linkType: soft + "@availity/mui-stepper@workspace:*, @availity/mui-stepper@workspace:packages/stepper": version: 0.0.0-use.local resolution: "@availity/mui-stepper@workspace:packages/stepper" @@ -7800,6 +7818,15 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10/21fb903e0917e5cb16591b4d0ef6a028a54b83ac30cd1fca58dece3d4e0990512a8723f9f83130d88a41e2af8b1f7be1386fda3ea2d181bb1a62155e75e95e23 + languageName: node + linkType: hard + "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.4 resolution: "agent-base@npm:7.1.4" @@ -8304,6 +8331,18 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.16.1": + version: 1.16.1 + resolution: "axios@npm:1.16.1" + dependencies: + follow-redirects: "npm:^1.16.0" + form-data: "npm:^4.0.5" + https-proxy-agent: "npm:^5.0.1" + proxy-from-env: "npm:^2.1.0" + checksum: 10/9b6218cf96321cfbbf8f160658d695367114bcf4fb62492bdc1ccd647f184b5c71ae400e5ecaaf41079bc561de2ecbaf1fec63f398b3ec53389beff7694df64c + languageName: node + linkType: hard + "axobject-query@npm:^4.1.0": version: 4.1.0 resolution: "axobject-query@npm:4.1.0" @@ -11652,6 +11691,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.16.0": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" + peerDependenciesMeta: + debug: + optional: true + checksum: 10/3fbe3d80b3b544c22705d837aa5d4a0d07a740d913534a2620b0a004c610af4148e3b58723536dd099aaa1c9d3a155964bde9665d6e5cb331460809a1fc572fd + languageName: node + linkType: hard + "for-each@npm:^0.3.3, for-each@npm:^0.3.5": version: 0.3.5 resolution: "for-each@npm:0.3.5" @@ -11671,7 +11720,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.4": +"form-data@npm:^4.0.4, form-data@npm:^4.0.5": version: 4.0.5 resolution: "form-data@npm:4.0.5" dependencies: @@ -12485,6 +12534,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^5.0.1": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10/f0dce7bdcac5e8eaa0be3c7368bb8836ed010fb5b6349ffb412b172a203efe8f807d9a6681319105ea1b6901e1972c7b5ea899672a7b9aad58309f766dcbe0df + languageName: node + linkType: hard + "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" @@ -17390,6 +17449,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10/fbbaf4dab2a6231dc9e394903a5f66f20475e36b734335790b46feb9da07c37d6b32e2c02e3e2ea4d4b23774c53d8562e5b7cc73282cb43f4a597b7eacaee2ee + languageName: node + linkType: hard + "psl@npm:^1.1.33": version: 1.15.0 resolution: "psl@npm:1.15.0"