Skip to content

Commit 9e85a0a

Browse files
committed
[challenge] Add TaaS management functionality - diazz submission
1 parent 264d7c9 commit 9e85a0a

File tree

30 files changed

+1813
-8
lines changed

30 files changed

+1813
-8
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@nateradebaugh/react-datetime": "^4.4.11",
1111
"@popperjs/core": "^2.5.4",
1212
"@svgr/webpack": "2.4.1",
13+
"@toast-ui/editor": "^2.5.1",
1314
"axios": "^0.19.0",
1415
"babel-core": "7.0.0-bridge.0",
1516
"babel-eslint": "9.0.0",
@@ -43,6 +44,7 @@
4344
"file-loader": "2.0.0",
4445
"filestack-js": "^3.20.0",
4546
"filestack-react": "^4.0.1",
47+
"formik": "^2.4.6",
4648
"fs-extra": "7.0.0",
4749
"html-webpack-plugin": "4.0.0-alpha.2",
4850
"identity-obj-proxy": "3.0.0",
@@ -117,7 +119,8 @@
117119
"webpack": "^4.43.0",
118120
"webpack-dev-server": "^3.11.0",
119121
"webpack-manifest-plugin": "2.0.4",
120-
"xss": "^1.0.6"
122+
"xss": "^1.0.6",
123+
"yup": "^1.6.1"
121124
},
122125
"scripts": {
123126
"start": "node server.js",

src/actions/projects.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ export function createProject (project) {
8484
}
8585
}
8686

87+
/**
88+
* Only loads project details
89+
* @param {String} projectId Id of the project
90+
*/
91+
export function loadOnlyProjectInfo (projectId) {
92+
return (dispatch) => {
93+
return dispatch({
94+
type: LOAD_PROJECT_DETAILS,
95+
payload: fetchProjectById(projectId)
96+
})
97+
}
98+
}
99+
87100
export function reloadProjectMembers (projectId) {
88101
return (dispatch) => {
89102
return dispatch({

src/assets/images/plus-gray.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/images/x-gray.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* FieldDescription
3+
*/
4+
5+
import React from 'react'
6+
import PropTypes from 'prop-types'
7+
8+
import TuiEditor from '../TuiEditor'
9+
import styles from './styles.scss'
10+
11+
const FieldDescription = props => (
12+
<TuiEditor
13+
{...props}
14+
toolbarItems={[
15+
'heading',
16+
'bold',
17+
'italic',
18+
'strike',
19+
'code',
20+
'divider',
21+
'quote',
22+
'codeblock',
23+
'hr',
24+
'divider',
25+
'ul',
26+
'ol',
27+
'divider',
28+
'image',
29+
'link'
30+
]}
31+
plugins={[]}
32+
className={styles['description-editor']}
33+
previewStyle='vertical'
34+
height='400px'
35+
hideModeSwitch
36+
initialEditType='wysiwyg'
37+
initialValue={props.value}
38+
/>
39+
)
40+
41+
FieldDescription.propTypes = {
42+
onChange: PropTypes.func.isRequired,
43+
className: PropTypes.string,
44+
placeholder: PropTypes.string,
45+
value: PropTypes.string
46+
}
47+
48+
export default FieldDescription
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@import "../../styles/includes";
2+
3+
.description-editor {
4+
width: 100%;
5+
6+
:global {
7+
.tui-editor-contents {
8+
code {
9+
background-color: transparent;
10+
color: inherit;
11+
}
12+
13+
pre {
14+
background-color: transparent;
15+
color: inherit;
16+
padding: 0;
17+
}
18+
}
19+
}
20+
}

src/components/FieldInput/index.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import styles from './styles.module.scss'
4+
5+
const FieldInput = ({
6+
onChangeValue,
7+
placeholder,
8+
value,
9+
type
10+
}) => {
11+
return (
12+
<input
13+
className={styles.inputField}
14+
type={type}
15+
placeholder={placeholder}
16+
onChange={x => onChangeValue(x.target.value)}
17+
value={value}
18+
onKeyDown={
19+
type === 'number'
20+
? evt => {
21+
if (['e', 'E', '+'].includes(evt.key)) {
22+
evt.preventDefault()
23+
}
24+
}
25+
: undefined
26+
}
27+
/>
28+
)
29+
}
30+
31+
FieldInput.defaultProps = {
32+
type: 'text',
33+
onChangeValue: () => {}
34+
}
35+
36+
FieldInput.propTypes = {
37+
onChangeValue: PropTypes.func,
38+
placeholder: PropTypes.string,
39+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
40+
type: PropTypes.string
41+
}
42+
43+
export default FieldInput
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@import '../../styles/includes';
2+
3+
@-moz-document url-prefix() {
4+
.inputField {
5+
&::-moz-placeholder {
6+
/* Mozilla Firefox 19+ */
7+
line-height: 38px;
8+
}
9+
&::-webkit-input-placeholder {
10+
/* Webkit */
11+
line-height: 38px;
12+
}
13+
&:-ms-input-placeholder {
14+
/* IE */
15+
line-height: 38px;
16+
}
17+
}
18+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import styles from './styles.module.scss'
4+
import cn from 'classnames'
5+
6+
const FieldLabelDynamic = ({
7+
title,
8+
isRequired,
9+
children,
10+
errorMsg,
11+
className,
12+
direction,
13+
info
14+
}) => {
15+
return (
16+
<div
17+
className={cn(
18+
styles.row,
19+
{
20+
[styles.vertical]: direction === 'vertical',
21+
[styles.horizontal]: direction === 'horizontal'
22+
},
23+
className
24+
)}
25+
>
26+
<div className={cn(styles.field, styles.col1)}>
27+
<label>
28+
{title} {isRequired && <span>*</span>}
29+
</label>
30+
</div>
31+
<div className={styles.blockContent}>
32+
<div className={styles.blockValue}>
33+
{info && <div className={styles.blockInfo}>{info}</div>}
34+
<div className={cn(styles.field, styles.col2)}>{children}</div>
35+
</div>
36+
{errorMsg && <div className={cn(styles.error)}>{errorMsg}</div>}
37+
</div>
38+
</div>
39+
)
40+
}
41+
42+
FieldLabelDynamic.defaultProps = {
43+
direction: 'horizontal'
44+
}
45+
46+
FieldLabelDynamic.propTypes = {
47+
direction: PropTypes.oneOf(['horizontal', 'vertical']),
48+
title: PropTypes.string,
49+
errorMsg: PropTypes.string,
50+
className: PropTypes.string,
51+
info: PropTypes.string,
52+
isRequired: PropTypes.bool,
53+
children: PropTypes.node
54+
}
55+
56+
export default FieldLabelDynamic
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
@import '../../styles/includes';
2+
3+
.row {
4+
box-sizing: border-box;
5+
display: flex;
6+
align-content: space-between;
7+
justify-content: flex-start;
8+
gap: 5px;
9+
10+
&.vertical {
11+
flex-direction: column;
12+
}
13+
14+
&.horizontal {
15+
gap: 10px;
16+
17+
.col1 {
18+
margin-top: 10px;
19+
}
20+
21+
.blockContent {
22+
flex: 1;
23+
}
24+
}
25+
26+
.field {
27+
label {
28+
@include roboto-bold();
29+
30+
font-size: 16px;
31+
line-height: 19px;
32+
font-weight: 500;
33+
color: $tc-gray-80;
34+
margin-bottom: 0;
35+
}
36+
37+
&.col1 {
38+
white-space: nowrap;
39+
display: flex;
40+
flex-direction: column;
41+
42+
span {
43+
color: $tc-red;
44+
}
45+
}
46+
47+
&.col2 {
48+
width: 100%;
49+
}
50+
}
51+
}
52+
53+
.blockContent {
54+
display: flex;
55+
flex-direction: column;
56+
gap: 5px;
57+
}
58+
59+
.blockValue {
60+
display: flex;
61+
flex-direction: column;
62+
gap: 5px;
63+
}
64+
65+
.blockInfo {
66+
font-size: 13px;
67+
color: $tc-gray-70;
68+
font-weight: 400;
69+
font-style: italic;
70+
}
71+
72+
.error {
73+
color: $tc-red;
74+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, { useMemo } from 'react'
2+
import PropTypes from 'prop-types'
3+
import Select from '../Select'
4+
import { searchSkills } from '../../services/skills'
5+
import { AUTOCOMPLETE_DEBOUNCE_TIME_MS } from '../../config/constants'
6+
import _ from 'lodash'
7+
8+
const fetchSkills = _.debounce((inputValue, callback) => {
9+
searchSkills(inputValue)
10+
.then(skills => {
11+
const suggestedOptions = skills.map(skillItem => ({
12+
label: skillItem.name,
13+
value: skillItem.id
14+
}))
15+
return callback(suggestedOptions)
16+
})
17+
.catch(() => {
18+
return callback(null)
19+
})
20+
}, AUTOCOMPLETE_DEBOUNCE_TIME_MS)
21+
22+
const FieldSkillsBasic = ({ value, onChangeValue, id, placeholder }) => {
23+
const selectedSkills = useMemo(
24+
() =>
25+
value.map(skill => ({
26+
label: skill.name,
27+
value: skill.skillId
28+
})),
29+
[value]
30+
)
31+
32+
return (
33+
<Select
34+
id={id}
35+
isMulti
36+
simpleValue
37+
isAsync
38+
value={selectedSkills}
39+
onChange={values => {
40+
onChangeValue(
41+
(values || []).map(value => ({
42+
name: value.label,
43+
skillId: value.value
44+
}))
45+
)
46+
}}
47+
cacheOptions
48+
loadOptions={fetchSkills}
49+
placeholder={placeholder}
50+
/>
51+
)
52+
}
53+
54+
FieldSkillsBasic.defaultProps = {
55+
onChangeValue: () => {},
56+
id: 'skill-select',
57+
value: [],
58+
placeholder: ''
59+
}
60+
61+
FieldSkillsBasic.propTypes = {
62+
value: PropTypes.arrayOf(PropTypes.shape()),
63+
id: PropTypes.string,
64+
onChangeValue: PropTypes.func,
65+
placeholder: PropTypes.string
66+
}
67+
68+
export default FieldSkillsBasic

0 commit comments

Comments
 (0)