diff --git a/src/components/InstallationWizard/FinishStep/index.tsx b/src/components/InstallationWizard/FinishStep/index.tsx
index c69c6af16..bb2211bf6 100644
--- a/src/components/InstallationWizard/FinishStep/index.tsx
+++ b/src/components/InstallationWizard/FinishStep/index.tsx
@@ -1,13 +1,19 @@
import { DefaultTheme, useTheme } from "styled-components";
import { getThemeKind } from "../../common/App/styles";
+import { CircleLoader } from "../../common/CircleLoader";
import { BellIcon } from "../../common/icons/BellIcon";
+import { ChatIcon } from "../../common/icons/ChatIcon";
+import { CheckmarkCircleInvertedIcon } from "../../common/icons/CheckmarkCircleInvertedIcon";
import { GearIcon } from "../../common/icons/GearIcon";
import { PlayIcon } from "../../common/icons/PlayIcon";
import { SlackLogoIcon } from "../../common/icons/SlackLogoIcon";
+import { WarningCircleLargeIcon } from "../../common/icons/WarningCircleLargeIcon";
import { Link } from "../styles";
import * as s from "./styles";
import { FinishStepProps } from "./types";
+const EMAIL_ERROR_MESSAGE = "Enter a valid email";
+
const getPlayIconColor = (theme: DefaultTheme) => {
switch (theme.mode) {
case "light":
@@ -18,6 +24,16 @@ const getPlayIconColor = (theme: DefaultTheme) => {
}
};
+const getErrorIconColor = (theme: DefaultTheme) => {
+ switch (theme.mode) {
+ case "light":
+ return "#e00036";
+ case "dark":
+ case "dark-jetbrains":
+ return "#f93967";
+ }
+};
+
export const FinishStep = (props: FinishStepProps) => {
const theme = useTheme();
const themeKind = getThemeKind(theme);
@@ -41,28 +57,42 @@ export const FinishStep = (props: FinishStepProps) => {
>
)}
- Stay Up To Date(optional)
+ Stay up to date(optional)
Enter your E-mail address to be the first to get Digma updates
-
- {props.emailErrorMessage && (
- {props.emailErrorMessage}
- )}
-
-
- Join our Slack channel
-
+
+
+ {props.isEmailValid === false && (
+
+
+ {EMAIL_ERROR_MESSAGE}
+
+ )}
+ {props.isEmailValid && (
+
+
+
+ )}
+ {props.isEmailValidating && (
+
+
+
+ )}
+
Run / Debug your application
@@ -73,7 +103,7 @@ export const FinishStep = (props: FinishStepProps) => {
- Getting Started
+ Getting started
We've prepared a short video to show you the ropes on getting
started analyzing your code with Digma.
@@ -92,6 +122,17 @@ export const FinishStep = (props: FinishStepProps) => {
/>
+
+ Give us feedback
+
+
+
+ Join Our Slack Channel
+
);
};
diff --git a/src/components/InstallationWizard/FinishStep/styles.ts b/src/components/InstallationWizard/FinishStep/styles.ts
index 4cd70c245..7f55be9e1 100644
--- a/src/components/InstallationWizard/FinishStep/styles.ts
+++ b/src/components/InstallationWizard/FinishStep/styles.ts
@@ -41,6 +41,7 @@ export const SectionDescription = styled(CommonSectionDescription)`
export const IllustrationContainer = styled(CommonIllustrationContainer)`
margin: 0 0 12px;
position: relative;
+ overflow: hidden;
`;
export const PlayIconContainer = styled.div`
@@ -62,6 +63,14 @@ export const RunOrDebugIllustration = styled.img`
margin: 7% 17%;
`;
+export const EmailField = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 20px;
+ position: relative;
+`;
+
export const EmailInput = styled.input`
box-sizing: border-box;
font-size: 12px;
@@ -127,8 +136,22 @@ export const EmailInput = styled.input`
}
`;
+export const EmailInputIconContainer = styled.div`
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 8px;
+ height: 16px;
+ width: 16px;
+ margin: auto;
+`;
+
export const ErrorMessage = styled(CommonSectionDescription)`
- margin-top: 4px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 12px;
+ line-height: 14px;
color: ${({ theme }) => {
switch (theme.mode) {
case "light":
@@ -144,10 +167,14 @@ export const SlackLink = styled(Link)`
display: flex;
align-items: center;
gap: 4px;
- margin: 12px 0 20px;
+ margin-bottom: 12px;
`;
export const ThumbnailPlayCircleIcon = styled(PlayCircleIcon)`
width: 100%;
height: 100%;
`;
+
+export const GiveUsFeedbackTitle = styled(SectionTitle)`
+ margin-top: 8px;
+`;
diff --git a/src/components/InstallationWizard/FinishStep/types.ts b/src/components/InstallationWizard/FinishStep/types.ts
index cc1b93fd9..43506c634 100644
--- a/src/components/InstallationWizard/FinishStep/types.ts
+++ b/src/components/InstallationWizard/FinishStep/types.ts
@@ -3,7 +3,8 @@ import { ChangeEvent } from "react";
export interface FinishStepProps {
quickstartURL?: string;
slackChannelURL: string;
- onEmailChange: (e: ChangeEvent) => void;
+ onEmailInputChange: (e: ChangeEvent) => void;
email: string;
- emailErrorMessage?: string;
+ isEmailValid?: boolean;
+ isEmailValidating: boolean;
}
diff --git a/src/components/InstallationWizard/InstallStep/index.tsx b/src/components/InstallationWizard/InstallStep/index.tsx
index e3afcfe33..cd38063d5 100644
--- a/src/components/InstallationWizard/InstallStep/index.tsx
+++ b/src/components/InstallationWizard/InstallStep/index.tsx
@@ -74,7 +74,7 @@ export const InstallStep = (props: InstallStepProps) => {
- Install Digma Docker Extension
+ Install Digma Docker extension
(You'll need{" "}
diff --git a/src/components/InstallationWizard/index.tsx b/src/components/InstallationWizard/index.tsx
index fc31bff21..0f75cabeb 100644
--- a/src/components/InstallationWizard/index.tsx
+++ b/src/components/InstallationWizard/index.tsx
@@ -1,8 +1,9 @@
-import { useEffect, useRef, useState } from "react";
+import { ChangeEvent, useEffect, useRef, useState } from "react";
import { CSSTransition } from "react-transition-group";
import { useTheme } from "styled-components";
import { dispatcher } from "../../dispatcher";
import { IDE } from "../../globals";
+import { useDebounce } from "../../hooks/useDebounce";
import { usePrevious } from "../../hooks/usePrevious";
import { ide } from "../../platform";
import { addPrefix } from "../../utils/addPrefix";
@@ -97,6 +98,10 @@ const getStepStatus = (index: number, currentStep: number): StepStatus => {
return "not-completed";
};
+const validateEmailFormat = (email: string): boolean => {
+ return new RegExp(EMAIL_ADDRESS_REGEX).test(email);
+};
+
export const InstallationWizard = () => {
const [currentStep, setCurrentStep] = useState(firstStep);
const previousStep = usePrevious(currentStep);
@@ -113,7 +118,20 @@ export const InstallationWizard = () => {
const theme = useTheme();
const themeKind = getThemeKind(theme);
const [email, setEmail] = useState(preselectedEmail);
- const [emailErrorMessage, setEmailErrorMessage] = useState("");
+ const [isEmailValid, setIsEmailValid] = useState(
+ email.length > 0 ? validateEmailFormat(email) : undefined
+ );
+ const [isEmailValidating, setIsEmailValidating] = useState(false);
+ const debouncedEmail = useDebounce(email, 1000);
+
+ useEffect(() => {
+ const res =
+ debouncedEmail.length > 0
+ ? validateEmailFormat(debouncedEmail)
+ : undefined;
+ setIsEmailValid(res);
+ setIsEmailValidating(false);
+ }, [debouncedEmail]);
useEffect(() => {
if (previousStep === 0 && currentStep === 1) {
@@ -228,29 +246,17 @@ export const InstallationWizard = () => {
setInstallationType(installationType);
};
- const handleEmailChange = (e: React.ChangeEvent) => {
- if (emailErrorMessage) {
- setEmailErrorMessage("");
- }
+ const handleEmailInputChange = (e: ChangeEvent) => {
+ setIsEmailValid(undefined);
+ setIsEmailValidating(true);
setEmail(e.target.value.trim());
};
- const validateEmailFormat = (email: string): boolean => {
- return new RegExp(EMAIL_ADDRESS_REGEX).test(email);
- };
-
const handleFinishButtonClick = () => {
- if (email && !validateEmailFormat(email)) {
- setEmailErrorMessage(
- "E-mail has incorrect format. Please try another one"
- );
- return;
- }
-
window.sendMessageToDigma({
action: actions.FINISH,
payload: {
- ...(email.length > 0 ? { email } : {})
+ ...(debouncedEmail.length > 0 ? { email: debouncedEmail } : {})
}
});
};
@@ -296,8 +302,9 @@ export const InstallationWizard = () => {
quickstartURL={quickstartURL}
slackChannelURL={SLACK_CHANNEL_URL}
email={email}
- onEmailChange={handleEmailChange}
- emailErrorMessage={emailErrorMessage}
+ onEmailInputChange={handleEmailInputChange}
+ isEmailValid={isEmailValid}
+ isEmailValidating={isEmailValidating}
/>
)
}
@@ -393,7 +400,10 @@ export const InstallationWizard = () => {
transitionClassName={footerTransitionClassName}
transitionDuration={TRANSITION_DURATION}
>
-
+
Finish
diff --git a/src/components/common/CircleLoader/index.tsx b/src/components/common/CircleLoader/index.tsx
new file mode 100644
index 000000000..eadad96f5
--- /dev/null
+++ b/src/components/common/CircleLoader/index.tsx
@@ -0,0 +1,17 @@
+import { DEFAULT_ICON_SIZE } from "../icons/hooks";
+import * as s from "./styles";
+import { CircleLoaderProps } from "./types";
+
+export const CircleLoader = (props: CircleLoaderProps) => {
+ const size = props.size || DEFAULT_ICON_SIZE;
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/common/CircleLoader/styles.ts b/src/components/common/CircleLoader/styles.ts
new file mode 100644
index 000000000..92a56aac5
--- /dev/null
+++ b/src/components/common/CircleLoader/styles.ts
@@ -0,0 +1,29 @@
+import styled, { keyframes } from "styled-components";
+import { InnerCircleProps, OuterCircleProps } from "./types";
+
+const rotateAnimation = keyframes`
+ from { transform: rotate(0); }
+ to { transform: rotate(360deg); }
+`;
+
+export const OuterCircle = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: ${({ size }) => size}px;
+ height: ${({ size }) => size}px;
+ border-radius: 50%;
+ background: conic-gradient(
+ from 90deg at 50% 50%,
+ ${({ startColor }) => startColor} 0deg,
+ ${({ endColor }) => endColor} 360deg
+ );
+ animation: ${rotateAnimation} 1s linear infinite;
+`;
+
+export const InnerCircle = styled.div`
+ width: 83%;
+ height: 83%;
+ border-radius: 50%;
+ background: ${({ background }) => background};
+`;
diff --git a/src/components/common/CircleLoader/types.ts b/src/components/common/CircleLoader/types.ts
new file mode 100644
index 000000000..75c249542
--- /dev/null
+++ b/src/components/common/CircleLoader/types.ts
@@ -0,0 +1,18 @@
+export interface CircleLoaderProps {
+ size?: number;
+ colors: {
+ start: string;
+ end: string;
+ background: string;
+ };
+}
+
+export interface OuterCircleProps {
+ size: number;
+ startColor: string;
+ endColor: string;
+}
+
+export interface InnerCircleProps {
+ background: string;
+}
diff --git a/src/components/common/Loader/index.tsx b/src/components/common/Loader/index.tsx
index 3afef7393..f422bb611 100644
--- a/src/components/common/Loader/index.tsx
+++ b/src/components/common/Loader/index.tsx
@@ -1,35 +1,7 @@
import React from "react";
+import { CircleLoader } from "../CircleLoader";
import { LoaderProps } from "./types";
-import styled, { keyframes } from "styled-components";
-
-const rotateAnimation = keyframes`
- from { transform: rotate(0); }
- to { transform: rotate(360deg); }
-`;
-
-const PupilOuterCircle = styled.div`
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: conic-gradient(
- from 90deg at 50% 50%,
- rgba(53, 56, 205, 0) 0deg,
- #3538cd 360deg
- );
- animation: ${rotateAnimation} 1s linear infinite;
-`;
-
-const PupilInnerCircle = styled.div`
- width: 83%;
- height: 83%;
- border-radius: 50%;
- background: #f3f3f3;
-`;
-
const LoaderComponent = (props: LoaderProps) => {
const size = props.size || 20;
@@ -370,14 +342,24 @@ const LoaderComponent = (props: LoaderProps) => {
d="M112.241 75.0235C112.241 75.0235 121.052 67.0769 121.32 66.789C121.595 66.364 121.753 65.8742 121.779 65.3687C121.804 64.8632 121.696 64.36 121.464 63.9098C120.898 63.1132 121.349 64.4856 120.927 65.4358C120.505 66.3859 110.12 72.1539 110.12 72.1539L112.241 75.0235Z"
/>
-
-
-
+
-
-
-
+
);
diff --git a/src/components/common/icons/ChatIcon.tsx b/src/components/common/icons/ChatIcon.tsx
new file mode 100644
index 000000000..1a89f4496
--- /dev/null
+++ b/src/components/common/icons/ChatIcon.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import { useIconProps } from "./hooks";
+import { IconProps } from "./types";
+
+const ChatIconComponent = (props: IconProps) => {
+ const { size, color } = useIconProps(props);
+
+ return (
+
+ );
+};
+
+export const ChatIcon = React.memo(ChatIconComponent);
diff --git a/src/components/common/icons/WarningCircleLargeIcon.tsx b/src/components/common/icons/WarningCircleLargeIcon.tsx
new file mode 100644
index 000000000..0a891aaec
--- /dev/null
+++ b/src/components/common/icons/WarningCircleLargeIcon.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import { useIconProps } from "./hooks";
+import { IconProps } from "./types";
+
+const WarningCircleLargeIconComponent = (props: IconProps) => {
+ const { size, color } = useIconProps(props);
+
+ return (
+
+ );
+};
+
+export const WarningCircleLargeIcon = React.memo(
+ WarningCircleLargeIconComponent
+);
diff --git a/src/components/common/icons/hooks.ts b/src/components/common/icons/hooks.ts
index d5a4866d9..b73bfe848 100644
--- a/src/components/common/icons/hooks.ts
+++ b/src/components/common/icons/hooks.ts
@@ -2,7 +2,7 @@ import { useMemo } from "react";
import { useTheme } from "styled-components";
import { IconProps } from "./types";
-const DEFAULT_ICON_SIZE = 12;
+export const DEFAULT_ICON_SIZE = 12;
export const useIconProps = (props: IconProps): IconProps => {
const theme = useTheme();