Skip to content

Instantly share code, notes, and snippets.

@sahilkashyap64
Last active July 14, 2023 21:02
Show Gist options
  • Save sahilkashyap64/b3db782d9f56e546d5d5d63f40d32c74 to your computer and use it in GitHub Desktop.
Save sahilkashyap64/b3db782d9f56e546d5d5d63f40d32c74 to your computer and use it in GitHub Desktop.
React native shopify
export const getAllProducts = async(playerAuthToken = '') => {
// console.log("getAllProducts",playerAuthToken);
var query = `{
collections(first: 250) {
edges {
node {
title
}
}
}
products(first: 250)
{
edges
{
node
{
id
title
handle
totalInventory
descriptionHtml
createdAt
updatedAt
priceRange
{
maxVariantPrice
{
amount
currencyCode
}
minVariantPrice
{
amount
currencyCode
}
}
featuredImage
{
id
url
}
images(first: 10)
{
edges
{
node
{
url
}
}
}
options
{
id
name
values
}
availableForSale
collections(first: 250){
edges{
node{
title
}
}
}
variants(first: 100)
{
edges
{
node
{
availableForSale
selectedOptions
{
name
value
}
id
title
currentlyNotInStock
priceV2
{
amount
}
price
{
currencyCode
amount
}
image
{
id
url
}
}
}
}
}
}
}
}`;
var requestOptions = {
method: 'POST',
headers: {"Content-Type": "application/graphql","X-Shopify-Storefront-Access-Token": `${STOREFRONT_ACCESS_TOKEN}`},
body: query,
// redirect: 'follow'
};
const response = await fetch(`${SHOPIFY_URL}/api/2023-04/graphql.json`, requestOptions);
return await response.json();
};
import {atom} from 'recoil';
export const userState=atom({
key:"userSession",
default:{
session : null,
userType : null,
onboarding : false,
profile:null,
err:null,
loading: true,
locations: [],
}
})
export const productsState = atom({
key: `productsState`,
default: [],
})
export const collectionState = atom({
key: `collectionState`,
default: [],
})
export const cartState = atom({
key: 'cartState',
default: [],
})
import { useRecoilState ,selector} from 'recoil';
import { cartState,userState } from '../store/atoms';
import { SHOPIFY_URL } from '../constants/urls';
export const useAddProduct = () => {
const [cart, setCart] = useRecoilState(cartState);
return (product) => {
const index = cart.findIndex(item => item.id === product.id);
if (index === -1) {
return setCart([...cart, { ...product, qty: 1 }]);
}
const newCart = cart.map((item, i) => {
if(i === index) {
return {
...item,
qty: item.qty + 1,
}
}
return item;
})
setCart(newCart)
}
}
export const useRemoveProduct = () => {
const [cart, setCart] = useRecoilState(cartState);
return (product) => {
const index = cart.findIndex(item => item.id === product.id);
if (index === -1) {
return setCart([...cart, { ...product, qty: 1 }]);
}
const newCart = cart.map((item, i) => {
if(i === index) {
return {
...item,
qty: item.qty > 1 ? item.qty - 1 : 1
}
}
return item;
})
setCart(newCart)
}
}
export const useDeleteProduct = () => {
const [cart, setCart] = useRecoilState(cartState);
return (product) => {
const index = cart.findIndex(item => item.id === product.id);
console.log("product remove",product);
if (index === -1) {
alert('Product not found in cart!')
return;
}
const newCart = cart.filter(item => item.id !== product.id)
setCart(newCart)
}
}
export const clearCart = () => {
const [cart, setCart] = useRecoilState(cartState);
setCart([]);
return;
}
export const cartStatus = selector({
key: 'cartStatus',
get: ({ get }) => {
const cart = get(cartState);
const user = get(userState);
const {profile} =user;
const totalItems = cart.length;
const totalPrice = cart.reduce((total, { qty, price }) => total + (qty * price), 0).toFixed(2);
const totalQtyInCart = cart.reduce((acc, { qty }) => acc + qty , 0);
var queryString = cart.map(function(params) {
return params['variant_id'] + ':' + params['qty']
}).join(',');
const url = `${SHOPIFY_URL}/cart/${queryString}?checkout[email]=${profile.email}&attributes[user_email]=${profile.email}&attributes[user_id]=${profile.user_id}`;
return {
totalItems,
totalPrice,
url,
totalQtyInCart,
}
}
})
// const url = `http://garage-dummy.myshopify.com/cart/${queryString}?checkout[email]=${user.profile.email}&attributes[coach_email]=${coach_email}&attributes[user_email]=${profile.email}&attributes[coach_id]=${2}&attributes[user_id]=${profile.user_id}`;
import React from 'react';
import { normalize, SCREEN_WIDTH } from '../../../../theme/metrics';
import { View, Text, TextInput, ScrollView, Image, SafeAreaView, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
const pageWidth = Dimensions.get('window').width;
const logo = {
uri: 'https://reactnative.dev/img/tiny_logo.png',
width: 64,
height: 64,
};
const CollectionOptions = ({productTitle, name, values, selectedOptions, setOptions, productInventory, selectedVariant }) => {
return (<View style={styles.fieldSet}>
<Text style={styles.legend}>{name}</Text>
<ScrollView horizontal={true}
showsHorizontalScrollIndicator={false}
style={{
marginTop: 5,
}}>
<TouchableOpacity
key={0}
style={{ marginHorizontal: 5 }}
onPress={() => {
setOptions('All')
}}
>
<Text style={[styles.textWithRoundedBorder, { backgroundColor: selectedOptions=='All' ? 'black' : 'white' }, { color: selectedOptions=='All' ? 'white' : 'black' }]}>All</Text>
</TouchableOpacity>
{values.map((item, index) => {
const checked = selectedOptions === item
return (
<TouchableOpacity
style={{ marginHorizontal: 5 }}
key={index}
onPress={() => {
setOptions(item)
}}
>
<Text style={[styles.textWithRoundedBorder, { backgroundColor: checked ? 'black' : 'white' }, { color: checked ? 'white' : 'black' }]}>{item}</Text>
</TouchableOpacity>
)
})}
</ScrollView></View>)
};
const styles = StyleSheet.create({
fieldSet: {
marginTop:5,
marginLeft: 16,
marginRight: 16,
paddingHorizontal: 5,
paddingBottom: 5,
borderRadius: 5,
borderWidth: 1,
alignItems: 'center',
borderColor: '#000'
},
legend: {
position: 'absolute',
top: -10,
left: 10,
fontWeight: 'bold',
backgroundColor: '#FFFFFF'
},
previewContainer: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
marginTop: 8,
},
textWithRoundedBorder: {
borderRadius: 5,
fontWeight: "bold",
padding: normalize(5),
backgroundColor: '#000',
overflow: 'hidden',
borderWidth: 1,
borderColor: 'black'
}
});
export default CollectionOptions;
import React from "react";
import { View, Text, Image, TouchableOpacity } from "react-native";
import Button from "../../components/Button";
import { normalize } from "../../theme/metrics";
import Typography from "../../theme/typography";
import Colors from "../../theme/colors";
import { useAddProduct, useRemoveProduct } from "../../utils/cartHelper";
import AsyncStorage from "@react-native-async-storage/async-storage";
import useAuth from "../../hooks/useAuth";
import { useRecoilState } from "recoil";
import { productsState } from "../../store/atoms";
import Ripple from 'react-native-material-ripple';
import {
getAllProducts,
} from "../../api";
const Item = ({
name,
price,
width,
imgSrc,
sold,
product,
isCoach = false,
playerId,
options
}) => {
const [products, setProducts] = useRecoilState(productsState);
const { user } = useAuth();
const mergeArrays = (arr1, arr2) =>
arr1 &&
arr1.map((obj) => (arr2 && arr2.find((p) => p.id === obj.id)) || obj);
const addProduct = useAddProduct();
const removeProduct = useRemoveProduct();
return (
<View
style={{
width: width / 2 - 30,
height: width / 2 - 10,
borderWidth: 0.5,
borderColor: "#dddddd",
borderRadius: 10,
marginTop: 15,
}}
>
<View style={{ flex: 2 }}>
<Image
style={{
flex: 1,
width: null,
height: null,
resizeMode: "cover",
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
}}
source={{ uri: imgSrc }}
/>
<View
style={{
flex: 1,
// alignItems: "flex-start",
justifyContent: "space-evenly",
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
}}
>
<Text
style={{
paddingLeft: 10,
marginTop: "5%",
fontSize: 12,
fontFamily: Typography.fontFamily.montserratRegular,
color: "#000000",
}}
>
{name}
</Text>
<Text
style={{
paddingLeft: 10,
marginTop: "3%",
marginBottom: "5%",
fontSize: 15,
color: "#53B17A",
}}
>
${price}
</Text>
{product && !product.recommended && !isCoach ? (
product.availableForSale?<Ripple
rippleColor='#53B17A'
rippleCentered={true}
rippleOpacity={0.87}
rippleDuration={1200}
onPress={async () => {
if (!isCoach) {
// console.log("Add to cart1 item", product.id);
// product.price=product.variants[0].price
addProduct({
...product,
price,
variant_title: product.variants[0].title,
variant_id: product.variants[0].id,
image_url :imgSrc
});
} else {
console.log("Add to List", product);
const productId = product.id;
console.log("Add to playerId", playerId);
// console.log("products", products);
let ax = { ...product, recommended: true };
let b = [];
b.push(ax);
// for adding products
// const a = await addProductToRecommendedList({
// coachAuthToken: user.session.access_token,
// playerId: playerId,
// productId: productId,
// });
// console.log("aaaa", a);
// setProducts(mergeArrays(products, b));
// const getAllProductsResponse = await getAllProducts(
// user.session.access_token
// );
// console.log("Fetch all");
// setProducts(getAllProductsResponse);
}
}}
activeOpacity={0.9}
style={{
backgroundColor: "#000000",
width: "100%",
height: normalize(35),
borderRadius: 5,
alignItems: "center",
justifyContent: "center",
elevation: 5,
}}
>
<Text
style={{
textAlign: "center",
flexDirection: "row",
// width:'100%',
color: Colors.theme.white,
fontSize: Typography.fontSize.small,
fontFamily: Typography.fontFamily.grtskteraRegular,
}}
>
{isCoach ? "View Item" : "Add to Cart"}
</Text>
</Ripple>: <Ripple
rippleColor='#53B17A'
rippleCentered={true}
rippleOpacity={0.87}
rippleDuration={1200}
activeOpacity={0.9}
style={{
borderWidth:2,
borderColor:'black',
backgroundColor: "#FFFFFF",
width: "100%",
height: normalize(35),
borderRadius: 5,
alignItems: "center",
justifyContent: "center",
elevation: 5,
}}
><Text
style={{
textAlign: "center",
flexDirection: "row",
// width:'100%',
color: Colors.theme.black,
fontSize: Typography.fontSize.small,
fontFamily: Typography.fontFamily.grtskteraRegular,
}}
>
Sold out
</Text></Ripple>
) : (!isCoach)?(
<Ripple
onPress={async () => {
if (!isCoach) {
console.log("Add to cart1 item", product.id);
// product.price=product.variants[0].price
addProduct({
...product,
price,
variant_title: product.variants[0].title,
variant_id: product.variants[0].id,
image_url :imgSrc
});
} else {
console.log("Remove from list", product);
let ax = { ...product, recommended: false };
let b = [];
b.push(ax);
// console.log("ax",ax);
// return;
//remove products
// await removeProductToRecommendedList({
// coachAuthToken: user.session.access_token,
// recommendedItemId: product.id,
// });
// setProducts(mergeArrays(products, b));
const getAllProductsResponse = await getAllProducts(
user.session.access_token
);
console.log("after remove");
setProducts(getAllProductsResponse);
}
}}
activeOpacity={0.9}
style={isCoach?{
backgroundColor: Colors.theme.bottleGreen,
width: "100%",
height: normalize(35),
borderRadius: 5,
alignItems: "center",
justifyContent: "center",
elevation: 5,
}:{
backgroundColor: "#000000",
width: "100%",
height: normalize(35),
borderRadius: 5,
alignItems: "center",
justifyContent: "center",
elevation: 5,
}}
>
<Text
style={{
textAlign: "center",
flexDirection: "row",
// width:'100%',
color: Colors.theme.white,
fontSize: Typography.fontSize.small,
fontFamily: Typography.fontFamily.grtskteraRegular,
}}
>
{isCoach?'Added':'Add to Cart'}
</Text>
</Ripple>
):null}
</View>
</View>
</View>
);
};
export default Item;
// miscellaneous helper functions
export const discountCalc = (bill, discount) => {
let afterDiscount = bill - (bill * discount / 100);
return afterDiscount;
}
export const percentageCalc = (percentage, amount) => {
let result = (percentage / 100) * amount;
// let afterDiscount = bill - (bill * discount / 100);
return result;
}
// formating shopify response
export const withoutEdgesAndNodes = (data) => {
let result = Array.isArray(data) ? [] : {};
if (typeof data !== "object") return data;
for (const key in data) {
if (typeof key === "string" && key === "edges") {
result = withoutEdgesAndNodes(data.edges.map((edge) => edge.node));
} else {
// let trimedData= key=='descriptionHtml'?JSON.stringify(data[key]):data[key];
let trimedData = key == 'id' ? data[key].split("/").pop() : data[key];
result = Object.assign(result, {
[key]: withoutEdgesAndNodes(trimedData),
});
}
}
return result;
};
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
ScrollView,
Image,
TouchableOpacity,
LogBox,
SafeAreaView,
FlatList,
RefreshControl,
Dimensions,
TextInput,
} from 'react-native';
import internalStyle from './style';
import { useRecoilState } from 'recoil';
import {
productsState,
cartState,
collectionState
} from '../../../../store/atoms';
import Item from '../../../../components/Product/Item';
import { getAllProducts } from '../../../../api/PlayerApi/playerScheduleScreen';
import {discountCalc,withoutEdgesAndNodes,percentageCalc} from '../../../../utils/miscHelper';
import { useRecoilValue } from 'recoil';
import { cartStatus } from '../../../../utils/cartHelper';
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
interpolate,
Extrapolate,
withSpring,
runOnJS,
} from 'react-native-reanimated';
import Icon from 'react-native-vector-icons/Ionicons';
import CollectionOptions from "../../Pages/PlayerCart/CollectionOptions";
const { width } = Dimensions.get("window");
const ProductListComponent = ({ navigation, route }) => {
const cart = useRecoilValue(cartStatus);
const [selectedOptions, setSelectedOptions] = useState("All")
const [products, setProducts] = useRecoilState(productsState);
const [collections, setCollections] = useRecoilState(collectionState);
const [search, setSearch] = useState('');
const [filteredDataSource, setFilteredDataSource] = useState([]);
const getProducts = async() => {
console.log("getProducts");
setmoreLoading(true);
try {
const getProductsResponse = await getAllProducts(user.session.access_token);
const responseData = await getProductsResponse;
let removedShopifyNodesAndEdges=withoutEdgesAndNodes(responseData);
// console.log("removedShopifyNodesAndEdges",JSON.stringify(removedShopifyNodesAndEdges.data.products));
setFilteredDataSource(removedShopifyNodesAndEdges.data.products);
setProducts(removedShopifyNodesAndEdges.data.products);
let collectionValues = removedShopifyNodesAndEdges.data.collections.map(function(value) {
return value.title;
});
setCollections(collectionValues);
setmoreLoading(false);
}catch(err){
setmoreLoading(false);
}
}
useEffect(() => {
getProducts();
}, []);
useEffect(() => {
// console.log("cartCounter.value",cartCounter.value);
cartCounter.value = 0;//because we jump between 0-1
bounceCart.value = 0;//because we jump between 0-1
if(cart.totalQtyInCart>0){
bounceCart.value = withTiming(1, {duration: 400});
cartCounter.value = withSpring(1, {duration: 600});
}
}, [cart.totalQtyInCart]);
const searchFilterFunction = (text) => {
// Check if searched text is not blank
console.log("text",text);
if (text) {
// Inserted text is not blank
// Filter the products and update FilteredDataSource
const newData = products.filter(function (item) {
// Applying filter for the inserted text in search bar
const itemData = item.title
? item.title.toUpperCase()
: ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilteredDataSource(newData);
setSearch(text);
} else {
// Inserted text is blank
// Update FilteredDataSource with products
setFilteredDataSource(products);
setSearch(text);
}
};
function setOptions(item) {
searchShopifyCollectionFilterFunction(item)
setSelectedOptions(item)
}
const searchShopifyCollectionFilterFunction = (text) => {
// Check if searched text is not blank
// console.log("text",text);
if (text && text!=='All') {
// Inserted text is not blank
// Filter the products and update FilteredDataSource
const newData = products.filter(
(element) => element.collections.some(
(collection) => collection.title == text));
setFilteredDataSource(newData);
// setSearch(text);
} else {
// Inserted text is blank
// Update FilteredDataSource with products
setFilteredDataSource(products);
// setSearch(text);
}
};
const bounceCart = useSharedValue(0);
const cartCounter = useSharedValue(0);
// for cart animation
const cartStyle = useAnimatedStyle(() => {
const tranX = interpolate(
bounceCart.value,
[0, 0.2, 0.4, 0.6, 0.8, 1],
[0, -20, 20, -20, 20, 0],
{
extrapolateLeft: Extrapolate.CLAMP,
extrapolateRight: Extrapolate.CLAMP,
},
);
return {
//withspring animation
transform: [{translateX: withSpring(tranX)}],
};
});
// for cart counter animation
const cartCounterStyle = useAnimatedStyle(() => {
return {
transform: [{scale: cartCounter.value}],
};
});
return (
<SafeAreaView>
<View style={internalStyle.mainContainer}>
{topBar == 'Products' ? (
products && products.length !== 0 ? (
<View>
<TextInput
style={{
height: 40,
borderWidth: 1,
paddingLeft: 20,
marginTop: 5,
marginBottom: 5,
marginLeft: 18,
marginRight: 18,
borderColor: '#000000',
backgroundColor: '#FFFFFF',
}}
onChangeText={(text) => searchFilterFunction(text)}
value={search}
underlineColorAndroid="transparent"
placeholder="Search Products"
/>
{
collections && collections.length !== 0?<CollectionOptions
productTitle={"xxxx"}
key={`key-`}
name={"Collection"}
values={collections}
selectedOptions={selectedOptions}
setOptions={setOptions}
// selectedVariant={selectedVariant}
// productInventory={productInventory}
// available={available}
/>:null
}
<ScrollView
contentContainerStyle={[internalStyle.listContainer,{marginTop:-10,paddingBottom: 80}]}
>
<View style={{ flex: 1 }}>
<View
style={{
paddingHorizontal: 20,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
}}
>
{filteredDataSource.map((item) => {
return (
<TouchableOpacity
key={item.variant_id}
onPress={() =>
navigation.navigate(
'ProductInfo',
{ item,coachData }
)
}
>
<Item
product={item}
imgSrc={item.featuredImage.url}
width={width}
name={item.title}
price={
Number(item.priceRange
.maxVariantPrice.amount).toFixed(2)
}
sold={"item.sold"}
options={item.options}
/>
</TouchableOpacity>
);
})}
</View>
</View>
</ScrollView>
<TouchableOpacity
activeOpacity={0.5}
onPress={() =>
navigation.navigate(ScreenNames.PLAYER_CART, {
coachData,
})
}
style={{
position: 'absolute',
width: 50,
height: 50,
alignItems: 'center',
justifyContent: 'center',
right: 30,
bottom: 80,
backgroundColor:'black',
borderRadius:50 / 2,
}}
>
<Animated.View style={[{position: 'relative'}, cartStyle]}>
<Icon name="ios-cart-outline" color="white" size={30} />
<Animated.View
style={[
internalStyle.circle,
cartCounterStyle,
{
top: -3,
right: -4,
zIndex: 2,
alignItems: 'center',
justifyContent: 'center',
},
]}>
<Text style={{color: 'white', fontWeight: 'bold', fontSize: 12}}>
{cart.totalQtyInCart ? cart.totalQtyInCart : null}
</Text>
</Animated.View>
</Animated.View>
</TouchableOpacity>
</View>
) : (
<View style={internalStyle.blankContainer}>
<Text style={internalStyle.blankText}>
No Product available.
</Text>
</View>
)
) : null} </View>
</SafeAreaView>
);
};
export default ProductListComponent
export const API_URL="https://xxxxSome.com/api"
export const SHOPIFY_URL="https://garage-dummy.myshopify.com" //dummy shopify store
// export const SHOPIFY_URL="https://prod.com" //prod shopify store
export const STOREFRONT_ACCESS_TOKEN="xxxx"//dummy garage shopify
export const AS_USER_KEY = `async_user_key`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment