diff --git a/packages/client/components/StageTimerModalTimeLimit.tsx b/packages/client/components/StageTimerModalTimeLimit.tsx
index 3593b9695cb..8cab4f75b19 100644
--- a/packages/client/components/StageTimerModalTimeLimit.tsx
+++ b/packages/client/components/StageTimerModalTimeLimit.tsx
@@ -107,7 +107,7 @@ const StageTimerModalTimeLimit = (props: Props) => {
{scheduledEndTime ? 'Add Time' : `Start ${MeetingLabels.TIMER}`}
- {error && {error}}
+ {error && {error.message}}
)
}
diff --git a/packages/client/hooks/useForm.tsx b/packages/client/hooks/useForm.tsx
index 6474e3a326e..865fccae501 100644
--- a/packages/client/hooks/useForm.tsx
+++ b/packages/client/hooks/useForm.tsx
@@ -109,7 +109,7 @@ const useForm = (fieldInputDict: FieldInputDict, deps: any[] = []) => {
const validate = useEventCallback((name: string, value: any) => {
const validateField = fieldInputDict[name].validate
- if (!validateField) return {error: undefined, value: state[name].value}
+ if (!validateField) return {error: undefined, value}
const res: Legitity = validateField(value)
dispatch({type: 'setError', name, error: res.error})
return res
diff --git a/packages/client/modules/newTeam/NewTeam.tsx b/packages/client/modules/newTeam/NewTeam.tsx
index 888b6830e9e..bd588fc02bc 100644
--- a/packages/client/modules/newTeam/NewTeam.tsx
+++ b/packages/client/modules/newTeam/NewTeam.tsx
@@ -74,7 +74,7 @@ const NewTeam = (props: Props) => {
return (
-
+
{isDesktop && (
diff --git a/packages/client/modules/newTeam/components/NewTeamForm/NewTeamForm.tsx b/packages/client/modules/newTeam/components/NewTeamForm/NewTeamForm.tsx
index 6e94245414e..70171135b82 100644
--- a/packages/client/modules/newTeam/components/NewTeamForm/NewTeamForm.tsx
+++ b/packages/client/modules/newTeam/components/NewTeamForm/NewTeamForm.tsx
@@ -1,20 +1,15 @@
import {NewTeamForm_organizations} from '../../../../__generated__/NewTeamForm_organizations.graphql'
-import React, {Component} from 'react'
+import React, {useState, ChangeEvent, FormEvent} from 'react'
import styled from '@emotion/styled'
import {createFragmentContainer} from 'react-relay'
import graphql from 'babel-plugin-relay/macro'
-import {RouteComponentProps, withRouter} from 'react-router-dom'
import FieldLabel from '../../../../components/FieldLabel/FieldLabel'
import Panel from '../../../../components/Panel/Panel'
import PrimaryButton from '../../../../components/PrimaryButton'
import Radio from '../../../../components/Radio/Radio'
-import withAtmosphere, {
- WithAtmosphereProps
-} from '../../../../decorators/withAtmosphere/withAtmosphere'
import NewTeamOrgPicker from '../../../team/components/NewTeamOrgPicker'
import AddOrgMutation from '../../../../mutations/AddOrgMutation'
import AddTeamMutation from '../../../../mutations/AddTeamMutation'
-import withMutationProps, {WithMutationProps} from '../../../../utils/relay/withMutationProps'
import Legitity from '../../../../validation/Legitity'
import teamNameValidation from '../../../../validation/teamNameValidation'
import NewTeamFormBlock from './NewTeamFormBlock'
@@ -22,6 +17,11 @@ import NewTeamFormOrgName from './NewTeamFormOrgName'
import NewTeamFormTeamName from './NewTeamFormTeamName'
import StyledError from '../../../../components/StyledError'
import DashHeaderTitle from '../../../../components/DashHeaderTitle'
+import linkify from '../../../../utils/linkify'
+import useMutationProps from '../../../../hooks/useMutationProps'
+import useAtmosphere from '../../../../hooks/useAtmosphere'
+import useRouter from '../../../../hooks/useRouter'
+import useForm from '../../../../hooks/useForm'
const StyledForm = styled('form')({
margin: 0,
@@ -63,79 +63,26 @@ const StyledButton = styled(PrimaryButton)({
const controlSize = 'medium'
-interface Props extends WithMutationProps, WithAtmosphereProps, RouteComponentProps<{}> {
- isNewOrganization: boolean
+interface Props {
+ isInitiallyNewOrg: boolean
organizations: NewTeamForm_organizations
}
-interface State {
- isNewOrg: boolean
- orgId: string
- fields: Fields
-}
-
-interface Field {
- dirty?: boolean
- error: string | undefined
- value: string
-}
-
-interface Fields {
- orgName: Field
- teamName: Field
-}
+const NewTeamForm = (props: Props) => {
+ const {isInitiallyNewOrg, organizations} = props
+ const [isNewOrg, setIsNewOrg] = useState(isInitiallyNewOrg)
+ const [orgId, setOrgId] = useState('')
-type FieldName = 'orgName' | 'teamName'
-const DEFAULT_FIELD = {value: '', error: undefined, dirty: false}
-
-class NewTeamForm extends Component {
- state = {
- isNewOrg: this.props.isNewOrganization,
- orgId: '',
- fields: {
- orgName: {...DEFAULT_FIELD},
- teamName: {...DEFAULT_FIELD}
- }
- }
-
- setOrgId = (orgId: string) => {
- this.setState(
- {
- orgId
- },
- () => {
- this.validate('teamName')
- }
- )
- }
-
- validate = (name: FieldName) => {
- const validators = {
- teamName: this.validateTeamName,
- orgName: this.validateOrgName
- }
- const res: Legitity = validators[name]()
-
- const {fields} = this.state
- const field = fields[name]
- if (res.error !== field.error) {
- this.setState({
- fields: {
- ...fields,
- [name]: {
- ...field,
- error: res.error
- }
- }
- })
- }
- return res
+ const validateOrgName = (orgName: string) => {
+ return new Legitity(orgName)
+ .trim()
+ .required('Your new org needs a name!')
+ .min(2, 'C’mon, you call that an organization?')
+ .max(100, 'Maybe just the legal name?')
+ .test((val) => (linkify.match(val) ? 'Try using a name, not a link!' : undefined))
}
- validateTeamName = () => {
- const {organizations} = this.props
- const {isNewOrg, orgId, fields} = this.state
- const rawTeamName = fields.teamName.value
+ const validateTeamName = (teamName: string) => {
let teamNames: string[] = []
if (!isNewOrg) {
const org = organizations.find((org) => org.id === orgId)
@@ -143,109 +90,50 @@ class NewTeamForm extends Component {
teamNames = org.teams.map((team) => team.name)
}
}
- return teamNameValidation(rawTeamName, teamNames)
- }
-
- validateOrgName = () => {
- const {fields} = this.state
- const rawOrgName = fields.orgName.value
- return new Legitity(rawOrgName)
- .trim()
- .required('Your new org needs a name!')
- .min(2, 'C’mon, you call that an organization?')
- .max(100, 'Maybe just the legal name?')
+ return teamNameValidation(teamName, teamNames)
}
- handleBlur = (e: React.FocusEvent) => {
- this.setDirty(e.target.name as FieldName)
- }
-
- setDirty = (name: FieldName) => {
- const {fields} = this.state
- const field = fields[name]
- if (!field.dirty) {
- this.setState({
- fields: {
- ...fields,
- [name]: {
- ...field,
- dirty: true
- }
- }
- })
+ const {fields, onChange, validateField} = useForm({
+ orgName: {
+ getDefault: () => '',
+ validate: validateOrgName
+ },
+ teamName: {
+ getDefault: () => '',
+ validate: validateTeamName
}
- }
+ })
- handleInputChange = (e: React.ChangeEvent) => {
- const {fields} = this.state
- const {value} = e.target
- const name = e.target.name as FieldName
- const field = fields[name]
- this.setState(
- {
- fields: {
- ...fields,
- [name]: {
- ...field,
- value
- }
- }
- },
- () => {
- this.validate(name)
- }
- )
+ const {submitting, onError, error, onCompleted, submitMutation} = useMutationProps()
+ const atmosphere = useAtmosphere()
+ const {history} = useRouter()
+
+ const updateOrgId = (orgId: string) => {
+ setOrgId(orgId)
}
- handleIsNewOrgChange = (e: React.ChangeEvent) => {
+ const handleIsNewOrgChange = (e: ChangeEvent) => {
const isNewOrg = e.target.value === 'true'
- this.setState(
- {
- isNewOrg,
- fields: {
- ...this.state.fields,
- orgName: {
- ...this.state.fields.orgName,
- dirty: false,
- error: undefined
- }
- }
- },
- () => {
- this.validate('teamName')
- }
- )
- // if (isNewOrg) {
- // setTimeout(() => {
- // this.newOrgInputRef.current && this.newOrgInputRef.current.focus()
- // })
- // }
+ setIsNewOrg(isNewOrg)
}
- onSubmit = (e: React.FormEvent) => {
- const {atmosphere, history, onError, onCompleted, submitMutation, submitting} = this.props
+ const onSubmit = (e: FormEvent) => {
e.preventDefault()
if (submitting) return
- const {isNewOrg, orgId} = this.state
- const fieldNames: FieldName[] = ['teamName']
- fieldNames.forEach(this.setDirty)
- const fieldRes = fieldNames.map(this.validate)
- const hasError = fieldRes.reduce((err: boolean, val) => err || !!val.error, false)
- if (hasError) return
- const [teamRes] = fieldRes
+ const {error: teamErr, value: teamName} = validateField('teamName')
+ if (teamErr) return
if (isNewOrg) {
- this.setDirty('orgName')
- const {error, value: orgName} = this.validate('orgName')
- if (error) return
+ const {error: orgErr, value: orgName} = validateField('orgName')
+ if (orgErr) return
const newTeam = {
- name: teamRes.value
+ name: teamName
}
const variables = {newTeam, orgName}
submitMutation()
AddOrgMutation(atmosphere, variables, {history, onError, onCompleted})
} else {
const newTeam = {
- name: teamRes.value,
+ name: teamName,
orgId
}
submitMutation()
@@ -253,67 +141,58 @@ class NewTeamForm extends Component {
}
}
- render() {
- const {fields, isNewOrg, orgId} = this.state
- const {error, submitting, organizations} = this.props
-
- return (
-
-
- {'Create a New Team'}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {'Create a New Team'}
+
+
+
+
+
+
+
+
-
-
-
- {isNewOrg ? 'Create Team & Org' : 'Create Team'}
-
- {error && {error}}
-
-
-
- )
- }
+
+
+
+
+
+
+
+
+ {isNewOrg ? 'Create Team & Org' : 'Create Team'}
+
+ {error && {error.message}}
+
+
+
+ )
}
-export default createFragmentContainer(withAtmosphere(withRouter(withMutationProps(NewTeamForm))), {
+export default createFragmentContainer(NewTeamForm, {
organizations: graphql`
fragment NewTeamForm_organizations on Organization @relay(plural: true) {
...NewTeamOrgPicker_organizations
diff --git a/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormOrgName.tsx b/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormOrgName.tsx
index 8c3605c4227..46b57697790 100644
--- a/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormOrgName.tsx
+++ b/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormOrgName.tsx
@@ -1,58 +1,41 @@
-import React, {Component} from 'react'
+import React, {ChangeEvent} from 'react'
import BasicInput from '../../../../components/InputField/BasicInput'
import Radio from '../../../../components/Radio/Radio'
import {NewTeamFieldBlock} from './NewTeamForm'
import NewTeamFormBlock from './NewTeamFormBlock'
interface Props {
- dirty: boolean
error: string | undefined
isNewOrg: boolean
- onTypeChange: (e: React.ChangeEvent) => void
-
- onChange(e: React.ChangeEvent): void
-
+ onTypeChange: (e: ChangeEvent) => void
+ onChange(e: ChangeEvent): void
orgName: string
placeholder: string
-
- onBlur(e: React.FocusEvent): void
}
-class NewTeamFormOrgName extends Component {
- render() {
- const {
- dirty,
- error,
- isNewOrg,
- onChange,
- onTypeChange,
- orgName,
- placeholder,
- onBlur
- } = this.props
- return (
-
- {
+ const {error, isNewOrg, onChange, onTypeChange, orgName, placeholder} = props
+ return (
+
+
+
+
-
-
-
-
- )
- }
+
+
+ )
}
export default NewTeamFormOrgName
diff --git a/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormTeamName.tsx b/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormTeamName.tsx
index 51f9d89c86a..0be7ae9b19e 100644
--- a/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormTeamName.tsx
+++ b/packages/client/modules/newTeam/components/NewTeamForm/NewTeamFormTeamName.tsx
@@ -1,4 +1,4 @@
-import React, {Component} from 'react'
+import React, {ChangeEvent} from 'react'
import styled from '@emotion/styled'
import FieldLabel from '../../../../components/FieldLabel/FieldLabel'
import BasicInput from '../../../../components/InputField/BasicInput'
@@ -14,34 +14,23 @@ const FormBlockInline = styled(NewTeamFormBlock)({
})
interface Props {
- dirty: boolean
error: string | undefined
- onChange(e: React.ChangeEvent): void
+ onChange(e: ChangeEvent): void
teamName: string
-
- onBlur(e: React.FocusEvent): void
}
-class NewTeamFormTeamName extends Component {
- render() {
- const {dirty, error, onChange, onBlur, teamName} = this.props
- return (
-
-
-
-
-
-
- )
- }
+const NewTeamFormTeamName = (props: Props) => {
+ const {error, onChange, teamName} = props
+ return (
+
+
+
+
+
+
+ )
}
export default NewTeamFormTeamName
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationList.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationList.tsx
index 18d99672bee..fdb40e6628e 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationList.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationList.tsx
@@ -109,7 +109,7 @@ const SlackNotificationList = (props: Props) => {
teamId={teamId}
/>
- {error && {error}}
+ {error && {error.message}}
{TEAM_EVENTS.map((event) => {
return (
{
Private Notifications
{'@Parabol'}
- {error && {error}}
+ {error && {error.message}}
{localPrivateChannelId &&
USER_EVENTS.map((event) => {
return (
diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationRow.tsx
index d89391a8333..3c96bf84ce6 100644
--- a/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationRow.tsx
+++ b/packages/client/modules/teamDashboard/components/ProviderRow/SlackNotificationRow.tsx
@@ -71,7 +71,7 @@ const SlackNotificationRow = (props: Props) => {
- {error && {error}}
+ {error && {error.message}}
>
)
}
diff --git a/packages/client/mutations/AddOrgMutation.ts b/packages/client/mutations/AddOrgMutation.ts
index 713b689c8b7..7088aa9f59f 100644
--- a/packages/client/mutations/AddOrgMutation.ts
+++ b/packages/client/mutations/AddOrgMutation.ts
@@ -11,6 +11,7 @@ import {
} from '../types/relayMutations'
import {AddOrgMutation_organization} from '../__generated__/AddOrgMutation_organization.graphql'
import {AddOrgMutation as TAddOrgMutation} from '../__generated__/AddOrgMutation.graphql'
+import getGraphQLError from '../utils/relay/getGraphQLError'
graphql`
fragment AddOrgMutation_organization on AddOrgPayload {
@@ -100,7 +101,8 @@ const AddOrgMutation: StandardMutation = (
onCompleted(res, errors)
}
const {addOrg} = res
- if (!errors) {
+ const error = getGraphQLError(res, errors)
+ if (!error) {
const {authToken} = addOrg
atmosphere.setAuthToken(authToken)
popOrganizationCreatedToast(addOrg, {atmosphere})
diff --git a/packages/client/validation/teamNameValidation.ts b/packages/client/validation/teamNameValidation.ts
index a74bfeccd39..74acab7d3f4 100644
--- a/packages/client/validation/teamNameValidation.ts
+++ b/packages/client/validation/teamNameValidation.ts
@@ -1,12 +1,14 @@
+import linkify from '../utils/linkify'
import Legitity from './Legitity'
-const teamNameValidation = (rawTeamName, teamNames) => {
+const teamNameValidation = (rawTeamName: string, teamNames: string[]) => {
return new Legitity(rawTeamName)
.trim()
.required('“The nameless wonder” is better than nothing')
.min(2, 'The “A Team” had a longer name than that')
.max(50, 'That isn’t very memorable. Maybe shorten it up?')
- .test((val) => teamNames.includes(val) && 'That name is already taken')
+ .test((val) => (teamNames.includes(val) ? 'That name is already taken' : undefined))
+ .test((val) => (linkify.match(val) ? 'Try using a name, not a link!' : undefined))
}
export default teamNameValidation
diff --git a/packages/client/validation/templates.js b/packages/client/validation/templates.js
index 53e566b53ae..d47aac098c1 100644
--- a/packages/client/validation/templates.js
+++ b/packages/client/validation/templates.js
@@ -1,5 +1,6 @@
import {TASK_MAX_CHARS} from '../utils/constants'
import {compositeIdRegex, emailRegex, idRegex} from './regex'
+import linkify from '../utils/linkify'
export const avatar = {
size: (value) =>
@@ -43,6 +44,7 @@ export const orgName = (value) =>
.required('Your new org needs a name!')
.min(2, 'C’mon, you call that an organization?')
.max(100, 'Maybe just the legal name?')
+ .test((val) => (linkify.match(val) ? 'Try using a name, not a link!' : undefined))
export const preferredName = (value) =>
value
@@ -63,6 +65,7 @@ export const teamName = (value) =>
.required('“The nameless wonder” is better than nothing')
.min(2, 'The “A Team” had a longer name than that')
.max(50, 'That isn’t very memorable. Maybe shorten it up?')
+ .test((val) => (linkify.match(val) ? 'Try using a name, not a link!' : undefined))
export const makeTeamNameSchema = (teamNames) => (value) =>
value
@@ -71,20 +74,18 @@ export const makeTeamNameSchema = (teamNames) => (value) =>
.min(2, 'The “A Team” had a longer name than that')
.max(50, 'That isn’t very memorable. Maybe shorten it up?')
.test((val) => teamNames.includes(val) && 'That name is already taken')
+ .test((val) => (linkify.match(val) ? 'Try using a name, not a link!' : undefined))
export const optionalUrl = (value) =>
value
.trim()
- .test(
- (value) => {
- if (value) {
- try {
- new URL(value)
- } catch (e) {
- return e.message
- }
+ .test((value) => {
+ if (value) {
+ try {
+ new URL(value)
+ } catch (e) {
+ return e.message
}
- },
- 'that url doesn’t look quite right'
- )
+ }
+ }, 'that url doesn’t look quite right')
.max(2000, 'please use a shorter url')
diff --git a/packages/server/graphql/mutations/addOrg.ts b/packages/server/graphql/mutations/addOrg.ts
index 45de64e16fa..3f65c8f0cf8 100644
--- a/packages/server/graphql/mutations/addOrg.ts
+++ b/packages/server/graphql/mutations/addOrg.ts
@@ -27,7 +27,7 @@ export default {
description: 'The new team object with exactly 1 team member'
},
orgName: {
- type: GraphQLString,
+ type: new GraphQLNonNull(GraphQLString),
description: 'The name of the new team'
}
},
diff --git a/packages/server/graphql/mutations/addTeam.ts b/packages/server/graphql/mutations/addTeam.ts
index e134d59dc96..d9dcfe6b95e 100644
--- a/packages/server/graphql/mutations/addTeam.ts
+++ b/packages/server/graphql/mutations/addTeam.ts
@@ -46,7 +46,10 @@ export default {
.getAll(orgId, {index: 'orgId'})
.run()
- const orgTeamNames = orgTeams.map((team) => !team.isArchived && team.name)
+ const orgTeamNames = [] as string[]
+ orgTeams.forEach((team) => {
+ if (!team.isArchived) orgTeamNames.push(team.name)
+ })
const {
data: {newTeam},
errors
diff --git a/packages/server/graphql/mutations/helpers/addOrgValidation.js b/packages/server/graphql/mutations/helpers/addOrgValidation.ts
similarity index 72%
rename from packages/server/graphql/mutations/helpers/addOrgValidation.js
rename to packages/server/graphql/mutations/helpers/addOrgValidation.ts
index 200e2636956..2d8475b2313 100644
--- a/packages/server/graphql/mutations/helpers/addOrgValidation.js
+++ b/packages/server/graphql/mutations/helpers/addOrgValidation.ts
@@ -1,5 +1,5 @@
import legitify from 'parabol-client/validation/legitify'
-import { orgName, teamName } from 'parabol-client/validation/templates'
+import {orgName, teamName} from 'parabol-client/validation/templates'
export default function addOrgValidation() {
return legitify({
diff --git a/packages/server/graphql/mutations/helpers/addTeamValidation.js b/packages/server/graphql/mutations/helpers/addTeamValidation.ts
similarity index 54%
rename from packages/server/graphql/mutations/helpers/addTeamValidation.js
rename to packages/server/graphql/mutations/helpers/addTeamValidation.ts
index 17389a71598..8097ed4f74c 100644
--- a/packages/server/graphql/mutations/helpers/addTeamValidation.js
+++ b/packages/server/graphql/mutations/helpers/addTeamValidation.ts
@@ -1,7 +1,7 @@
import legitify from 'parabol-client/validation/legitify'
-import { makeTeamNameSchema, requiredId } from 'parabol-client/validation/templates'
+import {makeTeamNameSchema, requiredId} from 'parabol-client/validation/templates'
-export default function addTeamValidation(teamNames) {
+export default function addTeamValidation(teamNames: string[]) {
return legitify({
newTeam: {
name: makeTeamNameSchema(teamNames),