Skip to content

Commit 63a2d6e

Browse files
Add confetti after new onboarding flow (#16543)
* add react-confetti dep * Adding ConfettiContextProvider * show confetti on completion * renaming * validate url w/ lib instead of input type * add suspense around confetti
1 parent 2877b3b commit 63a2d6e

File tree

7 files changed

+108
-25
lines changed

7 files changed

+108
-25
lines changed

components/dashboard/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
"monaco-editor": "^0.25.2",
2424
"query-string": "^7.1.1",
2525
"react": "^17.0.1",
26+
"react-confetti": "^6.1.0",
2627
"react-datepicker": "^4.8.0",
2728
"react-dom": "^17.0.1",
2829
"react-intl-tel-input": "^8.2.0",
2930
"react-popper": "^2.3.0",
3031
"react-portal": "^4.2.2",
3132
"react-router-dom": "^5.2.0",
33+
"validator": "^13.9.0",
3234
"xterm": "^4.11.0",
3335
"xterm-addon-fit": "^0.5.0"
3436
},
@@ -49,6 +51,7 @@
4951
"@types/react-router": "^5.1.13",
5052
"@types/react-router-dom": "^5.1.7",
5153
"@types/uuid": "^8.3.1",
54+
"@types/validator": "^13.7.12",
5255
"@typescript-eslint/eslint-plugin": "^4.21.0",
5356
"@typescript-eslint/parser": "^4.21.0",
5457
"autoprefixer": "^9.8.6",

components/dashboard/src/app/AppRoutes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { ContextURL, Team, User } from "@gitpod/gitpod-protocol";
88
import React, { FunctionComponent, useContext, useState } from "react";
9-
import { Redirect, Route, Switch, useLocation, useParams } from "react-router";
9+
import { Redirect, Route, Switch, useLocation } from "react-router";
1010
import { AppNotifications } from "../AppNotifications";
1111
import Menu from "../menu/Menu";
1212
import OAuthClientApproval from "../OauthClientApproval";
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { lazy, createContext, FC, useMemo, useState, useContext, Suspense } from "react";
8+
9+
const Confetti = lazy(() => import(/* webpackPrefetch: true */ "react-confetti"));
10+
11+
type ConfettiContextType = {
12+
isConfettiDropping: boolean;
13+
dropConfetti(): void;
14+
hideConfetti(): void;
15+
};
16+
const ConfettiContext = createContext<ConfettiContextType>({
17+
isConfettiDropping: false,
18+
dropConfetti: () => undefined,
19+
hideConfetti: () => undefined,
20+
});
21+
22+
export const ConfettiContextProvider: FC = ({ children }) => {
23+
const [isConfettiDropping, setIsConfettiDropping] = useState(false);
24+
const value = useMemo(() => {
25+
return {
26+
isConfettiDropping: isConfettiDropping,
27+
dropConfetti: () => setIsConfettiDropping(true),
28+
hideConfetti: () => setIsConfettiDropping(false),
29+
};
30+
}, [isConfettiDropping]);
31+
32+
return (
33+
<ConfettiContext.Provider value={value}>
34+
{children}
35+
{isConfettiDropping && (
36+
<Suspense fallback={<></>}>
37+
<Confetti recycle={false} numberOfPieces={300} onConfettiComplete={value.hideConfetti} />
38+
</Suspense>
39+
)}
40+
</ConfettiContext.Provider>
41+
);
42+
};
43+
44+
export const useConfetti = () => {
45+
return useContext(ConfettiContext);
46+
};

components/dashboard/src/index.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { isWebsiteSlug } from "./utils";
2525

2626
import "./index.css";
2727
import { setupQueryClientProvider } from "./data/setup";
28+
import { ConfettiContextProvider } from "./contexts/ConfettiContext";
2829

2930
const bootApp = () => {
3031
// gitpod.io specific boot logic
@@ -57,27 +58,29 @@ const bootApp = () => {
5758
ReactDOM.render(
5859
<React.StrictMode>
5960
<GitpodQueryClientProvider>
60-
<UserContextProvider>
61-
<AdminContextProvider>
62-
<PaymentContextProvider>
63-
<LicenseContextProvider>
64-
<TeamsContextProvider>
65-
<ProjectContextProvider>
66-
<ThemeContextProvider>
67-
<StartWorkspaceModalContextProvider>
68-
<BrowserRouter>
69-
<FeatureFlagContextProvider>
70-
<App />
71-
</FeatureFlagContextProvider>
72-
</BrowserRouter>
73-
</StartWorkspaceModalContextProvider>
74-
</ThemeContextProvider>
75-
</ProjectContextProvider>
76-
</TeamsContextProvider>
77-
</LicenseContextProvider>
78-
</PaymentContextProvider>
79-
</AdminContextProvider>
80-
</UserContextProvider>
61+
<ConfettiContextProvider>
62+
<UserContextProvider>
63+
<AdminContextProvider>
64+
<PaymentContextProvider>
65+
<LicenseContextProvider>
66+
<TeamsContextProvider>
67+
<ProjectContextProvider>
68+
<ThemeContextProvider>
69+
<StartWorkspaceModalContextProvider>
70+
<BrowserRouter>
71+
<FeatureFlagContextProvider>
72+
<App />
73+
</FeatureFlagContextProvider>
74+
</BrowserRouter>
75+
</StartWorkspaceModalContextProvider>
76+
</ThemeContextProvider>
77+
</ProjectContextProvider>
78+
</TeamsContextProvider>
79+
</LicenseContextProvider>
80+
</PaymentContextProvider>
81+
</AdminContextProvider>
82+
</UserContextProvider>
83+
</ConfettiContextProvider>
8184
</GitpodQueryClientProvider>
8285
</React.StrictMode>,
8386
document.getElementById("root"),

components/dashboard/src/onboarding/StepOrgInfo.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getExplorationReasons } from "./exploration-reasons";
1515
import { getJobRoleOptions, JOB_ROLE_OTHER } from "./job-roles";
1616
import { OnboardingStep } from "./OnboardingStep";
1717
import { getSignupGoalsOptions, SIGNUP_GOALS_OTHER } from "./signup-goals";
18+
import isURL from "validator/lib/isURL";
1819

1920
type Props = {
2021
user: User;
@@ -127,7 +128,9 @@ export const StepOrgInfo: FC<Props> = ({ user, onComplete }) => {
127128
]);
128129

129130
const jobRoleError = useOnBlurError("Please select one", !!jobRole);
130-
const isValid = jobRoleError.isValid && signupGoals.length > 0;
131+
const websiteError = useOnBlurError("Please enter a valid url", !companyWebsite || isURL(companyWebsite));
132+
const isValid =
133+
jobRoleError.isValid && websiteError.isValid && signupGoals.length > 0 && explorationReasons.length > 0;
131134

132135
return (
133136
<OnboardingStep
@@ -178,9 +181,10 @@ export const StepOrgInfo: FC<Props> = ({ user, onComplete }) => {
178181
<TextInputField
179182
value={companyWebsite}
180183
label="Company Website (optional)"
181-
type="url"
182-
placeholder="https://"
184+
placeholder="example.com"
185+
error={websiteError.message}
183186
onChange={setCompanyWebsite}
187+
onBlur={websiteError.onBlur}
184188
/>
185189

186190
<InputField label="I'm exploring Gitpod..." />

components/dashboard/src/onboarding/UserOnboarding.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { StepOrgInfo } from "./StepOrgInfo";
1515
import { StepPersonalize } from "./StepPersonalize";
1616
import { useUpdateCurrentUserMutation } from "../data/current-user/update-mutation";
1717
import Alert from "../components/Alert";
18+
import { useConfetti } from "../contexts/ConfettiContext";
1819

1920
// This param is optionally present to force an onboarding flow
2021
// 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<Props> = ({ user }) => {
3435
const location = useLocation();
3536
const { setUser } = useContext(UserContext);
3637
const updateUser = useUpdateCurrentUserMutation();
38+
const { dropConfetti } = useConfetti();
3739

3840
const [step, setStep] = useState(STEPS.ONE);
3941
const [completingError, setCompletingError] = useState("");
@@ -73,6 +75,7 @@ const UserOnboarding: FunctionComponent<Props> = ({ user }) => {
7375

7476
try {
7577
const onboardedUser = await updateUser.mutateAsync(updates);
78+
dropConfetti();
7679
setUser(onboardedUser);
7780

7881
// Look for the `onboarding=force` query param, and remove if present
@@ -86,6 +89,7 @@ const UserOnboarding: FunctionComponent<Props> = ({ user }) => {
8689
});
8790
}
8891
} catch (e) {
92+
console.log("error caught", e);
8993
console.error(e);
9094
setCompletingError("There was a problem completing your onboarding");
9195
}
@@ -101,6 +105,7 @@ const UserOnboarding: FunctionComponent<Props> = ({ user }) => {
101105
location.pathname,
102106
location.search,
103107
setUser,
108+
dropConfetti,
104109
updateUser,
105110
],
106111
);

yarn.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3428,6 +3428,11 @@
34283428
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz"
34293429
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
34303430

3431+
"@types/validator@^13.7.12":
3432+
version "13.7.12"
3433+
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.12.tgz#a285379b432cc8d103b69d223cbb159a253cf2f7"
3434+
integrity sha512-YVtyAPqpefU+Mm/qqnOANW6IkqKpCSrarcyV269C8MA8Ux0dbkEuQwM/4CjL47kVEM2LgBef/ETfkH+c6+moFA==
3435+
34313436
"@types/webpack-sources@*":
34323437
version "3.2.0"
34333438
resolved "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz"
@@ -15129,6 +15134,13 @@ react-app-polyfill@^2.0.0:
1512915134
regenerator-runtime "^0.13.7"
1513015135
whatwg-fetch "^3.4.1"
1513115136

15137+
react-confetti@^6.1.0:
15138+
version "6.1.0"
15139+
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.1.0.tgz#03dc4340d955acd10b174dbf301f374a06e29ce6"
15140+
integrity sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==
15141+
dependencies:
15142+
tween-functions "^1.2.0"
15143+
1513215144
react-datepicker@^4.8.0:
1513315145
version "4.8.0"
1513415146
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.8.0.tgz#11b8918d085a1ce4781eee4c8e4641b3cd592010"
@@ -17740,6 +17752,11 @@ [email protected]:
1774017752
resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz"
1774117753
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
1774217754

17755+
tween-functions@^1.2.0:
17756+
version "1.2.0"
17757+
resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff"
17758+
integrity sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==
17759+
1774317760
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
1774417761
version "0.14.5"
1774517762
resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz"
@@ -18252,6 +18269,11 @@ validate-npm-package-license@^3.0.1:
1825218269
spdx-correct "^3.0.0"
1825318270
spdx-expression-parse "^3.0.0"
1825418271

18272+
validator@^13.9.0:
18273+
version "13.9.0"
18274+
resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855"
18275+
integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==
18276+
1825518277
value-equal@^1.0.1:
1825618278
version "1.0.1"
1825718279
resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz"

0 commit comments

Comments
 (0)