From 043b20a05a74d8d789d971d0ad3a7bb41fd81ae4 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Mon, 14 Mar 2022 11:11:37 +0800 Subject: [PATCH] feat(console): connector sender test (#367) --- .../console/src/components/Tooltip/index.tsx | 24 +++- .../components/SenderTester/index.module.scss | 35 ++++++ .../components/SenderTester/index.tsx | 119 ++++++++++++++++++ .../pages/ConnectorDetails/index.module.scss | 10 +- .../src/pages/ConnectorDetails/index.tsx | 9 +- packages/console/src/utils/regex.ts | 3 + packages/core/src/connectors/types.ts | 2 +- packages/core/src/routes/connector.test.ts | 95 ++++++++++++++ packages/core/src/routes/connector.ts | 61 ++++++++- packages/phrases/src/locales/en.ts | 12 +- packages/phrases/src/locales/zh-cn.ts | 12 +- 11 files changed, 361 insertions(+), 21 deletions(-) create mode 100644 packages/console/src/pages/ConnectorDetails/components/SenderTester/index.module.scss create mode 100644 packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx create mode 100644 packages/console/src/utils/regex.ts diff --git a/packages/console/src/components/Tooltip/index.tsx b/packages/console/src/components/Tooltip/index.tsx index d22d36bef..a260fce06 100644 --- a/packages/console/src/components/Tooltip/index.tsx +++ b/packages/console/src/components/Tooltip/index.tsx @@ -9,6 +9,7 @@ type Props = { content: ReactNode; domRef: RefObject>; className?: string; + behavior?: 'visibleOnHover' | 'visibleByDefault'; }; type Position = { @@ -16,10 +17,15 @@ type Position = { left: number; }; -const Tooltip = ({ content, domRef, className }: Props) => { +const Tooltip = ({ + content, + domRef, + className, + behavior = 'visibleOnHover', +}: Props) => { const [tooltipDom, setTooltipDom] = useState(); const [position, setPosition] = useState(); - const isVisible = position !== undefined; + const positionCaculated = position !== undefined; useEffect(() => { if (!domRef.current) { @@ -28,6 +34,14 @@ const Tooltip = ({ content, domRef, className }: Props) => const dom = domRef.current; + if (behavior === 'visibleByDefault') { + const { top, left, width } = domRef.current.getBoundingClientRect(); + const { scrollTop, scrollLeft } = document.documentElement; + setPosition({ top: top + scrollTop - 12, left: left + scrollLeft + width / 2 }); + + return; + } + const enterHandler = () => { if (domRef.current) { const { top, left, width } = domRef.current.getBoundingClientRect(); @@ -47,10 +61,10 @@ const Tooltip = ({ content, domRef, className }: Props) => dom.removeEventListener('mouseenter', enterHandler); dom.removeEventListener('mouseleave', leaveHandler); }; - }, [domRef]); + }, [domRef, behavior]); useEffect(() => { - if (!isVisible) { + if (!positionCaculated) { if (tooltipDom) { tooltipDom.remove(); setTooltipDom(undefined); @@ -66,7 +80,7 @@ const Tooltip = ({ content, domRef, className }: Props) => } return () => tooltipDom?.remove(); - }, [isVisible, tooltipDom]); + }, [positionCaculated, tooltipDom]); if (!tooltipDom || !position) { return null; diff --git a/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.module.scss b/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.module.scss new file mode 100644 index 000000000..e8a4cc5c9 --- /dev/null +++ b/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.module.scss @@ -0,0 +1,35 @@ +@use '@/scss/underscore' as _; + +.fields { + display: flex; + align-items: flex-end; + margin-bottom: _.unit(1); + + .textField { + @include _.form-text-field; + } + + .send { + margin-left: _.unit(1); + margin-bottom: 1px; + } +} + +.error { + font: var(--font-body-2); + color: var(--color-error); +} + +.description { + font: var(--font-body-2); + color: var(--color-component-caption); +} + +div.successTooltip { + background: #008a71; + color: #fff; + + &::after { + border-top-color: #008a71; + } +} diff --git a/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx b/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx new file mode 100644 index 000000000..e1648cc4a --- /dev/null +++ b/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx @@ -0,0 +1,119 @@ +import { ConnectorType } from '@logto/schemas'; +import classNames from 'classnames'; +import ky from 'ky'; +import React, { useEffect, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +import Button from '@/components/Button'; +import FormField from '@/components/FormField'; +import TextInput from '@/components/TextInput'; +import Tooltip from '@/components/Tooltip'; +import { phoneRegEx, emailRegEx } from '@/utils/regex'; + +import * as styles from './index.module.scss'; + +type Props = { + connectorType: Exclude; +}; + +type FormData = { + sendTo: string; +}; + +const SenderTester = ({ connectorType }: Props) => { + const buttonPosReference = useRef(null); + const [showTooltip, setShowTooltip] = useState(false); + const [submitting, setSubmitting] = useState(false); + const { + handleSubmit, + register, + formState: { + errors: { sendTo: inputError }, + }, + } = useForm(); + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const isSms = connectorType === ConnectorType.SMS; + + useEffect(() => { + if (!showTooltip) { + return; + } + + const tooltipTimeout = setTimeout(() => { + setShowTooltip(false); + setSubmitting(false); + }, 2000); + + return () => { + clearTimeout(tooltipTimeout); + }; + }, [showTooltip]); + + const onSubmit = handleSubmit(async (formData) => { + const { sendTo } = formData; + setSubmitting(true); + + const data = isSms ? { phone: sendTo } : { email: sendTo }; + + try { + await ky + .post(`/api/connectors/test/${connectorType.toLowerCase()}`, { + json: data, + }) + .json(); + setShowTooltip(true); + } catch (error: unknown) { + console.error(error); + setSubmitting(false); + } + }); + + return ( +
+
+ + + +
+
+ {showTooltip && ( + + )} +
+
+ {inputError?.message ? inputError.message : t('connector_details.test_sender_description')} +
+
+ ); +}; + +export default SenderTester; diff --git a/packages/console/src/pages/ConnectorDetails/index.module.scss b/packages/console/src/pages/ConnectorDetails/index.module.scss index b88884386..70ab83b2c 100644 --- a/packages/console/src/pages/ConnectorDetails/index.module.scss +++ b/packages/console/src/pages/ConnectorDetails/index.module.scss @@ -50,6 +50,12 @@ } } +.container .body { + > :not(:first-child) { + margin-top: _.unit(4); + } +} + .readme { background: var(--color-card-background); padding: _.unit(6); @@ -65,10 +71,6 @@ } } -.space { - margin-bottom: _.unit(4); -} - .actions { border-top: 1px solid var(--color-border); display: flex; diff --git a/packages/console/src/pages/ConnectorDetails/index.tsx b/packages/console/src/pages/ConnectorDetails/index.tsx index cc7836873..c69a6c95c 100644 --- a/packages/console/src/pages/ConnectorDetails/index.tsx +++ b/packages/console/src/pages/ConnectorDetails/index.tsx @@ -1,4 +1,4 @@ -import { ConnectorDTO, RequestErrorBody } from '@logto/schemas'; +import { ConnectorDTO, ConnectorType, RequestErrorBody } from '@logto/schemas'; import ky, { HTTPError } from 'ky'; import React, { useEffect, useState } from 'react'; import { toast } from 'react-hot-toast'; @@ -18,6 +18,7 @@ import Close from '@/icons/Close'; import * as drawerStyles from '@/scss/drawer.module.scss'; import { RequestError } from '@/swr'; +import SenderTester from './components/SenderTester'; import * as styles from './index.module.scss'; const ConnectorDetails = () => { @@ -125,13 +126,12 @@ const ConnectorDetails = () => { )} {data && ( - + {t('connector_details.tab_settings')} -
{ setConfig(value); }} /> + {data.metadata.type !== ConnectorType.Social && ( + + )} {saveError &&
{saveError}
}