We have to break the checkout flow in our mobile app and move it outside the app. So currently we have it setup that tapping Cart & Account will result in their default browser opening to the websites corresponding URL. However we are struggling with getting the Add To Cart Button to open their default browser to the products corresponding URL on the website.
ProductSingleScreen.js
renderAddToCart = (product) => {
//console.log(this.props)
const { purchasable, isOutOfStock, soldIndividually } = product;
const {isAddingToCart} = this.props
//console.log(this.props.isAddingToCart)
if(purchasable) {
if (isOutOfStock) {
return (
<Text style={styles.addToCartButtonText} uppercase={false}>{I18n.t('Out of Stock!')}</Text>
)
}
return (
<Row style={styles.addToCartbuttonContainer}>
{
!Boolean(soldIndividually) &&
<Col style={styles.quantityButtonsContainer}>
<Row>
<Col><Button style={styles.quantityAddRemove} bordered full onPress={this.props.decreaseQty}>
<Icon type={'Ionicons'} name={'ios-remove'}></Icon>
</Button></Col>
<Col style={styles.quantityInputBox}><Text>{this.props.getQtyVal}</Text></Col>
<Col><Button bordered block style={styles.quantityAddRemove} onPress={this.props.increaseQty}>
<Icon name={'ios-add'}></Icon>
</Button></Col>
</Row>
</Col>
}
<Col style={styles.addButtons}>
<Button disabled={(isAddingToCart || (product.type == 'variable' && !this.state.variation_id)) ? true : false } full onPress={() => this.processAddToCart(product)}>
{isAddingToCart && <ActivityIndicator size="large" color="#ffffff" />}
{!isAddingToCart && <Text style={styles.addToCartButtonText} uppercase={false}>{I18n.t('Add To Cart')}</Text>}
</Button>
</Col>
</Row>
);
}
};
renderEnternalProductButton =(product) => {
const { purchasable, button_text, external_url } = product;
if(!purchasable) {
return (
<Row style={styles.addToCartbuttonContainer}>
<Col style={styles.addButtons}>
<Button full onPress={ ()=> Linking.openURL(external_url) }>
<Text style={styles.addToCartButtonText} uppercase={false}>{button_text}</Text>
</Button>
</Col>
</Row>
)
}
}
renderCategories = (product) => {
const { categories } = product
if(categories.length == 0 ) return null
return (
<View style={styles.categoryList}>
<View style={styles.categoryLabel} >
{ categories.length == 1 && <Text style={styles.categoryLabelText}>{I18n.t('Category')}:</Text> }
{ categories.length > 1 && <Text style={styles.categoryLabelText}>{I18n.t('Categories')}:</Text> }
</View>
{
categories.map((category, index) => {
return(
<View style={styles.singleCategory} key={category.id}>
<Text style={styles.categoryText}>{decode(category.name)}</Text>
{ (index+1 < categories.length ) && <Text style={styles.categoryText}>, </Text> }
</View>
)
})
}
</View>
)
}
renderTags = (product) => {
const { tags } = product
if(tags.length == 0 ) return null
return (
<View style={styles.tagList}>
<View style={styles.categoryLabel} >
{ tags.length == 1 && <Text style={styles.categoryLabelText}>{I18n.t('Tag')}:</Text> }
{ tags.length > 1 && <Text style={styles.categoryLabelText}>{I18n.t('Tags')}:</Text> }
</View>
{
tags.map((category, index) => {
return(
<View style={styles.singleCategory} key={category.id}>
<Text style={styles.categoryText}>{decode(category.name)}</Text>
{ (index+1 < tags.length ) && <Text style={styles.categoryText}>, </Text> }
</View>
)
})
}
</View>
)
}
renderSku = (product) => {
const { sku } = product
if( !sku ) return null
return (
<View style={styles.tagList}>
<View style={styles.categoryLabel} >
<Text style={styles.categoryLabelText}>{I18n.t('SKU')}:</Text>
</View>
<View style={styles.singleCategory} >
<Text style={styles.categoryText}>{sku}</Text>
</View>
</View>
)
}
renderAdditionalInfo = (product) => {
return (
<View>
{ product.weight != "" && <View style={styles.tagList}>
<View style={styles.categoryLabel} >
<Text style={styles.categoryLabelText}>{I18n.t('Weight')}:</Text>
</View>
<View style={styles.singleCategory} >
<Text style={styles.categoryText}>{product.weight + ' ' + product.product_units.weight_unit}</Text>
</View>
</View>}
{ ( product.dimensions.length != "" || product.dimensions.width != "" || product.dimensions.height != "" ) &&
<View style={styles.tagList}>
<View style={styles.categoryLabel} >
<Text style={styles.categoryLabelText}>{I18n.t('Dimensions')}:</Text>
</View>
<View style={styles.singleCategory} >
<Text style={styles.categoryText}>{product.dimensions.length + ' x ' + product.dimensions.width + ' x ' + product.dimensions.height + ' ' + product.product_units.dimension_unit}</Text>
</View>
</View> }
{ product.attributes.length != 0 &&
product.attributes.map((attribute, index) => {
if(!attribute.visible ) return null
return(
<View style={styles.tagList} key={attribute.id}>
<View style={styles.categoryLabel} >
<Text style={styles.categoryLabelText}>{attribute.name}:</Text>
</View>
<View style={styles.singleCategory}>
<Text style={styles.categoryText}>{attribute.options.join(', ')}</Text>
</View>
</View>
)
})
}
</View>
)
}
renderPolicyData = (policyData) => {
return (
<View>
<View>
<H3 style={{textDecorationLine: 'underline', fontWeight: 'bold'}}>{policyData.shipping_policy_heading}</H3>
<View>
<HtmlViewer
html={policyData.shipping_policy}
containerStyle={{ padding: 0, paddingRight: 5 }}
htmlWrapCssString={`color: ${Colors.text}; font-size: 14px;`}
/>
</View>
</View>
<View>
<H3 style={{textDecorationLine: 'underline', fontWeight: 'bold'}}>{policyData.refund_policy_heading}</H3>
<View>
<HtmlViewer
html={policyData.refund_policy}
containerStyle={{ padding: 0, paddingRight: 5 }}
htmlWrapCssString={`color: ${Colors.text}; font-size: 14px;`}
/>
</View>
</View>
<View>
<H3 style={{textDecorationLine: 'underline', fontWeight: 'bold'}}>{policyData.cancellation_policy_heading}</H3>
<View>
<HtmlViewer
html={policyData.cancellation_policy}
containerStyle={{ padding: 0, paddingRight: 5 }}
htmlWrapCssString={`color: ${Colors.text}; font-size: 14px;`}
/>
</View>
</View>
</View>
)
}
render() {
const { product, isLoading, attributes, variations, isLoadingVariations, variationAttributes, validVariationAttributes } = this.props
// console.log(validVariationAttributes)
if (!product || isLoading) {
return (
<View>
<ActivityIndicator animating size='large' />
</View>
)
}
if( product.type === 'variable' && ( ! attributes || ! variations || !validVariationAttributes ) ) {
return (
<View>
<ActivityIndicator animating size='large' />
</View>
)
}
const gallerySlider = [product.illustration, ...product.gallery]
const { name: productName, price_html: productPrice, hidePrice, store: productStore, description,
showAdditionalInfoTab, wcfm_product_policy_data: policyData,
average_rating: productRating
} = product
const scrollY = Animated.add(
this.state.scrollY,
Platform.OS === 'ios' ? HEADER_MAX_HEIGHT : 0,
);
const headerTranslate = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [0, -HEADER_SCROLL_DISTANCE],
extrapolate: 'clamp',
});
const backButtonTranslate = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [0, HEADER_SCROLL_DISTANCE -10],
extrapolate: 'clamp',
});
const imageOpacity = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE / 4, HEADER_SCROLL_DISTANCE],
outputRange: [1, 1, 0],
extrapolate: 'clamp',
});
const imageTranslate = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [0, 100],
extrapolate: 'clamp',
});
return (
<View style={styles.fill}>
<StatusBar barStyle={Platform.OS === 'ios' ? "dark-content" : "light-content"} backgroundColor={Colors.statusBar} />
<Animated.ScrollView
style={styles.fill}
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
{ useNativeDriver: true },
)}
contentInset={{
top: HEADER_MAX_HEIGHT,
}}
contentOffset={{
y: -HEADER_MAX_HEIGHT,
}}
>
<View style={styles.scrollViewContent}>
{/* Product Title Price */}
<View style={styles.productNamePriceSection}>
<View style={styles.productNameContainer}>
<Text style={styles.productName}>{decode(productName)}</Text>
</View>
{
!Boolean(hidePrice) &&
<HtmlViewer
html={productPrice}
containerStyle={{ padding: 0, paddingRight: 5 }}
htmlWrapCssString={`color: ${Colors.text}; font-size: 22px;`}
/>
}
{
Boolean(productRating) &&
<View style={styles.productRating}>
{this.renderRating(productRating)}
</View>
}
</View>
{/* Product Title Price End */}
{/* Add To Cart Section */}
<View style={styles.addToCartSection}>
{(product.type === 'variable') &&
<View style={styles.variationSection}>
<Form>
{Object.keys(validVariationAttributes).length > 0 && attributes.map((attribute, index) => {
if ( attribute.variation ) {
return (
<View key={index} style={{ paddingBottom: 20 }}>
<Text>{attribute.name}</Text>
<View>
<Field name={attribute.slug} mode="dialog" style={{ left: 10 }} component={this.renderSelect} >
<Item key={''} label={I18n.t('Choose an option')} value={''} />
{validVariationAttributes[attribute.slug].map((option, key) => {
return (
<Item key={key} label={option} value={option} />
)
})}
</Field>
</View>
</View>
)
} else {
return null
}
})}
</Form>
</View>
}
{
this.state.variation_id && this.state.variation_price &&
<HtmlViewer
html={this.state.variation_price_html}
containerStyle={{ padding: 0, paddingRight: 5 }}
htmlWrapCssString={`color: ${Colors.text}; font-size: 22px;`}
/>
}
{ this.renderAddToCart(product) }
{(product.type === 'external') && this.renderEnternalProductButton(product) }
</View>
{/* Add To Cart Section End */}
{ this.renderCategories(product) }
{ this.renderTags(product) }
{ this.renderSku(product) }
<View>
<Accordian
title = {'Description'}
data = {description}
htmlContent = {true}
/>
{
!productStore.errors &&
<Accordian
title = {'Store'}
>
{/* Vendor Section */}
<View style={styles.storeVendorContainer}>
<View style={styles.vendorImageContainer}>
<Image style={styles.vendorImage} small source={{ uri: productStore.vendor_shop_logo }} />
</View>
<View>
<Text>{decode(productStore.vendor_shop_name)}</Text>
</View>
</View>
{/* Vendor Section End */}
</Accordian>
}
{
showAdditionalInfoTab &&
<Accordian
title = {'Additional Information'}
>
{this.renderAdditionalInfo(product)}
</Accordian>
}
{
policyData.visible &&
<Accordian
title = {policyData.tab_title}
>
{this.renderPolicyData(policyData)}
</Accordian>
}
</View>
</View>
</Animated.ScrollView>
<Animated.View
style={[
styles.header,
{ transform: [{ translateY: headerTranslate }] },
]}
>
<Animated.View
style={[styles.backButtonContainer,
{ transform: [{ translateY: backButtonTranslate }] },
]}>
<TouchableOpacity onPress={() => this.props.navigation.goBack()} style={{flex:1, justifyContent: 'center', alignItems: 'center'}}>
<Icon style={{color: Colors.text, marginTop:4, marginRight:4}} name='arrow-back' />
</TouchableOpacity>
</Animated.View>
<Animated.View
style={[
styles.backgroundImage,
{
opacity: imageOpacity,
transform: [{ translateY: imageTranslate }],
},
]}
>
<AnimatedSlidebox
images={gallerySlider}
sliderBoxHeight={300}
//onCurrentImagePressed={index => console.warn(`image ${index} pressed`)}
dotColor={Colors.whiteBackground}
inactiveDotColor="#90A4AE"
resizeMethod={'resize'}
resizeMode={'cover'}
dotStyle={styles.sliderNavigationDots}
/>
</Animated.View>
</Animated.View>
</View>
)
}
}
const mapStateToProps = (state) => {
return {
product: ProductSingleSelectors.getProduct(state),
isLoading: ProductSingleSelectors.isLoading(state),
attributes: ProductSingleSelectors.getAttributes(state),
variations: ProductSingleSelectors.getVariations(state),
isLoadingVariations: ProductSingleSelectors.isLoadingVariations(state),
variationAttributes: ProductSingleSelectors.getVariationAttributes(state),
validVariationAttributes: ProductSingleSelectors.getValidVariationAttributes(state),
getQtyVal: ProductSingleSelectors.getQuantity(state),
isAddingToCart: CartSelectors.isLoading(state),
}
}
const mapDispatchToProps = {
getProduct: ProductSingleActions.productRequest,
getVariations: ProductSingleActions.variationRequest,
increaseQty: ProductSingleActions.increaseQty,
decreaseQty: ProductSingleActions.decreaseQty,
addToCart: CartActions.addToCartRequest,
reset: ProductSingleActions.reset,
}
ProductSingleScreen = compose(
connect(mapStateToProps, mapDispatchToProps),
withNavigation
)(ProductSingleScreen);
export default reduxForm({
form: 'variationForm'
})(ProductSingleScreen)
// export default compose(
// connect(mapStateToProps, mapDispatchToProps),
// withNavigation
// )(ProductSingleScreen)
This is the Product Display Page it handles the display of products inside the app and is used for every product queried over the rest api connection with Woocommerce. It handles product options etc. see screenshot. [Product Display Page][1] [1]: https://i.stack.imgur.com/1jLXG.png
Where we are stuck is trying to get this button to behave dynamically based on the product being viewed and when Add To Cart is pressed opens the url of the product being viewed in the app in the default browser installed on the device.
We have tried onPress={() => Linking.openURL('https://www.ourwebsite.com/')} which makes every product open to the url set above.