├── 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 |
152 |
153 |
154 | {this.state.product.name}
155 |
156 |
157 |
168 |
169 | {thumbnails}
170 |
171 |
172 | {this.state.product.shortDescription}
173 |
174 |
175 |
176 | );
177 | }
178 | }
179 |
180 | const itStyles = StyleSheet.create({
181 | container: {
182 | justifyContent: "space-between",
183 | flexDirection: "column",
184 | backgroundColor: "#ececec"
185 | },
186 | productTitleContainer: {
187 | flex: 0.35,
188 | backgroundColor: "#FFFFFF",
189 | justifyContent: "center",
190 | alignItems: "center"
191 | },
192 | productImageContainer: {
193 | flex: 3,
194 | flexDirection: "column",
195 | justifyContent: "flex-start",
196 | alignItems: "stretch",
197 | padding: 10
198 | },
199 | productThumbsContainer: {
200 | flex: 1,
201 | flexDirection: "row",
202 | backgroundColor: "#000000"
203 | },
204 | productThumbView: {
205 | width: 95,
206 | height: 75,
207 | alignItems: "stretch",
208 | padding: 5
209 | },
210 | productThumbImg: {
211 | width: 88,
212 | height: 65,
213 | alignSelf: "stretch"
214 | },
215 | productTitle: {
216 | fontSize: 26,
217 | textAlign: "center"
218 | },
219 | productInfoField: {
220 | flex: 1,
221 | fontSize: 16,
222 | paddingLeft: 5
223 | },
224 | button: {},
225 | buttonContainer: {
226 | flex: 0.3,
227 | height: 60,
228 | justifyContent: "center",
229 | backgroundColor: "#65D0E5",
230 | margin: 12,
231 | borderWidth: 0,
232 | borderRadius: 8,
233 | paddingTop: 20,
234 | paddingBottom: 20
235 | },
236 | productDetailsContainer: {
237 | flex: 2
238 | }
239 | });
240 |
241 | export default connect(({ routes }) => ({ routes }))(InfoTile);
242 |
--------------------------------------------------------------------------------
/SFCC-RN-Components/src/components/Navbar/Navbar.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @file Navbar.js
5 | * @fileoverview - A Navbar component to hold the top navbar that will be included globaly
6 | * for all scenes in the app.
7 | */
8 |
9 | import React, {Component} from 'react';
10 | import {
11 | Easing,
12 | View,
13 | Text,
14 | StyleSheet,
15 | Image,
16 | TouchableHighlight,
17 | Dimensions,
18 | Animated
19 | } from 'react-native';
20 |
21 | import { Actions } from 'react-native-router-flux';
22 | import { updateProfile } from '../UserAccount/actions';
23 | //import LeftButton from './leftbutton'
24 | import { dispatch, connect } from 'react-redux';
25 | import { appConfig } from '../../config/appConfig';
26 | import { navbarStyles, colors } from '../../styles/globalStyles';
27 |
28 | /**
29 | * @class
30 | * @description - A Hamburger style menu that allows for rendering of custom
31 | * menu items as child components.
32 | */
33 | class Navbar extends Component {
34 | constructor(props) {
35 | super(props);
36 | this.props = props;
37 |
38 | /* Class property to disable buttons when animation is taking place. */
39 | this.isAnimating = false;
40 |
41 | const screenSize = Dimensions.get('window');
42 | const edge = screenSize.width - (screenSize.width * appConfig.sidebar.marginRight);
43 |
44 | this.state = {
45 | animator: new Animated.Value(-1 * edge),
46 | isOpen: false,
47 | width: screenSize.width,
48 | height: screenSize.height,
49 | edge: edge,
50 | topMenuHeight: 46,
51 | toggleTime: appConfig.sidebar.toggleTime,
52 | menuItems: props.menuItems,
53 | pageTitle: props.pageTitle
54 | };
55 | }
56 |
57 | /**
58 | * @function componentDidMount - React lifecycle method that is executed when
59 | * this component is first rendered to the dom.
60 | */
61 | componentDidMount() {
62 | this.props.categoryActions.getCategory();
63 | }
64 |
65 | /**
66 | * @function componentWillReceiveProps - React lifecycle method used to pass
67 | * in data from the application state.
68 | * @param {object} nextProps - The updated props object that is passed in from
69 | * the component's container.
70 | */
71 | componentWillReceiveProps(nextProps) {
72 | this.setState((prevState, nextProps) => ({
73 | ...prevState,
74 | menuItems: nextProps.menuItems,
75 | pateTitle: nextProps.pageTitle
76 | }));
77 | }
78 |
79 | /**
80 | * @function _toggleSidebar
81 | * Toggle the sidebar menu.
82 | */
83 | _toggleSidebar() {
84 | if (!this.isAnimating) {
85 | this.setState((prevState, props) => ({
86 | isOpen: !prevState.isOpen
87 | }));
88 |
89 | /* Disable the hamburger button */
90 | this.isAnimating = true;
91 | this._animateSidebar(() => {
92 | this.forceUpdate();
93 | });
94 | }
95 | }
96 |
97 | /**
98 | * @function _menuItemSelect - Used to call an action creation method assigned
99 | * to handle a click action on a menu item.
100 | * @param {object} item - An action creation method to call when the
101 | * associated menu item is selcted.
102 | */
103 | _menuItemSelect(item) {
104 | Actions[item.id].apply();
105 | }
106 |
107 | /**
108 | * @function _animateSidebar - Animate the toggle of the sidebar menu.
109 | * @param {function callback() {}}
110 | */
111 | _animateSidebar(callback) {
112 | /* Set the current value for the open/close animation */
113 | const animateValue = this.state.isOpen
114 | ? 0
115 | : (-1 * this.state.edge);
116 | this.state.animator.setValue(animateValue);
117 |
118 | /* Set the value we want to animate to */
119 | const newX = !this.state.isOpen
120 | ? 0
121 | : (-1 * this.state.edge);
122 |
123 | const animateConfig = {
124 | toValue: newX,
125 | duration: this.state.toggleTime,
126 | easing: Easing.linear
127 | };
128 |
129 | // Animate the opening and closing of the menu, and set the state to reflect the changes.
130 | Animated.timing(this.state.animator, {
131 | ...animateConfig
132 | },).start(() => {
133 | /* Enable the hamburger button. */
134 | this.isAnimating = false;
135 | });
136 | }
137 |
138 | /**
139 | * @function _handleLayoutChange - Event handler method to adjust rendering of
140 | * the navbar component when the screen orientation of the host device is
141 | * changed between landscape and portrait.
142 | * @param {Event} event - The Event object that is created when the action
143 | * occurs.
144 | */
145 | _handleLayoutChange(event) {
146 | const layout = event.nativeEvent.layout;
147 | this.setState({
148 | height: layout.height,
149 | width: layout.width,
150 | edge: layout.width - (layout.width * appConfig.sidebar.marginRight)
151 | });
152 | }
153 |
154 | /**
155 | * @function _getSidebarOverlay - Creates an overlay for holding all of the controls
156 | * that live in the sidebar control.
157 | * @return {React.Component}
158 | */
159 | _getSidebarOverlay() {
160 | const menuWidth = this.state.isOpen
161 | ? this.state.edge
162 | : 0;
163 |
164 | const mySidebar = this.state.menuItems.map((item) => {
165 | if (!item.renderProps) {
166 | return (
167 | {
169 | this._toggleSidebar();
170 | this._menuItemSelect(item);
171 | }}>
172 |
173 |
174 |
175 | {item.title}
176 |
177 |
178 |
179 |
180 | );
181 | } else {
182 | return item.renderProps();
183 | }
184 | });
185 |
186 | return (
187 |
199 | {mySidebar}
200 |
201 | );
202 | }
203 |
204 | /**
205 | * @function _updateProfile - Calls the action creator method to navigate to
206 | * the user profile editor.
207 | */
208 | _updateProfile() {
209 | Actions['userAccount'].apply();
210 | }
211 |
212 | /**
213 | * @function render - Renders the custom function when a layout change is needed.
214 | * @return {React.Component}
215 | */
216 | render() {
217 | return (
218 |
219 |
222 |
223 |
228 |
229 |
232 |
233 | {this.props.pageTitle}
234 |
235 |
236 |
239 |
240 |
241 |
242 |
243 |
244 | {this._getSidebarOverlay()}
245 |
246 |
252 | {this.props.children}
253 |
254 |
255 | );
256 | }
257 | }
258 |
259 | export default connect(({routes}) => ({routes}))(Navbar);
260 |
--------------------------------------------------------------------------------
/SFCC-RN-Components/src/lib/OCAPIService/OCAPIService.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file OCAPIService.js
3 | * @desc The OCAPIService is a singleton service wrapper for making API calls to
4 | * the Open Commerce API of your Sales Force Cloud Commerce instance.
5 | *
6 | * Last Changed: 12/4/17 -- ghgofort
7 | *
8 | * @author Galen Goforth
9 | * - Email: galengoforth@gmail.com
10 | * - Github: ghgofort
11 | */
12 |
13 | import { apiConfig } from '../../config/apiConfig';
14 | import { appConfig } from '../../config/appConfig';
15 | import OCAPICallInfo from './OCAPICallInfo';
16 |
17 | let OCAPIServiceInstance = null;
18 |
19 | /**
20 | * @class
21 | * @name OCAPIService
22 | * @desc The OCAPIService is a JS service layer for making calls to the Sales Force
23 | * Commerce Cloud instance utilizing the Open Commerce API.
24 | */
25 | export default class OCAPIService {
26 | constructor() {
27 | this._isMock = apiConfig.OCAPI.environment[appConfig.instanceType] === 'mock';
28 | if (!OCAPIServiceInstance) {
29 | OCAPIServiceInstance = this;
30 | }
31 | return OCAPIServiceInstance;
32 | }
33 |
34 | /* ========================================================================
35 | * Public Class Members
36 | * ========================================================================*/
37 |
38 | /**
39 | * makeCall
40 | * @param {OCAPICallInfo} callSetup - An instance of the OCAPICallInfo class.
41 | * @return {Promise} - Returns a Promise that resolves to the requested data.
42 | */
43 | makeCall(callSetup) {
44 | return this._fetchData(callSetup);
45 | }
46 |
47 | /**
48 | * Gets the full URI of the API call, and sets up the body of the call and returns this
49 | * information as key value pairs in an object.
50 | *
51 | * @param {string} callName
52 | * @param {object} callData
53 | * @return {OCAPICallInfo} - Returns an instance of the OCAPICallInfo class with the attributes
54 | * set to the values needed to make the call to the REST API.
55 | */
56 | setupCall(resourceName, callName, callData) {
57 | const setupData = new OCAPICallInfo();
58 | const usedParams = [];
59 | let ep = '';
60 | let errMsg = 'ERROR in OCAPIService at setupCall';
61 |
62 | try {
63 | const rescSetup = apiConfig.OCAPI.resources[resourceName] || null;
64 | const callSetup = rescSetup && rescSetup.calls && rescSetup.calls[callName] ?
65 | rescSetup.calls[callName] : null;
66 |
67 | setupData.apiCallFunction = this._isMock ? this._fetchMockData : this._fetchData;
68 | setupData.endpoint = '';
69 | setupData.body = {};
70 | setupData.error = false;
71 | setupData.errMsg ='';
72 |
73 | // Set the API to return mock data if setup to use the mock in the apiConfig object.
74 | if (this._isMock) {
75 | setupData.apiCallFunction = this._fetchMockData;
76 | }
77 |
78 | // Setup the URI for making the REST call.
79 | if (callSetup) {
80 | setupData.headers = callSetup.headers;
81 | setupData.httpVerb = callSetup.callType;
82 | // Get the base endpoint from the config file for the configured instance type.
83 | ep = apiConfig.OCAPI.baseEndpoints[appConfig.instanceType];
84 |
85 | // Append the API path and the API version for this call.
86 | ep += apiConfig.OCAPI.API[rescSetup.API].path;
87 | ep += '/' + apiConfig.OCAPI.currentVersion;
88 | // Append the resource name, and the call name paths.
89 | ep += rescSetup.path;
90 | ep += callSetup.path;
91 |
92 | // Append the client_id field as a URL parameter.
93 | ep += ('?client_id=' + apiConfig.OCAPI.clientIDs[appConfig.instanceType]);
94 | setupData.endpoint = ep;
95 | } else {
96 | errMsg += '\nThe requested resource, or resource method was not found:';
97 | errMsg += '\nResource: ' + resourceName + '\nCall Name: ' + callName;
98 | setupData.error = true;
99 | setupData.errMsg = errMsg;
100 | }
101 |
102 | // Check for any required url parameters included in the config setup and add
103 | // them to the config endpoint string.
104 | if (callSetup && callSetup.requiredParams && callSetup.requiredParams.length) {
105 | callSetup.requiredParams.forEach(field => {
106 | if (!callData[field]) {
107 | setupData.error = true;
108 | setupData.errMsg = errMsg + '\nRequired call parameter: ' + field + ' is missing.';
109 | } else {
110 | // Append each query string parameter to the URL
111 | setupData.endpoint = setupData.endpoint + '&' + field + '=' + callData[field];
112 | usedParams.push(field);
113 | }
114 | });
115 | }
116 |
117 | // Check for any required request fields included in the config setup.
118 | if (callSetup && callSetup.requiredData && callSetup.requiredData.length) {
119 | callSetup.requiredData.forEach(field => {
120 | if (!callData[field]) {
121 | setupData.error = true;
122 | setupData.errMsg = setupData.errMsg + '\nRequired call data: ' + field + ' is missing.';
123 | } else {
124 | let bd = setupData.body;
125 | bd[field] = callData[field];
126 | setupData.body = bd;
127 | usedParams.push(field);
128 | }
129 | });
130 | }
131 |
132 | // Check if there are any path parameters and replace the place holder with the value passed in.
133 | if (callSetup && callSetup.pathParams && callSetup.pathParams.length) {
134 | callSetup.pathParams.forEach(field => {
135 | if (!callData[field.name]) {
136 | setupData.error = true;
137 | setupData.errMsg += '\nRequired path parameter: ' + field.name + ' is missing.';
138 | } else {
139 | // If we are getting multiples of a resource, then we turn the array of values into a
140 | // comma-seperated list enclosed in parenthasis.
141 | let fieldValue = Array.isArray(callData[field.name]) ? '(' + callData[field.name].toString() + ')' : callData[field.name];
142 | let strReplace = '{' + field.index + '}';
143 | setupData.endpoint = setupData.endpoint.replace(strReplace, fieldValue);
144 | usedParams.push(field);
145 | }
146 | });
147 | }
148 |
149 | // Add any non-required parameters that were included.
150 | if (callData && usedParams.length) {
151 | Object.keys(callData).filter(key => usedParams.indexOf(key) > -1).forEach(key => {
152 | setupData.endpoint = setupData.endpoint + '&' + key + '=' + callData[key];
153 | });
154 | }
155 | } catch (e) {
156 | console.log('Exception caught:');
157 | console.log(e);
158 | setupData.error = true;
159 | Object.keys(e).forEach(key => { setupData.errMsg = setupData.errMsg + key + ': ' + e[key] });
160 | }
161 | return setupData;
162 | }
163 |
164 |
165 | /* ========================================================================
166 | * Private Class Members
167 | * ========================================================================*/
168 |
169 | /**
170 | * Makes a call to the live API to retrieve data, then returns a Promise that
171 | * will resolve to the results of the call.
172 | *
173 | * @private
174 | * @param {OCAPICallInfo} callData - An instance of the OCAPICallInfo class that contains the concatenated
175 | * endpoint, headers, and body of the request that will be made.
176 | * @return {Promise} - Returns a Promise that is returned from the fetch call to the API endpoint.
177 | */
178 | _fetchData(callData) {
179 | // Return the Promise created by the fetch operation.
180 | return fetch(callData.endpoint, {
181 | method: callData.httpVerb,
182 | headers: new Headers(callData.headers)
183 | });
184 | }
185 |
186 | /**
187 | * Retrieves data from the mockData static file and returns a Promise that resolves to the data
188 | * that was retrieved.
189 | *
190 | * @private
191 | * @param {OCAPICallInfo} callData - An instance of the OCAPICallInfo class that contains the concatenated
192 | * endpoint, headers, and body of the request that will be made.
193 | * @return {Promise} - Returns a Promise that is returned from the fetch call to the API endpoint.
194 | */
195 | _fetchMockData(callData) {
196 | return new Promise(
197 | (data) => {}, (reason) => {
198 |
199 | }
200 | );
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/SFCC-RN-Components/src/lib/documents/Product.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Product.js
3 | * Contains the Product class.
4 | */
5 |
6 | import BundledProduct from './BundledProduct';
7 | import ImageGroup from './ImageGroup';
8 | import Inventory from './Inventory';
9 | import Option from './Option';
10 | import ProductLink from './ProductLink';
11 | import ProductPromotion from './ProductPromotion';
12 | import ProductType from './ProductType';
13 | import Recommendation from './Recommendation';
14 | import Variant from './Variant';
15 | import VariationGroup from './VariationGroup';
16 |
17 | /**
18 | * The Product class is a data model class for an SFCC Product {dw.order.Product} instance
19 | * returned from an OCAPI API call.
20 | * @see https://documentation.demandware.com/DOC2/topic/com.demandware.dochelp/OCAPI/18.1/shop/Documents/Product.html?cp=0_12_5_80
21 | * @class Product
22 | */
23 | export default class Product {
24 |
25 | /**
26 | * @param {Object} [args] - An optional object of key value pairs used to pre-populate
27 | * attribute values of the Product instance.
28 | */
29 | constructor(args) {
30 | /** @type {String} */
31 | this._brand = args && args.brand ? args.brand : '';
32 | /** @type {Array} */
33 | this._bundledProducts = args && args.bundled_products ?
34 | args.bundled_products.map(bp => new BundledProduct(bp)) : [];
35 | /** @type {String} */
36 | this._currency = args && args.currency ? args.currency : '';
37 | /** @type {String} */
38 | this._ean = args && args.ean ? args.ean : '';
39 | /** @type {String} */
40 | this._id = args && args.id ? args.id : '';
41 | /** @type {Array} */
42 | this._imageGroups = args && args.image_groups ?
43 | args.image_groups.map(ig => new ImageGroup(ig)) : [];
44 | /** @type {Array} */
45 | this._inventories = args && args.inventories ?
46 | args.inventories.map(iv => new Inventory(iv)) : [];
47 | /** @type {Inventory} */
48 | this._inventory = args && args.inventory ? new Inventory(args.inventory) : new Inventory();
49 | /** @type {String} */
50 | this._longDescription = args && args.long_description ? args.long_description : '';
51 | /** @type {String} */
52 | this._manufacturerName = args && args.manufacturer_name ? args.manufacturer_name : '';
53 | /** @type {String} */
54 | this._manufacturerSKU = args && args.manufacturer_sku ? args.manufacturer_sku : '';
55 | /** @type {boolean} */
56 | this._master = args && args.isMaster ? args.isMaster : false;
57 | /** @type {Number} */
58 | this._minOrderQuantity = args && args.min_order_quantity ? args.min_order_quantity : '';
59 | /** @type {String} */
60 | this._name = args && args.name ? args.name : '';
61 | /** @type {Array