-
Notifications
You must be signed in to change notification settings - Fork 147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial commit of Salesforce Commerce Cloud app. #3633
Conversation
cdd32a5
to
02f5e0e
Compare
); | ||
}; | ||
|
||
const CategorySearchResult = (props: SearchResultProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CategorySearchResult
could be broken out into it's own file just to keep this file a bit more focused
headerHeight, | ||
} from './SearchPicker'; | ||
|
||
const CategorySearchResults = (props: SearchResultsProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it would be good to destructure the props here for consistency
headerHeight, | ||
} from './SearchPicker'; | ||
|
||
const CategorySearchResults = (props: SearchResultsProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it would be good to destructure the props here for consistency
let description = 'Description'; | ||
if (product.shortDescription.default) { | ||
description = | ||
product.shortDescription.default.markup.length > descriptionLength | ||
? `${product.shortDescription.default.markup.substring(0, descriptionLength)}...` | ||
: product.shortDescription.default.markup; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let description = 'Description'; | |
if (product.shortDescription.default) { | |
description = | |
product.shortDescription.default.markup.length > descriptionLength | |
? `${product.shortDescription.default.markup.substring(0, descriptionLength)}...` | |
: product.shortDescription.default.markup; | |
} | |
const formatShortDescription = (productMarkup?: string) => { | |
if (!productMarkup) { | |
return ''; | |
} | |
if (productMarkup.length > descriptionLength) { | |
return `${productMarkup.substring(0, descriptionLength)}...`; | |
} | |
return productMarkup; | |
} | |
const description = formatShortDescription(product.shortDescription.default?.markup) || 'Description'; |
Changing this over to a const and using a function with a return value could help make this section a little faster to read
); | ||
}; | ||
|
||
const ProductSearchResult = (props: SearchResultProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as before, I feel this would be nicer to move into it's own file for separation of concerns
stickyHeaderBreakpoint, | ||
} from './SearchPicker'; | ||
|
||
const ProductSearchResults = (props: SearchResultsProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it would be good to destructure the props here for consistency
|
||
return ( | ||
<Flex css={productWrapperStyles}> | ||
<div |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of divs, could we use <box>
and move a lot of the styling into the component?
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
const noop = () => {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
love the declared noop
} | ||
`; | ||
|
||
const controlProps = { ...props, fieldType }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
beautifully written!
@@ -0,0 +1,292 @@ | |||
import React, { useState } from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we create a SearchBar folder, and break these out into separate files? This one is a big one!
Also similar comments as earlier with consistently destructing props when you're using them in the component (eg. SearchBar doesn't need to because it's just passing props, but the subcomponents who are using them would make it easier to read the file)
const Component = useMemo(() => { | ||
for (const [location, component] of Object.entries(ComponentLocationSettings)) { | ||
if (sdk.location.is(location)) { | ||
return component; | ||
} | ||
} | ||
}, [sdk.location]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like this may be better suited for a useEffect, since we want this hooked into the react lifecycle. (You may need to tweak the typing on useState)
const Component = useMemo(() => { | |
for (const [location, component] of Object.entries(ComponentLocationSettings)) { | |
if (sdk.location.is(location)) { | |
return component; | |
} | |
} | |
}, [sdk.location]); | |
const [Component, setComponent] = useState<ReactElement>(); | |
useEffect(() => { | |
for (const [location, component] of Object.entries(ComponentLocationSettings)) { | |
if (sdk.location.is(location)) { | |
setComponent(component) | |
} | |
} | |
}, [sdk.location]); |
headerHeight, | ||
} from './SearchPicker'; | ||
|
||
const CategorySearchResults = (props: SearchResultsProps) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it would be good to destructure the props here for consistency
} | ||
}, 500); | ||
return () => clearTimeout(timeOutId); | ||
}, [fieldType, query, installParameters]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw we setup the ReactQuery provider in the App.tsx, could we use the queryClient here instead?
margin-right: ${tokens.spacingM}; | ||
`; | ||
|
||
const SelectItemAction = (props: { fieldValue?: string | string[] }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it would be good to destructure the props here for consistency, also creating an interface for the component
@@ -0,0 +1,173 @@ | |||
import React from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file could be broken up into it's own folder
}; | ||
|
||
return sdk.field.onValueChanged(valueChangeHandler); | ||
}, [sdk.field]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just learned this the other day! Check out: useResourceValue
! This takes care of line 21 - 29
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks amazing! All my comments are structure/code readability related. The actual logic is solid
const queryArray: string[] = []; | ||
if (props.fieldValue) { | ||
props.fieldValue instanceof Array | ||
? queryArray.push(...props.fieldValue) | ||
: queryArray.push(props.fieldValue); | ||
} | ||
|
||
const client = new SfccClient(installParameters); | ||
const currentItemQueries = useQueries({ | ||
queries: queryArray.map((id: string) => { | ||
const [itemId, catalogId] = id.split(':'); | ||
return { | ||
queryKey: ['itemInfo', itemId], | ||
queryFn: | ||
fieldType === 'product' | ||
? () => client.fetchProduct(itemId) | ||
: () => client.fetchCategory(itemId, catalogId), | ||
}; | ||
}), | ||
}); | ||
|
||
const queriesComplete = currentItemQueries.every((query) => query.isSuccess || query.isError); | ||
|
||
useEffect(() => { | ||
if (queriesComplete) { | ||
const updatedData: any[] = []; | ||
for (const query of currentItemQueries) { | ||
if (query.isSuccess) { | ||
updatedData.push(query.data); | ||
} | ||
} | ||
|
||
if (updatedData.length) { | ||
setCurrentData(updatedData); | ||
} | ||
} | ||
}, [queriesComplete]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel moving lines 32 - 68 into a hook would make this file easier to digest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created a ticket for feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created a ticket for feedback
Note: Reopens #3620 to workaround permission issue.
Purpose
This app provides an integration with Salesforce Commerce Cloud storefronts to pick products and categories within the Contentful UI.
Approach
npx create-contentful-app
in theapps
directory of the repository.Field
,Configuration
, andDialog
locationsSfcc
utility to facilitate interaction with Salesforce Commerce Cloud apis.Dependencies and/or References
https://developer.salesforce.com/docs/commerce/commerce-api/overview
Deployment
App is currently hosted on Contentful.