diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index ade265f42e..c53c7f1942 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -53,9 +53,6 @@ module.exports = { markdown: { mermaid: true }, customFields: { // expose env vars etc here - unleashProxyUrl: process.env.UNLEASH_PROXY_URL, - unleashProxyClientKey: process.env.UNLEASH_PROXY_CLIENT_KEY, - unleashFeedbackTargetUrl: process.env.UNLEASH_FEEDBACK_TARGET_URL, environment: process.env.NODE_ENV, }, themeConfig: { diff --git a/website/src/components/UserFeedback/UserFeedback.stories.jsx b/website/src/components/UserFeedback/UserFeedback.stories.jsx deleted file mode 100644 index fc6132fb02..0000000000 --- a/website/src/components/UserFeedback/UserFeedback.stories.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { initialData, FeedbackWrapper } from './index'; - -export default { - title: 'User feedback component', - component: FeedbackWrapper, -}; - -const Template = (args) => ; - -export const Step1 = Template.bind({}); -Step1.args = { - open: true, - seedData: { - currentStep: 1, - }, -}; - -export const Step2 = Template.bind({}); -Step2.args = { - seedData: { - currentStep: 2, - }, - open: true, -}; - -export const Step3 = Template.bind({}); -Step3.args = { - seedData: { - currentStep: 3, - }, - open: true, -}; - -export const Step4 = Template.bind({}); -Step4.args = { - seedData: { - currentStep: 4, - }, - open: true, -}; - -export const WithLocalStorage = Template.bind({}); -WithLocalStorage.args = { - open: true, -}; - -export const Closed = Template.bind({}); diff --git a/website/src/components/UserFeedback/index.tsx b/website/src/components/UserFeedback/index.tsx deleted file mode 100644 index cdb86f2ec7..0000000000 --- a/website/src/components/UserFeedback/index.tsx +++ /dev/null @@ -1,531 +0,0 @@ -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; diff --git a/website/src/components/UserFeedback/styles.module.css b/website/src/components/UserFeedback/styles.module.css deleted file mode 100644 index 68784bfa54..0000000000 --- a/website/src/components/UserFeedback/styles.module.css +++ /dev/null @@ -1,244 +0,0 @@ -.user-feedback-container { - --outline-style: 2px solid var(--ifm-color-primary); - --row-gap: 1rem; - --element-horizontal-gap: 1rem; - - --animation-duration: 0.25s; - --fade-out-transition: opacity var(--animation-duration); - --fade-in-transition: opacity var(--animation-duration) - calc(var(--animation-duration) / 2); -} - -@media screen and (prefers-reduced-motion: reduced) { - .user-feedback-container { - --animation-duration: 0; - } -} - -.user-feedback { - width: 100%; - position: fixed; - background: var(--ifm-background-color); - bottom: 0; - border: var(--ifm-global-border-width) solid var(--unleash-color-gray); - border-radius: var(--ifm-global-radius) var(--ifm-global-radius) 0 0; - box-shadow: var(--ifm-global-shadow-lw); - padding: var(--ifm-spacing-vertical) var(--ifm-spacing-horizontal); - text-align: center; - transition: var(--fade-in-transition); -} - -.user-feedback fieldset { - border: none; - margin: 0; - padding: 0; - width: 100%; -} - -:is(.user-feedback, .user-feedback fieldset) > * + * { - margin-top: var(--row-gap); -} - -.user-feedback button { - border: none; - border-radius: var(--ifm-global-radius); - padding: var(--ifm-spacing-vertical) calc(var(--ifm-spacing-horizontal) / 2); -} - -.user-feedback form > * + * { - margin-top: var(--row-gap); -} - -.hidden { - display: none; -} - -.user-feedback-container * { - outline-offset: 4px; -} - -.user-feedback-container *:focus-visible { - outline: var(--outline-style); -} - -.satisfaction-input-container { - display: flex; - flex-flow: wrap; - place-content: center; - align-items: center; - gap: var(--element-horizontal-gap); -} - -.satisfaction-input-inputs { - display: flex; - flex-flow: wrap; - place-content: center; - align-items: center; - gap: var(--element-horizontal-gap); -} - -.satisfaction-input-visual-label { - display: none; -} - -@media screen and (min-width: 800px) { - .satisfaction-input-visual-label { - display: inline; - } -} - -@media screen and (max-width: 400px) { - .satisfaction-input-inputs { - gap: calc(var(--element-horizontal-gap) / 2); - } -} - -.user-satisfaction-score-label { - display: grid; - place-content: center; - height: 3em; - width: 3em; - border: var(--ifm-global-border-width) solid currentColor; - border-radius: 50%; -} - -.user-satisfaction-score-input:focus-visible + .user-satisfaction-score-label { - outline: var(--outline-style); -} - -.user-satisfaction-score-label:hover { - color: var(--ifm-color-primary); -} - -.user-satisfaction-score-input:checked + label { - color: var(--ifm-color-primary); - background: var(--ifm-color-primary); - color: var(--ifm-color-primary-contrast-background); - border-color: var(--ifm-color-primary); -} - -.button-container { - margin-top: var(--row-gap); - display: flex; - flex-direction: row-reverse; - justify-content: flex-end; - gap: var(--element-horizontal-gap); -} - -button.close-button { - background: none; - border: none; - border-radius: 50%; - padding: 0; - aspect-ratio: 1; - height: 1em; - color: var(--ifm-font-color-base); -} - -.close-button:hover { - color: var(--ifm-color-primary); -} - -.close-button:active { - color: var(--ifm-color-primary-darker); -} - -.close-button-row { - display: flex; - justify-content: flex-end; -} - -.close-button svg { - fill: currentColor; -} - -.primary-button, -.user-feedback button[type='submit'] { - background-color: var(--ifm-color-primary); - color: var(--ifm-background-color); - padding-inline: calc(var(--ifm-spacing-horizontal) * 4); -} - -.primary-button:hover, -.user-feedback button[type='submit']:hover { - background-color: var(--ifm-color-primary-lighter); -} - -.primary-button:hover, -.user-feedback button[type='submit']:active { - background-color: var(--ifm-color-primary-dark); -} - -.button-secondary { - color: var(--ifm-color-primary); - background: none; -} - -.button-secondary:active { - color: var(--ifm-color-primary-darker); -} - -.button-secondary:hover { - color: var(--ifm-color-primary-lightest); -} - -.user-feedback textarea { - display: block; - width: 100%; - background-color: var(--ifm-background-color); - color: currentColor; - border-radius: var(--ifm-global-radius); - border: var(--ifm-global-border-width) solid var(--ifm-color-emphasis-400); - font-style: normal; - font-family: inherit; - padding: var(--ifm-spacing-vertical) var(--ifm-spacing-horizontal); -} - -.customer-type-inputs { - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: var(--ifm-spacing-horizontal); - accent-color: var(--ifm-color-primary); -} - -.open-feedback-button { - padding-block: var(--ifm-spacing-vertical); - padding-inline: var(--ifm-spacing-horizontal); - border-radius: 0 var(--ifm-global-radius) var(--ifm-global-radius) 0; - border: none; - position: fixed; - bottom: 25vh; - right: 0; - transition: var(--fade-in-transition); - transform: rotate(180deg); - writing-mode: vertical-lr; -} - -/* note: Chrome doesn't support writing-mode on buttons, so we need to add a - span for the text and change the writing-mode there. Simultaneously, Firefox - does some weird stuff with the padding of the text if writing-mode isn't - specified on the button itself, so we need to set that too. */ -.open-feedback-button > span { - writing-mode: vertical-lr; -} - -.invisible, -.open-feedback-button[disabled] { - opacity: 0; - transition: var(--fade-out-transition); - pointer-events: none; -} - -.form-section-container { - display: grid; - align-items: center; - max-width: 850px; - margin: auto; -} - -.form-section-container > * { - grid-column: 1; - grid-row: 1; - transition: var(--fade-in-transition); -} diff --git a/website/src/theme/Root.tsx b/website/src/theme/Root.tsx deleted file mode 100644 index c69cb7cb8d..0000000000 --- a/website/src/theme/Root.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import UserFeedback from '@site/src/components/UserFeedback'; -import { UnleashClient } from 'unleash-proxy-client'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; - -// Default implementation, that you can customize -function Root({ children }) { - const { - siteConfig: { customFields }, - } = useDocusaurusContext(); - - const unleashConfig = { - clientKey: customFields.unleashProxyClientKey as string, - url: customFields.unleashProxyUrl as string, - disableRefresh: true, - appName: `docs.getunleash.io-${customFields.environment}`, - }; - - const [showFeedback, setShowFeedback] = React.useState(false); - - if (typeof fetch !== 'undefined') { - try { - const unleash = new UnleashClient(unleashConfig); - unleash.on('ready', () => { - setShowFeedback(unleash.isEnabled('docs-feedback-survey-v1')); - }); - unleash.start(); - } catch (e) { - console.warn('Unable to initialize the Unleash client:', e.message); - } - } - - return ( - <> - {children} - {showFeedback && } - - ); -} - -export default Root;