diff --git a/src/components/InstallationWizard/InstallationTypeButton/index.tsx b/src/components/InstallationWizard/InstallationTypeButton/index.tsx deleted file mode 100644 index 88d63e2a5..000000000 --- a/src/components/InstallationWizard/InstallationTypeButton/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as s from "./styles"; -import { InstallationTypeButtonProps } from "./types"; - -export const InstallationTypeButton = (props: InstallationTypeButtonProps) => { - const handleClick = () => { - props.onClick(props.installationType); - }; - - return ( - - - {props.icon} - - - - {props.title} - - {props.description} - - - ); -}; diff --git a/src/components/InstallationWizard/InstallationTypeCard/index.tsx b/src/components/InstallationWizard/InstallationTypeCard/index.tsx new file mode 100644 index 000000000..abb6561b1 --- /dev/null +++ b/src/components/InstallationWizard/InstallationTypeCard/index.tsx @@ -0,0 +1,25 @@ +import * as s from "./styles"; +import { InstallationTypeCardProps } from "./types"; + +export const InstallationTypeCard = (props: InstallationTypeCardProps) => { + const handleClick = () => { + if (!props.disabled) { + props.onClick(props.installationType); + } + }; + + return ( + + + + {props.icon} + + + {props.title} + {props.description} + + + {props.additionalContent &&
{props.additionalContent}
} +
+ ); +}; diff --git a/src/components/InstallationWizard/InstallationTypeButton/styles.ts b/src/components/InstallationWizard/InstallationTypeCard/styles.ts similarity index 66% rename from src/components/InstallationWizard/InstallationTypeButton/styles.ts rename to src/components/InstallationWizard/InstallationTypeCard/styles.ts index 9d3d71c80..efb783409 100644 --- a/src/components/InstallationWizard/InstallationTypeButton/styles.ts +++ b/src/components/InstallationWizard/InstallationTypeCard/styles.ts @@ -1,27 +1,18 @@ import styled from "styled-components"; -import { InstallationTypeButtonElementProps } from "./types"; +import { ButtonProps, ContainerProps } from "./types"; -export const InstallationTypeButton = styled.button` +export const Container = styled.div` display: flex; + flex-direction: column; + gap: 20px; padding: 12px; - gap: 8px; border-radius: 4px; - font-size: 12px; - line-height: 14px; border: 1px solid transparent; - color: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#828797"; - case "dark": - case "dark-jetbrains": - return "#9b9b9b"; - } - }}; + cursor: ${({ disabled }) => (disabled ? "default" : "pointer")}; background: ${({ theme }) => { switch (theme.mode) { case "light": - return "#f1f5fa"; + return "#e9eef4"; case "dark": case "dark-jetbrains": return "#2e2e2e"; @@ -61,21 +52,25 @@ export const InstallationTypeButton = styled.button` } }}; } +`; - &:disabled { - color: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#b9c0d4"; - case "dark": - case "dark-jetbrains": - return "#9b9b9b"; - } - }}; - } +export const ContentContainer = styled.div` + display: flex; + gap: 8px; + font-size: 12px; + line-height: normal; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + }}; `; -export const InstallationTypeButtonIconContainer = styled.span` +export const IconContainer = styled.span` display: flex; flex-shrink: 0; height: 72px; @@ -92,17 +87,15 @@ export const InstallationTypeButtonIconContainer = styled.span (disabled ? "opacity: 0.5;" : "")} `; -export const InstallationTypeButtonTextContainer = styled.span` +export const TextContainer = styled.span` display: flex; flex-direction: column; text-align: start; `; -export const InstallationTypeButtonTitle = styled.span` +export const Title = styled.span` display: flex; align-items: center; gap: 4px; @@ -110,17 +103,7 @@ export const InstallationTypeButtonTitle = styled.span { - if (disabled) { - switch (theme.mode) { - case "light": - return "#b9c0d4"; - case "dark": - case "dark-jetbrains": - return "#9b9b9b"; - } - } - + color: ${({ theme }) => { switch (theme.mode) { case "light": return "#4d668a"; diff --git a/src/components/InstallationWizard/InstallationTypeButton/types.ts b/src/components/InstallationWizard/InstallationTypeCard/types.ts similarity index 66% rename from src/components/InstallationWizard/InstallationTypeButton/types.ts rename to src/components/InstallationWizard/InstallationTypeCard/types.ts index 234d79e6b..c03006be0 100644 --- a/src/components/InstallationWizard/InstallationTypeButton/types.ts +++ b/src/components/InstallationWizard/InstallationTypeCard/types.ts @@ -1,15 +1,18 @@ import { ReactNode } from "react"; import { InstallationType } from "../types"; -export type InstallationTypeButtonProps = { +export type InstallationTypeCardProps = { disabled?: boolean; installationType: InstallationType; icon: ReactNode; title: ReactNode; description: ReactNode; onClick: (installationType: InstallationType) => void; + additionalContent?: ReactNode; }; -export type InstallationTypeButtonElementProps = { +export type ButtonProps = { disabled?: boolean; }; + +export type ContainerProps = ButtonProps; diff --git a/src/components/InstallationWizard/index.tsx b/src/components/InstallationWizard/index.tsx index 3ad3a2f6e..c52383380 100644 --- a/src/components/InstallationWizard/index.tsx +++ b/src/components/InstallationWizard/index.tsx @@ -11,13 +11,17 @@ import { addPrefix } from "../../utils/addPrefix"; import { actions as globalActions } from "../common/App"; import { ConfigContext } from "../common/App/ConfigContext"; import { getThemeKind } from "../common/App/styles"; +import { Button } from "../common/Button"; +import { Checkbox } from "../common/Checkbox"; +import { Loader } from "../common/Loader"; +import { TextField } from "../common/TextField"; import { CloudDownloadIcon } from "../common/icons/CloudDownloadIcon"; import { DigmaGreetingIcon } from "../common/icons/DigmaGreetingIcon"; import { OpenTelemetryDisplayIcon } from "../common/icons/OpenTelemetryDisplayIcon"; import { SlackLogoIcon } from "../common/icons/SlackLogoIcon"; import { FinishStep } from "./FinishStep"; import { InstallStep } from "./InstallStep"; -import { InstallationTypeButton } from "./InstallationTypeButton"; +import { InstallationTypeCard } from "./InstallationTypeCard"; import { ObservabilityStep } from "./ObservabilityStep"; import { Step } from "./Step"; import { StepData, StepStatus } from "./Step/types"; @@ -97,6 +101,8 @@ export const InstallationWizard = () => { const [connectionCheckStatus, setConnectionCheckStatus] = useState(); const footerContentRef = useRef(null); + const [userEmail, setUserEmail] = useState(""); + const [isUserEmailCaptured, setIsUserEmailCaptured] = useState(false); // TO DO: // add environment variable for presetting the correct installation type @@ -117,6 +123,10 @@ export const InstallationWizard = () => { ); const [isEmailValidating, setIsEmailValidating] = useState(false); const debouncedEmail = useDebounce(email, 1000); + const [ + isDigmaCloudNotificationCheckboxChecked, + setIsDigmaCloudNotificationCheckboxChecked + ] = useState(false); useEffect(() => { if (email === debouncedEmail) { @@ -285,6 +295,30 @@ export const InstallationWizard = () => { }); }; + const handleDigmaCloudNotificationCheckboxChange = (value: boolean) => { + setIsDigmaCloudNotificationCheckboxChecked(value); + }; + + const handleUserEmailInputChange = (e: ChangeEvent) => { + setUserEmail(e.target.value); + }; + + const handleEmailAddButton = () => { + if (userEmail.length > 0) { + setIsUserEmailCaptured(true); + window.sendMessageToDigma({ + action: globalActions.SEND_TRACKING_EVENT, + payload: { + eventName: + trackingEvents.DIGMA_CLOUD_AVAILABILITY_NOTIFICATION_EMAIL_ADDRESS_CAPTURED, + data: { + email: userEmail + } + } + }); + } + }; + const steps: StepData[] = [ { key: "install", @@ -365,7 +399,7 @@ export const InstallationWizard = () => { Select installation type: - { } /> - { description={ <>Data will be sent anonymously to Digma for processing } + additionalContent={ + + {isUserEmailCaptured ? ( + + + Thank you for subscription! + + ) : ( + <> + + {isDigmaCloudNotificationCheckboxChecked && ( + + Add + + } + /> + )} + + )} + + } disabled={true} /> diff --git a/src/components/InstallationWizard/styles.ts b/src/components/InstallationWizard/styles.ts index 229013394..beb486748 100644 --- a/src/components/InstallationWizard/styles.ts +++ b/src/components/InstallationWizard/styles.ts @@ -234,6 +234,7 @@ export const Badge = styled.span` font-weight: 400; border-radius: 4px; padding: 2px 4px; + text-align: center; background: ${({ theme }) => { switch (theme.mode) { case "light": @@ -244,3 +245,28 @@ export const Badge = styled.span` } }}; `; + +export const SubscriptionContentContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +export const SubscriptionSuccessMessage = styled.div` + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 700; + line-height: normal; + margin-bottom: 43px; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#1dc693"; + case "dark": + case "dark-jetbrains": + return "#67d28b"; + } + }}; +`; diff --git a/src/components/InstallationWizard/tracking.ts b/src/components/InstallationWizard/tracking.ts index 49fb6cb9a..34b713274 100644 --- a/src/components/InstallationWizard/tracking.ts +++ b/src/components/InstallationWizard/tracking.ts @@ -12,7 +12,9 @@ export const trackingEvents = addPrefix( OBSERVABILITY_BUTTON_CLICKED: "set observability button clicked", TAB_CLICKED: "tab clicked", NO_DOCKER_SLACK_LINK_CLICKED: "no docker slack link clicked", - ENGINE_ACTION_BUTTON_CLICKED: "engine action button clicked" + ENGINE_ACTION_BUTTON_CLICKED: "engine action button clicked", + DIGMA_CLOUD_AVAILABILITY_NOTIFICATION_EMAIL_ADDRESS_CAPTURED: + "digma cloud availability notification email address captured" }, " " ); diff --git a/src/components/common/Checkbox/Checkbox.stories.tsx b/src/components/common/Checkbox/Checkbox.stories.tsx new file mode 100644 index 000000000..230b3df58 --- /dev/null +++ b/src/components/common/Checkbox/Checkbox.stories.tsx @@ -0,0 +1,24 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { Checkbox } from "."; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Common/Checkbox", + component: Checkbox, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + id: "id", + label: "Click me", + value: false + } +}; diff --git a/src/components/common/Checkbox/index.tsx b/src/components/common/Checkbox/index.tsx new file mode 100644 index 000000000..eea104f4e --- /dev/null +++ b/src/components/common/Checkbox/index.tsx @@ -0,0 +1,22 @@ +import { ChangeEvent } from "react"; +import * as s from "./styles"; +import { CheckboxProps } from "./types"; + +export const Checkbox = (props: CheckboxProps) => { + const handleChange = (e: ChangeEvent) => { + props.onChange(e.target.checked); + }; + + return ( + + + {props.label} + + ); +}; diff --git a/src/components/common/Checkbox/styles.ts b/src/components/common/Checkbox/styles.ts new file mode 100644 index 000000000..8dd217a00 --- /dev/null +++ b/src/components/common/Checkbox/styles.ts @@ -0,0 +1,39 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + gap: 4px; + align-items: center; + font-size: 12px; + line-height: normal; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + }}; + + &:hover { + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#e2e7ff"; + } + }}; + } +`; + +export const Checkbox = styled.input` + cursor: pointer; +`; + +export const Label = styled.label` + user-select: none; + cursor: pointer; +`; diff --git a/src/components/common/Checkbox/types.ts b/src/components/common/Checkbox/types.ts new file mode 100644 index 000000000..54036c48d --- /dev/null +++ b/src/components/common/Checkbox/types.ts @@ -0,0 +1,9 @@ +import { ReactNode } from "react"; + +export interface CheckboxProps { + id: string; + value: boolean; + label: ReactNode; + disabled?: boolean; + onChange: (value: boolean) => void; +} diff --git a/src/components/common/TextField/TextField.stories.tsx b/src/components/common/TextField/TextField.stories.tsx new file mode 100644 index 000000000..032c0d6a5 --- /dev/null +++ b/src/components/common/TextField/TextField.stories.tsx @@ -0,0 +1,30 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { TextField } from "."; +import { DigmaLogoIcon } from "../icons/DigmaLogoIcon"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Common/TextField", + component: TextField, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + value: "some text" + } +}; + +export const WithInputEndContent: Story = { + args: { + value: "some text", + inputEndContent: + } +}; diff --git a/src/components/common/TextField/index.tsx b/src/components/common/TextField/index.tsx new file mode 100644 index 000000000..26cd25ac4 --- /dev/null +++ b/src/components/common/TextField/index.tsx @@ -0,0 +1,29 @@ +import { useState } from "react"; +import * as s from "./styles"; +import { TextFieldProps } from "./types"; + +export const TextField = (props: TextFieldProps) => { + const [isFocused, setIsFocused] = useState(false); + + const handleFocus = () => { + setIsFocused(true); + }; + + const handleBlur = () => { + setIsFocused(false); + }; + + return ( + + + {props.inputEndContent &&
{props.inputEndContent}
} +
+ ); +}; diff --git a/src/components/common/TextField/styles.ts b/src/components/common/TextField/styles.ts new file mode 100644 index 000000000..a3c89afbd --- /dev/null +++ b/src/components/common/TextField/styles.ts @@ -0,0 +1,70 @@ +import styled from "styled-components"; +import { ContainerProps } from "./types"; + +export const Container = styled.div` + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: 4px; + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#e9eef4"; + case "dark": + case "dark-jetbrains": + return "#252526"; + } + }}; + border: 1px solid + ${({ theme, focused }) => { + if (focused) { + switch (theme.mode) { + case "light": + return "#7891d0"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + } + + switch (theme.mode) { + case "light": + return "#d0d6eb"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } + }}; +`; + +export const Input = styled.input` + border: none; + background: none; + outline: none; + display: flex; + flex-grow: 1; + font-size: 12px; + line-height: 14px; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#4d668a"; + case "dark": + case "dark-jetbrains": + return "#dadada"; + } + }}; + + &::placeholder { + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#4d668a"; + case "dark": + case "dark-jetbrains": + return "#dadada"; + } + }}; + } +`; diff --git a/src/components/common/TextField/types.ts b/src/components/common/TextField/types.ts new file mode 100644 index 000000000..878bae85a --- /dev/null +++ b/src/components/common/TextField/types.ts @@ -0,0 +1,12 @@ +import { ChangeEventHandler, ReactNode } from "react"; + +export interface TextFieldProps { + placeholder?: string; + inputEndContent?: ReactNode; + value: string; + onChange: ChangeEventHandler; +} + +export interface ContainerProps { + focused: boolean; +} diff --git a/src/components/common/icons/OpenTelemetryDisplayIcon.tsx b/src/components/common/icons/OpenTelemetryDisplayIcon.tsx index d8024f5a7..0eb66a863 100644 --- a/src/components/common/icons/OpenTelemetryDisplayIcon.tsx +++ b/src/components/common/icons/OpenTelemetryDisplayIcon.tsx @@ -26,13 +26,16 @@ const OpenTelemetryDisplayIconComponent = ( fill="#4F5DA3" d="M49.06 1H2.246c-.33 0-.648.152-.881.422C1.13 1.692 1 2.06 1 2.442V33.48a2 2 0 0 0 2 2h45.305a2 2 0 0 0 2-2V2.44c0-.189-.032-.376-.094-.551a1.474 1.474 0 0 0-.27-.468 1.251 1.251 0 0 0-.405-.312A1.1 1.1 0 0 0 49.06 1Z" /> - +