@webority/insights-react 0.0.0-stage → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +104 -2
- package/dist/insights-react.js +1 -0
- package/dist/types/FeedbackButton.d.ts +20 -0
- package/dist/types/InsightsFeedbackProvider.d.ts +11 -0
- package/dist/types/NpsPrompt.d.ts +23 -0
- package/dist/types/client.d.ts +91 -0
- package/dist/types/context.d.ts +10 -0
- package/dist/types/index.d.ts +4 -0
- package/package.json +56 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright (c) Webority Technologies. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its source code are proprietary and confidential to
|
|
4
|
+
Webority Technologies. Use is restricted to Webority products and teams under
|
|
5
|
+
internal license. Redistribution outside Webority Technologies is prohibited.
|
package/README.md
CHANGED
|
@@ -1,3 +1,105 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @webority/insights-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Product feedback for a React portal: the one NPS question and a feedback button. Both post to the
|
|
4
|
+
product's feedback API, which the product's backend hosts through Webority.Insights. It is built on
|
|
5
|
+
`@webority/ui-react`, so it looks like the rest of the portal with no extra CSS.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @webority/insights-react @webority/ui-react react-bootstrap bootstrap@5.3.3
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`@webority/ui-react`, `react`, `react-dom`, `react-bootstrap` and `bootstrap` are peer
|
|
14
|
+
dependencies: the portal already has them. Pin `@webority/insights-react` to the same exact
|
|
15
|
+
version as `@webority/ui-react`.
|
|
16
|
+
|
|
17
|
+
## Set up the provider
|
|
18
|
+
|
|
19
|
+
Give the provider a client built on the portal's own signed-in HTTP instance. That instance already
|
|
20
|
+
carries the bearer token and refreshes it, so the feedback calls sign in the same way as every
|
|
21
|
+
other call.
|
|
22
|
+
|
|
23
|
+
```jsx
|
|
24
|
+
import { createInsightsFeedbackClient, InsightsFeedbackProvider } from "@webority/insights-react";
|
|
25
|
+
import { userClient } from "./services/api/clients";
|
|
26
|
+
|
|
27
|
+
const feedbackClient = createInsightsFeedbackClient(userClient);
|
|
28
|
+
|
|
29
|
+
export function App() {
|
|
30
|
+
return (
|
|
31
|
+
<InsightsFeedbackProvider client={feedbackClient}>
|
|
32
|
+
<Routes />
|
|
33
|
+
</InsightsFeedbackProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`createInsightsFeedbackClient(http, options)` accepts any axios-like instance. `options.prefix`
|
|
39
|
+
replaces the API prefix, which defaults to `/api/insights/feedback`. The client returns
|
|
40
|
+
`{ getNps(), answerNps(body), leaveComment(body) }`. Every method rejects with an
|
|
41
|
+
`InsightsFeedbackApiError` carrying `code`, `status` and a `message` written for the person, which
|
|
42
|
+
the components show as-is.
|
|
43
|
+
|
|
44
|
+
## Components
|
|
45
|
+
|
|
46
|
+
### `<NpsPrompt />`
|
|
47
|
+
|
|
48
|
+
The "how likely are you to recommend us" question. It asks the server once per mount whether the
|
|
49
|
+
question is due, and renders nothing while it loads, when it is not due, or when that read fails.
|
|
50
|
+
When it is due it shows a 0 to 10 scale, a comment box once a score is chosen, Send and Not now.
|
|
51
|
+
Send thanks the person. Not now is a permanent answer: the server records it and the question does
|
|
52
|
+
not come back.
|
|
53
|
+
|
|
54
|
+
```jsx
|
|
55
|
+
<NpsPrompt question="How likely are you to recommend us to a colleague?" />
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Prop | Default |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `question` | "How likely are you to recommend us to a colleague?" |
|
|
61
|
+
| `lowLabel` | "Not likely" |
|
|
62
|
+
| `highLabel` | "Very likely" |
|
|
63
|
+
| `commentLabel` | "What is the main reason for your score? (optional)" |
|
|
64
|
+
| `sendLabel` | "Send" |
|
|
65
|
+
| `notNowLabel` | "Not now" |
|
|
66
|
+
| `thanks` | "Thanks for your feedback." |
|
|
67
|
+
|
|
68
|
+
### `<FeedbackButton />`
|
|
69
|
+
|
|
70
|
+
A button that opens a drawer with one comment box. Send posts the comment with the page it was
|
|
71
|
+
written on, then thanks the person. The page path is read when the person sends, unless `pagePath`
|
|
72
|
+
is given.
|
|
73
|
+
|
|
74
|
+
```jsx
|
|
75
|
+
<FeedbackButton label="Feedback" />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Prop | Default |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `label` | "Feedback" |
|
|
81
|
+
| `title` | "Send us feedback" |
|
|
82
|
+
| `placeholder` | "Tell us what works and what does not" |
|
|
83
|
+
| `sendLabel` | "Send" |
|
|
84
|
+
| `thanks` | "Thanks, we read every message." |
|
|
85
|
+
| `pagePath` | the current `window.location.pathname` at send time |
|
|
86
|
+
|
|
87
|
+
## The wire contract
|
|
88
|
+
|
|
89
|
+
The prefix defaults to `/api/insights/feedback`. Errors are a JSON body `{ code, message }`.
|
|
90
|
+
|
|
91
|
+
| Method and route | Request body | Response |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `GET {prefix}/nps` | none | `{ due, trigger }` |
|
|
94
|
+
| `POST {prefix}/nps` | `{ trigger, score: number or null, comment?: string or null }` | 204. A `score` of `null` is a permanent "not now". |
|
|
95
|
+
| `POST {prefix}/comments` | `{ comment, pagePath?: string or null }` | 204 |
|
|
96
|
+
|
|
97
|
+
## The X-Webority-Surface header
|
|
98
|
+
|
|
99
|
+
Every POST sends the header `X-Webority-Surface: Web`, so the server can tell the portal's answers
|
|
100
|
+
from the other surfaces'. The GET does not send it.
|
|
101
|
+
|
|
102
|
+
## Server side
|
|
103
|
+
|
|
104
|
+
The server side is Webority.Insights.AspNetCore's `MapWeborityInsightsFeedbackEndpoints`, which maps
|
|
105
|
+
the three routes above under the product's prefix.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AppAlert as e,AppButton as t,AppCard as n,AppDrawer as r,AppScoreScale as i,AppTextarea as a}from"@webority/ui-react";import{createContext as o,useContext as s,useEffect as c,useMemo as l,useRef as u,useState as d}from"react";import{Fragment as f,jsx as p,jsxs as m}from"react/jsx-runtime";var h=`/api/insights/feedback`,g=`Something went wrong. Please try again.`,_={headers:{"X-Webority-Surface":`Web`}},v=class extends Error{constructor(e,t,n){super(t),this.name=`InsightsFeedbackApiError`,this.code=e,this.status=n}};async function y(e){if(e&&typeof Blob<`u`&&e instanceof Blob)try{return JSON.parse(await e.text())}catch{return null}if(typeof e==`string`)try{return JSON.parse(e)}catch{return null}return e??null}async function b(e){if(e instanceof v)return e;let t=e?.response;if(!t)return new v(`network`,g);let n=await y(t.data);return new v(typeof n?.code==`string`&&n.code?n.code:`unknown`,typeof n?.message==`string`&&n.message?n.message:g,t.status)}async function x(e){try{return(await e()).data}catch(e){throw await b(e)}}function S(e,t={}){let n=(t.prefix??h).replace(/\/+$/,``);return{getNps:()=>x(()=>e.get(`${n}/nps`)),answerNps:t=>x(()=>e.post(`${n}/nps`,t,_)),leaveComment:t=>x(()=>e.post(`${n}/comments`,t,_))}}var C=o(null);function w(){let e=s(C);if(!e)throw Error(`InsightsFeedbackProvider is missing.`);return e.client}var T=1e3;function E({label:n=`Feedback`,title:i=`Send us feedback`,placeholder:o=`Tell us what works and what does not`,sendLabel:s=`Send`,thanks:c=`Thanks, we read every message.`,pagePath:l}){let u=w(),[h,g]=d(!1),[_,v]=d(``),[y,b]=d(!1),[x,S]=d(!1),[C,E]=d(null);function D(){S(!1),E(null),g(!0)}async function O(){b(!0),E(null);try{await u.leaveComment({comment:_.trim(),pagePath:l??window.location.pathname}),v(``),S(!0)}catch(e){E(e)}finally{b(!1)}}let k=x?null:p(`div`,{className:`d-flex justify-content-end w-100`,children:p(t,{variant:`primary`,onClick:O,disabled:_.trim()===``||y,loading:y,children:s})});return m(f,{children:[p(t,{variant:`outline`,onClick:D,children:n}),p(r,{open:h,onClose:()=>g(!1),title:i,footer:k,children:x?p(`p`,{role:`status`,className:`mb-0`,children:c}):m(f,{children:[p(a,{"aria-label":i,placeholder:o,rows:5,maxLength:T,value:_,disabled:y,onChange:e=>v(e.target.value)}),C&&p(e,{variant:`danger`,children:C.message})]})})]})}function D({client:e,children:t}){let n=l(()=>({client:e}),[e]);return p(C.Provider,{value:n,children:t})}var O=1e3;function k({question:r=`How likely are you to recommend us to a colleague?`,lowLabel:o=`Not likely`,highLabel:s=`Very likely`,commentLabel:l=`What is the main reason for your score? (optional)`,sendLabel:f=`Send`,notNowLabel:h=`Not now`,thanks:g=`Thanks for your feedback.`}){let _=w(),[v,y]=d(`loading`),[b,x]=d(null),[S,C]=d(null),[T,E]=d(``),[D,k]=d(!1),[A,j]=d(null),M=u(!1);c(()=>{M.current||(M.current=!0,_.getNps().then(e=>{e?.due?(x(e.trigger),y(`asking`)):y(`closed`)},()=>y(`closed`)))},[_]);async function N(e){k(!0),j(null);try{return await _.answerNps({trigger:b,...e}),!0}catch(e){return j(e),!1}finally{k(!1)}}async function P(){let e=T.trim();await N({score:S,comment:e||null})&&y(`thanked`)}async function F(){await N({score:null})&&y(`closed`)}return v===`loading`||v===`closed`?null:v===`thanked`?p(n,{children:p(`p`,{role:`status`,className:`mb-0`,children:g})}):m(n,{children:[p(i,{name:`nps-score`,value:S,onChange:C,min:0,max:10,label:r,lowLabel:o,highLabel:s,disabled:D}),S!==null&&p(a,{label:l,rows:2,maxLength:O,value:T,disabled:D,onChange:e=>E(e.target.value)}),A&&p(e,{variant:`danger`,children:A.message}),m(`div`,{className:`d-flex gap-2 mt-3`,children:[p(t,{variant:`primary`,onClick:P,disabled:S===null||D,loading:D,children:f}),p(t,{variant:`secondary`,onClick:F,disabled:D,children:h})]})]})}export{E as FeedbackButton,v as InsightsFeedbackApiError,D as InsightsFeedbackProvider,k as NpsPrompt,S as createInsightsFeedbackClient};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A button that opens a drawer with one box for a comment. The comment is sent with the current
|
|
3
|
+
* page path, unless `pagePath` is given.
|
|
4
|
+
*
|
|
5
|
+
* @param {object} props
|
|
6
|
+
* @param {string} [props.label="Feedback"] The button text.
|
|
7
|
+
* @param {string} [props.title="Send us feedback"] The drawer's heading.
|
|
8
|
+
* @param {string} [props.placeholder="Tell us what works and what does not"]
|
|
9
|
+
* @param {string} [props.sendLabel="Send"]
|
|
10
|
+
* @param {string} [props.thanks="Thanks, we read every message."]
|
|
11
|
+
* @param {string|null} [props.pagePath] The page the comment is about. Defaults to the current path, read when sent.
|
|
12
|
+
*/
|
|
13
|
+
export default function FeedbackButton({ label, title, placeholder, sendLabel, thanks, pagePath, }: {
|
|
14
|
+
label?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
sendLabel?: string;
|
|
18
|
+
thanks?: string;
|
|
19
|
+
pagePath?: string | null;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Makes a feedback client available to the NPS question and the feedback button below it.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} props
|
|
5
|
+
* @param {import('./client.js').InsightsFeedbackClient} props.client From `createInsightsFeedbackClient(http)`.
|
|
6
|
+
* @param {import('react').ReactNode} props.children
|
|
7
|
+
*/
|
|
8
|
+
export default function InsightsFeedbackProvider({ client, children }: {
|
|
9
|
+
client: import('./client.js').InsightsFeedbackClient;
|
|
10
|
+
children: import('react').ReactNode;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one "how likely are you to recommend us" question. It asks once per mount, and only when
|
|
3
|
+
* the server says it is due. Send posts the score and an optional comment; Not now is a permanent
|
|
4
|
+
* answer the server records, so the question does not come back.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} props
|
|
7
|
+
* @param {string} [props.question="How likely are you to recommend us to a colleague?"]
|
|
8
|
+
* @param {string} [props.lowLabel="Not likely"]
|
|
9
|
+
* @param {string} [props.highLabel="Very likely"]
|
|
10
|
+
* @param {string} [props.commentLabel="What is the main reason for your score? (optional)"]
|
|
11
|
+
* @param {string} [props.sendLabel="Send"]
|
|
12
|
+
* @param {string} [props.notNowLabel="Not now"]
|
|
13
|
+
* @param {string} [props.thanks="Thanks for your feedback."]
|
|
14
|
+
*/
|
|
15
|
+
export default function NpsPrompt({ question, lowLabel, highLabel, commentLabel, sendLabel, notNowLabel, thanks, }: {
|
|
16
|
+
question?: string;
|
|
17
|
+
lowLabel?: string;
|
|
18
|
+
highLabel?: string;
|
|
19
|
+
commentLabel?: string;
|
|
20
|
+
sendLabel?: string;
|
|
21
|
+
notNowLabel?: string;
|
|
22
|
+
thanks?: string;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A failed feedback request. `message` is written for the person and is safe to show as-is.
|
|
3
|
+
*/
|
|
4
|
+
export declare class InsightsFeedbackApiError extends Error {
|
|
5
|
+
code: string;
|
|
6
|
+
status: number;
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} code Machine-readable reason from the API, or "network" when no response arrived.
|
|
9
|
+
* @param {string} message The sentence to show the person.
|
|
10
|
+
* @param {number} [status] HTTP status, when a response arrived.
|
|
11
|
+
*/
|
|
12
|
+
constructor(code: string, message: string, status?: number);
|
|
13
|
+
}
|
|
14
|
+
export type InsightsHttp = {
|
|
15
|
+
get: (url: string, config?: object) => Promise<{
|
|
16
|
+
data: any;
|
|
17
|
+
}>;
|
|
18
|
+
post: (url: string, data?: any, config?: object) => Promise<{
|
|
19
|
+
data: any;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
export type NpsState = {
|
|
23
|
+
/**
|
|
24
|
+
* Whether the question should be asked now.
|
|
25
|
+
*/
|
|
26
|
+
due: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The server's reason for asking; sent back with the answer.
|
|
29
|
+
*/
|
|
30
|
+
trigger: string;
|
|
31
|
+
};
|
|
32
|
+
export type NpsAnswer = {
|
|
33
|
+
/**
|
|
34
|
+
* The `trigger` the question was asked with.
|
|
35
|
+
*/
|
|
36
|
+
trigger: string;
|
|
37
|
+
/**
|
|
38
|
+
* 0 to 10, or null for a permanent "not now".
|
|
39
|
+
*/
|
|
40
|
+
score: number | null;
|
|
41
|
+
comment?: string | null;
|
|
42
|
+
};
|
|
43
|
+
export type CommentInput = {
|
|
44
|
+
comment: string;
|
|
45
|
+
/**
|
|
46
|
+
* The page the comment is about; the component sends the current path.
|
|
47
|
+
*/
|
|
48
|
+
pagePath?: string | null;
|
|
49
|
+
};
|
|
50
|
+
export type InsightsFeedbackClient = {
|
|
51
|
+
getNps: () => Promise<NpsState>;
|
|
52
|
+
answerNps: (body: NpsAnswer) => Promise<void>;
|
|
53
|
+
leaveComment: (body: CommentInput) => Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {object} InsightsHttp An axios-like HTTP client that already carries the bearer token.
|
|
57
|
+
* @property {(url: string, config?: object) => Promise<{ data: any }>} get
|
|
58
|
+
* @property {(url: string, data?: any, config?: object) => Promise<{ data: any }>} post
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {object} NpsState What the server says about the question for this person.
|
|
62
|
+
* @property {boolean} due Whether the question should be asked now.
|
|
63
|
+
* @property {string} trigger The server's reason for asking; sent back with the answer.
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {object} NpsAnswer
|
|
67
|
+
* @property {string} trigger The `trigger` the question was asked with.
|
|
68
|
+
* @property {number|null} score 0 to 10, or null for a permanent "not now".
|
|
69
|
+
* @property {string|null} [comment]
|
|
70
|
+
*/
|
|
71
|
+
/**
|
|
72
|
+
* @typedef {object} CommentInput
|
|
73
|
+
* @property {string} comment
|
|
74
|
+
* @property {string|null} [pagePath] The page the comment is about; the component sends the current path.
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {object} InsightsFeedbackClient
|
|
78
|
+
* @property {() => Promise<NpsState>} getNps
|
|
79
|
+
* @property {(body: NpsAnswer) => Promise<void>} answerNps
|
|
80
|
+
* @property {(body: CommentInput) => Promise<void>} leaveComment
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* Binds the feedback HTTP contract to the product's own authenticated client.
|
|
84
|
+
*
|
|
85
|
+
* @param {import('axios').AxiosInstance} http The product's signed-in axios client (web-core userClient).
|
|
86
|
+
* @param {{ prefix?: string }} [options] `prefix` overrides the API prefix, which defaults to /api/insights/feedback.
|
|
87
|
+
* @returns {InsightsFeedbackClient}
|
|
88
|
+
*/
|
|
89
|
+
export declare function createInsightsFeedbackClient(http: import('axios').AxiosInstance, options?: {
|
|
90
|
+
prefix?: string;
|
|
91
|
+
}): InsightsFeedbackClient;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @type {import('react').Context<{ client: import('./client.js').InsightsFeedbackClient } | null>} */
|
|
2
|
+
export declare const InsightsFeedbackContext: import('react').Context<{
|
|
3
|
+
client: import('./client.js').InsightsFeedbackClient;
|
|
4
|
+
} | null>;
|
|
5
|
+
/**
|
|
6
|
+
* The feedback client the nearest InsightsFeedbackProvider holds. Internal to the package.
|
|
7
|
+
*
|
|
8
|
+
* @returns {import('./client.js').InsightsFeedbackClient}
|
|
9
|
+
*/
|
|
10
|
+
export declare function useInsightsFeedbackClient(): import('./client.js').InsightsFeedbackClient;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createInsightsFeedbackClient, InsightsFeedbackApiError } from "./client.js";
|
|
2
|
+
export { default as FeedbackButton } from "./FeedbackButton.jsx";
|
|
3
|
+
export { default as InsightsFeedbackProvider } from "./InsightsFeedbackProvider.jsx";
|
|
4
|
+
export { default as NpsPrompt } from "./NpsPrompt.jsx";
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/insights-react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.65.0",
|
|
4
|
+
"description": "Webority product feedback for React portals: the NPS question and a feedback button that post to the product's Webority.Insights feedback API, built on @webority/ui-react.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"!dist/**/__tests__",
|
|
14
|
+
"!dist/**/*.test.*",
|
|
15
|
+
"!dist/**/*.map"
|
|
16
|
+
],
|
|
17
|
+
"main": "./dist/insights-react.js",
|
|
18
|
+
"module": "./dist/insights-react.js",
|
|
19
|
+
"types": "./dist/types/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/types/index.d.ts",
|
|
23
|
+
"default": "./dist/insights-react.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"bootstrap": "5.3.3",
|
|
30
|
+
"react": "^19.0.0",
|
|
31
|
+
"react-bootstrap": "^2.10.0",
|
|
32
|
+
"react-dom": "^19.0.0",
|
|
33
|
+
"@webority/ui-react": "0.65.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@testing-library/dom": "10.4.2",
|
|
37
|
+
"@testing-library/jest-dom": "7.0.1",
|
|
38
|
+
"@testing-library/react": "^16.3.0",
|
|
39
|
+
"@testing-library/user-event": "^14.6.1",
|
|
40
|
+
"@types/react": "^19.2.17",
|
|
41
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
42
|
+
"@vitest/coverage-v8": "5.0.1",
|
|
43
|
+
"bootstrap": "5.3.3",
|
|
44
|
+
"jsdom": "30.1.1",
|
|
45
|
+
"react": "^19.2.6",
|
|
46
|
+
"react-bootstrap": "^2.10.6",
|
|
47
|
+
"react-dom": "^19.2.6",
|
|
48
|
+
"typescript": "^7.0.2",
|
|
49
|
+
"vite": "^8.0.12",
|
|
50
|
+
"vitest": "5.0.1",
|
|
51
|
+
"@webority/ui-react": "0.65.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "vite build && tsc -p tsconfig.json",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"test:coverage": "vitest run --coverage"
|
|
58
|
+
}
|
|
6
59
|
}
|