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
52 changes: 34 additions & 18 deletions frontend/components/OrderList.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
import React from 'react'
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useGetOrdersQuery } from '../state/ordersApi';
import { toggleSizeFilter } from '../state/ordersSlice';

export default function OrderList() {
const orders = []
const { data: orders, isLoading: ordersLoading, isFetching: ordersRefreshing, error } = useGetOrdersQuery();

const filterSize = useSelector(state => state.ordersState.filterSize);
const dispatch = useDispatch();

const handleSizeFilterChange = (size) => {
dispatch(toggleSizeFilter(size));
};

return (
<div id="orderList">
<h2>Pizza Orders</h2>
<h2>Pizza Orders {(ordersLoading || ordersRefreshing) && 'loading...'}</h2>
<ol>
{
orders.map(() => {
return (
<li key={1}>
<div>
order details here
</div>
</li>
)
})
ordersLoading ? 'loading...' :
orders?.filter(order => filterSize === 'All' || order.size === filterSize)
.map(order => (
<li key={order.id}>
<div>
<div>{order.customer} ordered a size {order.size} with {order.toppings?.length > 0 ? `${order.toppings.length} toppings` : 'no toppings'}</div>
</div>
</li>
))
}
</ol>
<div id="sizeFilters">
Filter by size:
{
['All', 'S', 'M', 'L'].map(size => {
const className = `button-filter${size === 'All' ? ' active' : ''}`
return <button
data-testid={`filterBtn${size}`}
className={className}
key={size}>{size}</button>
const className = `button-filter${size === 'All' ? ' active' : ''}`;
return (
<button
data-testid={`filterBtn${size}`}
className={className}
onClick={() => handleSizeFilterChange(size)}
key={size}>
{size}
</button>
);
})
}
</div>
</div>
)
);
}
160 changes: 133 additions & 27 deletions frontend/components/PizzaForm.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,83 @@
import React from 'react'
import React, { useReducer, useState } from 'react';
import { useCreateOrderMutation } from '../state/ordersApi';

const initialFormState = { // suggested
const CHANGE_INPUT = 'CHANGE_INPUT';
const TOGGLE_TOPPING = 'TOGGLE_TOPPING';
const RESET_FORM = 'RESET_FORM';

const initialFormState = {
fullName: '',
size: '',
'1': false,
'2': false,
'3': false,
'4': false,
'5': false,
}
toppings: [],
};

const reducer = (state, action) => {
switch (action.type) {
case CHANGE_INPUT: {
const { name, value } = action.payload;
return { ...state, [name]: value };
}
case TOGGLE_TOPPING: {
const { topping } = action.payload;
const newToppings = state.toppings.includes(topping)
? state.toppings.filter(t => t !== topping)
: [...state.toppings, topping];
return { ...state, toppings: newToppings };
}
case RESET_FORM:
return initialFormState;
default:
return state;
}
};

export default function PizzaForm() {
const [state, dispatch] = useReducer(reducer, initialFormState);
const [error, setError] = useState('');
const [createOrder, { isLoading: creatingOrder }] = useCreateOrderMutation();

const onChange = ({ target: { name, value } }) => {
dispatch({ type: CHANGE_INPUT, payload: { name, value } });
setError('');
};

const onToppingChange = ({ target: { name } }) => {
dispatch({ type: TOGGLE_TOPPING, payload: { topping: name } });
};

const resetForm = () => {
dispatch({ type: RESET_FORM });
setError('');
};

const handleSubmit = (evt) => {
evt.preventDefault();
const { fullName, size, toppings } = state;

if (!fullName) {
setError('fullName is required');
return;
}
if (!['S', 'M', 'L'].includes(size)) {
setError('Size must be one of the following values: S, M, L');
return;
}

createOrder({ fullName, size, toppings })
.unwrap()
.then(() => {
resetForm();
})
.catch((err) => {
setError('Order failed: ' + err.message);
});
};

return (
<form>
<form onSubmit={handleSubmit}>
<h2>Pizza Form</h2>
{true && <div className='pending'>Order in progress...</div>}
{true && <div className='failure'>Order failed: fullName is required</div>}

{creatingOrder && <div className='pending'>Order in progress...</div>}
{error && <div className='error'>{error}</div>}
<div className="input-group">
<div>
<label htmlFor="fullName">Full Name</label><br />
Expand All @@ -26,40 +87,85 @@ export default function PizzaForm() {
name="fullName"
placeholder="Type full name"
type="text"
value={state.fullName}
onChange={onChange}
/>
</div>
</div>

<div className="input-group">
<div>
<label htmlFor="size">Size</label><br />
<select data-testid="sizeSelect" id="size" name="size">
<select
data-testid="sizeSelect"
id="size"
name="size"
value={state.size}
onChange={onChange}
>
<option value="">----Choose size----</option>
<option value="S">Small</option>
<option value="M">Medium</option>
<option value="L">Large</option>
</select>
</div>
</div>

<div className="input-group">
<label>
<input data-testid="checkPepperoni" name="1" type="checkbox" />
Pepperoni<br /></label>
<input
data-testid="checkPepperoni"
name="1"
type="checkbox"
checked={state.toppings.includes('1')}
onChange={onToppingChange}
/>
Pepperoni<br />
</label>
<label>
<input data-testid="checkGreenpeppers" name="2" type="checkbox" />
Green Peppers<br /></label>
<input
data-testid="checkGreenpeppers"
name="2"
type="checkbox"
checked={state.toppings.includes('2')}
onChange={onToppingChange}
/>
Green Peppers<br />
</label>
<label>
<input data-testid="checkPineapple" name="3" type="checkbox" />
Pineapple<br /></label>
<input
data-testid="checkPineapple"
name="3"
type="checkbox"
checked={state.toppings.includes('3')}
onChange={onToppingChange}
/>
Pineapple<br />
</label>
<label>
<input data-testid="checkMushrooms" name="4" type="checkbox" />
Mushrooms<br /></label>
<input
data-testid="checkMushrooms"
name="4"
type="checkbox"
checked={state.toppings.includes('4')}
onChange={onToppingChange}
/>
Mushrooms<br />
</label>
<label>
<input data-testid="checkHam" name="5" type="checkbox" />
Ham<br /></label>
<input
data-testid="checkHam"
name="5"
type="checkbox"
checked={state.toppings.includes('5')}
onChange={onToppingChange}
/>
Ham<br />
</label>
</div>
<input data-testid="submit" type="submit" />
<button
data-testid="submit"
type="submit">
Submit
</button>
</form>
)
);
}
27 changes: 27 additions & 0 deletions frontend/state/ordersApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const ordersApi = createApi({
reducerPath: 'ordersApi',
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:9009/api/pizza/' }),
tagTypes: ['Orders'],
endpoints: (builder) => ({
getOrders: builder.query({
query: () => 'history',
transformResponse: (response) => {
console.log('Fetched Orders:', response);
return response;
},
providesTags: ['Orders'],
}),
createOrder: builder.mutation({
query: (order) => ({
url: 'order',
method: 'POST',
body: order,
}),
invalidatesTags: ['Orders'],
}),
}),
});

export const { useGetOrdersQuery, useCreateOrderMutation} = ordersApi;
25 changes: 25 additions & 0 deletions frontend/state/ordersSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createSlice } from "@reduxjs/toolkit";

export const ordersSlice = createSlice({
name: 'orders',
initialState: {
showAllOrders: true,
orderList: [],
filterSize: 'All',
},
reducers: {
toggleShowAllOrders: state => {
state.showAllOrders = !state.showAllOrders;
},
setOrders: (state, action) => {
state.orderList = action.payload;
},
toggleSizeFilter: (state, action) => {
state.filterSize = action.payload;
},
},
});

export const { toggleShowAllOrders, setOrders, toggleSizeFilter } = ordersSlice.actions;

export default ordersSlice.reducer;
12 changes: 5 additions & 7 deletions frontend/state/store.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { configureStore } from '@reduxjs/toolkit'
import ordersReducer from './ordersSlice';
import { ordersApi } from './ordersApi';

const exampleReducer = (state = { count: 0 }) => {
return state
}

export const resetStore = () => configureStore({
reducer: {
example: exampleReducer,
// add your reducer(s) here
ordersState: ordersReducer,
[ordersApi.reducerPath]: ordersApi.reducer,
},
middleware: getDefault => getDefault().concat(
// if using RTK Query for your networking: add your middleware here
// if using Redux Thunk for your networking: you can ignore this
ordersApi.middleware,
),
})

Expand Down
Loading