Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
SSHOC
sshoc-marketplace-frontend
Commits
1d8467b4
Commit
1d8467b4
authored
Mar 04, 2021
by
Stefan Probst
Browse files
feat: add create actor dialog
parent
0de01416
Pipeline
#178000
passed with stage
in 5 minutes and 38 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
src/components/item/ActorsFormSection/ActorsFormSection.tsx
View file @
1d8467b4
import
{
Dialog
}
from
'
@reach/dialog
'
import
{
useState
}
from
'
react
'
import
{
useGetActors
,
useGetAllActorRoles
}
from
'
@/api/sshoc
'
import
{
useQueryClient
}
from
'
react-query
'
import
{
useCreateActor
,
useGetActors
,
useGetAllActorRoles
}
from
'
@/api/sshoc
'
import
type
{
ActorCore
}
from
'
@/api/sshoc
'
import
{
Button
}
from
'
@/elements/Button/Button
'
import
{
Icon
}
from
'
@/elements/Icon/Icon
'
import
{
Svg
as
CloseIcon
}
from
'
@/elements/icons/small/cross.svg
'
import
{
useToast
}
from
'
@/elements/Toast/useToast
'
import
{
useDebouncedState
}
from
'
@/lib/hooks/useDebouncedState
'
import
{
useAuth
}
from
'
@/modules/auth/AuthContext
'
import
{
FormComboBox
}
from
'
@/modules/form/components/FormComboBox/FormComboBox
'
import
{
FormFieldAddButton
}
from
'
@/modules/form/components/FormFieldAddButton/FormFieldAddButton
'
import
{
FormFieldRemoveButton
}
from
'
@/modules/form/components/FormFieldRemoveButton/FormFieldRemoveButton
'
...
...
@@ -9,7 +17,10 @@ import { FormRecord } from '@/modules/form/components/FormRecord/FormRecord'
import
{
FormRecords
}
from
'
@/modules/form/components/FormRecords/FormRecords
'
import
{
FormSection
}
from
'
@/modules/form/components/FormSection/FormSection
'
import
{
FormSelect
}
from
'
@/modules/form/components/FormSelect/FormSelect
'
import
{
FormTextField
}
from
'
@/modules/form/components/FormTextField/FormTextField
'
import
{
Form
}
from
'
@/modules/form/Form
'
import
{
FormFieldArray
}
from
'
@/modules/form/FormFieldArray
'
import
{
isEmail
,
isUrl
}
from
'
@/modules/form/validate
'
export
interface
ActorsFormSectionProps
{
initialValues
?:
any
...
...
@@ -19,8 +30,23 @@ export interface ActorsFormSectionProps {
* Form section for contributors.
*/
export
function
ActorsFormSection
(
props
:
ActorsFormSectionProps
):
JSX
.
Element
{
const
[
showCreateNewDialog
,
setShowCreateNewDialog
]
=
useState
(
false
)
function
openCreateNewDialog
()
{
setShowCreateNewDialog
(
true
)
}
function
closeCreateNewDialog
()
{
setShowCreateNewDialog
(
false
)
}
return
(
<
FormSection
title
=
{
'
Actors
'
}
>
<
FormSection
title
=
{
'
Actors
'
}
actions
=
{
<
FormFieldAddButton
onPress
=
{
openCreateNewDialog
}
>
{
'
Create new actor
'
}
</
FormFieldAddButton
>
}
>
<
FormFieldArray
name
=
"contributors"
>
{
({
fields
})
=>
{
return
(
...
...
@@ -56,6 +82,10 @@ export function ActorsFormSection(props: ActorsFormSectionProps): JSX.Element {
)
}
}
</
FormFieldArray
>
<
CreateActorDialog
isOpen
=
{
showCreateNewDialog
}
onDismiss
=
{
closeCreateNewDialog
}
/>
</
FormSection
>
)
}
...
...
@@ -130,3 +160,171 @@ function ActorComboBox(props: ActorComboBoxProps): JSX.Element {
</
FormComboBox
>
)
}
interface
CreateActorDialogProps
{
isOpen
:
boolean
onDismiss
:
()
=>
void
}
/**
* Create new actor dialog.
*/
function
CreateActorDialog
(
props
:
CreateActorDialogProps
)
{
return
(
<
Dialog
isOpen
=
{
props
.
isOpen
}
onDismiss
=
{
props
.
onDismiss
}
className
=
"flex flex-col w-full max-w-screen-lg px-32 py-16 mx-auto bg-white rounded shadow-lg"
style
=
{
{
width
:
'
60vw
'
,
marginTop
:
'
10vh
'
,
marginBottom
:
'
10vh
'
}
}
aria
-
label
=
"Create new actor"
>
<
button
onClick
=
{
props
.
onDismiss
}
className
=
"self-end"
aria
-
label
=
"Close dialog"
>
<
Icon
icon
=
{
CloseIcon
}
className
=
""
/>
</
button
>
<
section
className
=
"flex flex-col space-y-6"
>
<
h2
className
=
"text-2xl font-medium"
>
Create new actor
</
h2
>
{
/* this form is rendered in a portal, so it's valid html, even though it's a <form> "nested" in another <form>. */
}
<
CreateActorForm
onDismiss
=
{
props
.
onDismiss
}
/>
</
section
>
</
Dialog
>
)
}
type
ActorFormValues
=
ActorCore
interface
CreateActorFormProps
{
onDismiss
:
()
=>
void
}
/**
* Create actor.
*/
function
CreateActorForm
(
props
:
CreateActorFormProps
)
{
const
createActor
=
useCreateActor
()
const
auth
=
useAuth
()
const
queryClient
=
useQueryClient
()
const
toast
=
useToast
()
function
onSubmit
(
values
:
ActorFormValues
)
{
if
(
auth
.
session
?.
accessToken
===
undefined
)
{
toast
.
error
(
'
Authentication required.
'
)
return
Promise
.
reject
()
}
return
createActor
.
mutateAsync
(
[
values
,
{
token
:
auth
.
session
?.
accessToken
}],
{
onSuccess
()
{
queryClient
.
invalidateQueries
([
'
getActors
'
])
toast
.
success
(
'
Sucessfully created actor.
'
)
},
onError
()
{
toast
.
error
(
'
Failed to create actor.
'
)
},
onSettled
()
{
props
.
onDismiss
()
},
},
)
}
function
onValidate
(
values
:
Partial
<
ActorFormValues
>
)
{
const
errors
:
Partial
<
Record
<
keyof
typeof
values
,
string
>>
=
{}
if
(
values
.
name
===
undefined
)
{
errors
.
name
=
'
Name is required.
'
}
if
(
values
.
email
!==
undefined
&&
!
isEmail
(
values
.
email
))
{
errors
.
email
=
'
Please provide a valid email address.
'
}
if
(
values
.
website
!==
undefined
&&
!
isUrl
(
values
.
website
))
{
errors
.
website
=
'
Please provide a valid URL.
'
}
return
errors
}
return
(
<
Form
onSubmit
=
{
onSubmit
}
validate
=
{
onValidate
}
>
{
({
handleSubmit
,
pristine
,
invalid
,
submitting
})
=>
{
return
(
<
form
noValidate
className
=
"flex flex-col space-y-6"
onSubmit
=
{
handleSubmit
}
>
<
FormTextField
name
=
"name"
label
=
"Name"
isRequired
variant
=
"form"
style
=
{
{
flex
:
1
}
}
/>
<
FormTextField
name
=
"email"
label
=
"Email"
variant
=
"form"
style
=
{
{
flex
:
1
}
}
/>
<
FormTextField
name
=
"website"
label
=
"Website"
variant
=
"form"
style
=
{
{
flex
:
1
}
}
/>
<
FormFieldArray
name
=
"affiliations"
>
{
({
fields
})
=>
{
return
(
<
FormRecords
>
{
fields
.
map
((
name
,
index
)
=>
{
return
(
<
FormRecord
key
=
{
name
}
actions
=
{
<
FormFieldRemoveButton
onPress
=
{
()
=>
fields
.
remove
(
index
)
}
aria
-
label
=
{
'
Remove affiliation
'
}
/>
}
>
<
ActorComboBox
name
=
{
`
${
name
}
.code`
}
label
=
"Affiliation"
index
=
{
index
}
/>
</
FormRecord
>
)
})
}
<
FormFieldAddButton
onPress
=
{
()
=>
fields
.
push
(
undefined
)
}
>
{
'
Add affiliation
'
}
</
FormFieldAddButton
>
</
FormRecords
>
)
}
}
</
FormFieldArray
>
<
div
className
=
"flex justify-end space-x-12"
>
<
Button
variant
=
"link"
onPress
=
{
props
.
onDismiss
}
>
Cancel
</
Button
>
<
Button
type
=
"submit"
variant
=
"gradient"
isDisabled
=
{
pristine
||
invalid
||
submitting
||
createActor
.
isLoading
}
>
Create
</
Button
>
</
div
>
</
form
>
)
}
}
</
Form
>
)
}
src/modules/form/components/FormFieldAddButton/FormFieldAddButton.tsx
View file @
1d8467b4
...
...
@@ -22,7 +22,7 @@ export function FormFieldAddButton(
const
styles
=
{
button
:
'
transition cursor-default
self-start
inline-flex space-x-1.5 items-center font-body font-normal font-ui-base text-primary-750 hover:text-secondary-600 focus:text-gray-800 focus:outline-none
'
,
'
transition cursor-default inline-flex space-x-1.5 items-center font-body font-normal font-ui-base text-primary-750 hover:text-secondary-600 focus:text-gray-800 focus:outline-none
'
,
icon
:
'
w-2.5 h-2.5
'
,
}
...
...
src/modules/form/components/FormSection/FormSection.tsx
View file @
1d8467b4
import
type
{
ReactNode
}
from
'
react
'
import
type
{
CSSProperties
,
ReactNode
}
from
'
react
'
export
interface
FormSectionProps
{
title
?:
string
/** @default 2 */
headingLevel
?:
1
|
2
|
3
|
4
children
?:
ReactNode
actions
?:
ReactNode
style
?:
CSSProperties
}
export
function
FormSection
(
props
:
FormSectionProps
):
JSX
.
Element
{
...
...
@@ -17,16 +19,21 @@ export function FormSection(props: FormSectionProps): JSX.Element {
}
if
(
title
===
undefined
)
{
return
<
section
className
=
{
styles
.
section
}
>
{
props
.
children
}
</
section
>
return
(
<
section
className
=
{
styles
.
section
}
style
=
{
props
.
style
}
>
{
props
.
children
}
</
section
>
)
}
return
(
<
section
className
=
{
styles
.
section
}
>
<
section
className
=
{
styles
.
section
}
style
=
{
props
.
style
}
>
<
div
className
=
"flex items-baseline space-x-4"
>
<
ElementType
className
=
"font-medium text-gray-800 font-body text-ui-3xl"
>
{
title
}
</
ElementType
>
<
span
className
=
"flex-1 border-b border-gray-200"
/>
<
div
className
=
"leading-none"
>
{
props
.
actions
}
</
div
>
</
div
>
{
props
.
children
}
</
section
>
...
...
src/pages/_app.tsx
View file @
1d8467b4
...
...
@@ -22,7 +22,7 @@ import '@/styles/nprogress.css'
/** should use ReactToastify.minimal.css */
import
'
react-toastify/dist/ReactToastify.css
'
import
'
@/styles/combobox.css
'
import
'
@
reach/dialog
/styles.css
'
import
'
@/styles
/dialog
.css
'
/**
* Report web vitals.
...
...
src/styles/dialog.css
0 → 100644
View file @
1d8467b4
:root
{
--reach-dialog
:
1
;
}
[
data-reach-dialog-overlay
]
{
background
:
hsla
(
0
,
0%
,
0%
,
0.33
);
position
:
fixed
;
top
:
0
;
right
:
0
;
bottom
:
0
;
left
:
0
;
overflow
:
auto
;
}
tailwind.config.js
View file @
1d8467b4
...
...
@@ -9,6 +9,9 @@ module.exports = {
purge
:
[
'
src/**/*.tsx
'
,
'
content/**/*.mdx
'
],
theme
:
{
extend
:
{
backgroundSize
:
{
double
:
'
150% 150%
'
,
},
colors
:
{
gray
:
{
50
:
'
#FAFAFA
'
,
...
...
@@ -130,7 +133,9 @@ module.exports = {
},
},
variants
:
{
// boxShadow: ['responsive', 'hover', 'focus', 'focus-visible']
extend
:
{
backgroundPosition
:
[
'
hover
'
,
'
focus
'
],
},
},
plugins
:
[
require
(
'
@tailwindcss/typography
'
)({
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment