diff --git a/web/src/assets/plushie.svg b/web/src/assets/plushie.svg new file mode 100644 index 0000000000..4c8433e81c --- /dev/null +++ b/web/src/assets/plushie.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/src/components/ContactUs/ContactUs.styled.ts b/web/src/components/ContactUs/ContactUs.styled.ts new file mode 100644 index 0000000000..daddc0d1ff --- /dev/null +++ b/web/src/components/ContactUs/ContactUs.styled.ts @@ -0,0 +1,73 @@ +import styled from 'styled-components'; +import {Modal as AntModal, Button, Typography} from 'antd'; +import Plushie from 'assets/plushie.svg'; + +export const Container = styled.div` + position: absolute; + right: 12px; + bottom: 12px; + cursor: pointer; +`; + +export const PlushieImage = styled.img.attrs({ + src: Plushie, +})``; + +export const PulseButtonContainer = styled.div` + position: absolute; + left: 2px; + top: -4px; +`; + +export const ModalFooter = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const FullWidthButton = styled(Button)` + && { + && { + width: 100%; + margin: 0px; + } + } +`; + +export const Header = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +`; + +export const Title = styled(Typography.Title)``; + +export const Message = styled(Typography.Paragraph)` + && { + margin: 0; + } +`; + +export const Modal = styled(AntModal).attrs({ + width: 'auto', +})` + right: 32px; + top: calc(100vh - 410px); + position: absolute; + height: max-content; + padding: 0; + + .ant-modal-content { + width: 340px; + } + + .ant-modal-footer { + border: none; + padding-top: 0; + } + + .ant-modal-body { + padding-bottom: 19px; + } +`; diff --git a/web/src/components/ContactUs/ContactUs.tsx b/web/src/components/ContactUs/ContactUs.tsx new file mode 100644 index 0000000000..3fb9a86f30 --- /dev/null +++ b/web/src/components/ContactUs/ContactUs.tsx @@ -0,0 +1,35 @@ +import {createContext, useContext, useMemo, useState} from 'react'; +import {noop} from 'lodash'; +import * as S from './ContactUs.styled'; +import ContactUsModal from './ContactUsModal'; +import PulseButton from '../PulseButton'; + +interface IContext { + onOpen(): void; +} + +export const Context = createContext({ + onOpen: noop, +}); + +export const useContactUsModal = () => useContext(Context); + +const ContactUs: React.FC = ({children}) => { + const [isOpen, setIsOpen] = useState(false); + const value = useMemo(() => ({onOpen: () => setIsOpen(true)}), []); + + return ( + + {children} + setIsOpen(true)}> + + + + + + setIsOpen(false)} /> + + ); +}; + +export default ContactUs; diff --git a/web/src/components/ContactUs/ContactUsModal.tsx b/web/src/components/ContactUs/ContactUsModal.tsx new file mode 100644 index 0000000000..6f75a01ac1 --- /dev/null +++ b/web/src/components/ContactUs/ContactUsModal.tsx @@ -0,0 +1,24 @@ +import * as S from './ContactUs.styled'; +import ContactUsModalFooter from './ContactUsModalFooter'; + +interface IProps { + isOpen: boolean; + onClose(): void; +} + +const ContactUsModal = ({isOpen, onClose}: IProps) => { + return ( + }> + + + Let us help you + + + Technical glitches can be tricky, even for the best of us. Don't fret! We're here to save your day. + Create an Issue or contact us via Discord and our tech-savvy team are ready to lend a helping hand. + + + ); +}; + +export default ContactUsModal; diff --git a/web/src/components/ContactUs/ContactUsModalFooter.tsx b/web/src/components/ContactUs/ContactUsModalFooter.tsx new file mode 100644 index 0000000000..8fd6221f0a --- /dev/null +++ b/web/src/components/ContactUs/ContactUsModalFooter.tsx @@ -0,0 +1,19 @@ +import {DISCORD_URL, GITHUB_ISSUES_URL} from 'constants/Common.constants'; +import * as S from './ContactUs.styled'; + +const ContactUsModalFooter = () => { + return ( + + + Create an Issue + + + + Contact Team on Discord + + + + ); +}; + +export default ContactUsModalFooter; diff --git a/web/src/components/ContactUs/index.ts b/web/src/components/ContactUs/index.ts new file mode 100644 index 0000000000..5b52ead37b --- /dev/null +++ b/web/src/components/ContactUs/index.ts @@ -0,0 +1,5 @@ +import {useContactUsModal} from './ContactUs'; + +// eslint-disable-next-line no-restricted-exports +export {default} from './ContactUs'; +export {useContactUsModal}; diff --git a/web/src/components/PulseButton/PulseButton.styled.ts b/web/src/components/PulseButton/PulseButton.styled.ts new file mode 100644 index 0000000000..98d9294e2e --- /dev/null +++ b/web/src/components/PulseButton/PulseButton.styled.ts @@ -0,0 +1,31 @@ +import styled, {DefaultTheme, keyframes} from 'styled-components'; + +const getPulseAnimation = (theme: DefaultTheme) => + keyframes` + 0% { + transform: scale(.7); + box-shadow: 0 0 0 0 ${theme.color.primaryLight}; + } + 70% { + transform: scale(1); + box-shadow: 0 0 0 2px ${theme.color.primaryLight}; + } + 100% { + transform: scale(.7); + box-shadow: 0 0 0 0 ${theme.color.primaryLight}; + } +`; + +export const PulseButton = styled.button` + width: 9px; + height: 9px; + border: none; + padding: 0px; + border-radius: 50%; + cursor: pointer; + background: ${({theme}) => theme.color.primary}; + + animation-name: ${({theme}) => getPulseAnimation(theme)}; + animation-duration: 1.5s; + animation-iteration-count: infinite; +`; diff --git a/web/src/components/PulseButton/index.ts b/web/src/components/PulseButton/index.ts new file mode 100644 index 0000000000..33f5035b60 --- /dev/null +++ b/web/src/components/PulseButton/index.ts @@ -0,0 +1,3 @@ +import {PulseButton} from './PulseButton.styled'; + +export default PulseButton; diff --git a/web/src/pages/Settings/Settings.tsx b/web/src/pages/Settings/Settings.tsx index 10c0bfd153..c382f551ed 100644 --- a/web/src/pages/Settings/Settings.tsx +++ b/web/src/pages/Settings/Settings.tsx @@ -3,14 +3,17 @@ import withAnalytics from 'components/WithAnalytics/WithAnalytics'; import DataStoreProvider from 'providers/DataStore'; import SettingsProvider from 'providers/Settings'; import Content from './Content'; +import ContactUs from '../../components/ContactUs/ContactUs'; const Settings = () => ( - - - - - + + + + + + + ); diff --git a/web/src/providers/DataStore/DataStore.provider.tsx b/web/src/providers/DataStore/DataStore.provider.tsx index 34f9938bee..b67ec01f0d 100644 --- a/web/src/providers/DataStore/DataStore.provider.tsx +++ b/web/src/providers/DataStore/DataStore.provider.tsx @@ -9,6 +9,7 @@ import { useDeleteDataStoreMutation, } from 'redux/apis/TraceTest.api'; import DataStoreService from 'services/DataStore.service'; +import {useContactUsModal} from 'components/ContactUs'; import {SupportedDataStores, TConnectionResult, TDraftDataStore} from 'types/DataStore.types'; import DataStore from 'models/DataStore.model'; import useDataStoreNotification from './hooks/useDataStoreNotification'; @@ -49,6 +50,8 @@ const DataStoreProvider = ({children}: IProps) => { const [isFormValid, setIsFormValid] = useState(false); const {showSuccessNotification, showTestConnectionNotification} = useDataStoreNotification(); const {onOpen} = useConfirmationModal(); + const [connectionTries, setConnectionTries] = useState(0); + const {onOpen: onContactUsOpen} = useContactUsModal(); const onSaveConfig = useCallback( async (draft: TDraftDataStore, defaultDataStore: DataStore) => { @@ -105,11 +108,16 @@ const DataStoreProvider = ({children}: IProps) => { try { const result = await testConnection(dataStore.spec!).unwrap(); showTestConnectionNotification(result, draft.dataStoreType!); + setConnectionTries(0); } catch (err) { + setConnectionTries(prev => prev + 1); showTestConnectionNotification(err as TConnectionResult, draft.dataStoreType!); + if (connectionTries + 1 === 3) { + onContactUsOpen(); + } } }, - [showTestConnectionNotification, testConnection] + [connectionTries, onContactUsOpen, showTestConnectionNotification, testConnection] ); const value = useMemo(