From 8152b45f720eb1baccd3acd1ba549c70da2aa47a Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 23 Feb 2023 23:02:27 +0000 Subject: [PATCH 1/6] add react-confetti dep --- components/dashboard/package.json | 1 + yarn.lock | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/components/dashboard/package.json b/components/dashboard/package.json index 23e009eb720bf2..bcf027677f41d5 100644 --- a/components/dashboard/package.json +++ b/components/dashboard/package.json @@ -23,6 +23,7 @@ "monaco-editor": "^0.25.2", "query-string": "^7.1.1", "react": "^17.0.1", + "react-confetti": "^6.1.0", "react-datepicker": "^4.8.0", "react-dom": "^17.0.1", "react-intl-tel-input": "^8.2.0", diff --git a/yarn.lock b/yarn.lock index d3004bf09e061d..671d7791fe5c24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15129,6 +15129,13 @@ react-app-polyfill@^2.0.0: regenerator-runtime "^0.13.7" whatwg-fetch "^3.4.1" +react-confetti@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.1.0.tgz#03dc4340d955acd10b174dbf301f374a06e29ce6" + integrity sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw== + dependencies: + tween-functions "^1.2.0" + react-datepicker@^4.8.0: version "4.8.0" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.8.0.tgz#11b8918d085a1ce4781eee4c8e4641b3cd592010" @@ -17740,6 +17747,11 @@ tunnel@0.0.6: resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== +tween-functions@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff" + integrity sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" From b62d94c0ad203602f68444f764343ba09dd9db7b Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 23 Feb 2023 23:03:08 +0000 Subject: [PATCH 2/6] Adding ConfettiContextProvider --- components/dashboard/src/app/AppRoutes.tsx | 2 +- .../src/contexts/ConfettiContext.tsx | 44 ++++++++++++++++++ components/dashboard/src/index.tsx | 45 ++++++++++--------- 3 files changed, 69 insertions(+), 22 deletions(-) create mode 100644 components/dashboard/src/contexts/ConfettiContext.tsx diff --git a/components/dashboard/src/app/AppRoutes.tsx b/components/dashboard/src/app/AppRoutes.tsx index f10a1f4e132bf8..09a737d6a423d4 100644 --- a/components/dashboard/src/app/AppRoutes.tsx +++ b/components/dashboard/src/app/AppRoutes.tsx @@ -6,7 +6,7 @@ import { ContextURL, Team, User } from "@gitpod/gitpod-protocol"; import React, { FunctionComponent, useContext, useState } from "react"; -import { Redirect, Route, Switch, useLocation, useParams } from "react-router"; +import { Redirect, Route, Switch, useLocation } from "react-router"; import { AppNotifications } from "../AppNotifications"; import Menu from "../menu/Menu"; import OAuthClientApproval from "../OauthClientApproval"; diff --git a/components/dashboard/src/contexts/ConfettiContext.tsx b/components/dashboard/src/contexts/ConfettiContext.tsx new file mode 100644 index 00000000000000..90bc2832567449 --- /dev/null +++ b/components/dashboard/src/contexts/ConfettiContext.tsx @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +import { lazy, createContext, FC, useMemo, useState, useContext } from "react"; + +const Confetti = lazy(() => import(/* webpackPrefetch: true */ "react-confetti")); + +type ConfettiContextType = { + isConfettiShowing: boolean; + showConfetti(): void; + hideConfetti(): void; +}; +const ConfettiContext = createContext({ + isConfettiShowing: false, + showConfetti: () => undefined, + hideConfetti: () => undefined, +}); + +export const ConfettiContextProvider: FC = ({ children }) => { + const [showConfetti, setShowConfetti] = useState(false); + const value = useMemo(() => { + return { + isConfettiShowing: showConfetti, + showConfetti: () => setShowConfetti(true), + hideConfetti: () => setShowConfetti(false), + }; + }, [showConfetti]); + + return ( + + {children} + {showConfetti && ( + setShowConfetti(false)} /> + )} + + ); +}; + +export const useConfetti = () => { + return useContext(ConfettiContext); +}; diff --git a/components/dashboard/src/index.tsx b/components/dashboard/src/index.tsx index f2f0de983823a0..b3dce8cde2f60d 100644 --- a/components/dashboard/src/index.tsx +++ b/components/dashboard/src/index.tsx @@ -25,6 +25,7 @@ import { isWebsiteSlug } from "./utils"; import "./index.css"; import { setupQueryClientProvider } from "./data/setup"; +import { ConfettiContextProvider } from "./contexts/ConfettiContext"; const bootApp = () => { // gitpod.io specific boot logic @@ -57,27 +58,29 @@ const bootApp = () => { ReactDOM.render( - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + , document.getElementById("root"), From 52859427f9bb9216afb68279abd566caa55945b5 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 23 Feb 2023 23:03:27 +0000 Subject: [PATCH 3/6] show confetti on completion --- components/dashboard/src/onboarding/UserOnboarding.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/dashboard/src/onboarding/UserOnboarding.tsx b/components/dashboard/src/onboarding/UserOnboarding.tsx index 4a3236524912b8..6d02396c4ec3de 100644 --- a/components/dashboard/src/onboarding/UserOnboarding.tsx +++ b/components/dashboard/src/onboarding/UserOnboarding.tsx @@ -15,6 +15,7 @@ import { StepOrgInfo } from "./StepOrgInfo"; import { StepPersonalize } from "./StepPersonalize"; import { useUpdateCurrentUserMutation } from "../data/current-user/update-mutation"; import Alert from "../components/Alert"; +import { useConfetti } from "../contexts/ConfettiContext"; // This param is optionally present to force an onboarding flow // Can be used if other conditions aren't true, i.e. if user has already onboarded, but we want to force the flow again @@ -34,6 +35,7 @@ const UserOnboarding: FunctionComponent = ({ user }) => { const location = useLocation(); const { setUser } = useContext(UserContext); const updateUser = useUpdateCurrentUserMutation(); + const { showConfetti } = useConfetti(); const [step, setStep] = useState(STEPS.ONE); const [completingError, setCompletingError] = useState(""); @@ -73,6 +75,7 @@ const UserOnboarding: FunctionComponent = ({ user }) => { try { const onboardedUser = await updateUser.mutateAsync(updates); + showConfetti(); setUser(onboardedUser); // Look for the `onboarding=force` query param, and remove if present @@ -101,6 +104,7 @@ const UserOnboarding: FunctionComponent = ({ user }) => { location.pathname, location.search, setUser, + showConfetti, updateUser, ], ); From 9ab238a9a8970ba4de870b599b47dd0dbe91789f Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 23 Feb 2023 23:19:21 +0000 Subject: [PATCH 4/6] renaming --- .../src/contexts/ConfettiContext.tsx | 22 +++++++++---------- .../src/onboarding/UserOnboarding.tsx | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/dashboard/src/contexts/ConfettiContext.tsx b/components/dashboard/src/contexts/ConfettiContext.tsx index 90bc2832567449..058a9772829546 100644 --- a/components/dashboard/src/contexts/ConfettiContext.tsx +++ b/components/dashboard/src/contexts/ConfettiContext.tsx @@ -9,31 +9,31 @@ import { lazy, createContext, FC, useMemo, useState, useContext } from "react"; const Confetti = lazy(() => import(/* webpackPrefetch: true */ "react-confetti")); type ConfettiContextType = { - isConfettiShowing: boolean; - showConfetti(): void; + isConfettiDropping: boolean; + dropConfetti(): void; hideConfetti(): void; }; const ConfettiContext = createContext({ - isConfettiShowing: false, - showConfetti: () => undefined, + isConfettiDropping: false, + dropConfetti: () => undefined, hideConfetti: () => undefined, }); export const ConfettiContextProvider: FC = ({ children }) => { - const [showConfetti, setShowConfetti] = useState(false); + const [isConfettiDropping, setIsConfettiDropping] = useState(false); const value = useMemo(() => { return { - isConfettiShowing: showConfetti, - showConfetti: () => setShowConfetti(true), - hideConfetti: () => setShowConfetti(false), + isConfettiDropping: isConfettiDropping, + dropConfetti: () => setIsConfettiDropping(true), + hideConfetti: () => setIsConfettiDropping(false), }; - }, [showConfetti]); + }, [isConfettiDropping]); return ( {children} - {showConfetti && ( - setShowConfetti(false)} /> + {isConfettiDropping && ( + )} ); diff --git a/components/dashboard/src/onboarding/UserOnboarding.tsx b/components/dashboard/src/onboarding/UserOnboarding.tsx index 6d02396c4ec3de..f8620045add4af 100644 --- a/components/dashboard/src/onboarding/UserOnboarding.tsx +++ b/components/dashboard/src/onboarding/UserOnboarding.tsx @@ -35,7 +35,7 @@ const UserOnboarding: FunctionComponent = ({ user }) => { const location = useLocation(); const { setUser } = useContext(UserContext); const updateUser = useUpdateCurrentUserMutation(); - const { showConfetti } = useConfetti(); + const { dropConfetti } = useConfetti(); const [step, setStep] = useState(STEPS.ONE); const [completingError, setCompletingError] = useState(""); @@ -75,7 +75,7 @@ const UserOnboarding: FunctionComponent = ({ user }) => { try { const onboardedUser = await updateUser.mutateAsync(updates); - showConfetti(); + dropConfetti(); setUser(onboardedUser); // Look for the `onboarding=force` query param, and remove if present @@ -104,7 +104,7 @@ const UserOnboarding: FunctionComponent = ({ user }) => { location.pathname, location.search, setUser, - showConfetti, + dropConfetti, updateUser, ], ); From a3f4f71b963ea54e463664300506788ea22108cd Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 23 Feb 2023 21:58:37 +0000 Subject: [PATCH 5/6] validate url w/ lib instead of input type --- components/dashboard/package.json | 2 ++ components/dashboard/src/onboarding/StepOrgInfo.tsx | 10 +++++++--- yarn.lock | 10 ++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/components/dashboard/package.json b/components/dashboard/package.json index bcf027677f41d5..3cc861b8f8f040 100644 --- a/components/dashboard/package.json +++ b/components/dashboard/package.json @@ -30,6 +30,7 @@ "react-popper": "^2.3.0", "react-portal": "^4.2.2", "react-router-dom": "^5.2.0", + "validator": "^13.9.0", "xterm": "^4.11.0", "xterm-addon-fit": "^0.5.0" }, @@ -50,6 +51,7 @@ "@types/react-router": "^5.1.13", "@types/react-router-dom": "^5.1.7", "@types/uuid": "^8.3.1", + "@types/validator": "^13.7.12", "@typescript-eslint/eslint-plugin": "^4.21.0", "@typescript-eslint/parser": "^4.21.0", "autoprefixer": "^9.8.6", diff --git a/components/dashboard/src/onboarding/StepOrgInfo.tsx b/components/dashboard/src/onboarding/StepOrgInfo.tsx index d6fc0f49c2d543..fb6118ad35180f 100644 --- a/components/dashboard/src/onboarding/StepOrgInfo.tsx +++ b/components/dashboard/src/onboarding/StepOrgInfo.tsx @@ -15,6 +15,7 @@ import { getExplorationReasons } from "./exploration-reasons"; import { getJobRoleOptions, JOB_ROLE_OTHER } from "./job-roles"; import { OnboardingStep } from "./OnboardingStep"; import { getSignupGoalsOptions, SIGNUP_GOALS_OTHER } from "./signup-goals"; +import isURL from "validator/lib/isURL"; type Props = { user: User; @@ -127,7 +128,9 @@ export const StepOrgInfo: FC = ({ user, onComplete }) => { ]); const jobRoleError = useOnBlurError("Please select one", !!jobRole); - const isValid = jobRoleError.isValid && signupGoals.length > 0; + const websiteError = useOnBlurError("Please enter a valid url", !companyWebsite || isURL(companyWebsite)); + const isValid = + jobRoleError.isValid && websiteError.isValid && signupGoals.length > 0 && explorationReasons.length > 0; return ( = ({ user, onComplete }) => { diff --git a/yarn.lock b/yarn.lock index 671d7791fe5c24..159c7e853ef98b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3428,6 +3428,11 @@ resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz" integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== +"@types/validator@^13.7.12": + version "13.7.12" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.12.tgz#a285379b432cc8d103b69d223cbb159a253cf2f7" + integrity sha512-YVtyAPqpefU+Mm/qqnOANW6IkqKpCSrarcyV269C8MA8Ux0dbkEuQwM/4CjL47kVEM2LgBef/ETfkH+c6+moFA== + "@types/webpack-sources@*": version "3.2.0" resolved "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz" @@ -18264,6 +18269,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^13.9.0: + version "13.9.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855" + integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA== + value-equal@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz" From aec5fb912c230c8599e2ebd339d5c23ee0ebb4f6 Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Thu, 23 Feb 2023 23:41:12 +0000 Subject: [PATCH 6/6] add suspense around confetti --- components/dashboard/src/contexts/ConfettiContext.tsx | 6 ++++-- components/dashboard/src/onboarding/UserOnboarding.tsx | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/dashboard/src/contexts/ConfettiContext.tsx b/components/dashboard/src/contexts/ConfettiContext.tsx index 058a9772829546..a3507f58a98542 100644 --- a/components/dashboard/src/contexts/ConfettiContext.tsx +++ b/components/dashboard/src/contexts/ConfettiContext.tsx @@ -4,7 +4,7 @@ * See License.AGPL.txt in the project root for license information. */ -import { lazy, createContext, FC, useMemo, useState, useContext } from "react"; +import { lazy, createContext, FC, useMemo, useState, useContext, Suspense } from "react"; const Confetti = lazy(() => import(/* webpackPrefetch: true */ "react-confetti")); @@ -33,7 +33,9 @@ export const ConfettiContextProvider: FC = ({ children }) => { {children} {isConfettiDropping && ( - + }> + + )} ); diff --git a/components/dashboard/src/onboarding/UserOnboarding.tsx b/components/dashboard/src/onboarding/UserOnboarding.tsx index f8620045add4af..e2222a03f901ad 100644 --- a/components/dashboard/src/onboarding/UserOnboarding.tsx +++ b/components/dashboard/src/onboarding/UserOnboarding.tsx @@ -89,6 +89,7 @@ const UserOnboarding: FunctionComponent = ({ user }) => { }); } } catch (e) { + console.log("error caught", e); console.error(e); setCompletingError("There was a problem completing your onboarding"); }