-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New User Onboarding Flow UI #16501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New User Onboarding Flow UI #16501
Changes from all commits
cb6af81
e853507
d4b7832
6cc4f18
5307855
f78b0b2
066820a
74263bc
05ebdd7
f078fcd
aa05a79
4ec1fd7
02b3cd3
0f12ff5
273525e
189542b
f2d868e
6b91b5d
1a5d15c
a676453
cae16dd
1cc9482
3cb0b60
c019664
9f526a8
3be00ab
6a9e763
f49cd2f
452874b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* 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 classNames from "classnames"; | ||
import { FC, useCallback, useContext, useState } from "react"; | ||
import { ThemeContext } from "../theme-context"; | ||
import SelectableCardSolid from "./SelectableCardSolid"; | ||
|
||
type Theme = "light" | "dark" | "system"; | ||
|
||
type Props = { | ||
className?: string; | ||
}; | ||
// Theme Selection is purely clientside, so this component handles all state and writes to localStorage | ||
export const ThemeSelector: FC<Props> = ({ className }) => { | ||
const { setIsDark } = useContext(ThemeContext); | ||
const [theme, setTheme] = useState<Theme>(localStorage.theme || "system"); | ||
|
||
const actuallySetTheme = useCallback( | ||
(theme: Theme) => { | ||
if (theme === "dark" || theme === "light") { | ||
localStorage.theme = theme; | ||
} else { | ||
localStorage.removeItem("theme"); | ||
} | ||
const isDark = | ||
localStorage.theme === "dark" || | ||
(localStorage.theme !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches); | ||
setIsDark(isDark); | ||
setTheme(theme); | ||
}, | ||
[setIsDark], | ||
); | ||
|
||
return ( | ||
<div className={classNames(className)}> | ||
<h3>Theme</h3> | ||
<p className="text-base text-gray-500 dark:text-gray-400">Early bird or night owl? Choose your side.</p> | ||
<div className="mt-4 flex items-center flex-wrap"> | ||
<SelectableCardSolid | ||
className="w-36 h-32 m-1" | ||
title="Light" | ||
selected={theme === "light"} | ||
onClick={() => actuallySetTheme("light")} | ||
> | ||
<div className="flex-grow flex items-end p-1"> | ||
<svg width="112" height="64" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path | ||
d="M0 8a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8ZM0 32a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8ZM0 56a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8ZM40 6a6 6 0 0 1 6-6h60a6 6 0 0 1 6 6v28a6 6 0 0 1-6 6H46a6 6 0 0 1-6-6V6Z" | ||
fill="#D6D3D1" | ||
/> | ||
</svg> | ||
</div> | ||
</SelectableCardSolid> | ||
<SelectableCardSolid | ||
className="w-36 h-32 m-1" | ||
title="Dark" | ||
selected={theme === "dark"} | ||
onClick={() => actuallySetTheme("dark")} | ||
> | ||
<div className="flex-grow flex items-end p-1"> | ||
<svg width="112" height="64" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path | ||
d="M0 8a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8ZM0 32a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8ZM0 56a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8ZM40 6a6 6 0 0 1 6-6h60a6 6 0 0 1 6 6v28a6 6 0 0 1-6 6H46a6 6 0 0 1-6-6V6Z" | ||
fill="#78716C" | ||
/> | ||
</svg> | ||
</div> | ||
</SelectableCardSolid> | ||
<SelectableCardSolid | ||
className="w-36 h-32 m-1" | ||
title="System" | ||
selected={theme === "system"} | ||
onClick={() => actuallySetTheme("system")} | ||
> | ||
<div className="flex-grow flex items-end p-1"> | ||
<svg width="112" height="64" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path | ||
d="M0 8a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8ZM40 6a6 6 0 0 1 6-6h60a6 6 0 0 1 6 6v28a6 6 0 0 1-6 6H46a6 6 0 0 1-6-6V6Z" | ||
fill="#D9D9D9" | ||
/> | ||
<path | ||
d="M84 0h22a6 6 0 0 1 6 6v28a6 6 0 0 1-6 6H68L84 0ZM0 32a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8Z" | ||
fill="#78716C" | ||
/> | ||
<path d="M0 56a8 8 0 0 1 8-8h16a8 8 0 1 1 0 16H8a8 8 0 0 1-8-8Z" fill="#D9D9D9" /> | ||
</svg> | ||
</div> | ||
</SelectableCardSolid> | ||
</div> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,19 @@ | ||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||
* 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 { User } from "@gitpod/gitpod-protocol"; | ||||||||||||||||||||||||||||||||||||
import { useMutation } from "@tanstack/react-query"; | ||||||||||||||||||||||||||||||||||||
import { getGitpodService } from "../../service/service"; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
type UpdateCurrentUserArgs = Partial<User>; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
export const useUpdateCurrentUserMutation = () => { | ||||||||||||||||||||||||||||||||||||
return useMutation({ | ||||||||||||||||||||||||||||||||||||
mutationFn: async (partialUser: UpdateCurrentUserArgs) => { | ||||||||||||||||||||||||||||||||||||
return await getGitpodService().server.updateLoggedInUser(partialUser); | ||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||
Comment on lines
+13
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion (non-blocking): How about we split it out in chunks, it can be also useful in the future to reuse the similar functionality any other place
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yah, good thinking. I'm going to hold off on extracting these for now until there's another mutation that would use the function, but that would be a good way to share some logic. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* 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 { FC, FormEvent, useCallback } from "react"; | ||
import Alert from "../components/Alert"; | ||
|
||
type Props = { | ||
title: string; | ||
subtitle: string; | ||
isValid: boolean; | ||
isSaving?: boolean; | ||
error?: string; | ||
onSubmit(): void; | ||
}; | ||
export const OnboardingStep: FC<Props> = ({ | ||
title, | ||
subtitle, | ||
isValid, | ||
isSaving = false, | ||
error, | ||
children, | ||
onSubmit, | ||
}) => { | ||
const handleSubmit = useCallback( | ||
async (e: FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
if (isSaving || !isValid) { | ||
return; | ||
} | ||
|
||
onSubmit(); | ||
}, | ||
[isSaving, isValid, onSubmit], | ||
); | ||
|
||
return ( | ||
<div className="flex flex-col items-center justify-center max-w-full"> | ||
{/* TODO: Fix our base heading styles so we don't have to override */} | ||
<h2 className="text-3xl text-gray-900 dark:text-gray-100 font-bold">{title}</h2> | ||
<p className="text-base text-gray-500 dark:text-gray-400">{subtitle}</p> | ||
|
||
<form className="mt-8 mb-14 max-w-lg" onSubmit={handleSubmit}> | ||
{/* Form contents provided as children */} | ||
{children} | ||
|
||
{error && <Alert type="error">{error}</Alert>} | ||
|
||
<div> | ||
<button type="submit" disabled={!isValid || isSaving} className="w-full mt-8"> | ||
Continue | ||
</button> | ||
</div> | ||
</form> | ||
</div> | ||
); | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.