1
+ import GitHubIcon from "@mui/icons-material/GitHub" ;
1
2
import LoadingButton from "@mui/lab/LoadingButton" ;
2
3
import AlertTitle from "@mui/material/AlertTitle" ;
3
4
import Autocomplete from "@mui/material/Autocomplete" ;
5
+ import Button from "@mui/material/Button" ;
4
6
import Checkbox from "@mui/material/Checkbox" ;
5
7
import Link from "@mui/material/Link" ;
6
8
import MenuItem from "@mui/material/MenuItem" ;
@@ -15,8 +17,7 @@ import { PasswordField } from "components/PasswordField/PasswordField";
15
17
import { SignInLayout } from "components/SignInLayout/SignInLayout" ;
16
18
import { Stack } from "components/Stack/Stack" ;
17
19
import { type FormikContextType , useFormik } from "formik" ;
18
- import type { FC } from "react" ;
19
- import { useEffect } from "react" ;
20
+ import { type ChangeEvent , type FC , useCallback } from "react" ;
20
21
import { docs } from "utils/docs" ;
21
22
import {
22
23
getFormHelpers ,
@@ -33,7 +34,8 @@ export const Language = {
33
34
emailInvalid :"Please enter a valid email address." ,
34
35
emailRequired :"Please enter an email address." ,
35
36
passwordRequired :"Please enter a password." ,
36
- create :"Create account" ,
37
+ create :"Continue with email" ,
38
+ githubCreate :"Continue with GitHub" ,
37
39
welcomeMessage :< > Welcome to Coder</ > ,
38
40
firstNameLabel :"First name" ,
39
41
lastNameLabel :"Last name" ,
@@ -50,13 +52,29 @@ export const Language = {
50
52
developersRequired :"Please select the number of developers in your company." ,
51
53
} ;
52
54
55
+ const usernameValidator = nameValidator ( Language . usernameLabel ) ;
56
+ const usernameFromEmail = ( email :string ) :string => {
57
+ try {
58
+ const emailPrefix = email . split ( "@" ) [ 0 ] ;
59
+ const username = emailPrefix . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, "-" ) ;
60
+ usernameValidator . validateSync ( username ) ;
61
+ return username ;
62
+ } catch ( error ) {
63
+ console . warn (
64
+ "failed to automatically generate username, defaulting to 'admin'" ,
65
+ error ,
66
+ ) ;
67
+ return "admin" ;
68
+ }
69
+ } ;
70
+
53
71
const validationSchema = Yup . object ( {
54
72
email :Yup . string ( )
55
73
. trim ( )
56
74
. email ( Language . emailInvalid )
57
75
. required ( Language . emailRequired ) ,
58
76
password :Yup . string ( ) . required ( Language . passwordRequired ) ,
59
- username :nameValidator ( Language . usernameLabel ) ,
77
+ username :usernameValidator ,
60
78
trial :Yup . bool ( ) ,
61
79
trial_info :Yup . object ( ) . when ( "trial" , {
62
80
is :true ,
@@ -81,16 +99,23 @@ const numberOfDevelopersOptions = [
81
99
"2500+" ,
82
100
] ;
83
101
102
+ const iconStyles = {
103
+ width :16 ,
104
+ height :16 ,
105
+ } ;
106
+
84
107
export interface SetupPageViewProps {
85
108
onSubmit :( firstUser :TypesGen . CreateFirstUserRequest ) => void ;
86
109
error ?:unknown ;
87
110
isLoading ?:boolean ;
111
+ authMethods :TypesGen . AuthMethods | undefined ;
88
112
}
89
113
90
114
export const SetupPageView :FC < SetupPageViewProps > = ( {
91
115
onSubmit,
92
116
error,
93
117
isLoading,
118
+ authMethods,
94
119
} ) => {
95
120
const form :FormikContextType < TypesGen . CreateFirstUserRequest > =
96
121
useFormik < TypesGen . CreateFirstUserRequest > ( {
@@ -112,6 +137,10 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
112
137
} ,
113
138
validationSchema,
114
139
onSubmit,
140
+ // With validate on blur set to true, the form lights up red whenever
141
+ // you click out of it. This is a bit jarring. We instead validate
142
+ // on submit and change.
143
+ validateOnBlur :false ,
115
144
} ) ;
116
145
const getFieldHelpers = getFormHelpers < TypesGen . CreateFirstUserRequest > (
117
146
form ,
@@ -142,23 +171,36 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
142
171
</ header >
143
172
< VerticalForm onSubmit = { form . handleSubmit } >
144
173
< FormFields >
145
- < TextField
146
- autoFocus
147
- { ...getFieldHelpers ( "username" ) }
148
- onChange = { onChangeTrimmed ( form ) }
149
- autoComplete = "username"
150
- fullWidth
151
- label = { Language . usernameLabel }
152
- />
153
- < TextField
154
- { ...getFieldHelpers ( "name" ) }
155
- autoComplete = "name"
156
- fullWidth
157
- label = { Language . nameLabel }
158
- />
174
+ { authMethods ?. github . enabled && (
175
+ < >
176
+ < Button
177
+ fullWidth
178
+ component = "a"
179
+ href = "/api/v2/users/oauth2/github/callback"
180
+ variant = "contained"
181
+ startIcon = { < GitHubIcon css = { iconStyles } /> }
182
+ type = "submit"
183
+ size = "xlarge"
184
+ >
185
+ { Language . githubCreate }
186
+ </ Button >
187
+ < div className = "flex items-center gap-4" >
188
+ < div className = "h-[1px] w-full bg-border" />
189
+ < div className = "shrink-0 text-xs uppercase text-content-secondary tracking-wider" >
190
+ or
191
+ </ div >
192
+ < div className = "h-[1px] w-full bg-border" />
193
+ </ div >
194
+ </ >
195
+ ) }
159
196
< TextField
160
197
{ ...getFieldHelpers ( "email" ) }
161
- onChange = { onChangeTrimmed ( form ) }
198
+ onChange = { ( event ) => {
199
+ const email = event . target . value ;
200
+ const username = usernameFromEmail ( email ) ;
201
+ form . setFieldValue ( "username" , username ) ;
202
+ onChangeTrimmed ( form ) ( event as ChangeEvent < HTMLInputElement > ) ;
203
+ } }
162
204
autoComplete = "email"
163
205
fullWidth
164
206
label = { Language . emailLabel }
@@ -340,9 +382,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
340
382
loading = { isLoading }
341
383
type = "submit"
342
384
data-testid = "create"
343
- size = "large"
344
- variant = "contained"
345
- color = "primary"
385
+ size = "xlarge"
346
386
>
347
387
{ Language . create }
348
388
</ LoadingButton >