From 7b510183d1d9737625d8d0f0c395d0a0d24827c6 Mon Sep 17 00:00:00 2001 From: Anastasiia Solop <35258279+anastasiya1155@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:58:08 +0100 Subject: [PATCH] Login changes (#1069) * use 2-step onboarding, some refactoring * show login page any time there is no user_login field in config (meaning user is not authorized) * Update github copy --------- Co-authored-by: Louis Knight-Webb --- .../src/components/AddStudioContext/index.tsx | 2 +- .../DialogText/index.tsx | 0 client/src/locales/en.json | 7 +- client/src/locales/es.json | 5 +- client/src/locales/it.json | 2 +- client/src/locales/ja.json | 7 +- client/src/locales/zh-CN.json | 7 +- .../index.tsx} | 8 +- .../AddRepos}/GithubReposStep/index.tsx | 16 +- .../AddRepos}/GoBackButton/index.tsx | 4 +- .../AddRepos}/LocalReposStep/index.tsx | 18 +- .../AddRepos}/PublicGithubReposStep/index.tsx | 10 +- client/src/pages/HomeTab/AddRepos/index.tsx | 6 +- .../{ => Desktop}/FeaturesStep/Feature.tsx | 0 .../{ => Desktop}/FeaturesStep/index.tsx | 8 +- .../Onboarding/Desktop/UserForm/Step1.tsx | 161 ++++++++ .../Onboarding/Desktop/UserForm/Step2.tsx | 175 +++++++++ .../Onboarding/Desktop/UserForm/index.tsx | 71 ++++ client/src/pages/Onboarding/Desktop/index.tsx | 81 ++++ .../src/pages/Onboarding/SelfServe/index.tsx | 61 +++ .../pages/Onboarding/SelfServeStep/index.tsx | 41 -- .../src/pages/Onboarding/UserForm/index.tsx | 354 ------------------ client/src/pages/Onboarding/index.tsx | 142 +------ 23 files changed, 620 insertions(+), 566 deletions(-) rename client/src/{pages/Onboarding => components/SeparateOnboardingStep}/DialogText/index.tsx (100%) rename client/src/pages/HomeTab/AddRepos/{AddCodeStudio.tsx => AddCodeStudio/index.tsx} (87%) rename client/src/pages/{Onboarding => HomeTab/AddRepos}/GithubReposStep/index.tsx (84%) rename client/src/pages/{Onboarding => HomeTab/AddRepos}/GoBackButton/index.tsx (76%) rename client/src/pages/{Onboarding => HomeTab/AddRepos}/LocalReposStep/index.tsx (87%) rename client/src/pages/{Onboarding => HomeTab/AddRepos}/PublicGithubReposStep/index.tsx (93%) rename client/src/pages/Onboarding/{ => Desktop}/FeaturesStep/Feature.tsx (100%) rename client/src/pages/Onboarding/{ => Desktop}/FeaturesStep/index.tsx (89%) create mode 100644 client/src/pages/Onboarding/Desktop/UserForm/Step1.tsx create mode 100644 client/src/pages/Onboarding/Desktop/UserForm/Step2.tsx create mode 100644 client/src/pages/Onboarding/Desktop/UserForm/index.tsx create mode 100644 client/src/pages/Onboarding/Desktop/index.tsx create mode 100644 client/src/pages/Onboarding/SelfServe/index.tsx delete mode 100644 client/src/pages/Onboarding/SelfServeStep/index.tsx delete mode 100644 client/src/pages/Onboarding/UserForm/index.tsx diff --git a/client/src/components/AddStudioContext/index.tsx b/client/src/components/AddStudioContext/index.tsx index b2d2d51ede..6d5cf08872 100644 --- a/client/src/components/AddStudioContext/index.tsx +++ b/client/src/components/AddStudioContext/index.tsx @@ -8,7 +8,7 @@ import React, { import { Trans, useTranslation } from 'react-i18next'; import { CloseSign, PlusSignInCircle } from '../../icons'; import SeparateOnboardingStep from '../SeparateOnboardingStep'; -import DialogText from '../../pages/Onboarding/DialogText'; +import DialogText from '../SeparateOnboardingStep/DialogText'; import SearchableRepoList from '../RepoList/SearchableRepoList'; import Button from '../Button'; import { diff --git a/client/src/pages/Onboarding/DialogText/index.tsx b/client/src/components/SeparateOnboardingStep/DialogText/index.tsx similarity index 100% rename from client/src/pages/Onboarding/DialogText/index.tsx rename to client/src/components/SeparateOnboardingStep/DialogText/index.tsx diff --git a/client/src/locales/en.json b/client/src/locales/en.json index b4c9187397..a86f6c4f14 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -200,7 +200,7 @@ "Something went wrong": "Something went wrong", "chats in bloop": "chats in bloop", "Setup bloop": "Setup bloop", - "Please log into your GitHub account to complete setup": "Please log into your GitHub account to complete setup", + "Please log into your GitHub account to complete setup, this helps us us combat misuse.": "Please log into your GitHub account to complete setup, this helps us us combat misuse.", "Select color theme:": "Select color theme:", "Connect account": "Connect account", "Continue": "Continue", @@ -416,5 +416,6 @@ "Search pages": "Search pages", "This page is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.": "This page is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.", "Hold <1> to add multiple files": "Hold <1> to add multiple files", - "Clear section": "Clear section" -} \ No newline at end of file + "Clear section": "Clear section", + "Let’s get you started with bloop!": "Let’s get you started with bloop!" +} diff --git a/client/src/locales/es.json b/client/src/locales/es.json index bdcfeff41b..849d86f574 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -200,7 +200,7 @@ "Something went wrong": "Algo salió mal", "chats in bloop": "Chats en bloop", "Setup bloop": "Configuración de bloop", - "Please log into your GitHub account to complete setup": "Inicie sesión con su cuenta de GitHub para completar la configuración", + "Please log into your GitHub account to complete setup, this helps us us combat misuse.": "Inicie sesión con su cuenta de GitHub para completar la configuración", "Select color theme:": "Seleccione el tema:", "Connect account": "Conectar cuenta", "Continue": "Continuar", @@ -417,5 +417,6 @@ "Search pages": "Páginas de búsqueda", "This page is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.": "Esta página no está disponible. La capacidad de generar se reanudará tan pronto como se resuelva este problema.", "Hold <1> to add multiple files": "Mantenga presionada <1> para agregar múltiples archivos", - "Clear section": "Borrar sección" + "Clear section": "Borrar sección", + "Let’s get you started with bloop!": "¡Vamos a comenzar con Bloop!" } diff --git a/client/src/locales/it.json b/client/src/locales/it.json index a51752e7fa..c728133078 100644 --- a/client/src/locales/it.json +++ b/client/src/locales/it.json @@ -200,7 +200,7 @@ "Something went wrong": "Qualcosa è andato storto", "chats in bloop": "chat in bloop", "Setup bloop": "Configura bloop", - "Please log into your GitHub account to complete setup": "Accedi al tuo account GitHub per completare la configurazione", + "Please log into your GitHub account to complete setup, this helps us us combat misuse.": "Accedi al tuo account GitHub per completare la configurazione", "Select color theme:": "Seleziona tema colori:", "Connect account": "Collega account", "Continue": "Continua", diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index bb43143331..97c086c6d0 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -200,7 +200,7 @@ "Something went wrong": "何かがうまくいかなかった", "chats in bloop": "bloopでチャット", "Setup bloop": "bloopをセットアップする", - "Please log into your GitHub account to complete setup": "セットアップを完了するため、GitHubアカウントにログインしてください", + "Please log into your GitHub account to complete setup, this helps us us combat misuse.": "セットアップを完了するため、GitHubアカウントにログインしてください", "Select color theme:": "カラーテーマを選択する", "Connect account": "アカウントを接続する", "Continue": "次へ", @@ -414,5 +414,6 @@ "Search pages": "検索ページ", "This page is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.": "このページは現在利用できません。 生成能力は、この問題が解決されるとすぐに再開されます。", "Hold <1> to add multiple files": "<1> を保持して複数のファイルを追加する", - "Clear section": "クリアセクション" -} \ No newline at end of file + "Clear section": "クリアセクション", + "Let’s get you started with bloop!": "Bloopを始めましょう!" +} diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json index b3fd4c83e3..a426376d3d 100644 --- a/client/src/locales/zh-CN.json +++ b/client/src/locales/zh-CN.json @@ -203,7 +203,7 @@ "Something went wrong": "出错了", "chats in bloop": "在bloop中对话", "Setup bloop": "设置bloop", - "Please log into your GitHub account to complete setup": "请登录您的GitHub账户以完成设置", + "Please log into your GitHub account to complete setup, this helps us us combat misuse.": "请登录您的GitHub账户以完成设置", "Select color theme:": "选择颜色主题:", "Connect account": "连接账户", "Continue": "继续", @@ -423,5 +423,6 @@ "Search pages": "搜索页", "This page is currently unavailable. Ability to generate will be resumed as soon as this issue is resolved.": "此页面当前不可用。 一旦解决此问题,就会恢复产生的能力。", "Hold <1> to add multiple files": "保持<1> 添加多个文件", - "Clear section": "清除部分" -} \ No newline at end of file + "Clear section": "清除部分", + "Let’s get you started with bloop!": "让我们开始开始Bloop!" +} diff --git a/client/src/pages/HomeTab/AddRepos/AddCodeStudio.tsx b/client/src/pages/HomeTab/AddRepos/AddCodeStudio/index.tsx similarity index 87% rename from client/src/pages/HomeTab/AddRepos/AddCodeStudio.tsx rename to client/src/pages/HomeTab/AddRepos/AddCodeStudio/index.tsx index c92d6e4d41..262c1244aa 100644 --- a/client/src/pages/HomeTab/AddRepos/AddCodeStudio.tsx +++ b/client/src/pages/HomeTab/AddRepos/AddCodeStudio/index.tsx @@ -7,10 +7,10 @@ import React, { useState, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import TextInput from '../../../components/TextInput'; -import DialogText from '../../Onboarding/DialogText'; -import { CodeStudioIcon } from '../../../icons'; -import Button from '../../../components/Button'; +import TextInput from '../../../../components/TextInput'; +import DialogText from '../../../../components/SeparateOnboardingStep/DialogText'; +import { CodeStudioIcon } from '../../../../icons'; +import Button from '../../../../components/Button'; type Props = { handleSubmit: (name: string) => void; diff --git a/client/src/pages/Onboarding/GithubReposStep/index.tsx b/client/src/pages/HomeTab/AddRepos/GithubReposStep/index.tsx similarity index 84% rename from client/src/pages/Onboarding/GithubReposStep/index.tsx rename to client/src/pages/HomeTab/AddRepos/GithubReposStep/index.tsx index 495f3ff4b2..19f6cf8b70 100644 --- a/client/src/pages/Onboarding/GithubReposStep/index.tsx +++ b/client/src/pages/HomeTab/AddRepos/GithubReposStep/index.tsx @@ -1,19 +1,18 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import DialogText from '../DialogText'; -import Button from '../../../components/Button'; -import { ArrowRight } from '../../../icons'; -import SearchableRepoList from '../../../components/RepoList/SearchableRepoList'; +import DialogText from '../../../../components/SeparateOnboardingStep/DialogText'; +import Button from '../../../../components/Button'; +import { ArrowRight } from '../../../../icons'; +import SearchableRepoList from '../../../../components/RepoList/SearchableRepoList'; import { RepoProvider, RepoType, RepoUi, SyncStatus, -} from '../../../types/general'; +} from '../../../../types/general'; import GoBackButton from '../GoBackButton'; -import { UIContext } from '../../../context/uiContext'; -import { splitPath } from '../../../utils'; -import { RepositoriesContext } from '../../../context/repositoriesContext'; +import { splitPath } from '../../../../utils'; +import { RepositoriesContext } from '../../../../context/repositoriesContext'; type Props = { handleNext: (e?: any) => void; @@ -21,7 +20,6 @@ type Props = { disableSkip?: boolean; }; -export const STEP_KEY = 'STEP_GITHUB_REPOS'; let intervalId: number; const GithubReposStep = ({ handleNext, handleBack, disableSkip }: Props) => { diff --git a/client/src/pages/Onboarding/GoBackButton/index.tsx b/client/src/pages/HomeTab/AddRepos/GoBackButton/index.tsx similarity index 76% rename from client/src/pages/Onboarding/GoBackButton/index.tsx rename to client/src/pages/HomeTab/AddRepos/GoBackButton/index.tsx index 742cf99542..41e6c954be 100644 --- a/client/src/pages/Onboarding/GoBackButton/index.tsx +++ b/client/src/pages/HomeTab/AddRepos/GoBackButton/index.tsx @@ -1,5 +1,5 @@ -import Button from '../../../components/Button'; -import { ArrowLeft } from '../../../icons'; +import Button from '../../../../components/Button'; +import { ArrowLeft } from '../../../../icons'; type Props = { handleBack: (e: any) => void; diff --git a/client/src/pages/Onboarding/LocalReposStep/index.tsx b/client/src/pages/HomeTab/AddRepos/LocalReposStep/index.tsx similarity index 87% rename from client/src/pages/Onboarding/LocalReposStep/index.tsx rename to client/src/pages/HomeTab/AddRepos/LocalReposStep/index.tsx index 2c49e85a59..6c4f8e4686 100644 --- a/client/src/pages/Onboarding/LocalReposStep/index.tsx +++ b/client/src/pages/HomeTab/AddRepos/LocalReposStep/index.tsx @@ -1,15 +1,15 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import DialogText from '../DialogText'; -import Button from '../../../components/Button'; -import { ArrowRight } from '../../../icons'; -import SearchableRepoList from '../../../components/RepoList/SearchableRepoList'; -import { scanLocalRepos, syncRepo } from '../../../services/api'; -import { RepoType, RepoUi } from '../../../types/general'; +import DialogText from '../../../../components/SeparateOnboardingStep/DialogText'; +import Button from '../../../../components/Button'; +import { ArrowRight } from '../../../../icons'; +import SearchableRepoList from '../../../../components/RepoList/SearchableRepoList'; +import { scanLocalRepos, syncRepo } from '../../../../services/api'; +import { RepoType, RepoUi } from '../../../../types/general'; import GoBackButton from '../GoBackButton'; -import { splitPath } from '../../../utils'; -import { DeviceContext } from '../../../context/deviceContext'; -import { RepositoriesContext } from '../../../context/repositoriesContext'; +import { splitPath } from '../../../../utils'; +import { DeviceContext } from '../../../../context/deviceContext'; +import { RepositoriesContext } from '../../../../context/repositoriesContext'; type Props = { handleNext: (e?: any) => void; diff --git a/client/src/pages/Onboarding/PublicGithubReposStep/index.tsx b/client/src/pages/HomeTab/AddRepos/PublicGithubReposStep/index.tsx similarity index 93% rename from client/src/pages/Onboarding/PublicGithubReposStep/index.tsx rename to client/src/pages/HomeTab/AddRepos/PublicGithubReposStep/index.tsx index 9cf4be3b5d..19e7de4527 100644 --- a/client/src/pages/Onboarding/PublicGithubReposStep/index.tsx +++ b/client/src/pages/HomeTab/AddRepos/PublicGithubReposStep/index.tsx @@ -1,12 +1,12 @@ import React, { FormEvent, useCallback, useState } from 'react'; import axios from 'axios'; import { Trans, useTranslation } from 'react-i18next'; -import DialogText from '../DialogText'; -import Button from '../../../components/Button'; -import { ArrowRight, Globe2 } from '../../../icons'; +import DialogText from '../../../../components/SeparateOnboardingStep/DialogText'; +import Button from '../../../../components/Button'; +import { ArrowRight, Globe2 } from '../../../../icons'; import GoBackButton from '../GoBackButton'; -import TextInput from '../../../components/TextInput'; -import { syncRepo } from '../../../services/api'; +import TextInput from '../../../../components/TextInput'; +import { syncRepo } from '../../../../services/api'; type Props = { handleNext: (e?: any) => void; diff --git a/client/src/pages/HomeTab/AddRepos/index.tsx b/client/src/pages/HomeTab/AddRepos/index.tsx index 00c41d716f..096213af44 100644 --- a/client/src/pages/HomeTab/AddRepos/index.tsx +++ b/client/src/pages/HomeTab/AddRepos/index.tsx @@ -1,9 +1,9 @@ import { memo, useCallback } from 'react'; -import LocalReposStep from '../../Onboarding/LocalReposStep'; -import GithubReposStep from '../../Onboarding/GithubReposStep'; import SeparateOnboardingStep from '../../../components/SeparateOnboardingStep'; -import PublicGithubReposStep from '../../Onboarding/PublicGithubReposStep'; import useKeyboardNavigation from '../../../hooks/useKeyboardNavigation'; +import LocalReposStep from './LocalReposStep'; +import GithubReposStep from './GithubReposStep'; +import PublicGithubReposStep from './PublicGithubReposStep'; import AddCodeStudio from './AddCodeStudio'; type Props = { diff --git a/client/src/pages/Onboarding/FeaturesStep/Feature.tsx b/client/src/pages/Onboarding/Desktop/FeaturesStep/Feature.tsx similarity index 100% rename from client/src/pages/Onboarding/FeaturesStep/Feature.tsx rename to client/src/pages/Onboarding/Desktop/FeaturesStep/Feature.tsx diff --git a/client/src/pages/Onboarding/FeaturesStep/index.tsx b/client/src/pages/Onboarding/Desktop/FeaturesStep/index.tsx similarity index 89% rename from client/src/pages/Onboarding/FeaturesStep/index.tsx rename to client/src/pages/Onboarding/Desktop/FeaturesStep/index.tsx index 43a17c7f1d..cb4a73e16c 100644 --- a/client/src/pages/Onboarding/FeaturesStep/index.tsx +++ b/client/src/pages/Onboarding/Desktop/FeaturesStep/index.tsx @@ -1,9 +1,9 @@ import React, { useCallback } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import Button from '../../../components/Button'; -import { ChatBubble, PointClick, CodeStudioIcon } from '../../../icons'; -import DialogText from '../DialogText'; -import GoBackButton from '../GoBackButton'; +import Button from '../../../../components/Button'; +import { ChatBubble, PointClick, CodeStudioIcon } from '../../../../icons'; +import DialogText from '../../../../components/SeparateOnboardingStep/DialogText'; +import GoBackButton from '../../../HomeTab/AddRepos/GoBackButton'; import Feature from './Feature'; type Props = { diff --git a/client/src/pages/Onboarding/Desktop/UserForm/Step1.tsx b/client/src/pages/Onboarding/Desktop/UserForm/Step1.tsx new file mode 100644 index 0000000000..adba3e4627 --- /dev/null +++ b/client/src/pages/Onboarding/Desktop/UserForm/Step1.tsx @@ -0,0 +1,161 @@ +import React, { + Dispatch, + memo, + SetStateAction, + useCallback, + useContext, + useState, +} from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import TextInput from '../../../../components/TextInput'; +import { EMAIL_REGEX } from '../../../../consts/validations'; +import Dropdown from '../../../../components/Dropdown/Normal'; +import { themesMap } from '../../../../components/Settings/Preferences'; +import { MenuItemType } from '../../../../types/general'; +import { Theme } from '../../../../types'; +import { previewTheme } from '../../../../utils'; +import Button from '../../../../components/Button'; +import { UIContext } from '../../../../context/uiContext'; +import { Form } from '../../index'; +import { DeviceContext } from '../../../../context/deviceContext'; + +type Props = { + form: Form; + setForm: Dispatch>; + onContinue: () => void; +}; + +const UserFormStep1 = ({ form, setForm, onContinue }: Props) => { + const { t } = useTranslation(); + const { theme, setTheme } = useContext(UIContext.Theme); + const { openLink } = useContext(DeviceContext); + const [showErrors, setShowErrors] = useState(false); + + const handleSubmit = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if ( + !form.firstName || + !form.lastName || + !form.email || + !!form.emailError || + !EMAIL_REGEX.test(form.email) + ) { + if (!EMAIL_REGEX.test(form.email)) { + setForm((prev) => ({ + ...prev, + emailError: t('Email is not valid'), + })); + } + setShowErrors(true); + return; + } + onContinue(); + }, + [form, onContinue], + ); + + return ( +
e.preventDefault()} + > + + setForm((prev) => ({ ...prev, firstName: e.target.value })) + } + autoFocus + error={ + showErrors && !form.firstName + ? t('First name is required') + : undefined + } + /> + + setForm((prev) => ({ ...prev, lastName: e.target.value })) + } + error={ + showErrors && !form.lastName ? t('Last name is required') : undefined + } + /> + + setForm((prev) => ({ + ...prev, + email: e.target.value, + emailError: null, + })) + } + validate={() => { + if (form.email && !EMAIL_REGEX.test(form.email)) { + setForm((prev) => ({ + ...prev, + emailError: t('Email is not valid'), + })); + } + }} + error={ + form.emailError || + (showErrors && !form.email ? t('Email is required') : undefined) + } + name="email" + placeholder={t('Email address')} + /> +
+ + Select color theme: + + } + btnClassName="w-full border-transparent" + items={Object.entries(themesMap).map(([key, name]) => ({ + type: MenuItemType.DEFAULT, + text: t(name), + onClick: () => setTheme(key as Theme), + onMouseOver: () => previewTheme(key), + }))} + onClose={() => previewTheme(theme)} + selected={{ + type: MenuItemType.DEFAULT, + text: t(themesMap[theme]), + }} + /> +
+ +

+ By continuing you accept our +
+ {' '} + and + +

+ + ); +}; + +export default memo(UserFormStep1); diff --git a/client/src/pages/Onboarding/Desktop/UserForm/Step2.tsx b/client/src/pages/Onboarding/Desktop/UserForm/Step2.tsx new file mode 100644 index 0000000000..c4cacd2569 --- /dev/null +++ b/client/src/pages/Onboarding/Desktop/UserForm/Step2.tsx @@ -0,0 +1,175 @@ +import React, { + memo, + useCallback, + useContext, + useEffect, + useState, +} from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { ChevronRight, GitHubLogo } from '../../../../icons'; +import Tooltip from '../../../../components/Tooltip'; +import { UIContext } from '../../../../context/uiContext'; +import { DeviceContext } from '../../../../context/deviceContext'; +import { getConfig, githubLogin, githubLogout } from '../../../../services/api'; +import { copyToClipboard } from '../../../../utils'; +import Button from '../../../../components/Button'; +import { polling } from '../../../../utils/requestUtils'; + +type Props = { + onContinue: () => void; +}; + +const UserFormStep2 = ({ onContinue }: Props) => { + const { t } = useTranslation(); + const { isGithubConnected, setGithubConnected } = useContext( + UIContext.GitHubConnected, + ); + const { envConfig, openLink, setEnvConfig } = useContext(DeviceContext); + const [isTimedOut, setIsTimedOut] = useState(false); + const [isBtnClicked, setBtnClicked] = useState(false); + const [loginUrl, setLoginUrl] = useState(''); + const [isLinkShown, setLinkShown] = useState(false); + const [isLinkCopied, setLinkCopied] = useState(false); + + const handleLogout = useCallback(() => { + githubLogout(); + setGithubConnected(false); + }, []); + + const onClick = useCallback(() => { + if (isGithubConnected) { + handleLogout(); + setBtnClicked(false); + } else { + if (isTimedOut) { + setIsTimedOut(false); + githubLogin().then((data) => { + setLoginUrl(data.authentication_needed.url); + openLink(data.authentication_needed.url); + }); + } else { + openLink(loginUrl); + } + setBtnClicked(true); + } + }, [isGithubConnected, loginUrl, openLink, isTimedOut]); + + const handleCopy = useCallback(() => { + copyToClipboard(loginUrl); + setLinkCopied(true); + setTimeout(() => setLinkCopied(false), 2000); + }, [loginUrl]); + + useEffect(() => { + githubLogin().then((data) => { + setLoginUrl(data.authentication_needed.url); + }); + }, []); + + const checkGHAuth = useCallback(async () => { + const d = await getConfig(); + setGithubConnected(!!d.user_login); + setEnvConfig((prev) => + JSON.stringify(prev) === JSON.stringify(d) ? prev : d, + ); + return d; + }, []); + + useEffect(() => { + let intervalId: number; + let timerId: number; + if (loginUrl && !isGithubConnected) { + checkGHAuth(); + intervalId = polling( + () => + checkGHAuth().then((d) => { + if (!!d.user_login) { + onContinue(); + } + }), + 500, + ); + timerId = window.setTimeout( + () => { + clearInterval(intervalId); + setBtnClicked(false); + setIsTimedOut(true); + }, + 10 * 60 * 1000, + ); + } + + return () => { + clearInterval(intervalId); + clearTimeout(timerId); + }; + }, [loginUrl, isGithubConnected, checkGHAuth, onContinue]); + + useEffect(() => { + if (loginUrl) { + checkGHAuth(); + } + }, [loginUrl, checkGHAuth]); + + return ( +
+
+
+ +

+ {isGithubConnected ? envConfig.user_login : 'GitHub'} +

+
+ +
+ {!isGithubConnected && ( +
+ {isLinkShown ? ( + +

+ {loginUrl} +

+
+ ) : ( +

+ or go to the following link{' '} + +

+ )} +
+ )} +
+ ); +}; + +export default memo(UserFormStep2); diff --git a/client/src/pages/Onboarding/Desktop/UserForm/index.tsx b/client/src/pages/Onboarding/Desktop/UserForm/index.tsx new file mode 100644 index 0000000000..bd67d4f99d --- /dev/null +++ b/client/src/pages/Onboarding/Desktop/UserForm/index.tsx @@ -0,0 +1,71 @@ +import React, { Dispatch, SetStateAction, useContext, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { BloopLogo } from '../../../../icons'; +import Button from '../../../../components/Button'; +import { DeviceContext } from '../../../../context/deviceContext'; +import { Form } from '../../index'; +import LanguageSelector from '../../../../components/LanguageSelector'; +import Step1 from './Step1'; +import Step2 from './Step2'; + +type Props = { + form: Form; + setForm: Dispatch>; + onContinue: () => void; +}; + +const UserForm = ({ form, setForm, onContinue }: Props) => { + useTranslation(); + const { envConfig } = useContext(DeviceContext); + const [step, setStep] = useState(0); + + return ( + <> +
+
+ {step === 0 ? ( +
+ ) : ( + + )} + +
+
+
+ +
+

+ Setup bloop +

+ {envConfig.credentials_upgrade && ( +

+ + We’ve updated our auth service to make bloop more secure, please + reauthorise your client with GitHub + +

+ )} +

+ {step === 0 ? ( + Let’s get you started with bloop! + ) : ( + + Please log into your GitHub account to complete setup, this + helps us us combat misuse. + + )} +

+
+
+ {step === 0 ? ( + setStep(1)} /> + ) : ( + + )} + + ); +}; + +export default UserForm; diff --git a/client/src/pages/Onboarding/Desktop/index.tsx b/client/src/pages/Onboarding/Desktop/index.tsx new file mode 100644 index 0000000000..085ea867aa --- /dev/null +++ b/client/src/pages/Onboarding/Desktop/index.tsx @@ -0,0 +1,81 @@ +import React, { memo, useCallback, useContext, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import NavBar from '../../../components/NavBar'; +import { DeviceContext } from '../../../context/deviceContext'; +import { + getJsonFromStorage, + saveJsonToStorage, + USER_DATA_FORM, +} from '../../../services/storage'; +import { Form } from '../index'; +import { saveUserData } from '../../../services/api'; +import SeparateOnboardingStep from '../../../components/SeparateOnboardingStep'; +import FeaturesStep from './FeaturesStep'; +import UserForm from './UserForm'; + +type Props = { + activeTab: string; + closeOnboarding: () => void; +}; + +const Desktop = ({ activeTab, closeOnboarding }: Props) => { + const { t } = useTranslation(); + const [shouldShowPopup, setShouldShowPopup] = useState(false); + const [form, setForm] = useState
({ + firstName: '', + lastName: '', + email: '', + emailError: null, + ...getJsonFromStorage(USER_DATA_FORM), + }); + const { os, envConfig } = useContext(DeviceContext); + + const onSubmit = useCallback(() => { + saveUserData({ + email: form.email, + first_name: form.firstName, + last_name: form.lastName, + unique_id: envConfig.tracking_id || '', + }); + saveJsonToStorage(USER_DATA_FORM, form); + closeOnboarding(); + setTimeout(() => setShouldShowPopup(true), 1000); + }, [form, envConfig.tracking_id]); + + return ( +
+ {os.type === 'Darwin' && } + +
+
+
+ +
+
+ +
+ setShouldShowPopup(false)} + > + setShouldShowPopup(false)} /> + +
+ ); +}; + +export default memo(Desktop); diff --git a/client/src/pages/Onboarding/SelfServe/index.tsx b/client/src/pages/Onboarding/SelfServe/index.tsx new file mode 100644 index 0000000000..810db2ef07 --- /dev/null +++ b/client/src/pages/Onboarding/SelfServe/index.tsx @@ -0,0 +1,61 @@ +import React, { memo, useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router-dom'; +import NavBar from '../../../components/NavBar'; +import StatusBar from '../../../components/StatusBar'; +import { githubLogin } from '../../../services/api'; +import DialogText from '../../../components/SeparateOnboardingStep/DialogText'; +import Button from '../../../components/Button'; +import { GitHubLogo } from '../../../icons'; + +type Props = { + activeTab: string; +}; + +const SelfServe = ({ activeTab }: Props) => { + const { t } = useTranslation(); + const [loginUrl, setLoginUrl] = useState(''); + const location = useLocation(); + + useEffect(() => { + githubLogin( + encodeURIComponent( + encodeURIComponent( + `${location.pathname}${location.search}${location.hash}`, + ), + ), + ).then((resp) => { + setLoginUrl(resp.authentication_needed.url); + }); + }, []); + + return ( +
+ +
+ +
+ +
+ ); +}; + +export default memo(SelfServe); diff --git a/client/src/pages/Onboarding/SelfServeStep/index.tsx b/client/src/pages/Onboarding/SelfServeStep/index.tsx deleted file mode 100644 index 3b030f3b87..0000000000 --- a/client/src/pages/Onboarding/SelfServeStep/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { useTranslation, Trans } from 'react-i18next'; -import DialogText from '../DialogText'; -import Button from '../../../components/Button'; -import { GitHubLogo } from '../../../icons'; -import { githubLogin } from '../../../services/api'; - -const SelfServeStep = () => { - const { t } = useTranslation(); - const [loginUrl, setLoginUrl] = useState(''); - const location = useLocation(); - - useEffect(() => { - githubLogin( - encodeURIComponent( - encodeURIComponent( - `${location.pathname}${location.search}${location.hash}`, - ), - ), - ).then((resp) => { - setLoginUrl(resp.authentication_needed.url); - }); - }, []); - - return ( - <> - - - - - - ); -}; - -export default SelfServeStep; diff --git a/client/src/pages/Onboarding/UserForm/index.tsx b/client/src/pages/Onboarding/UserForm/index.tsx deleted file mode 100644 index 53eadb0689..0000000000 --- a/client/src/pages/Onboarding/UserForm/index.tsx +++ /dev/null @@ -1,354 +0,0 @@ -import React, { - Dispatch, - SetStateAction, - useCallback, - useContext, - useEffect, - useState, -} from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { BloopLogo, ChevronRight, GitHubLogo } from '../../../icons'; -import TextInput from '../../../components/TextInput'; -import { EMAIL_REGEX } from '../../../consts/validations'; -import Button from '../../../components/Button'; -import { UIContext } from '../../../context/uiContext'; -import { DeviceContext } from '../../../context/deviceContext'; -import { getConfig, githubLogout, githubLogin } from '../../../services/api'; -import { Form } from '../index'; -import Dropdown from '../../../components/Dropdown/Normal'; -import { MenuItemType } from '../../../types/general'; -import { Theme } from '../../../types'; -import { themesMap } from '../../../components/Settings/Preferences'; -import { copyToClipboard, previewTheme } from '../../../utils'; -import LanguageSelector from '../../../components/LanguageSelector'; -import Tooltip from '../../../components/Tooltip'; -import { polling } from '../../../utils/requestUtils'; - -type Props = { - form: Form; - setForm: Dispatch>; - onContinue: () => void; -}; - -const UserForm = ({ form, setForm, onContinue }: Props) => { - const { t } = useTranslation(); - const { isGithubConnected, setGithubConnected } = useContext( - UIContext.GitHubConnected, - ); - const { envConfig, openLink, setEnvConfig } = useContext(DeviceContext); - const { theme, setTheme } = useContext(UIContext.Theme); - const [loginUrl, setLoginUrl] = useState(''); - const [isLinkShown, setLinkShown] = useState(false); - const [isBtnClicked, setBtnClicked] = useState(false); - const [isLinkCopied, setLinkCopied] = useState(false); - const [isTimedOut, setIsTimedOut] = useState(false); - const [showErrors, setShowErrors] = useState(false); - - const handleLogout = useCallback(() => { - githubLogout(); - setGithubConnected(false); - }, []); - - useEffect(() => { - githubLogin().then((data) => { - setLoginUrl(data.authentication_needed.url); - }); - }, []); - - const onClick = useCallback(() => { - if (isGithubConnected) { - handleLogout(); - setBtnClicked(false); - } else { - if (isTimedOut) { - setIsTimedOut(false); - githubLogin().then((data) => { - setLoginUrl(data.authentication_needed.url); - openLink(data.authentication_needed.url); - }); - } else { - openLink(loginUrl); - } - setBtnClicked(true); - } - }, [isGithubConnected, loginUrl, openLink, isTimedOut]); - - const checkGHAuth = useCallback(async () => { - const d = await getConfig(); - setGithubConnected(!!d.user_login); - setEnvConfig((prev) => - JSON.stringify(prev) === JSON.stringify(d) ? prev : d, - ); - return d; - }, []); - - useEffect(() => { - let intervalId: number; - let timerId: number; - if (loginUrl && !isGithubConnected) { - intervalId = polling( - () => - checkGHAuth().then((d) => { - if ( - !!d.user_login && - form.firstName && - form.lastName && - form.email && - !form.emailError && - document.activeElement?.tagName !== 'INPUT' - ) { - onContinue(); - } - }), - 500, - ); - timerId = window.setTimeout( - () => { - clearInterval(intervalId); - setBtnClicked(false); - setIsTimedOut(true); - }, - 10 * 60 * 1000, - ); - } - - return () => { - clearInterval(intervalId); - clearTimeout(timerId); - }; - }, [loginUrl, isGithubConnected, form, checkGHAuth]); - - useEffect(() => { - if (loginUrl) { - checkGHAuth(); - } - }, [loginUrl, checkGHAuth]); - - const handleCopy = useCallback(() => { - copyToClipboard(loginUrl); - setLinkCopied(true); - setTimeout(() => setLinkCopied(false), 2000); - }, [loginUrl]); - - const handleSubmit = useCallback( - (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - if ( - !isGithubConnected || - !form.firstName || - !form.lastName || - !form.email || - !!form.emailError || - !EMAIL_REGEX.test(form.email) - ) { - if (!EMAIL_REGEX.test(form.email)) { - setForm((prev) => ({ - ...prev, - emailError: t('Email is not valid'), - })); - } - setShowErrors(true); - return; - } - onContinue(); - }, - [form, isGithubConnected, onContinue], - ); - - return ( - <> -
-
- -
-
-
- -
-

- Setup bloop -

- {envConfig.credentials_upgrade && ( -

- - We’ve updated our auth service to make bloop more secure, please - reauthorise your client with GitHub - -

- )} -

- Please log into your GitHub account to complete setup -

-
-
- e.preventDefault()} - > - - setForm((prev) => ({ ...prev, firstName: e.target.value })) - } - autoFocus - error={ - showErrors && !form.firstName - ? t('First name is required') - : undefined - } - /> - - setForm((prev) => ({ ...prev, lastName: e.target.value })) - } - error={ - showErrors && !form.lastName - ? t('Last name is required') - : undefined - } - /> - - setForm((prev) => ({ - ...prev, - email: e.target.value, - emailError: null, - })) - } - validate={() => { - if (form.email && !EMAIL_REGEX.test(form.email)) { - setForm((prev) => ({ - ...prev, - emailError: t('Email is not valid'), - })); - } - }} - error={ - form.emailError || - (showErrors && !form.email ? t('Email is required') : undefined) - } - name="email" - placeholder={t('Email address')} - /> -
- - Select color theme: - - } - btnClassName="w-full border-transparent" - items={Object.entries(themesMap).map(([key, name]) => ({ - type: MenuItemType.DEFAULT, - text: t(name), - onClick: () => setTheme(key as Theme), - onMouseOver: () => previewTheme(key), - }))} - onClose={() => previewTheme(theme)} - selected={{ - type: MenuItemType.DEFAULT, - text: t(themesMap[theme]), - }} - /> -
-
- -

- {isGithubConnected ? envConfig.user_login : 'GitHub'} -

- -
- {showErrors && !isGithubConnected && ( -

- Connect GitHub account to continue -

- )} - {!isGithubConnected && ( -
- {isLinkShown ? ( - -

- {loginUrl} -

-
- ) : ( -

- or go to the following link{' '} - -

- )} -
- )} - - - {isGithubConnected && ( -

- By continuing you accept our -
- {' '} - and - -

- )} - - ); -}; - -export default UserForm; diff --git a/client/src/pages/Onboarding/index.tsx b/client/src/pages/Onboarding/index.tsx index e2c1b1b56e..26ba24d1c5 100644 --- a/client/src/pages/Onboarding/index.tsx +++ b/client/src/pages/Onboarding/index.tsx @@ -1,31 +1,15 @@ -import React, { - memo, - useCallback, - useContext, - useEffect, - useState, -} from 'react'; -import { useTranslation } from 'react-i18next'; -import { useLocation } from 'react-router-dom'; +import React, { memo, useCallback, useContext, useEffect } from 'react'; import { UIContext } from '../../context/uiContext'; import { DeviceContext } from '../../context/deviceContext'; -import NavBar from '../../components/NavBar'; import { - getJsonFromStorage, getPlainFromStorage, ONBOARDING_DONE_KEY, REFRESH_TOKEN_KEY, - saveJsonToStorage, savePlainToStorage, - SESSION_ID_KEY, - USER_DATA_FORM, } from '../../services/storage'; -import { getConfig, getRepos, saveUserData } from '../../services/api'; -import SeparateOnboardingStep from '../../components/SeparateOnboardingStep'; -import StatusBar from '../../components/StatusBar'; -import UserForm from './UserForm'; -import FeaturesStep from './FeaturesStep'; -import SelfServeStep from './SelfServeStep'; +import { getConfig, refreshToken } from '../../services/api'; +import SelfServe from './SelfServe'; +import Desktop from './Desktop'; export type Form = { firstName: string; @@ -35,43 +19,16 @@ export type Form = { }; const Onboarding = ({ activeTab }: { activeTab: string }) => { - const { t } = useTranslation(); - const [form, setForm] = useState
({ - firstName: '', - lastName: '', - email: '', - emailError: null, - ...getJsonFromStorage(USER_DATA_FORM), - }); - const [shouldShowPopup, setShouldShowPopup] = useState(false); const { shouldShowWelcome, setShouldShowWelcome } = useContext( UIContext.Onboarding, ); - const { isGithubConnected, refreshToken } = useContext( - UIContext.GitHubConnected, - ); - const { isSelfServe, os, setEnvConfig, envConfig } = - useContext(DeviceContext); - const location = useLocation(); + const { isSelfServe, setEnvConfig, envConfig } = useContext(DeviceContext); const closeOnboarding = useCallback(() => { setShouldShowWelcome(false); savePlainToStorage(ONBOARDING_DONE_KEY, 'true'); }, []); - useEffect(() => { - if (import.meta.env.ONBOARDING) { - if ( - getPlainFromStorage(SESSION_ID_KEY) !== - window.__APP_SESSION__.toString() - ) { - localStorage.removeItem(ONBOARDING_DONE_KEY); - savePlainToStorage(SESSION_ID_KEY, window.__APP_SESSION__.toString()); - setShouldShowWelcome(true); - } - } - }, []); - useEffect(() => { if (isSelfServe) { let token: string | null = null; @@ -96,88 +53,29 @@ const Onboarding = ({ activeTab }: { activeTab: string }) => { setShouldShowWelcome(true); } } else { - getConfig() - .then((d) => { - setEnvConfig(d); - if (!d.user_login) { - setShouldShowWelcome(true); - } else { - closeOnboarding(); - } - }) - .catch(() => { - setShouldShowWelcome(true); - }); + getConfig().then((d) => { + setEnvConfig(d); + }); } }, []); - const onSubmit = useCallback(() => { - saveUserData({ - email: form.email, - first_name: form.firstName, - last_name: form.lastName, - unique_id: envConfig.tracking_id || '', - }); - saveJsonToStorage(USER_DATA_FORM, form); - closeOnboarding(); - setTimeout(() => setShouldShowPopup(true), 1000); - }, [form, envConfig.tracking_id]); + useEffect(() => { + if (!isSelfServe) { + if (!envConfig.user_login) { + setShouldShowWelcome(true); + } else { + closeOnboarding(); + } + } + }, [envConfig]); return shouldShowWelcome ? ( isSelfServe ? ( -
- -
-
-
-
-
- -
-
-
-
-
- -
+ ) : ( -
- {os.type === 'Darwin' && } - -
-
-
- -
-
- -
-
+ ) - ) : ( - setShouldShowPopup(false)} - > - setShouldShowPopup(false)} /> - - ); + ) : null; }; export default memo(Onboarding);