├── SFCC-RN-Components ├── .watchmanconfig ├── app.json ├── assets │ └── images │ │ ├── school.png │ │ ├── user_icon.png │ │ ├── missing_img.png │ │ ├── profile_icon.png │ │ ├── search_icon.png │ │ ├── parchment_logo.png │ │ ├── hamburger_collapsed.png │ │ └── hamburger_expanded.png ├── .babelrc ├── App.test.js ├── src │ ├── lib │ │ ├── documents │ │ │ ├── Option.js │ │ │ ├── Variant.js │ │ │ ├── Inventory.js │ │ │ ├── ProductLink.js │ │ │ ├── ProductType.js │ │ │ ├── BundledProduct.js │ │ │ ├── VariationGroup.js │ │ │ ├── ProductPromotion.js │ │ │ ├── Recommendation.js │ │ │ ├── VariationAttribute.js │ │ │ ├── ProductRef.js │ │ │ ├── ProductSearchResult.js │ │ │ ├── ImageGroup.js │ │ │ ├── Basket.js │ │ │ ├── Image.js │ │ │ ├── CategoryResult.js │ │ │ ├── Category.js │ │ │ ├── ProductSearchHit.js │ │ │ └── Product.js │ │ ├── OCAPIService │ │ │ ├── Authorize │ │ │ │ ├── AuthorizeService.js │ │ │ │ └── __tests__ │ │ │ │ │ └── AuthTokenTests.js │ │ │ ├── __tests__ │ │ │ │ ├── CategoryTests.js │ │ │ │ └── ProductTests.js │ │ │ ├── OCAPICallInfo.js │ │ │ ├── OCAPIService.js │ │ │ └── __mocks__ │ │ │ │ └── CategoryMock.js │ │ └── utilityHelpers │ │ │ ├── URLHelper.js │ │ │ └── __tests__ │ │ │ └── utilityHelperTests.js │ ├── menuItems.js │ ├── components │ │ ├── Home │ │ │ ├── actions.js │ │ │ ├── HomeContainer.js │ │ │ └── Home.js │ │ ├── SFCC │ │ │ ├── Product │ │ │ │ ├── ListView │ │ │ │ │ ├── ListView.js │ │ │ │ │ ├── ListViewContainer.js │ │ │ │ │ └── actions.js │ │ │ │ └── InfoTile │ │ │ │ │ ├── InfoTileContainer.js │ │ │ │ │ ├── actions.js │ │ │ │ │ └── InfoTile.js │ │ │ └── Category │ │ │ │ └── ProductCategoryTree │ │ │ │ ├── ProductCategoryTree.js │ │ │ │ ├── MenuCategory.js │ │ │ │ └── actions.js │ │ ├── Layout │ │ │ └── SectionCard.js │ │ ├── UserAccount │ │ │ ├── UserAccountContainer.js │ │ │ ├── actions.js │ │ │ └── UserAccount.js │ │ ├── ImageCarousel │ │ │ └── ThumbnailImage.js │ │ └── Navbar │ │ │ ├── NavbarContainer.js │ │ │ └── Navbar.js │ ├── reducers │ │ ├── productSearchReducer.js │ │ ├── homeReducer.js │ │ ├── rootReducer.js │ │ ├── categoryTreeReducer.js │ │ ├── __tests__ │ │ │ └── categoryTreeReducerTests.js │ │ ├── UserAccountReducer.js │ │ └── infoTileReducer.js │ ├── services │ │ └── deviceStorage │ │ │ ├── __tests__ │ │ │ └── DeviceStorageServiceTests.js │ │ │ └── DeviceStorageService.js │ ├── models │ │ └── UserProfile.js │ ├── configureStore.js │ ├── actionTypes.js │ ├── config │ │ ├── appConfig.js │ │ └── apiConfig.js │ └── styles │ │ └── globalStyles.js ├── .gitignore ├── App.js ├── package.json ├── .flowconfig ├── .eslintrc └── README.md ├── .gitignore ├── package.json ├── LICENSE ├── README.md └── yarn.lock /SFCC-RN-Components/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | node_modules/** 3 | .idea/** 4 | .DS_Store 5 | **/*.code-workspace -------------------------------------------------------------------------------- /SFCC-RN-Components/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "sdkVersion": "23.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/school.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/school.png -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/user_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/user_icon.png -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/missing_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/missing_img.png -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/profile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/profile_icon.png -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/search_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/search_icon.png -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/parchment_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/parchment_logo.png -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/hamburger_collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/hamburger_collapsed.png -------------------------------------------------------------------------------- /SFCC-RN-Components/assets/images/hamburger_expanded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghgofort/sfcc-react/HEAD/SFCC-RN-Components/assets/images/hamburger_expanded.png -------------------------------------------------------------------------------- /SFCC-RN-Components/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SFCC-RN-Components/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); 10 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/Option.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Option.js 3 | * Option OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class Option 8 | */ 9 | export default class Option { 10 | constructor(args) { 11 | /** @todo Implement Option document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/Variant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Variant.js 3 | * Variant OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class Variant 8 | */ 9 | export default class Variant { 10 | constructor(args) { 11 | /** @todo Implement Variant document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/Inventory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Inventory.js 3 | * Inventory OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class Inventory 8 | */ 9 | export default class Inventory { 10 | constructor(args) { 11 | /** @todo Implement Inventory document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/ProductLink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductLink.js 3 | * ProductLink OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class ProductLink 8 | */ 9 | export default class ProductLink { 10 | constructor(args) { 11 | /** @todo Implement ProductLink document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/ProductType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductType.js 3 | * ProductType OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class ProductType 8 | */ 9 | export default class ProductType { 10 | constructor(args) { 11 | /** @todo Implement ProductType document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/BundledProduct.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file BundledProduct.js 3 | * BundledProduct OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class BundledProduct 8 | */ 9 | export default class BundledProduct { 10 | constructor(args) { 11 | /** @todo Implement BundledProduct document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/VariationGroup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file VariationGroup.js 3 | * VariationGroup OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class VariationGroup 8 | */ 9 | export default class VariationGroup { 10 | constructor(args) { 11 | /** @todo Implement VariationGroup document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # expo 4 | .expo/ 5 | 6 | # dependencies 7 | /node_modules 8 | 9 | # misc 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/ProductPromotion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductPromotion.js 3 | * ProductPromotion OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class ProductPromotion 8 | */ 9 | export default class ProductPromotion { 10 | constructor(args) { 11 | /** @todo Implement ProductPromotion document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/Recommendation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Recommendation.js 3 | * Recommendation OCAPI document class. 4 | */ 5 | 6 | /** 7 | * @class Recommendation 8 | */ 9 | export default class Recommendation { 10 | constructor(args) { 11 | /** @todo Implement Recommendation document class */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/VariationAttribute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class VariationAttribute 3 | * @classdesc Model class that is used for easily handling OCAPI documents 4 | * of type 'VariationAttribute'. 5 | */ 6 | export default class VariationAttribute { 7 | /** 8 | * @constructor 9 | */ 10 | constructor(args) { 11 | 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/menuItems.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file menuItems.js 3 | * @fileoverview - Contains the routes for links on home screen and navigation bar. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const Routes = [ 9 | {id: 'home', title: 'Home', index: 0}, 10 | {id: 'userAccount', title: 'User Account Home', index: 1} 11 | ]; 12 | 13 | export default (Routes = Routes); 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/Home/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * home/actions.js 3 | * Action creation methods for actions that occur in the Home scene of the app. 4 | */ 5 | 6 | import * as actionTypes from '../../actionTypes'; 7 | import {getLogin} from '../../reducers/rootReducer'; 8 | import {Actions} from 'react-native-router-flux'; 9 | 10 | // Action creators 11 | export const home = () => { 12 | return {type: actionTypes.SCENE_TRANSITION}; 13 | }; 14 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/reducers/productSearchReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file productSearchReducer.js 3 | * @fileoverview - Reduces state change from ProductSearch API calls and merges 4 | * them into the app global state. This file also exports any selector methods. 5 | */ 6 | 7 | import * as actionTypes from '../actionTypes'; 8 | 9 | const DEFAULT_STATE = {}; 10 | 11 | export default function productSearchReducer(state = DEFAULT_STATE, action) { 12 | switch (action.type) { 13 | 14 | default: 15 | return state; 16 | } 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfcc-react", 3 | "version": "0.1.0", 4 | "description": "POC for use of ReactJS with Sales Force Commerce Cloud e-commerce platform.", 5 | "main": "index.js", 6 | "repository": { 7 | "url": "ghgoforth@bitbucket.org:ghgoforth/dw-ghgoforth.git", 8 | "type": "git" 9 | }, 10 | "author": "Galen Goforth ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "jest": "^22.4.2", 14 | "react": "^16.0.0", 15 | "react-dom": "^16.0.0", 16 | "react-native-cli": "^2.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Product/ListView/ListView.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ListView.js 3 | * @fileoverview - ListView component class for displaying a list of products 4 | * returned from a call to the Open Commerce API or cached from an earlier call 5 | * to the API. 6 | */ 7 | 8 | 9 | /** 10 | * @class ListView 11 | * @classdesc Displays a flexible list of product tiles in different display 12 | * formats for easier sorting, and browsing. 13 | */ 14 | export default class ListView extends Component { 15 | 16 | 17 | render() { 18 | return ( 19 | 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /SFCC-RN-Components/src/services/deviceStorage/__tests__/DeviceStorageServiceTests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file DeviceStorageServiceTests.js 3 | * @fileoverview - Provides unit test coverage of the DeviceStorageService class 4 | * and all of its critical public member methods. 5 | */ 6 | 7 | describe('src/services/deviceStorage/DeviceStorageService.js -- Tests', () => { 8 | 9 | // Encryption Method Tests 10 | describe('DeviceStorageService.encryptValues() -- Tests', () => { 11 | // Suite level variable declaration. 12 | test('should encrypt data for storage on a device.', () => { 13 | expect(true); 14 | }); 15 | }); 16 | 17 | }); 18 | 19 | 20 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/ProductRef.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductRef.js 3 | * @fileoverview - Contains a class representation of the OCAPI Shop API 4 | * ProductRef (product reference) document type. 5 | */ 6 | 7 | /** 8 | * @class ProductRef 9 | * @classdesc A class representation of the OCAPI ProductRef document type that 10 | * can be instantiated by passing an OCAPI ProductRef document as the only 11 | * argument to the constructor. 12 | */ 13 | export default class ProductRef { 14 | /** 15 | * @constructor 16 | * @param {Object} args 17 | */ 18 | constructor() { 19 | /** @todo : Implement ProductRef OCAPI document class. */ 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/Home/HomeContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * home/index.js 3 | * Container for the CollegeTools component. 4 | */ 5 | import {connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | import Home from './Home'; 8 | import * as actions from './actions'; 9 | import {getHome} from '../../reducers/rootReducer'; 10 | 11 | const mapStateToProps = state => { 12 | return { 13 | user: 'mario' 14 | }; 15 | }; 16 | 17 | const mapDispatchToProps = (dispatch) => { 18 | return { 19 | actions: bindActionCreators(actions, dispatch) 20 | }; 21 | }; 22 | 23 | const HomeContainer = connect(mapStateToProps, mapDispatchToProps)(Home); 24 | 25 | export default HomeContainer; 26 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/Layout/SectionCard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * SectionCard.js 5 | * A Layout component for seperation of sections within a scene. 6 | */ 7 | 8 | import React from 'react'; 9 | import { 10 | View, 11 | StyleSheet, 12 | } from 'react-native'; 13 | 14 | const SectionCard = (props) => { 15 | return ( 16 | 17 | {props.children} 18 | 19 | ); 20 | }; 21 | 22 | const styles = StyleSheet.create({ 23 | containerStyle: { 24 | padding: 5, 25 | backgroundColor: '#fff', 26 | justifyContent: 'center', 27 | position: 'relative', 28 | marginTop: 10, 29 | marginBottom: 0, 30 | margin: 10, 31 | borderRadius: 5, 32 | } 33 | }); 34 | 35 | export default SectionCard; 36 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Product/ListView/ListViewContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ListViewContainer.js 3 | * @fileoverview - A wrapper for the ListView component which utilizes redux and 4 | * react lifecycle methods in order to interact with the application scoped 5 | * state object. 6 | */ 7 | 8 | import { connect } from 'react-redux'; 9 | import * as actionCreators from './actions'; 10 | import { bindActionCreators } from 'redux'; 11 | // import { getInfoTileProduct, getImageURL } from '../../../../reducers/rootReducer'; 12 | import ListView from './ListView'; 13 | 14 | const mapStateToProps = state => { 15 | return { 16 | 17 | } 18 | }; 19 | 20 | const mapDispatchToProps = (dispatch) => { 21 | return { 22 | actions: bindActionCreators(actionCreators, dispatch) 23 | }; 24 | } 25 | 26 | const ListViewContainer = connect(mapStateToProps, mapDispatchToProps)(ListView); 27 | 28 | export default ListViewContainer; 29 | 30 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Product/InfoTile/InfoTileContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file InfoFileContainer.js 3 | * A container component for the Product/InfoTile component which displays product 4 | * information from and SFCC instance. 5 | */ 6 | 7 | import { connect } from 'react-redux'; 8 | import * as actionCreators from './actions'; 9 | import { bindActionCreators } from 'redux'; 10 | import { getInfoTileProduct, getImageURL } from '../../../../reducers/rootReducer'; 11 | import InfoTile from './InfoTile'; 12 | 13 | const mapStateToProps = state => { 14 | return { 15 | infoTile: { 16 | product: getInfoTileProduct(state), 17 | imageURL: getImageURL(state) 18 | } 19 | }; 20 | }; 21 | 22 | const mapDispatchToProps = (dispatch) => { 23 | return { 24 | actions: bindActionCreators(actionCreators, dispatch) 25 | }; 26 | } 27 | 28 | const InfoTileContainer = connect(mapStateToProps, mapDispatchToProps)(InfoTile); 29 | 30 | export default InfoTileContainer; 31 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/OCAPIService/Authorize/AuthorizeService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file AuthorizeService.js 3 | * @fileoverview - Provides authorization service call setup and execution for 4 | * use with the OCAPI API and an authorization server used to provide OAuth 2.0 5 | * bearer tokens. 6 | */ 7 | 8 | import { apiConfig } from '../../../config/apiConfig'; 9 | 10 | /** 11 | * @class AuthorizeService 12 | * @classdesc A service class used for making calls to the OCAPI APIs which 13 | * require authorization bearer tokens or JWT tokens for access to needed 14 | * resources. 15 | */ 16 | export default class AuthorizeService { 17 | 18 | /** 19 | * @constructor 20 | */ 21 | constructor() { 22 | 23 | } 24 | 25 | /** 26 | * Returns the credentials needed to make a call to the authorization server 27 | */ 28 | getAuthServerConfig() { 29 | return (apiConfig.OCAPI.API.meta && apiConfig.OCAPI.API.meta.authServer) ? 30 | apiConfig.OCAPI.API.meta.authServer : null; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/models/UserProfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * UserProfile.js 5 | * Data model class to define the structure of the user profile object. 6 | */ 7 | 8 | 9 | export default class UserProfile { 10 | 11 | constructor(params) { 12 | // Ititiate any parameter fields that were passed to the constructor. 13 | this.address = { 14 | city: params && params.city ? params.city : '', 15 | state: params && params.state ? params.state : '', 16 | } 17 | this.fName = params && params.firstName ? params.firstName : ''; 18 | this.lName = params && params.lastName ? params.lastName : ''; 19 | 20 | this.imageLoc = ''; 21 | } 22 | 23 | // User home city. 24 | get city() {return this.city;} 25 | set city(city) {this.city = city;} 26 | // User first name 27 | get firstName() {return this.fName;} 28 | set firstName(firstName) {this.fName = firstName;} 29 | // Last name 30 | get lastName() {return this.lName;} 31 | set lastName(lastName) {this.lName = lastName;} 32 | } 33 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/reducers/homeReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * homeReducer.js 3 | * Used to handle changes in state for actions that affect the CollegeTools 4 | * component. 5 | */ 6 | 7 | import * as actionTypes from '../actionTypes'; 8 | 9 | /** 10 | * DEFAULT_STATE - Define a default state object in case on is not passed in. 11 | * @type {Object} 12 | */ 13 | const DEFAULT_STATE = {sceneTransistion: false}; 14 | 15 | /** 16 | * Reducer function for the Home scene 17 | * @param {Object} [state=DEFAULT_STATE] - The current state object for the app. 18 | * @param {Object} action - The object holding the action information that was emmited. 19 | * @return {Object} - The updated state object. 20 | */ 21 | export default function(state = DEFAULT_STATE, action) { 22 | switch (action.type) { 23 | case actionTypes.SCENE_TRANSITION: 24 | return {...state, onLogging: true}; 25 | default: 26 | return state; 27 | } 28 | } 29 | 30 | // Selectors (mapStateToProps) 31 | export const getHome = ({onLogging}) => ({ 32 | onLogging 33 | }); 34 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/configureStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file configureStore.js 3 | * @author Galen Goforth -- galengoforth@gmail.com 4 | * @desc This is used to set the options for the store in order to set the setup 5 | * the state of the app., and to apply middleware to any calls that are being 6 | * emitted to the store. 7 | */ 8 | 9 | import {createStore, applyMiddleware, compose} from 'redux'; 10 | import promise from 'redux-promise'; 11 | import {createLogger} from 'redux-logger'; 12 | import thunk from 'redux-thunk'; 13 | import rootReducer from './reducers/rootReducer'; 14 | 15 | /** 16 | * Configure the options for the store. 17 | * @param {Object} initialState - The initial app state when the store instance 18 | * is created. 19 | * @return {Store} 20 | */ 21 | export default function configureStore(initialState: any = undefined) { 22 | const logger = createLogger(); 23 | 24 | /* Apply middleware here */ 25 | const enhancer = compose(applyMiddleware(promise, thunk, logger)); 26 | 27 | return createStore(rootReducer, initialState, enhancer); 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Galen Goforth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/reducers/rootReducer.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import homeReducer, * as fromHome from './homeReducer'; 3 | import infoTileReducer, * as fromInfoTile from './infoTileReducer'; 4 | import userAccountReducer, * as fromUserAccount from './userAccountReducer'; 5 | import categoryTreeReducer, * as fromCategoryTree from './categoryTreeReducer'; 6 | 7 | export default rootReducer = combineReducers({ 8 | homeReducer, 9 | infoTileReducer, 10 | userAccountReducer, 11 | categoryTreeReducer 12 | }); 13 | 14 | /** 15 | * Export the different states from each reducer so that these will be accessible through out 16 | * the global scope of the application. 17 | * 18 | * Example: 19 | * export const getNavigation = state => fromNavigation.getNavigation(state.navigationReducer); 20 | */ 21 | export const getHome = state => fromHome.getHome(state.homeReducer); 22 | export const getInfoTileProduct = (state) => fromInfoTile.getInfoTileProduct(state.infoTileReducer); 23 | export const getImageURL = (state) => fromInfoTile.getImageURL(state.infoTileReducer); 24 | export const getCategoryTree = state => fromCategoryTree.getCategoryTree(state.categoryTreeReducer); 25 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/ProductSearchResult.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductSearchResult.js 3 | * @fileoverview - ProductSearchResult OCAPI document (v_18.7) class 4 | */ 5 | 6 | /** 7 | * @class ProductSearchResult 8 | * @classdesc Provides a class to represent the result of a ProductSearch 9 | * request to the Open Commerce API. 10 | */ 11 | export default class ProductSearchResult { 12 | /** 13 | * @constructor 14 | */ 15 | constructor(args={}) { 16 | this._count = args.count || 0; 17 | this._hits = args.hits || []; 18 | this._next = args.next || ''; 19 | this._previous = args.previous || ''; 20 | this._query = args.query || ''; 21 | this._refinements = args.refinements || []; 22 | this._searchPhraseSuggestion = args.search_phrase_suggestion || 23 | new Suggestion(); 24 | this._select = args.select || ''; 25 | this._selectedRefinements = args.selected_refinements || new Map(); 26 | this._selectedSortingOption = args.selected_sorting_option || ''; 27 | this._sortingOptions = args.sorting_options || ''; 28 | this._start = args.start || 0; 29 | this._total = args.total || 0; 30 | } 31 | 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/reducers/categoryTreeReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file categoryTreeReducer.js 3 | * @desc Merges in changes to the state for the ProductCategoryTree component 4 | * which is used in the app's main Navbar component (hamburger menu). 5 | */ 6 | 7 | import * as actionTypes from '../actionTypes'; 8 | 9 | 10 | const DEFAULT_STATE = { 11 | isLoadingCatagories: false, 12 | category: { 13 | categories: [] 14 | } 15 | }; 16 | 17 | export default function categoryTreeReducer(state = DEFAULT_STATE, action = {}) { 18 | switch (action.type) { 19 | // Category -- Get 20 | case actionTypes.REQUEST_RESOURCE_CATEGORY_BY_ID: 21 | return { 22 | ...state, 23 | isLoadingCatagories: true 24 | }; 25 | 26 | case actionTypes.RECEIVED_RESOURCE_CATEGORY_BY_ID: 27 | return { 28 | isLoadingCatagories: false, 29 | category: action.category 30 | }; 31 | 32 | case actionTypes.FAILED_RESOURCE_CATEGORY_BY_ID: 33 | return { 34 | ...state, 35 | isLoadingCatagories: false 36 | } 37 | default: 38 | return state 39 | } 40 | } 41 | 42 | // Selectors 43 | export const getCategoryTree = state => { 44 | return state.category; 45 | }; 46 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/OCAPIService/__tests__/CategoryTests.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import OCAPIService from '../OCAPIService'; 3 | import OCAPICallInfo from '../OCAPICallInfo'; 4 | 5 | describe('OCAPIService.setupCall() for making a request for categories.', () => { 6 | let svc, setupResult, expectedResult; 7 | 8 | // Test suite setup. 9 | beforeEach(() => { 10 | svc = new OCAPIService(); 11 | 12 | // Setup the expected Result. 13 | expectedResult = new OCAPICallInfo(); 14 | expectedResult.httpVerb = 'GET'; 15 | expectedResult.headers = { 'Content-Type': 'application/json' }; 16 | }); 17 | 18 | // Test suite tear down. 19 | afterAll(() => { 20 | svc = null; 21 | expectedResult = null; 22 | setupResult = null; 23 | }, 3000); 24 | 25 | // Test successfull setup call. 26 | describe('Test successfull OCAPI call setup for request of product categories.', () => { 27 | beforeEach(() => { 28 | setupResult = svc.setupCall('categories', 'get', {categoryID: 'root', levels: 2}) 29 | }); 30 | 31 | test('Verify that we get the correct setup for a \'Get Category\' API call', () => { 32 | expect(setupResult.error).toBeFalsy(); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /SFCC-RN-Components/src/reducers/__tests__/categoryTreeReducerTests.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import categoryTreeReducer from '../categoryTreeReducer'; 3 | import { Reducer } from 'redux-testkit'; 4 | import * as actionTypes from '../../actionTypes'; 5 | import Category from '../../lib/documents/Category'; 6 | import CategoryMock from '../../lib/OCAPIService/__mocks__/CategoryMock'; 7 | 8 | const DEFAULT_STATE = { 9 | isLoadingCatagories: false, 10 | category: {categories: []} 11 | }; 12 | 13 | describe('src/reducers/categoryTreeReducer.js -- Tests', () => { 14 | let catTree; 15 | 16 | beforeEach(() => { 17 | catTree = categoryTreeReducer(); 18 | }); 19 | 20 | // Test that the default state is returned when no action is passed. 21 | it('should have an initial state defined.', () => { 22 | expect(catTree).toEqual(DEFAULT_STATE); 23 | }); 24 | 25 | // Test addition of a CategoryResult instance to the state. 26 | it('should add a category_result document instance to the state.', () => { 27 | const category = new Category(CategoryMock); 28 | const action = { type: actionTypes.RECEIVED_RESOURCE_CATEGORY_BY_ID, category: category} 29 | Reducer(categoryTreeReducer).expect(action).toReturnState({...DEFAULT_STATE, category}); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/ImageGroup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ImageGroup.js 3 | * @fileoverview - ImageGroup OCAPI document class. 4 | */ 5 | 6 | import Image from './Image'; 7 | import VariationAttribute from './VariationAttribute'; 8 | 9 | /** 10 | * @class ImageGroup 11 | */ 12 | export default class ImageGroup { 13 | /** 14 | * 15 | * @param {Object} [args] - An optional arguments Object holding any property values 16 | * to be set on instance initialization. 17 | */ 18 | constructor(args) { 19 | /** @type {Array} */ 20 | this._images = args && args.images ? args.images.map(img => new Image(img)) : []; 21 | /** @type {Array} */ 22 | this._variationAttributes = args && args.variation_attributes ? 23 | args.variation_attributes.map(va => new VariationAttribute(va)) : []; 24 | /** @type {string} */ 25 | this._viewType = args && args.view_type ? args.view_type : ''; 26 | } 27 | 28 | get images() {return this._images} 29 | set images(value) {this._images = value} 30 | get variationAttributes() {return this._variationAttributes} 31 | set variationAttributes(value) {this._variationAttributes = value} 32 | get viewType() {return this._viewType} 33 | set viewType(value) {this._viewType = value} 34 | } 35 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/UserAccount/UserAccountContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UserAccountContainer.js 3 | * Container component for handling lifecycle events for the component lifecycle, 4 | * and connecting the component to the application state. 5 | */ 6 | import { connect } from 'react-redux'; 7 | import { bindActionCreators } from 'redux'; 8 | import * as actionCreators from './actions'; 9 | import UserAccount from './UserAccount'; 10 | import { getNavigation, getProfile } from '../../reducers/rootReducer'; 11 | 12 | const mapStateToProps = (state) => { 13 | console.log('isEdit: ' + state.profileReducer.isEdit); 14 | return { 15 | isEdit: state.profileReducer.isEdit, 16 | userProfile: state.profileReducer.userProfile, 17 | profileLoadError: state.profileReducer.profileLoadError, 18 | profileSaveError: state.profileReducer.profileSaveError, 19 | isLoadingProfile: state.profileReducer.isLoadingProfile, 20 | isSavingProfile: state.profileReducer.isSavingProfile 21 | } 22 | }; 23 | 24 | const mapDispatchToProps = (dispatch) => { 25 | return { 26 | actions: bindActionCreators(actionCreators, dispatch) 27 | }; 28 | } 29 | 30 | const UserAccountContainer = connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(UserAccount); 34 | 35 | export default UserAccountContainer; 36 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/OCAPIService/Authorize/__tests__/AuthTokenTests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file AuthTokenTests.js 3 | * @fileoverview - Unit tests for the OAuth 2.0 authorization process through 4 | * the authorization service provided by the /lib/OCAPIService/Authorize/** files. 5 | */ 6 | 7 | import expect from 'expect'; 8 | import AuthorizeService from '../AuthorizeService'; 9 | 10 | /** @description - SUITE == Class: OCAPIService */ 11 | describe('Testing: Class: OCAPIService.Authorization', () => { 12 | 13 | /** @description - SUITE == Method: getAuthServerConfig() */ 14 | describe('Testing method: getAuthServerConfig():', () => { 15 | let svc, mockConfig; 16 | 17 | beforeEach(() => { 18 | svc = new AuthorizeService(); 19 | mockConfig = { 20 | userName: 'appUser', 21 | userPassword: 'appPassword' 22 | }; 23 | }); 24 | 25 | /** @description - TEST: Correct User Name */ 26 | test('...should succeed: The stored username is returned.', 27 | () => { 28 | const serviceConfig = svc.getAuthServerConfig('meta'); 29 | expect(serviceConfig.userName).toEqual(mockConfig.userName); 30 | } 31 | ); 32 | 33 | /** @description - TEST: Correct Password */ 34 | test('...should succeed: The stored password is returned.', 35 | () => { 36 | const serviceConfig = svc.getAuthServerConfig('meta'); 37 | expect(serviceConfig.userPassword).toEqual(mockConfig.userPassword); 38 | } 39 | ); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /SFCC-RN-Components/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | 7 | import configureStore from './src/configureStore'; 8 | import React, {Component} from 'react'; 9 | import {Router, Scene, ActionConst, Actions} from 'react-native-router-flux'; 10 | import {Provider, connect} from 'react-redux'; 11 | import HomeContainer from './src/components/Home/HomeContainer'; 12 | import InfoTileContainer from './src/components/SFCC/Product/InfoTile/InfoTileContainer'; 13 | import NavbarContainer from './src/components/Navbar/NavbarContainer'; 14 | import UserAccountContainer from './src/components/UserAccount/UserAccountContainer'; 15 | 16 | const RouterWithRedux = connect()(Router); 17 | const store = configureStore(); 18 | 19 | const Scenes = Actions.create( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | export default class App extends Component { 32 | render() { 33 | return ( 34 | 35 | 36 | 37 | 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/ImageCarousel/ThumbnailImage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ThumbnailImage.js 3 | * A Stateless function component that holds a thumnail image. 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import { View, Image, TouchableHighlight, StyleSheet } from 'react-native'; 8 | import PropTypes from 'prop-types'; 9 | 10 | const ThumbnailImage = (props) => { 11 | const imgSrc = props.src ? 12 | props.src : require('../../../assets/images/missing_img.png'); 13 | const imgStyle = !props.imgStyle ? tiStyles.imgStyle : props.imgStyle; 14 | const viewStyle = !props.viewStyle ? tiStyles.viewStyle : props.viewStyle; 15 | 16 | 17 | return ( 18 | 19 | 20 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | /** 29 | * Default styles if not passed in as props. 30 | */ 31 | const tiStyles = StyleSheet.create({ 32 | imgStyle: { 33 | width: 55, 34 | height: 75, 35 | alignSelf: 'stretch' 36 | }, 37 | viewStyle: { 38 | width: 65, 39 | height: 75, 40 | alignItems: 'stretch', 41 | padding: 5 42 | } 43 | }); 44 | 45 | ThumbnailImage.propTypes = { 46 | thumbnailSelected: PropTypes.func.isRequired, 47 | src: PropTypes.object, 48 | imgStyle: PropTypes.any, 49 | viewStyle: PropTypes.any 50 | }; 51 | 52 | export default ThumbnailImage = ThumbnailImage; 53 | 54 | 55 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/Basket.js: -------------------------------------------------------------------------------- 1 | import OCAPIService from "../OCAPIService/OCAPIService"; 2 | 3 | 4 | /** 5 | * @class Basket 6 | * @classdesc Provides an OCAPI wrapper for interacting with the Basket resource type. 7 | */ 8 | export default class Basket { 9 | /** 10 | * @constructor 11 | */ 12 | constructor(basketID) { 13 | this._service = new OCAPIService(); 14 | 15 | if (basketID) { 16 | this.ID = basketID; 17 | } 18 | 19 | 20 | } 21 | 22 | /** ======================================================================== 23 | * Static Class Members 24 | * ========================================================================*/ 25 | 26 | 27 | /** ======================================================================== 28 | * Private Class Members 29 | * ========================================================================*/ 30 | 31 | 32 | /** ======================================================================== 33 | * Public Class Members 34 | * ========================================================================*/ 35 | 36 | getProductLineItems(shipment) { 37 | 38 | } 39 | 40 | getGiftCirtificateLineItems(shipment) { 41 | 42 | } 43 | 44 | getAllLineItems(shipment) { 45 | 46 | } 47 | 48 | getShipments() { 49 | 50 | } 51 | 52 | createShipment(shipmentID) { 53 | 54 | } 55 | 56 | getAppliedDiscounts() { 57 | 58 | } 59 | 60 | addProduct(productID, quantity) { 61 | 62 | } 63 | 64 | addGiftCertificatie(giftCertificateID) { 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Product/ListView/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ListView/actions.js 3 | * @fileoverview - Exports redux action creation methods for modifying the 4 | * application scope state object from the ListView component. 5 | */ 6 | 7 | import OCAPIService from '../../../../lib/OCAPIService/OCAPIService'; 8 | import { 9 | REQUEST_RESOURCE_PRODUCT_BY_ID, 10 | RECEIVED_RESOURCE_PRODUCT_BY_ID, 11 | FAILED_RESOURCE_PRODUCT_BY_ID, 12 | REQUEST_RESOURCE_PRODUCT_IMAGES, 13 | RECEIVED_RESOURCE_PRODUCT_IMAGES, 14 | FAILED_RESOURCE_PRODUCT_IMAGES 15 | } from '../../../../actionTypes'; 16 | 17 | /* ========================================================================== * 18 | * Async Action Creators 19 | * ========================================================================== */ 20 | 21 | export const requestProductSearch = (productID) => { 22 | return (dispatch) => { 23 | 24 | } 25 | }; 26 | 27 | /* ========================================================================== * 28 | * Synchronous Action Creators 29 | * ========================================================================== */ 30 | 31 | // Product / images 32 | export const requestProductSearch = (productID) => { 33 | return { 34 | type: REQUEST_RESOURCE_PRODUCT_IMAGES, 35 | product: productID 36 | }; 37 | }; 38 | 39 | export const receivedProductSearch = (product) => { 40 | return { 41 | type: RECEIVED_RESOURCE_PRODUCT_IMAGES, 42 | product: product 43 | }; 44 | }; 45 | 46 | export const failedProductSearch = (err) => { 47 | return { 48 | type: FAILED_RESOURCE_PRODUCT_IMAGES, 49 | error: err 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Category/ProductCategoryTree/ProductCategoryTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductCategoryTree.js 3 | * @fileoverview - The ProductCategoryTree file is a React Native component used 4 | * as part of the Navbar component, but could be adopted for use in other 5 | * places in the app if needed. 6 | */ 7 | 8 | import React, { Component } from 'react'; 9 | import { View, Text, } from 'react-native'; 10 | import PropTypes from 'prop-types'; 11 | import { MenuCategory } from "./MenuCategory"; 12 | 13 | /** 14 | * @class 15 | * @description - Renders SFCC product categories as an expandable tree 16 | * type component for use in the main navigation menu. 17 | */ 18 | export default class ProductCategoryTree extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.props = props; 22 | 23 | this.state = { 24 | catagories: props.catagories || [] 25 | }; 26 | }; 27 | 28 | /** 29 | * @function _renderCategory - Render a single category. 30 | */ 31 | _renderCategory() { 32 | 33 | } 34 | 35 | /** 36 | * @function _renderCategories - Renders each category in the component state 37 | * as an item in the Category tree. Loops through each top level category, 38 | * and then recursivly calls renderCategory to fill in all of the child 39 | * catagories. 40 | */ 41 | _renderCatagories() { 42 | const cats = this.state.catagories; 43 | 44 | } 45 | 46 | render() { 47 | const cats = this._renderCatagories(); 48 | 49 | return ( 50 | 51 | 52 | 53 | ); 54 | } 55 | } 56 | 57 | ProductCategoryTree.propTypes = { 58 | catagories: PropTypes.array 59 | }; 60 | 61 | 62 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/Navbar/NavbarContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NavbarContainer.js 3 | * Container component for handling lifecycle events for the component lifecycle, 4 | * and connecting the component to the application state. 5 | */ 6 | import React, { Component } from 'react'; 7 | import { connect } from 'react-redux'; 8 | import { bindActionCreators } from 'redux'; 9 | import * as categoryActions from '../SFCC/Category/ProductCategoryTree/actions'; 10 | import * as actionCreators from '../UserAccount/actions'; 11 | import Navbar from './Navbar'; 12 | import MenuCategory from '../SFCC/Category/ProductCategoryTree/MenuCategory'; 13 | import { getProfile, getCategoryTree } from '../../reducers/rootReducer'; 14 | 15 | const mapDispatchToProps = (dispatch) => { 16 | return { 17 | actions: bindActionCreators(actionCreators, dispatch), 18 | categoryActions: bindActionCreators(categoryActions, dispatch) 19 | }; 20 | }; 21 | 22 | const mapStateToProps = (state) => { 23 | let menuItems = [{id: 'home', title: 'yesir'}]; 24 | const catTree = getCategoryTree(state).categories; 25 | 26 | // If categories were returned from the API call, then add these to the 27 | // menuItems array. 28 | if (catTree && catTree.length) { 29 | catTree.forEach(cat => { 30 | menuItems.push({ 31 | renderProps: () => ( 32 | 37 | ) 38 | }); 39 | }); 40 | } 41 | 42 | return { 43 | menuItems: menuItems, 44 | pageTitle: 'hello' 45 | }; 46 | }; 47 | 48 | const NavbarContainer = connect(mapStateToProps, mapDispatchToProps)(Navbar); 49 | 50 | export default NavbarContainer; 51 | -------------------------------------------------------------------------------- /SFCC-RN-Components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfcc-rn-components", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "babel-eslint": "^8.2.2", 7 | "babel-plugin-jest-hoist": "^23.0.1", 8 | "eslint": "^4.19.1", 9 | "eslint-plugin-react": "^7.7.0", 10 | "eslint-plugin-react-native": "^3.2.1", 11 | "expect": "^22.0.3", 12 | "glob": "*", 13 | "jest-cli": "^22.4.3", 14 | "jest-environment-node": "^22.4.3", 15 | "jest-expo": "23.0.0", 16 | "react-native-scripts": "^1.11.1", 17 | "react-test-renderer": "16.0.0", 18 | "redux-mock-store": "^1.3.0", 19 | "redux-testkit": "^1.0.6" 20 | }, 21 | "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js", 22 | "scripts": { 23 | "start": "react-native-scripts start", 24 | "eject": "react-native-scripts eject", 25 | "android": "react-native-scripts android", 26 | "ios": "react-native-scripts ios", 27 | "test": "node_modules/jest/bin/jest.js", 28 | "test:watch": "node node_modules/jest/bin/jest.js --watch" 29 | }, 30 | "jest": { 31 | "preset": "jest-expo", 32 | "testEnvironment": "node", 33 | "transformIgnorePatterns": [ 34 | "node_modules/(?!react-native|expo|react-navigation|native-base-shoutem-theme|@shoutem/theme|@shoutem/animation|@shoutem/ui|tcomb-form-native|mobx-react|react-native-router-flux)" 35 | ] 36 | }, 37 | "dependencies": { 38 | "babel-core": "^6.0.0", 39 | "expo": "^23.0.4", 40 | "react": "16.0.0", 41 | "react-native": "0.50.3", 42 | "react-native-router-flux": "^4.0.0-beta.28", 43 | "react-redux": "^5.0.6", 44 | "redux": "^3.7.2", 45 | "redux-logger": "^3.0.6", 46 | "redux-promise": "^0.5.3", 47 | "redux-thunk": "^2.2.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Category/ProductCategoryTree/MenuCategory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MenuCategory.js 3 | */ 4 | 5 | import React, { Component } from 'react'; 6 | import { 7 | Animated, 8 | Text, 9 | View, 10 | TouchableHighlight, 11 | StyleSheet 12 | } from 'react-native'; 13 | import { navbarStyles, colors } from '../../../../styles/globalStyles'; 14 | import * as actions from './actions'; 15 | 16 | /** 17 | * Stateless functional component for rendering individual catagories in the 18 | * app hamburger menu (Navbar component). 19 | * @param {Object} props 20 | */ 21 | const MenuCategory = (props) => { 22 | const childMenuLevel = props.menuLevel + 1; 23 | const children = props.category && 24 | props.category.categories && 25 | props.category.categories.length ? 26 | props.category.categories.map((childCategory) => ( 27 | 32 | )) : 33 | [()]; 34 | 35 | 36 | this.onSelect = (ID) => { 37 | console.log(ID); 38 | /** 39 | * @todo --> Navigate to the product list page for the selected category. 40 | */ 41 | }; 42 | 43 | return ( 44 | 45 | { 46 | this.onSelect(props.category.ID); 47 | } 48 | }> 49 | 50 | 51 | {props.category.name} 52 | 53 | 54 | 55 | 56 | {children} 57 | 58 | 59 | ); 60 | }; 61 | 62 | export default MenuCategory = MenuCategory; 63 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/Home/Home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Home.jsx 3 | * @author Galen Goforth -- galengoforth@gmail.com 4 | * @desc A home page example that utilizes the SFCC React Native API and 5 | * the included set of components to show SFCC Business Manager 6 | * created content sections. 7 | */ 8 | 9 | import React, {Component} from 'react'; 10 | import { View, Text, StyleSheet, Dimensions } from 'react-native'; 11 | import {connect} from 'react-redux'; 12 | import {Actions} from 'react-native-router-flux'; 13 | import Routes from '../../menuItems'; 14 | import NavbarContainer from '../Navbar/NavbarContainer'; 15 | import InfoTileContainer from '../SFCC/Product/InfoTile/InfoTileContainer'; 16 | import InfoTile from '../SFCC/Product/InfoTile/InfoTile'; 17 | 18 | const screenSize = Dimensions.get('window'); 19 | 20 | 21 | class Home extends Component { 22 | constructor(props) { 23 | super(props); 24 | const screenSize = Dimensions.get('window'); 25 | 26 | this.state = { 27 | width: screenSize.width, 28 | height: screenSize.height, 29 | routes: Routes 30 | }; 31 | } 32 | 33 | componentWillReceiveProps(nextProps) { 34 | this.setState((prevState, nextProps) => { 35 | // Recieve props from other components. 36 | }); 37 | } 38 | 39 | render() { 40 | return ( 41 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | const homeStyles = StyleSheet.create({ 56 | mainView: { 57 | flexDirection: 'column', 58 | backgroundColor: '#cccccc' 59 | } 60 | }); 61 | 62 | export default connect(({routes}) => ({routes}))(Home); 63 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/actionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * actionTypes.js 3 | * Define action types for non-navigation actions that need to be emitted 4 | * in order to update the state of the application. 5 | */ 6 | 7 | /******** OTHER ACTION TYPES **********/ 8 | export const SCENE_TRANSITION = 'sceneTransition'; 9 | 10 | /******** ACTION CONSTANTS FOR THE USER'S PROFILE **********/ 11 | export const UPDATE_PROFILE = 'UPDATE_PROFILE'; 12 | 13 | export const REQUEST_LOAD_PROFILE = 'REQUEST_LOAD_PROFILE'; 14 | export const RECEIVED_LOAD_PROFILE = 'RECEIVED_LOAD_PROFILE'; 15 | export const FAILED_LOAD_PROFILE = 'FAILED_LOAD_PROFILE'; 16 | 17 | export const REQUEST_SAVE_PROFILE = 'REQUEST_SAVE_PROFILE'; 18 | export const RECEIVED_SAVE_PROFILE = 'RECEIVED_SAVE_PROFILE'; 19 | export const FAILED_SAVE_PROFILE = 'FAILED_SAVE_PROFILE'; 20 | 21 | /******** ACTION CONSTANTS FOR PRODUCT REQUESTS **********/ 22 | export const REQUEST_RESOURCE_PRODUCT_BY_ID = 'REQUEST_RESOURCE_PRODUCT_BY_ID'; 23 | export const RECEIVED_RESOURCE_PRODUCT_BY_ID = 'RECEIVED_RESOURCE_PRODUCT_BY_ID'; 24 | export const FAILED_RESOURCE_PRODUCT_BY_ID = 'FAILED_RESOURCE_PRODUCT_BY_ID'; 25 | 26 | // Product Images 27 | export const REQUEST_RESOURCE_PRODUCT_IMAGES = 'REQUEST_RESOURCE_PRODUCT_IMAGES'; 28 | export const RECEIVED_RESOURCE_PRODUCT_IMAGES = 'RECEIVED_RESOURCE_PRODUCT_IMAGES'; 29 | export const FAILED_RESOURCE_PRODUCT_IMAGES = 'FAILED_RESOURCE_PRODUCT_IMAGES'; 30 | 31 | // Product Search 32 | export const REQUEST_RESOURCE_PRODUCT_SEARCH = 'REQUEST_RESOURCE_PRODUCT_SEARCH'; 33 | export const RECEIVED_RESOURCE_PRODUCT_SEARCH = 'RECEIVED_RESOURCE_PRODUCT_SEARCH'; 34 | export const FAILED_RESOURCE_PRODUCT_SEARCH = 'FAILED_RESOURCE_PRODUCT_SEARCH'; 35 | 36 | /******** ACTION CONSTANTS FOR Category REQUESTS **********/ 37 | export const REQUEST_RESOURCE_CATEGORY_BY_ID = 'REQUEST_RESOURCE_CATEGORY_BY_ID'; 38 | export const RECEIVED_RESOURCE_CATEGORY_BY_ID = 'RECEIVED_RESOURCE_CATEGORY_BY_ID'; 39 | export const FAILED_RESOURCE_CATEGORY_BY_ID = 'FAILED_RESOURCE_CATEGORY_BY_ID'; 40 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/config/appConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file appConfig.js 3 | * Defines application configuration settings and provides helper functions for 4 | * easily getting the needed information. 5 | */ 6 | 7 | /**************************************************** 8 | // Environment specific app settings. 9 | *****************************************************/ 10 | export const appConfig = { 11 | /** 12 | * Operation Mode 13 | * This value is meant to control certain settings that are set differently 14 | * for development purposes than they need to be for production purposes. 15 | * Options. 16 | */ 17 | 18 | // instanceType: 'production', 19 | // instanceType: 'staging', 20 | // instanceType: 'qa', 21 | instanceType: 'development', 22 | 23 | // Definces the settings for each instance type. 24 | environment: { 25 | 26 | // Setup for app development. 27 | development: { 28 | // This can be set to override the initial path to images that is returned 29 | // by OCAPI api calls for product images. 30 | overrides: { 31 | productImage: { 32 | /** 33 | * @todo : Refactor to get rid of the isOverride property since there 34 | * is already null checking. 35 | */ 36 | isOverride: true, 37 | pathToReplace: '/AAFF_S25/', 38 | pathReplacement: '/AAFF_STG/' 39 | } 40 | } 41 | }, 42 | 43 | // Setup for a production release. 44 | production: { 45 | 46 | } 47 | }, 48 | 49 | /**************************************************** 50 | // Settings for UI settings. 51 | *****************************************************/ 52 | 53 | // Configuration settings for the navbar menu in the expanded state. 54 | sidebar: { 55 | 56 | // Defines the proportion of the screen that will not be covered by the 57 | // sidebar menu. 58 | marginRight: .3, 59 | 60 | // Define the time (in ms) that it takes to toggle the sidebar menu. 61 | toggleTime: 300 62 | }, 63 | 64 | siteID: 'SiteGenesis' 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/UserAccount/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file UserAccount/actions.js 3 | * @fileoverview - Exports redux action creation methods for modifying the 4 | * application scope state from the UserAccount component. 5 | */ 6 | 7 | import { 8 | REQUEST_SAVE_PROFILE, 9 | RECEIVED_SAVE_PROFILE, 10 | FAILED_SAVE_PROFILE, 11 | REQUEST_LOAD_PROFILE, 12 | RECEIVED_LOAD_PROFILE, 13 | FAILED_LOAD_PROFILE, 14 | UPDATE_PROFILE 15 | } from '../../actionTypes'; 16 | import ReduxThunk from 'redux-thunk'; 17 | import {Actions} from 'react-native-router-flux'; 18 | import DeviceStorageService from '../../services/deviceStorage/DeviceStorageService'; 19 | import { dispatch } from 'react-redux'; 20 | 21 | /* ========================================================================== * 22 | * Async Action Creators 23 | * ========================================================================== */ 24 | 25 | export const saveProfile = (user) => { 26 | return (dispatch) => { 27 | const svc = new AsyncStorageService(); 28 | 29 | // Dispatch notification that a save is taking place. 30 | dispatch(requestSaveProfile); 31 | 32 | // Write the updated profile to the service. 33 | const wrPromise = svc.write(['profile', stuent]); 34 | 35 | // Handle Promise resolve and fails. 36 | wrPromise.then((res) => { 37 | dispatch(recievedSaveProfile(user)); 38 | }, (error) => { 39 | dispatch(failedSaveProfile(error)); 40 | }); 41 | } 42 | }; 43 | 44 | /* ========================================================================== * 45 | * Synchronous Action Creators 46 | * ========================================================================== */ 47 | 48 | 49 | export const requestSaveProfile = (user) => { 50 | return { 51 | type: REQUEST_SAVE_PROFILE, 52 | user: user 53 | } 54 | } 55 | 56 | export const updateProfile = () => { 57 | return { 58 | type: UPDATE_PROFILE 59 | } 60 | }; 61 | 62 | export const recievedSaveProfile = (user) => { 63 | 64 | } 65 | 66 | export const failedSaveProfile = (user) => { 67 | 68 | } 69 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/Image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Image.js 3 | * @fileoverview - Class file for an OCAPI Image document. 4 | */ 5 | 6 | /** 7 | * @class Image 8 | */ 9 | export default class Image { 10 | /** 11 | * 12 | * @param {Object} [args] - An optional argument object with properties to match the response 13 | * fields of a image type in an OCAPI call. 14 | * @param {String} [args.alt] - Image alternate text to be displayed if image is not rendered. 15 | * @param {String} [args.dis_base_link] - Image server's base URI link. 16 | * @param {String} [args.link] - The URI link to the image's location. 17 | * @param {String} [args.title] - An optional image title. 18 | */ 19 | constructor(args) { 20 | /** @type {String} */ 21 | this._alt = args && args.alt ? args.alt : ''; 22 | /** @type {String} */ 23 | this._disBaseLink = args && args.dis_base_link ? args.dis_base_link : ''; 24 | /** @type {String} */ 25 | this._link = args && args.link ? args.link : ''; 26 | /** @type {String} */ 27 | this._title = args && args.title ? args.title : ''; 28 | } 29 | 30 | get link() { return this._link } 31 | set link(value) { this._link = value } 32 | get disBaseLink() { return this._disBaseLink } 33 | set disBaseLink(value) { this._disBaseLink = value } 34 | get title() { return this._title } 35 | set title(value) { this._title = value } 36 | get alt() { return this._alt } 37 | set alt(value) { this._alt = value } 38 | 39 | /** 40 | * Returns an object literal representation of the class instance that matches 41 | * the OCAPI specification (v18.6) in order to provide an easy way to include 42 | * the document instance in POST/PUT API calls to OCAPI. 43 | * 44 | * @return {Object} - The object literal representation of the class instance 45 | * according to the OCAPI document specification. 46 | */ 47 | getDocument() { 48 | const doc = { 49 | link: this._link, 50 | dis_base_link: this._disBaseLink, 51 | title: this._title, 52 | alt: this._alt 53 | }; 54 | 55 | return doc; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/OCAPIService/OCAPICallInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class OCAPICallInfo 3 | * @classdesc Class with properties for passing arguments to to the OCAPIService._fetchData 4 | * call in order to make a call to the Open Commerce API. 5 | * 6 | */ 7 | export default class OCAPICallInfo { 8 | /** 9 | * Creates an instance of OCAPICallInfo. 10 | * 11 | * @memberof OCAPICallInfo 12 | * @constructor 13 | */ 14 | constructor(args) { 15 | /** @type {function} */ 16 | this._apiCallFunction = args && args.apiCallFunction ? args.apiCallFunction : null; 17 | /** @type {string} */ 18 | this._httpVerb = args && args.httpVerb ? args.httpVerb : ''; 19 | /** @type {string} */ 20 | this._endpoint = args && args.endpoint ? args.endpoint : ''; 21 | /** @type {object} */ 22 | this._body = args && args.body ? args.body : {}; 23 | /** @type {object} */ 24 | this._headers = args && args.headers ? args.headers : {}; 25 | /** @type {boolean} */ 26 | this._error = args && args.error ? args.error : false; 27 | /** @type {string} */ 28 | this._errMsg = args && args.errMsg ? args.errMsg : 'ERROR in OCAPIService at setupCall'; 29 | } 30 | 31 | /* ======================================================================== 32 | * Public Class Members 33 | * ========================================================================*/ 34 | 35 | 36 | /* ======== Class Member Accessors and Mutators ======= */ 37 | 38 | get apiCallFunction() {return this._apiCallFunction;} 39 | get httpVerb() {return this._httpVerb;} 40 | get endpoint() {return this._endpoint;} 41 | get body() {return this._body;} 42 | get headers() {return this._headers;} 43 | get error() {return this._error;} 44 | get errMsg() {return this._errMsg;} 45 | 46 | set apiCallFunction(value) {this._apiCallFunction = value;} 47 | set httpVerb(value) {this._httpVerb = value;} 48 | set endpoint(value) {this._endpoint = value;} 49 | set body(value) {this._body = value;} 50 | set headers(value) {this._headers = value;} 51 | set error(value) {this._error = value;} 52 | set errMsg(value) {this._errMsg = value;} 53 | } 54 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/CategoryResult.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CategoryResult.js 3 | * @fileoverview - Provides a JS class for working with the OCAPI category_result 4 | * document type that is returned when getting categories with the Categories resource. 5 | */ 6 | 7 | import Category from './Category'; 8 | 9 | /** 10 | * @class CategoryResult 11 | * @classdesc Class definition for passing category_result document type data 12 | * between modules, and storing this information within application state in a 13 | * consistent manner. 14 | */ 15 | export default class CategoryResult { 16 | /** 17 | * @constructor 18 | * @param {Object} [args] - An optional arguments object for setting instance 19 | * properties at the time of class instantiation. 20 | * @param {Category} [args.data = []] - An array of returned Category class 21 | * instances. 22 | * @param {Number} [args.count = 0] -The number of returned documents. 23 | */ 24 | constructor(args) { 25 | /** @type {Number} */ 26 | this._count = args && args.count ? args.count : 0; 27 | /** @type {Category} */ 28 | this._data = args && args.data && Array.isArray(args.data) ? 29 | args.data.map((cat) => { 30 | return new Category(cat); 31 | }) : []; 32 | /** @type {Number} */ 33 | this._total = args && args.total ? args.total : 0; 34 | } 35 | 36 | get count() { return this._count } 37 | set count(value) { this._count = value } 38 | get total() { return this._total } 39 | set total(value) { this._total = value } 40 | get data() { return this._data } 41 | set data(value) { this._data = value } 42 | 43 | /** 44 | * Returns the class instance according to the OCAPI specification (with 45 | * snake_case variable names) for easy use when making a POST/PUT call to the 46 | * OCAPI endpoint. 47 | * 48 | * @return {Object} - Returns an object literal representing the class 49 | * instance according to the OCAPIl specification. 50 | */ 51 | getDocument() { 52 | const doc = { 53 | count: this._count, 54 | total: this._total, 55 | data: this._data 56 | }; 57 | 58 | return doc; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/OCAPIService/__tests__/ProductTests.js: -------------------------------------------------------------------------------- 1 | import expect from "expect"; 2 | import OCAPIService from "../OCAPIService"; 3 | import OCAPICallInfo from "../OCAPICallInfo"; 4 | 5 | // Test the setupCall method in the OCAPIService class. 6 | describe("OCAPIService.setupCall() for getting a single product.", () => { 7 | const svc = new OCAPIService(); 8 | let setupResult, expectedResult; 9 | 10 | beforeEach(() => { 11 | expectedResult = new OCAPICallInfo(); 12 | expectedResult.httpVerb = "GET"; 13 | expectedResult.headers = { "Content-Type": "application/json" }; 14 | setupResult = svc.setupCall("products", "get", { productID: "12345678" }); 15 | }); 16 | 17 | test("Verify Error flag is false for setup of 'Get Product' API calls", () => { 18 | expect(setupResult.error).toBeFalsy(); 19 | }); 20 | 21 | test("Verify the correct headers for 'Get Product' API calls", () => { 22 | expect(setupResult.headers).toEqual(expectedResult.headers); 23 | }); 24 | 25 | test("Verify the correct HTTP verb for 'Get Product' API calls", () => { 26 | expect(setupResult.httpVerb).toEqual(expectedResult.httpVerb); 27 | }); 28 | 29 | afterAll(() => { 30 | expectedResult = null; 31 | setupResult = null; 32 | }, 3000); 33 | }); 34 | 35 | // Test the setupCall method in the OCAPIService class. 36 | describe("OCAPIService.setupCall() for getting multiple products", () => { 37 | let svc, setupResult, expectedResult; 38 | const productIDs = []; 39 | 40 | beforeEach(() => { 41 | svc = new OCAPIService(); 42 | // Setup an expected result. 43 | expectedResult = new OCAPICallInfo({ 44 | headers: { "Content-Type": "application-json" }, 45 | httpVerb: "GET", 46 | endpoint: "sandbox.demandware.net/s/SITE-ID/dw/shop/v17_8/products/", 47 | body: {}, 48 | error: false, 49 | errorMessage: "" 50 | }); 51 | 52 | // Store the result of calling the setupCall method to the setupResult variable. 53 | setupResult = svc.setupCall("products", "get", { 54 | productID: ["12345678", "12345677", "12345676"] 55 | }); 56 | }); 57 | 58 | test("Verify that the endpoint for getting product information for multiple products is correctly formatted", () => { 59 | expect(setupResult.endpoint).toContain( 60 | "/products/" + productIDs.toString() 61 | ); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sfcc-react 2 | sfcc-react is a React Native mobile application that uses Sales Force Commerce Cloud or SFCC (formerly Demandware) and their Open Commerce API (OCAPI) to read and write to an SFCC instance. 3 | 4 | The app uses the Redux framework for global state management and redux-thunk for handling ASYNC action creation methods in order to make calls to OCAPI. This project was bootstrapped with [Create React Native App](https://github.com/react-community/create-react-native-app). 5 | 6 | ## Setup and Run 7 | To run this app you need to setup a connection to your SFCC sandbox instance. This can be done by changing the settings in the appConfig.js file: SFCC-RN-Components/src/config/appConfig.js. There are 2 main exports from this file that define the current compile time settings for the app. 8 | 9 | ### apiConfig 10 | The apiConfig object literal that is exported from the appConfig.js configuration file contains the information needed for your app to make calls to your SFCC instance using OCAPI. All of the OCAPI config is contained in a property of the apiConfig export simply named 'OCAPI'. There are several properties to this object property that allow for different setups: 11 | - environment : An attribute with string properties for each type of instance setup (i.e.: production, staging, qa, development). Each of these environment attributes has a string property denoting if the app is to use live API calls to the SFCC instance for that environment setup, or if it should use mock API calls instead. 12 | - resources : This is where you define the resource calls that you will be accessing from the Open Commerce API, and the different types of parameters that will be accepted and/or required to make a specific API call for a resource. 13 | - baseEndpoints : Contains attributes for each type of environment which point to the corresponding URL for making OCAPI calls. 14 | - clientIDs: This attribute must be setup with a valid SFCC user ID that has been configured for OCAPI use. See the SFCC documentation for information on how to setup a user account for OCAPI access. Like the baseEndpoints, and environment attributes, this setup object also has attributes for describing the URL for each individual SFCC instance type. 15 | 16 | ## Disclaimer 17 | The repo is still in the setup phase and there is still a large amout of work left to do. Check back soon to see more. 18 | 19 | Author: Galen Goforth -- galengoforth@gmail.com 20 | -------------------------------------------------------------------------------- /SFCC-RN-Components/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore templates for 'react-native init' 6 | /node_modules/react-native/local-cli/templates/.* 7 | 8 | ; Ignore RN jest 9 | /node_modules/react-native/jest/.* 10 | 11 | ; Ignore RNTester 12 | /node_modules/react-native/RNTester/.* 13 | 14 | ; Ignore the website subdir 15 | /node_modules/react-native/website/.* 16 | 17 | ; Ignore the Dangerfile 18 | /node_modules/react-native/danger/dangerfile.js 19 | 20 | ; Ignore Fbemitter 21 | /node_modules/fbemitter/.* 22 | 23 | ; Ignore "BUCK" generated dirs 24 | /node_modules/react-native/\.buckd/ 25 | 26 | ; Ignore unexpected extra "@providesModule" 27 | .*/node_modules/.*/node_modules/fbjs/.* 28 | 29 | ; Ignore polyfills 30 | /node_modules/react-native/Libraries/polyfills/.* 31 | 32 | ; Ignore various node_modules 33 | /node_modules/react-native-gesture-handler/.* 34 | /node_modules/expo/.* 35 | /node_modules/react-navigation/.* 36 | /node_modules/xdl/.* 37 | /node_modules/reqwest/.* 38 | /node_modules/metro-bundler/.* 39 | 40 | [include] 41 | 42 | [libs] 43 | node_modules/react-native/Libraries/react-native/react-native-interface.js 44 | node_modules/react-native/flow/ 45 | node_modules/expo/flow/ 46 | 47 | [options] 48 | emoji=true 49 | 50 | module.system=haste 51 | 52 | module.file_ext=.js 53 | module.file_ext=.jsx 54 | module.file_ext=.json 55 | module.file_ext=.ios.js 56 | 57 | munge_underscores=true 58 | 59 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 60 | 61 | suppress_type=$FlowIssue 62 | suppress_type=$FlowFixMe 63 | suppress_type=$FlowFixMeProps 64 | suppress_type=$FlowFixMeState 65 | suppress_type=$FixMe 66 | 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) 68 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ 69 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 70 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 71 | 72 | unsafe.enable_getters_and_setters=true 73 | 74 | [version] 75 | ^0.56.0 76 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/reducers/UserAccountReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * navigationReducer.js 3 | */ 4 | 5 | import * as actionTypes from '../actionTypes'; 6 | import UserProfile from '../models/UserProfile'; 7 | 8 | const DEFAULT_STATE = { 9 | isEdit: false, 10 | isLoadingProfile: false, 11 | isSavingProfile: false, 12 | userProfile: (new UserProfile()), 13 | profileLoadError: {}, 14 | profileSaveError: {} 15 | }; 16 | 17 | export default function userAccountReducer(state = DEFAULT_STATE, action = {}) { 18 | switch (action.type) { 19 | // focus action is dispatched when a new screen comes into focus 20 | case actionTypes.UPDATE_PROFILE: 21 | return { 22 | ...state, 23 | isEdit: true, 24 | isLoadingProfile: false, 25 | isSavingProfile: false, 26 | userProfile: action.userProfile 27 | } 28 | case actionTypes.REQUEST_LOAD_PROFILE: 29 | return { 30 | ...state, 31 | isLoadingProfile: true, 32 | isSavingProfile: false, 33 | } 34 | case actionTypes.RECEIVED_LOAD_PROFILE: 35 | return { 36 | ...state, 37 | userProfile: action.userProfile, 38 | isLoadingProfile: false, 39 | isSavingProfile: false, 40 | profileLoadError: {}, 41 | profileSaveError: {} 42 | } 43 | case actionTypes.FAILED_LOAD_PROFILE: 44 | return { 45 | ...state, 46 | isLoadingProfile: false, 47 | profileLoadError: { 48 | msg: 'An error occured while loading the user profile', 49 | errorCount: state.profileLoadError.errorCount + 1 50 | } 51 | } 52 | case actionTypes.REQUEST_SAVE_PROFILE: 53 | return { 54 | ...state, 55 | isSavingProfile: true, 56 | isLoadingProfile: false, 57 | } 58 | case actionTypes.RECEIVED_SAVE_PROFILE: 59 | return { 60 | ...state, 61 | isLoadingProfile: false, 62 | isSavingProfile: false, 63 | profileLoadError: {}, 64 | profileSaveError: {} 65 | } 66 | case actionTypes.FAILED_SAVE_PROFILE: 67 | return { 68 | ...state, 69 | isSavingProfile: false, 70 | profileSaveError: { 71 | msg: 'An error occured while saving the user profile', 72 | errorCount: state.profileSaveError.errorCount + 1 73 | } 74 | } 75 | 76 | default: 77 | return state; 78 | } 79 | } 80 | 81 | // Selectors (mapStateToProps) 82 | export const getProfile = ({ onLogging }) => ({ 83 | onLogging 84 | }); 85 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/utilityHelpers/URLHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file URLHelper.js 3 | * Provides helper methods for URLs. 4 | */ 5 | 6 | import {appConfig} from '../../config/appConfig'; 7 | 8 | /** 9 | * @class URLHelper 10 | * @classdesc A class of with static member methods for URL formatting and overrides. 11 | */ 12 | export default class URLHelper { 13 | /** 14 | * If there is an override URL specified for the given type, then the base 15 | * portion of the URL is replaced with the override setting specified in the appConfig.js. 16 | * 17 | * @param {string} url - The original URL returned from the OCAPI request. 18 | * @param {string} urlType - The type of url to perform overrides on. This type must be added 19 | * to the appConfig.js file under the 'Overrides' section, the 'isOverride' 20 | * config flag must be set to true, and the 'overrideURL' property contain the 21 | * base url to use for overriding the path. 22 | * @return {string} - Returns the url with the base portion overridden. 23 | */ 24 | static updateURL(url, urlType) { 25 | let updatedURL = url; 26 | 27 | // Check that all of the required fields for overriding a portion of the URI path 28 | // are setup in the appConfig.js for the URL type. 29 | if (URLHelper.needsOverride(urlType)) { 30 | // Get the override config values. 31 | const orConfig = appConfig.environment[appConfig.instanceType].overrides[urlType]; 32 | // Get the text pattern or portion of text to be replaced. 33 | /** @todo: Add Regex support for URL mapping. Just static string replacement for now :( */ 34 | const replaceText = orConfig.pathToReplace; 35 | const replacementText = orConfig.pathReplacement; 36 | updatedURL = url.replace(replaceText, replacementText); 37 | } 38 | 39 | return updatedURL; 40 | } 41 | 42 | /** 43 | * Helper method to check if a given type of URL should be overriden based on the 44 | * setting defined in appConfig for this type of URL. If no setting is found the 45 | * default value is false. 46 | * 47 | * @param {string} urlType - The type of URL to check. 48 | * Valid URL types: {'productImage'} 49 | */ 50 | static needsOverride(urlType) { 51 | const envConfig = appConfig.environment[appConfig.instanceType]; 52 | return (envConfig.overrides && 53 | envConfig.overrides[urlType] && 54 | envConfig.overrides[urlType].isOverride && 55 | envConfig.overrides[urlType].pathToReplace && 56 | envConfig.overrides[urlType].pathReplacement); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Category/ProductCategoryTree/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductCategoryTree/actions.js 3 | * @fileoverview Provides redux action creation methods for propogating events 4 | * that need to modify the scope at a global level. 5 | */ 6 | 7 | import ReduxThunk from 'redux-thunk'; 8 | import { Actions } from 'react-native-router-flux'; 9 | import Category from '../../../../lib/documents/Category'; 10 | import OCAPIService from '../../../../lib/OCAPIService/OCAPIService'; 11 | import { 12 | REQUEST_RESOURCE_CATEGORY_BY_ID, 13 | RECEIVED_RESOURCE_CATEGORY_BY_ID, 14 | FAILED_RESOURCE_CATEGORY_BY_ID 15 | } from '../../../../actionTypes'; 16 | 17 | /* ========================================================================== * 18 | * Synchronous Action Creators 19 | * ========================================================================== */ 20 | 21 | export const requestCategory = (categoryID, levels) => { 22 | return { 23 | type: REQUEST_RESOURCE_CATEGORY_BY_ID, 24 | categoryID: categoryID, 25 | levels: levels 26 | }; 27 | }; 28 | 29 | export const recievedCategory = (category) => { 30 | return { 31 | type: RECEIVED_RESOURCE_CATEGORY_BY_ID, 32 | category: category 33 | }; 34 | }; 35 | 36 | export const failedCategory = (categoryID, levels, error) => { 37 | return { 38 | type: FAILED_RESOURCE_CATEGORY_BY_ID, 39 | categoryID: categoryID, 40 | levels: levels, 41 | error: error 42 | }; 43 | }; 44 | 45 | /* ========================================================================== * 46 | * Async Action Creators 47 | * ========================================================================== */ 48 | 49 | /** 50 | * 51 | * @param {String|String[]} [categoryID=root] - The Category ID attribute or an array of Category IDs. 52 | * @param {number} [levels=2] - The depth of child categories to be retrieved. 53 | */ 54 | export const getCategory = (categoryID = 'root', levels = 2) => { 55 | return (dispatch) => { 56 | // Dispatch the Redux action to identify that an OCAPI request was made. 57 | dispatch(requestCategory(categoryID, levels)); 58 | const svc = new OCAPIService(); 59 | const callSetup = svc.setupCall('categories', 'get', { 60 | categoryID: categoryID, levels: levels }); 61 | 62 | if (!callSetup.error) { 63 | svc.makeCall(callSetup) 64 | .then(response => { 65 | if (response.status >= 200 && response.status < 300 && response.ok) { 66 | return response.json(); 67 | } else { 68 | return { 69 | error: true, 70 | errMsg: 'ERROR at Category/ProductCategoryTree/actions.js in ' + 71 | 'ASYNC action creator: requestCategory' 72 | } 73 | } 74 | }).then(result => { 75 | if (!result.error) { 76 | dispatch(recievedCategory(new Category(result))); 77 | } else { 78 | console.log(result.errMsg); 79 | dispatch(failedCategory(categoryID, levels, result.error)); 80 | } 81 | }, 82 | 83 | // Handle error conditions. 84 | err => dispatch(failedCategory(categoryID, levels, err)) 85 | ); 86 | 87 | } else { 88 | console.log('Call Setup Error:'); 89 | console.log(callSetup.errMsg); 90 | dispatch(failedCategory(categoryID, levels)); 91 | } 92 | }; 93 | }; 94 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/utilityHelpers/__tests__/utilityHelperTests.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import URLHelper from '../URLHelper'; 3 | 4 | 5 | describe('URLHelper.js utility helper methods.', () => { 6 | let appConfig; 7 | 8 | // Setup Test Group. 9 | beforeEach(() => { 10 | // Setup a mock appConfig that with a valid replacement string for a 11 | // section of the url path. 12 | appConfig = { 13 | instanceType: 'test', 14 | environment: { 15 | test: { 16 | overrides: { 17 | // Valid : should return true. 18 | productImage: { 19 | isOverride: true, 20 | pathToReplace: '/AAFF_S25/', 21 | pathReplacement: '/AAFF_STG/' 22 | }, 23 | // Valid : should return false. 24 | blogImage: { 25 | isOverride: false, 26 | pathToReplace: '/AAFF_S25/', 27 | pathReplacement: '/AAFF_STG/' 28 | }, 29 | // Missing pathReplacement property : should return false. 30 | iconImage: { 31 | isOverride: true, 32 | pathToReplace: '/AAFF_S25/', 33 | }, 34 | // Missing pathToReplace property : should return false. 35 | mensProductImage: { 36 | isOverride: true, 37 | pathReplacement: '/AAFF_STG/' 38 | }, 39 | // Missing isOverride property : should return false. 40 | womensProductImage: { 41 | pathToReplace: '/AAFF_S25/', 42 | pathReplacement: '/AAFF_STG/' 43 | } 44 | } 45 | } 46 | } 47 | }; 48 | }); 49 | 50 | /** 51 | * Test Group - class: URLHelper, method: needsOverride 52 | */ 53 | describe('URLHelper.needsOverride() - Check for URL mappings in appConfig.js for a specified imageType', () => { 54 | 55 | test('should return: true => when: the URL type has a valid override in the overrides configuration property.', () => { 56 | expect(URLHelper.needsOverride('productImage')).toBeTruthy(); 57 | }); 58 | 59 | test('should return: false => when: the URL type does not exist in the overrides configuration property.',() => { 60 | expect(URLHelper.needsOverride('contentImage')).toBeFalsy(); 61 | }); 62 | 63 | test('should return: false => when: the URL type has an invalid overrides configuration property.', () => { 64 | expect(URLHelper.needsOverride('blogImage')).toBeFalsy(); 65 | expect(URLHelper.needsOverride('iconImage')).toBeFalsy(); 66 | expect(URLHelper.needsOverride('mensProductImage')).toBeFalsy(); 67 | expect(URLHelper.needsOverride('womensProductImage')).toBeFalsy(); 68 | }); 69 | }); 70 | 71 | describe('Test URLHelper.updateURL(url : string, urlType : string) : string Static class member', () => { 72 | const beforeURL = 'https://sits-pod52.demandware.net/dw/image/v2/AAFF_S25/on/demandware.static/-/Sites-masterCatalogHoka/default/v1514346396403/images/white/1016786-CCYN_1.jpg'; 73 | 74 | test('should return: a modified URL with the proper portion of the path replaced => when: an override exists for this urlType', () => { 75 | const afterURL = 'https://sits-pod52.demandware.net/dw/image/v2/AAFF_STG/on/demandware.static/-/Sites-masterCatalogHoka/default/v1514346396403/images/white/1016786-CCYN_1.jpg' 76 | expect(URLHelper.updateURL(beforeURL, 'productImage')).toEqual(afterURL); 77 | }); 78 | 79 | test('should return: an unmodified url => when: there is no override for the urlType', () => { 80 | expect(URLHelper.updateURL(beforeURL, 'blogImage')).toEqual(beforeURL); 81 | }); 82 | }); 83 | 84 | }); -------------------------------------------------------------------------------- /SFCC-RN-Components/src/reducers/infoTileReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file infoTileReducer.js 3 | * @desc Reduces the state changes from InfoTile class API calls and merges them 4 | * into the app global state. 5 | */ 6 | 7 | import * as actionTypes from '../actionTypes'; 8 | import Product from '../lib/documents/Product'; 9 | import URLHelper from '../lib/utilityHelpers/URLHelper'; 10 | 11 | const DEFAULT_STATE = { 12 | sceneTransistion: false, 13 | isLoadingProduct: false, 14 | isLoadingProductImages: false, 15 | infoTile: { 16 | product: (new Product()) 17 | } 18 | }; 19 | 20 | export default function infoTileReducer(state = DEFAULT_STATE, action) { 21 | switch (action.type) { 22 | // Product -- Get 23 | case actionTypes.REQUEST_RESOURCE_PRODUCT_BY_ID: 24 | return { 25 | ...state, 26 | isLoadingProduct: true 27 | }; 28 | case actionTypes.RECEIVED_RESOURCE_PRODUCT_BY_ID: 29 | return { 30 | ...state, 31 | isLoadingProduct: false, 32 | infoTile: { 33 | product: action.product 34 | } 35 | }; 36 | case actionTypes.FAILED_RESOURCE_PRODUCT_BY_ID: 37 | return { 38 | ...state, 39 | isLoadingProduct: false 40 | } 41 | 42 | // Product -- Images 43 | case actionTypes.REQUEST_RESOURCE_PRODUCT_IMAGES: 44 | return { 45 | ...state, 46 | isLoadingProductImages: true 47 | }; 48 | case actionTypes.RECEIVED_RESOURCE_PRODUCT_IMAGES: 49 | return { 50 | ...state, 51 | infoTile: { 52 | product: addImagesToState(action.product, state.infoTile.product), 53 | isLoadingProductImages: false 54 | } 55 | }; 56 | case actionTypes.FAILED_RESOURCE_PRODUCT_IMAGES: 57 | return { 58 | ...state, 59 | isLoadingProductImages: false 60 | }; 61 | default: 62 | return state; 63 | } 64 | } 65 | 66 | // Selectors 67 | 68 | /** 69 | * A helper selector for getting the current Product instance stored in the info tile state. 70 | * @param {Object} state - The current state of the app. 71 | * @return {Product} - Returns the current Product instance stored in state.infoTile.product. 72 | */ 73 | export const getInfoTileProduct = (state) => { 74 | return state.infoTile.product; 75 | }; 76 | 77 | export const getImageURL = (state) => { 78 | if (state.infoTile.product.imageGroups.length) { 79 | return state.infoTile.product.imageGroups[0].images[0].disBaseLink; 80 | } else { 81 | return ''; 82 | } 83 | }; 84 | 85 | 86 | // Merge Functions 87 | 88 | /** 89 | * Used to merge requests for product images into an existing product instance. 90 | * @param {Product} product - The product returned from an API call. 91 | * @param {Product} infoTileProduct - The product that is currently in the app state if it exists. 92 | * @return {Product} - Returns the updated documents/Product instance with the appropriate 93 | * properties appended. 94 | */ 95 | function addImagesToState(product, infoTileProduct) { 96 | console.log(product); 97 | const URL_TYPE = 'productImage'; 98 | // Check if there is an override for Image URLs 99 | if (URLHelper.needsOverride(URL_TYPE)) { 100 | if (product.imageGroups && product.imageGroups.length) { 101 | product.imageGroups.forEach((ig) => { 102 | ig.images.forEach((img) => { 103 | img.disBaseLink = URLHelper.updateURL(img.disBaseLink, URL_TYPE); 104 | }); 105 | }); 106 | } 107 | } 108 | // If there is not currently a product in the app state, then we can just return 109 | // the product that was returned from the API call. 110 | if (!infoTileProduct || !infoTileProduct.ID) { 111 | return product; 112 | } 113 | 114 | if (product.imageGroups && product.imageGroups.length && 115 | (!infoTileProduct.imageGroups || infoTileProduct.imageGroups.length < 1)) { 116 | infoTileProduct.imageGroups = product.imageGroups; 117 | } 118 | console.log(infoTileProduct); 119 | return infoTileProduct; 120 | }; 121 | 122 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/UserAccount/UserAccount.js: -------------------------------------------------------------------------------- 1 | import React, { Component} from 'react'; 2 | import { View, Text, Dimensions, StyleSheet, Image } from 'react-native'; 3 | import Routes from '../../menuItems'; 4 | import { connect } from 'react-redux'; 5 | import SectionCard from '../Layout/SectionCard' 6 | import { colors } from '../../styles/globalStyles'; 7 | import Navbar from '../Navbar/Navbar'; 8 | import UserProfile from '../../models/UserProfile'; 9 | 10 | class UserAccount extends Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | const screenSize = Dimensions.get('window'); 15 | this.displayName = 'User Account'; 16 | this.state = { 17 | menuItems: Routes, 18 | width: screenSize.width, 19 | height: screenSize.height, 20 | userProfile: props.userProfile || (new UserProfile()), 21 | isEdit: props.isEdit || false 22 | } 23 | } 24 | 25 | /** 26 | * Lifecycle function for receiving changes to the component props when the 27 | * app state changes. 28 | * 29 | * @param nextProps 30 | */ 31 | componentWillReceiveProps(nextProps) { 32 | this.setState((prevState, nextProps) => ({ 33 | userProfile: nextProps.userProfile, 34 | isEdit: nextProps.isEdit, 35 | isLoadingProfile: nextProps.isLoadingProfile, 36 | isSavingProfile: nextProps.isSavingProfile, 37 | profileLoadError: nextProps.profileLoadError, 38 | profileSaveError: nextProps.profileSaveError 39 | })); 40 | } 41 | 42 | 43 | render() { 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 57 | {this.state.userProfile.firstName} {this.state.userProfile.lastName} 58 | 59 | {this.state.userProfile.userName} 60 | 61 | {this.state.userProfile.address.city}{', '}{this.state.userProfile.address.state} 62 | 63 | 64 | 65 | 66 | 67 | 68 | Account Information 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Loyalty Information 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Security Information 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ); 97 | } 98 | } 99 | 100 | const styles = StyleSheet.create({ 101 | 102 | container: { 103 | flex: 1, 104 | backgroundColor: colors.mercury, 105 | marginTop: 50, 106 | }, 107 | 108 | footer: { 109 | flex: .10, 110 | backgroundColor: '#CCCCCC', 111 | borderTopWidth: 10, 112 | borderTopColor: '#fff', 113 | }, 114 | 115 | headingText: { 116 | 117 | fontSize: 22, 118 | color: colors.boulder 119 | }, 120 | 121 | sectionText: { 122 | 123 | fontSize: 18, 124 | color: colors.blueLagoon 125 | }, 126 | 127 | mainText: { 128 | 129 | fontSize: 30, 130 | color: colors.boulder, 131 | }, 132 | 133 | sectionStyle: { 134 | borderBottomWidth: 1, 135 | borderBottomColor: colors.boulder 136 | }, 137 | 138 | containerStyle: { 139 | padding: 5, 140 | backgroundColor: '#fff', 141 | justifyContent: 'center', 142 | position: 'relative', 143 | marginTop: 10, 144 | marginBottom: 0, 145 | margin: 10, 146 | borderRadius: 5, 147 | } 148 | }); 149 | 150 | export default connect(({routes}) => ({routes}))(UserAccount); 151 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Product/InfoTile/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file InfoTile/actions.js 3 | * @fileoverview - Exports redux thunk action creation methods for modifying the 4 | * application scope state object from the ProductListView component. 5 | */ 6 | 7 | import OCAPIService from '../../../../lib/OCAPIService/OCAPIService'; 8 | import Product from '../../../../lib/documents/Product'; 9 | import { 10 | REQUEST_RESOURCE_PRODUCT_BY_ID, 11 | RECEIVED_RESOURCE_PRODUCT_BY_ID, 12 | FAILED_RESOURCE_PRODUCT_BY_ID, 13 | REQUEST_RESOURCE_PRODUCT_IMAGES, 14 | RECEIVED_RESOURCE_PRODUCT_IMAGES, 15 | FAILED_RESOURCE_PRODUCT_IMAGES 16 | } from '../../../../actionTypes'; 17 | 18 | /* ========================================================================== * 19 | * Async Action Creators 20 | * ========================================================================== */ 21 | 22 | /** 23 | * Async action creation method used to make an OCAPI request for a product. 24 | * @param {string} productID 25 | */ 26 | export const requestProduct = (productID) => { 27 | return (dispatch) => { 28 | // Dispatch a synchronous action to the store to show that an API call has begun. 29 | dispatch(requestProductById(productID)); 30 | // Get a reference to the singleton service instance. 31 | const svc = new OCAPIService(); 32 | // Get the call data object to make the call. 33 | const callSetup = svc.setupCall('products', 'get', { productID: productID }); 34 | if (!callSetup.error) { 35 | console.log('API call setup successfull.'); 36 | svc.makeCall(callSetup) 37 | .then((response) => { 38 | console.log(response); 39 | if (response.status >= 200 && response.status < 300 && response.ok) { 40 | return response.json() 41 | } else { 42 | return { 43 | error: true, 44 | errMsg: 'ERROR at Product/infoTile/actions.js in ASYNC action creator: requestProduct' 45 | } 46 | } 47 | }) 48 | .then( 49 | result => { 50 | if (!result.error) { 51 | dispatch(receivedProductById(new Product(result))); 52 | } else { 53 | dispatch(failedProductById(result.errMsg)); 54 | } 55 | }, 56 | err => dispatch(failedProductById(err)) 57 | ); 58 | } else { 59 | dispatch(failedProductById(callSetup.errMsg)); 60 | } 61 | 62 | }; 63 | }; 64 | 65 | export const requestImagesForProduct = (productID) => { 66 | return (dispatch) => { 67 | // Dispatch a synchronous action to the store to show that an API call has begun. 68 | dispatch(requestProductImages(productID)); 69 | // Get a reference to the singleton service instance. 70 | const svc = new OCAPIService(); 71 | // Get the call data object to make the call. 72 | const callSetup = svc.setupCall('products', 'images', { productID: productID, all_images: true }); 73 | if (!callSetup.error) { 74 | svc.makeCall(callSetup) 75 | .then((response) => { 76 | if (response.status >= 200 && response.status < 300 && response.ok) { 77 | return response.json(); 78 | } else { 79 | return { 80 | error: true, 81 | errMsg: 'ERROR at Product/infoTile/actions.js in ASYNC action creator: requestProduct' 82 | }; 83 | } 84 | }) 85 | .then( 86 | result => { 87 | console.log(result); 88 | if (!result.error) { 89 | dispatch(receivedProductImages(new Product(result))); 90 | } else { 91 | console.log(result.errMsg); 92 | dispatch(failedProductImages(result.errMsg)); 93 | } 94 | }, 95 | err => dispatch(failedProductImages(err)) 96 | ); 97 | } else { 98 | dispatch(failedProductImages(callSetup.errMsg)); 99 | } 100 | }; 101 | } 102 | 103 | /* ========================================================================== * 104 | * Synchronous Action Creators 105 | * ========================================================================== */ 106 | 107 | // Product / images 108 | export const requestProductImages = (productID) => { 109 | return { 110 | type: REQUEST_RESOURCE_PRODUCT_IMAGES, 111 | product: productID 112 | }; 113 | }; 114 | 115 | export const receivedProductImages = (product) => { 116 | return { 117 | type: RECEIVED_RESOURCE_PRODUCT_IMAGES, 118 | product: product 119 | }; 120 | }; 121 | 122 | export const failedProductImages = (err) => { 123 | return { 124 | type: FAILED_RESOURCE_PRODUCT_IMAGES, 125 | error: err 126 | }; 127 | }; 128 | 129 | // Product / get 130 | export const requestProductById = (productID) => { 131 | return { 132 | type: REQUEST_RESOURCE_PRODUCT_BY_ID, 133 | product: productID 134 | }; 135 | }; 136 | 137 | export const receivedProductById = (product) => { 138 | return { 139 | type: RECEIVED_RESOURCE_PRODUCT_BY_ID, 140 | product: product 141 | }; 142 | }; 143 | 144 | export const failedProductById = (err) => { 145 | return { 146 | type: FAILED_RESOURCE_PRODUCT_BY_ID, 147 | error: err 148 | }; 149 | }; 150 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/Category.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Category 3 | * @classdesc Represents the OCAPI Category document type. 4 | * 5 | * @property {Category[]} categories - An array of child categories. 6 | * @property {String} - The localized description of the category. 7 | * @property {String} ID - The ID of the category in SFCC Business Manager. 8 | * @property {String} 9 | * @property {String} name - The name assigned to the Category. 10 | * @property {String} pageDescription - A description to use for displaying the category as a page. 11 | * @property {String} pageTitle - A title to use for displaying the category as a page. 12 | * @property {String} parentCategoryID - The ID of the parent category. 13 | * @property {String} type - The type of OCAPI document that the class instance represents. 14 | * @property {String} version - The version of the OCAPI document that is being stored to memory. 15 | * 16 | */ 17 | export default class Category { 18 | /** 19 | * @constructor 20 | * @param {Object} [args] - An optional Object literal that follows the OCAPI standard for the 21 | * Category document spec. 22 | * @param {String} [args._type=category] - The type of OCAPI document that a class instance represents. 23 | * @param {String} [args._v] - The version number assigned to the current OCAPI document. 24 | * @param {Category[]} [args.categories] - An array of child categories. 25 | * @param {String} [args.description] - The localized description of the category. 26 | * @param {String} [args.id] - The ID property of the SFCC Category. 27 | * @param {String} [args.image] - The URL to the category image. 28 | * @param {String} [args.name] - The name property of the SFCC Category. 29 | * @param {String} [args.page_description] - A description that can be used to display the Category as a page. 30 | * @param {String} [args.page_title] - A title that can be used to display the Category as a page. 31 | * @param {String} [args.parent_category_id] - The ID attribute of the parent category. 32 | */ 33 | constructor(args) { 34 | /** @type {Category[]} */ 35 | this._categories = args && args.categories ? 36 | args.categories.map(cat => new Category(cat)) : []; 37 | /** @type {string} */ 38 | this._id = args && args.id ? args.id : ''; 39 | /** @type {string} */ 40 | this._image = args && args.image ? args.image : ''; 41 | /** @type {string} */ 42 | this._description = args && args.description ? args.description : ''; 43 | /** @type {string} */ 44 | this._name = args && args.name ? args.name : ''; 45 | /** @type {string} */ 46 | this._pageDescription = args && args.page_description ? 47 | args.page_description : ''; 48 | /** @type {string} */ 49 | this._pageKeywords = args && args.page_keywords ? args.page_keywords : ''; 50 | /** @type {string} */ 51 | this._pageTitle = args && args.page_title ? args.page_title : ''; 52 | /** @type {string} */ 53 | this._parentCategoryID = args && args.parent_category_id ? 54 | args.parent_category_id : ''; 55 | /** @type {string} */ 56 | this._type = 'category'; 57 | /** @type {string} */ 58 | this._version = args && args._v ? args._v : ''; 59 | 60 | // Add custom attributes to the instance. 61 | if (args && args.length) { 62 | Object.keys(args) 63 | .filter(key => /^c_/.test(key)) 64 | .forEach(key => this[key] = args[key]); 65 | } 66 | } 67 | 68 | get categories() { return this._categories } 69 | set categories(value) { this._categories = value } 70 | get description() { return this._description } 71 | set description(value) { this._description = value } 72 | get ID() { return this._id } 73 | set ID(value) { this._id = value } 74 | get image() { return this._image } 75 | set image(value) { this._image = value } 76 | get name() { return this._name } 77 | set name(value) { this._name = value } 78 | get pageDescription() { return this._pageDescription } 79 | set pageDescription(value) { this._pageDescription = value } 80 | get pageKeywords() { return this._pageKeywords } 81 | set pageKeywords(value) { this._pageKeywords = value } 82 | get pageTitle() { return this._pageTitle } 83 | set pageTitle(value) { this._pageTitle = value } 84 | get parentCategoryID() { return this._parentCategoryID } 85 | set parentCategoryID(value) { this._parentCategoryID = value } 86 | get type() { return this._type } 87 | set type(value) { this._type = value } 88 | get version() { return this._version } 89 | set version(value) { this._version = value } 90 | 91 | /** 92 | * Gets an object literal of the class that matches the OCAPI document (v18.6) 93 | * definition. 94 | * 95 | * @return {Object} - A JSON string representation of the class instance that 96 | * matches the OCAPI document definition. 97 | */ 98 | getDocument() { 99 | const doc = { 100 | categories: this._categories, 101 | description: this._description, 102 | id: this._id, 103 | image: this._image, 104 | name: this._name, 105 | page_description: this._pageDescription, 106 | page_keywords: this._pageKeywords, 107 | page_title: this.pageTitle, 108 | parent_category_id: this._parentCategoryID, 109 | thumbnail: this._thumbnail 110 | }; 111 | 112 | return doc; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/lib/documents/ProductSearchHit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProductSearchHit.js 3 | * @fileoverview - ProductSearchHit OCAPI document class. 4 | */ 5 | 6 | import Image from './Image'; 7 | import ProductRef from './ProductRef'; 8 | import ProductType from './ProductType'; 9 | import VariationAttribute from './VariationAttribute'; 10 | 11 | /** 12 | * @class ProductSearchHit 13 | * @classdesc - A data class for keeping the standard document type when 14 | * handling responses from productSearch calls to the Open Commerce API. 15 | */ 16 | export default class ProductSearchHit { 17 | /** 18 | * @private 19 | * @type {String} 20 | */ 21 | _docType = 'ProductSerachHit'; 22 | 23 | /** 24 | * @constructor 25 | * @param {Object} [args] - An optional arguments object that is in the form 26 | * of the OCAPI Shop API ProductSearchHit doucment so that your API call 27 | * results can be passed directly into the constructor for easy data 28 | * standardization. 29 | */ 30 | constructor(args) { 31 | /** @type {String} */ 32 | this._currency = args && args.currency ? args.currency : ''; 33 | /** @type {String} */ 34 | this._hitType = args && args.hit_type ? args.hit_type : ''; 35 | /** @type {Image} */ 36 | this._image = args && args.image ? new Image(args.image) : new Image(); 37 | /** @type {String} */ 38 | this._link = args && args.link ? args.link : ''; 39 | /** @type {Boolean} */ 40 | this._orderable = args && args.orderable ? args.orderable : ''; 41 | /** @type {Number} */ 42 | this._price = args && args.price ? args.price : 0; 43 | /** @type {Number} */ 44 | this._priceMax = args && args.price_max ? args.price_max : 0; 45 | /** @type {Map} */ 46 | this._prices = args && args.prices ? args.prices : new Map(); 47 | /** @type {String} */ 48 | this._productID = args && args.product_id ? args.product_id : ''; 49 | /** @type {String} */ 50 | this._productName = args && args.product_name ? args.product_name : ''; 51 | /** @type {ProductType} */ 52 | this._productType = args && args.product_type ? 53 | new ProductType(args.product_type) : new ProductType(); 54 | /** @type {ProductRef} */ 55 | this._representedProduct = args && args.represented_product ? 56 | new ProductRef(args.represented_product) : new ProductRef(); 57 | /** @type {ProductRef[]} */ 58 | this._representedProducts = args && args.represented_products && 59 | args.represented_products.length ? 60 | args.represented_products.map((rp) => { 61 | return new ProductRef(rp); 62 | }) : []; 63 | /** @type {VariationAttribute} */ 64 | this._variationAttributes = args && args.variation_attributes && 65 | args.variation_attributes.length ? 66 | args.variation_attributes.map((va) => { 67 | return new VariationAttribute(va); 68 | }) : []; 69 | } 70 | 71 | /* Class Mutators / Accessors 72 | ======================================================================== */ 73 | 74 | get docType() { return this._docType } 75 | get currency() { return this._currency } 76 | set currency(value) { this._currency = value } 77 | get hitType() { return this._hitType } 78 | set hitType(value) { this._hitType = value } 79 | get image() { return this._image } 80 | set image(value) { this._image = value } 81 | get link() { return this._link } 82 | set link(value) { this._link = value } 83 | get orderable() { return this._orderable } 84 | set orderable(value) { this._orderable = value } 85 | get price() { return this._price } 86 | set price(value) { this._price = value } 87 | get priceMax() { return this._priceMax } 88 | set priceMax(value) { this._priceMax = value } 89 | get prices() { return this._prices } 90 | set prices(value) { this._prices = value } 91 | get productID() { return this._productID } 92 | set productID(value) { this._productID = value } 93 | get productName() { return this._productName } 94 | set productName(value) { this._productName = value } 95 | get ProductType() { return this._ProductType } 96 | set ProductType(value) { this._ProductType = value } 97 | get representedProduct() { return this._representedProduct } 98 | set representedProduct(value) { this._representedProduct = value } 99 | get representedProducts() { return this._representedProducts } 100 | set representedProducts(value) { this._representedProducts = value } 101 | get variationAttributes() { return this._variationAttributes } 102 | set variationAttributes(value) { this._variationAttributes = value } 103 | 104 | /* Public Instance Methods 105 | ======================================================================== */ 106 | 107 | /** 108 | * Gets a JSON string of the class that matches the OCAPI document (v18.3) 109 | * definition. 110 | * 111 | * @return {String} - A JSON string representation of the instance that 112 | * matches the OCAPI document definition. 113 | */ 114 | getDocument() { 115 | const doc = { 116 | currency: this._currency, 117 | hit_type: this._hitType, 118 | image: this._image, 119 | link: this._link, 120 | orderable: this._orderable, 121 | price: this._price, 122 | price_max: this._priceMax, 123 | prices: this._prices, 124 | product_id: this._productID, 125 | product_name: this._productName, 126 | product_type: this._productType, 127 | represented_product: this._representedProduct, 128 | represented_products: this._representedProducts, 129 | variation_attributes: this._variationAttributes 130 | }; 131 | 132 | return doc; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/styles/globalStyles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * globalStyles.js 3 | * Class with getStyleSheet helper method that returns style objects for each globaly 4 | * used component in the app. 5 | * 6 | * *** Instructions *** 7 | * * Create the style sheet as a property of the class in the constructor 8 | */ 9 | 10 | import {StyleSheet, Dimensions} from 'react-native'; 11 | 12 | const colorNames = { 13 | // Colors by Name 14 | white: '#ffffff', 15 | bondiBlue: '#009382', 16 | darkCerulean: '#005789', 17 | gravel: '#51534A', 18 | orange: '#FF5E00', 19 | green: '#6DBE45', 20 | wildSand: '#F4F4F4', 21 | mercury: '#E6E6E6', 22 | alto: '#CFCFCF', 23 | dustyGray: '#999999', 24 | boulder: '#777777', 25 | // Teal Spectrum 26 | catskillWhite: '#E9F3F5', 27 | paleTurquoise: '#B2F2FF', 28 | frenchPass: '#65D0E5', 29 | java: '#1DAFCC', 30 | bondiBlue: '#0093B2', 31 | blueLagoon: '#067A91', 32 | atoll: '#095F70', 33 | tarawera: '#09414D', 34 | // Mustard Spectrum 35 | peach: '#FFE7B2', 36 | buttercup: '#F1AE1D', 37 | pirateGold: '#B37A00', 38 | // Green Spectrum 39 | confier: '#90E665', 40 | apple: '#6DEB45', 41 | limeade: '#349106', 42 | // Orange Spectrum 43 | apricot: '#E69465', 44 | blazeOrange: '#FF5E00', 45 | fire: '#834100', 46 | // Dark Blue Spectrum 47 | deepCerulean: '#0071B3', 48 | veniceBlue: '#065E91', 49 | tealBlue: '#094A70', 50 | // Red Spectrum 51 | sundown: '#FFB2B5', 52 | thunderBird: '#C4151C', 53 | brightRed: '#B30006', 54 | // Purple Spectrum 55 | lavenderRose: '#FFB2FF', 56 | strikeMaster: '#976197', 57 | violetEggplant: '#910691', 58 | // Validation Backgrounds 59 | whiteSmoke: '#FFEDED', 60 | parache: '#FAF4E7', 61 | oldLace: '#FFF6E2', 62 | // Light Blue Spectrum 63 | anakiwa: '#B2E3FF', 64 | truquoiseBlue: '#65B6E6', 65 | curiousBlue: '#1DBCCC', 66 | } 67 | 68 | const colorAliasNames = { 69 | // Brand Colors 70 | brand: { 71 | trustworthy: colorNames.bondiBlue, 72 | pTeal: colorNames.bondiBlue, 73 | innovative: colorNames.darkCerulean, 74 | pDarkBlue: colorNames.darkCerulean, 75 | bold: colorNames.gravel, 76 | pBrown: colorNames.gravel, 77 | pOrange: colorNames.orange, 78 | pGreen: colorNames.green, 79 | visionary: colorNames.green, 80 | }, 81 | 82 | // Gray Spectrum 83 | gray: { 84 | backgrnd1: colorNames.wildSand, 85 | backgrnd2: colorNames.mercury, 86 | border: colorNames.alto, 87 | lightText: colorNames.dustyGray, 88 | lightGray: colorNames.dustyGray, 89 | disabled: colorNames.dustyGray, 90 | mediumText: colorNames.boulder, 91 | }, 92 | 93 | // Teal Spectrum 94 | teal: { 95 | shade1: '#E9F3F5', 96 | shade2: '#B2F2FF', 97 | shade3: '#65D0E5', 98 | shade4: '#1DAFCC', 99 | shade5: '#0093B2', 100 | shade6: '#067A91', 101 | shade7: '#095F70', 102 | shade8: '#09414D', 103 | }, 104 | } 105 | 106 | // Styling Helper 107 | const absoluteStyling = { 108 | position: 'absolute', 109 | left: 0, 110 | top: 0, 111 | }; 112 | 113 | /** 114 | * Navbar Styles 115 | * @type {StyleSheet} 116 | */ 117 | export const navbarStyles = StyleSheet.create({ 118 | menuContainer: { 119 | backgroundColor: 'transparent', 120 | }, 121 | icon: { 122 | width: 45, 123 | height: 45, 124 | flex: 1 125 | }, 126 | navbar: { 127 | overflow: 'hidden', 128 | zIndex: 600, 129 | borderBottomWidth: 2, 130 | borderRightWidth: 2, 131 | borderBottomColor: colorNames.mercury, 132 | borderRightColor: colorNames.mercury, 133 | }, 134 | item: { 135 | paddingLeft: 15, 136 | paddingRight: 15, 137 | zIndex: 600, 138 | backgroundColor: '#09414D', 139 | opacity: .95, 140 | }, 141 | topBorder: { 142 | borderTopWidth: 1, 143 | borderTopColor: '#E6E6E6', 144 | } , 145 | lightText: { 146 | padding: 15, 147 | opacity: 1, 148 | 149 | fontSize: 15, 150 | fontWeight: '600', 151 | color: '#E6E6E6', 152 | }, 153 | backLayer: { 154 | position: 'absolute', 155 | top: 46, 156 | left: 0, 157 | zIndex: 500, 158 | } 159 | }); 160 | 161 | /** 162 | * Button Styles 163 | * @type {StyleSheet} 164 | */ 165 | export const btnStyles = StyleSheet.create({ 166 | btn: { 167 | height: 60, 168 | justifyContent: 'center', 169 | backgroundColor: '#65D0E5', 170 | margin: 12, 171 | borderWidth: 0, 172 | borderRadius: 8, 173 | paddingTop: 20, 174 | paddingBottom: 20 175 | }, 176 | btnText: { 177 | alignSelf: 'center', 178 | alignItems: 'center', 179 | fontSize: 20, 180 | fontWeight: '600' 181 | } 182 | }); 183 | 184 | export const txtStyles = StyleSheet.create({ 185 | txtPrimary: { 186 | 187 | fontSize: 14, 188 | color: colorAliasNames.teal.shade9, 189 | }, 190 | txtPrimaryLight: { 191 | 192 | fontSize: 14, 193 | color: colorAliasNames.teal.shade9, 194 | } 195 | }); 196 | 197 | /** 198 | * Form Element Styles 199 | * @type {StyleSheet} 200 | */ 201 | export const frmStyles = StyleSheet.create({ 202 | inpText: { 203 | backgroundColor: colorNames.white, 204 | alignSelf: 'stretch', 205 | borderWidth: 1, 206 | borderColor: '#777777', 207 | color: '#777777' 208 | }, 209 | inpTextActive: { 210 | borderColor: '#B2F2FF', 211 | } 212 | }); 213 | 214 | /** 215 | * Colors 216 | * @type {Object} 217 | */ 218 | const _colors = colorNames; 219 | _colors.brand = colorAliasNames.brand; 220 | _colors.grays = colorAliasNames.gray; 221 | 222 | export const colors = _colors; 223 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/config/apiConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file apiConfig.js 3 | * Defines api call configuration settings 4 | */ 5 | 6 | import { appConfig } from './appConfig'; 7 | 8 | /**************************************************** 9 | // Settings for any API data calls made from the app. 10 | *****************************************************/ 11 | export const apiConfig = { 12 | OCAPI: { 13 | // Set each type of environment to 'mock' or 'live' to use a live API call 14 | // or mock data. 15 | environment: { 16 | // development: 'mock', 17 | development: 'live', 18 | qa: 'live', 19 | staging: 'live', 20 | production: 'live' 21 | }, 22 | 23 | // Define each possible call type that is available. 24 | resources: { 25 | /* ====== Baskets ====== */ 26 | baskets: { 27 | path: '/baskets', 28 | API: 'shop', 29 | calls: { 30 | create: { 31 | path: '', 32 | pathParams: [], 33 | requiredParams: [], 34 | requiredData: [], 35 | callType: 'GET', 36 | headers: { 37 | 'Content-Type': 'application-json' 38 | } 39 | } 40 | } 41 | }, 42 | /* ====== Categories ====== */ 43 | categories: { 44 | path: '/categories', 45 | API: 'shop', 46 | calls: { 47 | get: { 48 | path: '/{0}', 49 | pathParams: [{ name: 'categoryID', index: 0 }], 50 | requiredParams: ['levels'], 51 | requiredData: [], 52 | callType: 'GET', 53 | headers: { 54 | 'Content-Type': 'application/json' 55 | } 56 | } 57 | } 58 | }, 59 | /* ====== ProductSearch ====== */ 60 | productSearch: { 61 | path: '/product_search', 62 | API: 'shop', 63 | calls: { 64 | get: { 65 | path: '', 66 | pathParams: [], 67 | requiredParams: [], 68 | requiredData: [], 69 | callType: 'GET', 70 | headers: { 71 | 'Content-Type': 'application/json' 72 | } 73 | }, 74 | getAvailability: { 75 | path: '/{0}', 76 | pathParams: [], 77 | requiredParams: [], 78 | requiredData: [], 79 | callType: 'GET', 80 | headers: { 81 | 'Content-Type': 'application/json' 82 | } 83 | }, 84 | getImages: { 85 | path: '/{0}', 86 | pathParams: [], 87 | requiredParams: [], 88 | requiredData: [], 89 | callType: 'GET', 90 | headers: { 91 | 'Content-Type': 'application/json' 92 | } 93 | }, 94 | getPrices: { 95 | path: '/{0}', 96 | pathParams: [], 97 | requiredParams: [], 98 | requiredData: [], 99 | callType: 'GET', 100 | headers: { 101 | 'Content-Type': 'application/json' 102 | } 103 | }, 104 | getRepresentedProducts: { 105 | path: '/{0}', 106 | pathParams: [], 107 | requiredParams: [], 108 | requiredData: [], 109 | callType: 'GET', 110 | headers: { 111 | 'Content-Type': 'application/json' 112 | } 113 | }, 114 | getVariations: { 115 | path: '/{0}', 116 | pathParams: [], 117 | requiredParams: [], 118 | requiredData: [], 119 | callType: 'GET', 120 | headers: { 121 | 'Content-Type': 'application/json' 122 | } 123 | } 124 | } 125 | }, 126 | /* ====== Products ====== */ 127 | products: { 128 | path: '/products', 129 | API: 'shop', 130 | calls: { 131 | get: { 132 | path: '/{0}', 133 | pathParams: [{ name: 'productID', index: 0 }], 134 | requiredParams: [], 135 | requiredData: [], 136 | callType: 'GET', 137 | headers: { 138 | 'Content-Type': 'application/json' 139 | } 140 | }, 141 | images: { 142 | path: '/{0}/images', 143 | pathParams: [{ name: 'productID', index: 0 }], 144 | requiredParams: ['all_images'], 145 | requiredData: [], 146 | callType: 'GET', 147 | headers: { 148 | 'Content-Type': 'application/json' 149 | } 150 | } 151 | } 152 | }, 153 | }, 154 | 155 | // API general settings 156 | API: { 157 | shop: { 158 | path: '/shop' 159 | }, 160 | data: { 161 | path: '/data', 162 | }, 163 | meta: { 164 | // Authorization Server Credentials 165 | authServer: { 166 | userName: 'appUser', 167 | userPassword: 'appPassword' 168 | }, 169 | } 170 | }, 171 | 172 | // The base URI of the API call endpoint that will be used if the selected environment type 173 | // is setup to use the live API. 174 | baseEndpoints: { 175 | development: 'https://dw-web-example.demandware.net/s/' + appConfig.siteID + '/dw/', 176 | qa: 'https://qa-web-example.demandware.net/s/' + appConfig.siteID + '/dw/', 177 | staging: 'https://staging-web-example.demandware.net/s' + appConfig.siteID + '/dw/', 178 | production: 'https://www.example.com/dw/' 179 | }, 180 | 181 | // Client ID's for application identification in each type of envoironment. 182 | clientIDs: { 183 | development: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 184 | production: '', 185 | staging: '', 186 | qa: '' 187 | }, 188 | 189 | // Current API version in use. 190 | currentVersion: 'v17_8', 191 | } 192 | }; -------------------------------------------------------------------------------- /SFCC-RN-Components/src/services/deviceStorage/DeviceStorageService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DeviceStorageService.js 3 | * 4 | * Singleton instance of a Service class to read and write to a devices local 5 | * storage using the React-Native Async Storage library. 6 | */ 7 | 8 | import {AsyncStorage} from 'react-native'; 9 | 10 | let DeviceStorageServiceInstance = null; 11 | 12 | export default class AsyncStorageService { 13 | constructor() { 14 | if (!DeviceStorageServiceInstance) { 15 | DeviceStorageServiceInstance = this; 16 | } 17 | return DeviceStorageServiceInstance; 18 | } 19 | 20 | /** 21 | * Reads an item or multiple items from the device storage database and 22 | * returns a promise that resolves with the results. 23 | * 24 | * @param {string|Array} itemNames - String key name(s) for the items 25 | * to be read from AsyncStorage. Either a single name can be passed as a 26 | * string, or multiple item names can be passed as an Array of strings. 27 | * @return {Promise} - A Promise that will resolve to return the data read 28 | * from AsyncStorage. 29 | */ 30 | read(itemNames) { 31 | // Create curried error callback instance. 32 | const cb = this._libError(itemNames, 'read'); 33 | try { 34 | // Use infrence to determine if this is a single read or a collection. 35 | if (Array.isArray(itemNames)) { 36 | return AsyncStorage.multiGet(itemNames, cb); 37 | 38 | // If a single item name is passed in as a string. 39 | } else if (typeof itemNames === 'string') { 40 | return AsyncStorage.getItem(itemNames, cb); 41 | 42 | // Handle unexpected input types. 43 | } else { 44 | throw new TypeError('Expected either a string or an Array of strings.'); 45 | } 46 | 47 | // Handle error conditions. 48 | } catch (err) { 49 | return _loggServiceError({items: items}, err, 'read'); 50 | } 51 | } 52 | 53 | /** 54 | * Write key value pairs to the local device storage. 55 | * @param {Array|Object} keyValArray - Key/Value pairs in Array or Object 56 | * form to write to device storage. 57 | * @return {Promise} - Promise with the results from the data 58 | * write. 59 | * 60 | * @example 61 | * write({ 62 | * 'fruit': 'apples', 63 | * 'vegetables': { 64 | * 'roots': ['potatoes', 'turnips'], 65 | * 'leaves': ['spinich', 'lettuce'] 66 | * } 67 | * }); 68 | * 69 | * // or 70 | * write([ 71 | * ['fruit', 'apples'], 72 | * ['vegetables', 73 | * ['roots', ['potatoes', 'turnips']], 74 | * ['leaves', ['spinish', 'lettuce']], 75 | * ] 76 | * ]) 77 | */ 78 | write(keyValArray) { 79 | // Create curried error callback instance. 80 | const cb = this._libError(itemNames, 'write'); 81 | try { 82 | 83 | // If the Key / Value pairs were passed as an array. 84 | if (Array.isArray(keyValArray)) { 85 | 86 | // Write Objects in Array form ([{key:value}] or [[key, value]]) 87 | keyValArray = keyValArray.map(val => { 88 | let k = keyValArray[val]; 89 | if (Array.isArray(k) && k.length === 2) { 90 | return new Array(k[0], JSON.stringify(k[1])); 91 | } else if (typeof k === 'object') { 92 | return Object.keys(keyValArray).map(key => { 93 | return new Array(key, JSON.stringify(k)); 94 | }); 95 | } else { 96 | throw new TypeError('error', 'Expected key value array or key value ' 97 | + 'object for AsyncStorage write operation.'); 98 | } 99 | }); 100 | 101 | return AsyncStorage.multiSet(keyValArray, cb); 102 | 103 | // If the Key / Value pairs were passed in Object form. 104 | } else if (typeof keyValArray === 'object') { 105 | return AsyncStorage.multiSet(Object.keys(keyValArray).map(key => { 106 | let k = keyValArray[key]; 107 | if (typeof k !== 'string' && typeof k !== 'number') { 108 | k = JSON.stringify(k); 109 | } else if (typeof k === 'number') { 110 | k = k.toString(); 111 | } 112 | return new Array(key, keyValArray[key]); 113 | })); 114 | } else { 115 | throw new TypeError('Expected key value array or key value ' 116 | + 'object for AsyncStorage write operation.'); 117 | } 118 | 119 | } catch(err) { 120 | return _loggServiceError({items: items, values: itemValues}, err, 'read'); 121 | } 122 | 123 | } 124 | 125 | merge(item1Names, item2Names) { 126 | // @TODO - Create service method to merge storage keys. 127 | } 128 | 129 | /** 130 | * Deletes an item or items with given key(s) from local device storage. 131 | * @param {Array|string} itemNames - The key names of the items to be 132 | * removed from device storage. 133 | * @return {Promise} - Returns a Promise that resolves to an object with a 134 | * 'success' property that indicates if the item(s) were successfully 135 | * deleted from the device storage. 136 | */ 137 | delete(itemNames) { 138 | try { 139 | if (Array.isArray(itemsNames)) { 140 | itemNames.forEach((itemName) => { 141 | AsyncStorage.delete(itemName); 142 | }) 143 | } else if (typeof itemNames === 'string') { 144 | AsyncStorage.delete(itemNames); 145 | } else { 146 | throw new TypeError('Delete can only be performed on an item of type ' 147 | + 'string or Array'); 148 | } 149 | } catch (err) { 150 | return _loggServiceError({itemNames: itemNames,},err, 'delete'); 151 | } 152 | } 153 | 154 | _loggServiceError(params, error, callName) { 155 | console.log('ERROR:: in DeviceStorageService.js\n===> Service Call: '); 156 | 157 | if (callName) { 158 | console.log(callName + '\n===> Call Params: '); 159 | } 160 | 161 | if (params) { 162 | console.log(JSON.stringify(params)); 163 | } 164 | 165 | if (error) { 166 | console.log(JSON.stringify(error)); 167 | } 168 | 169 | return Promise.reject(new Error('There was an error in the DeviceStorageService')); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /SFCC-RN-Components/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "parser": "babel-eslint", 5 | 6 | "env": { 7 | "es6": true, 8 | "jest": true 9 | }, 10 | 11 | "plugins": [ 12 | "eslint-comments", 13 | "flowtype", 14 | "prettier", 15 | "react", 16 | "react-native", 17 | "jest" 18 | ], 19 | 20 | "globals": { 21 | "__DEV__": true, 22 | "__dirname": false, 23 | "__fbBatchedBridgeConfig": false, 24 | "alert": false, 25 | "cancelAnimationFrame": false, 26 | "cancelIdleCallback": false, 27 | "clearImmediate": true, 28 | "clearInterval": false, 29 | "clearTimeout": false, 30 | "console": false, 31 | "document": false, 32 | "escape": false, 33 | "Event": false, 34 | "EventTarget": false, 35 | "exports": false, 36 | "fetch": false, 37 | "FormData": false, 38 | "global": false, 39 | "jest": false, 40 | "Map": true, 41 | "module": false, 42 | "navigator": false, 43 | "process": false, 44 | "Promise": true, 45 | "requestAnimationFrame": true, 46 | "requestIdleCallback": true, 47 | "require": false, 48 | "Set": true, 49 | "setImmediate": true, 50 | "setInterval": false, 51 | "setTimeout": false, 52 | "window": false, 53 | "XMLHttpRequest": false, 54 | "pit": false 55 | }, 56 | 57 | "rules": { 58 | "comma-dangle": [2, { 59 | "arrays": "ignore", 60 | "objects": "ignore", 61 | "imports": "ignore", 62 | "exports": "ignore", 63 | "functions": "never" 64 | }], 65 | 66 | "no-cond-assign": 1, 67 | "no-console": 0, 68 | "no-const-assign": 2, 69 | "no-constant-condition": 0, 70 | "no-control-regex": 1, 71 | "no-debugger": 1, 72 | "no-dupe-keys": 2, 73 | "no-empty": 0, 74 | "no-ex-assign": 1, 75 | "no-extra-boolean-cast": 1, 76 | "no-extra-parens": 0, 77 | "no-extra-semi": 1, 78 | "no-func-assign": 1, 79 | "no-inner-declarations": 0, 80 | "no-invalid-regexp": 1, 81 | "no-negated-in-lhs": 1, 82 | "no-obj-calls": 1, 83 | "no-regex-spaces": 1, 84 | "no-reserved-keys": 0, 85 | "no-sparse-arrays": 1, 86 | "no-unreachable": 2, 87 | "use-isnan": 1, 88 | "valid-jsdoc": 0, 89 | "valid-typeof": 1, 90 | 91 | "block-scoped-var": 0, 92 | "complexity": 0, 93 | "consistent-return": 0, 94 | "curly": 1, 95 | "default-case": 0, 96 | "dot-notation": 1, 97 | "eqeqeq": [1, "allow-null"], 98 | "guard-for-in": 0, 99 | "no-alert": 1, 100 | "no-caller": 1, 101 | "no-div-regex": 1, 102 | "no-else-return": 0, 103 | "no-eq-null": 0, 104 | "no-eval": 2, 105 | "no-extend-native": 1, 106 | "no-extra-bind": 1, 107 | "no-fallthrough": 1, 108 | "no-floating-decimal": 1, 109 | "no-implied-eval": 1, 110 | "no-labels": 1, 111 | "no-iterator": 1, 112 | "no-lone-blocks": 1, 113 | "no-loop-func": 0, 114 | "no-multi-str": 0, 115 | "no-native-reassign": 0, 116 | "no-new": 1, 117 | "no-new-func": 2, 118 | "no-new-wrappers": 1, 119 | "no-octal": 1, 120 | "no-octal-escape": 1, 121 | "no-proto": 1, 122 | "no-redeclare": 0, 123 | "no-return-assign": 1, 124 | "no-script-url": 1, 125 | "no-self-compare": 1, 126 | "no-sequences": 1, 127 | "no-unused-expressions": 0, 128 | "no-void": 1, 129 | "no-warning-comments": 0, 130 | "no-with": 1, 131 | "radix": 1, 132 | "semi-spacing": 1, 133 | "vars-on-top": 0, 134 | "wrap-iife": 0, 135 | "yoda": 1, 136 | 137 | "no-catch-shadow": 1, 138 | "no-delete-var": 1, 139 | "no-label-var": 1, 140 | "no-shadow": 1, 141 | "no-shadow-restricted-names": 1, 142 | "no-undef": 2, 143 | "no-undefined": 0, 144 | "no-undef-init": 1, 145 | "no-unused-vars": [1, { "vars": "all", "args": "none" }], 146 | "no-use-before-define": 0, 147 | 148 | "handle-callback-err": 1, 149 | "no-mixed-requires": 1, 150 | "no-new-require": 1, 151 | "no-path-concat": 1, 152 | "no-process-exit": 0, 153 | "no-restricted-modules": 1, 154 | "no-sync": 0, 155 | 156 | "eslint-comments/no-aggregating-enable": 1, 157 | "eslint-comments/no-unlimited-disable": 1, 158 | "eslint-comments/no-unused-disable": 1, 159 | "eslint-comments/no-unused-enable": 1, 160 | 161 | "flowtype/define-flow-type": 1, 162 | "flowtype/use-flow-type": 1, 163 | 164 | "prettier/prettier": [2, "fb", "@format"], 165 | 166 | "key-spacing": 0, 167 | "keyword-spacing": 1, 168 | "jsx-quotes": [1, "prefer-double"], 169 | "comma-spacing": 0, 170 | "no-multi-spaces": 0, 171 | "brace-style": 0, 172 | "camelcase": 0, 173 | "consistent-this": 1, 174 | "eol-last": 1, 175 | "func-names": 0, 176 | "func-style": 0, 177 | "new-cap": 0, 178 | "new-parens": 1, 179 | "no-nested-ternary": 0, 180 | "no-array-constructor": 1, 181 | "no-empty-character-class": 1, 182 | "no-lonely-if": 0, 183 | "no-new-object": 1, 184 | "no-spaced-func": 1, 185 | "no-ternary": 0, 186 | "no-trailing-spaces": 1, 187 | "no-underscore-dangle": 0, 188 | "no-mixed-spaces-and-tabs": 1, 189 | "quotes": [1, "single", "avoid-escape"], 190 | "quote-props": 0, 191 | "semi": 1, 192 | "sort-vars": 0, 193 | "space-in-brackets": 0, 194 | "space-in-parens": 0, 195 | "space-infix-ops": 1, 196 | "space-unary-ops": [1, { "words": true, "nonwords": false }], 197 | "max-nested-callbacks": 0, 198 | "one-var": 0, 199 | "wrap-regex": 0, 200 | 201 | "max-depth": 0, 202 | "max-len": 0, 203 | "max-params": 0, 204 | "max-statements": 0, 205 | "no-bitwise": 1, 206 | "no-plusplus": 0, 207 | 208 | "react/display-name": 0, 209 | "react/jsx-boolean-value": 0, 210 | "react/jsx-no-comment-textnodes": 1, 211 | "react/jsx-no-duplicate-props": 2, 212 | "react/jsx-no-undef": 2, 213 | "react/jsx-sort-props": 0, 214 | "react/jsx-uses-react": 1, 215 | "react/jsx-uses-vars": 1, 216 | "react/no-did-mount-set-state": 1, 217 | "react/no-did-update-set-state": 1, 218 | "react/no-multi-comp": 0, 219 | "react/no-string-refs": 1, 220 | "react/no-unknown-property": 0, 221 | "react/prop-types": 0, 222 | "react/react-in-jsx-scope": 1, 223 | "react/self-closing-comp": 1, 224 | "react/wrap-multilines": 0, 225 | 226 | "react-native/no-inline-styles": 1, 227 | 228 | "jest/no-disabled-tests": 1, 229 | "jest/no-focused-tests": 1, 230 | "jest/no-identical-title": 1, 231 | "jest/valid-expect": 1 232 | }, 233 | 234 | "overrides": [ 235 | { 236 | "files": [ 237 | "Libraries/**/*.js", 238 | "RNTester/**/*.js", 239 | "jest/**/*.js" 240 | ], 241 | "rules": { 242 | 243 | "comma-dangle": 0 244 | } 245 | }, 246 | { 247 | "files": [ 248 | "local-cli/**/*.js" 249 | ], 250 | "rules": { 251 | 252 | "comma-dangle": 0, 253 | "lint/extra-arrow-initializer": 0, 254 | "no-alert": 0, 255 | "no-console-disallow": 0 256 | }, 257 | "env": { 258 | "node": true 259 | } 260 | } 261 | ] 262 | } 263 | -------------------------------------------------------------------------------- /SFCC-RN-Components/src/components/SFCC/Product/InfoTile/InfoTile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file InfoTile.js 3 | * @fileoverview An InfoTile instance is a visual componenet that represnts a 4 | * product from a SFCC instance. 5 | */ 6 | 7 | import React, { Component } from "react"; 8 | import { 9 | View, 10 | Text, 11 | Button, 12 | StyleSheet, 13 | Image, 14 | Dimensions, 15 | FlatList 16 | } from "react-native"; 17 | import Routes from "../../../../menuItems"; 18 | import { connect } from "react-redux"; 19 | import ThumbnailImage from "../../../ImageCarousel/ThumbnailImage"; 20 | 21 | /** 22 | * @class 23 | * @name InfoTile 24 | * @desc - React Native component that displays basic product information from 25 | * a SFCC instance. 26 | */ 27 | class InfoTile extends Component { 28 | constructor(props) { 29 | super(props); 30 | const screenSize = Dimensions.get("window"); 31 | this.props = props; 32 | this.height = props.height ? props.height : screenSize.height - 46; 33 | this.width = props.width ? props.width : screenSize.width; 34 | this.state = { 35 | product: { 36 | id: "test", 37 | imageGroups: [ 38 | { 39 | images: [] 40 | } 41 | ] 42 | }, 43 | imgURL: "", 44 | imageIndex: 0 45 | }; 46 | } 47 | 48 | /** 49 | * Redux component lifecycle method used for passing in props to the component. 50 | * @param {Object} nextProps - The key/value pairs from the root reducer that 51 | * make up the applicaitons next state. 52 | */ 53 | componentWillReceiveProps(nextProps) { 54 | this.setState((prevState, nextProps) => ({ 55 | ...prevState, 56 | product: nextProps.infoTile.product, 57 | imgURL: nextProps.infoTile.imageURL 58 | })); 59 | } 60 | 61 | /************************************************* 62 | * Private instance functions. 63 | *************************************************/ 64 | 65 | /** 66 | * Event handler for the pressing of the button to get a product for showing. 67 | * 68 | * __NOTE:__ This is a method for application setup only and should be 69 | * removed once there are calls capable of getting product ID's 70 | * back to populate the parameters of this call in the future. 71 | */ 72 | _buttonPressed() { 73 | this.props.actions.requestImagesForProduct("1016786"); 74 | } 75 | 76 | /** 77 | * Sets the source of the main product image, or a _missing_ badge to show 78 | * that the photo that is supposed to be displayed could not be found. 79 | * @return {string|object} 80 | */ 81 | _setImageSrc() { 82 | if (this.state.imgURL && this.state.imgURL !== "") { 83 | return { uri: this.state.imgURL }; 84 | } else { 85 | return require("../../../../../assets/images/missing_img.png"); 86 | } 87 | } 88 | 89 | /** 90 | * @desc - Event handler for the touch/click of a product thumbnail image. 91 | * @param {Image} item - The Image class isntance that is bound to the 92 | * thumbnail that was selected. 93 | */ 94 | _thumbnailsSelected(item) { 95 | this.setState(prevState => ({ 96 | ...prevState, 97 | imgURL: item.disBaseLink 98 | })); 99 | } 100 | 101 | /** 102 | * @desc - Returns an array of ThumbnailImage stateless components for the current 103 | * ImageGroup images, or an empty View component if there are not any ImageGroups 104 | * in the current component state's 'product' property. 105 | * @return {ThumbnailImage[]|View} 106 | */ 107 | _getThumbnails() { 108 | const thumbs = 109 | this.state.product.imageGroups.length && 110 | this.state.product.imageGroups[this.state.imageIndex].images.length ? ( 111 | item.disBaseLink} 114 | horizontal={true} 115 | renderItem={({ item }) => ( 116 | this._thumbnailsSelected(item)} 120 | viewStyle={itStyles.productThumbView} 121 | imgStyle={itStyles.productThumbImg} 122 | /> 123 | )} 124 | /> 125 | ) : ( 126 | 127 | ); 128 | 129 | return thumbs; 130 | } 131 | 132 | render() { 133 | const srcObj = this._setImageSrc(); 134 | const thumbnails = this._getThumbnails(); 135 | 136 | return ( 137 | 146 | 147 |