Skip to content

Commit

Permalink
Improvements and form validation (#15)
Browse files Browse the repository at this point in the history
* Improvements and form validation

* Fix notification contents

* Fix duplicate api calls

* Improve notification handling

* Only request suggestions on analyze

* Minimize api calls

* Implement edit comment
  • Loading branch information
marlonbaeten authored Nov 22, 2022
1 parent 9244add commit 6efbbdf
Show file tree
Hide file tree
Showing 25 changed files with 275 additions and 71 deletions.
10 changes: 8 additions & 2 deletions src/components/CasHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export default function CasHeader() {
const store = useStore();
const t = useTranslations();
const navigate = useNavigation();
const noParents = !store.ca || store.parents[store.ca].length === 0;
const parentSet = store.ca && store.parents[store.ca].length > 0;
const repoSet = store.repoStatus && store.ca && store.repoStatus[store.ca]?.last_exchange;

return (
<>
Expand All @@ -28,11 +29,16 @@ export default function CasHeader() {
/>
</div>
</div>
{noParents && (
{!parentSet && (
<div className="notification error">
{t.caDetails.onboardingWarning}
</div>
)}
{parentSet && !repoSet && (
<div className="notification error">
{t.caDetails.initializeRepository}
</div>
)}
<div>
<ul className="tabs">
<li>
Expand Down
6 changes: 5 additions & 1 deletion src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import React, { ReactNode, useEffect } from 'react';
import { useRoute } from 'react-router5';
import { locales } from '../core/config';
import useNavigation from '../hooks/useNavigation';
Expand All @@ -23,6 +23,10 @@ export default function Layout({ children }: LayoutProps) {
const year = new Date().getFullYear();
const t = useTranslations();

useEffect(() => {
document.title = `Krill - ${store.ca}`;
}, [store.ca]);

return (
<>
<Loader initial={false} />
Expand Down
28 changes: 21 additions & 7 deletions src/components/NotificationContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import React from 'react';
import useNavigation from '../hooks/useNavigation';
import React, { useEffect, useState } from 'react';
import Store from '../core/store';
import useStore from '../hooks/useStore';
import NotificationPopup from './NotificationPopup';

export default function NotificationContainer() {
const { notification } = useStore();
const navigate = useNavigation();
const store = useStore() as Store;
const [hide, setHide] = useState<boolean>(false);

const close = () => {
setHide(true);
store.setNotification(null);
};

// hide notification after 5s
useEffect(() => {
if (store.notification) {
setHide(false);
const timer = setTimeout(close, 5000);
return () => clearTimeout(timer);
}
}, [store.notification]);

if (!notification) {
if (!store.notification || hide) {
return null;
}

return (
<NotificationPopup
notification={notification}
onClose={() => navigate({ notification: 'clear' })}
notification={store.notification}
onClose={close}
/>
);
}
50 changes: 33 additions & 17 deletions src/components/forms/Add.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { FormEvent, useState } from 'react';
import { Roa } from '../../core/types';
import { prefixMaxLength } from '../../core/utils';
import useNavigation from '../../hooks/useNavigation';
Expand All @@ -17,16 +17,28 @@ export default function Add({ onClose, roa }: AddProps) {
const [comment, setComment] = useState(roa?.comment || '');
const maxLengthFallback = prefixMaxLength(roa?.prefix);
const [maxLength, setMaxLength] = useState(roa?.max_length?.toString() || maxLengthFallback);

const onSubmit = (e: FormEvent) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
if (form.checkValidity()) {
navigate({ asn, prefix, comment, max_length: maxLength});
} else {
form.reportValidity();
}
};

return (
<>
<h3>{t.caDetails.addRoa}</h3>
<form>
<form onSubmit={onSubmit}>
<div>
<label htmlFor="asn required">
{t.announcements.asn}
</label>
<input
type="number"
min="0"
name="asn"
value={asn}
onChange={(e) => setAsn(e.target.value)}
Expand All @@ -39,6 +51,7 @@ export default function Add({ onClose, roa }: AddProps) {
</label>
<input
name="prefix"
pattern="^((([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.(?!\/)|\/)){4}([0-9]|1[0-9]|2[0-4]))|(([a-fA-F0-9:]+)\/([1-9]|[1-9][0-9]|1[01][0-9]|12[0-8]))$"
value={prefix}
onChange={(e) => setPrefix(e.target.value)}
required
Expand All @@ -49,6 +62,9 @@ export default function Add({ onClose, roa }: AddProps) {
{t.caDetails.maxLength}
</label>
<input
type="number"
min="0"
max="128"
name="maxLength"
value={maxLength}
onChange={(e) => setMaxLength(e.target.value)}
Expand All @@ -63,24 +79,24 @@ export default function Add({ onClose, roa }: AddProps) {
name="comment"
value={comment}
onChange={(e) => setComment(e.target.value)}
required
/>
</div>
<div className="actions">
<button
type="button"
className="button outline"
onClick={onClose}
>
{t.common.cancel}
</button>
<button
type="submit"
className="button"
>
{t.common.confirm}
</button>
</div>
</form>
<div className="actions">
<button
className="button outline"
onClick={onClose}
>
{t.common.cancel}
</button>
<button
className="button"
onClick={() => navigate({ asn, prefix, comment, max_length: maxLength})}
>
{t.common.confirm}
</button>
</div>
</>
);
}
7 changes: 7 additions & 0 deletions src/components/forms/CaModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Roa } from '../../core/types';
import useStore from '../../hooks/useStore';
import Add from './Add';
import Delete from './Delete';
import Edit from './Edit';
import Modal from './Modal';

export default function CaModal() {
Expand All @@ -31,6 +32,12 @@ export default function CaModal() {
roa={roa}
/>
)}
{route.name.startsWith('cas.edit') && (
<Edit
onClose={onClose}
roa={roa}
/>
)}
{route.name === 'cas.delete' && (
<Delete
onClose={onClose}
Expand Down
56 changes: 56 additions & 0 deletions src/components/forms/Edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState } from 'react';
import { Roa } from '../../core/types';
import useNavigation from '../../hooks/useNavigation';
import useTranslations from '../../hooks/useTranslations';

interface EditProps {
onClose: () => void,
roa?: Roa,
}

export default function Edit({ onClose, roa }: EditProps) {
const t = useTranslations();
const navigate = useNavigation();
const [comment, setComment] = useState(roa?.comment || '');

return (
<>
<h3>{t.caDetails.addRoa}</h3>
<form
onSubmit={(e) => {
e.preventDefault();
if (comment !== roa?.comment) {
navigate({ comment });
} else {
onClose();
}
}}
>
<div>
<label htmlFor="comment required">
{t.caDetails.comment}
</label>
<input
name="comment"
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
</div>
</form>
<div className="actions">
<button
className="button outline"
onClick={onClose}
>
{t.common.cancel}
</button>
<button
type="submit"
className="button"
>
{t.common.confirm}
</button>
</div>
</>
);
}
2 changes: 1 addition & 1 deletion src/components/forms/ParentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function ParentModal() {
/>
)}
<h3>{t.caDetails.parentsTab.addParent}</h3>
<form onSubmit={ onSubmit } method="POST">
<form onSubmit={onSubmit} method="POST">
<div>
<label>{t.caDetails.parentsTab.request}</label>
<textarea name="request" readOnly value={request} id="request" onChange={(e) => setRequest(e.target.value)} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/forms/RepoModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function RepoModal() {
/>
)}
<h3>{t.caDetails.repoTab.addRepo}</h3>
<form onSubmit={ onSubmit } method="POST">
<form onSubmit={onSubmit} method="POST">
<div>
<label>{t.caDetails.repoTab.request}</label>
<textarea name="request" readOnly value={request} id="request" onChange={(e) => setRequest(e.target.value)} />
Expand Down
14 changes: 10 additions & 4 deletions src/components/tables/RoaTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Roa, RoaStateHelp} from '../../core/types';
import useTranslations from '../../hooks/useTranslations';
import RoaTableRowSubTable from './RoaTableRowSubTable';
import trash from '../../img/trash.svg?url';
import edit from '../../img/edit.svg?url';
import plus from '../../img/plus.svg?url';
import useNavigation from '../../hooks/useNavigation';

Expand Down Expand Up @@ -62,15 +63,20 @@ export default function RoaTableRow({ roa, allowAdd, allowDelete, hasAnnouncemen
</button>
)}
{allowDelete && (
<button className="button icon" onClick={() => navigate(params, 'cas.delete')}>
<img src={trash} />
</button>
<>
<button className="button icon light" onClick={() => navigate(params, 'cas.edit')}>
<img src={edit} />
</button>
<button className="button icon" onClick={() => navigate(params, 'cas.delete')}>
<img src={trash} />
</button>
</>
)}
</td>
</tr>
{hasAnnouncements && expanded && (
<tr className="announcements">
<td colSpan={5}>
<td colSpan={6}>
<RoaTableRowSubTable
authorizes={roa.authorizes || []}
disallows={roa.disallows || []}
Expand Down
44 changes: 26 additions & 18 deletions src/core/handlers/handleCaData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,39 @@ export default async function handleCaData(toState: State, store: Store) {
}
}

// add routes
if (toState.name === 'cas.add' && toState.params.asn) {
if (await store.addRoute(toState.params as RouteParams)) {
// edit routes
if (toState.name === 'cas.edit' && toState.params.id && toState.params.comment !== undefined) {
if (await store.editRoute(toState.params.id, toState.params.comment)) {
return Promise.reject({redirect: {name: 'cas', params: {ca: store.ca}}});
} else {
return Promise.reject({redirect: {name: toState.name, params: {
ca: store.ca,
id: toState.params.id,
}}});
}
}

if (toState.name === 'cas.add_new' && toState.params.asn) {
// add routes
if ((toState.name === 'cas.add' || toState.name === 'cas.add_new') && toState.params.asn) {
if (await store.addRoute(toState.params as RouteParams)) {
return Promise.reject({redirect: {name: 'cas', params: {ca: store.ca}}});
} else {
return Promise.reject({redirect: {name: toState.name, params: {
ca: store.ca,
id: toState.params.id,
}}});
}
}

if (toState.name === 'cas.analyse') {
await store.loadSuggestions();
}

if (toState.name === 'cas.change' && toState.params.ids) {
const ids: string[] = JSON.parse(toState.params.ids);
console.log(`ids ${ids}`);
const suggestions = store.getSuggestions().filter(suggestion => ids.includes(suggestion.id || ''));
console.log(`suggestions ${suggestions}`);
const add: Suggestion[] = [];
const remove: Suggestion[] = [];
for (const suggestion of suggestions) {
if (suggestion.action === 'add') {
add.push(suggestion);
} else if (suggestion.action === 'remove') {
remove.push(suggestion);
}
}
const add: Suggestion[] = suggestions.filter((s) => s.action === 'add');
const remove: Suggestion[] = suggestions.filter((s) => s.action === 'remove');
await store.changeRoutes(add, remove);
return Promise.reject({redirect: {name: 'cas', params: {ca: store.ca}}});
}
Expand Down Expand Up @@ -71,8 +77,10 @@ export default async function handleCaData(toState: State, store: Store) {
// only fetch details on cas route
if (toState.name.startsWith('cas')) {
// load ca details and roa's
await store.loadCa();
await store.loadRepoStatus();
await store.loadParents();
await Promise.all([
store.loadCa(),
store.loadParents(),
store.loadRepoStatus(),
]);
}
}
7 changes: 0 additions & 7 deletions src/core/handlers/handleLoginRouting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ export default async function handleLoginRouting(toState: State, store: Store) {
delete toState.params.password;
}

// clear notification
if (toState.params.notification === 'clear') {
store.setNotification(null);
delete toState.params.notification;
return Promise.reject({ redirect: toState});
}

// logout (set token to null) and redirect to login
if (toState.name === 'logout') {
store.setToken(null);
Expand Down
Loading

0 comments on commit 6efbbdf

Please sign in to comment.