From b59160c088b528063122f345f6683c317eee5b6b Mon Sep 17 00:00:00 2001 From: Alex Efremov Date: Sun, 24 Mar 2024 20:36:26 +0700 Subject: [PATCH 1/5] Changed profile.email to profile.id as a value of the shopperId field --- react/AddProductBtn.tsx | 2 +- react/ProductSummaryWishlist.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react/AddProductBtn.tsx b/react/AddProductBtn.tsx index d9190c5..5a3f282 100644 --- a/react/AddProductBtn.tsx +++ b/react/AddProductBtn.tsx @@ -215,7 +215,7 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { if (sessionResponse) { isAuthenticated = sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - shopperId = sessionResponse?.namespaces?.profile?.email?.value ?? null + shopperId = sessionResponse?.namespaces?.profile?.id?.value ?? null localStore.setItem( 'wishlist_isAuthenticated', diff --git a/react/ProductSummaryWishlist.tsx b/react/ProductSummaryWishlist.tsx index 08faa6d..235bfba 100644 --- a/react/ProductSummaryWishlist.tsx +++ b/react/ProductSummaryWishlist.tsx @@ -77,7 +77,7 @@ const ProductSummaryList: FC = ({ if (sessionResponse) { isAuthenticated = sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - shopperId = sessionResponse?.namespaces?.profile?.email?.value ?? null + shopperId = sessionResponse?.namespaces?.profile?.id?.value ?? null localStore.setItem( 'wishlist_isAuthenticated', From 274b45b88adda6d32f49c66c4af10ec9db4bea3a Mon Sep 17 00:00:00 2001 From: Alex Efremov Date: Wed, 10 Apr 2024 16:10:00 +0700 Subject: [PATCH 2/5] Added the profile query to understand is this a PII account or not --- react/AddProductBtn.tsx | 12 +++++++++--- react/ProductSummaryWishlist.tsx | 14 ++++++++++++-- react/WishlistAdmin.tsx | 4 ++-- react/queries/profile.gql | 5 +++++ 4 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 react/queries/profile.gql diff --git a/react/AddProductBtn.tsx b/react/AddProductBtn.tsx index 5a3f282..b0a1104 100644 --- a/react/AddProductBtn.tsx +++ b/react/AddProductBtn.tsx @@ -6,7 +6,7 @@ import React, { useEffect, SyntheticEvent, } from 'react' -import { useMutation, useLazyQuery } from 'react-apollo' +import { useMutation, useLazyQuery, useQuery } from 'react-apollo' import { defineMessages, useIntl } from 'react-intl' import { ProductContext } from 'vtex.product-context' import { Button, ToastContext } from 'vtex.styleguide' @@ -18,6 +18,7 @@ import { getSession } from './modules/session' import storageFactory from './utils/storage' import checkItem from './queries/checkItem.gql' import addToList from './queries/addToList.gql' +import profile from './queries/profile.gql' import removeFromList from './queries/removeFromList.gql' import styles from './styles.css' @@ -156,6 +157,9 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { const { selectedItem, product } = useContext(ProductContext) as any const sessionResponse: any = useSessionResponse() const [handleCheck, { data, loading, called }] = useLazyQuery(checkItem) + const { data: profileData } = useQuery(profile, { + fetchPolicy: 'no-cache' + }) const [productId] = String(product?.productId).split('-') const sku = product?.sku?.itemId @@ -215,7 +219,8 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { if (sessionResponse) { isAuthenticated = sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - shopperId = sessionResponse?.namespaces?.profile?.id?.value ?? null + + shopperId = !profileData?.profile?.pii? sessionResponse?.namespaces?.profile?.email?.value : sessionResponse?.namespaces?.profile?.id?.value?? null localStore.setItem( 'wishlist_isAuthenticated', @@ -353,7 +358,8 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { } if ( - data?.checkList?.inList !== true && + data?.checkList && + data.checkList.inList !== true && productCheck[productId] === undefined && wishListed.find( (item: any) => item.productId === productId && item.sku === sku diff --git a/react/ProductSummaryWishlist.tsx b/react/ProductSummaryWishlist.tsx index 235bfba..2ed09b4 100644 --- a/react/ProductSummaryWishlist.tsx +++ b/react/ProductSummaryWishlist.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState, useEffect, FC } from 'react' -import { useLazyQuery } from 'react-apollo' +import { useLazyQuery, useQuery } from 'react-apollo' // @ts-expect-error - useTreePath is a private API import { ExtensionPoint, useRuntime, useTreePath } from 'vtex.render-runtime' import { useListContext, ListContextProvider } from 'vtex.list-context' @@ -14,6 +14,7 @@ import productsQuery from './queries/productById.gql' import ViewLists from './queries/viewLists.gql' import { getSession } from './modules/session' import storageFactory from './utils/storage' +import profile from './queries/profile.gql' const localStore = storageFactory(() => sessionStorage) @@ -73,11 +74,20 @@ const ProductSummaryList: FC = ({ fetchPolicy: 'network-only', } ) + const { data: profileData } = useQuery(profile, { + fetchPolicy: 'no-cache' + }) if (sessionResponse) { isAuthenticated = sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - shopperId = sessionResponse?.namespaces?.profile?.id?.value ?? null + + if (profileData) { + shopperId = profileData.profile.pii? sessionResponse?.namespaces?.profile?.id?.value : sessionResponse?.namespaces?.profile?.email?.value + } + else { + shopperId = null + } localStore.setItem( 'wishlist_isAuthenticated', diff --git a/react/WishlistAdmin.tsx b/react/WishlistAdmin.tsx index 25275f4..ac1b224 100644 --- a/react/WishlistAdmin.tsx +++ b/react/WishlistAdmin.tsx @@ -26,7 +26,7 @@ const WishlistAdmin: FC = ({ intl }) => { const { loading } = state const downloadWishlist = (allWishlists: any) => { - const header = ['Email', 'Product ID', 'SKU', 'Title'] + const header = ['Shopper ID', 'Product ID', 'SKU', 'Title'] const data: any = [] for (const shopper of allWishlists) { @@ -34,7 +34,7 @@ const WishlistAdmin: FC = ({ intl }) => { for (const wishlist of wishlists) { for (const wishlistItem of wishlist.listItems) { const shopperData = { - Email: shopper.email, + 'Shopper ID': shopper.email, 'Product ID': wishlistItem.productId, SKU: wishlistItem.sku, Title: wishlistItem.title, diff --git a/react/queries/profile.gql b/react/queries/profile.gql new file mode 100644 index 0000000..613dd36 --- /dev/null +++ b/react/queries/profile.gql @@ -0,0 +1,5 @@ +query Profile { + profile @context(provider: "vtex.store-graphql") { + pii + } +} From 97bd4af7a5cfec0087f4eef59755557ca74dbd97 Mon Sep 17 00:00:00 2001 From: Alex Efremov Date: Wed, 10 Apr 2024 18:21:03 +0700 Subject: [PATCH 3/5] Performance improvements --- react/AddProductBtn.tsx | 38 +++++++------- react/ProductSummaryWishlist.tsx | 86 ++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/react/AddProductBtn.tsx b/react/AddProductBtn.tsx index b0a1104..057891e 100644 --- a/react/AddProductBtn.tsx +++ b/react/AddProductBtn.tsx @@ -153,13 +153,11 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { const { navigate, history, route, account } = useRuntime() const { push } = usePixel() const handles = useCssHandles(CSS_HANDLES) - const { showToast } = useContext(ToastContext) + const { showToast } = useContext(ToastContext) as any const { selectedItem, product } = useContext(ProductContext) as any const sessionResponse: any = useSessionResponse() const [handleCheck, { data, loading, called }] = useLazyQuery(checkItem) - const { data: profileData } = useQuery(profile, { - fetchPolicy: 'no-cache' - }) + const { data: profileData } = useQuery(profile) const [productId] = String(product?.productId).split('-') const sku = product?.sku?.itemId @@ -216,23 +214,25 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { toastMessage('addProductFail', toastURL) } - if (sessionResponse) { - isAuthenticated = - sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - - shopperId = !profileData?.profile?.pii? sessionResponse?.namespaces?.profile?.email?.value : sessionResponse?.namespaces?.profile?.id?.value?? null - - localStore.setItem( - 'wishlist_isAuthenticated', - JSON.stringify(isAuthenticated) - ) - localStore.setItem('wishlist_shopperId', String(shopperId)) - if (!isAuthenticated && !shopperId) { - if (localStore.getItem('wishlist_wishlisted')) { - localStore.removeItem('wishlist_wishlisted') + useEffect(() => { + if (sessionResponse) { + isAuthenticated = + sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' + + shopperId = !profileData?.profile?.pii? sessionResponse?.namespaces?.profile?.email?.value : sessionResponse?.namespaces?.profile?.id?.value?? null + + localStore.setItem( + 'wishlist_isAuthenticated', + JSON.stringify(isAuthenticated) + ) + localStore.setItem('wishlist_shopperId', String(shopperId)) + if (!isAuthenticated && !shopperId) { + if (localStore.getItem('wishlist_wishlisted')) { + localStore.removeItem('wishlist_wishlisted') + } } } - } + }, [sessionResponse, profileData]) const { isWishlistPage } = state diff --git a/react/ProductSummaryWishlist.tsx b/react/ProductSummaryWishlist.tsx index 2ed09b4..38b4211 100644 --- a/react/ProductSummaryWishlist.tsx +++ b/react/ProductSummaryWishlist.tsx @@ -74,53 +74,63 @@ const ProductSummaryList: FC = ({ fetchPolicy: 'network-only', } ) - const { data: profileData } = useQuery(profile, { - fetchPolicy: 'no-cache' - }) + const { data: profileData } = useQuery(profile) - if (sessionResponse) { - isAuthenticated = - sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' + useEffect(() => { + if (sessionResponse) { + isAuthenticated = + sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - if (profileData) { - shopperId = profileData.profile.pii? sessionResponse?.namespaces?.profile?.id?.value : sessionResponse?.namespaces?.profile?.email?.value - } - else { - shopperId = null + if (profileData) { + shopperId = profileData.profile.pii? sessionResponse?.namespaces?.profile?.id?.value : sessionResponse?.namespaces?.profile?.email?.value + } + else { + shopperId = null + } + + localStore.setItem( + 'wishlist_isAuthenticated', + JSON.stringify(isAuthenticated) + ) + localStore.setItem('wishlist_shopperId', String(shopperId)) + if (!listCalled && !!shopperId) { + loadLists({ + variables: { + shopperId, + }, + }) + } } + }, [sessionResponse, profileData, listCalled, shopperId]) + + const [productList, setProductList] = useState([]) - localStore.setItem( - 'wishlist_isAuthenticated', - JSON.stringify(isAuthenticated) + useEffect(() => { + console.log('setProductList useEffect') + setProductList( + dataLists?.viewLists[0]?.data.map((item: any) => { + const [id] = item.productId.split('-') + return { + productId: id, + sku: item.sku, + } + }) ?? [] ) - localStore.setItem('wishlist_shopperId', String(shopperId)) - if (!listCalled && !!shopperId) { - loadLists({ + }, [dataLists]) + + useEffect(() => { + console.log('loadProducts useEffect') + if (!called && dataLists && productList) { + const ids = productList.map((item: any) => item.productId) + localStore.setItem('wishlist_wishlisted', JSON.stringify(productList)) + loadProducts({ variables: { - shopperId, + ids, }, }) } - } - let productList = [] as any - productList = - dataLists?.viewLists[0]?.data.map((item: any) => { - const [id] = item.productId.split('-') - return { - productId: id, - sku: item.sku, - } - }) ?? [] - if (!called && dataLists && productList) { - const ids = productList.map((item: any) => item.productId) - localStore.setItem('wishlist_wishlisted', JSON.stringify(productList)) - loadProducts({ - variables: { - ids, - }, - }) - } - + }, [called, dataLists, productList]) + const { productsByIdentifier: products } = data || {} const newListContextValue = useMemo(() => { From d0c0476c2d5141092c23646d7a50d1d17a83d319 Mon Sep 17 00:00:00 2001 From: Alex Efremov Date: Thu, 18 Apr 2024 10:30:40 +0700 Subject: [PATCH 4/5] add ssr: false to useQuery; remove previously added useEffect hooks; remove console.logs. --- react/AddProductBtn.tsx | 36 ++++++------- react/ProductSummaryWishlist.tsx | 86 ++++++++++++++------------------ react/WishlistAdmin.tsx | 2 - 3 files changed, 56 insertions(+), 68 deletions(-) diff --git a/react/AddProductBtn.tsx b/react/AddProductBtn.tsx index 057891e..9e8b02b 100644 --- a/react/AddProductBtn.tsx +++ b/react/AddProductBtn.tsx @@ -157,7 +157,9 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { const { selectedItem, product } = useContext(ProductContext) as any const sessionResponse: any = useSessionResponse() const [handleCheck, { data, loading, called }] = useLazyQuery(checkItem) - const { data: profileData } = useQuery(profile) + const { data: profileData } = useQuery(profile, { + ssr: false + }) const [productId] = String(product?.productId).split('-') const sku = product?.sku?.itemId @@ -214,25 +216,23 @@ const AddBtn: FC = ({ toastURL = '/account/#wishlist' }) => { toastMessage('addProductFail', toastURL) } - useEffect(() => { - if (sessionResponse) { - isAuthenticated = - sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - - shopperId = !profileData?.profile?.pii? sessionResponse?.namespaces?.profile?.email?.value : sessionResponse?.namespaces?.profile?.id?.value?? null - - localStore.setItem( - 'wishlist_isAuthenticated', - JSON.stringify(isAuthenticated) - ) - localStore.setItem('wishlist_shopperId', String(shopperId)) - if (!isAuthenticated && !shopperId) { - if (localStore.getItem('wishlist_wishlisted')) { - localStore.removeItem('wishlist_wishlisted') - } + if (sessionResponse) { + isAuthenticated = + sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' + + shopperId = !profileData?.profile?.pii? sessionResponse?.namespaces?.profile?.email?.value : sessionResponse?.namespaces?.profile?.id?.value?? null + + localStore.setItem( + 'wishlist_isAuthenticated', + JSON.stringify(isAuthenticated) + ) + localStore.setItem('wishlist_shopperId', String(shopperId)) + if (!isAuthenticated && !shopperId) { + if (localStore.getItem('wishlist_wishlisted')) { + localStore.removeItem('wishlist_wishlisted') } } - }, [sessionResponse, profileData]) + } const { isWishlistPage } = state diff --git a/react/ProductSummaryWishlist.tsx b/react/ProductSummaryWishlist.tsx index 38b4211..877447c 100644 --- a/react/ProductSummaryWishlist.tsx +++ b/react/ProductSummaryWishlist.tsx @@ -74,63 +74,53 @@ const ProductSummaryList: FC = ({ fetchPolicy: 'network-only', } ) - const { data: profileData } = useQuery(profile) - - useEffect(() => { - if (sessionResponse) { - isAuthenticated = - sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' + const { data: profileData } = useQuery(profile, { + ssr: false + }) - if (profileData) { - shopperId = profileData.profile.pii? sessionResponse?.namespaces?.profile?.id?.value : sessionResponse?.namespaces?.profile?.email?.value - } - else { - shopperId = null - } + if (sessionResponse) { + isAuthenticated = + sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true' - localStore.setItem( - 'wishlist_isAuthenticated', - JSON.stringify(isAuthenticated) - ) - localStore.setItem('wishlist_shopperId', String(shopperId)) - if (!listCalled && !!shopperId) { - loadLists({ - variables: { - shopperId, - }, - }) - } + if (profileData) { + shopperId = profileData.profile.pii? sessionResponse?.namespaces?.profile?.id?.value : sessionResponse?.namespaces?.profile?.email?.value + } + else { + shopperId = null } - }, [sessionResponse, profileData, listCalled, shopperId]) - - const [productList, setProductList] = useState([]) - useEffect(() => { - console.log('setProductList useEffect') - setProductList( - dataLists?.viewLists[0]?.data.map((item: any) => { - const [id] = item.productId.split('-') - return { - productId: id, - sku: item.sku, - } - }) ?? [] + localStore.setItem( + 'wishlist_isAuthenticated', + JSON.stringify(isAuthenticated) ) - }, [dataLists]) - - useEffect(() => { - console.log('loadProducts useEffect') - if (!called && dataLists && productList) { - const ids = productList.map((item: any) => item.productId) - localStore.setItem('wishlist_wishlisted', JSON.stringify(productList)) - loadProducts({ + localStore.setItem('wishlist_shopperId', String(shopperId)) + if (!listCalled && !!shopperId) { + loadLists({ variables: { - ids, + shopperId, }, }) } - }, [called, dataLists, productList]) - + } + let productList = [] as any + productList = + dataLists?.viewLists[0]?.data.map((item: any) => { + const [id] = item.productId.split('-') + return { + productId: id, + sku: item.sku, + } + }) ?? [] + if (!called && dataLists && productList) { + const ids = productList.map((item: any) => item.productId) + localStore.setItem('wishlist_wishlisted', JSON.stringify(productList)) + loadProducts({ + variables: { + ids, + }, + }) + } + const { productsByIdentifier: products } = data || {} const newListContextValue = useMemo(() => { diff --git a/react/WishlistAdmin.tsx b/react/WishlistAdmin.tsx index ac1b224..159eb4c 100644 --- a/react/WishlistAdmin.tsx +++ b/react/WishlistAdmin.tsx @@ -108,7 +108,6 @@ const WishlistAdmin: FC = ({ intl }) => { const GetAllWishlists = async () => { setState({ ...state, loading: true }) - console.log(loading) if (!queryLoading) { const parsedData = data?.exportList @@ -152,7 +151,6 @@ const WishlistAdmin: FC = ({ intl }) => { options={options} value={selected1} onChange={(event: any) => { - console.log(event.target.value) setSelected1(event.target.value) setTimeout(()=>{refetch()},500) From 3b5850e1f97845f0c950e365b7215e9debffcb7b Mon Sep 17 00:00:00 2001 From: Alex Efremov Date: Thu, 18 Apr 2024 11:03:32 +0700 Subject: [PATCH 5/5] Add PII info to the README file --- docs/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/README.md b/docs/README.md index 1dc02b1..e7304bf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -288,6 +288,12 @@ To apply CSS customizations to this and other blocks, follow the instructions in | `wishlistIconContainer` | | `emptyMessage` | +## PII Compliance + +This app can be used in a Personal Identifiable Information (PII) compliant account. In this case, shopperId is the Profile ID. + +**Note**: If you are changing type of the account from regular to PII, you must replace the email values with profile ID values in the WishList data entity! + ## Contributors ✨