44 * See License.AGPL.txt in the project root for license information.
55 */
66
7- import {
8- OnboardingSettings_WelcomeMessage ,
9- OrganizationSettings ,
10- } from "@gitpod/public-api/lib/gitpod/v1/organization_pb" ;
7+ import { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb" ;
118import { FormEvent , useCallback , useEffect , useState } from "react" ;
129import { Heading2 , Heading3 , Subheading } from "../components/typography/headings" ;
13- import { useIsOwner , useListOrganizationMembers } from "../data/organizations/members-query" ;
10+ import { useIsOwner } from "../data/organizations/members-query" ;
1411import { useOrgSettingsQuery } from "../data/organizations/org-settings-query" ;
1512import { useCurrentOrg } from "../data/organizations/orgs-query" ;
1613import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation" ;
@@ -22,14 +19,12 @@ import type { PlainMessage } from "@bufbuild/protobuf";
2219import { InputField } from "../components/forms/InputField" ;
2320import { TextInput } from "../components/forms/TextInputField" ;
2421import { LoadingButton } from "@podkit/buttons/LoadingButton" ;
25- import MDEditor from "@uiw/react-md-editor" ;
26- import { Textarea } from "@podkit/forms/TextArea" ;
27- import Modal , { ModalFooter , ModalBody , ModalHeader } from "../components/Modal" ;
28- import { Button } from "@podkit/buttons/Button" ;
2922import { SwitchInputField } from "@podkit/switch/Switch" ;
30- import { DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuTrigger } from "@podkit/dropdown/DropDown" ;
23+ import { WelcomeMessagePreview } from "./onboarding/WelcomeMessagePreview" ;
24+ import { WelcomeMessageEditorModal } from "./onboarding/WelcomeMessageEditor" ;
3125
32- const gitpodWelcomeSubheading = `Gitpod’s sandboxed, ephemeral development environments enable you to use your existing tools without worrying about vulnerabilities impacting their local machines.` ;
26+ export const gitpodWelcomeSubheading =
27+ `Gitpod’s sandboxed, ephemeral development environments enable you to use your existing tools without worrying about vulnerabilities impacting their local machines.` as const ;
3328
3429export default function TeamOnboardingPage ( ) {
3530 useDocumentTitle ( "Organization Settings - Onboarding" ) ;
@@ -151,7 +146,7 @@ export default function TeamOnboardingPage() {
151146 />
152147 </ InputField >
153148
154- < WelcomeMessageEditor
149+ < WelcomeMessageEditorModal
155150 isLoading = { updateTeamSettings . isLoading }
156151 isOwner = { isOwner }
157152 isOpen = { welcomeMessageEditorOpen }
@@ -160,8 +155,6 @@ export default function TeamOnboardingPage() {
160155 settings = { settings ?. onboardingSettings ?. welcomeMessage }
161156 />
162157
163- { /* todo: add a warning if the welcome message is empty */ }
164-
165158 < span className = "text-pk-content-secondary text-sm" >
166159 Here's a preview of the welcome message that will be shown to your organization members:
167160 </ span >
@@ -174,158 +167,3 @@ export default function TeamOnboardingPage() {
174167 </ OrgSettingsPage >
175168 ) ;
176169}
177-
178- type WelcomeMessagePreviewProps = {
179- disabled ?: boolean ;
180- setWelcomeMessageEditorOpen ?: ( open : boolean ) => void ;
181- } ;
182- export const WelcomeMessagePreview = ( { disabled, setWelcomeMessageEditorOpen } : WelcomeMessagePreviewProps ) => {
183- const { data : settings } = useOrgSettingsQuery ( ) ;
184- const avatarUrl = settings ?. onboardingSettings ?. welcomeMessage ?. featuredMemberResolvedAvatarUrl ;
185- const welcomeMessage = settings ?. onboardingSettings ?. welcomeMessage ?. message ;
186-
187- return (
188- < div className = "max-w-2xl mx-auto" >
189- < div className = "flex justify-between gap-2 items-center" >
190- < Heading2 className = "py-6" > Welcome to Gitpod</ Heading2 >
191- { setWelcomeMessageEditorOpen && (
192- < Button variant = "secondary" onClick = { ( ) => setWelcomeMessageEditorOpen ( true ) } disabled = { disabled } >
193- Edit
194- </ Button >
195- ) }
196- </ div >
197- < Subheading > { gitpodWelcomeSubheading } </ Subheading >
198- { /* todo: sanitize md */ }
199- { welcomeMessage && (
200- < div className = "p-8 my-4 bg-pk-surface-secondary text-pk-content-primary rounded-xl flex flex-col gap-5 items-center justify-center" >
201- { avatarUrl && < img src = { avatarUrl } alt = "" className = "w-12 h-12 rounded-full" /> }
202- < MDEditor . Markdown
203- source = { welcomeMessage }
204- className = "md-preview space-y-4 text-center bg-pk-surface-secondary"
205- />
206- </ div >
207- ) }
208- </ div >
209- ) ;
210- } ;
211-
212- type WelcomeMessageEditorProps = {
213- settings : OnboardingSettings_WelcomeMessage | undefined ;
214- handleUpdateTeamSettings : (
215- newSettings : Partial < PlainMessage < OrganizationSettings > > ,
216- options ?: { throwMutateError ?: boolean } ,
217- ) => Promise < void > ;
218- isLoading : boolean ;
219- isOwner : boolean ;
220- isOpen : boolean ;
221- setIsOpen : ( isOpen : boolean ) => void ;
222- } ;
223- const WelcomeMessageEditor = ( {
224- handleUpdateTeamSettings,
225- isLoading,
226- isOwner,
227- settings,
228- isOpen,
229- setIsOpen,
230- } : WelcomeMessageEditorProps ) => {
231- const [ message , setMessage ] = useState < string | undefined > ( settings ?. message ) ;
232- const [ featuredMemberId , setFeaturedMemberId ] = useState < string | undefined > ( settings ?. featuredMemberId ) ;
233-
234- const updateWelcomeMessage = useCallback (
235- async ( e : FormEvent ) => {
236- e . preventDefault ( ) ;
237- await handleUpdateTeamSettings ( {
238- onboardingSettings : {
239- welcomeMessage : { message, featuredMemberId, enabled : settings ?. enabled ?? false } ,
240- } ,
241- } ) ;
242- } ,
243- [ handleUpdateTeamSettings , message , featuredMemberId , settings ?. enabled ] ,
244- ) ;
245-
246- return (
247- < Modal onClose = { ( ) => setIsOpen ( false ) } visible = { isOpen } containerClassName = "min-[576px]:max-w-[650px]" >
248- < ModalHeader > Edit welcome message</ ModalHeader >
249- < ModalBody >
250- < form id = "welcome-message-editor" onSubmit = { updateWelcomeMessage } className = "space-y-4" >
251- < TextInput readOnly value = "Welcome to Gitpod" className = "cursor-default" > </ TextInput >
252- < Textarea value = { gitpodWelcomeSubheading } readOnly className = "cursor-default resize-none" />
253- < div className = "w-full flex justify-center" >
254- < OrgMemberInput settings = { settings } setFeaturedMemberId = { setFeaturedMemberId } />
255- </ div >
256- < InputField label = "Welcome message" error = { undefined } className = "mb-4" labelHidden >
257- < Textarea
258- className = "bg-pk-surface-secondary text-pk-content-primary w-full p-4 rounded-xl min-h-[150px]"
259- value = { message }
260- placeholder = "Write a welcome message to your organization members. Markdown formatting is supported."
261- onChange = { ( e ) => setMessage ( e . target . value ) }
262- />
263- </ InputField >
264- </ form >
265- </ ModalBody >
266- < ModalFooter >
267- < Button variant = "secondary" onClick = { ( ) => setIsOpen ( false ) } >
268- Cancel
269- </ Button >
270- < LoadingButton type = "submit" loading = { isLoading } disabled = { ! isOwner } form = "welcome-message-editor" >
271- Save
272- </ LoadingButton >
273- </ ModalFooter >
274- </ Modal >
275- ) ;
276- } ;
277-
278- type OrgMemberSelectProps = {
279- settings : OnboardingSettings_WelcomeMessage | undefined ;
280- setFeaturedMemberId : ( featuredMemberId : string | undefined ) => void ;
281- } ;
282- const OrgMemberInput = ( { settings, setFeaturedMemberId } : OrgMemberSelectProps ) => {
283- const { data : members } = useListOrganizationMembers ( ) ;
284-
285- const [ avatarUrl , setAvatarUrl ] = useState < string | undefined > ( settings ?. featuredMemberResolvedAvatarUrl ) ;
286-
287- return (
288- < DropdownMenu >
289- < DropdownMenuTrigger >
290- < div className = "flex flex-col justify-center items-center gap-2" >
291- { avatarUrl ? (
292- < img src = { avatarUrl } alt = "" className = "w-16 h-16 rounded-full" />
293- ) : (
294- < div className = "w-16 h-16 rounded-full bg-[#EA71DE]" />
295- ) }
296- < Button variant = "secondary" size = "sm" type = "button" >
297- Change Photo
298- </ Button >
299- </ div >
300- </ DropdownMenuTrigger >
301- < DropdownMenuContent >
302- < DropdownMenuItem
303- key = "disabled"
304- onClick = { ( ) => {
305- setFeaturedMemberId ( undefined ) ;
306- setAvatarUrl ( undefined ) ;
307- } }
308- >
309- < div className = "flex items-center gap-2" >
310- < div className = "w-4 h-4 rounded-full bg-pk-surface-tertiary" />
311- Disable image
312- </ div >
313- </ DropdownMenuItem >
314- { members ?. map ( ( member ) => (
315- < DropdownMenuItem
316- key = { member . userId }
317- onClick = { ( ) => {
318- setFeaturedMemberId ( member . userId ) ;
319- setAvatarUrl ( member . avatarUrl ) ;
320- } }
321- >
322- < div className = "flex items-center gap-2" >
323- < img src = { member . avatarUrl } alt = { member . fullName } className = "w-4 h-4 rounded-full" />
324- { member . fullName }
325- </ div >
326- </ DropdownMenuItem >
327- ) ) }
328- </ DropdownMenuContent >
329- </ DropdownMenu >
330- ) ;
331- } ;
0 commit comments