From 96b23a02f0c5050c51159a42cf28d71e7aee702a Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Mon, 16 Oct 2023 22:52:33 -0400 Subject: [PATCH 01/31] initialized cms frontend merch api --- src/frontend/api/index.js | 4 +++- src/frontend/api/products.js | 26 ++++++++++++++++++++++++++ src/frontend/hooks/products-form.js | 0 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/frontend/api/products.js create mode 100644 src/frontend/hooks/products-form.js diff --git a/src/frontend/api/index.js b/src/frontend/api/index.js index 85e794e..2e529a2 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 products from './products' 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), + products: products(server) }; diff --git a/src/frontend/api/products.js b/src/frontend/api/products.js new file mode 100644 index 0000000..6c5c67b --- /dev/null +++ b/src/frontend/api/products.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`); +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), + addProductaddProductVariation: addProductaddProductVariation(server), + addProductVariation: addProductVariation(server), + deleteProduct: deleteProduct(server), + deleteProductVariation: deleteProductVariation(server), +}); diff --git a/src/frontend/hooks/products-form.js b/src/frontend/hooks/products-form.js new file mode 100644 index 0000000..e69de29 From 9871af47ad2ae6bfcce8957ccfdc569851f8ae20 Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Thu, 19 Oct 2023 21:53:44 -0400 Subject: [PATCH 02/31] Add product action to state --- src/frontend/state/action-types.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontend/state/action-types.js b/src/frontend/state/action-types.js index aa242d3..afab064 100644 --- a/src/frontend/state/action-types.js +++ b/src/frontend/state/action-types.js @@ -14,4 +14,6 @@ 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' \ No newline at end of file From 57d782ffb4db0a19207fb04b2820df3e5d650f5e Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Thu, 19 Oct 2023 21:56:06 -0400 Subject: [PATCH 03/31] Added state reducer and selector for products --- src/frontend/state/products/actions.js | 6 ++++++ src/frontend/state/products/reducer.js | 19 +++++++++++++++++++ src/frontend/state/products/selectors.js | 1 + 3 files changed, 26 insertions(+) create mode 100644 src/frontend/state/products/actions.js create mode 100644 src/frontend/state/products/reducer.js create mode 100644 src/frontend/state/products/selectors.js 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..3fe0e92 --- /dev/null +++ b/src/frontend/state/products/selectors.js @@ -0,0 +1 @@ +export const allProductInfo = (state) => state.productInfo.allProductInfo; From 9c78245d299edb49457670de6abeb66ef3b00cfa Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Thu, 19 Oct 2023 22:10:15 -0400 Subject: [PATCH 04/31] Add products hook and fixed api typo --- src/frontend/api/products.js | 2 +- src/frontend/hooks/products.js | 29 ++++++++++++++++++++++++ src/frontend/state/products/reducer.js | 4 ++-- src/frontend/state/products/selectors.js | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/frontend/hooks/products.js diff --git a/src/frontend/api/products.js b/src/frontend/api/products.js index 6c5c67b..6b69a87 100644 --- a/src/frontend/api/products.js +++ b/src/frontend/api/products.js @@ -19,7 +19,7 @@ export default (server) => ({ getProductVariations: getProductVariations(server), updateProduct: updateProduct(server), updateProductVariation:updateProductVariation(server), - addProductaddProductVariation: addProductaddProductVariation(server), + addProduct: addProduct(server), addProductVariation: addProductVariation(server), deleteProduct: deleteProduct(server), deleteProductVariation: deleteProductVariation(server), diff --git a/src/frontend/hooks/products.js b/src/frontend/hooks/products.js new file mode 100644 index 0000000..a96f370 --- /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.products.getProducts(); + + const newProducts = productsResponse.data; + + dispatch(productActions.updateProductInfo(newProducts)); + } catch (err) { + console.error(err); + } + })(); + }, [dispatch]); + + return { + geeseInfo, + }; +}; + +export default useProducts; diff --git a/src/frontend/state/products/reducer.js b/src/frontend/state/products/reducer.js index f262d83..e4ef9aa 100644 --- a/src/frontend/state/products/reducer.js +++ b/src/frontend/state/products/reducer.js @@ -1,7 +1,7 @@ import * as actionTypes from '../action-types'; const initialState = { - allProductInfo: [], + allProducts: [], }; const reducer = (state = initialState, action) => { @@ -9,7 +9,7 @@ const reducer = (state = initialState, action) => { case actionTypes.PRODUCTS_SET_PRODUCT: return { ...state, - allProductInfo: action.payload, + allProducts: action.payload, }; default: return state; diff --git a/src/frontend/state/products/selectors.js b/src/frontend/state/products/selectors.js index 3fe0e92..004fe1b 100644 --- a/src/frontend/state/products/selectors.js +++ b/src/frontend/state/products/selectors.js @@ -1 +1 @@ -export const allProductInfo = (state) => state.productInfo.allProductInfo; +export const allProducts = (state) => state.products.allProducts \ No newline at end of file From be110c0c8f8c7566bcddf56b459382610226ad12 Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Thu, 19 Oct 2023 22:23:09 -0400 Subject: [PATCH 05/31] Fixed product state functions --- src/frontend/state/products/reducer.js | 4 ++-- src/frontend/state/products/selectors.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/state/products/reducer.js b/src/frontend/state/products/reducer.js index e4ef9aa..f262d83 100644 --- a/src/frontend/state/products/reducer.js +++ b/src/frontend/state/products/reducer.js @@ -1,7 +1,7 @@ import * as actionTypes from '../action-types'; const initialState = { - allProducts: [], + allProductInfo: [], }; const reducer = (state = initialState, action) => { @@ -9,7 +9,7 @@ const reducer = (state = initialState, action) => { case actionTypes.PRODUCTS_SET_PRODUCT: return { ...state, - allProducts: action.payload, + allProductInfo: action.payload, }; default: return state; diff --git a/src/frontend/state/products/selectors.js b/src/frontend/state/products/selectors.js index 004fe1b..bebd62c 100644 --- a/src/frontend/state/products/selectors.js +++ b/src/frontend/state/products/selectors.js @@ -1 +1 @@ -export const allProducts = (state) => state.products.allProducts \ No newline at end of file +export const allProducts = (state) => state.productInfo.allProductInfo \ No newline at end of file From 6b811c5511630ca9661cfc529b9b5a89ba5a7532 Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Thu, 19 Oct 2023 22:42:29 -0400 Subject: [PATCH 06/31] add product info reducer --- src/frontend/state/reducer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontend/state/reducer.js b/src/frontend/state/reducer.js index e3e9d81..8dd34d8 100644 --- a/src/frontend/state/reducer.js +++ b/src/frontend/state/reducer.js @@ -5,6 +5,7 @@ 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'; export default combineReducers({ user: userReducer, @@ -12,5 +13,6 @@ export default combineReducers({ teams: teamsReducer, sponsors: sponsorsReducer, geeseInfo: geeseInfoReducer, - blogInfo: blogInfoReducer + blogInfo: blogInfoReducer, + productInfo: productInfoReducer }); From 5b46745e902f443e93b106733fe4ae3e06da7857 Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Fri, 20 Oct 2023 22:28:35 -0400 Subject: [PATCH 07/31] Added products-form hook --- src/frontend/hooks/products-form.js | 117 ++++++++++++++++++++++++++++ src/frontend/hooks/products.js | 4 +- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/frontend/hooks/products-form.js b/src/frontend/hooks/products-form.js index e69de29..30e9e76 100644 --- a/src/frontend/hooks/products-form.js +++ b/src/frontend/hooks/products-form.js @@ -0,0 +1,117 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useHistory, useParams } from 'react-router-dom'; +import useProducts from '../hooks/products' +import api from '../api'; +import moment from 'moment'; + +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 { + 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); + console.log(res.data) + + const productInfo = { + name: productName, + description, + category + } + + if (productName && description && category) { + if (params.productId) { + await api.products.updateProduct(params.productId, productInfo); + } + + else { + const res = await api.products.addProduct(productInfo); + console.log(res.data) + } + + } + else { + throw new Error('Please fill all the required fields.'); + } + // onSuccess: + closeForm(); + } catch (e) { + // TODO: Display "could not add/update" error to user as dialogue. + // eslint-disable-next-line no-console + + console.error(e); + } + }, [params, productName, description, category, closeForm]); + + const deleteForm = useCallback(async () => { + if (params.productId) { + await api.products.deleteProduct(params.productId); + } + closeForm(); + }, [params.productId, closeForm]); + + const getLastUpdated = useCallback(() => { + const product = products.find((product) => product.id === parseInt(params.productId, 10)); + if (product) { + return moment.utc(product.updatedAt).local().format('MMMM D, YYYY'); + } + return ''; + }, [products, params.productId]); + + return { + productName, + setProductName, + description, + setDescription, + category, + setCategory, + closeForm, + saveForm, + deleteForm, + getLastUpdated, + showModal, + openModal, + closeModal, + }; +}; + +export default useProductsForm; diff --git a/src/frontend/hooks/products.js b/src/frontend/hooks/products.js index a96f370..9e51ec6 100644 --- a/src/frontend/hooks/products.js +++ b/src/frontend/hooks/products.js @@ -19,10 +19,10 @@ const useProducts = () => { console.error(err); } })(); - }, [dispatch]); + }, [dispatch]); return { - geeseInfo, + products, }; }; From 7899b9fcb116411cfa272734c1d3b9d7d4b1d6de Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Fri, 27 Oct 2023 16:57:15 -0400 Subject: [PATCH 08/31] Init merch page --- .../pages/merch-products/ProductsPage.js | 117 ++++++++++++++++++ src/frontend/state/products/selectors.js | 2 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/frontend/pages/merch-products/ProductsPage.js diff --git a/src/frontend/pages/merch-products/ProductsPage.js b/src/frontend/pages/merch-products/ProductsPage.js new file mode 100644 index 0000000..168725c --- /dev/null +++ b/src/frontend/pages/merch-products/ProductsPage.js @@ -0,0 +1,117 @@ +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 * as moment from 'moment'; +import { useHistory } from 'react-router-dom'; + +const Button = styled(UnstyledButton)``; + +const Container = styled.div` + margin: ${(props) => props.theme.pageMargin}; +`; + +const CurrentProductHeader = styled.p` + font: ${({ theme }) => theme.fonts.medium24}; +`; + +const ButtonContainer = styled.div` + ${Button} { + margin-right: 28px; + box-shadow: ${({ theme }) => theme.shadows.shadow1}; + } +`; + +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 currentGoose = 'Goose V'; + + const geese = geeseInfo?.map((goose) => ({ + id: goose.id, + name: goose.name, + updatedAt: moment.utc(goose.updatedAt).local().format('MMMM D, YYYY'), + })); + + const onEdit = useCallback(() => { + // TODO: Implement functionality + console.log('Go to edit'); + }, []); + + const onPreview = useCallback(() => { + // TODO: Implement functionality + console.log('Go to preview'); + }, []); + + const addGoose = useCallback(() => { + history.push('/geese/add'); + }, [history]); + + const onEditGoose = useCallback( + (id) => { + history.push(`/geese/${id}`); + }, + [history], + ); + + return ( + + + Current Goose: {currentGoose} + + + + + + + + + + + + + + + + + + + + + + + + +/* + + + + + + + + + + + + + + + + +{/* + + + + + The images can be rearranged by dragging them into the desired + order. + + + {displayImages} + + + + + */} + + + + + + Product: {productName} + + + + + + + + + + + + + + + +{/* + + + + + The images can be rearranged by dragging them into the desired + order. + + + {displayImages} + + + + + */} + + + + + + + + + + + + + + + + + + + + + + + + +/* + + + + + + + + + + + + + + + + +{/* + + + + + The images can be rearranged by dragging them into the desired + order. + + + {displayImages} + + + + + */} + + + + + + Product: {productName} + + + + + + + + + + + + + + + +{/* + + + + + The images can be rearranged by dragging them into the desired + order. + + + {displayImages} + + + + + */} + + + -/* + @@ -190,7 +141,7 @@ const AddProductVariation = ({ add }) => { /> - + {displayImages} diff --git a/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js b/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js index c4b8b92..e246344 100644 --- a/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js +++ b/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js @@ -29,13 +29,6 @@ const VariationImages = styled.div` margin-bottom: 60px; `; -const ImagesText = styled.div` - font-family: IBM Plex Sans; - font-size: 18px; - color: #232535; - margin: 0; -`; - const ImageCard = styled.div` margin-right: 18px; `; @@ -125,7 +118,7 @@ const EditProductVariation = ({ add }) => { ]}; - }, [picture, pictureUrl, imageURLDelete, imageDelete, setPicture, setPictureURL]); + }, [picture, pictureUrl]); return ( @@ -168,7 +161,7 @@ const EditProductVariation = ({ add }) => { - + {displayImages} diff --git a/src/frontend/pages/merch-products/edit-a-product/EditProduct.js b/src/frontend/pages/merch-products/edit-a-product/EditProduct.js index dcc1532..5d37e28 100644 --- a/src/frontend/pages/merch-products/edit-a-product/EditProduct.js +++ b/src/frontend/pages/merch-products/edit-a-product/EditProduct.js @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import FormContainer from '../../../components/FormContainer/index'; import Button from '../../../components/Button/index'; import TextInput from '../../../components/TextInput/index'; From 7403e520bfc94a180b5711b3c8ec98c8f6fc91f4 Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Sun, 4 Feb 2024 00:43:20 -0500 Subject: [PATCH 26/31] streamlined products and variations in a flow --- src/frontend/hooks/product-variations-form.js | 167 +++++++++++------- src/frontend/hooks/product-variations.js | 46 +++-- src/frontend/hooks/products-form.js | 6 + src/frontend/hooks/products.js | 2 +- .../Variations.router.js | 6 +- .../VariationsPage.js | 23 ++- .../add-a-variation/AddVariation.js | 12 +- .../edit-a-product/EditProduct.js | 4 + 8 files changed, 155 insertions(+), 111 deletions(-) diff --git a/src/frontend/hooks/product-variations-form.js b/src/frontend/hooks/product-variations-form.js index 41cd80a..be5cbce 100644 --- a/src/frontend/hooks/product-variations-form.js +++ b/src/frontend/hooks/product-variations-form.js @@ -1,18 +1,18 @@ import { useState, useEffect, useCallback } from 'react'; import { useHistory, useParams } from 'react-router-dom'; -import useProductVariations from '../hooks/product-variations' -import useProducts from '../hooks/products' +import useProductVariations from '../hooks/product-variations'; +import useProducts from '../hooks/products'; import api from '../api'; import moment from 'moment'; const useProductVariationsForm = () => { - const [productName, setProductName] = useState('') - const [productId, setProductId] = useState(null) + 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 [pictureUrl, setPictureURL] = useState(null); const [showModal, setShowModal] = useState(false); const history = useHistory(); const params = useParams(); @@ -20,45 +20,52 @@ const useProductVariationsForm = () => { const { productVariations } = useProductVariations(); useEffect(() => { - if (productVariations.length > 0 && products.length > 0) { - (async () => { - try { - if (params.variationId) { - const productVariationId = parseInt(params.variationId, 10); - const variation = productVariations.find((variation) => variation.id === productVariationId); - - if (variation) { - const product = await products.find(product => product.id === variation.productId); - setProductName(product.name); - setProductId(product.id); - setVariationName(variation.variationName); - setPrice(variation.price); - setStock(variation.stock); - setPictureURL(variation.picture); - } + (async () => { + try { + let variation = {}; + let product = {}; + if (params.variationId) { + const productVariationId = parseInt(params.variationId, 10); + variation = productVariations.find( + (variation) => variation.id === productVariationId, + ); + + if (variation) { + product = await products.find( + (product) => product.id === variation.productId, + ); } - } catch (err) { - console.log(err); } - })(); - } - }, [productVariations, products, params.variationId]); + if (params.productId) { + product = await products.find( + (product) => product.id == params.productId, + ); + } + setProductName(product.name); + setProductId(product.id); + setVariationName(variation.variationName); + setPrice(variation.price); + setStock(variation.stock); + setPictureURL(variation.picture); + } catch (err) { + console.log(err); + } + })(); + }, [productVariations, products, params.variationId, params.productId]); const imageUpload = (image) => { - setPicture(image) - setPictureURL(URL.createObjectURL(image)) - } + setPicture(image); + setPictureURL(URL.createObjectURL(image)); + }; - const imageDelete = useCallback(() => { - setPicture(null) - setPictureURL(null) - }, [setPicture]) + const imageDelete = useCallback(() => { + setPicture(null); + setPictureURL(null); + }, [setPicture]); - - const imageURLDelete = useCallback(() => { - setPictureURL(null) - }, [setPicture, setPictureURL, pictureUrl]) - + const imageURLDelete = useCallback(() => { + setPictureURL(null); + }, [setPicture, setPictureURL, pictureUrl]); const openModal = () => { setShowModal(true); @@ -69,24 +76,24 @@ const useProductVariationsForm = () => { }; const closeForm = useCallback(() => { - history.push('/variations'); - }, [history]); + history.push(`/variations/${productId}`); + }, [history, productId]); const saveForm = useCallback(async () => { try { - let newPictureUrl = picture; + let newPictureUrl = pictureUrl; if (picture instanceof File) { const data = new FormData(); data.append('files', picture, picture.name); await api.formUpload(data); - newPictureUrl = pictureUrl.replace("blob:","") + newPictureUrl = pictureUrl.replace('blob:', ''); } - + const currentDateTime = new Date(); const unixTimestamp = currentDateTime.getTime(); - + const productVariationInfo = { variationName, productId, @@ -95,12 +102,19 @@ const useProductVariationsForm = () => { picture: newPictureUrl, lastUpdated: unixTimestamp, }; - + if (variationName && price && stock && picture) { if (params.variationId) { - await api.products.updateProductVariation(params.variationId, productId, productVariationInfo); + await api.products.updateProductVariation( + params.variationId, + productId, + productVariationInfo, + ); } else { - await api.products.addProductVariation(productVariationInfo); + await api.products.addProductVariation({ + ...productVariationInfo, + productId, + }); } setPicture(picture); } else { @@ -112,22 +126,37 @@ const useProductVariationsForm = () => { // TODO: Display "could not add/update" error to the user as dialogue. console.error(e); } - }, [params, variationName, price, stock, closeForm, picture, productId]); - + }, [ + 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); - + const variation = productVariations.find( + (variation) => variation.id === productVariationId, + ); + if (variation) { - const product = products.find(product => product.id === variation.productId); - + const product = products.find( + (product) => product.id === variation.productId, + ); + if (product) { const productId = product.id; - await api.products.deleteProductVariation(productId, params.variationId); - } + await api.products.deleteProductVariation( + productId, + params.variationId, + ); + } } } closeForm(); @@ -138,7 +167,9 @@ const useProductVariationsForm = () => { }, [params, productVariations, products, closeForm]); const getLastUpdated = useCallback(() => { - const variation = productVariations.find((variation) => variation.id === parseInt(params.variationId, 10)); + const variation = productVariations.find( + (variation) => variation.id === parseInt(params.variationId, 10), + ); if (variation) { return moment.utc(variation.updatedAt).local().format('MMMM D, YYYY'); } @@ -146,22 +177,26 @@ const useProductVariationsForm = () => { }, [productVariations, params.variationId]); const getProductNames = useCallback(() => { - const uniqueProductNames = [...new Set(products.map((product) => ({ text: product.name, id: product.id })))]; - + const uniqueProductNames = [ + ...new Set( + products.map((product) => ({ text: product.name, id: product.id })), + ), + ]; + return uniqueProductNames; }, [productVariations]); - - const setVariationIdGivenName = useCallback((productName) => { - const varId = productVariations.find((variation) => variation.productName == productName).productId - - setProductId(varId) - }) + const setVariationIdGivenName = useCallback((productName) => { + const varId = productVariations.find( + (variation) => variation.productName == productName, + ).productId; + setProductId(varId); + }); return { - productName, + productName, setProductName, - productId, + productId, setProductId, variationName, setVariationName, diff --git a/src/frontend/hooks/product-variations.js b/src/frontend/hooks/product-variations.js index 9c0d16b..a400463 100644 --- a/src/frontend/hooks/product-variations.js +++ b/src/frontend/hooks/product-variations.js @@ -1,41 +1,51 @@ 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); + const productVariations = useSelector( + productVariationSelectors.allVariations, + ); useEffect(() => { (async () => { try { - // get all products - const productsResponse = await api.products.getProducts(); - const products = productsResponse.data; - - // array of all unique product ids - const uniqueProductIds = [...new Set(products.map(product => product.id))]; - let newProductVariations = []; + if (params.productId) { + const productVariationsResponse = + await api.products.getProductVariations(params.productId); + // get all products + const productsResponse = await api.products.getProducts(); + const products = productsResponse.data; - // fetch all product variations for ids - for (const productId of uniqueProductIds) { - const productVariationsResponse = await api.products.getProductVariations(productId); - const productName = products.find((product) => product.id === productId)?.name; + const productName = products.find( + (product) => product.id == params.productId, + ).name; - for (const productVariation of productVariationsResponse.data){ - const variation = {...productVariation, productName}; + for (const productVariation of productVariationsResponse.data) { + const variation = { ...productVariation, productName }; newProductVariations = [...newProductVariations, variation]; } - } - dispatch(productVariationActions.updateProductVariationsInfo(newProductVariations)); + + dispatch( + productVariationActions.updateProductVariationsInfo( + newProductVariations, + ), + ); } catch (err) { - console.error("error fetching products in product-variations.js: ", err); + console.error( + 'error fetching products in product-variations.js: ', + err, + ); } })(); - }, [dispatch]); + }, [dispatch, params.productId, params.variationId]); return { productVariations, diff --git a/src/frontend/hooks/products-form.js b/src/frontend/hooks/products-form.js index e2a6d94..d1d2eaa 100644 --- a/src/frontend/hooks/products-form.js +++ b/src/frontend/hooks/products-form.js @@ -100,6 +100,11 @@ const useProductsForm = () => { return ''; }, [products, params.productId]); + + const openVariations = useCallback(() => { + history.push(`/variations/${params.productId}`) + }, [history]) + return { productName, setProductName, @@ -112,6 +117,7 @@ const useProductsForm = () => { deleteForm, getLastUpdated, showModal, + openVariations, openModal, closeModal, }; diff --git a/src/frontend/hooks/products.js b/src/frontend/hooks/products.js index 9e51ec6..8e38c26 100644 --- a/src/frontend/hooks/products.js +++ b/src/frontend/hooks/products.js @@ -19,7 +19,7 @@ const useProducts = () => { console.error(err); } })(); - }, [dispatch]); + }, [dispatch]); return { products, diff --git a/src/frontend/pages/merch-product-variations/Variations.router.js b/src/frontend/pages/merch-product-variations/Variations.router.js index cdb8085..e13140e 100644 --- a/src/frontend/pages/merch-product-variations/Variations.router.js +++ b/src/frontend/pages/merch-product-variations/Variations.router.js @@ -8,13 +8,13 @@ const VariationsRouter = () => { const { path } = useRouteMatch(); return ( - + - + - + diff --git a/src/frontend/pages/merch-product-variations/VariationsPage.js b/src/frontend/pages/merch-product-variations/VariationsPage.js index 01e9f5e..636e7e1 100644 --- a/src/frontend/pages/merch-product-variations/VariationsPage.js +++ b/src/frontend/pages/merch-product-variations/VariationsPage.js @@ -35,10 +35,6 @@ const TableHeader = styled.p` `; const headers = [ - { - id:'productName', - value:"Product" - }, { id: 'variationName', value: 'Variation', @@ -57,10 +53,9 @@ const headers = [ } ]; -const RowComponent = ({productName, variationName, price, stock, lastUpdated }) => ( +const RowComponent = ({variationName, price, stock, lastUpdated }) => ( <> - {productName} {variationName} ${price.toFixed(2)} {stock} @@ -70,26 +65,30 @@ const RowComponent = ({productName, variationName, price, stock, lastUpdated }) const VariationsPage = () => { const { productVariations } = useProductVariations(); + + const productId = productVariations[0]?.productId + const history = useHistory(); const rowVariation = productVariations?.map((variation) => ({ id: variation.id, - productName: variation.productName, 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 addVariation = useCallback(() => { - history.push('/variations/add'); - }, [history]); + history.push(`/variations/add/${productId}`); + }, [history, productId]); const onEditVariation = useCallback( (variationId) => { - history.push(`/variations/edit/${variationId}/`); + history.push(`/variations/${productId}/edit/${variationId}/`); }, - [history], + [history, productId], ); return ( @@ -97,7 +96,7 @@ const VariationsPage = () => { Merch Product Variations - All Variations + {productName ? `${productName} Variations`: ''} + From dbb43734d8a5207c9de7ef34ce809dc911881bcc Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Mon, 5 Feb 2024 21:45:36 -0500 Subject: [PATCH 27/31] remove unnecessary imports --- src/frontend/hooks/product-variations-form.js | 19 +++++++++++-------- src/frontend/pages/landing/LandingPage.js | 6 ------ .../edit-a-variation/EditVariation.js | 3 --- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/frontend/hooks/product-variations-form.js b/src/frontend/hooks/product-variations-form.js index be5cbce..fb2785a 100644 --- a/src/frontend/hooks/product-variations-form.js +++ b/src/frontend/hooks/product-variations-form.js @@ -24,23 +24,26 @@ const useProductVariationsForm = () => { try { let variation = {}; let product = {}; - if (params.variationId) { + + if (params.productId) { + product = await products.find( + (product) => product.id == params.productId, + ); + } + + else if (params.variationId) { const productVariationId = parseInt(params.variationId, 10); variation = productVariations.find( (variation) => variation.id === productVariationId, ); if (variation) { - product = await products.find( + product = products.find( (product) => product.id === variation.productId, ); } } - if (params.productId) { - product = await products.find( - (product) => product.id == params.productId, - ); - } + setProductName(product.name); setProductId(product.id); setVariationName(variation.variationName); @@ -48,7 +51,7 @@ const useProductVariationsForm = () => { setStock(variation.stock); setPictureURL(variation.picture); } catch (err) { - console.log(err); + console.error('Error init\'ing product variation form info', err); } })(); }, [productVariations, products, params.variationId, params.productId]); diff --git a/src/frontend/pages/landing/LandingPage.js b/src/frontend/pages/landing/LandingPage.js index 8b2b54d..7e1b7d7 100644 --- a/src/frontend/pages/landing/LandingPage.js +++ b/src/frontend/pages/landing/LandingPage.js @@ -69,12 +69,6 @@ const sections = [ previewLink: 'https://teamwaterloop.ca/products', icon: MerchPageSVG, }, - { - name: 'Merch Store Variations', - editLink: '/variations', - previewLink: 'https://teamwaterloop.ca/variations', - icon: MerchPageSVG, - }, ]; const LandingPage = () => { diff --git a/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js b/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js index e246344..85393f8 100644 --- a/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js +++ b/src/frontend/pages/merch-product-variations/edit-a-variation/EditVariation.js @@ -89,12 +89,9 @@ const EditProductVariation = ({ add }) => { price, setPrice, picture, - setPicture, pictureUrl, - setPictureURL, imageUpload, imageDelete, - imageURLDelete, stock, setStock, closeForm, From e9c11bdcb2c961bdc1ba4f4e5b845c9abc7ff012 Mon Sep 17 00:00:00 2001 From: Kevin Lau Date: Wed, 7 Feb 2024 21:33:25 -0500 Subject: [PATCH 28/31] cleanup products and variations --- src/frontend/App.js | 6 --- src/frontend/hooks/product-variations-form.js | 20 ++++----- src/frontend/hooks/products-form.js | 42 +++++++++---------- src/frontend/pages/landing/LandingPage.js | 2 +- .../Variations.router.js | 23 ---------- .../VariationsPage.js | 42 +++++++++++-------- .../pages/merch-products/Products.router.js | 15 ++++++- .../pages/merch-products/ProductsPage.js | 2 +- 8 files changed, 69 insertions(+), 83 deletions(-) delete mode 100644 src/frontend/pages/merch-product-variations/Variations.router.js diff --git a/src/frontend/App.js b/src/frontend/App.js index ce6e7e8..6483fd8 100644 --- a/src/frontend/App.js +++ b/src/frontend/App.js @@ -14,7 +14,6 @@ import SponsorsRouter from './pages/sponsors/Sponsors.router'; import GeeseRouter from './pages/geese/Geese.router'; import TeamDescriptionsRouter from './pages/team-descriptions/TeamDescriptions.router'; import ProductsRouter from './pages/merch-products/Products.router'; -import VariationsRouter from './pages/merch-product-variations/Variations.router'; import { addAuthTokenToRequests } from './api/server'; import BlogsRouter from './pages/blogs/Blogs.Router'; @@ -77,11 +76,6 @@ const App = () => { - - {!token && } - - - {/* {!token && } */} diff --git a/src/frontend/hooks/product-variations-form.js b/src/frontend/hooks/product-variations-form.js index fb2785a..17eee28 100644 --- a/src/frontend/hooks/product-variations-form.js +++ b/src/frontend/hooks/product-variations-form.js @@ -31,25 +31,20 @@ const useProductVariationsForm = () => { ); } - else if (params.variationId) { + if (params.variationId) { const productVariationId = parseInt(params.variationId, 10); variation = productVariations.find( (variation) => variation.id === productVariationId, ); - - if (variation) { - product = products.find( - (product) => product.id === variation.productId, - ); - } } - 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); } @@ -79,20 +74,21 @@ const useProductVariationsForm = () => { }; const closeForm = useCallback(() => { - history.push(`/variations/${productId}`); + 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); await api.formUpload(data); - + newPictureUrl = pictureUrl.replace('blob:', ''); } + console.log(newPictureUrl) const currentDateTime = new Date(); const unixTimestamp = currentDateTime.getTime(); @@ -106,7 +102,7 @@ const useProductVariationsForm = () => { lastUpdated: unixTimestamp, }; - if (variationName && price && stock && picture) { + if (variationName && price && stock && pictureUrl) { if (params.variationId) { await api.products.updateProductVariation( params.variationId, diff --git a/src/frontend/hooks/products-form.js b/src/frontend/hooks/products-form.js index d1d2eaa..6b6b35c 100644 --- a/src/frontend/hooks/products-form.js +++ b/src/frontend/hooks/products-form.js @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react'; import { useHistory, useParams } from 'react-router-dom'; -import useProducts from '../hooks/products' +import useProducts from '../hooks/products'; import api from '../api'; import moment from 'moment'; @@ -20,11 +20,13 @@ const useProductsForm = () => { // init relevant product data when editing if (params.productId) { const productId = parseInt(params.productId, 10); - const product = products.find((product) => product.id === productId); + const product = products.find( + (product) => product.id === productId, + ); if (product) { setProductName(product.name); setDescription(product.description); - setCategory(product.category) + setCategory(product.category); } } } catch (err) { @@ -47,44 +49,39 @@ const useProductsForm = () => { }, [history]); const saveForm = useCallback(async () => { - try { - const data = new FormData(); - const res = await api.formUpload(data); - + const data = new FormData(); + const res = await api.formUpload(data); + const productInfo = { name: productName, description, - category - } - + category, + }; + // ensure all fields are filled if (productName && description && category) { // update product if it exists if (params.productId) { await api.products.updateProduct(params.productId, productInfo); - } + } // add product if it doesnt exist else { const res = await api.products.addProduct(productInfo); } - - } - else { + } 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 + // delete product if (params.productId) { await api.products.deleteProduct(params.productId); } @@ -92,18 +89,19 @@ const useProductsForm = () => { }, [params.productId, closeForm]); const getLastUpdated = useCallback(() => { - const product = products.find((product) => product.id === parseInt(params.productId, 10)); - // returns last updated time of product + const product = products.find( + (product) => product.id === parseInt(params.productId, 10), + ); + // returns last updated time of product if (product) { return moment.utc(product.updatedAt).local().format('MMMM D, YYYY'); } return ''; }, [products, params.productId]); - const openVariations = useCallback(() => { - history.push(`/variations/${params.productId}`) - }, [history]) + history.push(`/products/${params.productId}/variations`); + }, [history, params.productId]); return { productName, diff --git a/src/frontend/pages/landing/LandingPage.js b/src/frontend/pages/landing/LandingPage.js index 7e1b7d7..035f98f 100644 --- a/src/frontend/pages/landing/LandingPage.js +++ b/src/frontend/pages/landing/LandingPage.js @@ -64,7 +64,7 @@ const sections = [ icon: BlogPostsPageSVG, }, { - name: 'Merch Store Products', + name: 'Merch Store', editLink: '/products', previewLink: 'https://teamwaterloop.ca/products', icon: MerchPageSVG, diff --git a/src/frontend/pages/merch-product-variations/Variations.router.js b/src/frontend/pages/merch-product-variations/Variations.router.js deleted file mode 100644 index e13140e..0000000 --- a/src/frontend/pages/merch-product-variations/Variations.router.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { Switch, Route, useRouteMatch } from 'react-router-dom'; -import EditVariation from './edit-a-variation/EditVariation'; -import AddVariation from './add-a-variation/AddVariation.js' -import VariationsPage from './VariationsPage'; - -const VariationsRouter = () => { - const { path } = useRouteMatch(); - return ( - - - - - - - - - - - - ); -}; -export default VariationsRouter; diff --git a/src/frontend/pages/merch-product-variations/VariationsPage.js b/src/frontend/pages/merch-product-variations/VariationsPage.js index 636e7e1..eb7764d 100644 --- a/src/frontend/pages/merch-product-variations/VariationsPage.js +++ b/src/frontend/pages/merch-product-variations/VariationsPage.js @@ -5,7 +5,8 @@ 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 moment from 'moment'; +import useProductVariationsForm from '../../hooks/product-variations-form'; const Button = styled(UnstyledButton)``; @@ -49,12 +50,11 @@ const headers = [ }, { id: 'lastUpdated', - value: 'Last Updated' - } + value: 'Last Updated', + }, ]; -const RowComponent = ({variationName, price, stock, lastUpdated }) => ( - +const RowComponent = ({ variationName, price, stock, lastUpdated }) => ( <> {variationName} ${price.toFixed(2)} @@ -65,8 +65,7 @@ const RowComponent = ({variationName, price, stock, lastUpdated }) => ( const VariationsPage = () => { const { productVariations } = useProductVariations(); - - const productId = productVariations[0]?.productId + const { productId } = useProductVariationsForm(); const history = useHistory(); @@ -75,29 +74,38 @@ const VariationsPage = () => { variationName: variation.variationName, price: variation.price, stock: variation.stock, - lastUpdated: moment.utc(variation.lastUpdated).local().format('MMMM D, YYYY'), + lastUpdated: moment + .utc(variation.lastUpdated) + .local() + .format('MMMM D, YYYY'), })); - const productName = productVariations[0]?.productName + const productName = productVariations[0]?.productName; - const addVariation = useCallback(() => { - history.push(`/variations/add/${productId}`); + const onAddVariation = useCallback(() => { + history.push(`/products/${productId}/variations/add`); }, [history, productId]); const onEditVariation = useCallback( (variationId) => { - history.push(`/variations/${productId}/edit/${variationId}/`); + history.push(`/products/${productId}/variations/edit/${variationId}/`); }, [history, productId], ); + + const onReturnToProducts = useCallback(() => { + history.push(`/products`) + }, []) + return ( - - Merch Product Variations - + Merch Product Variations + - {productName ? `${productName} Variations`: ''} - - - - - - - - - - - - - - - - - - - - - - {displayImages} - - - - - - - -