Skip to content

Commit b2433ea

Browse files
committed
Add NewOrgModal
Add a new app modal, that allows users to create organizations. It supports suggesting handles from the name and checking if handles are taken or not. It also allows the user to pick between public (free) orgs, and commercial (paid) orgs. When picking a commercial org, the backend will create a support ticket (in addition to the org).
1 parent 6b95171 commit b2433ea

15 files changed

+572
-31
lines changed

elm-git.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"git-dependencies": {
33
"direct": {
4-
"https://github.com/unisonweb/ui-core": "194adc73aceca4e3ae1519206e122419c9985f35"
4+
"https://github.com/unisonweb/ui-core": "78ba490fac82e43146be5653e5a9be2288ac6ad0"
55
},
66
"indirect": {}
77
}

elm.json

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"j-maas/elm-ordered-containers": "1.0.0",
3232
"justinmimbs/time-extra": "1.1.1",
3333
"krisajenkins/remotedata": "6.0.1",
34+
"kuon/elm-string-normalize": "1.0.6",
3435
"mgold/elm-nonempty-list": "4.2.0",
3536
"noahzgordon/elm-color-extra": "1.0.2",
3637
"rtfeldman/elm-iso8601-date-strings": "1.1.4",

src/UnisonShare/Account.elm

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type alias Account a =
2020
, completedTours : List Tour
2121
, organizationMemberships : List OrganizationMembership
2222
, isSuperAdmin : Bool
23+
, primaryEmail : String
2324
}
2425

2526

@@ -89,20 +90,22 @@ isProjectOwner projectRef account =
8990
decodeSummary : Decode.Decoder AccountSummary
9091
decodeSummary =
9192
let
92-
makeSummary handle name_ avatarUrl completedTours organizationMemberships isSuperAdmin =
93+
makeSummary handle name_ avatarUrl completedTours organizationMemberships isSuperAdmin primaryEmail =
9394
{ handle = handle
9495
, name = name_
9596
, avatarUrl = avatarUrl
9697
, pronouns = Nothing
9798
, completedTours = Maybe.withDefault [] completedTours
9899
, organizationMemberships = organizationMemberships
99100
, isSuperAdmin = isSuperAdmin
101+
, primaryEmail = primaryEmail
100102
}
101103
in
102-
Decode.map6 makeSummary
104+
Decode.map7 makeSummary
103105
(field "handle" UserHandle.decodeUnprefixed)
104106
(maybe (field "name" string))
105107
(maybe (field "avatarUrl" decodeUrl))
106108
(maybe (field "completedTours" (Decode.list Tour.decode)))
107109
(field "organizationMemberships" (Decode.list (Decode.map OrganizationMembership UserHandle.decodeUnprefixed)))
108110
(field "isSuperadmin" Decode.bool)
111+
(field "primaryEmail" string)

src/UnisonShare/Api.elm

+31-2
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,26 @@ import UnisonShare.Tour as Tour exposing (Tour)
3636
import Url.Builder exposing (QueryParameter, int, string)
3737

3838

39+
profile : UserHandle -> Endpoint
40+
profile handle =
41+
GET { path = [ "users", UserHandle.toUnprefixedString handle ], queryParams = [] }
42+
43+
3944
user : UserHandle -> Endpoint
4045
user handle =
4146
GET { path = [ "users", UserHandle.toUnprefixedString handle ], queryParams = [] }
4247

4348

49+
org : UserHandle -> Endpoint
50+
org handle =
51+
GET { path = [ "users", UserHandle.toUnprefixedString handle ], queryParams = [] }
52+
53+
4454
updateUserProfile : UserHandle -> { bio : String } -> Endpoint
45-
updateUserProfile handle profile =
55+
updateUserProfile handle profile_ =
4656
let
4757
body =
48-
Encode.object [ ( "bio", Encode.string profile.bio ) ]
58+
Encode.object [ ( "bio", Encode.string profile_.bio ) ]
4959
|> Http.jsonBody
5060
in
5161
PATCH
@@ -178,6 +188,25 @@ session =
178188
-- ORG ROLE ASSIGNMENTS (COLLABORATORS)
179189

180190

191+
createOrg : { a | handle : UserHandle, primaryEmail : String } -> String -> UserHandle -> Bool -> Endpoint
192+
createOrg owner name orgHandle isCommercial =
193+
let
194+
body =
195+
Encode.object
196+
[ ( "name", Encode.string name )
197+
, ( "handle", Encode.string (UserHandle.toUnprefixedString orgHandle) )
198+
, ( "isCommercial", Encode.bool isCommercial )
199+
, ( "owner", Encode.string (UserHandle.toUnprefixedString owner.handle) )
200+
, ( "email", Encode.string owner.primaryEmail )
201+
]
202+
in
203+
POST
204+
{ path = [ "orgs" ]
205+
, queryParams = []
206+
, body = Http.jsonBody body
207+
}
208+
209+
181210
orgRoleAssignments : UserHandle -> Endpoint
182211
orgRoleAssignments orgHandle =
183212
let

src/UnisonShare/App.elm

+34
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import UnisonShare.AppDocument as AppDocument
4040
import UnisonShare.AppError as AppError exposing (AppError)
4141
import UnisonShare.AppHeader as AppHeader
4242
import UnisonShare.Link as Link
43+
import UnisonShare.NewOrgModal as NewOrgModal
4344
import UnisonShare.Page.AcceptTermsPage as AcceptTermsPage
4445
import UnisonShare.Page.AccountPage as AccountPage
4546
import UnisonShare.Page.AppErrorPage as AppErrorPage
@@ -86,6 +87,7 @@ type Page
8687
type AppModal
8788
= NoModal
8889
| KeyboardShortcuts
90+
| NewOrg NewOrgModal.Model
8991

9092

9193
type alias Model =
@@ -213,6 +215,7 @@ type Msg
213215
| ToggleAccountMenu
214216
| ToggleCreateAccountMenu
215217
| ShowKeyboardShortcuts
218+
| ShowNewOrgModal
216219
| CloseModal
217220
| WhatsNewFetchFinished (HttpResult WhatsNew.LoadedWhatsNew)
218221
| WhatsNewMarkAllAsRead
@@ -224,6 +227,7 @@ type Msg
224227
| ProjectPageMsg ProjectPage.Msg
225228
| AccountPageMsg AccountPage.Msg
226229
| AcceptTermsPageMsg AcceptTermsPage.Msg
230+
| NewOrgModalMsg NewOrgModal.Msg
227231

228232

229233
update : Msg -> Model -> ( Model, Cmd Msg )
@@ -465,6 +469,9 @@ update msg ({ appContext } as model) =
465469
( _, ShowKeyboardShortcuts ) ->
466470
( { model | openedAppHeaderMenu = AppHeader.NoneOpened, appModal = KeyboardShortcuts }, Cmd.none )
467471

472+
( _, ShowNewOrgModal ) ->
473+
( { model | openedAppHeaderMenu = AppHeader.NoneOpened, appModal = NewOrg NewOrgModal.init }, Cmd.none )
474+
468475
( _, CloseModal ) ->
469476
( { model | appModal = NoModal }, Cmd.none )
470477

@@ -523,6 +530,29 @@ update msg ({ appContext } as model) =
523530
in
524531
( { model | page = AcceptTerms continueUrl acceptTerms_ }, Cmd.map AcceptTermsPageMsg acceptTermsCmd )
525532

533+
( _, NewOrgModalMsg newOrgMsg ) ->
534+
case ( model.appModal, appContext.session ) of
535+
( NewOrg newOrg, Session.SignedIn account ) ->
536+
let
537+
( newOrg_, cmd, out ) =
538+
NewOrgModal.update appContext account newOrgMsg newOrg
539+
540+
appModal =
541+
case out of
542+
NewOrgModal.NoOutMsg ->
543+
NewOrg newOrg_
544+
545+
NewOrgModal.RequestCloseModal ->
546+
NoModal
547+
548+
NewOrgModal.AddedOrg _ ->
549+
NoModal
550+
in
551+
( { model | appModal = appModal }, Cmd.map NewOrgModalMsg cmd )
552+
553+
_ ->
554+
( model, Cmd.none )
555+
526556
_ ->
527557
( model, Cmd.none )
528558

@@ -720,6 +750,7 @@ view model =
720750
, toggleAccountMenuMsg = ToggleAccountMenu
721751
, toggleCreateAccountMenuMsg = ToggleCreateAccountMenu
722752
, showKeyboardShortcutsModalMsg = ShowKeyboardShortcuts
753+
, showNewOrgModal = ShowNewOrgModal
723754
}
724755

725756
appDocument =
@@ -784,6 +815,9 @@ view model =
784815
KeyboardShortcuts ->
785816
{ appDocument | modal = Just (viewKeyboardShortcutsModal appContext.operatingSystem) }
786817

818+
NewOrg m ->
819+
{ appDocument | modal = Just (Html.map NewOrgModalMsg (NewOrgModal.view m)) }
820+
787821
appDocumentWithWelcomeTermsModal =
788822
-- We link to TermsOfService and PrivacyPolicy from the welcome
789823
-- terms modal and the AcceptTerms page is used during UCM signup,

src/UnisonShare/AppHeader.elm

+18-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Html.Attributes exposing (class, classList)
55
import Lib.HttpApi exposing (HttpApi)
66
import Lib.UserHandle as UserHandle exposing (UserHandle)
77
import Time
8+
import UI
89
import UI.ActionMenu as ActionMenu
910
import UI.AnchoredOverlay as AnchoredOverlay
1011
import UI.AppHeader exposing (AppHeader, AppTitle(..))
@@ -120,6 +121,7 @@ type alias AppHeaderContext msg =
120121
, toggleHelpAndResourcesMenuMsg : msg
121122
, toggleAccountMenuMsg : msg
122123
, toggleCreateAccountMenuMsg : msg
124+
, showNewOrgModal : msg
123125
, showKeyboardShortcutsModalMsg : msg
124126
}
125127

@@ -277,21 +279,21 @@ view ctx appHeader_ =
277279
]
278280
)
279281

280-
SignedIn sesh ->
282+
SignedIn account ->
281283
let
282284
nav =
283285
case activeNavItem of
284286
Catalog ->
285-
Navigation.empty |> Navigation.withItems [] navItems.catalog [ navItems.profile sesh.handle ]
287+
Navigation.empty |> Navigation.withItems [] navItems.catalog [ navItems.profile account.handle ]
286288

287289
Profile ->
288-
Navigation.empty |> Navigation.withItems [ navItems.catalog ] (navItems.profile sesh.handle) []
290+
Navigation.empty |> Navigation.withItems [ navItems.catalog ] (navItems.profile account.handle) []
289291

290292
_ ->
291-
Navigation.empty |> Navigation.withNoSelectedItems [ navItems.catalog, navItems.profile sesh.handle ]
293+
Navigation.empty |> Navigation.withNoSelectedItems [ navItems.catalog, navItems.profile account.handle ]
292294

293295
avatar =
294-
Avatar.avatar sesh.avatarUrl (Maybe.map (String.left 1) sesh.name)
296+
Avatar.avatar account.avatarUrl (Maybe.map (String.left 1) account.name)
295297
|> Avatar.view
296298

297299
viewAccountMenuTrigger isOpen =
@@ -306,6 +308,16 @@ view ctx appHeader_ =
306308
div [ classList [ ( "account-menu-trigger", True ), ( "account-menu_is-open", isOpen ) ] ]
307309
[ avatar, Icon.view chevron ]
308310

311+
newOrgButton =
312+
if account.isSuperAdmin then
313+
Button.iconThenLabel ctx.showNewOrgModal Icon.largePlus "New Org"
314+
|> Button.small
315+
|> Button.positive
316+
|> Button.view
317+
318+
else
319+
UI.nothing
320+
309321
accountMenu =
310322
ActionMenu.items
311323
(ActionMenu.optionItem Icon.cog "Account Settings" Link.account)
@@ -315,7 +327,7 @@ view ctx appHeader_ =
315327
|> ActionMenu.view
316328
|> (\a -> div [ class "account-menu" ] [ a ])
317329
in
318-
( nav, [ helpAndResources, accountMenu ] )
330+
( nav, [ newOrgButton, helpAndResources, accountMenu ] )
319331
in
320332
UI.AppHeader.appHeader (appTitle (Click.href "/"))
321333
|> UI.AppHeader.withNavigation navigation

0 commit comments

Comments
 (0)