Skip to content

Commit fd2825e

Browse files
committed
squashed (- oidc/newUser)
Tool: gitpod/catfood.gitpod.cloud
1 parent acfdd52 commit fd2825e

File tree

25 files changed

+4045
-861
lines changed

25 files changed

+4045
-861
lines changed

components/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"react-focus-on": "^3.8.1",
5454
"react-intl-tel-input": "^8.2.0",
5555
"react-linkedin-login-oauth2": "^2.0.1",
56+
"react-markdown": "^9.0.3",
5657
"react-popper": "^2.3.0",
5758
"react-portal": "^4.2.2",
5859
"react-router-dom": "^5.2.0",

components/dashboard/src/components/forms/InputField.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ type Props = {
1616
topMargin?: boolean;
1717
className?: string;
1818
disabled?: boolean;
19+
labelHidden?: boolean;
1920
};
2021

2122
export const InputField: FunctionComponent<Props> = memo(
22-
({ label, id, hint, error, topMargin = true, className, children, disabled = false }) => {
23+
({ label, id, hint, error, topMargin = true, className, children, disabled = false, labelHidden = false }) => {
2324
return (
2425
<div className={cn("flex flex-col space-y-2", { "mt-4": topMargin }, className)}>
2526
{label && (
2627
<label
2728
className={cn(
2829
"text-md font-semibold",
30+
{ "sr-only": labelHidden },
2931
disabled
3032
? "text-gray-400 dark:text-gray-400"
3133
: error
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) 2025 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 * as React from "react";
8+
9+
import { cn } from "@podkit/lib/cn";
10+
11+
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
12+
({ className, ...props }, ref) => {
13+
return (
14+
<textarea
15+
className={cn(
16+
"flex min-h-[80px] w-full rounded-md border border-input bg-pk-surface-primary px-3 py-2 text-base ring-offset-background placeholder:text-pk-content-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
17+
className,
18+
)}
19+
ref={ref}
20+
{...props}
21+
/>
22+
);
23+
},
24+
);
25+
Textarea.displayName = "Textarea";
26+
27+
export { Textarea };

components/dashboard/src/data/organizations/update-org-settings-mutation.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@ import { useOrgWorkspaceClassesQueryInvalidator } from "./org-workspace-classes-
1414
import { PlainMessage } from "@bufbuild/protobuf";
1515
import { useOrgRepoSuggestionsInvalidator } from "./suggested-repositories-query";
1616

17-
type UpdateOrganizationSettingsArgs = Partial<
18-
Pick<
19-
PlainMessage<OrganizationSettings>,
20-
| "workspaceSharingDisabled"
21-
| "defaultWorkspaceImage"
22-
| "allowedWorkspaceClasses"
23-
| "pinnedEditorVersions"
24-
| "restrictedEditorNames"
25-
| "defaultRole"
26-
| "timeoutSettings"
27-
| "roleRestrictions"
28-
| "maxParallelRunningWorkspaces"
29-
| "onboardingSettings"
30-
| "annotateGitCommits"
31-
>
17+
export type UpdateOrganizationSettingsArgs = Partial<
18+
Omit<
19+
Pick<
20+
PlainMessage<OrganizationSettings>,
21+
| "workspaceSharingDisabled"
22+
| "defaultWorkspaceImage"
23+
| "allowedWorkspaceClasses"
24+
| "pinnedEditorVersions"
25+
| "restrictedEditorNames"
26+
| "defaultRole"
27+
| "timeoutSettings"
28+
| "roleRestrictions"
29+
| "maxParallelRunningWorkspaces"
30+
| "annotateGitCommits"
31+
>,
32+
never
33+
> & {
34+
onboardingSettings?: Partial<PlainMessage<OrganizationSettings>["onboardingSettings"]>; // this enables us to not have to specify all of the onboarding settings on every update
35+
}
3236
>;
3337

3438
export const useUpdateOrgSettingsMutation = () => {
@@ -66,7 +70,14 @@ export const useUpdateOrgSettingsMutation = () => {
6670
roleRestrictions,
6771
updateRoleRestrictions: !!roleRestrictions,
6872
maxParallelRunningWorkspaces,
69-
onboardingSettings,
73+
onboardingSettings: {
74+
...onboardingSettings,
75+
updateRecommendedRepositories: !!onboardingSettings?.recommendedRepositories,
76+
welcomeMessage: {
77+
...onboardingSettings?.welcomeMessage,
78+
featuredMemberResolvedAvatarUrl: undefined, // This field is not allowed to be set in the request.
79+
},
80+
},
7081
annotateGitCommits,
7182
});
7283
return settings.settings!;

components/dashboard/src/index.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
margin: 0 auto !important;
9696
}
9797
p {
98-
@apply text-sm text-gray-400 dark:text-gray-600;
98+
@apply text-sm text-pk-content-secondary;
9999
}
100100

101101
.app-container {
@@ -118,7 +118,7 @@
118118
}
119119

120120
code {
121-
@apply bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded-md text-sm font-mono font-medium;
121+
@apply bg-gray-100 dark:bg-gray-800 text-pk-content-primary px-1.5 py-0.5 rounded-md text-sm font-mono font-medium;
122122
}
123123

124124
textarea,

components/dashboard/src/login/SSOLoginForm.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ import { useOnboardingState } from "../dedicated-setup/use-needs-setup";
1616
import { getOrgSlugFromQuery } from "../data/organizations/orgs-query";
1717
import { storageAvailable } from "../utils";
1818

19-
type Props = {
20-
onSuccess: () => void;
21-
};
22-
2319
function getOrgSlugFromPath(path: string) {
2420
// '/login/acme' => ['', 'login', 'acme']
2521
const pathSegments = path.split("/");
@@ -29,6 +25,9 @@ function getOrgSlugFromPath(path: string) {
2925
return pathSegments[2];
3026
}
3127

28+
type Props = {
29+
onSuccess: () => void;
30+
};
3231
export const SSOLoginForm: FC<Props> = ({ onSuccess }) => {
3332
const location = useLocation();
3433
const { data: onboardingState } = useOnboardingState();

components/dashboard/src/repositories/detail/general/ManageRepoSuggestion.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export const ManageRepoSuggestion: FC<Props> = ({ configuration }) => {
3636
await updateTeamSettings.mutateAsync(
3737
{
3838
onboardingSettings: {
39-
...orgSettings?.onboardingSettings,
4039
recommendedRepositories: [...newRepositories],
4140
},
4241
},
@@ -47,7 +46,7 @@ export const ManageRepoSuggestion: FC<Props> = ({ configuration }) => {
4746
},
4847
);
4948
},
50-
[orgSettings?.onboardingSettings, toast, updateTeamSettings],
49+
[orgSettings?.onboardingSettings?.recommendedRepositories, toast, updateTeamSettings],
5150
);
5251

5352
const isSuggested = orgSettings?.onboardingSettings?.recommendedRepositories?.includes(configuration.id);

components/dashboard/src/start/StartWorkspace.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function parseParameters(search?: string): { notFound?: boolean } {
8888

8989
export interface StartWorkspaceState {
9090
/**
91-
* This is set to the istanceId we started (think we started on).
91+
* This is set to the instanceId we started (think we started on).
9292
* We only receive updates for this particular instance, or none if not set.
9393
*/
9494
startedInstanceId?: string;

components/dashboard/src/teams/TeamOnboarding.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
87
import { FormEvent, useCallback, useEffect, useState } from "react";
98
import { Heading2, Heading3, Subheading } from "../components/typography/headings";
109
import { useIsOwner } from "../data/organizations/members-query";
1110
import { useOrgSettingsQuery } from "../data/organizations/org-settings-query";
1211
import { useCurrentOrg } from "../data/organizations/orgs-query";
13-
import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation";
12+
import {
13+
UpdateOrganizationSettingsArgs,
14+
useUpdateOrgSettingsMutation,
15+
} from "../data/organizations/update-org-settings-mutation";
1416
import { OrgSettingsPage } from "./OrgSettingsPage";
1517
import { ConfigurationSettingsField } from "../repositories/detail/ConfigurationSettingsField";
1618
import { useDocumentTitle } from "../hooks/use-document-title";
1719
import { useToast } from "../components/toasts/Toasts";
18-
import type { PlainMessage } from "@bufbuild/protobuf";
1920
import { InputField } from "../components/forms/InputField";
2021
import { TextInput } from "../components/forms/TextInputField";
2122
import { LoadingButton } from "@podkit/buttons/LoadingButton";
@@ -24,11 +25,16 @@ import { useOrgSuggestedRepos } from "../data/organizations/suggested-repositori
2425
import { RepositoryListItem } from "../repositories/list/RepoListItem";
2526
import { LoadingState } from "@podkit/loading/LoadingState";
2627
import { Table, TableHeader, TableRow, TableHead, TableBody } from "@podkit/tables/Table";
28+
import { WelcomeMessageConfigurationField } from "./onboarding/WelcomeMessageConfigurationField";
29+
30+
export type UpdateTeamSettingsOptions = {
31+
throwMutateError?: boolean;
32+
};
2733

2834
export default function TeamOnboardingPage() {
2935
useDocumentTitle("Organization Settings - Onboarding");
3036
const { toast } = useToast();
31-
const org = useCurrentOrg().data;
37+
const { data: org } = useCurrentOrg();
3238
const isOwner = useIsOwner();
3339

3440
const { data: settings } = useOrgSettingsQuery();
@@ -39,7 +45,7 @@ export default function TeamOnboardingPage() {
3945
const [internalLink, setInternalLink] = useState<string | undefined>(undefined);
4046

4147
const handleUpdateTeamSettings = useCallback(
42-
async (newSettings: Partial<PlainMessage<OrganizationSettings>>, options?: { throwMutateError?: boolean }) => {
48+
async (newSettings: UpdateOrganizationSettingsArgs, options?: UpdateTeamSettingsOptions) => {
4349
if (!org?.id) {
4450
throw new Error("no organization selected");
4551
}
@@ -70,11 +76,10 @@ export default function TeamOnboardingPage() {
7076
await handleUpdateTeamSettings({
7177
onboardingSettings: {
7278
internalLink,
73-
recommendedRepositories: settings?.onboardingSettings?.recommendedRepositories ?? [],
7479
},
7580
});
7681
},
77-
[handleUpdateTeamSettings, internalLink, settings?.onboardingSettings?.recommendedRepositories],
82+
[handleUpdateTeamSettings, internalLink],
7883
);
7984

8085
useEffect(() => {
@@ -146,7 +151,7 @@ export default function TeamOnboardingPage() {
146151
</TableRow>
147152
</TableHeader>
148153
<TableBody>
149-
{(suggestedRepos ?? []).map((repo) => (
154+
{suggestedRepos?.map((repo) => (
150155
<RepositoryListItem
151156
key={repo.configurationId}
152157
configuration={repo.configuration}
@@ -157,6 +162,8 @@ export default function TeamOnboardingPage() {
157162
</Table>
158163
)}
159164
</ConfigurationSettingsField>
165+
166+
<WelcomeMessageConfigurationField handleUpdateTeamSettings={handleUpdateTeamSettings} />
160167
</div>
161168
</OrgSettingsPage>
162169
);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright (c) 2025 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 { useState } from "react";
8+
import { useListOrganizationMembers } from "../../data/organizations/members-query";
9+
10+
import type { OnboardingSettings_WelcomeMessage } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
11+
import { Button } from "@podkit/buttons/Button";
12+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@podkit/dropdown/DropDown";
13+
14+
type Props = {
15+
settings: OnboardingSettings_WelcomeMessage | undefined;
16+
setFeaturedMemberId: (featuredMemberId: string | undefined) => void;
17+
};
18+
export const OrgMemberAvatarInput = ({ settings, setFeaturedMemberId }: Props) => {
19+
const { data: members } = useListOrganizationMembers();
20+
21+
const [avatarUrl, setAvatarUrl] = useState<string | undefined>(settings?.featuredMemberResolvedAvatarUrl);
22+
23+
return (
24+
<DropdownMenu>
25+
<DropdownMenuTrigger>
26+
<div className="flex flex-col justify-center items-center gap-2">
27+
{avatarUrl ? (
28+
<img src={avatarUrl} alt="" className="w-16 h-16 rounded-full" />
29+
) : (
30+
<div className="w-16 h-16 rounded-full bg-[#EA71DE]" />
31+
)}
32+
<Button variant="secondary" size="sm" type="button">
33+
Change Photo
34+
</Button>
35+
</div>
36+
</DropdownMenuTrigger>
37+
<DropdownMenuContent>
38+
<DropdownMenuItem
39+
key="disabled"
40+
onClick={() => {
41+
setFeaturedMemberId(undefined);
42+
setAvatarUrl(undefined);
43+
}}
44+
>
45+
<div className="flex items-center gap-2">
46+
<div className="w-4 h-4 rounded-full bg-pk-surface-tertiary" />
47+
Disable image
48+
</div>
49+
</DropdownMenuItem>
50+
{members?.map((member) => (
51+
<DropdownMenuItem
52+
key={member.userId}
53+
onClick={() => {
54+
setFeaturedMemberId(member.userId);
55+
setAvatarUrl(member.avatarUrl);
56+
}}
57+
>
58+
<div className="flex items-center gap-2">
59+
<img src={member.avatarUrl} alt={member.fullName} className="w-4 h-4 rounded-full" />
60+
{member.fullName}
61+
</div>
62+
</DropdownMenuItem>
63+
))}
64+
</DropdownMenuContent>
65+
</DropdownMenu>
66+
);
67+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright (c) 2025 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 { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
8+
import { Button } from "@podkit/buttons/Button";
9+
import { useCallback, useEffect, useMemo, useState } from "react";
10+
import { Modal, ModalBody, ModalFooter, ModalHeader } from "../../components/Modal";
11+
import { storageAvailable } from "../../utils";
12+
import { WelcomeMessagePreview } from "./WelcomeMessagePreview";
13+
14+
type Props = {
15+
orgSettings: OrganizationSettings;
16+
};
17+
export const OrganizationJoinModal = ({ orgSettings }: Props) => {
18+
const initialOrgOnboardingPending = useMemo(() => {
19+
if (storageAvailable("localStorage")) {
20+
return localStorage.getItem("newUserOnboardingPending") === "true";
21+
}
22+
}, []);
23+
const dismissOrgOnboardingPending = useCallback(() => {
24+
if (storageAvailable("localStorage")) {
25+
localStorage.removeItem("newUserOnboardingPending");
26+
}
27+
28+
setOrgOnboardingPending(false);
29+
}, []);
30+
const [orgOnboardingPending, setOrgOnboardingPending] = useState(initialOrgOnboardingPending ?? false);
31+
32+
// if the org-wide welcome message is not enabled, prevent showing it in the future
33+
useEffect(() => {
34+
if (!orgSettings?.onboardingSettings?.welcomeMessage?.enabled) {
35+
dismissOrgOnboardingPending();
36+
}
37+
}, [orgSettings?.onboardingSettings?.welcomeMessage?.enabled, dismissOrgOnboardingPending]);
38+
39+
if (!orgSettings?.onboardingSettings?.welcomeMessage?.enabled) {
40+
return null;
41+
}
42+
43+
return (
44+
<Modal
45+
visible={orgOnboardingPending}
46+
onClose={dismissOrgOnboardingPending}
47+
containerClassName="min-[576px]:max-w-[650px]"
48+
>
49+
<ModalHeader>Welcome to Gitpod</ModalHeader>
50+
<ModalBody>
51+
<WelcomeMessagePreview hideHeader />
52+
</ModalBody>
53+
<ModalFooter>
54+
<Button onClick={dismissOrgOnboardingPending}>Get Started</Button>
55+
</ModalFooter>
56+
</Modal>
57+
);
58+
};

0 commit comments

Comments
 (0)