diff --git a/package.json b/package.json index a5de55d1e..e52a0d24c 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "build:dashboard:dev": "webpack --config webpack.dev.ts --env app=dashboard", "build:documentation:dev": "webpack --config webpack.dev.ts --env app=documentation", "build:ide-launcher:dev": "webpack --config webpack.dev.ts --env app=ideLauncher", + "build:jaeger-login:dev": "webpack --config webpack.dev.ts --env app=jaegerLogin", "build:installation-wizard:dev": "webpack --config webpack.dev.ts --env app=installationWizard", "build:main:dev": "webpack --config webpack.dev.ts --env app=main", "build:notifications:dev": "webpack --config webpack.dev.ts --env app=notifications", @@ -26,6 +27,7 @@ "build:dashboard:prod": "webpack --config webpack.prod.ts --env app=dashboard", "build:documentation:prod": "webpack --config webpack.prod.ts --env app=documentation", "build:ide-launcher:prod": "webpack --config webpack.prod.ts --env app=ideLauncher", + "build:jaeger-login:prod": "webpack --config webpack.prod.ts --env app=jaegerLogin", "build:installation-wizard:prod": "webpack --config webpack.prod.ts --env app=installationWizard", "build:main:prod": "webpack --config webpack.prod.ts --env app=main", "build:notifications:prod": "webpack --config webpack.prod.ts --env app=notifications", diff --git a/src/components/JaegerLogin/JaegerLogin.stories.tsx b/src/components/JaegerLogin/JaegerLogin.stories.tsx new file mode 100644 index 000000000..dd26708c4 --- /dev/null +++ b/src/components/JaegerLogin/JaegerLogin.stories.tsx @@ -0,0 +1,19 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { JaegerLogin } from "."; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Jaeger Login/JaegerLogin", + component: JaegerLogin, + 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; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Default: Story = {}; diff --git a/src/components/JaegerLogin/index.tsx b/src/components/JaegerLogin/index.tsx new file mode 100644 index 000000000..7a16f1f6e --- /dev/null +++ b/src/components/JaegerLogin/index.tsx @@ -0,0 +1,103 @@ +import axios, { isAxiosError } from "axios"; +import { ChangeEvent, FormEvent, useState } from "react"; +import { Helmet } from "react-helmet"; +import { TextField } from "../common/v3/TextField"; +import * as s from "./styles"; + +export const JaegerLogin = () => { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + + const handleUsernameChange = (e: ChangeEvent) => { + setUsername(e.currentTarget.value); + }; + + const handlePasswordChange = (e: ChangeEvent) => { + setPassword(e.currentTarget.value); + }; + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + + axios + .post("/auth/login", { + username, + password + }) + .then(() => { + const urlParams = new URLSearchParams(window.location.search); + const returnUrl = urlParams.get("return_url"); + if (returnUrl) { + window.location.href = returnUrl; + } + }) + .catch((error: Error) => { + let errorMessage = "Unknown error"; + + if (isAxiosError(error)) { + if (error.response) { + switch (error.response.status) { + case 401: + errorMessage = "Invalid username or password"; + break; + } + } + } else { + errorMessage = error.message; + } + + setError(errorMessage); + }); + }; + + return ( + + + Digma Jaeger Login + + + + + + Sign in + + Sign in with your Digma credentials for secure access to your + Jaeger traces. + + + + + + + + + By signing in you agree with + + Terms & Privacy Policy + + + {error} + + + + ); +}; diff --git a/src/components/JaegerLogin/styles.ts b/src/components/JaegerLogin/styles.ts new file mode 100644 index 000000000..eff51c6ee --- /dev/null +++ b/src/components/JaegerLogin/styles.ts @@ -0,0 +1,98 @@ +import styled from "styled-components"; +import { + heading2BoldTypography, + subheading1RegularTypography, + subscriptRegularTypography +} from "../common/App/typographies"; +import { Link } from "../common/v3/Link"; +import { NewButton } from "../common/v3/NewButton"; + +export const Container = styled.div` + background: radial-gradient( + 100% 100% at 50% 0%, + rgb(124 144 248 / 20%) 0%, + rgb(124 144 248 / 0%) 100% + ), + ${({ theme }) => theme.colors.v3.surface.secondary}; + height: 100%; + padding: 0 20px; + + @media (width <= 375px) { + padding: 0; + } +`; + +export const ContentContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; +`; + +export const Panel = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + padding: 32px; + border-radius: 16px; + background: ${({ theme }) => theme.colors.v3.surface.secondary}; + text-align: center; + max-width: 386px; +`; + +export const TitleContainer = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const Title = styled.h1` + ${heading2BoldTypography} + + margin: 0; + color: ${({ theme }) => theme.colors.v3.text.primary}; +`; + +export const Subtitle = styled.span` + ${subheading1RegularTypography} + + color: ${({ theme }) => theme.colors.v3.text.secondary}; +`; + +export const Form = styled.form` + margin-top: 16px; + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const SignInButton = styled(NewButton)` + width: 100%; + justify-content: center; +`; + +export const Footer = styled.div` + ${subscriptRegularTypography} + + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; +`; + +export const FooterText = styled.span` + color: ${({ theme }) => theme.colors.v3.text.tertiary}; + opacity: 0.5; +`; + +export const TermsLink = styled(Link)` + padding: 4px 8px; + text-decoration: underline; +`; + +export const ErrorText = styled.span` + ${subscriptRegularTypography} + + height: 16px; + color: ${({ theme }) => theme.colors.v3.status.high}; +`; diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index 00a88b333..66c86cf46 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -62,7 +62,7 @@ const defaultCodeFont = isString(window.codeFont) ? window.codeFont : ""; export const App = ({ theme, children, id }: AppProps) => { const colorScheme = useColorScheme(); const [currentTheme, setCurrentTheme] = useState( - theme ?? getTheme() ?? colorScheme + theme ?? platform === "Web" ? colorScheme : getTheme() ); const [mainFont, setMainFont] = useState(defaultMainFont); const [codeFont, setCodeFont] = useState(defaultCodeFont); diff --git a/src/components/common/v3/Select/styles.ts b/src/components/common/v3/Select/styles.ts index 3200fa742..f9af1b1fd 100644 --- a/src/components/common/v3/Select/styles.ts +++ b/src/components/common/v3/Select/styles.ts @@ -174,7 +174,7 @@ export const SearchInputContainer = styled.div` gap: 4px; display: flex; align-items: center; - box-shadow: 0 2px 4px 0 rgba(0 0 0 / 13%); + box-shadow: 0 2px 4px 0 rgb(0 0 0 / 13%); `; export const SearchInput = styled.input` diff --git a/src/containers/IdeLauncher/index.tsx b/src/containers/IdeLauncher/index.tsx index 54a851bd9..ad1e66902 100644 --- a/src/containers/IdeLauncher/index.tsx +++ b/src/containers/IdeLauncher/index.tsx @@ -1,24 +1,8 @@ import { createRoot } from "react-dom/client"; -import { - cancelMessage, - initializeDigmaMessageListener, - sendMessage -} from "../../api"; import { App } from "../../components/common/App"; import { IdeLauncher } from "../../components/IdeLauncher"; -import { dispatcher } from "../../dispatcher"; -import { handleUncaughtError } from "../../utils/handleUncaughtError"; -const APP_ID = "documentation"; - -initializeDigmaMessageListener(dispatcher); - -window.sendMessageToDigma = sendMessage; -window.cancelMessageToDigma = cancelMessage; - -window.addEventListener("error", (e) => { - handleUncaughtError(APP_ID, e); -}); +const APP_ID = "ideLauncher"; const rootElement = document.getElementById("root"); diff --git a/src/containers/JaegerLogin/index.tsx b/src/containers/JaegerLogin/index.tsx new file mode 100644 index 000000000..b28832755 --- /dev/null +++ b/src/containers/JaegerLogin/index.tsx @@ -0,0 +1,16 @@ +import { createRoot } from "react-dom/client"; +import { App } from "../../components/common/App"; +import { JaegerLogin } from "../../components/JaegerLogin"; + +const APP_ID = "jaegerLogin"; + +const rootElement = document.getElementById("root"); + +if (rootElement) { + const root = createRoot(rootElement); + root.render( + + + + ); +} diff --git a/webpackEntries.ts b/webpackEntries.ts index 2814a43b2..f55b1b391 100644 --- a/webpackEntries.ts +++ b/webpackEntries.ts @@ -12,6 +12,9 @@ export const entries: AppEntries = { ideLauncher: { entry: path.resolve(__dirname, "./src/containers/IdeLauncher/index.tsx") }, + jaegerLogin: { + entry: path.resolve(__dirname, "./src/containers/JaegerLogin/index.tsx") + }, installationWizard: { entry: path.resolve( __dirname,