import React from 'react'; import styles from './styles.module.css'; import CloseIcon from '@site/src/icons/close'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; const join = (...cs: string[]) => cs.join(' '); type CustomerType = 'open source' | 'paying'; type FormData = { score: number; comment: undefined | string; customerType: undefined | CustomerType; }; type InitialData = { currentStep: number; data: { score: undefined | number; comment: undefined | string; customerType: undefined | CustomerType; }; closedOrCompleted: boolean; }; type CompleteData = InitialData & { initialized: number; }; const clearedData: InitialData = { currentStep: 1, data: { score: undefined, comment: undefined, customerType: undefined, }, closedOrCompleted: false, }; const localstorageKey = 'user-feedback-v1'; const populateData = (initialData: InitialData): CompleteData => { // if we get seed data, use that. Otherwise, check if the last entry in // localstorage was completed. If not, use that as base. const getSeedData = () => { if (initialData) { return initialData; } const userFeedbackLog = getUserDataRecord(); if (userFeedbackLog) { const mostRecentTimestamp = Math.max( ...Object.keys(userFeedbackLog).map(parseInt), ); const mostRecent = userFeedbackLog[mostRecentTimestamp]; if (mostRecent && !mostRecent.closedOrCompleted) { return mostRecent; } } return {}; }; const seedData = getSeedData(); return { currentStep: 1, ...seedData, data: { score: undefined, comment: undefined, customerType: undefined, ...seedData?.data, }, initialized: Date.now(), }; }; const getUserDataRecord = () => JSON.parse(localStorage.getItem(localstorageKey)); const storeData = (data: CompleteData) => { const existingData = getUserDataRecord(); localStorage.setItem( localstorageKey, JSON.stringify({ ...existingData, [data.initialized]: data, }), ); }; type Message = | { kind: 'close' } | { kind: 'completed' } | { kind: 'reset' } | { kind: 'set score'; data: number } | { kind: 'set comment'; data: string } | { kind: 'set customer type'; data: CustomerType } | { kind: 'step forward' } | { kind: 'step back' }; const stateReducer = (state: CompleteData, message: Message) => { switch (message.kind) { case 'close': return { ...state, closedOrCompleted: true }; case 'completed': return { ...state, closedOrCompleted: true }; case 'reset': return { ...populateData(clearedData), closedOrCompleted: false }; case 'set score': return { ...state, data: { ...state.data, score: message.data }, }; case 'set comment': return { ...state, data: { ...state.data, comment: message.data }, }; case 'set customer type': return { ...state, data: { ...state.data, customerType: message.data }, }; case 'step forward': return { ...state, currentStep: Math.min(state.currentStep + 1, 4), }; case 'step back': return { ...state, currentStep: Math.max(state.currentStep - 1, 1), }; } }; type Props = { seedData?: InitialData; open?: boolean; }; export const FeedbackWrapper: React.FC = ({ seedData, open }) => { const { siteConfig: { customFields }, } = useDocusaurusContext(); const feedbackTargetUrl: string | undefined = (customFields?.unleashFeedbackTargetUrl as string | undefined) ?? (typeof process !== 'undefined' && process?.env?.UNLEASH_FEEDBACK_TARGET_URL); const [feedbackIsOpen, setFeedbackIsOpen] = React.useState(open); const [manuallyOpened, setManuallyOpened] = React.useState(open); const [state, dispatch] = React.useReducer( stateReducer, seedData, populateData, ); const close = () => dispatch({ kind: 'close' }); if (feedbackIsOpen) { storeData(state); } const stepForward = () => { dispatch({ kind: 'step forward' }); }; const stepBack = () => { dispatch({ kind: 'step back' }); }; const setScore = (score: number) => dispatch({ kind: 'set score', data: score }); const setComment = (comment: string) => dispatch({ kind: 'set comment', data: comment }); const setCustomerType = (customerType: CustomerType) => dispatch({ kind: 'set customer type', data: customerType }); const submitFeedback = (data: FormData) => { if (feedbackTargetUrl) { fetch(feedbackTargetUrl, { method: 'post', body: JSON.stringify({ data: { ...data, openedManually: manuallyOpened, currentPage: location.pathname, }, }), headers: { 'content-type': 'application/json', }, }) .then(async (res) => res.ok ? console.log('Success! Feedback was registered.') : console.warn( `Oh, no! The feedback registration failed: ${await res.text()}`, ), ) .catch((e) => console.error( 'Oh, no! The feedback registration failed:', e, ), ); } else { console.warn( 'No target url specified for feedback. Not doing anything.', ); } dispatch({ kind: 'completed' }); stepForward(); }; const visuallyHidden = (stepNumber: number) => state.currentStep !== stepNumber; const isHidden = (stepNumber: number) => !feedbackIsOpen || visuallyHidden(stepNumber); const Step1 = () => { const hidden = isHidden(1); const [newValue, setNewValue] = React.useState(state.data.score); return (
{ e.preventDefault(); setScore(newValue); stepForward(); }} aria-hidden={hidden} >

On a scale from 1 to 5 where 1 is very unsatisfied and 5 is very satisfied, {' '} How would you rate your overall satisfaction with the Unleash documentation?

{[1, 2, 3, 4, 5].map((n, i) => ( { const value = parseInt( e.target.value, ); setNewValue(value); }} autoFocus={ manuallyOpened ? state.data.score ? state.data.score === n : i === 0 : false } /> ))}
); }; const Step2 = () => { const hidden = isHidden(2); const textareaId = 'feedback-comment-input'; const saveComment = () => setComment( (document.getElementById(textareaId) as HTMLTextAreaElement) .value, ); return (
{ e.preventDefault(); saveComment(); stepForward(); }} >
); }; const Step3 = () => { const hidden = isHidden(3); const [value, setValue] = React.useState( state.data.customerType, ); return (
{ e.preventDefault(); setCustomerType(value); // To ensure that we get the correct customer type included. // We can't rely on the reducer to set it because it won't // happen until the component re-renders, causing customer // type to have an old or empty value. const finalState = stateReducer(state, { kind: 'set customer type', data: value, }); submitFeedback(finalState.data); }} >
Finally, are you a paying customer or an open source customer of Unleash?
{[ ['a', 'paying', 'paying'], ['an', 'open source', 'opensource'], ].map(([article, customerType, key], i) => ( { setValue(customerType as CustomerType); }} /> ))}
); }; const Step4 = () => { const hidden = isHidden(4); return (

Thank you! 🙌

); }; return (
); }; export default FeedbackWrapper;