Skip to content

Commit

Permalink
adds create cocktail form
Browse files Browse the repository at this point in the history
  • Loading branch information
martinmckenna committed Apr 29, 2019
1 parent 51ff8f2 commit c1127ea
Show file tree
Hide file tree
Showing 14 changed files with 414 additions and 106 deletions.
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"axios": "^0.18.0",
"classnames": "^2.2.6",
"querystring": "^0.2.0",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"ramda": "^0.26.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^6.0.1",
"react-scripts": "2.1.3",
"react-select": "^2.3.0",
Expand All @@ -26,9 +27,10 @@
"@types/classnames": "^2.2.7",
"@types/jest": "23.3.13",
"@types/node": "10.12.18",
"@types/ramda": "^0.26.8",
"@types/reach__router": "^1.2.3",
"@types/react": "16.7.20",
"@types/react-dom": "16.0.11",
"@types/react": "^16.8.14",
"@types/react-dom": "^16.8.4",
"@types/react-redux": "^7.0.1",
"@types/react-select": "^2.0.11",
"@types/recompose": "^0.30.3",
Expand Down
10 changes: 6 additions & 4 deletions src/components/Searchbar/Searchbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface Props {
dropDownOptions?: ResolvedData[];
handleSelect: (value: any, action: any) => void;
handleSubmit: () => void;
filterIce?: boolean;
}

type CombinedProps = Props &
Expand Down Expand Up @@ -86,14 +87,15 @@ const Searchbar: React.SFC<CombinedProps> = props => {
* we're adding it by default
*/
const filteredOptions = dropDownOptions
? dropDownOptions.filter(
eachSelectObj => eachSelectObj.label.toLowerCase() !== 'ice'
)
? !!props.filterIce
? dropDownOptions.filter(
eachSelectObj => eachSelectObj.label.toLowerCase() !== 'ice'
)
: dropDownOptions
: [];

return (
<Select
isMulti
styles={customStyles}
onKeyDown={handleKeyDown}
inputValue={props.query}
Expand Down
9 changes: 5 additions & 4 deletions src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,25 @@ export interface Option<V = string | number, L = string | number> {

interface Props {
options: Option[];
defaultText?: string;
}

type CombinedProps = Props & TextFieldProps & WithStyles<ClassNames>;

const Select: React.SFC<CombinedProps> = props => {
const { options, SelectProps, ...rest } = props;
const { options, SelectProps, defaultText, ...rest } = props;
return (
<TextField
select
defaultValue="Select One"
defaultValue={defaultText || 'Select One'}
SelectProps={{
...SelectProps,
native: true
}}
{...rest}
>
<option disabled key={0} value="Select One">
Select One
<option disabled key={0} value={defaultText || 'Select One'}>
{defaultText || 'Select One'}
</option>
{options.map(option => (
<option key={option.value} value={option.value}>
Expand Down
19 changes: 13 additions & 6 deletions src/features/AdminLanding/AdminLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ import Button from '@material-ui/core/Button';
import CreateCocktailForm from './CreateForms/CreateCocktail';
import CreateIngredientForm from './CreateForms/CreateIngredient';

type ClassNames = 'root';
type ClassNames = 'root' | 'tabs';

const styles: StyleRulesCallback<ClassNames> = theme => ({
root: {}
root: {
margin: `${theme.spacing.unit * 2}px 0px 0px ${theme.spacing.unit * 2}px`
},
tabs: {
marginBottom: theme.spacing.unit * 2
}
});

type CombinedProps = WithStyles<ClassNames> & RouteComponentProps;
Expand All @@ -29,14 +34,16 @@ const AdminLanding: React.SFC<CombinedProps> = props => {
};

return (
<React.Fragment>
<Button onClick={navigateToCocktailForm}>Create Cocktail</Button>
<Button onClick={navigateToIngForm}>Create Ingredient</Button>
<div className={props.classes.root}>
<div className={props.classes.tabs}>
<Button onClick={navigateToCocktailForm}>Create Cocktail</Button>
<Button onClick={navigateToIngForm}>Create Ingredient</Button>
</div>
<Router>
<CreateCocktailForm path="cocktails/create" />
<CreateIngredientForm path="ingredients/create" />
</Router>
</React.Fragment>
</div>
);
};

Expand Down
259 changes: 240 additions & 19 deletions src/features/AdminLanding/CreateForms/CreateCocktail.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,252 @@
import {
StyleRulesCallback,
withStyles,
WithStyles
} from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { RouteComponentProps } from '@reach/router';
import { assocPath } from 'ramda';
import React from 'react';
import { compose } from 'recompose';
import { debounce } from 'throttle-debounce';

type ClassNames = 'root';
import Button from 'src/components/Button';
import Searchbar, { ResolvedData } from 'src/components/Searchbar';
import Select, { Option } from 'src/components/Select';
import TextField from 'src/components/TextField';

const styles: StyleRulesCallback<ClassNames> = theme => ({
root: {}
});
import withFormStyles, { FormStyleProps } from './CreateForms.styles';

interface Props {}
import {
ActionType,
createCocktail,
Finishes,
GlassType,
Ingredient as POSTIngredient
} from 'src/services/cocktails';
import { getIngredients } from 'src/services/ingredients';
import { APIError } from 'src/services/types';

interface State {}
import { transformAPIResponseToReactSelect } from 'src/utils/transformAPIResponseToReactSelect';

type CombinedProps = Props & WithStyles<ClassNames> & RouteComponentProps;
const glasses: Option<GlassType, GlassType>[] = [
{
label: 'Rocks',
value: 'Rocks'
},
{
label: 'Highball',
value: 'Highball'
},
{
label: 'Snifter',
value: 'Snifter'
}
];

class CreateCocktail extends React.PureComponent<CombinedProps, State> {
state: State = {};
const actions: Option<ActionType, ActionType>[] = [
{
label: 'Add',
value: 'Add'
},
{
label: 'Muddle',
value: 'Muddle'
},
{
label: 'Squeeze',
value: 'Squeeze'
}
];

render() {
return <div>hello world</div>;
const finishes: Option<Finishes, Finishes>[] = [
{
label: 'Shaken',
value: 'shaken'
},
{
label: 'Stirred',
value: 'stirred'
}
}
];

type CombinedProps = FormStyleProps & RouteComponentProps;

const CreateCocktail: React.FC<CombinedProps> = props => {
const [label, setLabel] = React.useState<string>('');
const [glass, setGlass] = React.useState<GlassType>('');
const [finish, setFinish] = React.useState<Finishes>(null);
const [loading, setLoading] = React.useState<boolean>(false);
const [isSearchingIngredient, setIsSearchingIngredient] = React.useState<
boolean
>(false);
const [dropDownOptions, setDropDownOptions] = React.useState<ResolvedData[]>(
[]
);
const [ingredientsCount, setIngredientsCount] = React.useState<number>(1);
const [ingredientIds, setIngredientIds] = React.useState<
Record<string, number>
>({});
const [ounces, setOunces] = React.useState<Record<string, number>>({});
const [selectedActions, setActions] = React.useState<
Record<string, ActionType>
>({});
const [error, setError] = React.useState<APIError | undefined>(undefined);

const fetchIngredient = (value: string) => {
return getIngredients({
name: value
})
.then(response => {
const firstFive = {
...response,
data: response.data.filter((eachIng, index) => index <= 5)
};
setIsSearchingIngredient(false);
setDropDownOptions(transformAPIResponseToReactSelect(firstFive));
})
.catch((e: Error) => {
setIsSearchingIngredient(false);
setDropDownOptions([]);
});
};

const debouncedFetch = debounce(400, false, fetchIngredient);

const handleSearch = (value: string) => {
setIsSearchingIngredient(true);
debouncedFetch(value);
};

/*
* handler for selecting or removing an option
*/
const handleChange = (
values: ResolvedData,
{ action, removedValue }: any,
index: number
) => {
switch (action) {
case 'remove-value':
case 'pop-value':
case 'select-option':
setIngredientIds(assocPath([index], values.key, ingredientIds));
default:
return;
}
};

const handleCreateCocktail = () => {
setLoading(true);
setError(undefined);

const ingPayload: POSTIngredient[] = Object.keys(ingredientIds).map(
eachIngredientId => {
return {
id: ingredientIds[eachIngredientId],
step: +eachIngredientId + 1,
ounces: +ounces[eachIngredientId] || 0,
action: selectedActions[eachIngredientId]
};
}
);

createCocktail({
name: label,
glass,
finish,
ingredients: ingPayload
})
.then(response => {
console.log(response);
setLoading(false);
})
.catch((e: APIError) => {
console.log(e);
setLoading(false);
setError(e);
});
};

const { classes } = props;

const styled = withStyles(styles);
return (
<form className={classes.root}>
<Typography variant="h4">Creating Cocktail {label}</Typography>
<TextField
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setLabel(e.target.value)
}
placeholder="Enter Cocktail Name"
/>
<Select
options={glasses}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
setGlass(e.target.value as GlassType)
}
defaultText="Select Glass"
/>
<Select
options={finishes as any}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
setFinish(e.target.value as Finishes)
}
defaultText="Select Finish (optional)"
/>
{Array.apply(null, Array(ingredientsCount)).map(
(eachIteration, index) => {
return (
<div className={classes.section} key={index}>
<Typography variant="h6">Ingredient {index + 1}</Typography>
<Searchbar
className="react-select-container"
classNamePrefix="react-select"
handleSubmit={() => null}
dropDownOptions={dropDownOptions}
loading={isSearchingIngredient}
handleChange={handleSearch}
handleSelect={(value, action) =>
handleChange(value, action, index)
}
loadingMessage={() => 'Fetching ingredients...'}
noOptionsMessage={() => 'No Ingredients Found'}
placeholder="Search for an ingredient"
/>
<TextField
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setOunces(assocPath([index], e.target.value, ounces))
}
placeholder="Enter Number of Ounces"
/>
<Select
options={actions}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setActions(
assocPath([index], e.target.value, selectedActions)
)
}
defaultText="Select Action"
/>
{index > 0 && (
<Button
onClick={() => setIngredientsCount(ingredientsCount - 1)}
>
Remove Ingredient
</Button>
)}
</div>
);
}
)}
<div className={classes.section}>
<Button onClick={() => setIngredientsCount(ingredientsCount + 1)}>
Add Ingredient
</Button>
<Button onClick={handleCreateCocktail} isLoading={!!loading}>
Create Cocktail
</Button>
</div>
</form>
);
};

export default styled(CreateCocktail);
export default compose<CombinedProps, RouteComponentProps>(
withFormStyles,
React.memo
)(CreateCocktail);
Loading

0 comments on commit c1127ea

Please sign in to comment.