-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(admin): added net promoter score in botpress (#11202)
* Basic NPS functionality without text tracking feat(nps): added comment functionnality for the netpromotingscore feat(nps): use realtime feat(nps): use time to display the nps form * use ms library for millisecond time * added traduction to missing traduction * Added Toaster and close banner when comments are added * Created a Folder for the Net Promoter application * Modified Prompt net promoter score * Updates the initial delay The three times are now confirmed with SK. * Modified traduction in english * fix(translation): modified one translation Co-authored-by: Patrick <87815239+ptrckbp@users.noreply.github.com> Co-authored-by: Samuel Massé <samuelmasse4@gmail.com>
- Loading branch information
1 parent
bba2dbc
commit fbbf064
Showing
16 changed files
with
468 additions
and
1 deletion.
There are no files selected for viewing
51 changes: 51 additions & 0 deletions
51
packages/ui-admin/src/app/NetPromoterScore/NetPromotingScore/NPS.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { lang } from 'botpress/shared' | ||
import React, { ReactNode } from 'react' | ||
import NPSScale from './components/NPSScale' | ||
import styles from './style.scss' | ||
|
||
interface Props { | ||
animated?: boolean | ||
dismissed?: boolean | ||
score?: number | null | ||
question?: string | ||
scaleWorstLabel?: string | ||
scaleBestLabel?: string | ||
onSubmit?: (score: number) => void | ||
onDismissed?: () => void | ||
children?: ReactNode | ||
} | ||
export function NPS({ | ||
animated = true, | ||
question = lang.tr('admin.netPromotingScore.question'), | ||
dismissed, | ||
score = null, | ||
scaleWorstLabel, | ||
scaleBestLabel, | ||
onSubmit, | ||
onDismissed, | ||
children = <p>{lang.tr('admin.netPromotingScore.feedback')}</p> | ||
}: Props) { | ||
const handleDismiss = () => { | ||
onDismissed && onDismissed() | ||
} | ||
const handleSubmit = (score: number) => { | ||
onSubmit && onSubmit(score) | ||
} | ||
|
||
return dismissed ? null : ( | ||
<div className={`${styles.root} ${animated ? styles.animated : ''}`}> | ||
<button className={styles.close} onClick={handleDismiss}> | ||
✕ | ||
</button> | ||
|
||
{score ? ( | ||
<div className={styles.inner}>{children}</div> | ||
) : ( | ||
<div className={styles.inner}> | ||
<p className={styles.message}>{question}</p> | ||
<NPSScale worstLabel={scaleWorstLabel} bestLabel={scaleBestLabel} score={score} onSubmit={handleSubmit} /> | ||
</div> | ||
)} | ||
</div> | ||
) | ||
} |
This file contains 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
83 changes: 83 additions & 0 deletions
83
...min/src/app/NetPromoterScore/NetPromotingScore/components/NPSAdditionComment/NPSModal.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { Button, Dialog, FormGroup, TextArea, Classes, Intent } from '@blueprintjs/core' | ||
import { lang, toast } from 'botpress/shared' | ||
import React, { FC, useEffect, useState } from 'react' | ||
import { trackEvent } from '../../SegmentHandler' | ||
|
||
interface Props { | ||
modalValue: boolean | ||
onChange: any | ||
} | ||
|
||
const CreateNPSModalComment: FC<Props> = props => { | ||
const [isNPSModalOpen, setIsNPSModalOpen] = useState(props.modalValue) | ||
const [comment, setComment] = useState('') | ||
const [isButtonDisabled, setIsButtonDisabled] = useState(true) | ||
|
||
useEffect(() => { | ||
if (comment !== '' && comment.length > 5) { | ||
return setIsButtonDisabled(false) | ||
} | ||
setIsButtonDisabled(true) | ||
}, [comment]) | ||
|
||
const createComment = e => { | ||
try { | ||
void trackEvent('nps_comment', { npsComment: comment }) | ||
toggleDialog(e) | ||
toast.success(lang.tr('admin.netPromotingScore.addComment')) | ||
} catch { | ||
toast.failure(lang.tr('admin.netPromoting.error')) | ||
} | ||
} | ||
|
||
const toggleDialog = e => { | ||
setIsNPSModalOpen(!isNPSModalOpen) | ||
e.target.value = isNPSModalOpen | ||
props.onChange(e) | ||
} | ||
|
||
return ( | ||
<Dialog | ||
title={lang.tr('admin.netPromotingScore.title')} | ||
icon="add" | ||
isOpen={isNPSModalOpen} | ||
onClose={toggleDialog} | ||
transitionDuration={0} | ||
canOutsideClickClose={false} | ||
> | ||
<form> | ||
<div className={Classes.DIALOG_BODY}> | ||
<FormGroup | ||
label={lang.tr('admin.netPromotingScore.modalTitle')} | ||
labelFor="comment" | ||
labelInfo="*" | ||
helperText={lang.tr('admin.netPromotingScore.helper')} | ||
> | ||
<TextArea | ||
id="input-comment" | ||
fill={true} | ||
growVertically={true} | ||
intent={Intent.PRIMARY} | ||
large={true} | ||
value={comment} | ||
onChange={event => setComment(event.target.value)} | ||
/> | ||
</FormGroup> | ||
</div> | ||
<div className={Classes.DIALOG_FOOTER}> | ||
<div className={Classes.DIALOG_FOOTER_ACTIONS}> | ||
<Button | ||
text={lang.tr('admin.netPromotingScore.button')} | ||
type="submit" | ||
onClick={createComment} | ||
intent={Intent.PRIMARY} | ||
disabled={isButtonDisabled} | ||
/> | ||
</div> | ||
</div> | ||
</form> | ||
</Dialog> | ||
) | ||
} | ||
|
||
export default CreateNPSModalComment |
36 changes: 36 additions & 0 deletions
36
...-admin/src/app/NetPromoterScore/NetPromotingScore/components/NPSAdditionComment/index.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Button } from '@blueprintjs/core' | ||
import { lang } from 'botpress/shared' | ||
import React, { FC, useState } from 'react' | ||
import NPSModal from './NPSModal' | ||
|
||
import style from './style.scss' | ||
|
||
interface Props { | ||
onDismissed?: () => void | ||
} | ||
const NPSAdditionComment: FC<Props> = props => { | ||
const [showForm, setShowForm] = useState(false) | ||
const openModal = e => { | ||
e.preventDefault() | ||
setShowForm(!showForm) | ||
} | ||
const handleDismiss = () => { | ||
props.onDismissed && props.onDismissed() | ||
} | ||
const handleChange = e => { | ||
setShowForm(!e.target.value) | ||
handleDismiss() | ||
} | ||
|
||
return ( | ||
<div className={style.flexBox}> | ||
<p>{lang.tr('admin.netPromotingScore.feedback')}</p> | ||
<Button intent="primary" onClick={openModal}> | ||
{lang.tr('admin.netPromotingScore.moreContent')} | ||
</Button> | ||
{showForm && <NPSModal modalValue={showForm} onChange={handleChange} />} | ||
</div> | ||
) | ||
} | ||
|
||
export default NPSAdditionComment |
7 changes: 7 additions & 0 deletions
7
...admin/src/app/NetPromoterScore/NetPromotingScore/components/NPSAdditionComment/style.scss
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.flexBox { | ||
display: flex; | ||
} | ||
|
||
.flexBox > * { | ||
flex: 1 1 auto; | ||
} |
7 changes: 7 additions & 0 deletions
7
.../src/app/NetPromoterScore/NetPromotingScore/components/NPSAdditionComment/style.scss.d.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// This file is automatically generated. | ||
// Please do not change this file! | ||
interface CssExports { | ||
'flexBox': string; | ||
} | ||
export const cssExports: CssExports; | ||
export default cssExports; |
55 changes: 55 additions & 0 deletions
55
packages/ui-admin/src/app/NetPromoterScore/NetPromotingScore/components/NPSScale/index.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { lang } from 'botpress/shared' | ||
import React from 'react' | ||
import styles from './style.scss' | ||
|
||
const MIN = 0 | ||
const MAX = 10 | ||
|
||
interface Props { | ||
score: number | null | ||
worstLabel?: string | ||
bestLabel?: string | ||
onSubmit?: (value: number) => void | ||
} | ||
export default function NPSScale({ | ||
score, | ||
worstLabel = lang.tr('admin.netPromotingScore.worstLabel'), | ||
bestLabel = lang.tr('admin.netPromotingScore.bestLabel'), | ||
onSubmit | ||
}: Props) { | ||
const [npsScore, setNpsScore] = React.useState<number | null>(score) | ||
const handleMouseEnter = (value: number) => { | ||
setNpsScore(value) | ||
} | ||
const handleMouseLeave = () => { | ||
setNpsScore(null) | ||
} | ||
const handleClick = (value: number) => { | ||
onSubmit && onSubmit(value) | ||
} | ||
return ( | ||
<div className={styles.root}> | ||
<div> | ||
{range(MIN, MAX).map(i => ( | ||
<div | ||
key={i} | ||
className={`${styles.value} ${npsScore !== null && npsScore >= i ? styles.selected : ''}`} | ||
onMouseEnter={() => handleMouseEnter(i)} | ||
onMouseLeave={handleMouseLeave} | ||
onClick={() => handleClick(i)} | ||
> | ||
<div>{i}</div> | ||
</div> | ||
))} | ||
</div> | ||
<div className={styles.legend}> | ||
<div className={`${styles.label} ${styles.left}`}>{worstLabel}</div> | ||
<div className={`${styles.label} ${styles.right}`}>{bestLabel}</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
function range(start: number, end: number) { | ||
return Array.from({ length: end - start + 1 }).map((_, idx) => start + idx) | ||
} |
48 changes: 48 additions & 0 deletions
48
packages/ui-admin/src/app/NetPromoterScore/NetPromotingScore/components/NPSScale/style.scss
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
.root { | ||
width: auto; | ||
max-width: 418px; | ||
margin: 0px auto; | ||
} | ||
.value { | ||
padding: 0px 3px; | ||
display: inline-block; | ||
} | ||
|
||
.value div { | ||
background: #f2f5fd; | ||
width: 32px; | ||
height: 32px; | ||
line-height: 32px; | ||
border-radius: 32px; | ||
cursor: pointer; | ||
transition: 0.15s ease all; | ||
color: #999; | ||
} | ||
.selected div { | ||
background: #3884ff; | ||
color: #fff; | ||
} | ||
.selected div { | ||
background: #3884ff; | ||
color: #fff; | ||
} | ||
.value:hover div { | ||
transform: scale(1.25); | ||
} | ||
|
||
.legend { | ||
display: flex; | ||
margin-top: 12px; | ||
} | ||
|
||
.label { | ||
flex: 1; | ||
color: #999; | ||
font-size: 12px; | ||
} | ||
.left { | ||
text-align: left; | ||
} | ||
.right { | ||
text-align: right; | ||
} |
13 changes: 13 additions & 0 deletions
13
...s/ui-admin/src/app/NetPromoterScore/NetPromotingScore/components/NPSScale/style.scss.d.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// This file is automatically generated. | ||
// Please do not change this file! | ||
interface CssExports { | ||
'label': string; | ||
'left': string; | ||
'legend': string; | ||
'right': string; | ||
'root': string; | ||
'selected': string; | ||
'value': string; | ||
} | ||
export const cssExports: CssExports; | ||
export default cssExports; |
2 changes: 2 additions & 0 deletions
2
packages/ui-admin/src/app/NetPromoterScore/NetPromotingScore/index.tsx
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import { NPS } from './NPS' | ||
export default NPS |
65 changes: 65 additions & 0 deletions
65
packages/ui-admin/src/app/NetPromoterScore/NetPromotingScore/style.scss
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
.root { | ||
position: fixed; | ||
bottom: 0px; | ||
left: 0px; | ||
right: 0px; | ||
border-top: 1px solid #e5e5e5; | ||
background: #fff; | ||
box-shadow: 0px -10px 10px rgba(200, 200, 200, 0.08); | ||
display: flex; | ||
align-items: flex-start; | ||
flex-direction: row-reverse; | ||
} | ||
.animated { | ||
animation-duration: 2s; | ||
animation-name: NPSInput-slidein; | ||
} | ||
.close { | ||
position: absolute; | ||
top: 10px; | ||
right: 10px; | ||
background: transparent; | ||
outline: none; | ||
display: inline-block; | ||
zoom: 1; | ||
line-height: normal; | ||
white-space: nowrap; | ||
vertical-align: baseline; | ||
text-align: center; | ||
cursor: pointer; | ||
user-select: none; | ||
font-family: inherit; | ||
font-size: 100%; | ||
padding: 0.5em 1em; | ||
text-decoration: none; | ||
border: 0; | ||
opacity: 0.4; | ||
font-size: 16px; | ||
} | ||
|
||
.close:hover { | ||
opacity: 1; | ||
} | ||
|
||
.inner { | ||
width: 100%; | ||
max-width: 1000px; | ||
margin: 20px auto; | ||
text-align: center; | ||
} | ||
|
||
.message { | ||
margin: 0px; | ||
margin-bottom: 15px; | ||
font-size: 16px; | ||
} | ||
|
||
@keyframes NPSInput-slidein { | ||
from { | ||
bottom: -100%; | ||
} | ||
|
||
to { | ||
bottom: 0px; | ||
} | ||
} |
Oops, something went wrong.