Skip to content

Commit e801b12

Browse files
feat: empty state & name validation
1 parent 281f506 commit e801b12

File tree

3 files changed

+66
-33
lines changed

3 files changed

+66
-33
lines changed

src/components/ApplicationGroup/Constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,6 @@ export const GetBranchChangeStatus = (statusText: string): BulkResponseStatus =>
183183
default:
184184
return
185185
}
186-
}
186+
}
187+
188+
export const FILTER_NAME_REGEX = /^[a-z][a-z0-9-]{1,}[a-z0-9]$/

src/components/ApplicationGroup/CreateAppGroup.tsx

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CHECKBOX_VALUE,
55
ConditionalWrap,
66
Drawer,
7+
GenericEmptyState,
78
Progressing,
89
showError,
910
stopPropagation,
@@ -12,9 +13,10 @@ import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
1213
import { ReactComponent as Error } from '../../assets/icons/ic-warning.svg'
1314
import { ReactComponent as CheckIcon } from '../../assets/icons/ic-check.svg'
1415
import { ReactComponent as Abort } from '../../assets/icons/ic-abort.svg'
16+
import Info from '../../assets/icons/ic-info-outline-grey.svg'
1517
import { CreateGroupType, CreateTypeOfAppListType, FilterParentType } from './AppGroup.types'
1618
import SearchBar from './SearchBar'
17-
import { CreateGroupTabs, CREATE_GROUP_TABS } from './Constants'
19+
import { CreateGroupTabs, CREATE_GROUP_TABS, FILTER_NAME_REGEX } from './Constants'
1820
import { toast } from 'react-toastify'
1921
import { createEnvGroup } from './AppGroup.service'
2022
import { useParams } from 'react-router-dom'
@@ -57,6 +59,18 @@ export default function CreateAppGroup({
5759
}
5860
}
5961

62+
const validateName = (name: string) => {
63+
if (!name) {
64+
return
65+
}
66+
if (!FILTER_NAME_REGEX.test(name) || name.length > 30) {
67+
// regex doesnt check of max length = 30
68+
setShowErrorMsg(true)
69+
} else {
70+
setShowErrorMsg(false)
71+
}
72+
}
73+
6074
useEffect(() => {
6175
document.addEventListener('click', outsideClickHandler)
6276
return (): void => {
@@ -97,8 +111,8 @@ export default function CreateAppGroup({
97111
}
98112

99113
const onInputChange = (event): void => {
100-
setShowErrorMsg(true)
101114
if (event.target.name === 'name') {
115+
validateName(event.target.value)
102116
setAppGroupName(event.target.value)
103117
} else {
104118
setAppGroupDescription(event.target.value)
@@ -157,7 +171,15 @@ export default function CreateAppGroup({
157171
setAuthorizedAppList(_authorizedAppList)
158172
}
159173

174+
const renderEmptyState = (title?: string): JSX.Element => {
175+
return <GenericEmptyState title={title} image={Info} imageClassName="h-20" classname="h-40vh" />
176+
}
177+
160178
const renderSelectedApps = (): JSX.Element => {
179+
const filteredAuthList = authorizedAppList.filter(
180+
(app) =>
181+
selectedAppsMap[app.id] && (!selectedAppSearchText || app.appName.indexOf(selectedAppSearchText) >= 0),
182+
)
161183
return (
162184
<div>
163185
<SearchBar
@@ -168,26 +190,22 @@ export default function CreateAppGroup({
168190
setSearchApplied={setSelectedAppSearchApplied}
169191
/>
170192
<div>
171-
{authorizedAppList
172-
.filter(
173-
(app) =>
174-
selectedAppsMap[app.id] &&
175-
(!selectedAppSearchText || app.appName.indexOf(selectedAppSearchText) >= 0),
176-
)
177-
.map((app) => {
178-
return (
179-
<div
180-
key={`selected-app-${app.id}`}
181-
className="flex left dc__hover-n50 p-8 fs-13 fw-4 cn-9 selected-app-row cursor"
182-
data-app-id={app.id}
183-
onClick={removeAppSelection}
184-
>
185-
<CheckIcon className="icon-dim-16 cursor check-icon scn-6 mr-8" />
186-
<Close className="icon-dim-16 cursor delete-icon mr-8" />
187-
<span>{app.appName}</span>
188-
</div>
189-
)
190-
})}
193+
{filteredAuthList.length <= 0
194+
? renderEmptyState('No matching results')
195+
: filteredAuthList.map((app) => {
196+
return (
197+
<div
198+
key={`selected-app-${app.id}`}
199+
className="flex left dc__hover-n50 p-8 fs-13 fw-4 cn-9 selected-app-row cursor"
200+
data-app-id={app.id}
201+
onClick={removeAppSelection}
202+
>
203+
<CheckIcon className="icon-dim-16 cursor check-icon scn-6 mr-8" />
204+
<Close className="icon-dim-16 cursor delete-icon mr-8" />
205+
<span>{app.appName}</span>
206+
</div>
207+
)
208+
})}
191209
{unauthorizedAppList.length > 0 && (
192210
<div className="dc__bold ml-4">
193211
{`You don't have admin/manager pemission for the following ${filterParentTypeMsg}.`}
@@ -282,12 +300,17 @@ export default function CreateAppGroup({
282300
)
283301
}
284302

303+
// called when showErrorMsg is true
285304
const nameErrorMessage = (): string => {
286305
if (!appGroupName) {
287306
return 'Group name is required field'
288-
} else {
307+
}
308+
if (appGroupName.length > 30) {
289309
return 'Max 30 char is allowed in name'
290310
}
311+
if (!FILTER_NAME_REGEX.test(appGroupName)) {
312+
return 'Min 3 chars; Start with alphabet; End with alphanumeric; Use only lowercase; Allowed:(-); Do not use spaces'
313+
}
291314
}
292315

293316
const renderBodySection = (): JSX.Element => {
@@ -310,7 +333,7 @@ export default function CreateAppGroup({
310333
disabled={selectedAppGroup && !!selectedAppGroup.value}
311334
/>
312335

313-
{showErrorMsg && (!appGroupName || appGroupName.length > 30) && (
336+
{showErrorMsg && (
314337
<span className="form__error">
315338
<Error className="form__icon form__icon--error" />
316339
{nameErrorMessage()}
@@ -360,6 +383,10 @@ export default function CreateAppGroup({
360383

361384
const handleSave = async (e): Promise<void> => {
362385
e.preventDefault()
386+
if (showErrorMsg) {
387+
toast.error('Please fix the errors')
388+
return
389+
}
363390
if (!appGroupName || appGroupDescription?.length > 50) {
364391
setShowErrorMsg(true)
365392
return

src/css/base.scss

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,7 +1839,7 @@ button.anchor {
18391839

18401840
.dc__link {
18411841
color: var(--B500);
1842-
&.dc__link_over{
1842+
&.dc__link_over {
18431843
color: var(--N600);
18441844
}
18451845
&:hover {
@@ -2289,7 +2289,7 @@ textarea,
22892289
background-color: var(--N50);
22902290
}
22912291

2292-
.c-n50{
2292+
.c-n50 {
22932293
color: var(--N50);
22942294
}
22952295

@@ -2672,7 +2672,7 @@ textarea,
26722672
width: 300px !important;
26732673
}
26742674

2675-
.w-320{
2675+
.w-320 {
26762676
width: 320px;
26772677
}
26782678

@@ -2945,7 +2945,7 @@ textarea,
29452945
height: 112px !important;
29462946
}
29472947

2948-
.h-128{
2948+
.h-128 {
29492949
height: 128px !important;
29502950
}
29512951

@@ -3031,7 +3031,7 @@ textarea,
30313031
}
30323032

30333033
.mxh-390-imp {
3034-
max-height: 390px!important;
3034+
max-height: 390px !important;
30353035
}
30363036

30373037
// Height in percentage
@@ -3047,6 +3047,10 @@ textarea,
30473047
height: 100vh;
30483048
}
30493049

3050+
.h-40vh {
3051+
height: 40vh;
3052+
}
3053+
30503054
.h-76-imp {
30513055
height: 76px !important;
30523056
}
@@ -3285,7 +3289,7 @@ textarea,
32853289
}
32863290
}
32873291

3288-
.dc__column-gap-8{
3292+
.dc__column-gap-8 {
32893293
column-gap: 8px;
32903294
}
32913295

@@ -3704,8 +3708,8 @@ textarea::placeholder {
37043708
}
37053709

37063710
.dc__disabled {
3707-
cursor: not-allowed !important;
3708-
opacity: 0.5;
3711+
cursor: not-allowed !important;
3712+
opacity: 0.5;
37093713
}
37103714

37113715
.dc__grabbable {

0 commit comments

Comments
 (0)