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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"formik": "^2.2.9",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hot-toast": "^2.4.1",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
Expand Down
137 changes: 107 additions & 30 deletions src/components/AddCustomer/AddCustomerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,67 @@ import {
StyledInput,
StyledLabel,
StyledAddButton,
StyledErrorDiv,
} from "./StyledAddCustomerForm";

type Props = {
saveCustomer: (customer: ICustomer | any) => void;
};

export const AddCustomerForm: React.FC<Props> = ({ saveCustomer }) => {
function validateEmail(value: string) {
let error;
if (!value) {
error = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
error = "Invalid email address";
}
return error;
}

function validateFirstName(value: string) {
let error;
if (!value) {
error = "Required";
} else if (!/^[a-zA-Z0-9]{2,50}$/i.test(value)) {
error = "Please enter a valid name";
}
return error;
}

function validateLastName(value: string) {
let error;
if (!value) {
error = "Required";
} else if (!/^[a-zA-Z0-9]{2,50}$/i.test(value)) {
error = "Please enter a valid Last Name";
}
return error;
}

function validateBirthDate(value: string) {
let error;
if (!value) {
error = "Required";
} else if (/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/i.test(value)) {
error = "Please enter a valid date";
} else {
const inputDate = new Date(value);
const currentDate = new Date();
if (inputDate.getTime() >= currentDate.getTime()) {
error = "Please enter a date before today's date";
}
}
return error;
}

export const AddCustomerForm = ({ saveCustomer }: Props) => {
return (
<Formik
initialValues={{
firstName: "",
lastName: "",
phoneNumber: "",
email: "",
birthDate: "",
}}
onSubmit={(
values: Customer,
Expand All @@ -28,34 +76,63 @@ export const AddCustomerForm: React.FC<Props> = ({ saveCustomer }) => {
setSubmitting(false);
}}
>
<StyledForm>
<StyledLabel htmlFor="firstName">First Name</StyledLabel>
<Field
as={StyledInput}
id="firstName"
name="firstName"
placeholder="John"
/>

<StyledLabel htmlFor="lastName">Last Name</StyledLabel>
<Field
as={StyledInput}
id="lastName"
name="lastName"
placeholder="Doe"
/>

<StyledLabel htmlFor="phoneNumber">Phone Number</StyledLabel>
<Field
as={StyledInput}
id="phoneNumber"
name="phoneNumber"
placeholder="john@acme.com"
type="tel"
/>

<StyledAddButton type="submit">Add Customer</StyledAddButton>
</StyledForm>
{({ errors, touched, validateForm }) => (
<StyledForm>
<StyledLabel htmlFor="firstName">First Name</StyledLabel>
<Field
as={StyledInput}
id="firstName"
name="firstName"
placeholder="John"
validate={validateFirstName}
/>
{errors.firstName && touched.firstName && (
<StyledErrorDiv>{errors.firstName}</StyledErrorDiv>
)}

<StyledLabel htmlFor="lastName">Last Name</StyledLabel>
<Field
as={StyledInput}
id="lastName"
name="lastName"
placeholder="Doe"
validate={validateLastName}
/>
{errors.lastName && touched.lastName && (
<StyledErrorDiv>{errors.lastName}</StyledErrorDiv>
)}

<StyledLabel htmlFor="email">Email</StyledLabel>
<Field
as={StyledInput}
id="email"
name="email"
validate={validateEmail}
placeholder="john@acme.com"
type="email"
/>
{errors.email && touched.email && (
<StyledErrorDiv>{errors.email}</StyledErrorDiv>
)}

<StyledLabel htmlFor="birthDate">Birthday</StyledLabel>
<Field
as={StyledInput}
id="birthDate"
name="birthDate"
placeholder="MM/DD/YYYY"
type="date"
validate={validateBirthDate}
/>
{errors.birthDate && touched.birthDate && (
<StyledErrorDiv>{errors.birthDate}</StyledErrorDiv>
)}

<StyledAddButton type="submit" onClick={() => validateForm()}>
Add Customer
</StyledAddButton>
</StyledForm>
)}
</Formik>
);
};
8 changes: 8 additions & 0 deletions src/components/AddCustomer/StyledAddCustomerForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ export const StyledAddButton = styled.button`
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
`;

export const StyledErrorDiv = styled.div`
color: red;
font-size: 16px;
margin: 0 0 1rem;
padding: 0;
`;
33 changes: 19 additions & 14 deletions src/components/Customer/Customer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,36 @@ import {
StyledCustomerInfo,
StyledCustomerName,
} from "./StyledCustomer";
import { Toaster } from "react-hot-toast";

type Props = {
customer: ICustomer;
removeCustomer: (customer: ICustomer) => void;
};

export const Customer: React.FC<Props> = ({ customer, removeCustomer }) => {
export const Customer = ({ customer, removeCustomer }: Props) => {
const dispatch: Dispatch<any> = useDispatch();

const deleteCustomer = React.useCallback(
(customer: ICustomer) => dispatch(removeCustomer(customer)),
[dispatch, removeCustomer]
(customer: ICustomer) => {
dispatch(removeCustomer(customer));
},
[dispatch]
);

return (
<StyledCustomer>
<StyledCustomerName>
{customer.firstName} {customer.lastName}
</StyledCustomerName>
<StyledCustomerInfo>
Phone number: {customer.phoneNumber}
</StyledCustomerInfo>
<StyledCustomerDelete onClick={() => deleteCustomer(customer)}>
Delete
</StyledCustomerDelete>
</StyledCustomer>
<>
<StyledCustomer>
<StyledCustomerName>
{customer.firstName} {customer.lastName}
</StyledCustomerName>
<StyledCustomerInfo>Email: {customer.email}</StyledCustomerInfo>
<StyledCustomerInfo>Birthday: {customer.birthDate}</StyledCustomerInfo>
<StyledCustomerDelete onClick={() => deleteCustomer(customer)}>
Delete
</StyledCustomerDelete>
</StyledCustomer>
<Toaster />
</>
);
};
2 changes: 2 additions & 0 deletions src/components/Customer/StyledCustomer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const StyledCustomer = styled.div`
border: 1px solid #ccc;
margin: 1rem;
padding: 1rem;
border-radius: 4px;
`;

export const StyledCustomerName = styled.h2`
Expand All @@ -25,4 +26,5 @@ export const StyledCustomerDelete = styled.button`
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
`;
8 changes: 8 additions & 0 deletions src/redux/reducers/customerReducers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ const initialState = {
id: 1,
lastName: "Babbage",
phoneNumber: "0412 123 123",
birthDate: "1791-12-26",
},
{
firstName: "Alan",
id: 2,
lastName: "Turing",
phoneNumber: "(03) 9599 1234",
birthDate: "1791-12-26",
},
{
firstName: "Ada",
id: 3,
lastName: "Lovelace",
phoneNumber: "+61 423 345 567",
birthDate: "1791-12-26",
},
],
};
Expand All @@ -40,6 +43,7 @@ describe("customer reducer", () => {
id: 1,
lastName: "Dummy",
phoneNumber: "000 000 000",
birthDate: "1791-12-26",
},
})
).toEqual({
Expand All @@ -49,6 +53,7 @@ describe("customer reducer", () => {
id: 1,
lastName: "Dummy",
phoneNumber: "000 000 000",
birthDate: "1791-12-26",
},
],
});
Expand All @@ -63,6 +68,7 @@ describe("customer reducer", () => {
id: 1,
lastName: "Babbage",
phoneNumber: "0412 123 123",
birthDate: "1791-12-26",
},
})
).toEqual({
Expand All @@ -72,12 +78,14 @@ describe("customer reducer", () => {
id: 2,
lastName: "Turing",
phoneNumber: "(03) 9599 1234",
birthDate: "1791-12-26",
},
{
firstName: "Ada",
id: 3,
lastName: "Lovelace",
phoneNumber: "+61 423 345 567",
birthDate: "1791-12-26",
},
],
});
Expand Down
40 changes: 32 additions & 8 deletions src/redux/reducers/customerReducers.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import { CustomerAction, CustomerState, ICustomer } from "../../types/types";
import { ADD_CUSTOMER, REMOVE_CUSTOMER } from "../actions/customerTypes";
import toast from "react-hot-toast";

export const initialState: CustomerState = {
customers: [
{
id: 1,
firstName: "Charles",
lastName: "Babbage",
phoneNumber: "0412 123 123",
email: "cb@test.com",
birthDate: "1791-12-26",
},
{
id: 2,
firstName: "Alan",
lastName: "Turing",
phoneNumber: "(03) 9599 1234",
email: "at@test.com",
birthDate: "1912-06-23",
},
{
id: 3,
firstName: "Ada",
lastName: "Lovelace",
phoneNumber: "+61 423 345 567",
email: "al@test.com",
birthDate: "1815-12-10",
},
],
};

const notifyExists = () =>
toast("Customer already exists!", {
icon: "🙅",
});
const notifySuccess = () => toast.success("Customer added !");
const notifyDelete = () => toast.error("Customer Deleted!");

export const customerReducer = (
state: CustomerState = initialState,
action: CustomerAction
Expand All @@ -34,16 +45,29 @@ export const customerReducer = (
id: action.customer.id ?? Math.random(), // not really unique but it's just an example
firstName: action.customer.firstName,
lastName: action.customer.lastName,
phoneNumber: action.customer.phoneNumber,
};
return {
...state,
customers: state.customers.concat(newCustomer),
email: action.customer.email,
birthDate: action.customer.birthDate,
};

const customerExists = state.customers.find(
(customer) => customer.firstName === newCustomer.firstName
);

if (customerExists) {
notifyExists();
return state;
} else {
notifySuccess();
return {
...state,
customers: state.customers.concat(newCustomer),
};
}
case REMOVE_CUSTOMER:
const updatedCustomers: ICustomer[] = state.customers.filter(
(customer) => customer.id !== action.customer.id
);
notifyDelete();
return {
...state,
customers: updatedCustomers,
Expand Down
3 changes: 2 additions & 1 deletion src/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export interface Customer {
firstName: string;
lastName: string;
phoneNumber: string;
email: string;
birthDate: string;
}

export interface ICustomer extends Customer {
Expand Down
Loading