diff --git a/src/frontend/App.js b/src/frontend/App.js
index 272194d..ad3ec07 100644
--- a/src/frontend/App.js
+++ b/src/frontend/App.js
@@ -13,6 +13,8 @@ import PostingsRouter from './pages/postings/Postings.router';
import SponsorsRouter from './pages/sponsors/Sponsors.router';
import GeeseRouter from './pages/geese/Geese.router';
import TeamDescriptionsRouter from './pages/team-descriptions/TeamDescriptions.router';
+import MerchStoreRouter from './pages/merch-store/Products.router'
+
import { addAuthTokenToRequests } from './api/server';
import BlogsRouter from './pages/blogs/Blogs.Router';
@@ -69,6 +71,11 @@ const App = () => {
+
+ {!token && }
+
+
+
{/* {!token && } */}
diff --git a/src/frontend/api/index.js b/src/frontend/api/index.js
index 85e794e..93ac1f2 100644
--- a/src/frontend/api/index.js
+++ b/src/frontend/api/index.js
@@ -7,6 +7,7 @@ import openingsDescription from './openings-description';
import sponsors from './sponsors';
import geeseInfo from './geese-info';
import blogs from './blogs';
+import merchStore from './merch-store'
export default {
google: google(server),
@@ -16,5 +17,6 @@ export default {
formUpload: formUpload(server),
openingsDescription: openingsDescription(server),
geeseInfo: geeseInfo(server),
- blogs: blogs(server)
+ blogs: blogs(server),
+ merchStore: merchStore(server)
};
diff --git a/src/frontend/api/merch-store.js b/src/frontend/api/merch-store.js
new file mode 100644
index 0000000..ce21909
--- /dev/null
+++ b/src/frontend/api/merch-store.js
@@ -0,0 +1,26 @@
+const getProducts = (server) => () => server.get('/api/products');
+const getProductVariations = (server) => (id) =>
+ server.get(`/api/products/${id}/variations`);
+const updateProduct = (server) => (id, updatedProductInfo) =>
+ server.patch(`/api/products/${id}`, updatedProductInfo);
+const updateProductVariation = (server) => (variationId, productId, updatedVariationInfo) =>
+ server.patch(`/api/products/${productId}/variations/${variationId}`, updatedVariationInfo);
+const addProduct = (server) => (product) =>
+ server.post(`/api/products`, product);
+const addProductVariation = (server) => (productVariation) =>
+ server.post(`/api/products/${productVariation.productId}/variations`, productVariation);
+const deleteProduct = (server) => (id) =>
+ server.delete(`/api/products/${id}`);
+ const deleteProductVariation = (server) => (productId, variationId) =>
+ server.delete(`/api/products/${productId}/variations/${variationId}`);
+
+export default (server) => ({
+ getProducts: getProducts(server),
+ getProductVariations: getProductVariations(server),
+ updateProduct: updateProduct(server),
+ updateProductVariation:updateProductVariation(server),
+ addProduct: addProduct(server),
+ addProductVariation: addProductVariation(server),
+ deleteProduct: deleteProduct(server),
+ deleteProductVariation: deleteProductVariation(server),
+});
diff --git a/src/frontend/assets/page-icons/merch.svg b/src/frontend/assets/page-icons/merch.svg
new file mode 100644
index 0000000..8019585
--- /dev/null
+++ b/src/frontend/assets/page-icons/merch.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/src/frontend/hooks/goose-form.js b/src/frontend/hooks/goose-form.js
index 26ed629..728b993 100644
--- a/src/frontend/hooks/goose-form.js
+++ b/src/frontend/hooks/goose-form.js
@@ -6,7 +6,7 @@ import moment from 'moment';
const useGooseForm = () => {
const [gooseName, setGooseName] = useState('');
- const [description, setDescription] = useState('');
+ const [description, setDescription] = useState('');
const [images, setImages] = useState([]);
const [imageUrls, setImageUrls] = useState([]);
const [imagesToDelete, setImagesToDelete] = useState([]);
diff --git a/src/frontend/hooks/product-variations-form.js b/src/frontend/hooks/product-variations-form.js
new file mode 100644
index 0000000..b39deb5
--- /dev/null
+++ b/src/frontend/hooks/product-variations-form.js
@@ -0,0 +1,199 @@
+import { useState, useEffect, useCallback } from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+import useProductVariations from './product-variations';
+import useProducts from './products';
+import api from '../api';
+import moment from 'moment';
+
+const useProductVariationsForm = () => {
+ const [productName, setProductName] = useState('');
+ const [productId, setProductId] = useState(null);
+ const [variationName, setVariationName] = useState('');
+ const [price, setPrice] = useState(null);
+ const [stock, setStock] = useState(null);
+ const [picture, setPicture] = useState(null);
+ const [pictureUrl, setPictureURL] = useState(null);
+ const [showModal, setShowModal] = useState(false);
+ const history = useHistory();
+ const params = useParams();
+ const { products } = useProducts();
+ const { productVariations } = useProductVariations();
+
+ useEffect(() => {
+ (async () => {
+ try {
+ let variation = {};
+ let product = {};
+
+ if (params.productId) {
+ product = await products.find(
+ (product) => product.id == params.productId,
+ );
+ }
+
+ if (params.variationId) {
+ const productVariationId = parseInt(params.variationId, 10);
+ variation = productVariations.find(
+ (variation) => variation.id === productVariationId,
+ );
+ }
+ setProductName(product.name);
+ setProductId(product.id);
+
+ setVariationName(variation.variationName);
+ setPrice(variation.price);
+ setStock(variation.stock);
+ setPictureURL(variation.picture);
+ } catch (err) {
+ console.error("Error init'ing product variation form info", err);
+ }
+ })();
+ }, [productVariations, products, params.variationId, params.productId]);
+
+ const imageUpload = (image) => {
+ setPicture(image);
+ setPictureURL(URL.createObjectURL(image));
+ };
+
+ const imageDelete = useCallback(() => {
+ setPicture(null);
+ setPictureURL(null);
+ }, [setPicture]);
+
+ const openModal = () => {
+ setShowModal(true);
+ };
+
+ const closeModal = () => {
+ setShowModal(false);
+ };
+
+ const closeForm = useCallback(() => {
+ history.push(`/products/${productId}/variations`);
+ }, [history, productId]);
+
+ const saveForm = useCallback(async () => {
+ try {
+ let newPictureUrl = pictureUrl;
+
+ if (picture instanceof File) {
+ const data = new FormData();
+ data.append('files', picture, picture.name);
+ const res = await api.formUpload(data);
+
+ newPictureUrl = res.data.data[0];
+ }
+
+ const currentDateTime = new Date();
+ const unixTimestamp = currentDateTime.getTime();
+
+ const productVariationInfo = {
+ variationName,
+ productId,
+ price,
+ stock,
+ picture: newPictureUrl,
+ lastUpdated: unixTimestamp,
+ };
+
+ if (variationName && price && stock && pictureUrl) {
+ if (params.variationId) {
+ await api.merchStore.updateProductVariation(
+ params.variationId,
+ productId,
+ productVariationInfo,
+ );
+ } else {
+ await api.merchStore.addProductVariation({
+ ...productVariationInfo,
+ productId,
+ });
+ }
+ setPicture(picture);
+ } else {
+ throw new Error('Please fill all the required fields.');
+ }
+ // onSuccess:
+ closeForm();
+ } catch (e) {
+ // TODO: Display "could not add/update" error to the user as dialogue.
+ console.error(e);
+ }
+ }, [
+ params,
+ variationName,
+ price,
+ stock,
+ closeForm,
+ picture,
+ productId,
+ pictureUrl,
+ ]);
+
+ const deleteForm = useCallback(async () => {
+ try {
+ if (params.variationId) {
+ const productVariationId = parseInt(params.variationId, 10);
+ const variation = productVariations.find(
+ (variation) => variation.id === productVariationId,
+ );
+
+ if (variation) {
+ const product = products.find(
+ (product) => product.id === variation.productId,
+ );
+
+ if (product) {
+ const productId = product.id;
+ await api.merchStore.deleteProductVariation(
+ productId,
+ params.variationId,
+ );
+ }
+ }
+ }
+ closeForm();
+ } catch (error) {
+ console.error('Error deleting product variation:', error);
+ closeForm();
+ }
+ }, [params, productVariations, products, closeForm]);
+
+ const getLastUpdated = useCallback(() => {
+ const variation = productVariations.find(
+ (variation) => variation.id === parseInt(params.variationId, 10),
+ );
+ if (variation) {
+ return moment.utc(variation.updatedAt).local().format('MMMM D, YYYY');
+ }
+ return '';
+ }, [productVariations, params.variationId]);
+
+ return {
+ productName,
+ setProductName,
+ productId,
+ setProductId,
+ variationName,
+ setVariationName,
+ price,
+ setPrice,
+ picture,
+ setPicture,
+ pictureUrl,
+ setPictureURL,
+ stock,
+ setStock,
+ imageUpload,
+ imageDelete,
+ closeForm,
+ saveForm,
+ deleteForm,
+ getLastUpdated,
+ showModal,
+ openModal,
+ closeModal,
+ };
+};
+
+export default useProductVariationsForm;
diff --git a/src/frontend/hooks/product-variations.js b/src/frontend/hooks/product-variations.js
new file mode 100644
index 0000000..3ab86a6
--- /dev/null
+++ b/src/frontend/hooks/product-variations.js
@@ -0,0 +1,55 @@
+import { useEffect } from 'react';
+import { useParams } from 'react-router-dom';
+import api from '../api';
+import { useDispatch, useSelector } from 'react-redux';
+import * as productVariationActions from '../state/product-variations/actions';
+import * as productVariationSelectors from '../state/product-variations/selectors';
+
+const useProductVariations = () => {
+ const params = useParams();
+
+ const dispatch = useDispatch();
+ const productVariations = useSelector(
+ productVariationSelectors.allVariations,
+ );
+ useEffect(() => {
+ (async () => {
+ try {
+ let newProductVariations = [];
+ if (params.productId) {
+ const productVariationsResponse =
+ await api.merchStore.getProductVariations(params.productId);
+ // get all products
+ const productsResponse = await api.merchStore.getProducts();
+ const products = productsResponse.data;
+
+ const productName = products.find(
+ (product) => product.id == params.productId,
+ ).name;
+
+ for (const productVariation of productVariationsResponse.data) {
+ const variation = { ...productVariation, productName };
+ newProductVariations = [...newProductVariations, variation];
+ }
+ }
+
+ dispatch(
+ productVariationActions.updateProductVariationsInfo(
+ newProductVariations,
+ ),
+ );
+ } catch (err) {
+ console.error(
+ 'error fetching products in product-variations.js: ',
+ err,
+ );
+ }
+ })();
+ }, [dispatch, params.productId, params.variationId]);
+
+ return {
+ productVariations,
+ };
+};
+
+export default useProductVariations;
diff --git a/src/frontend/hooks/products-form.js b/src/frontend/hooks/products-form.js
new file mode 100644
index 0000000..9c4c0bf
--- /dev/null
+++ b/src/frontend/hooks/products-form.js
@@ -0,0 +1,111 @@
+import { useState, useEffect, useCallback } from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+import useProducts from './products';
+import api from '../api';
+
+const useProductsForm = () => {
+ const [productName, setProductName] = useState('');
+ const [description, setDescription] = useState('');
+ const [category, setCategory] = useState('');
+ const [showModal, setShowModal] = useState(false);
+ const history = useHistory();
+ const params = useParams();
+ const { products } = useProducts();
+
+ useEffect(() => {
+ if (products.length > 0) {
+ (async () => {
+ try {
+ // init relevant product data when editing
+ if (params.productId) {
+ const productId = parseInt(params.productId, 10);
+ const product = products.find(
+ (product) => product.id === productId,
+ );
+ if (product) {
+ setProductName(product.name);
+ setDescription(product.description);
+ setCategory(product.category);
+ }
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ })();
+ }
+ }, [setProductName, setDescription, setCategory]);
+
+ const openModal = () => {
+ setShowModal(true);
+ };
+
+ const closeModal = () => {
+ setShowModal(false);
+ };
+
+ const closeForm = useCallback(() => {
+ history.push('/products');
+ }, [history]);
+
+ const saveForm = useCallback(async () => {
+ try {
+ const data = new FormData();
+ const res = await api.formUpload(data);
+
+ const productInfo = {
+ name: productName,
+ description,
+ category,
+ };
+
+ // ensure all fields are filled
+ if (productName && description && category) {
+ // update product if it exists
+ if (params.productId) {
+ await api.merchStore.updateProduct(params.productId, productInfo);
+ }
+
+ // add product if it doesnt exist
+ else {
+ await api.merchStore.addProduct(productInfo);
+ }
+ } else {
+ throw new Error('Please fill all the required fields.');
+ }
+ // onSuccess:
+ closeForm();
+ } catch (e) {
+ console.error(e);
+ }
+ }, [params, productName, description, category, closeForm]);
+
+ const deleteForm = useCallback(async () => {
+ // delete product
+ if (params.productId) {
+ await api.merchStore.deleteProduct(params.productId);
+ }
+ closeForm();
+ }, [params.productId, closeForm]);
+
+ const openVariations = useCallback(() => {
+ history.push(`/products/${params.productId}/variations`);
+ }, [history, params.productId]);
+
+ return {
+ productName,
+ setProductName,
+ description,
+ setDescription,
+ category,
+ setCategory,
+ closeForm,
+ saveForm,
+ deleteForm,
+ showModal,
+ openVariations,
+ openModal,
+ closeModal,
+ };
+};
+
+export default useProductsForm;
diff --git a/src/frontend/hooks/products.js b/src/frontend/hooks/products.js
new file mode 100644
index 0000000..cb04863
--- /dev/null
+++ b/src/frontend/hooks/products.js
@@ -0,0 +1,29 @@
+import { useEffect } from 'react';
+import api from '../api';
+import { useDispatch, useSelector } from 'react-redux';
+import * as productActions from '../state/products/actions';
+import * as productSelectors from '../state/products/selectors';
+
+const useProducts = () => {
+ const dispatch = useDispatch();
+ const products = useSelector(productSelectors.allProducts);
+ useEffect(() => {
+ (async () => {
+ try {
+ const productsResponse = await api.merchStore.getProducts();
+
+ const newProducts = productsResponse.data;
+
+ dispatch(productActions.updateProductInfo(newProducts));
+ } catch (err) {
+ console.error(err);
+ }
+ })();
+ }, [dispatch]);
+
+ return {
+ products,
+ };
+};
+
+export default useProducts;
diff --git a/src/frontend/pages/landing/LandingPage.js b/src/frontend/pages/landing/LandingPage.js
index 16e77c4..035f98f 100644
--- a/src/frontend/pages/landing/LandingPage.js
+++ b/src/frontend/pages/landing/LandingPage.js
@@ -6,6 +6,7 @@ import RecruitmentPageSVG from '../../assets/page-icons/recruitment.svg';
import SponsorsPageSVG from '../../assets/page-icons/sponsors.svg';
import TeamDescriptionsPageSVG from '../../assets/page-icons/team-descriptions.svg';
import BlogPostsPageSVG from '../../assets/page-icons/blog-posts.svg';
+import MerchPageSVG from '../../assets/page-icons/merch.svg'
import UnstyledSection from './components/Section';
import Grid from '@mui/material/Grid';
@@ -62,6 +63,12 @@ const sections = [
previewLink: 'https://teamwaterloop.ca/blogs',
icon: BlogPostsPageSVG,
},
+ {
+ name: 'Merch Store',
+ editLink: '/products',
+ previewLink: 'https://teamwaterloop.ca/products',
+ icon: MerchPageSVG,
+ },
];
const LandingPage = () => {
diff --git a/src/frontend/pages/merch-store/Products.router.js b/src/frontend/pages/merch-store/Products.router.js
new file mode 100644
index 0000000..95c895c
--- /dev/null
+++ b/src/frontend/pages/merch-store/Products.router.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import { Switch, Route, useRouteMatch } from 'react-router-dom';
+import ProductsPage from './ProductsPage';
+import EditProduct from './edit-a-product/EditProduct';
+import EditProductVariation from './variations/edit-a-variation/EditVariation';
+import VariationsPage from './variations/VariationsPage';
+
+
+const ProductsRouter = () => {
+ const { path } = useRouteMatch();
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+export default ProductsRouter;
diff --git a/src/frontend/pages/merch-store/ProductsPage.js b/src/frontend/pages/merch-store/ProductsPage.js
new file mode 100644
index 0000000..a942e3c
--- /dev/null
+++ b/src/frontend/pages/merch-store/ProductsPage.js
@@ -0,0 +1,92 @@
+import React, { useCallback } from 'react';
+import styled from 'styled-components';
+import UnstyledButton from '../../components/Button';
+import PreviewTable from '../../components/PreviewTable';
+import TableCell from '@mui/material/TableCell';
+import useProducts from '../../hooks/products';
+import { useHistory } from 'react-router-dom';
+
+const Button = styled(UnstyledButton)``;
+
+const Container = styled.div`
+ margin: ${(props) => props.theme.pageMargin};
+`;
+
+const ProductsHeader = styled.p`
+ font: ${({ theme }) => theme.fonts.medium24};
+`;
+
+const TableLabelHeader = styled.span`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 30px;
+
+ @media only screen and (max-width: ${({ theme }) => theme.breakpoints.md}px) {
+ justify-content: center;
+ flex-direction: column;
+ margin-bottom: 20px;
+ }
+`;
+
+const TableHeader = styled.p`
+ font: ${({ theme }) => theme.fonts.bold36};
+`;
+
+const headers = [
+ {
+ id: 'name',
+ value: 'Product',
+ },
+ {
+ id: 'category',
+ value: 'Category',
+ },
+];
+
+const RowComponent = ({ name, category }) => (
+ <>
+ {name}
+ {category}
+ >
+);
+
+const ProductsPage = () => {
+ const { products } = useProducts();
+ const history = useHistory();
+
+ const rowProduct = products?.map((product) => ({
+ id: product.id,
+ name: product.name,
+ category: product.category,
+ }));
+
+ const addProduct = useCallback(() => {
+ history.push('/products/add');
+ }, [history]);
+
+ const onEditProduct = useCallback(
+ (id) => {
+ history.push(`/products/${id}/edit`);
+ },
+ [history],
+ );
+
+ return (
+
+ Merch Store Products
+
+ All Products
+
+
+
+
+ );
+};
+
+export default ProductsPage;
diff --git a/src/frontend/pages/merch-store/edit-a-product/EditProduct.js b/src/frontend/pages/merch-store/edit-a-product/EditProduct.js
new file mode 100644
index 0000000..ab1fcb8
--- /dev/null
+++ b/src/frontend/pages/merch-store/edit-a-product/EditProduct.js
@@ -0,0 +1,158 @@
+import React from 'react';
+import useProductsForm from '../../../hooks/products-form';
+import FormContainer from '../../../components/FormContainer/index';
+import Button from '../../../components/Button/index';
+import TextInput from '../../../components/TextInput/index';
+import styled from 'styled-components';
+import Dialog from '@mui/material/Dialog';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogTitle from '@mui/material/DialogTitle';
+import CloseIcon from '@mui/icons-material/Close';
+import IconButton from '@mui/material/IconButton';
+
+const EditProductPage = styled.div`
+ padding: 64px 88px 65px 58px;
+`;
+
+const TopRow = styled.div`
+ display: flex;
+`;
+
+const EditProductBody = styled.div`
+ padding-left: 30px;
+`;
+
+const ProductInfo = styled.div`
+ padding-left: 10px;
+`;
+
+
+const ProductButtons = styled.div`
+ padding-left: 10px;
+`;
+
+const DeleteButton = styled.div`
+ float: right;
+`;
+
+const ModalTitle = styled.div`
+ font-family: IBM Plex Sans;
+ font-style: normal;
+ font-weight: bold;
+ font-size: 24px;
+ color: #2a2a2a;
+ display: flex;
+ justify-content: space-between;
+`;
+
+const ModalDescription = styled.div`
+ font-family: IBM Plex Sans;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 18px;
+ color: #2a2a2a;
+`;
+
+const RedDeleteButton = styled(Button)`
+ background-color: #d02027;
+ color: white;
+`;
+
+const EditProduct = ({ add }) => {
+ const {
+ productName,
+ setProductName,
+ description,
+ setDescription,
+ category,
+ setCategory,
+ closeForm,
+ saveForm,
+ deleteForm,
+ openVariations,
+ showModal,
+ openModal,
+ closeModal,
+ } = useProductsForm();
+
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!add && (
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default EditProduct;
diff --git a/src/frontend/pages/merch-store/variations/VariationsPage.js b/src/frontend/pages/merch-store/variations/VariationsPage.js
new file mode 100644
index 0000000..979c7a8
--- /dev/null
+++ b/src/frontend/pages/merch-store/variations/VariationsPage.js
@@ -0,0 +1,120 @@
+import React, { useCallback } from 'react';
+import styled from 'styled-components';
+import UnstyledButton from '../../../components/Button';
+import PreviewTable from '../../../components/PreviewTable';
+import TableCell from '@mui/material/TableCell';
+import useProductVariations from '../../../hooks/product-variations';
+import { useHistory } from 'react-router-dom';
+import moment from 'moment';
+import useProductVariationsForm from '../../../hooks/product-variations-form';
+
+const Button = styled(UnstyledButton)``;
+
+const Container = styled.div`
+ margin: ${(props) => props.theme.pageMargin};
+`;
+
+const VariationsHeader = styled.p`
+ font: ${({ theme }) => theme.fonts.medium24};
+`;
+
+const TableLabelHeader = styled.span`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 30px;
+
+ @media only screen and (max-width: ${({ theme }) => theme.breakpoints.md}px) {
+ justify-content: center;
+ flex-direction: column;
+ margin-bottom: 20px;
+ }
+`;
+
+const TableHeader = styled.p`
+ font: ${({ theme }) => theme.fonts.bold36};
+`;
+
+const headers = [
+ {
+ id: 'variationName',
+ value: 'Variation',
+ },
+ {
+ id: 'price',
+ value: 'Price',
+ },
+ {
+ id: 'stock',
+ value: 'Stock',
+ },
+ {
+ id: 'lastUpdated',
+ value: 'Last Updated',
+ },
+];
+
+const RowComponent = ({ variationName, price, stock, lastUpdated }) => (
+ <>
+ {variationName}
+ ${price.toFixed(2)}
+ {stock}
+ {lastUpdated}
+ >
+);
+
+const VariationsPage = () => {
+ const { productVariations } = useProductVariations();
+ const { productId } = useProductVariationsForm();
+
+ const history = useHistory();
+
+ const rowVariation = productVariations?.map((variation) => ({
+ id: variation.id,
+ variationName: variation.variationName,
+ price: variation.price,
+ stock: variation.stock,
+ lastUpdated: moment
+ .utc(variation.lastUpdated)
+ .local()
+ .format('MMMM D, YYYY'),
+ }));
+
+ const productName = productVariations[0]?.productName;
+
+ const onAddVariation = useCallback(() => {
+ history.push(`/products/${productId}/variations/add`);
+ }, [history, productId]);
+
+ const onEditVariation = useCallback(
+ (variationId) => {
+ history.push(`/products/${productId}/variations/edit/${variationId}/`);
+ },
+ [history, productId],
+ );
+
+ const onReturnToProducts = useCallback(() => {
+ history.push(`/products`)
+ }, [])
+
+ return (
+
+ Merch Product Variations
+
+
+
+ {productName ? `${productName} Variations` : ''}
+
+
+
+
+
+ );
+};
+
+export default VariationsPage;
diff --git a/src/frontend/pages/merch-store/variations/edit-a-variation/EditVariation.js b/src/frontend/pages/merch-store/variations/edit-a-variation/EditVariation.js
new file mode 100644
index 0000000..b6566ac
--- /dev/null
+++ b/src/frontend/pages/merch-store/variations/edit-a-variation/EditVariation.js
@@ -0,0 +1,207 @@
+import React, { useMemo } from 'react';
+import useProductVariationsForm from '../../../../hooks/product-variations-form';
+import ImagePreview from '../../../../components/ImagePreview/index';
+import FormContainer from '../../../../components/FormContainer/index';
+import TextInput from '../../../../components/TextInput/index';
+import Button from '../../../../components/Button';
+import styled from 'styled-components';
+import Dialog from '@mui/material/Dialog';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogTitle from '@mui/material/DialogTitle';
+import CloseIcon from '@mui/icons-material/Close';
+import IconButton from '@mui/material/IconButton';
+
+const EditVariationsPage = styled.div`
+ padding: 64px 88px 65px 58px;
+`;
+
+const EditVariationBody = styled.div`
+ padding-left: 30px;
+`;
+
+
+const TopRow = styled.div`
+ display: flex;
+`;
+const VariationImages = styled.div`
+ margin-bottom: 60px;
+`;
+
+const ImageCard = styled.div`
+ margin-right: 18px;
+`;
+
+const ImageCards = styled.div`
+ padding-top: 20px;
+ display: flex;
+`;
+
+
+const VariationInfo = styled.div`
+ padding-left: 10px;
+`;
+
+
+const VariationButtons = styled.div`
+ padding-left: 10px;
+`;
+
+const DeleteButton = styled.div`
+ float: right;
+`;
+
+const ModalTitle = styled.div`
+ font-family: IBM Plex Sans;
+ font-style: normal;
+ font-weight: bold;
+ font-size: 24px;
+ color: #2a2a2a;
+ display: flex;
+ justify-content: space-between;
+`;
+
+const ModalDescription = styled.div`
+ font-family: IBM Plex Sans;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 18px;
+ color: #2a2a2a;
+`;
+
+const ProductName = styled.h2`
+font-family: IBM Plex Sans;
+padding-left: 30px;
+font-size: 30px;
+`
+
+const RedDeleteButton = styled(Button)`
+ background-color: #d02027;
+ color: white;
+`;
+
+const EditProductVariation = ({ add }) => {
+ const {
+ productName,
+ variationName,
+ setVariationName,
+ price,
+ setPrice,
+ picture,
+ pictureUrl,
+ imageUpload,
+ imageDelete,
+ stock,
+ setStock,
+ closeForm,
+ saveForm,
+ deleteForm,
+ showModal,
+ openModal,
+ closeModal,
+ } = useProductVariationsForm();
+
+ const displayImages = useMemo(() => {
+ if (pictureUrl){
+ return [
+
+ {
+ imageDelete();
+ }}
+ />
+
+ ]};
+
+ }, [picture, pictureUrl]);
+
+ return (
+
+
+
+
+
+ Product: {productName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {displayImages}
+
+
+
+
+
+
+
+
+
+ {!add && (
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default EditProductVariation;
diff --git a/src/frontend/state/action-types.js b/src/frontend/state/action-types.js
index aa242d3..7cba3a8 100644
--- a/src/frontend/state/action-types.js
+++ b/src/frontend/state/action-types.js
@@ -14,4 +14,7 @@ export const SPONSORS_SET_SPONSOR_TIERS = 'SPONSORS_SET_SPONSOR_TIERS';
export const GEESE_INFO_SET_GEESE_INFO = 'GEESE_INFO_SET_GEESE_INFO';
-export const UPDATE_BLOG_INFO = 'UPDATE_BLOG_INFO';
\ No newline at end of file
+export const UPDATE_BLOG_INFO = 'UPDATE_BLOG_INFO';
+
+export const PRODUCTS_SET_PRODUCT = 'PRODUCTS_SET_PRODUCT'
+export const PRODUCT_VARIATIONS_SET_VARIATION = 'PRODUCT_VARIATIONS_SET_VARIATION'
\ No newline at end of file
diff --git a/src/frontend/state/product-variations/actions.js b/src/frontend/state/product-variations/actions.js
new file mode 100644
index 0000000..ec4ba70
--- /dev/null
+++ b/src/frontend/state/product-variations/actions.js
@@ -0,0 +1,6 @@
+import * as actionTypes from '../action-types';
+
+export const updateProductVariationsInfo = (productVariationsInfo) => ({
+ type: actionTypes.PRODUCT_VARIATIONS_SET_VARIATION,
+ payload: productVariationsInfo,
+});
diff --git a/src/frontend/state/product-variations/reducer.js b/src/frontend/state/product-variations/reducer.js
new file mode 100644
index 0000000..4d2ebdd
--- /dev/null
+++ b/src/frontend/state/product-variations/reducer.js
@@ -0,0 +1,19 @@
+import * as actionTypes from '../action-types';
+
+const initialState = {
+ allProductVariationsInfo: [],
+};
+
+const reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case actionTypes.PRODUCT_VARIATIONS_SET_VARIATION:
+ return {
+ ...state,
+ allProductVariationsInfo: action.payload,
+ };
+ default:
+ return state;
+ }
+};
+
+export default reducer;
diff --git a/src/frontend/state/product-variations/selectors.js b/src/frontend/state/product-variations/selectors.js
new file mode 100644
index 0000000..80ab0e4
--- /dev/null
+++ b/src/frontend/state/product-variations/selectors.js
@@ -0,0 +1 @@
+export const allVariations = (state) => state.productVariationsInfo.allProductVariationsInfo
\ No newline at end of file
diff --git a/src/frontend/state/products/actions.js b/src/frontend/state/products/actions.js
new file mode 100644
index 0000000..cacdab0
--- /dev/null
+++ b/src/frontend/state/products/actions.js
@@ -0,0 +1,6 @@
+import * as actionTypes from '../action-types';
+
+export const updateProductInfo = (productInfo) => ({
+ type: actionTypes.PRODUCTS_SET_PRODUCT,
+ payload: productInfo,
+});
diff --git a/src/frontend/state/products/reducer.js b/src/frontend/state/products/reducer.js
new file mode 100644
index 0000000..f262d83
--- /dev/null
+++ b/src/frontend/state/products/reducer.js
@@ -0,0 +1,19 @@
+import * as actionTypes from '../action-types';
+
+const initialState = {
+ allProductInfo: [],
+};
+
+const reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case actionTypes.PRODUCTS_SET_PRODUCT:
+ return {
+ ...state,
+ allProductInfo: action.payload,
+ };
+ default:
+ return state;
+ }
+};
+
+export default reducer;
diff --git a/src/frontend/state/products/selectors.js b/src/frontend/state/products/selectors.js
new file mode 100644
index 0000000..cc71d35
--- /dev/null
+++ b/src/frontend/state/products/selectors.js
@@ -0,0 +1 @@
+export const allProducts = (state) => state.productInfo.allProductInfo
diff --git a/src/frontend/state/reducer.js b/src/frontend/state/reducer.js
index e3e9d81..5def4ba 100644
--- a/src/frontend/state/reducer.js
+++ b/src/frontend/state/reducer.js
@@ -5,6 +5,8 @@ import teamsReducer from './teams/reducer';
import sponsorsReducer from './sponsors/reducer';
import geeseInfoReducer from './geese-info/reducer';
import blogInfoReducer from './blogs/reducer';
+import productInfoReducer from './products/reducer';
+import productVariationInfoReducer from './product-variations/reducer'
export default combineReducers({
user: userReducer,
@@ -12,5 +14,7 @@ export default combineReducers({
teams: teamsReducer,
sponsors: sponsorsReducer,
geeseInfo: geeseInfoReducer,
- blogInfo: blogInfoReducer
+ blogInfo: blogInfoReducer,
+ productInfo: productInfoReducer,
+ productVariationsInfo: productVariationInfoReducer
});