Skip to content

Commit

Permalink
update create team form (#280)
Browse files Browse the repository at this point in the history
- supports section managers creating teams
- redirects to new page on successful team creation
- includes a link to the PR that is created
  • Loading branch information
skykanin authored Jun 28, 2024
1 parent 69af07b commit 2c3d58e
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 86 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ProtectedRoute from './components/ProtectedRoute'

import CreateTeamForm from './pages/CreateTeamForm/CreateTeamForm.tsx'
import TeamCreated from './pages/CreateTeamForm/TeamCreated.tsx'
import NotFound from './pages/NotFound/NotFound.tsx'
import TeamOverview from './pages/TeamOverview/TeamOverview'
import UserProfile from './pages/UserProfile/UserProfile'
Expand All @@ -20,6 +21,7 @@ const App = () => {
<Route path='/:teamId' element={<TeamDetail />} />
<Route path='/:teamId/:shortName' element={<SharedBucketDetail />} />
<Route path='/opprett-team' element={<CreateTeamForm />} />
<Route path='/opprett-team/suksess' element={<TeamCreated />} />
<Route path='/not-found' element={<NotFound />} />
</Route>
</Routes>
Expand Down
84 changes: 40 additions & 44 deletions src/pages/CreateTeamForm/CreateTeamForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import {
import * as C from '@statisticsnorway/ssb-component-library'
import { Skeleton } from '@mui/material'
import { useEffect, useState, useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { Array as A, Console, Effect, Option as O, pipe } from 'effect'

import FormSubmissionResult, { FormSubmissionResultProps } from './FormSubmissionResult.tsx'
import FormSubmissionError, { FormSubmissionErrorProps } from './FormSubmissionError.tsx'
import PageLayout from '../../components/PageLayout/PageLayout'
import * as Klass from '../../services/klass'
import { AutonomyLevel, CreateTeamRequest, createTeam } from '../../services/createTeam'
import { AutonomyLevel, CreateTeamRequest, CreateTeamResponse, createTeam } from '../../services/createTeam'
import { User } from '../../@types/user'
import * as Utils from '../../utils/utils.ts'

Expand All @@ -41,6 +42,7 @@ interface FormError {
}

const CreateTeamForm = () => {
const navigate = useNavigate()
const uniformNameLengthLimit = 17
// TODO: These should be fetched from the dapla-team-api instead of being hardcoded
const teamAutonomyLevels: DisplayAutonomyLevel[] = [
Expand Down Expand Up @@ -81,21 +83,11 @@ const CreateTeamForm = () => {

const [submitButtonClicked, setSubmitButtonClicked] = useState(false)

const [createTeamResponseLoading, setCreateTeamResponseLoading] = useState(false)

const missingFieldErrorMessage = 'mangler'
const validationErrorMessage = 'har en valideringsfeil'

const resetForm = () => {
setDisplayName('')
setUniformName('')
setOverrideUniformName(false)
setUniformNameErrorMsg('')
setSelectedSection(O.none())

setSelectedAutonomyLevel(teamAutonomyLevels[0])
setAdditionalInformation('')
setSubmitButtonClicked(false)
}

const formErrors: FormError[] = useMemo(
() =>
pipe(
Expand All @@ -117,9 +109,8 @@ const CreateTeamForm = () => {
[displayName, uniformName, selectedSection, uniformNameErrorMsg]
)

const [formSubmissionResult, setFormSubmissionResult] = useState<FormSubmissionResultProps>({
loading: false,
formSubmissionResult: O.none(),
const [formSubmissionError, setFormSubmissionError] = useState<FormSubmissionErrorProps>({
formSubmissionError: O.none(),
})

useEffect(() => {
Expand Down Expand Up @@ -153,50 +144,55 @@ const CreateTeamForm = () => {
event.preventDefault()
// Only submit the form if no form errors are present
if (A.isEmptyArray(formErrors)) {
const userPrincipalName = O.getOrThrow(user).principal_name
const req: CreateTeamRequest = {
teamDisplayName: displayName,
uniformTeamName: uniformName,
sectionCode: O.getOrThrow(selectedSection).id.toString(),
additionalInformation: `This PR was created through Dapla Ctrl. Additional information from user ${userPrincipalName}:\n ${additionalInformation}`,
additionalInformation: `This PR was created through Dapla Ctrl. Additional information from author:\n\n ${additionalInformation}`,
autonomyLevel: selectedAutonomyLevel.id,
features: [],
}

setFormSubmissionResult({ loading: true, formSubmissionResult: O.none() })
setCreateTeamResponseLoading(true)
setFormSubmissionError({ formSubmissionError: O.none() })

Effect.gen(function* () {
const clientResponse = yield* createTeam(req)
yield* Console.log('ClientResponse', clientResponse)
return O.some(
clientResponse.status !== 200
? {
success: false,
message: `Det oppstod en feil ved opprettelse av team. Statuskode: ${clientResponse.status}`,
}
: { success: true, message: 'Opprettelse av team ble registert.' }
)
const createTeamResponse = yield* createTeam(req)
yield* Console.log('CreateTeamResponse:', createTeamResponse)
return {
success: true,
message: 'Opprettelse av team ble registert.',
body: O.some(createTeamResponse),
}
})
.pipe(
Effect.catchTags({
ResponseError: (error) => Effect.succeed(O.some({ success: false, message: error.message })),
RequestError: (error) => Effect.succeed(O.some({ success: false, message: error.message })),
ResponseError: (error) => Effect.succeed({ success: false, message: error.message, body: O.none() }),
RequestError: (error) => Effect.succeed({ success: false, message: error.message, body: O.none() }),
BodyError: (error) =>
Effect.succeed(O.some({ success: false, message: `Failed to parse body: ${error.reason._tag}` })),
Effect.succeed({
success: false,
message: `Failed to parse http request body: ${error.reason._tag}`,
body: O.none(),
}),
ParseError: (error) =>
Effect.succeed({
success: false,
message: `Failed to parse http response: ${error.message}`,
body: O.none(),
}),
}),
Effect.runPromise
)
.then((res: O.Option<{ success: boolean; message: string }>) => {
if (
Utils.option(
res,
() => false,
(r) => r.success
)
) {
resetForm()
.then((result: { success: boolean; message: string; body: O.Option<CreateTeamResponse> }) => {
if (result.success) {
navigate('/opprett-team/suksess', {
replace: true,
state: { kubenPullRequestUrl: O.getOrThrow(result.body).kubenPullRequestUrl },
})
} else {
setFormSubmissionError({ formSubmissionError: O.some(result.message) })
}
setFormSubmissionResult({ loading: false, formSubmissionResult: res })
})
}
}
Expand Down Expand Up @@ -359,7 +355,7 @@ const CreateTeamForm = () => {
</div>
</Dialog>
)}
<FormSubmissionResult {...formSubmissionResult} />
{createTeamResponseLoading && <FormSubmissionError {...formSubmissionError} />}
</form>
)

Expand Down
24 changes: 24 additions & 0 deletions src/pages/CreateTeamForm/FormSubmissionError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import styles from './createTeamForm.module.scss'
import { Dialog } from '@statisticsnorway/ssb-component-library'
import { Option as O } from 'effect'
import { CircularProgress } from '@mui/material'

export interface FormSubmissionErrorProps {
formSubmissionError: O.Option<string>
}

const FormSubmissionError = ({ formSubmissionError }: FormSubmissionErrorProps) =>
O.match(formSubmissionError, {
onNone: () => (
<div className={styles.center}>
<CircularProgress />
</div>
),
onSome: (errorMessage) => (
<Dialog className={styles.warning} type='warning' title='Feil oppstod ved innsending av skjema'>
{errorMessage}
</Dialog>
),
})

export default FormSubmissionError
37 changes: 0 additions & 37 deletions src/pages/CreateTeamForm/FormSubmissionResult.tsx

This file was deleted.

23 changes: 23 additions & 0 deletions src/pages/CreateTeamForm/TeamCreated.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Dialog, Link } from '@statisticsnorway/ssb-component-library'
import { useLocation } from 'react-router-dom'

import PageLayout from '../../components/PageLayout/PageLayout'
import styles from './createTeamForm.module.scss'

export interface TeamCreatedProps {
kubenPullRequestUrl: string
}

const TeamCreated = () => {
const { state }: { state: TeamCreatedProps } = useLocation()
const renderContent = () => (
<Dialog className={styles.warning} type={'info'} title={'Skjema ble innsendt'}>
<span>{`Opprettelse av team ble registert. Prosessen kan følges `}</span>
<Link href={state.kubenPullRequestUrl}>her</Link>
</Dialog>
)

return <PageLayout title='Opprett Team' content={renderContent()} />
}

export default TeamCreated
18 changes: 13 additions & 5 deletions src/services/createTeam.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Effect } from 'effect'
import { Schema } from '@effect/schema'
import { ParseResult, Schema } from '@effect/schema'
import * as Http from '@effect/platform/HttpClient'
import { HttpClientError } from '@effect/platform/Http/ClientError'
import { ClientResponse } from '@effect/platform/Http/ClientResponse'
import * as ClientResponse from '@effect/platform/Http/ClientResponse'
import { BodyError } from '@effect/platform/Http/Body'
import { DAPLA_TEAM_API_URL } from '../utils/utils'
import { withKeyEncoding } from '../utils/schema'
Expand All @@ -26,13 +26,21 @@ export type AutonomyLevel = Schema.Schema.Type<typeof AutonomyLevelSchema>
export type Feature = Schema.Schema.Type<typeof FeatureSchema>
export type CreateTeamRequest = Schema.Schema.Type<typeof CreateTeamRequestSchema>

const CreateTeamResponse = Schema.Struct({
uniformTeamName: withKeyEncoding('uniform_team_name', Schema.String),
kubenPullRequestId: withKeyEncoding('kuben_pull_request_id', Schema.Number),
kubenPullRequestUrl: withKeyEncoding('kuben_pull_request_url', Schema.String),
})

export type CreateTeamResponse = Schema.Schema.Type<typeof CreateTeamResponse>

export const createTeam = (
createTeamRequest: CreateTeamRequest
): Effect.Effect<ClientResponse, BodyError | HttpClientError> =>
): Effect.Effect<CreateTeamResponse, BodyError | HttpClientError | ParseResult.ParseError> =>
Http.request
.post(new URL(CREATE_TEAM_URL, window.location.origin))
.pipe(
Http.request.schemaBody(CreateTeamRequestSchema)(createTeamRequest),
Effect.flatMap(Http.client.fetch),
Effect.scoped
Effect.flatMap(Http.client.fetchOk),
ClientResponse.schemaBodyJsonScoped(CreateTeamResponse)
)

0 comments on commit 2c3d58e

Please sign in to comment.