Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34,606 changes: 24,008 additions & 10,598 deletions ui/package-lock.json

Large diffs are not rendered by default.

33 changes: 19 additions & 14 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@
"description": "Stackhawk code example ui project",
"private": true,
"dependencies": {
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.1.3",
"redux": "^4.0.4"
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.6.2",
"@mui/material": "^5.6.2",
"css-utility-belt": "^0.0.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-redux": "^8.0.1",
"redux": "^4.2.0"
},
"devDependencies": {
"react-scripts": "3.3.0",
"typescript": "~3.8.3",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.6"
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
"@testing-library/user-event": "^14.1.1",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.25",
"@types/react": "^18.0.6",
"@types/react-dom": "^18.0.2",
"@types/react-redux": "^7.1.6",
"react-scripts": "5.0.1",
"typescript": "~4.6.3"
},
"scripts": {
"start": "react-scripts start",
Expand Down
77 changes: 72 additions & 5 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,79 @@
import React from 'react';
import { Button, TextField } from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import React, { useEffect, useState } from 'react';
import './App.css';
import { createHawk, fetchHawks, updateHawk } from './services/hawk.service';
import { Hawk } from './services/hawk';
import HawkTable from './HawkTable';
import HawkForm from './HawkForm';

const App: React.FC = () => {
const [hawks, setHawks] = useState<Hawk[]>([]);
const [filterText, setFilterText] = useState<string>('');
const [hawkToView, setHawkToView] = useState<number | null>(null);

useEffect(() => {
fetchHawks({ filter: filterText }).then(({ hawks: hawkResponse }) => {
setHawks(hawkResponse);
});
}, [filterText]);

const saveHawk = (hawkInfo: Omit<Hawk, 'id'>) => {
if (hawkToView) {
updateHawk(hawkToView, { ...hawkInfo, id: hawkToView }).then(
(hawkResponse) => {
const updatedHawks = hawks.map((hawk) => {
if (hawk.id === hawkResponse.id) {
return hawkResponse;
}
return hawk;
});
setHawks(updatedHawks);
setHawkToView(null);
}
);
return;
}
createHawk(hawkInfo).then((hawkResponse) => {
setHawks([...hawks, hawkResponse]);
setHawkToView(null);
});
};

return (
<div className="App">
<h1>hello world</h1>
</div>
<main className="flex-row">
<div
className="padding-4 flex-column align-items-start"
style={{ flex: '1' }}
>
<Button
variant="contained"
className="margin-bottom-4"
onClick={() => {
setHawkToView(0);
}}
>
<AddIcon className="margin-right-2"></AddIcon>Add Hawk
</Button>

<TextField
variant="outlined"
label="Filter Hawks"
className="margin-right-2"
style={{ width: '40%' }}
onChange={({ target: { value } }) => setFilterText(value)}
></TextField>

<HawkTable
hawks={hawks}
onClick={(id) => setHawkToView(id)}
></HawkTable>
</div>
{hawkToView !== null && (
<HawkForm id={hawkToView} onSave={saveHawk}></HawkForm>
)}
</main>
);
}
};

export default App;
237 changes: 237 additions & 0 deletions ui/src/HawkForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import {
Button,
FormControl,
InputAdornment,
InputLabel,
MenuItem,
Select,
TextField,
} from '@mui/material';
import React, { useEffect, useState } from 'react';
import { Hawk } from './services/hawk';
import { fetchHawk } from './services/hawk.service';

interface Props {
id?: number;
onSave: (hawkInfo: Omit<Hawk, 'id'>) => void;
}

const DEFAULT_HAWK = {
name: '',
size: 'SMALL',
gender: 'MALE',
lengthBegin: 0,
lengthEnd: 0,
wingspanBegin: 0,
wingspanEnd: 0,
weightBegin: 0,
weightEnd: 0,
pictureUrl: '',
habitatDescription: '',
colorDescription: '',
behaviorDescription: '',
} as Omit<Hawk, 'id'>;

const CreateHawk: React.FC<Props> = ({ id, onSave }) => {
const [hawkInfo, setHawkInfo] = useState(DEFAULT_HAWK);

useEffect(() => {
if (!id) {
setHawkInfo(DEFAULT_HAWK);
return;
}
fetchHawk(id).then((hawkData) => {
setHawkInfo(hawkData);
});
}, [id]);

const updateProperty = (propertyName: keyof Hawk, value: string | number) => {
setHawkInfo({ ...hawkInfo, [propertyName]: value });
};

return (
<form className="padding-4 margin-top-0 flex-column">
<h3>Hawk Information</h3>
<TextField
className="margin-bottom-4"
required
label="Name"
variant="outlined"
onChange={({ target: { value } }) => updateProperty('name', value)}
value={hawkInfo.name}
></TextField>

<FormControl required className="margin-bottom-4" variant="outlined">
<InputLabel id="size">Size</InputLabel>
<Select
required
labelId="size"
value={hawkInfo.size}
label="Size"
onChange={({ target: { value } }) => updateProperty('size', value)}
>
<MenuItem value={'SMALL'}>Small</MenuItem>
<MenuItem value={'MEDIUM'}>Medium</MenuItem>
<MenuItem value={'LARGE'}>Large</MenuItem>
</Select>
</FormControl>

<FormControl required className="margin-bottom-4" variant="outlined">
<InputLabel id="gender">Gender</InputLabel>
<Select
required
labelId="gender"
value={hawkInfo.gender}
label="Gender"
onChange={({ target: { value } }) => updateProperty('gender', value)}
>
<MenuItem value={'MALE'}>Male</MenuItem>
<MenuItem value={'FEMALE'}>Female</MenuItem>
</Select>
</FormControl>

<div className="flex-row margin-bottom-4">
<TextField
className="margin-right-2"
required
label="Length From"
variant="outlined"
type="number"
onChange={({ target: { value } }) =>
updateProperty('lengthBegin', value)
}
InputProps={{
endAdornment: <InputAdornment position="end">cm</InputAdornment>,
}}
value={hawkInfo.lengthBegin}
></TextField>
<TextField
required
label="Length To"
variant="outlined"
type="number"
onChange={({ target: { value } }) =>
updateProperty('lengthEnd', value)
}
InputProps={{
endAdornment: <InputAdornment position="end">cm</InputAdornment>,
}}
value={hawkInfo.lengthEnd}
></TextField>
</div>

<div className="flex-row margin-bottom-4">
<TextField
className="margin-right-2"
required
label="Wingspan From"
variant="outlined"
type="number"
onChange={({ target: { value } }) =>
updateProperty('wingspanBegin', value)
}
InputProps={{
endAdornment: <InputAdornment position="end">cm</InputAdornment>,
}}
value={hawkInfo.wingspanBegin}
></TextField>
<TextField
required
label="Wingspan To"
variant="outlined"
type="number"
onChange={({ target: { value } }) =>
updateProperty('wingspanEnd', value)
}
InputProps={{
endAdornment: <InputAdornment position="end">cm</InputAdornment>,
}}
value={hawkInfo.wingspanEnd}
></TextField>
</div>

<div className="flex-row margin-bottom-4">
<TextField
className="margin-right-2"
required
label="Weight From"
variant="outlined"
type="number"
onChange={({ target: { value } }) =>
updateProperty('weightBegin', value)
}
InputProps={{
endAdornment: <InputAdornment position="end">grams</InputAdornment>,
}}
value={hawkInfo.weightBegin}
></TextField>
<TextField
required
label="Weight To"
variant="outlined"
type="number"
onChange={({ target: { value } }) =>
updateProperty('weightEnd', value)
}
InputProps={{
endAdornment: <InputAdornment position="end">grams</InputAdornment>,
}}
value={hawkInfo.weightEnd}
></TextField>
</div>

<TextField
className="margin-bottom-4"
required
label="Image Url"
variant="outlined"
onChange={({ target: { value } }) =>
updateProperty('pictureUrl', value)
}
value={hawkInfo.pictureUrl}
></TextField>

<TextField
className="margin-bottom-4"
required
label="Color Description"
variant="outlined"
multiline
onChange={({ target: { value } }) =>
updateProperty('colorDescription', value)
}
value={hawkInfo.colorDescription}
></TextField>

<TextField
className="margin-bottom-4"
required
label="Behavior Description"
variant="outlined"
multiline
onChange={({ target: { value } }) =>
updateProperty('behaviorDescription', value)
}
value={hawkInfo.behaviorDescription}
></TextField>

<TextField
className="margin-bottom-4"
required
label="Habitat Description"
variant="outlined"
multiline
onChange={({ target: { value } }) =>
updateProperty('habitatDescription', value)
}
value={hawkInfo.habitatDescription}
></TextField>

<Button variant="contained" onClick={() => onSave(hawkInfo)}>
Save
</Button>
</form>
);
};

export default CreateHawk;
Loading