├── .gitignore
├── README.md
├── backoffice
├── .gitignore
├── .prettierrc
├── README.md
├── graphql.config.json
├── package-lock.json
├── package.json
├── public
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.css
│ ├── App.js
│ ├── assets
│ │ └── img
│ │ │ ├── graphcool.png
│ │ │ └── graphql.png
│ ├── constants
│ │ └── index.js
│ ├── graphql
│ │ ├── mutations
│ │ │ └── index.js
│ │ └── queries
│ │ │ └── index.js
│ ├── i18n
│ │ └── index.js
│ ├── index.css
│ ├── index.js
│ ├── pages
│ │ ├── attributes
│ │ │ ├── components
│ │ │ │ ├── CreateAttribute.js
│ │ │ │ └── ListAttribute.js
│ │ │ └── styles
│ │ │ │ ├── CreateAttribute.css
│ │ │ │ └── ListAttributes.css
│ │ ├── best-sales
│ │ │ ├── ListBestSales.css
│ │ │ └── components
│ │ │ │ └── ListBestSales.js
│ │ ├── brands
│ │ │ ├── components
│ │ │ │ ├── CreateBrand.js
│ │ │ │ └── ListBrand.js
│ │ │ └── styles
│ │ │ │ ├── Listbrand.css
│ │ │ │ └── createbrand.css
│ │ ├── categories
│ │ │ ├── components
│ │ │ │ ├── CreateCategory.js
│ │ │ │ └── ListCategory.js
│ │ │ └── styles
│ │ │ │ ├── CreateCategory.css
│ │ │ │ └── ListCategory.css
│ │ ├── common
│ │ │ ├── components
│ │ │ │ ├── Button.js
│ │ │ │ └── Buttons.js
│ │ │ └── styles
│ │ │ │ ├── Buttons.css
│ │ │ │ ├── Reactable.css
│ │ │ │ └── button.css
│ │ ├── layout
│ │ │ ├── components
│ │ │ │ ├── Main.js
│ │ │ │ ├── Root.js
│ │ │ │ ├── Sidebar.js
│ │ │ │ └── SidebarItem.js
│ │ │ └── styles
│ │ │ │ ├── main.css
│ │ │ │ ├── root.css
│ │ │ │ ├── sidebar.css
│ │ │ │ └── sidebarItem.css
│ │ ├── login
│ │ │ ├── components
│ │ │ │ └── Login.js
│ │ │ └── styles
│ │ │ │ └── Login.css
│ │ ├── motd
│ │ │ ├── MOTD.css
│ │ │ └── MOTD.js
│ │ ├── new-products
│ │ │ ├── ListNewProducts.css
│ │ │ └── components
│ │ │ │ └── ListNewProducts.js
│ │ ├── options
│ │ │ ├── components
│ │ │ │ ├── CreateOption.js
│ │ │ │ └── ListOption.js
│ │ │ └── styles
│ │ │ │ ├── CreateOption.css
│ │ │ │ └── ListOption.css
│ │ ├── orders
│ │ │ ├── components
│ │ │ │ └── ListOrder.js
│ │ │ └── styles
│ │ │ │ └── Listorder.css
│ │ ├── products
│ │ │ ├── components
│ │ │ │ ├── CreateProduct.js
│ │ │ │ ├── ImageUpload.js
│ │ │ │ ├── ListProduct.js
│ │ │ │ └── Resume.js
│ │ │ └── styles
│ │ │ │ ├── createproduct.css
│ │ │ │ ├── imageupload.css
│ │ │ │ ├── listproduct.css
│ │ │ │ └── resume.css
│ │ └── users
│ │ │ ├── components
│ │ │ └── ListUser.js
│ │ │ └── styles
│ │ │ └── Listuser.css
│ └── registerServiceWorker.js
└── yarn.lock
├── mobile
├── .babelrc
├── .buckconfig
├── .flowconfig
├── .gitattributes
├── .gitignore
├── .prettierrc
├── .watchmanconfig
├── App.js
├── __tests__
│ └── App.js
├── android
│ ├── app
│ │ ├── BUCK
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── assets
│ │ │ └── fonts
│ │ │ │ ├── Entypo.ttf
│ │ │ │ ├── EvilIcons.ttf
│ │ │ │ ├── Feather.ttf
│ │ │ │ ├── FontAwesome.ttf
│ │ │ │ ├── Foundation.ttf
│ │ │ │ ├── Ionicons.ttf
│ │ │ │ ├── MaterialCommunityIcons.ttf
│ │ │ │ ├── MaterialIcons.ttf
│ │ │ │ ├── Octicons.ttf
│ │ │ │ ├── SimpleLineIcons.ttf
│ │ │ │ └── Zocial.ttf
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── aromaclop
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MainApplication.java
│ │ │ └── res
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── keystores
│ │ ├── BUCK
│ │ └── debug.keystore.properties
│ └── settings.gradle
├── app.json
├── graphql.config.json
├── index.js
├── ios
│ ├── Aromaclop-tvOS
│ │ └── Info.plist
│ ├── Aromaclop-tvOSTests
│ │ └── Info.plist
│ ├── Aromaclop.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ ├── Aromaclop-tvOS.xcscheme
│ │ │ └── Aromaclop.xcscheme
│ ├── Aromaclop.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── Aromaclop
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Aromaclop.entitlements
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.xib
│ │ ├── Images.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── main.m
│ ├── AromaclopTests
│ │ ├── AromaclopTests.m
│ │ └── Info.plist
│ ├── OneSignalNotificationServiceExtension
│ │ ├── Info.plist
│ │ ├── NotificationService.h
│ │ └── NotificationService.m
│ ├── Podfile
│ └── Podfile.lock
├── package.json
├── src
│ ├── assets
│ │ ├── fonts
│ │ │ └── index.js
│ │ ├── logo.png
│ │ └── no_data.png
│ ├── components
│ │ ├── banner
│ │ │ ├── Banner.js
│ │ │ ├── Banner.stories.js
│ │ │ └── Banner.styles.js
│ │ ├── basket-card
│ │ │ ├── BasketCard.js
│ │ │ ├── BasketCard.styles.js
│ │ │ ├── sub-total
│ │ │ │ └── SubTotal.js
│ │ │ └── taxons-selector
│ │ │ │ └── TaxonsSelector.js
│ │ ├── big-red-button
│ │ │ ├── BigRedButton.js
│ │ │ └── BigRedButton.styles.js
│ │ ├── button
│ │ │ ├── Button.js
│ │ │ ├── Button.stories.js
│ │ │ └── Button.styles.js
│ │ ├── card
│ │ │ ├── Card.js
│ │ │ ├── Card.stories.js
│ │ │ ├── Card.styles.js
│ │ │ └── assets
│ │ │ │ └── blank.png
│ │ ├── empty-list
│ │ │ └── EmptyList.js
│ │ ├── gradient
│ │ │ ├── Gradient.js
│ │ │ └── Gradient.stories.js
│ │ ├── input
│ │ │ ├── Input.js
│ │ │ ├── Input.stories.js
│ │ │ └── Input.styles.js
│ │ ├── layout
│ │ │ ├── Container.js
│ │ │ ├── KeyboardAwareCenteredView.js
│ │ │ └── KeyboardAwareCenteredView.styles.js
│ │ ├── loading
│ │ │ ├── FullLoading.js
│ │ │ └── LoadingIndicator.js
│ │ ├── modal-navigation-bar
│ │ │ ├── ModalNavigationBar.js
│ │ │ └── ModalNavigationBar.styles.js
│ │ ├── navigation-button
│ │ │ ├── NavigationButton.js
│ │ │ ├── NavigationButton.stories.js
│ │ │ └── NavigationButton.styles.js
│ │ ├── order-icon
│ │ │ ├── OrderIcon.js
│ │ │ └── OrderIcon.styles.js
│ │ ├── order
│ │ │ └── Order.js
│ │ └── title
│ │ │ ├── Title.js
│ │ │ └── Title.stories.js
│ ├── graphql
│ │ ├── fragments
│ │ │ └── ProductCardFragment.js
│ │ ├── queries
│ │ │ └── index.js
│ │ └── setupApollo.js
│ ├── helpers
│ │ └── StackModalNavigator.js
│ ├── i18n
│ │ └── index.js
│ ├── routes
│ │ └── index.js
│ ├── statics
│ │ ├── colors
│ │ │ └── index.js
│ │ └── storage-keys
│ │ │ └── index.js
│ └── views
│ │ ├── basket
│ │ ├── Basket.js
│ │ ├── BasketContainer.js
│ │ └── query.gql.js
│ │ ├── browse
│ │ ├── Browse.js
│ │ ├── BrowseContainer.js
│ │ └── query.gql.js
│ │ ├── filters
│ │ ├── Filters.js
│ │ ├── Filters.styles.js
│ │ ├── HOC
│ │ │ └── FiltersHOC.js
│ │ ├── filterByFields
│ │ │ ├── BrandFilters.js
│ │ │ ├── CategoryFilters.js
│ │ │ └── OptionsFilters.js
│ │ ├── filtersGroup
│ │ │ ├── Filter.js
│ │ │ ├── Filter.styles.js
│ │ │ ├── FiltersGroup.js
│ │ │ └── index.js
│ │ └── query.gql.js
│ │ ├── home
│ │ ├── Home.js
│ │ ├── Home.styles.js
│ │ ├── HomeContainer.js
│ │ └── query.gql.js
│ │ ├── login
│ │ ├── Login.js
│ │ └── Login.styles.js
│ │ ├── password-reset
│ │ ├── AskPasswordReset.js
│ │ ├── AskPasswordResetContainer.js
│ │ └── queries.gql.js
│ │ ├── payment
│ │ ├── AfterPayment.js
│ │ ├── Button.js
│ │ ├── Payment.js
│ │ ├── PaymentContainer.js
│ │ └── query.gql.js
│ │ ├── product
│ │ ├── Product.js
│ │ ├── Product.styles.js
│ │ ├── ProductContainer.js
│ │ └── queries.gql.js
│ │ ├── products
│ │ ├── Products.js
│ │ ├── Products.styles.js
│ │ ├── ProductsContainer.js
│ │ ├── query.gql.js
│ │ └── subscription.gql.js
│ │ ├── profile
│ │ ├── Profile.js
│ │ └── views
│ │ │ ├── delete
│ │ │ ├── Delete.js
│ │ │ ├── DeleteContainer.js
│ │ │ └── query.gql.js
│ │ │ ├── orders
│ │ │ ├── Orders.js
│ │ │ ├── OrdersContainer.js
│ │ │ └── query.gql.js
│ │ │ ├── password
│ │ │ ├── Password.js
│ │ │ ├── PasswordContainer.js
│ │ │ └── query.gql.js
│ │ │ ├── shops
│ │ │ ├── Shops.js
│ │ │ ├── ShopsContainer.js
│ │ │ └── queries.gql.js
│ │ │ ├── terms
│ │ │ ├── Terms.js
│ │ │ ├── TermsContainer.js
│ │ │ └── query.gql.js
│ │ │ └── username
│ │ │ ├── Username.js
│ │ │ ├── UsernameContainer.js
│ │ │ └── queries.gql.js
│ │ ├── recap
│ │ ├── Recap.js
│ │ ├── Recap.styles.js
│ │ └── query.gql.js
│ │ ├── search
│ │ ├── Search.js
│ │ ├── Search.styles.js
│ │ ├── SearchBox.js
│ │ ├── SearchHOC.js
│ │ └── query.gql.js
│ │ ├── sign-in
│ │ ├── SignIn.js
│ │ ├── SignIn.styles.js
│ │ ├── SignInContainer.js
│ │ └── queries.gql.js
│ │ └── sign-up
│ │ ├── SignUp.js
│ │ ├── SignUp.styles.js
│ │ ├── SignUpContainer.js
│ │ ├── query.gql.js
│ │ └── steps
│ │ ├── FinalStep.js
│ │ ├── FirstStep.js
│ │ ├── FourthStep.js
│ │ ├── SecondStep.js
│ │ └── ThirdStep.js
├── storybook
│ ├── addons.js
│ └── index.js
└── yarn.lock
└── prisma
├── .env
├── .gitignore
├── .graphqlconfig.yml
├── .import
├── lists
│ └── 000001.json
├── nodes
│ └── 000001.json
└── relations
│ └── 000001.json
├── .prettierrc
├── README.md
├── database
├── datamodel.graphql
├── docker-compose.yml
└── prisma.yml
├── docker-compose.yml
├── graphql.config.json
├── graphql.schema.json
├── package.json
├── scripts
├── backup.sh
└── deploy.sh
├── src
├── emails
│ └── passwordReset
│ │ ├── html.pug
│ │ └── subject.pug
├── exceptions
│ └── index.ts
├── generated
│ ├── prisma.graphql
│ └── prisma.ts
├── index.ts
├── resolvers
│ ├── AuthPayload.ts
│ ├── Mutation
│ │ ├── attribute.ts
│ │ ├── auth.ts
│ │ ├── brand.ts
│ │ ├── cart.ts
│ │ ├── category.ts
│ │ ├── index.ts
│ │ ├── option.ts
│ │ ├── order.ts
│ │ ├── product.ts
│ │ ├── shopMetadata.ts
│ │ ├── stripe.ts
│ │ └── user.ts
│ ├── Query
│ │ ├── Query.ts
│ │ └── User.ts
│ ├── Subscription
│ │ └── index.ts
│ └── index.ts
├── schema.graphql
├── third-party
│ ├── nodemailer.ts
│ └── oneSignal.ts
└── utils.ts
├── tsconfig.json
└── yarn.lock
/backoffice/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Example user template
3 |
4 | # IntelliJ project files
5 | .idea
6 | node_modules
7 | out
8 | gen
9 | .vscode/settings.json
10 |
--------------------------------------------------------------------------------
/backoffice/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": true,
4 | "trailingComma": "all",
5 | "arrowParens": "avoid",
6 | "jsxBracketSameLine": false,
7 | "tabWidth": 2,
8 | "printWidth": 90,
9 | "bracketSpacing": true
10 | }
11 |
--------------------------------------------------------------------------------
/backoffice/README.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | Install packages
4 |
5 | ### `yarn install`
6 |
7 | Start project
8 |
9 | ### `yarn start`
10 |
11 | You can now view react-aromaclop-backoffice in the browser.
12 |
13 | Local: http://localhost:3000/
14 |
15 | On Your Network: http://192.168.1.22:3000/
16 |
17 | Note that the development build is not optimized.
18 | To create a production build, use yarn build.
19 |
20 | ## Folder Structure
21 |
22 | ```
23 | app/
24 | src/
25 | graphql/
26 |
27 | queries/
28 | index.js
29 |
30 | mutations/
31 | index.js
32 |
33 | pages/
34 | products/
35 |
36 | components/
37 | Component.js
38 |
39 | styles/
40 | Component.css
41 | ```
--------------------------------------------------------------------------------
/backoffice/graphql.config.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "README_schema" : "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE",
4 | "schema": {
5 |
6 | "README_file" : "Remove 'file' to use request url below. A relative or absolute path to the JSON from a schema introspection query, e.g. '{ data: ... }'. Changes to the file are watched.",
7 |
8 | "README_request" : "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).",
9 | "request": {
10 | "url" : "http://localhost:4000/",
11 | "method" : "POST",
12 | "README_postIntrospectionQuery" : "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET",
13 | "postIntrospectionQuery" : true,
14 | "README_options" : "See the 'Options' section at https://github.com/then/then-request",
15 | "options" : {
16 | "headers": {
17 | "user-agent" : "JS GraphQL"
18 | }
19 | }
20 | }
21 |
22 | },
23 |
24 | "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE",
25 | "endpoints" : [
26 | {
27 | "name": "Default (http://localhost:4000/graphql)",
28 | "url": "http://localhost:4000/",
29 | "options" : {
30 | "headers": {
31 | "user-agent" : "JS GraphQL"
32 | }
33 | }
34 | }
35 | ]
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/backoffice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-aromaclop-backoffice",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "apollo-client": "^1.9.0-0",
7 | "axios": "^0.16.2",
8 | "bluebird": "^3.5.0",
9 | "chart.js": "^2.6.0",
10 | "graphql-tag": "^2.4.2",
11 | "lodash": "^4.17.4",
12 | "prop-types": "^15.5.10",
13 | "react": "^15.6.1",
14 | "react-apollo": "^1.4.4",
15 | "react-awesome-modal": "^2.0.1",
16 | "react-chartjs-2": "^2.5.5",
17 | "react-dom": "^15.6.1",
18 | "react-icons": "^2.2.5",
19 | "react-intl": "^2.4.0",
20 | "react-router": "^4.1.2",
21 | "react-router-dom": "next",
22 | "react-select": "^1.0.0-rc.5",
23 | "react-sortable-hoc": "^0.6.8",
24 | "react-spinkit": "^3.0.0",
25 | "react-table": "^6.5.1",
26 | "tachyons": "^4.7.4"
27 | },
28 | "devDependencies": {
29 | "prettier": "latest",
30 | "react-scripts": "1.0.10"
31 | },
32 | "scripts": {
33 | "start": "react-scripts start",
34 | "build": "react-scripts build",
35 | "test": "react-scripts test --env=jsdom",
36 | "eject": "react-scripts eject"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/backoffice/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Aromaclop
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/backoffice/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/backoffice/src/App.css:
--------------------------------------------------------------------------------
1 | .App-link {
2 | border-left: 3px solid #0f202e;
3 | text-decoration: none;
4 | color: rgba(255, 255, 255, 0.3);
5 | width: 100%;
6 | padding: 0 5px 0 10px;
7 | font-weight: 400;
8 | }
9 |
10 | .App-link-title {
11 | margin: 15px 0px 15px 15px;
12 | font-size: 10px;
13 | color: rgba(255, 255, 255, 0.7);
14 | text-transform: uppercase;
15 | letter-spacing: 1px;
16 | font-weight: bold;
17 | }
18 |
19 | .App-link-active {
20 | color: #f9f9f9;
21 | }
22 |
23 | .App-link:hover {
24 | border-left: 3px solid #cc6155;
25 | background-color: #282c36;
26 | }
27 |
28 | .App-link-label {
29 | margin: 10px 0 10px 0;
30 | font-size: 12px;
31 | }
32 |
33 | .App-icon {
34 | margin: 10px;
35 | }
36 |
37 | .App-separator {
38 | width: 80%;
39 | margin-top: 25px;
40 | border: 1px solid rgba(255, 255, 255, 0.06);
41 | }
42 |
--------------------------------------------------------------------------------
/backoffice/src/assets/img/graphcool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/backoffice/src/assets/img/graphcool.png
--------------------------------------------------------------------------------
/backoffice/src/assets/img/graphql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/backoffice/src/assets/img/graphql.png
--------------------------------------------------------------------------------
/backoffice/src/constants/index.js:
--------------------------------------------------------------------------------
1 | export const GC_AUTH_TOKEN = 'gc-auth-token';
2 |
--------------------------------------------------------------------------------
/backoffice/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/backoffice/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/backoffice/src/pages/attributes/styles/CreateAttribute.css:
--------------------------------------------------------------------------------
1 | .CreateAttribute-input {
2 | width: 96%;
3 | height: 34px;
4 | border: 1px solid rgba(0, 0, 0, 0.2);
5 | border-radius: 4px;
6 | padding-left: 10px;
7 | margin-bottom: 10px;
8 | }
9 |
10 | .CreateAttribute-select {
11 | margin-bottom: 10px;
12 | }
13 |
14 | .CreateAttribute-label {
15 | font-size: 14px;
16 | font-weight: 600;
17 | line-height: 30px;
18 | width: 96%;
19 | }
20 |
21 | .CreateAttribute-container {
22 | display: flex;
23 | flex: 1;
24 | justify-content: center;
25 | flex-direction: column;
26 | align-items: center;
27 | margin: 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/backoffice/src/pages/attributes/styles/ListAttributes.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/backoffice/src/pages/attributes/styles/ListAttributes.css
--------------------------------------------------------------------------------
/backoffice/src/pages/best-sales/ListBestSales.css:
--------------------------------------------------------------------------------
1 | .ListBestSales-container {
2 | padding: 30px 20%;
3 | }
4 |
5 | .ListBestSales-listItem {
6 | display: flex;
7 | align-items: center;
8 | width: 100%;
9 | padding: 20px;
10 | background-color: #fff;
11 | border-bottom: 1px solid #efefef;
12 | box-sizing: border-box;
13 | user-select: none;
14 | color: #333;
15 | font-weight: 400;
16 | }
17 |
18 | .ListBestSales-select {
19 | margin-bottom: 10px;
20 | flex: 1;
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/backoffice/src/pages/brands/styles/Listbrand.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/backoffice/src/pages/brands/styles/Listbrand.css
--------------------------------------------------------------------------------
/backoffice/src/pages/brands/styles/createbrand.css:
--------------------------------------------------------------------------------
1 | .Createbrand-input {
2 | width: 96%;
3 | height: 34px;
4 | border: 1px solid rgba(0, 0, 0, 0.2);
5 | border-radius: 4px;
6 | padding-left: 10px;
7 | margin-bottom: 10px;
8 | }
9 |
10 | .Createbrand-select {
11 | margin-bottom: 10px;
12 | }
13 |
14 | .Createbrand-label {
15 | font-size: 14px;
16 | font-weight: 600;
17 | line-height: 30px;
18 | width: 96%;
19 | }
20 |
21 | .Createbrand-container {
22 | display: flex;
23 | flex: 1;
24 | justify-content: center;
25 | flex-direction: column;
26 | align-items: center;
27 | margin: 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/backoffice/src/pages/categories/styles/CreateCategory.css:
--------------------------------------------------------------------------------
1 | .CreateCategory-input {
2 | width: 96%;
3 | height: 34px;
4 | border: 1px solid rgba(0, 0, 0, 0.2);
5 | border-radius: 4px;
6 | padding-left: 10px;
7 | margin-bottom: 10px;
8 | }
9 |
10 | .CreateCategory-label {
11 | font-size: 14px;
12 | font-weight: 600;
13 | line-height: 30px;
14 | width: 96%;
15 | }
16 |
17 | .CreateCategory-select {
18 | margin-bottom: 10px;
19 | }
20 |
21 | .CreateCategory-container {
22 | display: flex;
23 | flex: 1;
24 | justify-content: center;
25 | flex-direction: column;
26 | align-items: center;
27 | margin: 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/backoffice/src/pages/categories/styles/ListCategory.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/backoffice/src/pages/common/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import proptypes from 'prop-types';
3 |
4 | import '../styles/button.css';
5 |
6 | const Button = props => {
7 | return (
8 |
9 |
17 |
18 | );
19 | };
20 |
21 | Button.proptypes = {
22 | color: proptypes.string,
23 | callback: proptypes.func,
24 | icon: proptypes.element,
25 | label: proptypes.string,
26 | };
27 |
28 | export default Button;
29 |
--------------------------------------------------------------------------------
/backoffice/src/pages/common/components/Buttons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import proptypes from 'prop-types';
3 | import _ from 'lodash';
4 |
5 | import Button from './Button';
6 | import '../styles/Buttons.css';
7 |
8 | const Buttons = ({ buttons }) => (
9 |
10 | {_.map(buttons, (button, key) => (
11 |
18 | ))}
19 |
20 | );
21 |
22 | Buttons.proptypes = {
23 | buttons: proptypes.array.isRequired,
24 | };
25 |
26 | export default Buttons;
27 |
--------------------------------------------------------------------------------
/backoffice/src/pages/common/styles/Buttons.css:
--------------------------------------------------------------------------------
1 | .Buttons-container {
2 | display: flex;
3 | flex-direction: row;
4 | padding: 20px;
5 | height: 80px;
6 | align-items: center;
7 | background-color: #1d2027;
8 | }
--------------------------------------------------------------------------------
/backoffice/src/pages/common/styles/button.css:
--------------------------------------------------------------------------------
1 | .Button-container {
2 | margin: 0 15px 0 0;
3 | }
4 |
5 | .Button-link {
6 | color: rgba(255, 255, 255, 0.5);
7 | border-radius: 2px;
8 | cursor: pointer;
9 | padding: 7px;
10 | border-right: 1px solid #282c36;
11 | }
12 |
13 | .Button-link:focus {
14 | outline: none;
15 | }
16 |
17 | .Button-link:hover {
18 | background-color: rgba(23, 42, 58, 0.3);
19 | color: #f9f9f9;
20 | }
21 |
22 | .Button-link-label {
23 | margin: 0 5px 0 5px;
24 | font-size: 12px;
25 | font-weight: 400;
26 | font-family: 'Roboto', sans-serif;
27 | }
28 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/components/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/main.css';
3 |
4 | const Main = props => (
5 |
8 | );
9 |
10 | export default Main;
11 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/components/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/root.css';
3 |
4 | const Root = props => ;
5 |
6 | export default Root;
7 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/components/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TiBackspaceOutline } from 'react-icons/lib/ti';
3 |
4 | import '../styles/sidebar.css';
5 | import { FormattedMessage } from 'react-intl';
6 |
7 | const Sidebar = props => (
8 |
9 |
10 | Your brand
11 |
12 |
{props.children}
13 |
props.onUserDisconnected()} className="Sidebar-user">
14 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 |
26 | export default Sidebar;
27 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/components/SidebarItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/sidebarItem.css';
3 |
4 | const SidebarItem = props => ;
5 |
6 | export default SidebarItem;
7 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/styles/main.css:
--------------------------------------------------------------------------------
1 | .main {
2 | flex: 1;
3 | overflow: auto;
4 | background-color: #f9f9f9;
5 | }
6 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/styles/root.css:
--------------------------------------------------------------------------------
1 | .root {
2 | display: flex;
3 | height: 100vh;
4 | font-family: 'Roboto', sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/styles/sidebar.css:
--------------------------------------------------------------------------------
1 | .Sidebar-container {
2 | display: flex;
3 | flex-direction: column;
4 | width: 200px;
5 | overflow: auto;
6 | background: #1d2027;
7 | }
8 |
9 | .Sidebar-header {
10 | height: 80px;
11 | background-color: #1d2027;
12 | display: flex;
13 | justify-content: center;
14 | align-items: center;
15 | border-bottom: 1px solid #282c36;
16 | border-right: 1px solid #282c36;
17 | }
18 |
19 | .Sidebar-user {
20 | display: flex;
21 | flex: 0.15;
22 | border-bottom: 1px solid #282c36;
23 | padding-left: 15px;
24 | align-items: center;
25 | cursor: pointer;
26 | }
27 |
28 | .Sidebar-thumbnail {
29 | color: rgba(255,255, 255, 0.6);
30 | margin: 10px;
31 | }
32 |
33 | .Sidebar-logout {
34 | color: #cc6155;
35 | font-size: 13px;
36 | margin-top: 4px;
37 | }
38 |
39 | .Sidebar-nav {
40 | padding: 15px 0px 15px 0px;
41 | border-bottom: 1px solid #282c36;
42 | }
43 |
44 | .Sidebar-title {
45 | color: #cc6155;
46 | font-weight: 100;
47 | font-size: 26px;
48 | font-family: "Lobster", cursive;
49 | margin-left: 10px;
50 | user-select: none;
51 | }
52 |
53 | .Sidebar-icon {
54 | color: #f9f9f9;
55 | }
56 |
57 | .Sidebar-status {
58 | display: flex;
59 | flex: 1;
60 | flex-direction: column;
61 | align-items: center;
62 | justify-content: flex-end;
63 | margin: 20px;
64 | }
65 |
66 | .Sidebar-status-img {
67 | height: 35px;
68 | }
69 |
70 | .Sidebar-status-label {
71 | font-size: 12px;
72 | color: rgba(255, 255, 255, 0.2);
73 | margin-bottom: 30px;
74 | margin-top: 10px;
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/backoffice/src/pages/layout/styles/sidebarItem.css:
--------------------------------------------------------------------------------
1 | .SidebarItem-item {
2 | display: flex;
3 | white-space: nowrap;
4 | flex-direction: column;
5 | text-overflow: ellipsis;
6 | overflow: hidden;
7 | }
8 |
--------------------------------------------------------------------------------
/backoffice/src/pages/login/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { AuthenticateUser } from '../../../graphql/mutations/index';
4 | import { graphql } from 'react-apollo';
5 | import { GC_AUTH_TOKEN } from '../../../constants/index';
6 |
7 | import '../styles/Login.css';
8 |
9 | class Login extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | email: 'toto@toto.fr',
15 | password: 'toto',
16 | error: '',
17 | };
18 | }
19 |
20 | render() {
21 | return (
22 |
23 |
24 |
25 |
26 | this.setState({ email: e.target.value })}
30 | type="text"
31 | />
32 |
33 |
34 |
35 | this.setState({ password: e.target.value })}
39 | type="password"
40 | />
41 |
42 |
46 |
47 |
48 | { this.state.error && (
49 |
{this.state.error}
50 | ) }
51 |
52 | );
53 | }
54 |
55 | _confirm = async () => {
56 | const { email, password } = this.state;
57 | const result = await this.props.authenticateUser({
58 | variables: {
59 | email,
60 | password,
61 | },
62 | });
63 | const token = result.data.login.token;
64 | const role = result.data.login.user.role;
65 |
66 | if (role !== 'ADMIN') {
67 | return this.setState({ error: 'Only admins can login here.' });
68 | }
69 |
70 | if (token) {
71 | this._saveUserData(token);
72 | this.props.onUserAuthenticated();
73 | }
74 | };
75 |
76 | _saveUserData = token => {
77 | localStorage.setItem(GC_AUTH_TOKEN, token);
78 | };
79 | }
80 |
81 | export default graphql(AuthenticateUser, { name: 'authenticateUser' })(Login);
82 |
--------------------------------------------------------------------------------
/backoffice/src/pages/login/styles/Login.css:
--------------------------------------------------------------------------------
1 | .Login-container {
2 | display: flex;
3 | flex: 1;
4 | flex-direction: column;
5 | align-items: center;
6 | padding-top: 30vh;
7 | height: 100vh;
8 | background-color: #1d2027;
9 | }
10 |
11 | .Login-title {
12 | color: #cc6155;
13 | font-weight: 100;
14 | font-size: 100px;
15 | font-family: "Lobster", cursive;
16 | margin-bottom: 40px;
17 | }
18 |
19 | .Login-input {
20 | margin-bottom: 20px;
21 | width: 250px;
22 | height: 32px;
23 | border-radius: 5px;
24 | outline: none !important;
25 | padding: 5px;
26 | text-align: center;
27 | border: none;
28 | font-family: "Roboto", sans-serif;
29 | font-size: 14px;
30 | }
31 |
32 | .Login-button {
33 | color: #F9F9F9;
34 | margin-top: 15px;
35 | width: 250px;
36 | background-color: #CC6155;
37 | padding: 15px;
38 | font-size: 13px;
39 | border-radius: 6px;
40 | text-align: center;
41 | }
42 |
43 | .Login-label-container {
44 | display: flex;
45 | flex-direction: column;
46 | }
47 |
48 | .Login-label {
49 | color: #F9F9F9;
50 | font-size: 12px;
51 | margin-bottom: 5px;
52 | margin-left: 3px;
53 | }
54 |
55 | .Login-error {
56 | color: #F9F9F9;
57 | margin-top: 20px;
58 | }
59 |
--------------------------------------------------------------------------------
/backoffice/src/pages/motd/MOTD.css:
--------------------------------------------------------------------------------
1 | .MOTD-container {
2 | padding: 20px;
3 | }
4 |
5 | .MOTD-semi-label {
6 | display: block;
7 | font-size: 14px;
8 | font-weight: 600;
9 | line-height: 30px;
10 | }
11 |
12 | .MOTD-input {
13 | display: block;
14 | width: 50%;
15 | height: 200px;
16 | border: 1px solid rgba(0, 0, 0, 0.2);
17 | border-radius: 4px;
18 | padding-left: 10px;
19 | }
20 |
--------------------------------------------------------------------------------
/backoffice/src/pages/motd/MOTD.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { graphql, compose } from 'react-apollo';
4 |
5 | import { MotdQuery } from '../../graphql/queries'
6 | import {
7 | UpdateMotdMutation,
8 | UpdateMotdMutationOptions
9 | } from '../../graphql/mutations';
10 |
11 | import Button from '../common/components/Button';
12 | import { MdSave } from 'react-icons/lib/md/index';
13 |
14 | import './MOTD.css';
15 |
16 | class MOTD extends React.PureComponent {
17 | constructor(props) {
18 | super(props);
19 |
20 | this.state = {
21 | MOTD: ''
22 | };
23 |
24 | this.updateMOTD = this.updateMOTD.bind(this);
25 | }
26 |
27 | componentWillReceiveProps({ data }) {
28 | if (!data.loading) {
29 | this.setState({
30 | MOTD: data.me.selectedShop.MOTD || ''
31 | });
32 | }
33 | }
34 |
35 | updateMOTD() {
36 | this.props.updateMOTD({ MOTD: this.state.MOTD });
37 | }
38 |
39 | render() {
40 | return (
41 |
42 |
51 | }
55 | label={}
56 | />
57 |
58 | );
59 | }
60 | }
61 |
62 | export default compose(
63 | graphql(MotdQuery),
64 | graphql(UpdateMotdMutation, UpdateMotdMutationOptions),
65 | )(MOTD)
66 |
--------------------------------------------------------------------------------
/backoffice/src/pages/new-products/ListNewProducts.css:
--------------------------------------------------------------------------------
1 | .ListNewProducts-container {
2 | padding: 30px 20%;
3 | }
4 |
5 | .ListNewProducts-listItem {
6 | display: flex;
7 | align-items: center;
8 | width: 100%;
9 | padding: 20px;
10 | background-color: #fff;
11 | border-bottom: 1px solid #efefef;
12 | box-sizing: border-box;
13 | user-select: none;
14 | color: #333;
15 | font-weight: 400;
16 | }
17 |
18 | .ListNewProducts-select {
19 | margin-bottom: 10px;
20 | flex: 1;
21 | }
22 |
--------------------------------------------------------------------------------
/backoffice/src/pages/options/styles/CreateOption.css:
--------------------------------------------------------------------------------
1 | .CreateOption-input {
2 | width: 96%;
3 | height: 34px;
4 | border: 1px solid rgba(0, 0, 0, 0.2);
5 | border-radius: 4px;
6 | padding-left: 10px;
7 | margin-bottom: 10px;
8 | }
9 |
10 | .CreateOption-select {
11 | margin-bottom: 10px;
12 | }
13 |
14 | .CreateOption-label {
15 | font-size: 14px;
16 | font-weight: 600;
17 | line-height: 30px;
18 | width: 96%;
19 | }
20 |
21 | .CreateOption-container {
22 | display: flex;
23 | flex: 1;
24 | justify-content: center;
25 | flex-direction: column;
26 | align-items: center;
27 | margin: 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/backoffice/src/pages/options/styles/ListOption.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/backoffice/src/pages/orders/styles/Listorder.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/backoffice/src/pages/orders/styles/Listorder.css
--------------------------------------------------------------------------------
/backoffice/src/pages/products/components/ImageUpload.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import proptypes from 'prop-types';
3 | import { TiUpload } from 'react-icons/lib/ti';
4 |
5 | import '../styles/imageupload.css';
6 |
7 | export default class ImageUpload extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | imagePreviewUrl: props.imagePreviewUrl,
13 | };
14 | }
15 |
16 | componentWillReceiveProps({ imagePreviewUrl }) {
17 | this.setState({ imagePreviewUrl });
18 | }
19 |
20 | _handleImageChange(e) {
21 | e.preventDefault();
22 |
23 | if (!e.target.files.length && !this.state.imagePreviewUrl) {
24 | this.setState({ imagePreviewUrl: '' });
25 | return;
26 | }
27 |
28 | if (!e.target.files.length) {
29 | return;
30 | }
31 |
32 | const reader = new FileReader();
33 | const file = e.target.files[0];
34 |
35 | reader.onloadend = () => {
36 | this.props.onImageSelected({
37 | file: file,
38 | imagePreviewUrl: reader.result,
39 | });
40 | this.setState({ imagePreviewUrl: reader.result });
41 | };
42 |
43 | reader.readAsDataURL(file);
44 | }
45 |
46 | render() {
47 | const { imagePreviewUrl } = this.state;
48 | const imagePreview = (
49 |
50 | );
51 |
52 | return (
53 |
54 |
67 |
68 | );
69 | }
70 | }
71 |
72 | ImageUpload.proptypes = {
73 | onImageSelected: proptypes.func,
74 | imagePreviewUrl: proptypes.string,
75 | };
76 |
77 | ImageUpload.defaultProps = {
78 | imagePreviewUrl: '',
79 | };
80 |
--------------------------------------------------------------------------------
/backoffice/src/pages/products/components/Resume.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import proptypes from 'prop-types';
3 | import { GoPlus, GoDash } from 'react-icons/lib/go';
4 |
5 | import '../styles/resume.css';
6 |
7 | const Resume = props => {
8 | return (
9 |
10 | {props.data.available ? (
11 |
12 |
13 |
{props.data.productName}
14 |
15 | Ce produit est maintenant disponible en{' '}
16 | {props.data.name}
17 |
18 |
19 | ) : (
20 |
24 |
25 |
{props.data.productName}
26 |
27 | Ce produit n'est plus disponible en{' '}
28 | {props.data.name}
29 |
30 |
31 | )}
32 |
33 | );
34 | };
35 |
36 | Resume.proptypes = {
37 | data: proptypes.object.isRequired,
38 | };
39 |
40 | export default Resume;
41 |
--------------------------------------------------------------------------------
/backoffice/src/pages/products/styles/createproduct.css:
--------------------------------------------------------------------------------
1 | .Createproduct-container {
2 | margin: 80px 7%;
3 | width: 80%;
4 | height: 80%;
5 | }
6 |
7 | .Createproduct-row-container {
8 | display: flex;
9 | flex-direction: row;
10 | }
11 |
12 | .Createproduct-column-container {
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | flex: 0.8;
17 | }
18 |
19 | .Createproduct-input {
20 | display: block;
21 | width: 96%;
22 | height: 34px;
23 | border: 1px solid rgba(0, 0, 0, 0.2);
24 | border-radius: 4px;
25 | padding-left: 10px;
26 | }
27 |
28 | .Createproduct-textarea {
29 | width: 96%;
30 | height: 34px;
31 | border: 1px solid rgba(0, 0, 0, 0.2);
32 | border-radius: 4px;
33 | padding-left: 10px;
34 | display: block;
35 | }
36 |
37 | .Createproduct-package-container {
38 | margin-bottom: 5px;
39 | }
40 |
41 | .Createproduct-input-package {
42 | margin-right: 5px;
43 | height: 34px;
44 | border: 1px solid rgba(0, 0, 0, 0.2);
45 | border-radius: 4px;
46 | padding-left: 10px;
47 | }
48 |
49 | .Createproduct-delete-package {
50 | border-radius: 2px;
51 | padding: 5px;
52 | background-color: #cc6155;
53 | cursor: pointer;
54 | color: #f9f9f9;
55 | font-size: 12px;
56 | }
57 |
58 | .Createproduct-create-package {
59 | border-radius: 2px;
60 | padding: 6px;
61 | background-color: #1abc9c;
62 | cursor: pointer;
63 | color: #f9f9f9;
64 | font-size: 12px;
65 | }
66 |
67 | .Createproduct-label {
68 | font-size: 14px;
69 | font-weight: 600;
70 | line-height: 30px;
71 | }
72 |
73 | .Createproduct-semi-label {
74 | display: block;
75 | font-size: 14px;
76 | font-weight: 600;
77 | line-height: 30px;
78 | }
79 |
80 | .Createproduct-checkbox {
81 | margin-left: 8px;
82 | }
83 |
84 | .Createproduct-button {
85 | width: 100%;
86 | height: 40px;
87 | border: 1px solid rgba(0, 0, 0, 0.1);
88 | background-color: #1abc9c;
89 | color: rgba(255, 255, 255, 0.5);
90 | border-radius: 5px;
91 | font-size: 12px;
92 | text-transform: uppercase;
93 | font-weight: bold;
94 | margin-top: 20px;
95 | cursor: pointer;
96 | }
97 |
98 | .Createproduct-button:hover {
99 | color: #f9f9f9;
100 | }
101 |
102 | .Createproduct-switch-container {
103 | display: flex;
104 | flex-direction: row;
105 | width: 100%;
106 | }
107 |
108 | .Createproduct-switch {
109 | cursor: pointer;
110 | border-radius: 5px;
111 | font-size: 11px;
112 | font-weight: bold;
113 | color: #f9f9f9;
114 | padding: 5px 10px 5px 10px;
115 | margin-right: 5px;
116 | }
117 |
118 | #input-file {
119 | display: none;
120 | }
121 |
122 | .Createproduct-vignette-title {
123 | margin-right: 8px;
124 | }
125 |
--------------------------------------------------------------------------------
/backoffice/src/pages/products/styles/imageupload.css:
--------------------------------------------------------------------------------
1 | .Imageupload-label-file {
2 | display: flex;
3 | height: 120px;
4 | width: 120px;
5 | align-items: center;
6 | justify-content: center;
7 | border-radius: 3px;
8 | border: 1px dashed #cccccc;
9 | cursor: pointer;
10 | line-height: 30px;
11 | margin: 15px;
12 | }
13 |
14 | .Imageupload-icon {
15 | color: #cccccc;
16 | }
17 |
18 | .Imageupload-previewText {
19 | font-size: 14px;
20 | font-weight: normal;
21 | line-height: 30px;
22 | font-style: italic;
23 | }
24 |
25 | .Imageupload-container {
26 | margin-top: 15px;
27 | flex: 0.2;
28 | }
29 |
--------------------------------------------------------------------------------
/backoffice/src/pages/products/styles/listproduct.css:
--------------------------------------------------------------------------------
1 | .Listproduct-messages {
2 | display: flex;
3 | flex-direction: column;
4 | height: 440px;
5 | margin: 0 0 10px 0;
6 | overflow: scroll;
7 | }
8 |
9 | .Listproduct-modal {
10 | padding: 0 5px;
11 | }
12 |
13 | .Listproduct-delete {
14 | border-radius: 2px;
15 | padding: 5px;
16 | background-color: #cc6155;
17 | cursor: pointer;
18 | color: #f9f9f9;
19 | line-height: 60px;
20 | font-size: 12px;
21 | }
22 |
23 | .Listproduct-delete:hover {
24 | background-color: #d3746a;
25 | opacity: 0.9;
26 | }
27 |
28 | .Listproduct-edit {
29 | border-radius: 2px;
30 | padding: 5px;
31 | background-color: #1abc9c;
32 | cursor: pointer;
33 | color: #f9f9f9;
34 | line-height: 60px;
35 | font-size: 12px;
36 | }
37 |
38 | .Listproduct-edit:hover {
39 | background-color: #1cd7b5;
40 | opacity: 0.9;
41 | }
42 |
43 | .Listproduct-header-modal {
44 | display: flex;
45 | align-items: center;
46 | justify-content: center;
47 | background-color: #1d2027;
48 | height: 40px;
49 | }
50 |
51 | .Listproduct-header-label {
52 | color: #f9f9f9;
53 | font-size: 12px;
54 | }
55 |
--------------------------------------------------------------------------------
/backoffice/src/pages/products/styles/resume.css:
--------------------------------------------------------------------------------
1 | .Resume-container {
2 | display: flex;
3 | flex-direction: row;
4 | align-items: center;
5 | margin: 10px 10px 0px 10px;
6 | border-radius: 3px;
7 | background-color: rgba(26, 188, 156, 0.1);
8 | }
9 |
10 | .Resume-name {
11 | margin-left: 10px;
12 | font-size: 13px;
13 | font-weight: bold;
14 | }
15 |
16 | .Resume-message {
17 | margin-left: 15px;
18 | font-size: 13px;
19 | font-weight: 400;
20 | }
21 |
22 | .Resume-icon {
23 | margin-left: 10px;
24 | }
25 |
26 | .Resume-taxon {
27 | font-weight: bold;
28 | }
29 |
--------------------------------------------------------------------------------
/backoffice/src/pages/users/components/ListUser.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { graphql } from 'react-apollo';
3 | import ReactTable from 'react-table';
4 | import { ListAllUsersQuery } from '../../../graphql/queries/index';
5 | import { MdRefresh } from 'react-icons/lib/md';
6 | import Buttons from '../../common/components/Buttons';
7 |
8 | import '../styles/Listuser.css';
9 | import '../../common/styles/Reactable.css';
10 |
11 | class ListUser extends Component {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.state = {
16 | loading: false,
17 | };
18 | this.handleRefresh = this.handleRefresh.bind(this);
19 | }
20 |
21 | async handleRefresh() {
22 | this.setState({ loading: true });
23 | await this.props.data.refetch();
24 | this.setState({ loading: false });
25 | }
26 |
27 | render() {
28 | const columns = [
29 | {
30 | Header: 'ID',
31 | accessor: 'id',
32 | Cell: props => (
33 |
34 | {props.value}
35 |
36 | ),
37 | filterable: true,
38 | },
39 | {
40 | Header: 'Name',
41 | Cell: props => {`${props.original.firstName} ${props.original.lastName.toUpperCase()}`}
,
42 | },
43 | {
44 | Header: 'Email',
45 | accessor: 'email',
46 | Cell: props => {props.value}
,
47 | },
48 | ];
49 | const buttons = [
50 | {
51 | color: 'transparent',
52 | callback: this.handleRefresh,
53 | icon: ,
54 | label: 'Rafraichir les clients',
55 | },
56 | ];
57 | return (
58 |
59 |
60 |
68 |
69 | );
70 | }
71 | }
72 |
73 | export default graphql(ListAllUsersQuery)(ListUser);
74 |
--------------------------------------------------------------------------------
/backoffice/src/pages/users/styles/Listuser.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/backoffice/src/pages/users/styles/Listuser.css
--------------------------------------------------------------------------------
/mobile/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
4 |
--------------------------------------------------------------------------------
/mobile/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/mobile/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 |
16 | ; Ignore polyfills
17 | .*/Libraries/polyfills/.*
18 |
19 | ; Ignore metro
20 | .*/node_modules/metro/.*
21 |
22 | [include]
23 |
24 | [libs]
25 | node_modules/react-native/Libraries/react-native/react-native-interface.js
26 | node_modules/react-native/flow/
27 | node_modules/react-native/flow-github/
28 |
29 | [options]
30 | emoji=true
31 |
32 | module.system=haste
33 |
34 | munge_underscores=true
35 |
36 | 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'
37 |
38 | module.file_ext=.js
39 | module.file_ext=.jsx
40 | module.file_ext=.json
41 | module.file_ext=.native.js
42 |
43 | suppress_type=$FlowIssue
44 | suppress_type=$FlowFixMe
45 | suppress_type=$FlowFixMeProps
46 | suppress_type=$FlowFixMeState
47 |
48 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
49 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
50 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
51 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
52 |
53 | [version]
54 | ^0.67.0
55 |
--------------------------------------------------------------------------------
/mobile/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 | ios/Pods
25 |
26 | # Android/IntelliJ
27 | #
28 | .idea
29 | .vscode/
30 | .gradle
31 | local.properties
32 | *.iml
33 |
34 | # node.js
35 | #
36 | node_modules/
37 | npm-debug.log
38 | yarn-error.log
39 |
40 | # BUCK
41 | buck-out/
42 | \.buckd/
43 | *.keystore
44 |
45 | # fastlane
46 | #
47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
48 | # screenshots whenever they are needed.
49 | # For more information about the recommended setup visit:
50 | # https://docs.fastlane.tools/best-practices/source-control/
51 |
52 | */fastlane/report.xml
53 | */fastlane/Preview.html
54 | */fastlane/screenshots
55 |
56 | # Bundle artifact
57 | *.jsbundle
58 |
--------------------------------------------------------------------------------
/mobile/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": true,
4 | "trailingComma": "all",
5 | "arrowParens": "avoid",
6 | "jsxBracketSameLine": false,
7 | "tabWidth": 2,
8 | "printWidth": 90,
9 | "bracketSpacing": true
10 | }
11 |
--------------------------------------------------------------------------------
/mobile/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/mobile/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { ApolloProvider } from 'react-apollo';
3 |
4 | import { setupApolloClient } from './src/graphql/setupApollo';
5 |
6 | import RootSwitch from './src/routes';
7 |
8 | const apolloClient = setupApolloClient();
9 |
10 | export default class App extends Component {
11 | render() {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/mobile/__tests__/App.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import App from '../App';
4 |
5 | // Note: test renderer must be required after react-native.
6 | import renderer from 'react-test-renderer';
7 |
8 | it('renders correctly', () => {
9 | const tree = renderer.create(
10 |
11 | );
12 | });
13 |
--------------------------------------------------------------------------------
/mobile/android/app/BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | lib_deps = []
12 |
13 | for jarfile in glob(['libs/*.jar']):
14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
15 | lib_deps.append(':' + name)
16 | prebuilt_jar(
17 | name = name,
18 | binary_jar = jarfile,
19 | )
20 |
21 | for aarfile in glob(['libs/*.aar']):
22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
23 | lib_deps.append(':' + name)
24 | android_prebuilt_aar(
25 | name = name,
26 | aar = aarfile,
27 | )
28 |
29 | android_library(
30 | name = "all-libs",
31 | exported_deps = lib_deps,
32 | )
33 |
34 | android_library(
35 | name = "app-code",
36 | srcs = glob([
37 | "src/main/java/**/*.java",
38 | ]),
39 | deps = [
40 | ":all-libs",
41 | ":build_config",
42 | ":res",
43 | ],
44 | )
45 |
46 | android_build_config(
47 | name = "build_config",
48 | package = "com.aromaclop",
49 | )
50 |
51 | android_resource(
52 | name = "res",
53 | package = "com.aromaclop",
54 | res = "src/main/res",
55 | )
56 |
57 | android_binary(
58 | name = "app",
59 | keystore = "//android/keystores:debug",
60 | manifest = "src/main/AndroidManifest.xml",
61 | package_type = "debug",
62 | deps = [
63 | ":app-code",
64 | ],
65 | )
66 |
--------------------------------------------------------------------------------
/mobile/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/Entypo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/Entypo.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/EvilIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/EvilIcons.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/Feather.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/Feather.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/FontAwesome.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/FontAwesome.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/Foundation.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/Foundation.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/Ionicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/Ionicons.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/MaterialIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/MaterialIcons.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/Octicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/Octicons.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/SimpleLineIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/SimpleLineIcons.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/assets/fonts/Zocial.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/assets/fonts/Zocial.ttf
--------------------------------------------------------------------------------
/mobile/android/app/src/main/java/com/aromaclop/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.aromaclop;
2 |
3 | import com.facebook.react.ReactActivity;
4 |
5 | public class MainActivity extends ReactActivity {
6 |
7 | /**
8 | * Returns the name of the main component registered from JavaScript.
9 | * This is used to schedule rendering of the component.
10 | */
11 | @Override
12 | protected String getMainComponentName() {
13 | return "Aromaclop";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/mobile/android/app/src/main/java/com/aromaclop/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.aromaclop;
2 |
3 | import android.app.Application;
4 |
5 | import com.facebook.react.ReactApplication;
6 | import com.geektime.rnonesignalandroid.ReactNativeOneSignalPackage;
7 | import com.gettipsi.stripe.StripeReactPackage;
8 | import com.beefe.picker.PickerViewPackage;
9 | import com.BV.LinearGradient.LinearGradientPackage;
10 | import com.oblador.vectoricons.VectorIconsPackage;
11 | import com.facebook.react.ReactNativeHost;
12 | import com.facebook.react.ReactPackage;
13 | import com.facebook.react.shell.MainReactPackage;
14 | import com.facebook.soloader.SoLoader;
15 |
16 | import java.util.Arrays;
17 | import java.util.List;
18 |
19 | public class MainApplication extends Application implements ReactApplication {
20 |
21 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
22 | @Override
23 | public boolean getUseDeveloperSupport() {
24 | return BuildConfig.DEBUG;
25 | }
26 |
27 | @Override
28 | protected List getPackages() {
29 | return Arrays.asList(
30 | new MainReactPackage(),
31 | new ReactNativeOneSignalPackage(),
32 | new StripeReactPackage(),
33 | new PickerViewPackage(),
34 | new LinearGradientPackage(),
35 | new VectorIconsPackage()
36 | );
37 | }
38 |
39 | @Override
40 | protected String getJSMainModuleName() {
41 | return "index";
42 | }
43 | };
44 |
45 | @Override
46 | public ReactNativeHost getReactNativeHost() {
47 | return mReactNativeHost;
48 | }
49 |
50 | @Override
51 | public void onCreate() {
52 | super.onCreate();
53 | SoLoader.init(this, /* native exopackage */ false);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Aromaclop
3 |
4 |
--------------------------------------------------------------------------------
/mobile/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobile/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.google.com/'
8 | name 'Google'
9 | }
10 | google()
11 | maven { url 'https://plugins.gradle.org/m2/' } // Gradle Plugin Portal
12 |
13 | }
14 | dependencies {
15 | classpath 'com.android.tools.build:gradle:3.1.3'
16 | classpath 'gradle.plugin.com.onesignal:onesignal-gradle-plugin:0.11.0'
17 |
18 | // NOTE: Do not place your application dependencies here; they belong
19 | // in the individual module build.gradle files
20 | }
21 | }
22 |
23 | allprojects {
24 | repositories {
25 | // Add jitpack repository (added by tipsi-stripe)
26 | maven { url "https://jitpack.io" }
27 | mavenLocal()
28 | jcenter()
29 | maven {
30 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
31 | url "$rootDir/../node_modules/react-native/android"
32 | }
33 | maven {
34 | url 'https://maven.google.com/'
35 | name 'Google'
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/mobile/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.useDeprecatedNdk=true
21 | android.enableD8=true
22 |
--------------------------------------------------------------------------------
/mobile/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/mobile/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jul 21 19:37:27 CEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/mobile/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/mobile/android/keystores/BUCK:
--------------------------------------------------------------------------------
1 | keystore(
2 | name = "debug",
3 | properties = "debug.keystore.properties",
4 | store = "debug.keystore",
5 | visibility = [
6 | "PUBLIC",
7 | ],
8 | )
9 |
--------------------------------------------------------------------------------
/mobile/android/keystores/debug.keystore.properties:
--------------------------------------------------------------------------------
1 | key.store=debug.keystore
2 | key.alias=androiddebugkey
3 | key.store.password=android
4 | key.alias.password=android
5 |
--------------------------------------------------------------------------------
/mobile/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'Aromaclop'
2 | include ':react-native-onesignal'
3 | project(':react-native-onesignal').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-onesignal/android')
4 | include ':tipsi-stripe'
5 | project(':tipsi-stripe').projectDir = new File(rootProject.projectDir, '../node_modules/tipsi-stripe/android')
6 | include ':react-native-picker'
7 | project(':react-native-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-picker/android')
8 | include ':react-native-linear-gradient'
9 | project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
10 | include ':react-native-vector-icons'
11 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
12 |
13 | include ':app'
14 |
--------------------------------------------------------------------------------
/mobile/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Aromaclop",
3 | "displayName": "Aromaclop"
4 | }
--------------------------------------------------------------------------------
/mobile/graphql.config.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "README_schema" : "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE",
4 | "schema": {
5 |
6 | "README_file" : "Remove 'file' to use request url below. A relative or absolute path to the JSON from a schema introspection query, e.g. '{ data: ... }' or a .graphql/.graphqls file describing the schema using GraphQL Schema Language. Changes to the file are watched.",
7 |
8 | "README_request" : "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).",
9 | "request": {
10 | "url" : "http://localhost:4000/",
11 | "method" : "POST",
12 | "README_postIntrospectionQuery" : "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET",
13 | "postIntrospectionQuery" : true,
14 | "README_options" : "See the 'Options' section at https://github.com/then/then-request",
15 | "options" : {
16 | "headers": {
17 | "user-agent" : "JS GraphQL"
18 | }
19 | }
20 | }
21 |
22 | },
23 |
24 | "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE",
25 | "endpoints" : [
26 | {
27 | "name": "Default (http://localhost:8080/graphql)",
28 | "url": "http://localhost:4000/",
29 | "options" : {
30 | "headers": {
31 | "user-agent" : "JS GraphQL"
32 | }
33 | }
34 | }
35 | ]
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/mobile/index.js:
--------------------------------------------------------------------------------
1 | import { AppRegistry, YellowBox } from 'react-native';
2 | import App from './App';
3 | import StoryBookUI from './storybook';
4 |
5 | YellowBox.ignoreWarnings([
6 | 'Warning: isMounted(...) is deprecated',
7 | 'Remote debugger',
8 | 'Module RCTImageLoader',
9 | ]);
10 |
11 | //Return either App or StoryBookUI
12 | AppRegistry.registerComponent('Aromaclop', () => App);
13 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop-tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | NSLocationWhenInUseUsageDescription
40 |
41 | NSAppTransportSecurity
42 |
43 |
44 | NSExceptionDomains
45 |
46 | localhost
47 |
48 | NSExceptionAllowsInsecureHTTPLoads
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop-tvOSTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | @interface AppDelegate : UIResponder
11 |
12 | @property (nonatomic, strong) UIWindow *window;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import "AppDelegate.h"
9 |
10 | #import
11 | #import
12 | #import
13 | #import
14 |
15 | @implementation AppDelegate
16 |
17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
18 | {
19 | NSURL *jsCodeLocation;
20 |
21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
22 |
23 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
24 | moduleName:@"Aromaclop"
25 | initialProperties:nil
26 | launchOptions:launchOptions];
27 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
28 |
29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
30 | UIViewController *rootViewController = [UIViewController new];
31 | rootViewController.view = rootView;
32 | self.window.rootViewController = rootViewController;
33 | [self.window makeKeyAndVisible];
34 | return YES;
35 | }
36 |
37 | // This method handles opening native URLs (e.g., "yourexampleapp://")
38 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
39 | sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
40 | {
41 | BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url];
42 | if (stripeHandled) {
43 | return YES;
44 | }
45 |
46 | return [RCTLinkingManager application:application openURL:url
47 | sourceApplication:sourceApplication annotation:annotation];
48 | }
49 |
50 | @end
51 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop/Aromaclop.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.security.application-groups
8 |
9 | group.com.reactnative.aromaclop
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Aromaclop
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleTypeRole
27 | Editor
28 | CFBundleURLName
29 | aromaclop
30 | CFBundleURLSchemes
31 |
32 | aromaclop
33 |
34 |
35 |
36 | CFBundleVersion
37 | 1
38 | LSRequiresIPhoneOS
39 |
40 | NSAppTransportSecurity
41 |
42 | NSExceptionDomains
43 |
44 | localhost
45 |
46 | NSExceptionAllowsInsecureHTTPLoads
47 |
48 |
49 |
50 |
51 | NSLocationWhenInUseUsageDescription
52 |
53 | UIAppFonts
54 |
55 | Entypo.ttf
56 | EvilIcons.ttf
57 | Feather.ttf
58 | FontAwesome.ttf
59 | Foundation.ttf
60 | Ionicons.ttf
61 | MaterialCommunityIcons.ttf
62 | MaterialIcons.ttf
63 | Octicons.ttf
64 | SimpleLineIcons.ttf
65 | Zocial.ttf
66 |
67 | UIBackgroundModes
68 |
69 | remote-notification
70 |
71 | UILaunchStoryboardName
72 | LaunchScreen
73 | UIRequiredDeviceCapabilities
74 |
75 | armv7
76 |
77 | UISupportedInterfaceOrientations
78 |
79 | UIInterfaceOrientationPortrait
80 | UIInterfaceOrientationLandscapeLeft
81 | UIInterfaceOrientationLandscapeRight
82 |
83 | UIViewControllerBasedStatusBarAppearance
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/mobile/ios/Aromaclop/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/mobile/ios/AromaclopTests/AromaclopTests.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | #import
12 | #import
13 |
14 | #define TIMEOUT_SECONDS 600
15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
16 |
17 | @interface AromaclopTests : XCTestCase
18 |
19 | @end
20 |
21 | @implementation AromaclopTests
22 |
23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
24 | {
25 | if (test(view)) {
26 | return YES;
27 | }
28 | for (UIView *subview in [view subviews]) {
29 | if ([self findSubviewInView:subview matching:test]) {
30 | return YES;
31 | }
32 | }
33 | return NO;
34 | }
35 |
36 | - (void)testRendersWelcomeScreen
37 | {
38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
40 | BOOL foundElement = NO;
41 |
42 | __block NSString *redboxError = nil;
43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
44 | if (level >= RCTLogLevelError) {
45 | redboxError = message;
46 | }
47 | });
48 |
49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
52 |
53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
55 | return YES;
56 | }
57 | return NO;
58 | }];
59 | }
60 |
61 | RCTSetLogFunction(RCTDefaultLogFunction);
62 |
63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
65 | }
66 |
67 |
68 | @end
69 |
--------------------------------------------------------------------------------
/mobile/ios/AromaclopTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/mobile/ios/OneSignalNotificationServiceExtension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | OneSignalNotificationServiceExtension
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | NSExtension
24 |
25 | NSExtensionPointIdentifier
26 | com.apple.usernotifications.service
27 | NSExtensionPrincipalClass
28 | NotificationService
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/mobile/ios/OneSignalNotificationServiceExtension/NotificationService.h:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationService.h
3 | // OneSignalNotificationServiceExtension
4 | //
5 | // Created by weakky on 24/05/2018.
6 | // Copyright © 2018 Facebook. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface NotificationService : UNNotificationServiceExtension
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/mobile/ios/OneSignalNotificationServiceExtension/NotificationService.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "NotificationService.h"
4 |
5 | @interface NotificationService ()
6 |
7 | @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
8 | @property (nonatomic, strong) UNNotificationRequest *receivedRequest;
9 | @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
10 |
11 | @end
12 |
13 | @implementation NotificationService
14 |
15 | - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
16 | self.receivedRequest = request;
17 | self.contentHandler = contentHandler;
18 | self.bestAttemptContent = [request.content mutableCopy];
19 |
20 | [RCTOneSignalExtensionService didReceiveNotificationRequest:self.receivedRequest withContent:self.bestAttemptContent];
21 |
22 | // DEBUGGING: Uncomment the 2 lines below and comment out the one above to ensure this extension is excuting
23 | // Note, this extension only runs when mutable-content is set
24 | // Setting an attachment or action buttons automatically adds this
25 | //NSLog(@"Running NotificationServiceExtension");
26 | //self.bestAttemptContent.body = [@"[Modified] " stringByAppendingString:self.bestAttemptContent.body];
27 |
28 | self.contentHandler(self.bestAttemptContent);
29 | }
30 |
31 | - (void)serviceExtensionTimeWillExpire {
32 | // Called just before the extension will be terminated by the system.
33 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
34 |
35 | [RCTOneSignalExtensionService serviceExtensionTimeWillExpireRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];
36 |
37 | self.contentHandler(self.bestAttemptContent);
38 | }
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/mobile/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'Aromaclop' do
5 | pod 'Stripe', '~> 11.2.0'
6 | # inherit! :search_paths
7 | # pod 'react-native-onesignal', :path => '../node_modules/react-native-onesignal'
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/mobile/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Stripe (11.2.0)
3 |
4 | DEPENDENCIES:
5 | - Stripe (~> 11.2.0)
6 |
7 | SPEC REPOS:
8 | https://github.com/CocoaPods/Specs.git:
9 | - Stripe
10 |
11 | SPEC CHECKSUMS:
12 | Stripe: 898f83a95d72180c6eb4acd65b2ae6158fc6a8bd
13 |
14 | PODFILE CHECKSUM: eaba2a70be25d9fda981f43345f8e92b5b6df917
15 |
16 | COCOAPODS: 1.5.0
17 |
--------------------------------------------------------------------------------
/mobile/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Aromaclop",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node node_modules/react-native/local-cli/cli.js start",
7 | "run-ios": "node node_modules/react-native/local-cli/cli.js run-ios",
8 | "test": "jest",
9 | "storybook": "storybook start -p 7007"
10 | },
11 | "dependencies": {
12 | "@storybook/addon-knobs": "^3.2.13",
13 | "apollo-client-preset": "^1.0.5",
14 | "apollo-link-context": "^1.0.3",
15 | "apollo-link-ws": "^1.0.4",
16 | "apollo-utilities": "^1.0.3",
17 | "fecha": "^2.3.3",
18 | "graphql": "^0.11.7",
19 | "graphql-tag": "^2.6.0",
20 | "lodash": "latest",
21 | "react": "16.3.1",
22 | "react-apollo": "^2.0.4",
23 | "react-native": "0.55.4",
24 | "react-native-credit-card-input": "^0.4.1",
25 | "react-native-keyboard-aware-scroll-view": "^0.4.1",
26 | "react-native-linear-gradient": "^2.3.0",
27 | "react-native-onesignal": "^3.2.4",
28 | "react-native-picker": "^4.3.4",
29 | "react-native-settings-list": "^1.8.0",
30 | "react-native-tab-view": "^0.0.73",
31 | "react-native-vector-icons": "^4.4.2",
32 | "react-navigation": "^2.0.1",
33 | "react-navigation-props-mapper": "^0.1.2",
34 | "styled-components": "^3.2.6",
35 | "subscriptions-transport-ws": "^0.9.4",
36 | "tipsi-stripe": "^5.2.1"
37 | },
38 | "devDependencies": {
39 | "@storybook/addon-actions": "^3.2.13",
40 | "@storybook/addon-links": "^3.2.13",
41 | "@storybook/react-native": "^3.2.13",
42 | "babel-jest": "21.2.0",
43 | "babel-preset-react-native": "4.0.0",
44 | "jest": "21.2.1",
45 | "prop-types": "^15.6.0",
46 | "react-dom": "16.0.0-beta.5",
47 | "react-test-renderer": "16.0.0-beta.5"
48 | },
49 | "jest": {
50 | "preset": "react-native"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/mobile/src/assets/fonts/index.js:
--------------------------------------------------------------------------------
1 | import { Platform } from 'react-native';
2 |
3 | export default Platform.select({
4 | ios: 'Avenir Next',
5 | android: 'Roboto',
6 | });
7 |
--------------------------------------------------------------------------------
/mobile/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/src/assets/logo.png
--------------------------------------------------------------------------------
/mobile/src/assets/no_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/src/assets/no_data.png
--------------------------------------------------------------------------------
/mobile/src/components/banner/Banner.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Text, TouchableWithoutFeedback, StyleSheet, View } from 'react-native';
4 |
5 | import styles from './Banner.styles';
6 |
7 | const propTypes = {
8 | shopId: PropTypes.string,
9 | shop: PropTypes.string,
10 | address: PropTypes.string,
11 | postal: PropTypes.string,
12 | city: PropTypes.string,
13 | tel: PropTypes.string,
14 | opening: PropTypes.string,
15 | selected: PropTypes.bool,
16 | onBannerSelected: PropTypes.func,
17 | inverted: PropTypes.bool,
18 | };
19 |
20 | const defaultProps = {
21 | name: 'Aromaclop',
22 | address: '10 rue Jean Jaurès',
23 | zipCode: '54550',
24 | city: 'Pont-Saint-Vincent',
25 | phoneNumber: '06 08 64 77 61',
26 | openingHours: 'Ouvert du lundi au vendredi de 8h à 19h',
27 | selected: false,
28 | onBannerSelected: () => {},
29 | inverted: false,
30 | };
31 |
32 | class Banner extends Component {
33 | constructor(props) {
34 | super(props);
35 |
36 | this.onBannerSelected = this.onBannerSelected.bind(this);
37 | }
38 |
39 | onBannerSelected() {
40 | this.props.onBannerSelected({ shopId: this.props.shopId });
41 | }
42 |
43 | computeBackgroundColor() {
44 | const { selected, inverted } = this.props;
45 | if (selected) {
46 | return inverted ? 'rgba(204, 97, 85, 1)' : 'rgba(255, 255, 255, 0.1)';
47 | }
48 |
49 | return inverted ? 'rgba(204, 97, 85, 0.5)' : 'transparent'
50 | }
51 |
52 | render() {
53 | const { name, address, zipCode, city, phoneNumber, openingHours } = this.props;
54 |
55 | return (
56 |
57 |
66 | {name}
67 | {address}
68 |
69 | {zipCode}
70 | , {city}
71 |
72 | {phoneNumber}
73 | {openingHours}
74 |
75 |
76 | );
77 | }
78 | }
79 |
80 | Banner.propTypes = propTypes;
81 | Banner.defaultProps = defaultProps;
82 |
83 | export default Banner;
84 |
--------------------------------------------------------------------------------
/mobile/src/components/banner/Banner.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react-native';
3 | import { text, boolean } from '@storybook/addon-knobs';
4 |
5 | import Banner from './Banner';
6 |
7 | storiesOf('Banner', module).add('default', () => (
8 |
17 | ));
18 |
--------------------------------------------------------------------------------
/mobile/src/components/banner/Banner.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | import font from '../../assets/fonts/index';
4 | import color from '../../statics/colors/index';
5 |
6 | export default StyleSheet.create({
7 | container: {
8 | justifyContent: 'center',
9 | alignItems: 'center',
10 | padding: 20,
11 | },
12 | text: {
13 | fontFamily: font,
14 | color: color.white,
15 | fontSize: 14,
16 | },
17 | bold: {
18 | fontWeight: 'bold',
19 | },
20 | margin: {
21 | marginTop: 10,
22 | },
23 | title: {
24 | fontSize: 18,
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/mobile/src/components/basket-card/BasketCard.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | backgroundColor: 'rgba(255, 255, 255, 1)',
6 | borderWidth: 1,
7 | borderColor: 'rgba(0, 0, 0, 0.05)',
8 | borderRadius: 3,
9 | },
10 | imageUrl: {
11 | flex: 0.8,
12 | backgroundColor: '#F9F9F9',
13 | },
14 | preview: {
15 | flex: 1,
16 | },
17 | content: {
18 | flex: 0.2,
19 | flexDirection: 'row',
20 | justifyContent: 'space-between',
21 | alignItems: 'center',
22 | borderTopWidth: 1,
23 | borderColor: 'rgba(0, 0, 0, 0.05)',
24 | },
25 | left: {
26 | fontSize: 12,
27 | fontWeight: '600',
28 | paddingLeft: 10,
29 | },
30 | right: {
31 | fontSize: 12,
32 | paddingRight: 10,
33 | },
34 | loadingContent: {
35 | margin: 5,
36 | backgroundColor: '#F4F4F4',
37 | borderRadius: 100,
38 | height: 15,
39 | },
40 | });
41 |
--------------------------------------------------------------------------------
/mobile/src/components/basket-card/sub-total/SubTotal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { StyleSheet, Text, View } from 'react-native';
4 | import { translate } from '../../../i18n';
5 |
6 | const SubTotal = props => (
7 |
20 |
27 | {translate('sub_total')}
28 |
29 |
37 | {parseFloat(props.totalPrice).toFixed(2)} €
38 |
39 |
40 | );
41 |
42 | SubTotal.propTypes = {
43 | totalPrice: PropTypes.number,
44 | };
45 |
46 | export default SubTotal;
47 |
--------------------------------------------------------------------------------
/mobile/src/components/basket-card/taxons-selector/TaxonsSelector.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import { Text, View, Picker } from 'react-native';
4 |
5 | const Selector = props => (
6 | this.setState({ language: itemValue })}
9 | >
10 |
11 |
12 |
13 | );
14 |
15 | class TaxonSelector extends Component {
16 | constructor(props) {
17 | super(props);
18 |
19 | this.state = {
20 | taxon: '',
21 | quantity: '',
22 | };
23 | }
24 |
25 | render() {
26 | return (
27 |
28 | Select
29 |
30 | );
31 | }
32 | }
33 |
34 | TaxonSelector.propTypes = {};
35 | TaxonSelector.defaultProps = {};
36 |
37 | export default TaxonSelector;
38 |
--------------------------------------------------------------------------------
/mobile/src/components/big-red-button/BigRedButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ActivityIndicator, TouchableOpacity, View } from 'react-native';
3 | import PropTypes from 'prop-types';
4 | import Ionicons from 'react-native-vector-icons/Ionicons';
5 |
6 | import Title from '../title/Title';
7 |
8 | import Colors from '../../statics/colors';
9 | import styles from './BigRedButton.styles';
10 |
11 | const BigRedButton = props => (
12 |
21 | {props.loading && }
22 | {!props.loading && (
23 |
29 | {props.label}
30 |
31 | )}
32 | {!props.loading &&
33 | props.icon && (
34 |
35 |
36 |
37 | )}
38 |
39 | );
40 |
41 | BigRedButton.propTypes = {
42 | onPress: PropTypes.func,
43 | label: PropTypes.string,
44 | icon: PropTypes.string,
45 | loading: PropTypes.bool,
46 | disabled: PropTypes.bool,
47 | style: View.propTypes.style,
48 | };
49 |
50 | export default BigRedButton;
51 |
--------------------------------------------------------------------------------
/mobile/src/components/big-red-button/BigRedButton.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import Colors from '../../statics/colors';
3 |
4 | export default StyleSheet.create({
5 | bigRedButton: {
6 | alignSelf: 'center',
7 | backgroundColor: Colors.red,
8 | flexDirection: 'row',
9 | justifyContent: 'center',
10 | alignItems: 'center',
11 | height: 44,
12 | width: '100%',
13 | borderRadius: 6,
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/mobile/src/components/button/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Text, TouchableOpacity, Dimensions, ActivityIndicator } from 'react-native';
4 | import Icon from 'react-native-vector-icons/Ionicons';
5 |
6 | import color from '../../statics/colors/index';
7 | import styles from './Button.styles';
8 |
9 | const propTypes = {
10 | style: PropTypes.object,
11 | onPress: PropTypes.func.isRequired,
12 | label: PropTypes.string.isRequired,
13 | labelColor: PropTypes.string,
14 | icon: PropTypes.string,
15 | iconColor: PropTypes.string,
16 | backgroundColor: PropTypes.string,
17 | height: PropTypes.number,
18 | width: PropTypes.number,
19 | borderColor: PropTypes.string,
20 | fontSize: PropTypes.number,
21 | loading: PropTypes.bool,
22 | disabled: PropTypes.bool,
23 | };
24 |
25 | const defaultProps = {
26 | style: {},
27 | labelColor: color.white,
28 | iconColor: color.white,
29 | backgroundColor: 'transparent',
30 | height: 50,
31 | width: Dimensions.get('window').width - 40,
32 | borderColor: color.white,
33 | fontSize: 14,
34 | disabled: false,
35 | };
36 |
37 | const Button = props => {
38 | const {
39 | onPress,
40 | label,
41 | labelColor,
42 | icon,
43 | iconColor,
44 | backgroundColor,
45 | height,
46 | width,
47 | borderColor,
48 | fontSize,
49 | loading,
50 | disabled,
51 | } = props;
52 | return (
53 | !disabled && onPress()}
55 | style={[
56 | styles.touchable,
57 | {
58 | height: height,
59 | width: width,
60 | backgroundColor: backgroundColor,
61 | borderColor: borderColor,
62 | },
63 | props.style,
64 | ]}
65 | >
66 | {icon && }
67 | {!loading && (
68 |
77 | {label}
78 |
79 | )}
80 | {loading && (
81 |
82 | )}
83 |
84 | );
85 | };
86 |
87 | Button.propTypes = propTypes;
88 | Button.defaultProps = defaultProps;
89 |
90 | export default Button;
91 |
--------------------------------------------------------------------------------
/mobile/src/components/button/Button.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react-native';
3 | import { text, number, color } from '@storybook/addon-knobs';
4 |
5 | import Button from './Button';
6 |
7 | function log() {
8 | console.log('Callback!');
9 | }
10 |
11 | storiesOf('Button', module).add('default', () => (
12 |
24 | ));
25 |
--------------------------------------------------------------------------------
/mobile/src/components/button/Button.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | import font from '../../assets/fonts/index';
4 |
5 | export default StyleSheet.create({
6 | touchable: {
7 | flexDirection: 'row',
8 | justifyContent: 'center',
9 | alignItems: 'center',
10 | borderWidth: 1,
11 | borderRadius: 50,
12 | },
13 | text: {
14 | fontFamily: font,
15 | fontWeight: '600',
16 | paddingLeft: 16,
17 | paddingRight: 16,
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/mobile/src/components/card/Card.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react-native';
3 | import { text, number, boolean, array } from '@storybook/addon-knobs';
4 |
5 | import Card from './Card';
6 |
7 | function test() {
8 | console.log('Callback: OK');
9 | }
10 |
11 | storiesOf('Card', module).add('default', () => (
12 |
20 | ));
21 |
--------------------------------------------------------------------------------
/mobile/src/components/card/Card.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import colors from '../../statics/colors';
3 |
4 | export default StyleSheet.create({
5 | productPrice: {
6 | fontFamily: 'Avenir Next',
7 | fontWeight: '600',
8 | fontSize: 12,
9 | color: '#484848',
10 | marginTop: 5,
11 | },
12 | productBrand: {
13 | fontFamily: 'Avenir Next',
14 | fontWeight: '600',
15 | fontSize: 10,
16 | color: 'rgba(72, 72, 72, 0.4)',
17 | },
18 | productImage: { width: 60, height: 60, margin: 5 },
19 | productName: { fontFamily: 'Avenir Next', fontWeight: '300', fontSize: 14 },
20 | container: {
21 | backgroundColor: colors.white,
22 | borderWidth: StyleSheet.hairlineWidth,
23 | borderColor: '#ddd',
24 | borderRadius: 6,
25 | height: 77,
26 | flexDirection: 'row',
27 | alignItems: 'center',
28 | justifyContent: 'space-between',
29 | },
30 | optionValueContent: {
31 | fontFamily: 'Avenir Next',
32 | fontWeight: '600',
33 | fontSize: 11,
34 | marginLeft: 10,
35 | },
36 | optionValueTitle: {
37 | fontFamily: 'Avenir Next',
38 | fontSize: 11,
39 | marginLeft: 3,
40 | },
41 | optionValueContainer: {
42 | padding: 4,
43 | backgroundColor: '#F2E3E3',
44 | borderLeftWidth: StyleSheet.hairlineWidth,
45 | borderRightWidth: StyleSheet.hairlineWidth,
46 | borderBottomWidth: StyleSheet.hairlineWidth,
47 | borderColor: '#ddd',
48 | justifyContent: 'center',
49 | },
50 | });
51 |
--------------------------------------------------------------------------------
/mobile/src/components/card/assets/blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/src/components/card/assets/blank.png
--------------------------------------------------------------------------------
/mobile/src/components/empty-list/EmptyList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Image, View } from 'react-native';
3 | import PropTypes from 'prop-types';
4 | import font from '../../assets/fonts';
5 |
6 | import Title from '../title/Title';
7 |
8 | const EmptyList = props => {
9 | return (
10 |
11 |
16 |
22 | {props.children}
23 |
24 |
25 | );
26 | };
27 |
28 | EmptyList.propTypes = {
29 | children: PropTypes.node,
30 | };
31 | EmptyList.defaultProps = {};
32 |
33 | export default EmptyList;
34 |
--------------------------------------------------------------------------------
/mobile/src/components/gradient/Gradient.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Dimensions } from 'react-native';
4 |
5 | import LinearGradient from 'react-native-linear-gradient';
6 |
7 | const width = Dimensions.get('window').width;
8 |
9 | const propTypes = {
10 | colors: PropTypes.array,
11 | };
12 |
13 | const defaultProps = {
14 | colors: ['#CC6155', '#CC6155', '#DA7D75'],
15 | };
16 |
17 | const Gradient = ({ children, colors }) => (
18 |
19 | {children}
20 |
21 | );
22 |
23 | Gradient.propTypes = propTypes;
24 | Gradient.defaultProps = defaultProps;
25 |
26 | export default Gradient;
27 |
--------------------------------------------------------------------------------
/mobile/src/components/gradient/Gradient.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react-native';
3 | import { color } from '@storybook/addon-knobs';
4 |
5 | import Gradient from './Gradient';
6 |
7 | storiesOf('Gradient', module).add('default', () => (
8 |
11 | ));
12 |
--------------------------------------------------------------------------------
/mobile/src/components/input/Input.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react-native';
3 | import { text, number, color, boolean, select } from '@storybook/addon-knobs';
4 |
5 | import Input from './Input';
6 |
7 | function handleSubmit(value) {
8 | console.log(value);
9 | }
10 |
11 | const keyboardTypes = ['default', 'numeric', 'email-address', 'phone-pad'];
12 | const returnKeys = ['default', 'done', 'go', 'next', 'search', 'send'];
13 |
14 | storiesOf('Input', module).add('default', () => (
15 |
33 | ));
34 |
--------------------------------------------------------------------------------
/mobile/src/components/input/Input.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | import color from '../../statics/colors';
4 | import font from '../../assets/fonts';
5 |
6 | export default StyleSheet.create({
7 | input: {
8 | height: 50,
9 | fontSize: 22,
10 | color: color.white,
11 | fontWeight: 'bold',
12 | borderBottomWidth: 1,
13 | fontFamily: font,
14 | },
15 | inputIcon: {
16 | position: 'absolute',
17 | right: 5,
18 | top: 25,
19 | },
20 | label: {
21 | color: color.white,
22 | fontSize: 11,
23 | fontWeight: 'bold',
24 | fontFamily: font,
25 | backgroundColor: 'transparent',
26 | },
27 | helper: {
28 | fontSize: 12,
29 | color: color.white,
30 | fontWeight: '400',
31 | fontStyle: 'italic',
32 | fontFamily: font,
33 | paddingTop: 8,
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/mobile/src/components/layout/Container.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { View, StatusBar, Platform, StyleSheet, ScrollView } from 'react-native';
4 |
5 | import Colors from '../../statics/colors';
6 | import Title from '../title/Title';
7 | import NavigationButton from '../navigation-button/NavigationButton';
8 |
9 | const TopBar = props => (
10 |
19 |
20 | {props.navigation && (
21 | props.navigation.goBack()} back dark />
22 | )}
23 | {props.title ? (
24 |
25 | {props.title}
26 |
27 | ) : null}
28 |
29 | {props.leftButton}
30 |
31 | );
32 |
33 | const Container = props => {
34 | if (props.asScroll) {
35 | return (
36 |
37 |
41 |
42 | {props.children}
43 |
44 |
45 | );
46 | }
47 |
48 | return (
49 |
50 |
51 | {props.children}
52 |
53 | );
54 | };
55 |
56 | Container.propTypes = {
57 | children: PropTypes.any,
58 | title: PropTypes.string,
59 | leftButton: PropTypes.node,
60 | asScroll: PropTypes.bool,
61 | innerStyle: View.propTypes.style,
62 | containerStyle: View.propTypes.style,
63 | };
64 |
65 | const styles = StyleSheet.create({
66 | container: {
67 | flex: 1,
68 | backgroundColor: Colors.white,
69 | paddingTop: Platform.select({
70 | ios: 20,
71 | android: StatusBar.currentHeight,
72 | }),
73 | },
74 | subContainer: {
75 | padding: 16,
76 | },
77 | containerTitle: {
78 | flexDirection: 'row',
79 | justifyContent: 'space-between',
80 | alignItems: 'center',
81 | paddingTop: 16,
82 | paddingRight: 16,
83 | },
84 | });
85 |
86 | export default Container;
87 |
--------------------------------------------------------------------------------
/mobile/src/components/layout/KeyboardAwareCenteredView.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { KeyboardAvoidingView, StatusBar, Platform } from 'react-native';
3 |
4 | import Gradient from '../gradient/Gradient';
5 | import styles from './KeyboardAwareCenteredView.styles';
6 |
7 | const KeyboardAwareCenteredView = props => (
8 |
9 |
10 |
15 | {props.children}
16 |
17 |
18 | );
19 |
20 | export default KeyboardAwareCenteredView;
21 |
--------------------------------------------------------------------------------
/mobile/src/components/layout/KeyboardAwareCenteredView.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | flex: 1,
6 | alignItems: 'center',
7 | marginTop: 40,
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/mobile/src/components/loading/FullLoading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import LoadingIndicator from './LoadingIndicator';
5 |
6 | import Colors from '../../statics/colors';
7 |
8 | export default () => (
9 |
10 |
11 |
12 | );
13 |
14 | const LoadingContainer = styled.View`
15 | flex: 1;
16 | justify-content: center;
17 | align-items: center;
18 | background-color: ${Colors.white};
19 | `;
20 |
--------------------------------------------------------------------------------
/mobile/src/components/loading/LoadingIndicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import styled from 'styled-components';
4 |
5 | import Title from '../title/Title';
6 |
7 | import { translate } from '../../i18n';
8 |
9 | import Colors from '../../statics/colors';
10 | import font from '../../assets/fonts';
11 |
12 | export default () => (
13 |
14 |
15 | {translate('loading')}
16 |
17 |
18 |
19 | );
20 |
21 | const LoadingIndicator = styled.ActivityIndicator`
22 | margin-top: 10px;
23 | `;
24 |
--------------------------------------------------------------------------------
/mobile/src/components/modal-navigation-bar/ModalNavigationBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { TouchableOpacity, View } from 'react-native';
5 | import Ionicons from 'react-native-vector-icons/Ionicons';
6 |
7 | import Title from '../title/Title';
8 |
9 | import styles from './ModalNavigationBar.styles';
10 | import Colors from '../../statics/colors';
11 |
12 | const ModalNavigationBar = props => (
13 |
14 |
23 |
24 |
25 |
26 | {props.rightText}
27 |
28 |
29 | );
30 |
31 | ModalNavigationBar.propTypes = {
32 | style: PropTypes.object,
33 | onPressRight: PropTypes.func,
34 | rightText: PropTypes.string,
35 | closeModal: PropTypes.func,
36 | withBackButton: PropTypes.bool,
37 | };
38 |
39 | export default ModalNavigationBar;
40 |
--------------------------------------------------------------------------------
/mobile/src/components/modal-navigation-bar/ModalNavigationBar.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | topBarContainer: {
5 | flexDirection: 'row',
6 | justifyContent: 'space-between',
7 | alignItems: 'center',
8 | marginTop: 10
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/mobile/src/components/navigation-button/NavigationButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { TouchableOpacity, View } from 'react-native';
4 | import Icon from 'react-native-vector-icons/Ionicons';
5 |
6 | import color from '../../statics/colors/index';
7 | import styles from './NavigationButton.styles';
8 |
9 | const propTypes = {
10 | back: PropTypes.bool,
11 | enabled: PropTypes.bool,
12 | onPress: PropTypes.func.isRequired,
13 | dark: PropTypes.bool,
14 | };
15 |
16 | const defaultProps = {
17 | back: false,
18 | enabled: false,
19 | dark: false,
20 | };
21 |
22 | const NavigationButton = ({ dark, back, enabled, onPress }) => {
23 | return (
24 |
25 | {back ? (
26 |
35 |
36 |
37 | ) : (
38 |
53 |
59 |
60 | )}
61 |
62 | );
63 | };
64 |
65 | NavigationButton.propTypes = propTypes;
66 | NavigationButton.defaultProps = defaultProps;
67 |
68 | export default NavigationButton;
69 |
--------------------------------------------------------------------------------
/mobile/src/components/navigation-button/NavigationButton.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react-native';
3 | import { boolean } from '@storybook/addon-knobs';
4 |
5 | import NavigationButton from './NavigationButton';
6 |
7 | function log() {
8 | console.log('Done');
9 | }
10 |
11 | storiesOf('NavigationButton', module).add('default', () => (
12 |
17 | ));
18 |
--------------------------------------------------------------------------------
/mobile/src/components/navigation-button/NavigationButton.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | touchable: {
5 | alignItems: 'center',
6 | justifyContent: 'center',
7 | height: 40,
8 | width: 40,
9 | borderRadius: 30,
10 | marginBottom: 5,
11 | zIndex: -1,
12 | },
13 | container: {
14 | width: 40,
15 | backgroundColor: 'transparent',
16 | },
17 | icon: {
18 | zIndex: 10,
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/mobile/src/components/order-icon/OrderIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text } from 'react-native';
3 | import { withApollo } from 'react-apollo/withApollo';
4 | import SimpleLineIcons from 'react-native-vector-icons/SimpleLineIcons';
5 | import sumBy from 'lodash/sumBy';
6 |
7 | import commonQueries from '../../graphql/queries';
8 |
9 | import styles from './OrderIcon.styles';
10 |
11 | class OrderIcon extends React.PureComponent {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.state = {
16 | itemsCount: 0,
17 | };
18 | }
19 |
20 | async componentDidMount() {
21 | this.subscription = this.props.client
22 | .watchQuery({ query: commonQueries.userInformation })
23 | .subscribe(({ data }) => {
24 | if (data.me.cart.length > 0) {
25 | this.setState({
26 | itemsCount: sumBy(data.me.cart, lineItem => lineItem.quantity),
27 | });
28 | } else {
29 | this.setState({ itemsCount: 0 });
30 | }
31 | });
32 | }
33 |
34 | componentWillUnmount() {
35 | this.subscription.unsubscribe();
36 | }
37 |
38 | render() {
39 | return (
40 |
41 | {this.state.itemsCount > 0 && (
42 |
43 | {this.state.itemsCount}
44 |
45 | )}
46 | 0 ? 'basket-loaded' : 'basket'}
48 | size={22}
49 | style={{ color: this.props.tintColor }}
50 | />
51 |
52 | );
53 | }
54 | }
55 |
56 | export default withApollo(OrderIcon);
57 |
--------------------------------------------------------------------------------
/mobile/src/components/order-icon/OrderIcon.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import Colors from '../../statics/colors';
3 |
4 | export default StyleSheet.create({
5 | badgeContainer: {
6 | position: 'absolute',
7 | top: -4,
8 | right: -10,
9 | minWidth: 13,
10 | height: 13,
11 | backgroundColor: Colors.red,
12 | zIndex: 100,
13 | borderRadius: 100,
14 | justifyContent: 'center',
15 | alignItems: 'center',
16 | },
17 | badgeText: { color: Colors.white, fontSize: 10, marginLeft: 0.5 },
18 | });
19 |
--------------------------------------------------------------------------------
/mobile/src/components/title/Title.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Text, Platform } from 'react-native';
5 |
6 | const propTypes = {
7 | style: Text.propTypes.style,
8 | children: PropTypes.node.isRequired,
9 | size: PropTypes.number,
10 | weight: PropTypes.string,
11 | color: PropTypes.string,
12 | font: PropTypes.string,
13 | fontStyle: PropTypes.oneOf(['normal', 'italic']),
14 | };
15 |
16 | const defaultProps = {
17 | size: 16,
18 | weight: 'normal',
19 | color: 'black',
20 | ...Platform.select({
21 | ios: { font: 'Avenir Next' },
22 | android: { font: 'Roboto' },
23 | }),
24 | fontStyle: 'normal',
25 | };
26 |
27 | const Title = ({ children, style, size, weight, color, font, fontStyle }) => (
28 |
41 | {children}
42 |
43 | );
44 |
45 | Title.propTypes = propTypes;
46 | Title.defaultProps = defaultProps;
47 |
48 | export default Title;
49 |
--------------------------------------------------------------------------------
/mobile/src/components/title/Title.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react-native';
3 | import { text, number, select, color } from '@storybook/addon-knobs';
4 |
5 | import Title from './Title';
6 |
7 | const options = [100, 200, 300, 400, 500, 600, 700, 800, 900];
8 |
9 | storiesOf('Title', module).add('default', () => (
10 |
15 | {text('Label', 'Title')}
16 |
17 | ));
18 |
--------------------------------------------------------------------------------
/mobile/src/graphql/fragments/ProductCardFragment.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default gql`
4 | fragment ProductCardFragment on Product {
5 | id
6 | name
7 | available
8 | imageUrl
9 | displayPrice
10 | brand {
11 | id
12 | name
13 | }
14 | unavailableOptionsValues {
15 | id
16 | name
17 | }
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/mobile/src/graphql/queries/index.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default {
4 | userInformation: gql`
5 | query userInformation {
6 | me {
7 | id
8 | email
9 | oneSignalUserId
10 | firstName
11 | orders {
12 | id
13 | }
14 | cart {
15 | id
16 | deletedAt
17 | quantity
18 | variant {
19 | id
20 | available
21 | price
22 | selectedOptions {
23 | id
24 | option {
25 | id
26 | name
27 | }
28 | value {
29 | id
30 | name
31 | }
32 | }
33 | product {
34 | id
35 | name
36 | imageUrl
37 | available
38 | unavailableOptionsValues {
39 | id
40 | name
41 | }
42 | brand {
43 | id
44 | name
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | `,
52 | };
53 |
--------------------------------------------------------------------------------
/mobile/src/graphql/setupApollo.js:
--------------------------------------------------------------------------------
1 | import { AsyncStorage } from 'react-native';
2 | import { ApolloClient, InMemoryCache, HttpLink, split } from 'apollo-client-preset';
3 | import { WebSocketLink } from 'apollo-link-ws';
4 | import { setContext } from 'apollo-link-context';
5 | import { getMainDefinition } from 'apollo-utilities';
6 |
7 | import StorageKeys from '../statics/storage-keys';
8 |
9 | let cachedToken = '';
10 |
11 | async function getAuthorizationToken() {
12 | const token = cachedToken
13 | ? cachedToken
14 | : await AsyncStorage.getItem(StorageKeys.GC_TOKEN);
15 |
16 | cachedToken = token;
17 |
18 | return token;
19 | }
20 |
21 | export function setupApolloClient() {
22 | const wsLink = new WebSocketLink({
23 | uri: 'ws://localhost:4000/',
24 | options: {
25 | reconnect: true,
26 | },
27 | });
28 |
29 | const httpLink = new HttpLink({
30 | uri: 'http://localhost:4000/',
31 | });
32 |
33 | const authMiddleware = setContext(
34 | (_, { headers }) =>
35 | new Promise(async resolve => {
36 | // get the authentication token from local storage if it exists
37 | const token = await getAuthorizationToken();
38 |
39 | cachedToken = token;
40 |
41 | // return the headers to the context so httpLink can read them
42 | resolve({
43 | headers: {
44 | ...headers,
45 | authorization: token ? `Bearer ${token}` : null,
46 | },
47 | });
48 | }),
49 | );
50 |
51 | const httpLinkWithAuth = authMiddleware.concat(httpLink);
52 |
53 | const link = split(
54 | ({ query }) => {
55 | const { kind, operation } = getMainDefinition(query);
56 | return kind === 'OperationDefinition' && operation === 'subscription';
57 | },
58 | wsLink,
59 | httpLinkWithAuth,
60 | );
61 |
62 | const client = new ApolloClient({
63 | link,
64 | cache: new InMemoryCache({ dataIdFromObject: o => o.id }),
65 | connectToDevTools: true,
66 | });
67 |
68 | return client;
69 | }
70 |
--------------------------------------------------------------------------------
/mobile/src/helpers/StackModalNavigator.js:
--------------------------------------------------------------------------------
1 | import { createStackNavigator } from 'react-navigation';
2 |
3 | const StackModalNavigator = (routeConfigs, navigatorConfig) => {
4 | const CardStackNavigator = createStackNavigator(routeConfigs, navigatorConfig);
5 | const modalRouteConfig = {};
6 | const routeNames = Object.keys(routeConfigs);
7 |
8 | for (let i = 0; i < routeNames.length; i++) {
9 | modalRouteConfig[`${routeNames[i]}Modal`] = routeConfigs[routeNames[i]];
10 | }
11 |
12 | return createStackNavigator(
13 | {
14 | CardStackNavigator: { screen: CardStackNavigator },
15 | ...modalRouteConfig,
16 | },
17 | {
18 | mode: 'modal',
19 | headerMode: 'none',
20 | },
21 | );
22 | };
23 |
24 | export default StackModalNavigator;
25 |
--------------------------------------------------------------------------------
/mobile/src/statics/colors/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | white: '#FFF',
3 | red: '#CC6155',
4 | grey: '#808080',
5 | text: '#484848',
6 | liteGrey: 'rgba(46, 64, 87, 0.1)',
7 | };
8 |
--------------------------------------------------------------------------------
/mobile/src/statics/storage-keys/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | GC_TOKEN: 'GC_TOKEN',
3 | SHOP_ID: 'SHOP_ID',
4 | };
5 |
--------------------------------------------------------------------------------
/mobile/src/views/basket/BasketContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 |
3 | import commonQueries from '../../graphql/queries';
4 |
5 | import Basket from './Basket';
6 | import queries from './query.gql';
7 |
8 | export default compose(
9 | withApollo,
10 | graphql(queries.removeItemFromCart, {
11 | props: ({ mutate }) => ({
12 | removeItemFromBasket: ({ lineItemId }) =>
13 | mutate({
14 | variables: { lineItemId },
15 | update: store => {
16 | const data = store.readQuery({ query: commonQueries.userInformation });
17 |
18 | data.me.cart = data.me.cart.filter(lineItem => {
19 | return lineItem.id !== lineItemId;
20 | });
21 |
22 | store.writeQuery({ query: commonQueries.userInformation, data });
23 | },
24 | }),
25 | }),
26 | }),
27 | graphql(queries.updateLineItemQuantity, {
28 | props: ({ mutate }) => ({
29 | updateLineItemQuantity: ({ variantId, quantity }) => (
30 | mutate({ variables: { variantId, quantity } })
31 | )
32 | })
33 | })
34 | )(Basket);
35 |
--------------------------------------------------------------------------------
/mobile/src/views/basket/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default {
4 | removeItemFromCart: gql`
5 | mutation removeItemFromCart($lineItemId: ID!) {
6 | removeItemFromCart(lineItemId: $lineItemId) {
7 | id
8 | }
9 | }
10 | `,
11 | updateLineItemQuantity: gql`
12 | mutation updateLineItemQuantity($variantId: ID!, $quantity: Int!) {
13 | addItemToCart(variantId: $variantId, quantity: $quantity) {
14 | id
15 | quantity
16 | }
17 | }
18 | `
19 | };
20 |
--------------------------------------------------------------------------------
/mobile/src/views/browse/BrowseContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql } from 'react-apollo';
2 | import query from './query.gql';
3 |
4 | import Browse from './Browse';
5 |
6 | export default graphql(query)(Browse);
7 |
--------------------------------------------------------------------------------
/mobile/src/views/browse/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default gql`
4 | query dataForBrowsing {
5 | categories: allCategories {
6 | id
7 | name
8 | }
9 | }
10 | `;
11 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/Filters.styles.js:
--------------------------------------------------------------------------------
1 | import { Dimensions, StyleSheet } from 'react-native';
2 | import Colors from '../../statics/colors';
3 |
4 | export default StyleSheet.create({
5 | loadingContainer: {
6 | flex: 1,
7 | justifyContent: 'center',
8 | alignItems: 'center',
9 | },
10 | findProductContainer: {
11 | marginBottom: 30,
12 | alignSelf: 'center',
13 | backgroundColor: Colors.red,
14 | flexDirection: 'row',
15 | justifyContent: 'center',
16 | alignItems: 'center',
17 | height: 45,
18 | width: Dimensions.get('window').width * 0.85,
19 | borderRadius: 100,
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/filterByFields/BrandFilters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FilterGroup from '../filtersGroup/FiltersGroup';
3 | import Filter from '../filtersGroup/Filter';
4 | import { translate } from '../../../i18n';
5 |
6 | const filterName = 'brands';
7 |
8 | const BrandFilters = props => (
9 |
10 | props.setFilter([filterName, ''])}
13 | selected={props.filtersEnabled[filterName].length === 0}
14 | />
15 | {props.filtersValues[filterName].map(brand => (
16 | props.setFilter([filterName, brand.id])}
20 | selected={props.filtersEnabled[filterName].includes(brand.id)}
21 | />
22 | ))}
23 |
24 | );
25 |
26 | BrandFilters.propTypes = {};
27 |
28 | export default BrandFilters;
29 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/filterByFields/CategoryFilters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FilterGroup from '../filtersGroup/FiltersGroup';
3 | import Filter from '../filtersGroup/Filter';
4 | import { translate } from '../../../i18n';
5 |
6 | const filterName = 'attributes';
7 |
8 | const CategoryFilters = props => (
9 |
10 | props.setFilter([filterName, ''])}
13 | selected={props.filtersEnabled[filterName].length === 0}
14 | />
15 | {props.filtersValues[filterName].map(attribute => (
16 | props.setFilter([filterName, attribute.id])}
20 | selected={props.filtersEnabled[filterName].includes(attribute.id)}
21 | />
22 | ))}
23 |
24 | );
25 |
26 | CategoryFilters.propTypes = {};
27 |
28 | export default CategoryFilters;
29 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/filterByFields/OptionsFilters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FilterGroup from '../filtersGroup/FiltersGroup';
3 | import Filter from '../filtersGroup/Filter';
4 | import { translate } from '../../../i18n';
5 |
6 | const OptionsFilters = props =>
7 | props.filtersValues['options'].map(option => (
8 |
13 | props.setFilter([option.name, null])}
16 | selected={props.filtersEnabled[option.name].length === 0}
17 | />
18 | {option.values.map(optionValue => (
19 | props.setFilter([option.name, optionValue.id])}
23 | selected={props.filtersEnabled[option.name].includes(optionValue.id)}
24 | />
25 | ))}
26 |
27 | ));
28 |
29 | OptionsFilters.propTypes = {};
30 |
31 | export default OptionsFilters;
32 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/filtersGroup/Filter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Text, TouchableOpacity } from 'react-native';
4 |
5 | import Colors from '../../../statics/colors/index';
6 |
7 | import styles from './Filter.styles';
8 |
9 | const Filter = ({ setFilter, name, selected }) => (
10 |
14 | {name}
15 |
16 | );
17 |
18 | Filter.propTypes = {
19 | name: PropTypes.string,
20 | selected: PropTypes.bool,
21 | setFilter: PropTypes.func,
22 | };
23 |
24 | export default Filter;
25 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/filtersGroup/Filter.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | filterContainer: {
5 | marginBottom: 5,
6 | marginRight: 10,
7 | paddingTop: 5,
8 | paddingBottom: 5,
9 | paddingLeft: 7,
10 | paddingRight: 7,
11 | borderRadius: 4,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/filtersGroup/FiltersGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { View } from 'react-native';
4 |
5 | import Colors from '../../../statics/colors';
6 | import Title from '../../../components/title/Title';
7 |
8 | const FiltersGroup = props => (
9 |
10 |
11 | {props.title}
12 |
13 | {props.children}
14 |
15 | );
16 |
17 | FiltersGroup.propTypes = {
18 | title: PropTypes.string,
19 | exposedFilter: PropTypes.object,
20 | filtersValues: PropTypes.object,
21 | filtersEnabled: PropTypes.object,
22 | setFilter: PropTypes.func,
23 | };
24 |
25 | export default FiltersGroup;
26 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/filtersGroup/index.js:
--------------------------------------------------------------------------------
1 | import FilterGroup from './FiltersGroup';
2 |
3 | export default FilterGroup;
4 |
--------------------------------------------------------------------------------
/mobile/src/views/filters/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default gql`
4 | query filersForCategory($categoryId: ID) {
5 | brands: allBrands(categoryId: $categoryId) {
6 | id
7 | name
8 | }
9 | attributes: allAttributes(categoryId: $categoryId) {
10 | id
11 | value
12 | }
13 | options: allOptions(categoryId: $categoryId) {
14 | id
15 | name
16 | values {
17 | id
18 | name
19 | }
20 | }
21 | categories: allCategories {
22 | id
23 | name
24 | }
25 | }
26 | `;
27 |
--------------------------------------------------------------------------------
/mobile/src/views/home/Home.styles.js:
--------------------------------------------------------------------------------
1 | import { Dimensions, Platform, StatusBar, StyleSheet } from 'react-native';
2 | import Colors from '../../statics/colors';
3 |
4 | export default StyleSheet.create({
5 | smallRedButton: {
6 | marginBottom: 15,
7 | backgroundColor: Colors.red,
8 | justifyContent: 'center',
9 | alignItems: 'center',
10 | width: Dimensions.get('window').width * 0.42,
11 | height: 30,
12 | borderRadius: 100,
13 | },
14 | messageOfTheDay: {
15 | marginTop: 16,
16 | marginBottom: 20,
17 | padding: 10,
18 | backgroundColor: 'rgba(240, 240, 240, 0.4)',
19 | borderWidth: StyleSheet.hairlineWidth,
20 | borderColor: 'rgba(151, 151, 151, 0.8)',
21 | },
22 | titleContainer: {
23 | flexDirection: 'row',
24 | justifyContent: 'space-between',
25 | alignItems: 'center',
26 | },
27 | container: {
28 | flex: 1,
29 | backgroundColor: Colors.white,
30 | paddingTop: Platform.select({
31 | ios: 20,
32 | android: StatusBar.currentHeight,
33 | }),
34 | },
35 | ticketContainer: {
36 | backgroundColor: 'rgba(249, 249, 249, 0.8)',
37 | borderColor: 'rgba(151, 151, 151, 0.4)',
38 | borderWidth: StyleSheet.hairlineWidth,
39 | padding: 15,
40 | marginBottom: 20,
41 | },
42 | recapRowContainer: {
43 | flexDirection: 'row',
44 | justifyContent: 'space-between',
45 | marginBottom: 4,
46 | marginTop: 4,
47 | },
48 | separator: {
49 | alignSelf: 'center',
50 | width: Dimensions.get('window').width * 0.85,
51 | height: StyleSheet.hairlineWidth,
52 | backgroundColor: 'rgba(151, 151, 151, 0.3)',
53 | marginBottom: 8,
54 | marginTop: 8,
55 | },
56 | simpleProductListContainer: {
57 | flex: 1,
58 | marginBottom: 20,
59 | },
60 | });
61 |
--------------------------------------------------------------------------------
/mobile/src/views/home/HomeContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 | import Home from './Home';
3 | import queries from './query.gql';
4 | import commonQueries from '../../graphql/queries';
5 |
6 | //TODO: Faster mutation by invalidating cache instead of using refetchQueries
7 | export default compose(
8 | graphql(queries.homeInformation),
9 | graphql(queries.addOrderToCart, {
10 | props: ({ mutate }) => ({
11 | addOrderToCart: ({ orderId, replace }) =>
12 | mutate({
13 | variables: { orderId, replace },
14 | refetchQueries: [{ query: commonQueries.userInformation }],
15 | // update: (store, { data: { addOrderToCart } }) => {
16 | // const data = store.readQuery({ query: commonQueries.userInformation });
17 | //
18 | // console.log(addOrderToCart);
19 | //
20 | // data.me.cart = addOrderToCart;
21 | //
22 | // store.writeQuery({ query: commonQueries.userInformation, data });
23 | // }
24 | }),
25 | }),
26 | }),
27 | graphql(queries.updateOneSignalUserId, {
28 | props: ({ mutate }) => ({
29 | updateOneSignalUserId: ({ oneSignalUserId }) =>
30 | mutate({
31 | variables: { oneSignalUserId },
32 | }),
33 | }),
34 | }),
35 | withApollo
36 | )(Home);
37 |
--------------------------------------------------------------------------------
/mobile/src/views/login/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Image, StatusBar, TouchableOpacity, View } from 'react-native';
3 |
4 | import styles from './Login.styles';
5 | import color from '../../statics/colors';
6 | import { translate } from '../../i18n';
7 |
8 | import Gradient from '../../components/gradient/Gradient';
9 | import Title from '../../components/title/Title';
10 | import Button from '../../components/button/Button';
11 | import Icon from 'react-native-vector-icons/SimpleLineIcons';
12 |
13 | const Login = props => (
14 |
15 |
16 |
17 |
18 |
19 | Aromaclop
20 |
21 |
52 |
53 | );
54 |
55 | Login.propTypes = {};
56 | Login.defaultProps = {};
57 |
58 | export default Login;
59 |
--------------------------------------------------------------------------------
/mobile/src/views/login/Login.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | flex: 1,
6 | justifyContent: 'center',
7 | alignItems: 'center',
8 | backgroundColor: 'transparent',
9 | },
10 | logo: {
11 | height: 150,
12 | width: 150,
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/mobile/src/views/password-reset/AskPasswordResetContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose } from 'react-apollo';
2 |
3 | import AskPasswordReset from './AskPasswordReset';
4 | import queries from './queries.gql';
5 |
6 | export default compose(
7 | graphql(queries.resetPassword, {
8 | props: ({ mutate }) => ({
9 | resetPassword: ({ email }) => (
10 | mutate({ variables: { email } })
11 | )
12 | })
13 | })
14 | )(AskPasswordReset);
15 |
--------------------------------------------------------------------------------
/mobile/src/views/password-reset/queries.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default {
4 | resetPassword: gql`
5 | mutation resetPassword($email: String!) {
6 | resetPassword(email: $email) {
7 | mailMaybeSent
8 | }
9 | }
10 | `
11 | };
12 |
--------------------------------------------------------------------------------
/mobile/src/views/payment/AfterPayment.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { NavigationActions, StackActions } from 'react-navigation';
3 | import { View, StyleSheet, Dimensions } from 'react-native';
4 |
5 | import Title from '../../components/title/Title';
6 | import BigRedButton from '../../components/big-red-button/BigRedButton';
7 |
8 | import Colors from '../../statics/colors'
9 | import font from '../../assets/fonts'
10 | import Container from '../../components/layout/Container';
11 | import { translate } from '../../i18n';
12 |
13 | const Separator = () => ;
14 |
15 | class AfterPayment extends PureComponent {
16 |
17 | navigateToHome() {
18 | const resetAction = StackActions.reset({
19 | index: 0,
20 | actions: [NavigationActions.navigate({ routeName: 'Basket' })],
21 | });
22 | this.props.navigation.dispatch(resetAction);
23 | this.props.navigation.navigate('WelcomeTab');
24 | }
25 |
26 | render() {
27 | return (
28 |
29 |
36 | {translate('congratz_payment')}
37 |
38 |
39 |
45 | {translate('will_receive_notif')}
46 |
47 | this.navigateToHome()}
49 | style={{ marginTop: 16 }}
50 | label={translate('back_to_home')}
51 | />
52 |
53 | );
54 | }
55 | }
56 |
57 | AfterPayment.propTypes = {};
58 | AfterPayment.defaultProps = {};
59 |
60 | const styles = StyleSheet.create({
61 | separator: {
62 | alignSelf: 'center',
63 | width: Dimensions.get('window').width * 0.90,
64 | height: StyleSheet.hairlineWidth,
65 | backgroundColor: '#ccc',
66 | marginBottom: 16,
67 | marginTop: 16,
68 | },
69 | });
70 |
71 | export default AfterPayment;
72 |
--------------------------------------------------------------------------------
/mobile/src/views/payment/Button.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import {
3 | View,
4 | Text,
5 | TouchableHighlight,
6 | ActivityIndicator,
7 | Platform,
8 | StyleSheet,
9 | } from 'react-native';
10 | import PropTypes from 'prop-types';
11 |
12 | export default class Button extends PureComponent {
13 | static propTypes = {
14 | text: PropTypes.string.isRequired,
15 | disabledText: PropTypes.string,
16 | loading: PropTypes.bool,
17 | disabled: PropTypes.bool,
18 | style: PropTypes.any,
19 | onPress: PropTypes.func.isRequired,
20 | };
21 |
22 | static defaultProps = {
23 | disabledText: '',
24 | loading: false,
25 | disabled: false,
26 | style: undefined,
27 | };
28 |
29 | handlePress = event => {
30 | const { loading, disabled, onPress } = this.props;
31 |
32 | if (loading || disabled) {
33 | return;
34 | }
35 |
36 | if (onPress) {
37 | onPress(event);
38 | }
39 | };
40 |
41 | render() {
42 | const { text, disabledText, loading, disabled, style, ...rest } = this.props;
43 |
44 | return (
45 |
51 |
52 | {loading && }
53 | {!loading && !disabled && {text}}
54 | {!loading && disabled && {disabledText || text}}
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | const styles = StyleSheet.create({
62 | button: {
63 | padding: 8,
64 | margin: 10,
65 | height: Platform.OS === 'ios' ? 35 : 40,
66 | minWidth: 160,
67 | overflow: 'hidden',
68 | borderWidth: 1,
69 | borderRadius: 4,
70 | backgroundColor: 'white',
71 | alignItems: 'center',
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/mobile/src/views/payment/PaymentContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose } from 'react-apollo';
2 |
3 | import Payment from './Payment';
4 | import queries from './query.gql';
5 | import orderQueries from '../../views/profile/views/orders/query.gql';
6 | import homeQueries from '../../views/home/query.gql';
7 |
8 | export default compose(
9 | graphql(queries.orderStatuses, {
10 | name: 'orderStatuses',
11 | props: props => ({
12 | ...props,
13 | waitFor3DSecure: ({ orderId }) => {
14 | return props.orderStatuses.subscribeToMore({
15 | document: queries.waitFor3DSecure,
16 | variables: { orderId },
17 | updateQuery: (prev, { subscriptionData }) => {
18 | const updatedOrder = subscriptionData.data.waitFor3DSecure.node;
19 |
20 | return {
21 | ...prev,
22 | me: {
23 | ...prev.me,
24 | cart: updatedOrder.orderStatus === 'PAID' ? [] : prev.me.cart,
25 | orders: prev.me.orders.map(order => {
26 | if (order.id !== updatedOrder.id) {
27 | return order;
28 | }
29 |
30 | return {
31 | ...order,
32 | orderStatus: updatedOrder.orderStatus,
33 | };
34 | }),
35 | },
36 | };
37 | },
38 | });
39 | },
40 | }),
41 | }),
42 | graphql(queries.pay, {
43 | props: ({ mutate }) => ({
44 | pay: ({ stripeTokenId }) =>
45 | mutate({
46 | variables: { stripeTokenId },
47 | refetchQueries: [
48 | { query: orderQueries.userOrders },
49 | { query: homeQueries.homeInformation },
50 | ],
51 | update: (store, { data: { pay } }) => {
52 | const data = store.readQuery({ query: queries.orderStatuses });
53 |
54 | if (pay.order.orderStatus === 'PAID') {
55 | data.me.cart = [];
56 | }
57 |
58 | data.me.orders.push(pay.order);
59 |
60 | store.writeQuery({ query: queries.orderStatuses, data });
61 | },
62 | }),
63 | }),
64 | }),
65 | )(Payment);
66 |
--------------------------------------------------------------------------------
/mobile/src/views/payment/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default {
4 | pay: gql`
5 | mutation pay($stripeTokenId: String!) {
6 | pay(stripeTokenId: $stripeTokenId) {
7 | redirectUrl
8 | order {
9 | id
10 | createdAt
11 | orderStatus
12 | }
13 | }
14 | }
15 | `,
16 | orderStatuses: gql`
17 | query orderStatuses {
18 | me {
19 | id
20 | cart {
21 | id
22 | }
23 | orders(orderBy: createdAt_ASC) {
24 | id
25 | createdAt
26 | orderStatus
27 | }
28 | }
29 | }
30 | `,
31 | waitFor3DSecure: gql`
32 | subscription waitFor3DSecure($orderId: ID!) {
33 | waitFor3DSecure(orderId: $orderId) {
34 | node {
35 | id
36 | createdAt
37 | orderStatus
38 | }
39 | }
40 | }
41 | `,
42 | };
43 |
--------------------------------------------------------------------------------
/mobile/src/views/product/Product.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Dimensions } from 'react-native';
2 |
3 | import Colors from '../../statics/colors';
4 |
5 | const styles = StyleSheet.create({
6 | container: {
7 | flex: 1,
8 | alignItems: 'center',
9 | justifyContent: 'flex-end',
10 | backgroundColor: Colors.white,
11 | },
12 |
13 | close: {
14 | position: 'absolute',
15 | top: 25,
16 | left: 25,
17 | zIndex: 10,
18 | },
19 |
20 | favorite: {
21 | backgroundColor: Colors.red,
22 | position: 'absolute',
23 | top: 150,
24 | right: 0,
25 | padding: 10,
26 | borderBottomLeftRadius: 5,
27 | borderTopLeftRadius: 5,
28 | },
29 |
30 | image: {
31 | flex: 0.8,
32 | height: 250,
33 | width: 250,
34 | marginBottom: 30,
35 | },
36 |
37 | imageContainer: {
38 | flex: 0.8,
39 | justifyContent: 'flex-end',
40 | },
41 |
42 | addToCart: {
43 | backgroundColor: Colors.red,
44 | paddingLeft: 20,
45 | paddingRight: 20,
46 | paddingTop: 15,
47 | paddingBottom: 13,
48 | borderRadius: 50,
49 | marginBottom: 40,
50 | },
51 | productSheetDescription: {
52 | marginBottom: 30,
53 | textAlign: 'center',
54 | paddingLeft: 20,
55 | paddingRight: 20,
56 | },
57 | productSheetDetails: {
58 | flexDirection: 'column',
59 | justifyContent: 'center',
60 | alignItems: 'center',
61 | marginBottom: 10,
62 | },
63 | productSheetContainer: {
64 | flex: 1,
65 | flexDirection: 'column',
66 | justifyContent: 'center',
67 | alignItems: 'center',
68 | backgroundColor: Colors.white,
69 | },
70 | pickerActivatorChoiceIcon: {
71 | position: 'absolute',
72 | top: 6,
73 | right: 80,
74 | },
75 | pickerActivatorContainer: {
76 | flexDirection: 'row',
77 | height: 30,
78 | width: Dimensions.get('window').width * 0.85,
79 | alignItems: 'center',
80 | justifyContent: 'center',
81 | borderWidth: 1,
82 | borderColor: '#ddd',
83 | marginBottom: 10,
84 | },
85 | });
86 |
87 | export default styles;
88 |
--------------------------------------------------------------------------------
/mobile/src/views/product/ProductContainer.js:
--------------------------------------------------------------------------------
1 | import { compose, graphql, withApollo } from 'react-apollo';
2 |
3 | import queries from './queries.gql';
4 | import commonQueries from '../../graphql/queries';
5 | import Product from './Product';
6 |
7 | // TODO: Speed up mutation by updating the store manually instead of refetchQueries
8 | export default compose(
9 | withApollo,
10 | graphql(queries.addItemToCart, {
11 | props: ({ mutate }) => ({
12 | addItemToCart: ({ variantId, quantity }) =>
13 | mutate({
14 | variables: { variantId, quantity },
15 | refetchQueries: [
16 | {
17 | query: commonQueries.userInformation,
18 | },
19 | ],
20 | }),
21 | }),
22 | }),
23 | )(Product);
24 |
--------------------------------------------------------------------------------
/mobile/src/views/product/queries.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default {
4 | queryProductInfo: gql`
5 | query product($productId: ID!, $nullValue: DateTime) {
6 | product(id: $productId) {
7 | id
8 | name
9 | description
10 | available
11 | imageUrl
12 | displayPrice
13 | brand {
14 | id
15 | name
16 | }
17 | options {
18 | id
19 | name
20 | values {
21 | id
22 | name
23 | }
24 | }
25 | variants(where: { deletedAt: $nullValue }) {
26 | id
27 | available
28 | price
29 | selectedOptions {
30 | id
31 | option {
32 | id
33 | name
34 | }
35 | value {
36 | id
37 | name
38 | }
39 | }
40 | }
41 | }
42 | }
43 | `,
44 | addItemToCart: gql`
45 | mutation addItemToCart($variantId: ID!, $quantity: Int!) {
46 | addItemToCart(variantId: $variantId, quantity: $quantity) {
47 | id
48 | quantity
49 | variant {
50 | id
51 | }
52 | }
53 | }
54 | `,
55 | };
56 |
--------------------------------------------------------------------------------
/mobile/src/views/products/Products.styles.js:
--------------------------------------------------------------------------------
1 | import { Platform, StatusBar, StyleSheet } from 'react-native';
2 | import Colors from '../../statics/colors';
3 |
4 | const styles = StyleSheet.create({
5 | containerTitle: {
6 | flexDirection: 'row',
7 | justifyContent: 'space-between',
8 | alignItems: 'center',
9 | },
10 | container: {
11 | flex: 1,
12 | backgroundColor: Colors.white,
13 | paddingTop: Platform.select({
14 | ios: 20,
15 | android: StatusBar.currentHeight,
16 | }),
17 | },
18 | });
19 |
20 | export default styles;
21 |
--------------------------------------------------------------------------------
/mobile/src/views/products/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | import ProductCardFragment from '../../graphql/fragments/ProductCardFragment';
4 |
5 | export default gql`
6 | query productsWithFilters(
7 | $brandsIds: [ID!]!
8 | $attributesIds: [ID!]!
9 | $optionsValuesIds: [ID!]!
10 | $categoryId: ID!
11 | $first: Int!
12 | $skip: Int!
13 | ) {
14 | products: searchProducts(
15 | brandsIds: $brandsIds
16 | attributesIds: $attributesIds
17 | optionsValuesIds: $optionsValuesIds
18 | categoryId: $categoryId
19 | first: $first
20 | skip: $skip
21 | ) {
22 | aggregate {
23 | count
24 | }
25 | edges {
26 | node {
27 | ...ProductCardFragment
28 | }
29 | }
30 | }
31 | }
32 | ${ProductCardFragment}
33 | `;
34 |
--------------------------------------------------------------------------------
/mobile/src/views/products/subscription.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default gql`
4 | subscription subscribeToProductUpdates {
5 | updatedProduct {
6 | node {
7 | id
8 | available
9 | unavailableOptionsValues {
10 | id
11 | name
12 | }
13 | }
14 | }
15 | }
16 | `;
17 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/delete/Delete.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet } from 'react-native';
3 |
4 | import Container from '../../../../components/layout/Container';
5 | import Input from '../../../../components/input/Input';
6 | import Button from '../../../../components/big-red-button/BigRedButton';
7 |
8 | import colors from '../../../../statics/colors';
9 | import { translate } from '../../../../i18n';
10 |
11 | class Delete extends React.PureComponent {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.state = {
16 | password: null,
17 | };
18 | }
19 | render() {
20 | return (
21 |
22 | null}
27 | onChangeText={password => this.setState({ password })}
28 | placeHolder={translate('your_password')}
29 | placeHolderColor={colors.grey}
30 | cursorColor={colors.grey}
31 | returnKey="done"
32 | value={this.state.password}
33 | withValidation
34 | validationFunction={this.validateNewPasswordConfirmation}
35 | />
36 | this.deleteAccount()}
42 | />
43 |
44 | );
45 | }
46 |
47 | validatePassword() {
48 | return this.state.password && this.state.password.length > 3;
49 | }
50 |
51 | validateFields() {
52 | return this.validatePassword();
53 | }
54 |
55 | async deleteAccount() {
56 | const { navigation } = this.props;
57 |
58 | this.setState({ loading: true });
59 | await this.props
60 | .deleteAccount({
61 | password: this.state.password,
62 | })
63 | .then(_ => {
64 | navigation.navigate('Auth');
65 | })
66 | .catch(_ => {
67 | this.setState({ loading: false });
68 | });
69 | this.setState({ loading: false });
70 | }
71 | }
72 |
73 | const styles = StyleSheet.create({
74 | input: {
75 | fontSize: 14,
76 | color: colors.text,
77 | },
78 | });
79 |
80 | export default Delete;
81 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/delete/DeleteContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 | import query from './query.gql';
3 | import Delete from './Delete';
4 |
5 | export default compose(
6 | graphql(query.deleteAccountMutation, {
7 | props: ({ mutate }) => ({
8 | deleteAccount: ({ password }) =>
9 | mutate({
10 | variables: { password },
11 | }),
12 | }),
13 | }),
14 | withApollo,
15 | )(Delete);
16 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/delete/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | const deleteAccountMutation = gql`
4 | mutation deleteAccount($password: String!) {
5 | deleteAccount(password: $password) {
6 | id
7 | }
8 | }
9 | `;
10 |
11 | export default {
12 | deleteAccountMutation,
13 | };
14 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/orders/OrdersContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 | import queries from './query.gql';
3 | import commonQueries from '../../../../graphql/queries';
4 | import Orders from './Orders';
5 |
6 | //TODO: Faster mutation by invalidating cache instead of using refetchQueries
7 | export default compose(
8 | graphql(queries.userOrders, { options: { fetchPolicy: 'cache-and-network' } }),
9 | graphql(queries.addOrderToCart, {
10 | props: ({ mutate }) => ({
11 | addOrderToCart: ({ orderId, replace }) =>
12 | mutate({
13 | variables: { orderId, replace },
14 | refetchQueries: [{ query: commonQueries.userInformation }],
15 | }),
16 | }),
17 | }),
18 | withApollo,
19 | )(Orders);
20 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/orders/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | const userOrders = gql`
4 | query userOrders {
5 | me {
6 | id
7 | cart(first: 1) {
8 | id
9 | }
10 | orders(orderBy: createdAt_DESC) {
11 | id
12 | receiver { id }
13 | createdAt
14 | lineItems {
15 | id
16 | quantity
17 | variant {
18 | id
19 | price
20 | product {
21 | id
22 | name
23 | }
24 | selectedOptions {
25 | id
26 | value {
27 | id
28 | name
29 | }
30 | }
31 | }
32 | }
33 | totalPrice
34 | }
35 | }
36 | }
37 | `;
38 |
39 | const addOrderToCart = gql`
40 | mutation addOrderToCart($orderId: ID!, $replace: Boolean!) {
41 | addOrderToCart(orderId: $orderId, replace: $replace) {
42 | id
43 | quantity
44 | variant {
45 | id
46 | available
47 | price
48 | selectedOptions {
49 | id
50 | option {
51 | id
52 | name
53 | }
54 | value {
55 | id
56 | name
57 | }
58 | }
59 | product {
60 | id
61 | name
62 | imageUrl
63 | unavailableOptionsValues {
64 | id
65 | name
66 | }
67 | brand {
68 | id
69 | name
70 | }
71 | }
72 | }
73 | }
74 | }
75 | `;
76 |
77 | export default {
78 | userOrders,
79 | addOrderToCart,
80 | };
81 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/password/PasswordContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 | import query from './query.gql';
3 | import Password from './Password';
4 |
5 | export default compose(
6 | graphql(query.changePasswordMutation, {
7 | props: ({ mutate }) => ({
8 | changePassword: ({ oldPassword, newPassword }) =>
9 | mutate({
10 | variables: { oldPassword, newPassword },
11 | }),
12 | }),
13 | }),
14 | withApollo,
15 | )(Password);
16 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/password/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | const changePasswordMutation = gql`
4 | mutation changePassword($oldPassword: String!, $newPassword: String!) {
5 | changePassword(oldPassword: $oldPassword, newPassword: $newPassword) {
6 | id
7 | }
8 | }
9 | `;
10 |
11 | export default {
12 | changePasswordMutation,
13 | };
14 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/shops/ShopsContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 |
3 | import Shops from './Shops';
4 |
5 | import queries from './queries.gql';
6 |
7 | export default compose(
8 | graphql(queries.allShopsQuery),
9 | graphql(queries.updateSelectedShopMutation, {
10 | props: ({ mutate }) => ({
11 | updateSelectedShop: ({ selectedShopId }) =>
12 | mutate({
13 | variables: { selectedShopId },
14 | refetchQueries: [
15 | 'dataForBrowsing',
16 | 'filersForCategory'
17 | ],
18 | }),
19 | }),
20 | }),
21 | withApollo
22 | )(Shops);
23 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/shops/queries.gql.js:
--------------------------------------------------------------------------------
1 | import { gql } from 'apollo-client-preset';
2 | import ProductCardFragment from '../../../../graphql/fragments/ProductCardFragment'
3 |
4 | const allShopsQuery = gql`
5 | query shops {
6 | allShops {
7 | id
8 | name
9 | address
10 | zipCode
11 | city
12 | phoneNumber
13 | openingHours
14 | }
15 | me {
16 | id
17 | selectedShop {
18 | id
19 | name
20 | address
21 | zipCode
22 | city
23 | phoneNumber
24 | openingHours
25 | }
26 | }
27 | }
28 | `;
29 |
30 | const updateSelectedShopMutation = gql`
31 | mutation updateSelectedShop($selectedShopId: ID!) {
32 | updateUser(selectedShopId: $selectedShopId) {
33 | id
34 | firstName
35 | cart {
36 | id
37 | deletedAt
38 | quantity
39 | variant {
40 | id
41 | available
42 | price
43 | selectedOptions {
44 | id
45 | option {
46 | id
47 | name
48 | }
49 | value {
50 | id
51 | name
52 | }
53 | }
54 | product {
55 | ...ProductCardFragment
56 | }
57 | }
58 | }
59 | orders(first: 1, orderBy: createdAt_DESC) {
60 | id
61 | createdAt
62 | lineItems {
63 | id
64 | quantity
65 | variant {
66 | id
67 | price
68 | product {
69 | id
70 | name
71 | }
72 | }
73 | }
74 | totalPrice
75 | }
76 | selectedShop {
77 | id
78 | MOTD
79 | bestSellerProducts(orderBy: position_ASC, first: 5) {
80 | id
81 | product {
82 | ...ProductCardFragment
83 | }
84 | }
85 | newProducts(orderBy: position_ASC, first: 5) {
86 | id
87 | product {
88 | ...ProductCardFragment
89 | }
90 | }
91 | }
92 | }
93 | }
94 | ${ProductCardFragment}
95 | `;
96 |
97 | export default {
98 | allShopsQuery,
99 | updateSelectedShopMutation
100 | }
101 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/terms/Terms.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 |
4 | import Container from '../../../../components/layout/Container';
5 | import { translate } from '../../../../i18n';
6 |
7 | class Terms extends React.PureComponent {
8 | render() {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 | }
16 |
17 | export default Terms;
18 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/terms/TermsContainer.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/src/views/profile/views/terms/TermsContainer.js
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/terms/query.gql.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weakky/prisma-ecommerce/d9dfaa19995004efc06c7b14c3dc20cbb4454cb5/mobile/src/views/profile/views/terms/query.gql.js
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/username/UsernameContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 | import queries from './queries.gql';
3 | import Username from './Username';
4 |
5 | export default compose(
6 | graphql(queries.userIdentityQuery, { options: { fetchPolicy: 'cache-and-network' } }),
7 | graphql(queries.updateUserIdentityMutation, {
8 | props: ({ mutate }) => ({
9 | updateUserIdentity: ({ firstName, lastName }) =>
10 | mutate({
11 | variables: { firstName, lastName },
12 | }),
13 | }),
14 | }),
15 | withApollo,
16 | )(Username);
17 |
--------------------------------------------------------------------------------
/mobile/src/views/profile/views/username/queries.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | const userIdentityQuery = gql`
4 | query userIdentity {
5 | me {
6 | id
7 | firstName
8 | lastName
9 | }
10 | }
11 | `;
12 |
13 | const updateUserIdentityMutation = gql`
14 | mutation updateUser($firstName: String!, $lastName: String!) {
15 | updateUser(firstName: $firstName, lastName: $lastName) {
16 | id
17 | firstName
18 | lastName
19 | }
20 | }
21 | `;
22 |
23 | export default {
24 | userIdentityQuery,
25 | updateUserIdentityMutation,
26 | };
27 |
--------------------------------------------------------------------------------
/mobile/src/views/recap/Recap.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Dimensions, StatusBar, Platform } from 'react-native';
2 | import Colors from '../../statics/colors';
3 |
4 | const styles = StyleSheet.create({
5 | acceptGTC: {
6 | textAlign: 'center',
7 | marginTop: 24,
8 | paddingLeft: 20,
9 | paddingRight: 20,
10 | },
11 | container: {
12 | flex: 1,
13 | backgroundColor: Colors.white,
14 | paddingTop: Platform.select({
15 | ios: 20,
16 | android: StatusBar.currentHeight,
17 | }),
18 | paddingLeft: 10,
19 | paddingRight: 10,
20 | paddingBottom: 10,
21 | },
22 | ticketContainer: {
23 | backgroundColor: 'rgba(249, 249, 249, 0.8)',
24 | borderColor: 'rgba(151, 151, 151, 0.8)',
25 | borderWidth: StyleSheet.hairlineWidth,
26 | padding: 15,
27 | },
28 | separator: {
29 | alignSelf: 'center',
30 | width: Dimensions.get('window').width * 0.85,
31 | height: StyleSheet.hairlineWidth,
32 | backgroundColor: 'rgba(151, 151, 151, 0.3)',
33 | marginBottom: 8,
34 | marginTop: 8,
35 | },
36 | recapRowContainer: {
37 | flexDirection: 'row',
38 | justifyContent: 'space-between',
39 | marginBottom: 4,
40 | marginTop: 4,
41 | },
42 | });
43 |
44 | export default styles;
45 |
--------------------------------------------------------------------------------
/mobile/src/views/recap/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default gql`
4 | mutation checkout {
5 | checkout {
6 | id
7 | orderStatus
8 | totalPrice
9 | totalRefunded
10 | totalTax
11 | }
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/mobile/src/views/search/Search.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, FlatList, Keyboard } from 'react-native';
3 |
4 | import Card from '../../components/card/Card';
5 | import Title from '../../components/title/Title';
6 | import ModalNavigationBar from '../../components/modal-navigation-bar/ModalNavigationBar';
7 | import { EnhancedSearch } from './SearchHOC';
8 |
9 | import { translate } from '../../i18n';
10 |
11 | import Input from '../../components/input/Input';
12 | import Colors from '../../statics/colors/index';
13 | import styles from './Search.styles';
14 |
15 | const Search = props => (
16 |
17 | Keyboard.dismiss() || props.navigation.goBack()}
21 | />
22 |
33 |
34 | {props.products.length > 0 && (
35 |
36 | {translate('your_results')}
37 |
38 | )}
39 |
40 | item.node.id}
43 | keyboardShouldPersistTaps="always"
44 | keyboardDismissMode="on-drag"
45 | renderItem={({ item: { node: product } }) => (
46 |
47 |
49 | props.navigation.navigate('Product', {
50 | productId: product.id,
51 | unavailableOptionsValues: product.unavailableOptionsValues,
52 | })
53 | }
54 | loading={false}
55 | brand={product.brand.name}
56 | name={product.name}
57 | source={{ uri: product.imageUrl }}
58 | price={product.displayPrice}
59 | unavailableOptionsValues={product.unavailableOptionsValues}
60 | />
61 |
62 | )}
63 | />
64 |
65 | );
66 |
67 | Search.propTypes = {};
68 | Search.defaultProps = {};
69 |
70 | export default EnhancedSearch(Search);
71 |
--------------------------------------------------------------------------------
/mobile/src/views/search/Search.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import Colors from '../../statics/colors';
3 |
4 | const styles = StyleSheet.create({
5 | container: {
6 | flex: 1,
7 | backgroundColor: Colors.white,
8 | padding: 20,
9 | },
10 | searchInput: {
11 | color: 'black',
12 | marginBottom: 15,
13 | height: 50,
14 | fontSize: 22,
15 | fontWeight: 'bold',
16 | borderBottomColor: Colors.grey,
17 | },
18 | });
19 |
20 | export default styles;
21 |
--------------------------------------------------------------------------------
/mobile/src/views/search/SearchBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TouchableOpacity, View } from 'react-native';
3 | import Ionicons from 'react-native-vector-icons/Ionicons';
4 |
5 | import font from '../../assets/fonts';
6 | import Colors from '../../statics/colors/index';
7 |
8 | import Title from '../../components/title/Title';
9 | import { translate } from '../../i18n';
10 |
11 | const SearchBox = props => (
12 | props.navigation.navigate('Search')}
15 | >
16 |
17 |
18 |
25 | {translate('know_what_you_want')}
26 |
27 |
28 |
29 | );
30 |
31 | export default SearchBox;
32 |
--------------------------------------------------------------------------------
/mobile/src/views/search/SearchHOC.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import query from './query.gql';
3 | import { withApollo } from 'react-apollo';
4 |
5 | export const EnhancedSearch = WrappedComponent =>
6 | withApollo(
7 | class SearchContainer extends PureComponent {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | products: [],
13 | queryText: '',
14 | };
15 |
16 | this.performSearch = this.performSearch.bind(this);
17 | this.resetSearch = this.resetSearch.bind(this);
18 | }
19 |
20 | resetSearch() {
21 | this.setState({
22 | products: [],
23 | queryText: '',
24 | });
25 | }
26 |
27 | async performSearch(queryText) {
28 | if (!queryText) {
29 | return this.resetSearch();
30 | }
31 |
32 | this.setState({ queryText }, async () => {
33 | const { data } = await this.props.client.query({
34 | query,
35 | variables: { query: queryText },
36 | });
37 |
38 | this.setState({ products: data.searchProducts.edges });
39 | });
40 | }
41 |
42 | render() {
43 | return (
44 |
51 | );
52 | }
53 | },
54 | );
55 |
--------------------------------------------------------------------------------
/mobile/src/views/search/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default gql`
4 | query searchProduct($query: String!) {
5 | searchProducts(productName: $query, first: 6, skip: 0) {
6 | edges {
7 | node {
8 | id
9 | name
10 | imageUrl
11 | displayPrice
12 | brand {
13 | id
14 | name
15 | }
16 | unavailableOptionsValues {
17 | id
18 | name
19 | }
20 | }
21 | }
22 | }
23 | }
24 | `;
25 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-in/SignIn.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | container: {
5 | flex: 1,
6 | },
7 | nextButton: {
8 | alignSelf: 'flex-end',
9 | marginBottom: 15,
10 | marginRight: 15,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-in/SignInContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose, withApollo } from 'react-apollo';
2 |
3 | import SignIn from './SignIn';
4 | import queries from './queries.gql';
5 |
6 | export default compose(
7 | withApollo,
8 | graphql(queries.authenticateUser, {
9 | props: ({ mutate }) => ({
10 | authenticateUser: ({ email, password }) =>
11 | mutate({
12 | variables: { email, password },
13 | }),
14 | }),
15 | }),
16 | )(SignIn);
17 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-in/queries.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default {
4 | authenticateUser: gql`
5 | mutation authenticatedUser($email: String!, $password: String!) {
6 | login(email: $email, password: $password) {
7 | token
8 | }
9 | }
10 | `,
11 | };
12 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/SignUp.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { StatusBar } from 'react-native';
3 |
4 | import FirstStep from './steps/FirstStep';
5 | import SecondStep from './steps/SecondStep';
6 | import ThirdStep from './steps/ThirdStep';
7 | import FourthStep from './steps/FourthStep';
8 | import FinalStep from './steps/FinalStep';
9 |
10 | import KeyboardAwareCenteredView from '../../components/layout/KeyboardAwareCenteredView';
11 |
12 | class SignUp extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {
17 | step: 1,
18 | firstName: '',
19 | lastName: '',
20 | isMajor: false,
21 | selectedShopId: '',
22 | email: '',
23 | password: '',
24 | };
25 |
26 | this.previousStep = this.previousStep.bind(this);
27 | this.nextStep = this.nextStep.bind(this);
28 | }
29 |
30 | nextStep(data) {
31 | this.setState({
32 | step: this.state.step + 1,
33 | ...data,
34 | });
35 | }
36 |
37 | previousStep() {
38 | this.setState({ step: this.state.step - 1 });
39 | }
40 |
41 | renderCurrentStep() {
42 | switch (this.state.step) {
43 | case 1:
44 | return (
45 |
50 | );
51 | case 2:
52 | return ;
53 | case 3:
54 | return ;
55 | case 4:
56 | return ;
57 | case 5:
58 | return ;
59 | }
60 | }
61 |
62 | render() {
63 | return (
64 |
65 | {this.renderCurrentStep()}
66 |
67 | );
68 | }
69 | }
70 |
71 | SignUp.propTypes = {};
72 | SignUp.defaultProps = {};
73 |
74 | export default SignUp;
75 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/SignUp.styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create({
4 | nextButton: {
5 | alignSelf: 'flex-end',
6 | marginBottom: 15,
7 | marginRight: 15,
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/SignUpContainer.js:
--------------------------------------------------------------------------------
1 | import { graphql, compose } from 'react-apollo';
2 | import queries from './query.gql';
3 | import SignUp from './SignUp';
4 |
5 | //TODO: Faster mutation by invalidating cache instead of using refetchQueries
6 | export default compose(
7 | graphql(queries.signUp, {
8 | props: ({ mutate }) => ({
9 | signUp: ({ email, password, firstName, lastName, shopId }) =>
10 | mutate({
11 | variables: { email, password, firstName, lastName, shopId },
12 | }),
13 | }),
14 | }),
15 | )(SignUp);
16 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/query.gql.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag';
2 |
3 | export default {
4 | signUp: gql`
5 | mutation signUp($email: String!, $password: String!, $firstName: String!, $lastName: String!, $shopId: ID!) {
6 | signup(email: $email, password: $password, firstName: $firstName, lastName: $lastName, shopId: $shopId) {
7 | token
8 | }
9 | }
10 | `
11 | }
12 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/steps/FinalStep.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { StyleSheet, ActivityIndicator, View, StatusBar, AsyncStorage } from 'react-native';
4 | import Title from '../../../components/title/Title';
5 |
6 | import Colors from '../../../statics/colors';
7 | import { translate } from '../../../i18n';
8 | import StorageKeys from '../../../statics/storage-keys';
9 |
10 | class FinalStep extends React.PureComponent {
11 |
12 | async componentWillMount() {
13 | const { data } = await this.props.signUp({
14 | email: this.props.email,
15 | password: this.props.password,
16 | firstName: this.props.firstName,
17 | lastName: this.props.lastName,
18 | shopId: this.props.selectedShopId
19 | });
20 |
21 | await AsyncStorage.setItem(StorageKeys.GC_TOKEN, data.signup.token);
22 |
23 | StatusBar.setBarStyle('dark-content');
24 | this.props.navigation.navigate('App');
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
31 | {translate('thanks_trust')}
32 |
33 |
34 |
35 | {translate('gathering_articles')}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | const styles = StyleSheet.create({
43 | container: {
44 | flex: 1,
45 | alignItems: 'center',
46 | },
47 | });
48 |
49 | export default FinalStep;
50 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/steps/FirstStep.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 |
4 | import Colors from '../../../statics/colors';
5 |
6 | import Input from '../../../components/input/Input';
7 | import Title from '../../../components/title/Title';
8 | import NavigationButton from '../../../components/navigation-button/NavigationButton';
9 | import { translate } from '../../../i18n';
10 | import styles from '../SignUp.styles';
11 |
12 | class FirstStep extends React.PureComponent {
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {
17 | firstName: '',
18 | lastName: '',
19 | };
20 | }
21 |
22 | focusNextField(nextField) {
23 | this.refs[nextField].focus();
24 | }
25 |
26 | validateFields() {
27 | return !!this.state.firstName && !!this.state.lastName;
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
34 | this.props.navigation.goBack()} back />
35 |
40 | {translate('whats_your_name')}
41 |
42 | this.setState({ firstName })}
48 | value={this.state.firstName}
49 | onSubmit={firstName => firstName && this.focusNextField('1')}
50 | />
51 | this.setState({ lastName: lastName.toUpperCase() })}
57 | value={this.state.lastName}
58 | onSubmit={() => this.props.nextStep(this.state)}
59 | />
60 |
61 |
62 | this.validateFields() && this.props.nextStep(this.state)}
65 | />
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | export default FirstStep;
73 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/steps/FourthStep.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import Colors from '../../../statics/colors';
3 | import { View } from 'react-native';
4 |
5 | import Title from '../../../components/title/Title';
6 | import Input from '../../../components/input/Input';
7 | import NavigationButton from '../../../components/navigation-button/NavigationButton';
8 | import styles from '../SignUp.styles';
9 | import { translate } from '../../../i18n';
10 |
11 | class FourthStep extends PureComponent {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.state = {
16 | email: '',
17 | password: '',
18 | };
19 | }
20 |
21 | validateFields() {
22 | return !!this.state.email && !!this.state.password;
23 | }
24 |
25 | focusNextField(nextField) {
26 | this.refs[nextField].focus();
27 | }
28 |
29 | render() {
30 | return (
31 |
32 |
33 | this.props.previousStep()} back />
34 |
39 | {translate('finish_sign_up')} !
40 |
41 | this.setState({ email })}
46 | value={this.state.email}
47 | onSubmit={() => this.focusNextField('1')}
48 | />
49 | this.setState({ password })}
56 | value={this.state.password}
57 | onSubmit={() => this.props.nextStep(this.state)}
58 | />
59 |
60 |
61 | this.validateFields() && this.props.nextStep(this.state)}
64 | />
65 |
66 |
67 | );
68 | }
69 | }
70 |
71 | FourthStep.propTypes = {};
72 | FourthStep.defaultProps = {};
73 |
74 | export default FourthStep;
75 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/steps/SecondStep.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { View } from 'react-native';
3 |
4 | import Colors from '../../../statics/colors';
5 |
6 | import Title from '../../../components/title/Title';
7 | import Button from '../../../components/button/Button';
8 | import NavigationButton from '../../../components/navigation-button/NavigationButton';
9 | import { translate } from '../../../i18n';
10 |
11 | const lawSubtitle = {
12 | firstPart: "Selon l'article",
13 | secondPart: 'L351-2-1',
14 | thirdPart:
15 | ', il est interdit de vendre\n' +
16 | 'des dispositifs électroniques de vapotage ou des\n' +
17 | 'flacons de recharges à des mineurs.',
18 | };
19 |
20 | //TODO: Handle 'No' case
21 | class SecondStep extends PureComponent {
22 | render() {
23 | return (
24 |
25 | this.props.previousStep()} back />
26 |
27 | {translate('are_you_major')}
28 |
29 |
30 | {lawSubtitle.firstPart}
31 | {lawSubtitle.secondPart}
32 | {lawSubtitle.thirdPart}
33 |
34 | this.props.nextStep({ isMajor: true })}
37 | label={translate('yes')}
38 | backgroundColor={Colors.white}
39 | labelColor={Colors.red}
40 | fontSize={14}
41 | />
42 | {}} label={translate('cancel_sign_up')} fontSize={14} />
43 |
44 | );
45 | }
46 | }
47 |
48 | SecondStep.propTypes = {};
49 | SecondStep.defaultProps = {};
50 |
51 | export default SecondStep;
52 |
--------------------------------------------------------------------------------
/mobile/src/views/sign-up/steps/ThirdStep.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import gql from 'graphql-tag';
3 | import { graphql } from 'react-apollo';
4 | import Banner from '../../../components/banner/Banner';
5 | import { View } from 'react-native';
6 |
7 | import Colors from '../../../statics/colors';
8 | import Title from '../../../components/title/Title';
9 | import Button from '../../../components/button/Button';
10 | import NavigationButton from '../../../components/navigation-button/NavigationButton';
11 | import { translate } from '../../../i18n';
12 |
13 | class ThirdStep extends PureComponent {
14 | constructor(props) {
15 | super(props);
16 |
17 | this.state = {
18 | selectedShopId: null,
19 | };
20 |
21 | this.onBannerSelected = this.onBannerSelected.bind(this);
22 | }
23 |
24 | onBannerSelected({ shopId }) {
25 | this.setState({ selectedShopId: shopId });
26 | }
27 |
28 | render() {
29 | if (this.props.data.loading) {
30 | return null;
31 | }
32 |
33 | return (
34 |
35 |
36 | this.props.previousStep()} back />
37 |
42 | {translate('choose_your_store')}
43 |
44 |
45 | {translate('change_your_store_later')}
46 |
47 |
48 | {
49 | this.props.data.allShops.map(shop => (
50 |
57 | ))
58 | }
59 | {this.state.selectedShopId && (
60 | this.props.nextStep(this.state)}
64 | backgroundColor={Colors.white}
65 | labelColor={Colors.red}
66 | />
67 | )}
68 |
69 | );
70 | }
71 | }
72 |
73 | ThirdStep.propTypes = {};
74 | ThirdStep.defaultProps = {};
75 |
76 | export default graphql(gql`{
77 | allShops {
78 | id
79 | name
80 | address
81 | zipCode
82 | city
83 | phoneNumber
84 | openingHours
85 | }
86 | }`)(ThirdStep);
87 |
--------------------------------------------------------------------------------
/mobile/storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 | import '@storybook/addon-knobs/register';
--------------------------------------------------------------------------------
/mobile/storybook/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { AppRegistry, View, StyleSheet } from 'react-native';
3 | import { getStorybookUI, configure, addDecorator } from '@storybook/react-native';
4 | import { withKnobs } from '@storybook/addon-knobs';
5 |
6 | const styles = StyleSheet.create({
7 | decorator: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#CC6155' }
8 | });
9 |
10 | addDecorator(withKnobs);
11 | addDecorator(story => (
12 | {story()}
13 | ));
14 |
15 | function loadStories() {
16 | require('../src/components/title/Title.stories');
17 | require('../src/components/card/Card.stories');
18 | require('../src/components/input/Input.stories');
19 | require('../src/components/button/Button.stories');
20 | require('../src/components/banner/Banner.stories');
21 | require('../src/components/gradient/Gradient.stories');
22 | require('../src/components/navigation-button/NavigationButton.stories');
23 | }
24 |
25 | configure(loadStories, module);
26 |
27 | const StorybookUI = getStorybookUI({ port: 7007, onDeviceUI: true });
28 | AppRegistry.registerComponent('Aromaclop', () => StorybookUI);
29 |
30 | export default StorybookUI;
--------------------------------------------------------------------------------
/prisma/.env:
--------------------------------------------------------------------------------
1 | PRISMA_ENDPOINT="http://localhost:4466/prisma-ecommerce/dev"
2 | PRISMA_MANAGEMENT_API_SECRET="my-server-secret-123"
3 | PRISMA_SECRET="my-server-secret-123"
4 | APP_SECRET="jwtsecret123"
5 |
6 | # Stripe config
7 | STRIPE_WEBHOOK_SECRET=""
8 | STRIPE_SECRET_KEY=""
9 |
10 | # Node mailer config
11 |
12 | SMTP_HOST=""
13 | SMTP_PORT=""
14 | SMTP_USER=""
15 | SMTP_PASSWORD=""
16 | EMAIL_FROM=""
17 |
18 |
--------------------------------------------------------------------------------
/prisma/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | package-lock.json
3 | node_modules
4 | .idea
5 | .vscode
6 | *.log
7 | .env*
8 |
--------------------------------------------------------------------------------
/prisma/.graphqlconfig.yml:
--------------------------------------------------------------------------------
1 | projects:
2 | aromaclop:
3 | schemaPath: "src/schema.graphql"
4 | includes: ["**/*.graphql"]
5 | extensions:
6 | endpoints:
7 | dev: "http://localhost:4000"
8 | database:
9 | schemaPath: "src/generated/prisma.graphql"
10 | includes: ["**/*.graphql"]
11 | extensions:
12 | prisma: database/prisma.yml
13 | prepare-binding:
14 | output: src/generated/prisma.ts
15 | generator: prisma-ts
16 |
17 |
--------------------------------------------------------------------------------
/prisma/.import/lists/000001.json:
--------------------------------------------------------------------------------
1 | {"valueType":"lists","values":[]}
--------------------------------------------------------------------------------
/prisma/.import/relations/000001.json:
--------------------------------------------------------------------------------
1 | {"valueType":"relations","values":[[{"_typeName":"Variant","id":"cjgfks9jq003w09469q6x38ip","fieldName":"value"},{"_typeName":"OptionValue","id":"cjgfkl0ie003g09464njkeems"}],[{"_typeName":"Variant","id":"cjgmt1l92004b0938bo21qahe","fieldName":"value"},{"_typeName":"OptionValue","id":"cjgfkl0ie003g09464njkeems"}],[{"_typeName":"Variant","id":"cjgfks9jq003w09469q6x38ip","fieldName":"option"},{"_typeName":"Option","id":"cjgfkl0ie003f0946gonpbgcj"}],[{"_typeName":"Variant","id":"cjgmt1l92004b0938bo21qahe","fieldName":"option"},{"_typeName":"Option","id":"cjgfkl0ie003f0946gonpbgcj"}],[{"_typeName":"OptionValue","id":"cjgfkl0ie003g09464njkeems","fieldName":"parent"},{"_typeName":"Option","id":"cjgfkl0ie003f0946gonpbgcj","fieldName":"children"}],[{"_typeName":"OptionValue","id":"cjgfkl0ie003h0946muxazh9u","fieldName":"parent"},{"_typeName":"Option","id":"cjgfkl0ie003f0946gonpbgcj","fieldName":"children"}],[{"_typeName":"OptionValue","id":"cjgfkl0ie003i09463i05zmje","fieldName":"parent"},{"_typeName":"Option","id":"cjgfkl0ie003f0946gonpbgcj","fieldName":"children"}],[{"_typeName":"OptionValue","id":"cjgfkl0ie003j0946paxuycco","fieldName":"parent"},{"_typeName":"Option","id":"cjgfkl0ie003f0946gonpbgcj","fieldName":"children"}],[{"_typeName":"Product","id":"cjgfks9jq003v0946wmgoh5ac","fieldName":"brand"},{"_typeName":"Brand","id":"cjgfkl0h600330946utfpnu1m"}],[{"_typeName":"Product","id":"cjgmsvg4d00390938tth01ro9","fieldName":"brand"},{"_typeName":"Brand","id":"cjgfkl0h600330946utfpnu1m"}],[{"_typeName":"Product","id":"cjgmt1l8x004a0938zmis8ls2","fieldName":"brand"},{"_typeName":"Brand","id":"cjgfkl0h600330946utfpnu1m"}],[{"_typeName":"Product","id":"cjgfks9jq003v0946wmgoh5ac","fieldName":"categories"},{"_typeName":"Category","id":"cjgfkl0hl003709469x2hvzk7"}],[{"_typeName":"Variant","id":"cjgfks9jq003w09469q6x38ip"},{"_typeName":"Product","id":"cjgfks9jq003v0946wmgoh5ac","fieldName":"variants"}],[{"_typeName":"Variant","id":"cjgmt1l92004b0938bo21qahe"},{"_typeName":"Product","id":"cjgmt1l8x004a0938zmis8ls2","fieldName":"variants"}]]}
--------------------------------------------------------------------------------
/prisma/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": true,
4 | "trailingComma": "all",
5 | "arrowParens": "avoid",
6 | "jsxBracketSameLine": false,
7 | "tabWidth": 2,
8 | "printWidth": 90,
9 | "bracketSpacing": true,
10 | "parser": "typescript"
11 | }
--------------------------------------------------------------------------------
/prisma/database/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | prisma:
4 | image: prismagraphql/prisma:1.7
5 | restart: always
6 | ports:
7 | - "4466:4466"
8 | environment:
9 | PRISMA_CONFIG: |
10 | port: 4466
11 | # uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
12 | # managementApiSecret: my-secret
13 |
--------------------------------------------------------------------------------
/prisma/database/prisma.yml:
--------------------------------------------------------------------------------
1 | # the endpoint of your service (this also defines the cluster the service will be deployed to)
2 | endpoint: ${env:PRISMA_ENDPOINT}
3 |
4 | #secret: ${env:PRISMA_SECRET}
5 | disableAuth: true
6 |
7 | # the file path pointing to your data model
8 | datamodel: datamodel.graphql
9 |
10 | # seed your service with initial data based on seed.graphql
11 | seed:
12 | import: seed.zip
13 |
14 | hooks:
15 | post-deploy:
16 | - echo "Deployment finished"
17 | - graphql get-schema --project database
18 | - graphql prepare
19 |
--------------------------------------------------------------------------------
/prisma/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | prisma:
4 | image: prismagraphql/prisma:1.7
5 | restart: always
6 | ports:
7 | - "4466:4466"
8 | environment:
9 | PRISMA_CONFIG: |
10 | managementApiSecret: my-server-secret-123
11 | port: 4466
12 | databases:
13 | default:
14 | connector: mysql # or `postgres`
15 | active: true
16 | host: db
17 | port: 3306 # or `5432` for `postgres`
18 | user: root
19 | password: prisma
20 | db:
21 | image: mysql:5.7
22 | restart: always
23 | ports:
24 | - 4306:3306
25 | environment:
26 | MYSQL_USER: root
27 | MYSQL_ROOT_PASSWORD: prisma
--------------------------------------------------------------------------------
/prisma/graphql.config.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "README_schema" : "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE",
4 | "schema": {
5 |
6 | "README_file" : "Remove 'file' to use request url below. A relative or absolute path to the JSON from a schema introspection query, e.g. '{ data: ... }' or a .graphql/.graphqls file describing the schema using GraphQL Schema Language. Changes to the file are watched.",
7 | "file": "graphql.schema.json",
8 |
9 | "README_request" : "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).",
10 | "request": {
11 | "url" : "http://--graphql-server--/path-to-schema-json-or-introspection-endpoint",
12 | "method" : "POST",
13 | "README_postIntrospectionQuery" : "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET",
14 | "postIntrospectionQuery" : true,
15 | "README_options" : "See the 'Options' section at https://github.com/then/then-request",
16 | "options" : {
17 | "headers": {
18 | "user-agent" : "JS GraphQL"
19 | }
20 | }
21 | }
22 |
23 | },
24 |
25 | "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE",
26 | "endpoints" : [
27 | {
28 | "name": "Default (http://localhost:4000/)",
29 | "url": "http://localhost:4000",
30 | "options" : {
31 | "headers": {
32 | "user-agent" : "JS GraphQL"
33 | }
34 | }
35 | }
36 | ]
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/prisma/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aromaclop-prisma",
3 | "scripts": {
4 | "start": "dotenv -- nodemon -e ts,graphql -x ts-node src/index.ts",
5 | "dev": "npm-run-all --parallel start playground",
6 | "debug": "dotenv -- nodemon -e ts,graphql -x ts-node --inspect src/index.ts",
7 | "playground": "graphql playground",
8 | "build": "rimraf dist && tsc",
9 | "deploy": "scripts/deploy.sh",
10 | "backup": "scripts/backup.sh"
11 | },
12 | "dependencies": {
13 | "apollo-errors": "^1.9.0",
14 | "bcryptjs": "2.4.3",
15 | "bluebird": "^3.5.1",
16 | "email-templates": "^4.0.1",
17 | "generate-password": "^1.4.0",
18 | "graphql-yoga": "1.9.2",
19 | "jsonwebtoken": "8.2.1",
20 | "lodash": "^4.17.10",
21 | "nodemailer": "^4.6.7",
22 | "onesignal-node": "^1.1.2",
23 | "prisma-binding": "1.5.17",
24 | "stripe": "^6.0.0",
25 | "validator": "^10.4.0"
26 | },
27 | "devDependencies": {
28 | "@types/bcryptjs": "2.4.1",
29 | "@types/email-templates": "^3.5.0",
30 | "@types/lodash": "^4.14.108",
31 | "@types/nodemailer": "^4.6.2",
32 | "@types/stripe": "^5.0.10",
33 | "dotenv-cli": "1.4.0",
34 | "graphql-cli": "2.15.9",
35 | "nodemon": "1.17.3",
36 | "npm-run-all": "4.1.2",
37 | "prisma": "1.7.4",
38 | "rimraf": "2.6.2",
39 | "ts-node": "4.1.0",
40 | "typescript": "2.8.3"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/prisma/scripts/backup.sh:
--------------------------------------------------------------------------------
1 | prisma export -p database/seed.zip
2 |
--------------------------------------------------------------------------------
/prisma/scripts/deploy.sh:
--------------------------------------------------------------------------------
1 | prisma export -p database/seed.zip
2 | prisma deploy
--------------------------------------------------------------------------------
/prisma/src/emails/passwordReset/html.pug:
--------------------------------------------------------------------------------
1 | p Hi,
2 | p You requested a new password on Aromaclop.
3 | p Here's your new password: #{newPassword}
4 | p Please, go immediately to your account section, and change it to a personal one.
--------------------------------------------------------------------------------
/prisma/src/emails/passwordReset/subject.pug:
--------------------------------------------------------------------------------
1 | = `Here's your new password on Aromaclop`
2 |
--------------------------------------------------------------------------------
/prisma/src/exceptions/index.ts:
--------------------------------------------------------------------------------
1 | import { createError } from 'apollo-errors';
2 |
3 | const exceptions = {
4 | orderLineItemNotFound: {
5 | message: "Line item doesn't exist anymore.",
6 | code: 100,
7 | },
8 | productNotFound: {
9 | message: "Product doesn't exist anymore.",
10 | code: 101,
11 | },
12 | orderNotFound: {
13 | message: "Order doesn't exist.",
14 | code: 102,
15 | },
16 | productOrVariantNotFound: {
17 | message: "A product or a variant of your cart doesn't exist anymore",
18 | code: 103,
19 | },
20 | orderNotSentToCurrentShop: {
21 | message: "The order wasn't sent to your configured shop",
22 | code: 104,
23 | },
24 | invalidEmail: {
25 | message: 'Invalid email',
26 | code: 200,
27 | },
28 | invalidPassword: {
29 | message: 'Invalid password.',
30 | code: 201,
31 | },
32 | invalidOldPassword: {
33 | message: 'Invalid old password.',
34 | code: 202,
35 | },
36 | };
37 |
38 | export const OrderLineItemNotFoundException = createError('OrderLineItemNotFound', {
39 | message: exceptions.orderLineItemNotFound.message,
40 | data: { code: exceptions.orderLineItemNotFound.code },
41 | });
42 |
43 | export const ProductNotFoundException = createError('ProductNotFound', {
44 | message: exceptions.productNotFound.message,
45 | data: { code: exceptions.productNotFound.code },
46 | });
47 |
48 | export const OrderNotFoundException = createError('OrderNotFound', {
49 | message: exceptions.orderNotFound.message,
50 | data: { code: exceptions.orderNotFound.code },
51 | });
52 |
53 | export const ProductOrVariantNotFoundException = (deletedVariants, deletedProducts) =>
54 | createError('ProductOrVariantNotFound', {
55 | message: exceptions.productOrVariantNotFound.message,
56 | data: {
57 | code: exceptions.productOrVariantNotFound.code,
58 | deletedVariants,
59 | deletedProducts,
60 | },
61 | });
62 |
63 | export const OrderNotSentToCurrentShopException = createError(
64 | 'OrderNotSentToCurrentShop',
65 | {
66 | message: exceptions.orderNotSentToCurrentShop.message,
67 | data: { code: exceptions.orderNotSentToCurrentShop.code },
68 | },
69 | );
70 |
71 | export const InvalidEmailException = createError('InvalidEmail', {
72 | message: exceptions.invalidEmail.message,
73 | data: { code: exceptions.invalidEmail.code },
74 | });
75 |
76 | export const InvalidPasswordException = createError('InvalidPassword', {
77 | message: exceptions.invalidPassword.message,
78 | data: { code: exceptions.invalidPassword.code },
79 | });
80 |
81 | export const InvalidOldPasswordException = createError('InvalidOldPassword', {
82 | message: exceptions.invalidOldPassword.message,
83 | data: { code: exceptions.invalidOldPassword.code },
84 | });
85 |
--------------------------------------------------------------------------------
/prisma/src/resolvers/AuthPayload.ts:
--------------------------------------------------------------------------------
1 | import { Context } from '../utils'
2 |
3 | export const AuthPayload = {
4 | user: async ({ user: { id } }, args, ctx: Context, info) => {
5 | return ctx.db.query.user({ where: { id } }, info)
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/prisma/src/resolvers/Mutation/attribute.ts:
--------------------------------------------------------------------------------
1 | import { Context, getShopId } from '../../utils';
2 |
3 | export const attribute = {
4 | async upsertAttribute(parent, args, ctx: Context, info) {
5 | if (args.attributeId) {
6 | return ctx.db.mutation.updateAttribute({
7 | where: { id: args.attributeId },
8 | data: { value: args.value, category: { connect: { id: args.categoryId } } }
9 | }, info);
10 | }
11 |
12 | const shopId = await getShopId(ctx);
13 |
14 | return ctx.db.mutation.createAttribute({
15 | data: {
16 | value: args.value,
17 | shop: { connect: { id: shopId } },
18 | category: { connect: { id: args.categoryId } }
19 | },
20 | }, info);
21 | },
22 |
23 | deleteAttribute(parent, args, ctx: Context, info) {
24 | return ctx.db.mutation.deleteAttribute({ where: { id: args.attributeId } });
25 | },
26 | };
--------------------------------------------------------------------------------
/prisma/src/resolvers/Mutation/brand.ts:
--------------------------------------------------------------------------------
1 | import { Context, getShopId } from "../../utils";
2 |
3 | export const brand = {
4 | async upsertBrand(parent, args, ctx: Context, info) {
5 | if (args.brandId) {
6 | return ctx.db.mutation.updateBrand({
7 | where: { id: args.brandId },
8 | data: { name: args.name, category: { connect: { id: args.categoryId } } }
9 | }, info);
10 | }
11 |
12 | const shopId = await getShopId(ctx);
13 |
14 | return ctx.db.mutation.createBrand({
15 | data: {
16 | name: args.name,
17 | shop: { connect: { id: shopId } },
18 | category: { connect: { id: args.categoryId } },
19 | },
20 | }, info);
21 | },
22 |
23 | deleteBrand(parent, args, ctx: Context, info) {
24 | return ctx.db.mutation.deleteBrand({ where: { id: args.brandId } });
25 | },
26 | }
--------------------------------------------------------------------------------
/prisma/src/resolvers/Mutation/category.ts:
--------------------------------------------------------------------------------
1 | import { Context, getShopId } from '../../utils';
2 |
3 | export const category = {
4 | async upsertCategory(parent, args, ctx: Context, info) {
5 | const shopId = await getShopId(ctx);
6 |
7 | if (args.categoryId) {
8 | return ctx.db.mutation.updateCategory({
9 | where: { id: args.categoryId },
10 | data: { name: args.name }
11 | }, info);
12 | }
13 |
14 | return ctx.db.mutation.createCategory({
15 | data: {
16 | name: args.name,
17 | shop: { connect: { id: shopId } }
18 | },
19 | }, info);
20 | },
21 |
22 | deleteCategory(parent, args, ctx: Context, info) {
23 | return ctx.db.mutation.deleteCategory({ where: { id: args.categoryId } });
24 | },
25 | };
--------------------------------------------------------------------------------
/prisma/src/resolvers/Mutation/index.ts:
--------------------------------------------------------------------------------
1 | import { auth } from './auth';
2 | import { attribute } from './attribute';
3 | import { brand } from './brand';
4 | import { cart } from './cart';
5 | import { category } from './category';
6 | import { option } from './option';
7 | import { product } from './product';
8 | import { shopMetadata } from './shopMetadata';
9 | import { payment } from './stripe';
10 | import { user } from './user';
11 | import { order } from './order';
12 |
13 | export default {
14 | ...auth,
15 | ...shopMetadata,
16 | ...attribute,
17 | ...brand,
18 | ...cart,
19 | ...category,
20 | ...option,
21 | ...product,
22 | ...payment,
23 | ...user,
24 | ...order,
25 | };
--------------------------------------------------------------------------------
/prisma/src/resolvers/Mutation/user.ts:
--------------------------------------------------------------------------------
1 | import { Context, getUserId } from '../../utils';
2 | import { UserUpdateInput } from '../../generated/prisma';
3 |
4 | export const user = {
5 | updateUser(parent, args, ctx: Context, info) {
6 | const userId = getUserId(ctx);
7 |
8 | const data: UserUpdateInput = {
9 | ...(args.firstName && { firstName: args.firstName }),
10 | ...(args.lastName && { lastName: args.lastName }),
11 | ...(args.oneSignalUserId && { oneSignalUserId: args.oneSignalUserId }),
12 | ...(args.selectedShopId && { selectedShop: { connect: { id: args.selectedShopId } } }),
13 | };
14 |
15 | return ctx.db.mutation.updateUser({
16 | where: { id: userId },
17 | data,
18 | }, info);
19 | }
20 | }
--------------------------------------------------------------------------------
/prisma/src/resolvers/Query/User.ts:
--------------------------------------------------------------------------------
1 | import { Context, getUserId, getShopIdFromUserId } from "../../utils";
2 |
3 | export const User = {
4 | // Grab only orders made to the selectedShop of the user,
5 | // So that he cannot add an order to his cart with product
6 | // That were ordered from another Shop (that can potentially not be existing)
7 | async orders(parent, _, ctx: Context, info) {
8 | const userId = getUserId(ctx);
9 | const shopId = await getShopIdFromUserId(userId, ctx);
10 | const ordersIds = parent.orders.map(order => order.id);
11 |
12 | return ctx.db.query.orders({
13 | where: {
14 | id_in: ordersIds,
15 | owner: { id: userId },
16 | receiver: { id: shopId }
17 | }
18 | }, info)
19 | },
20 |
21 | async cart(parent, _, ctx: Context, info) {
22 | const userId = getUserId(ctx);
23 | const shopId = await getShopIdFromUserId(userId, ctx);
24 | const lineItemIds = parent.cart.map(lineItem => lineItem.id);
25 |
26 | return ctx.db.query.orderLineItems({
27 | where: {
28 | id_in: lineItemIds,
29 | shop: { id: shopId },
30 | owner: { id: userId },
31 | }
32 | }, info)
33 | },
34 | }
35 |
--------------------------------------------------------------------------------
/prisma/src/resolvers/Subscription/index.ts:
--------------------------------------------------------------------------------
1 | import { Context, getUserId } from '../../utils';
2 |
3 | export const Subscription = {
4 | updatedProduct: {
5 | subscribe(parent, args, ctx: Context, info) {
6 | return ctx.db.subscription.product({ where: { mutation_in: ['UPDATED'] } }, info);
7 | },
8 | resolve(payload, args, ctx: Context, info) {
9 | return payload.updatedProduct;
10 | }
11 | },
12 | waitFor3DSecure: {
13 | subscribe: (parent, args, ctx: Context, info) => {
14 | return ctx.db.subscription.order({
15 | where: {
16 | mutation_in: ['UPDATED'],
17 | node: {
18 | id: args.orderId
19 | }
20 | }
21 | }, info);
22 | },
23 | resolve: (payload, args, context, info) => {
24 | return payload.waitFor3DSecure;
25 | },
26 | }
27 | };
--------------------------------------------------------------------------------
/prisma/src/resolvers/index.ts:
--------------------------------------------------------------------------------
1 | import { Query } from './Query/Query'
2 | import Mutations from './Mutation';
3 | import { Subscription } from './Subscription'
4 | import { AuthPayload } from './AuthPayload'
5 | import { User } from './Query/User'
6 |
7 | export default {
8 | Query,
9 | Mutation: {
10 | ...Mutations,
11 | },
12 | Subscription,
13 | AuthPayload,
14 | User,
15 | }
16 |
--------------------------------------------------------------------------------
/prisma/src/third-party/nodemailer.ts:
--------------------------------------------------------------------------------
1 | import * as nodemailer from 'nodemailer';
2 | import * as Email from 'email-templates';
3 | import * as path from 'path';
4 |
5 | const emailTransport = nodemailer.createTransport({
6 | host: process.env.SMTP_HOST,
7 | port: parseInt(process.env.SMTP_PORT),
8 | auth: {
9 | user: process.env.SMTP_USER,
10 | pass: process.env.SMTP_PASSWORD,
11 | }
12 | });
13 |
14 | export const mailer = new Email({
15 | message: {
16 | from: process.env.EMAIL_FROM,
17 | },
18 | views: {
19 | root: path.join(__dirname, '../emails'),
20 | },
21 | transport: emailTransport,
22 | send: true,
23 | });
24 |
--------------------------------------------------------------------------------
/prisma/src/third-party/oneSignal.ts:
--------------------------------------------------------------------------------
1 | import * as OneSignal from 'onesignal-node';
2 |
3 | const ONE_SIGNAL_URL = "https://onesignal.com/api/v1/notifications"
4 | const ONE_SIGNAL_APP_ID = process.env.ONE_SIGNAL_APP_ID;
5 | const ONE_SIGNAL_REST_KEY = process.env.ONE_SIGNAL_REST_KEY;
6 |
7 | const Client = new OneSignal.Client({
8 | app: { appAuthKey: ONE_SIGNAL_REST_KEY, appId: ONE_SIGNAL_APP_ID }
9 | });
10 |
11 | export function sendNotificationToOne(oneSignalUserId, title, content) {
12 | var notification = new OneSignal.Notification({
13 | contents: {
14 | en: content,
15 | }
16 | });
17 | notification.setParameter('headings', { "en": title });
18 | notification.setTargetDevices([oneSignalUserId]);
19 |
20 | return Client.sendNotification(notification);
21 | }
--------------------------------------------------------------------------------
/prisma/src/utils.ts:
--------------------------------------------------------------------------------
1 | import * as jwt from 'jsonwebtoken'
2 | import { Prisma } from './generated/prisma'
3 | import * as Email from 'email-templates';
4 | export interface Context {
5 | db: Prisma
6 | mailer: Email
7 | request: any
8 | }
9 |
10 | export function getUserId(ctx: Context) {
11 | const Authorization = ctx.request.get('Authorization')
12 | if (Authorization) {
13 | const token = Authorization.replace('Bearer ', '')
14 | const { userId } = jwt.verify(token, process.env.APP_SECRET) as { userId: string }
15 | return userId
16 | }
17 |
18 | throw new AuthError()
19 | }
20 |
21 | export function getShopIdFromUserId(userId, ctx) {
22 | return ctx.db.query.user({ where: { id: userId } }, '{ selectedShop { id } }').then(user => user.selectedShop.id);
23 | }
24 |
25 | export function getShopId(ctx: Context) {
26 | const userId = getUserId(ctx);
27 |
28 | return getShopIdFromUserId(userId, ctx);
29 | }
30 |
31 | export class AuthError extends Error {
32 | constructor() {
33 | super('Not authorized')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/prisma/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "moduleResolution": "node",
5 | "module": "commonjs",
6 | "sourceMap": true,
7 | "rootDir": "src",
8 | "outDir": "dist",
9 | "lib": [
10 | "esnext", "dom"
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------